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 @@ -27,6 +27,7 @@ #include <X11/keysym.h> #include <X11/Xft/Xft.h> #include <fontconfig/fontconfig.h> +#include <wchar.h> #include "arg.h" @@ -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 { @@ -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; @@ -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; @@ -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); @@ -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); @@ -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) { @@ -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; } @@ -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); } /* @@ -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))) { @@ -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; } } @@ -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; @@ -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) @@ -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);