[dev] [st][PATCH] Wide character support

From: Eon S. Jeon <esjeon_AT_hyunmu.am>
Date: Thu, 5 Sep 2013 02:31:18 -0400

Hello.

I finally decided to submit my work. I've tested this for a while, and
concluded that this is, at least, stable enough for daily use.

I tested the following cases: general output, vim, node (Node.js, no
widechar support), tmux, partial overwriting, select-snap-copy, window
resizing (down to 1x0). Not that I didn't do much torturing (e.g.
stty cbreak).

Sincerely
Eon


diff --git a/st.c b/st.c
index c751aa1..73a78f6 100644
--- a/st.c
+++ b/st.c
_AT_@ -27,6 +27,7 @@
 #include <X11/keysym.h>
 #include <X11/Xft/Xft.h>
 #include <fontconfig/fontconfig.h>
+#include <wchar.h>
 
 #include "arg.h"
 
_AT_@ -96,6 +97,8 @@ enum glyph_attribute {
         ATTR_ITALIC = 16,
         ATTR_BLINK = 32,
         ATTR_WRAP = 64,
+ ATTR_WIDE = 128,
+ ATTR_WDUMMY = 256,
 };
 
 enum cursor_movement {
_AT_@ -165,7 +168,7 @@ typedef unsigned short ushort;
 
 typedef struct {
         char c[UTF_SIZ]; /* character code */
- uchar mode; /* attribute flags */
+ ushort mode; /* attribute flags */
         ulong fg; /* foreground */
         ulong bg; /* background */
 } Glyph;
_AT_@ -730,6 +733,12 @@ selsnap(int mode, int *x, int *y, int direction) {
                                 }
                         }
 
+ /* skip dummies */
+ if(term.line[*y][*x + direction].mode & ATTR_WDUMMY) {
+ *x += direction;
+ continue;
+ }
+
                         if(strchr(worddelimiters,
                                         term.line[*y][*x + direction].c[0])) {
                                 break;
_AT_@ -943,7 +952,7 @@ selcopy(void) {
                                 /* nothing */;
 
                         for(x = 0; gp <= last; x++, ++gp) {
- if(!selected(x, y))
+ if(!selected(x, y) || (gp->mode & ATTR_WDUMMY))
                                         continue;
 
                                 size = utf8size(gp->c);
_AT_@ -1544,6 +1553,17 @@ tsetchar(char *c, Glyph *attr, int x, int y) {
                 }
         }
 
+ /* remove the whole wide character */
+ if(term.line[y][x].mode & ATTR_WIDE) {
+ if (x + 1 < term.col) {
+ term.line[y][x + 1].c[0] = ' ';
+ term.line[y][x + 1].mode &= ~ATTR_WDUMMY;
+ }
+ } else if(term.line[y][x].mode & ATTR_WDUMMY) {
+ term.line[y][x - 1].c[0] = ' ';
+ term.line[y][x - 1].mode &= ~ATTR_WIDE;
+ }
+
         term.dirty[y] = 1;
         term.line[y][x] = *attr;
         memcpy(term.line[y][x].c, c, UTF_SIZ);
_AT_@ -2233,6 +2253,15 @@ void
 tputc(char *c, int len) {
         uchar ascii = *c;
         bool control = ascii < '\x20' || ascii == 0177;
+ long u8char;
+ int width;
+
+ if(len == 1)
+ width = 1;
+ else {
+ utf8decode(c, &u8char);
+ width = wcwidth(u8char);
+ }
 
         if(iofd != -1) {
                 if(xwrite(iofd, c, len) < 0) {
_AT_@ -2480,9 +2509,21 @@ tputc(char *c, int len) {
                         (term.col - term.c.x - 1) * sizeof(Glyph));
         }
 
+ if(term.c.x + width > term.col)
+ tnewline(1);
+
         tsetchar(c, &term.c.attr, term.c.x, term.c.y);
- if(term.c.x+1 < term.col) {
- tmoveto(term.c.x+1, term.c.y);
+
+ if(width == 2) {
+ term.line[term.c.y][term.c.x].mode |= ATTR_WIDE;
+ if (term.c.x + 1 < term.col) {
+ term.line[term.c.y][term.c.x + 1].c[0] = '\0';
+ term.line[term.c.y][term.c.x + 1].mode = ATTR_WDUMMY;
+ }
+ }
+
+ if(term.c.x + width < term.col) {
+ tmoveto(term.c.x + width, term.c.y);
         } else {
                 term.c.state |= CURSOR_WRAPNEXT;
         }
_AT_@ -3184,7 +3225,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                                 xp, winy + frc[i].font->ascent,
                                 (FcChar8 *)u8c, u8cblen);
 
- xp += xw.cw;
+ xp += xw.cw * wcwidth(u8char);
         }
 
         /*
_AT_@ -3204,18 +3245,27 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
 void
 xdrawcursor(void) {
         static int oldx = 0, oldy = 0;
- int sl;
+ int sl, width, curx;
         Glyph g = {{' '}, ATTR_NULL, defaultbg, defaultcs};
 
         LIMIT(oldx, 0, term.col-1);
         LIMIT(oldy, 0, term.row-1);
 
- memcpy(g.c, term.line[term.c.y][term.c.x].c, UTF_SIZ);
+ curx = term.c.x;
+
+ /* if the cursor was/is on a dummy, adjust the coordinate */
+ if(term.line[oldy][oldx].mode & ATTR_WDUMMY)
+ oldx --;
+ if(term.line[term.c.y][curx].mode & ATTR_WDUMMY)
+ curx --;
+
+ memcpy(g.c, term.line[term.c.y][curx].c, UTF_SIZ);
 
         /* remove the old cursor */
         sl = utf8size(term.line[oldy][oldx].c);
+ width = (term.line[oldy][oldx].mode & ATTR_WIDE)? 2: 1;
         xdraws(term.line[oldy][oldx].c, term.line[oldy][oldx], oldx,
- oldy, 1, sl);
+ oldy, width, sl);
 
         /* draw the new one */
         if(!(IS_SET(MODE_HIDE))) {
_AT_@ -3227,26 +3277,27 @@ xdrawcursor(void) {
                         }
 
                         sl = utf8size(g.c);
- xdraws(g.c, g, term.c.x, term.c.y, 1, sl);
+ width = (term.line[term.c.y][curx].mode & ATTR_WIDE)? 2: 1;
+ xdraws(g.c, g, curx, term.c.y, width, sl);
                 } else {
                         XftDrawRect(xw.draw, &dc.col[defaultcs],
- borderpx + term.c.x * xw.cw,
+ borderpx + curx * xw.cw,
                                         borderpx + term.c.y * xw.ch,
                                         xw.cw - 1, 1);
                         XftDrawRect(xw.draw, &dc.col[defaultcs],
- borderpx + term.c.x * xw.cw,
+ borderpx + curx * xw.cw,
                                         borderpx + term.c.y * xw.ch,
                                         1, xw.ch - 1);
                         XftDrawRect(xw.draw, &dc.col[defaultcs],
- borderpx + (term.c.x + 1) * xw.cw - 1,
+ borderpx + (curx + 1) * xw.cw - 1,
                                         borderpx + term.c.y * xw.ch,
                                         1, xw.ch - 1);
                         XftDrawRect(xw.draw, &dc.col[defaultcs],
- borderpx + term.c.x * xw.cw,
+ borderpx + curx * xw.cw,
                                         borderpx + (term.c.y + 1) * xw.ch - 1,
                                         xw.cw, 1);
                 }
- oldx = term.c.x, oldy = term.c.y;
+ oldx = curx, oldy = term.c.y;
         }
 }
 
_AT_@ -3295,6 +3346,7 @@ drawregion(int x1, int y1, int x2, int y2) {
         Glyph base, new;
         char buf[DRAW_BUF_SIZ];
         bool ena_sel = sel.ob.x != -1;
+ long u8char;
 
         if(sel.alt ^ IS_SET(MODE_ALTSCREEN))
                 ena_sel = 0;
_AT_@ -3312,6 +3364,8 @@ drawregion(int x1, int y1, int x2, int y2) {
                 ic = ib = ox = 0;
                 for(x = x1; x < x2; x++) {
                         new = term.line[y][x];
+ if(new.mode == ATTR_WDUMMY)
+ continue;
                         if(ena_sel && selected(x, y))
                                 new.mode ^= ATTR_REVERSE;
                         if(ib > 0 && (ATTRCMP(base, new)
_AT_@ -3324,10 +3378,10 @@ drawregion(int x1, int y1, int x2, int y2) {
                                 base = new;
                         }
 
- sl = utf8size(new.c);
+ sl = utf8decode(new.c, &u8char);
                         memcpy(buf+ib, new.c, sl);
                         ib += sl;
- ++ic;
+ ic += (new.mode & ATTR_WIDE)? 2: 1;
                 }
                 if(ib > 0)
                         xdraws(buf, base, ox, y, ic, ib);
Received on Thu Sep 05 2013 - 08:31:18 CEST

This archive was generated by hypermail 2.3.0 : Thu Sep 05 2013 - 08:36:07 CEST