JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Add support for multiple charset definitions
[st.git] / st.c
diff --git a/st.c b/st.c
index 9cdd969..77ea0c8 100644 (file)
--- 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 {
@@ -129,10 +132,21 @@ enum term_mode {
        MODE_FOCUS       = 65536,
        MODE_MOUSEX10    = 131072,
        MODE_MOUSEMANY   = 262144,
+       MODE_BRCKTPASTE  = 524288,
        MODE_MOUSE       = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\
                          |MODE_MOUSEMANY,
 };
 
+enum charset {
+       CS_GRAPHIC0,
+       CS_GRAPHIC1,
+       CS_UK,
+       CS_USA,
+       CS_MULTI,
+       CS_GER,
+       CS_FIN
+};
+
 enum escape_state {
        ESC_START      = 1,
        ESC_CSI        = 2,
@@ -165,7 +179,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;
@@ -212,6 +226,9 @@ typedef struct {
        int bot;      /* bottom scroll limit */
        int mode;     /* terminal mode flags */
        int esc;      /* escape state flags */
+       char trantbl[4]; /* charset table translation */
+       int charset;  /* current charset */
+       int icharset; /* selected charset for sequence */
        bool numlock; /* lock numbers in keyboard */
        bool *tabs;
 } Term;
@@ -362,7 +379,9 @@ static void tsetdirtattr(int);
 static void tsetmode(bool, bool, int *, int);
 static void tfulldirt(void);
 static void techo(char *, int);
-static ulong tdefcolor(int *, int *, int);
+static long tdefcolor(int *, int *, int);
+static void tselcs(void);
+static void tdeftran(char);
 static inline bool match(uint, uint);
 static void ttynew(void);
 static void ttyread(void);
@@ -420,7 +439,6 @@ static int isfullutf8(char *, int);
 static ssize_t xwrite(int, char *, size_t);
 static void *xmalloc(size_t);
 static void *xrealloc(void *, size_t);
-static void *xcalloc(size_t, size_t);
 
 static void (*handler[LASTEvent])(XEvent *) = {
        [KeyPress] = kpress,
@@ -509,16 +527,6 @@ xrealloc(void *p, size_t len) {
        return p;
 }
 
-void *
-xcalloc(size_t nmemb, size_t size) {
-       void *p = calloc(nmemb, size);
-
-       if(!p)
-               die("Out of memory\n");
-
-       return p;
-}
-
 int
 utf8decode(char *s, long *u) {
        uchar c;
@@ -730,8 +738,13 @@ selsnap(int mode, int *x, int *y, int direction) {
                                }
                        }
 
+                       if(term.line[*y][*x+direction].mode & ATTR_WDUMMY) {
+                               *x += direction;
+                               continue;
+                       }
+
                        if(strchr(worddelimiters,
-                                       term.line[*y][*x + direction].c[0])) {
+                                       term.line[*y][*x+direction].c[0])) {
                                break;
                        }
 
@@ -826,18 +839,23 @@ mousereport(XEvent *e) {
                button = oldbutton + 32;
                ox = x;
                oy = y;
-       } else if(!IS_SET(MODE_MOUSESGR)
-                       && (e->xbutton.type == ButtonRelease
-                               || button == AnyButton)) {
-               button = 3;
        } else {
-               button -= Button1;
-               if(button >= 3)
-                       button += 64 - 3;
+               if(!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
+                       button = 3;
+               } else {
+                       button -= Button1;
+                       if(button >= 3)
+                               button += 64 - 3;
+               }
                if(e->xbutton.type == ButtonPress) {
                        oldbutton = button;
                        ox = x;
                        oy = y;
+               } else if(e->xbutton.type == ButtonRelease) {
+                       oldbutton = 3;
+                       /* MODE_MOUSEX10: no button release reporting */
+                       if(IS_SET(MODE_MOUSEX10))
+                               return;
                }
        }
 
@@ -854,8 +872,7 @@ mousereport(XEvent *e) {
                                e->xbutton.type == ButtonRelease ? 'm' : 'M');
        } else if(x < 223 && y < 223) {
                len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
-                               IS_SET(MODE_MOUSEX10)? button-1 : 32+button,
-                               32+x+1, 32+y+1);
+                               32+button, 32+x+1, 32+y+1);
        } else {
                return;
        }
@@ -943,7 +960,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);
@@ -960,7 +977,7 @@ selcopy(void) {
                         * st.
                         * FIXME: Fix the computer world.
                         */
-                       if(y < sel.ne.y && !((gp-1)->mode & ATTR_WRAP))
+                       if(y < sel.ne.y && x > 0 && !((gp-1)->mode & ATTR_WRAP))
                                *ptr++ = '\n';
 
                        /*
@@ -1012,7 +1029,11 @@ selnotify(XEvent *e) {
                        *repl++ = '\r';
                }
 
+               if(IS_SET(MODE_BRCKTPASTE))
+                       ttywrite("\033[200~", 6);
                ttywrite((const char *)data, nitems * format / 8);
+               if(IS_SET(MODE_BRCKTPASTE))
+                       ttywrite("\033[201~", 6);
                XFree(data);
                /* number of 32-bit chunks returned */
                ofs += nitems * format / 32;
@@ -1336,13 +1357,14 @@ tfulldirt(void) {
 
 void
 tcursor(int mode) {
-       static TCursor c;
+       static TCursor c[2];
+       bool alt = IS_SET(MODE_ALTSCREEN);
 
        if(mode == CURSOR_SAVE) {
-               c = term.c;
+               c[alt] = term.c;
        } else if(mode == CURSOR_LOAD) {
-               term.c = c;
-               tmoveto(c.x, c.y);
+               term.c = c[alt];
+               tmoveto(c[alt].x, c[alt].y);
        }
 }
 
@@ -1362,6 +1384,8 @@ treset(void) {
        term.top = 0;
        term.bot = term.row - 1;
        term.mode = MODE_WRAP;
+       memset(term.trantbl, sizeof(term.trantbl), CS_USA);
+       term.charset = 0;
 
        tclearregion(0, 0, term.col-1, term.row-1);
        tmoveto(0, 0);
@@ -1370,7 +1394,7 @@ treset(void) {
 
 void
 tnew(int col, int row) {
-       memset(&term, 0, sizeof(Term));
+       term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
        tresize(col, row);
        term.numlock = 1;
 
@@ -1544,6 +1568,16 @@ tsetchar(char *c, Glyph *attr, int x, int y) {
                }
        }
 
+       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);
@@ -1626,7 +1660,7 @@ tdeleteline(int n) {
        tscrollup(term.c.y, n);
 }
 
-ulong
+long
 tdefcolor(int *attr, int *npar, int l) {
        long idx = -1;
        uint r, g, b;
@@ -1677,7 +1711,7 @@ tdefcolor(int *attr, int *npar, int l) {
 void
 tsetattr(int *attr, int l) {
        int i;
-       ulong idx;
+       long idx;
 
        for(i = 0; i < l; i++) {
                switch(attr[i]) {
@@ -1838,12 +1872,12 @@ tsetmode(bool priv, bool set, int *args, int narg) {
                        case 1034:
                                MODBIT(term.mode, set, MODE_8BIT);
                                break;
-                       case 1049: /* = 1047 and 1048 */
-                       case 47:
+                       case 1049: /* swap screen & set/restore cursor as xterm */
+                               tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
+                       case 47: /* swap screen */
                        case 1047:
                                if (!allowaltscreen)
                                        break;
-
                                alt = IS_SET(MODE_ALTSCREEN);
                                if(alt) {
                                        tclearregion(0, 0, term.col-1,
@@ -1857,6 +1891,9 @@ tsetmode(bool priv, bool set, int *args, int narg) {
                        case 1048:
                                tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
                                break;
+                       case 2004: /* 2004: bracketed paste mode */
+                               MODBIT(term.mode, set, MODE_BRCKTPASTE);
+                               break;
                        /* Not implemented mouse modes. See comments there. */
                        case 1001: /* mouse highlight mode; can hang the
                                      terminal by design when implemented. */
@@ -1900,6 +1937,9 @@ tsetmode(bool priv, bool set, int *args, int narg) {
 
 void
 csihandle(void) {
+       char buf[40];
+       int len;
+
        switch(csiescseq.mode) {
        default:
        unknown:
@@ -2048,6 +2088,13 @@ csihandle(void) {
        case 'm': /* SGR -- Terminal attribute (color) */
                tsetattr(csiescseq.arg, csiescseq.narg);
                break;
+       case 'n': /* DSR – Device Status Report (cursor position) */
+               if (csiescseq.arg[0] == 6) {
+                       len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
+                                       term.c.y+1, term.c.x+1);
+                       ttywrite(buf, len);
+                       break;
+               }
        case 'r': /* DECSTBM -- Set Scrolling Region */
                if(csiescseq.priv) {
                        goto unknown;
@@ -2230,9 +2277,45 @@ techo(char *buf, int len) {
 }
 
 void
+tdeftran(char ascii) {
+       char c, (*bp)[2];
+       static char tbl[][2] = {
+               {'0', CS_GRAPHIC0}, {'1', CS_GRAPHIC1}, {'A', CS_UK},
+               {'B', CS_USA},      {'<', CS_MULTI},    {'K', CS_GER},
+               {'5', CS_FIN},      {'C', CS_FIN},
+               {0, 0}
+       };
+
+       for (bp = &tbl[0]; (c = (*bp)[0]) && c != ascii; ++bp)
+               /* nothing */;
+
+       if (c == 0)
+               fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
+       else
+               term.trantbl[term.icharset] = (*bp)[1];
+}
+
+void
+tselcs(void) {
+       if (term.trantbl[term.charset] == CS_GRAPHIC0)
+               term.c.attr.mode |= ATTR_GFX;
+       else
+               term.c.attr.mode &= ~ATTR_GFX;
+}
+
+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) {
@@ -2304,19 +2387,20 @@ tputc(char *c, int len) {
                case '\a':   /* BEL */
                        if(!(xw.state & WIN_FOCUSED))
                                xseturgency(1);
+                       if (bellvolume)
+                               XBell(xw.dpy, bellvolume);
                        return;
                case '\033': /* ESC */
                        csireset();
                        term.esc = ESC_START;
                        return;
                case '\016': /* SO */
+                       term.charset = 0;
+                       tselcs();
+                       return;
                case '\017': /* SI */
-                       /*
-                        * Different charsets are hard to handle. Applications
-                        * should use the right alt charset escapes for the
-                        * only reason they still exist: line drawing. The
-                        * rest is incompatible history st should not support.
-                        */
+                       term.charset = 1;
+                       tselcs();
                        return;
                case '\032': /* SUB */
                case '\030': /* CAN */
@@ -2344,22 +2428,8 @@ tputc(char *c, int len) {
                        if(ascii == '\\')
                                strhandle();
                } else if(term.esc & ESC_ALTCHARSET) {
-                       switch(ascii) {
-                       case '0': /* Line drawing set */
-                               term.c.attr.mode |= ATTR_GFX;
-                               break;
-                       case 'B': /* USASCII */
-                               term.c.attr.mode &= ~ATTR_GFX;
-                               break;
-                       case 'A': /* UK (IGNORED) */
-                       case '<': /* multinational charset (IGNORED) */
-                       case '5': /* Finnish (IGNORED) */
-                       case 'C': /* Finnish (IGNORED) */
-                       case 'K': /* German (IGNORED) */
-                               break;
-                       default:
-                               fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
-                       }
+                       tdeftran(ascii);
+                       tselcs();
                        term.esc = 0;
                } else if(term.esc & ESC_TEST) {
                        if(ascii == '8') { /* DEC screen alignment test. */
@@ -2390,13 +2460,12 @@ tputc(char *c, int len) {
                                term.esc |= ESC_STR;
                                break;
                        case '(': /* set primary charset G0 */
+                       case ')': /* set secondary charset G1 */
+                       case '*': /* set tertiary charset G2 */
+                       case '+': /* set quaternary charset G3 */
+                               term.icharset = ascii - '(';
                                term.esc |= ESC_ALTCHARSET;
                                break;
-                       case ')': /* set secondary charset G1 (IGNORED) */
-                       case '*': /* set tertiary charset G2 (IGNORED) */
-                       case '+': /* set quaternary charset G3 (IGNORED) */
-                               term.esc = 0;
-                               break;
                        case 'D': /* IND -- Linefeed */
                                if(term.c.y == term.bot) {
                                        tscrollup(term.top, 1);
@@ -2480,9 +2549,20 @@ 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;
        }
@@ -2536,8 +2616,8 @@ tresize(int col, int row) {
        /* allocate any new rows */
        for(/* i == minrow */; i < row; i++) {
                term.dirty[i] = 1;
-               term.line[i] = xcalloc(col, sizeof(Glyph));
-               term.alt [i] = xcalloc(col, sizeof(Glyph));
+               term.line[i] = xmalloc(col * sizeof(Glyph));
+               term.alt[i] = xmalloc(col * sizeof(Glyph));
        }
        if(col > term.col) {
                bp = term.tabs + term.col;
@@ -3111,7 +3191,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                                                        winy + font->ascent,
                                                        (FcChar8 *)u8fs,
                                                        u8fblen);
-                                       xp += CEIL(font->width * cwscale * u8fl);
+                                       xp += xw.cw * u8fl;
 
                                }
                                break;
@@ -3121,7 +3201,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                        u8fblen += u8cblen;
                }
                if(doesexist) {
-                       if (oneatatime);
+                       if (oneatatime)
                                continue;
                        break;
                }
@@ -3184,7 +3264,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
                                xp, winy + frc[i].font->ascent,
                                (FcChar8 *)u8c, u8cblen);
 
-               xp += CEIL(font->width * cwscale);
+               xp += xw.cw * wcwidth(u8char);
        }
 
        /*
@@ -3204,18 +3284,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);
 
+       curx = term.c.x;
+
+       /* adjust position if in dummy */
+       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][term.c.x].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 +3316,28 @@ 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, term.c.x, 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 +3386,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 +3404,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 +3418,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);
@@ -3714,7 +3808,7 @@ main(int argc, char *argv[]) {
                        xw.fh = (int)hr;
                if(bitm & XNegative && xw.fx == 0)
                        xw.fx = -1;
-               if(bitm & XNegative && xw.fy == 0)
+               if(bitm & YNegative && xw.fy == 0)
                        xw.fy = -1;
 
                if(xw.fh != 0 && xw.fw != 0)