libvterm-0.1.4/0002755000175000017500000000000013720230652011462 5ustar leoleolibvterm-0.1.4/CONTRIBUTING0000644000175000017500000000121113720230652013305 0ustar leoleoHow to Contribute ----------------- The main resources for this library are: Launchpad https://launchpad.net/libvterm Freenode: ##tty or #tickit on irc.freenode.net Email: Paul "LeoNerd" Evans Bug reports and feature requests can be sent to any of the above resources. New features, bug patches, etc.. should in the first instance be discussed via any of the resources listed above, before starting work on the actual code. There may be future plans or development already in-progress that could be affected so it is better to discuss the ideas first before starting work actually writing any code. libvterm-0.1.4/LICENSE0000644000175000017500000000211213720230652012461 0ustar leoleo The MIT License Copyright (c) 2008 Paul Evans Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libvterm-0.1.4/bin/0002755000175000017500000000000013720230652012232 5ustar leoleolibvterm-0.1.4/bin/unterm.c0000644000175000017500000001424613720230652013715 0ustar leoleo#include #include #include #include #include #include #include "vterm.h" #include "../src/utf8.h" // fill_utf8 #define streq(a,b) (!strcmp(a,b)) static VTerm *vt; static VTermScreen *vts; static int cols; static int rows; static enum { FORMAT_PLAIN, FORMAT_SGR, } format = FORMAT_PLAIN; static int dump_cell_color(const VTermColor *col, int sgri, int sgr[], int fg) { /* Reset the color if the given color is the default color */ if (fg && VTERM_COLOR_IS_DEFAULT_FG(col)) { sgr[sgri++] = 39; return sgri; } if (!fg && VTERM_COLOR_IS_DEFAULT_BG(col)) { sgr[sgri++] = 49; return sgri; } /* Decide whether to send an indexed color or an RGB color */ if (VTERM_COLOR_IS_INDEXED(col)) { const uint8_t idx = col->indexed.idx; if (idx < 8) { sgr[sgri++] = (idx + (fg ? 30 : 40)); } else if (idx < 16) { sgr[sgri++] = (idx - 8 + (fg ? 90 : 100)); } else { sgr[sgri++] = (fg ? 38 : 48); sgr[sgri++] = 5; sgr[sgri++] = idx; } } else if (VTERM_COLOR_IS_RGB(col)) { sgr[sgri++] = (fg ? 38 : 48); sgr[sgri++] = 2; sgr[sgri++] = col->rgb.red; sgr[sgri++] = col->rgb.green; sgr[sgri++] = col->rgb.blue; } return sgri; } static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell) { switch(format) { case FORMAT_PLAIN: break; case FORMAT_SGR: { // If all 7 attributes change, that means 7 SGRs max // Each colour could consume up to 5 entries int sgr[7 + 2*5]; int sgri = 0; if(!prevcell->attrs.bold && cell->attrs.bold) sgr[sgri++] = 1; if(prevcell->attrs.bold && !cell->attrs.bold) sgr[sgri++] = 22; if(!prevcell->attrs.underline && cell->attrs.underline) sgr[sgri++] = 4; if(prevcell->attrs.underline && !cell->attrs.underline) sgr[sgri++] = 24; if(!prevcell->attrs.italic && cell->attrs.italic) sgr[sgri++] = 3; if(prevcell->attrs.italic && !cell->attrs.italic) sgr[sgri++] = 23; if(!prevcell->attrs.blink && cell->attrs.blink) sgr[sgri++] = 5; if(prevcell->attrs.blink && !cell->attrs.blink) sgr[sgri++] = 25; if(!prevcell->attrs.reverse && cell->attrs.reverse) sgr[sgri++] = 7; if(prevcell->attrs.reverse && !cell->attrs.reverse) sgr[sgri++] = 27; if(!prevcell->attrs.strike && cell->attrs.strike) sgr[sgri++] = 9; if(prevcell->attrs.strike && !cell->attrs.strike) sgr[sgri++] = 29; if(!prevcell->attrs.font && cell->attrs.font) sgr[sgri++] = 10 + cell->attrs.font; if(prevcell->attrs.font && !cell->attrs.font) sgr[sgri++] = 10; if(!vterm_color_is_equal(&prevcell->fg, &cell->fg)) { sgri = dump_cell_color(&cell->fg, sgri, sgr, 1); } if(!vterm_color_is_equal(&prevcell->bg, &cell->bg)) { sgri = dump_cell_color(&cell->bg, sgri, sgr, 0); } if(!sgri) break; printf("\x1b["); for(int i = 0; i < sgri; i++) printf(!i ? "%d" : CSI_ARG_HAS_MORE(sgr[i]) ? ":%d" : ";%d", CSI_ARG(sgr[i])); printf("m"); } break; } for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { char bytes[6]; bytes[fill_utf8(cell->chars[i], bytes)] = 0; printf("%s", bytes); } } static void dump_eol(const VTermScreenCell *prevcell) { switch(format) { case FORMAT_PLAIN: break; case FORMAT_SGR: if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic || prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike || prevcell->attrs.font) printf("\x1b[m"); break; } printf("\n"); } void dump_row(int row) { VTermPos pos = { .row = row, .col = 0 }; VTermScreenCell prevcell = { 0 }; vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg); while(pos.col < cols) { VTermScreenCell cell; vterm_screen_get_cell(vts, pos, &cell); dump_cell(&cell, &prevcell); pos.col += cell.width; prevcell = cell; } dump_eol(&prevcell); } static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) { VTermScreenCell prevcell = { 0 }; vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg); for(int col = 0; col < cols; col++) { dump_cell(cells + col, &prevcell); prevcell = cells[col]; } dump_eol(&prevcell); return 1; } static int screen_resize(int new_rows, int new_cols, void *user) { rows = new_rows; cols = new_cols; return 1; } static VTermScreenCallbacks cb_screen = { .sb_pushline = &screen_sb_pushline, .resize = &screen_resize, }; int main(int argc, char *argv[]) { rows = 25; cols = 80; int opt; while((opt = getopt(argc, argv, "f:l:c:")) != -1) { switch(opt) { case 'f': if(streq(optarg, "plain")) format = FORMAT_PLAIN; else if(streq(optarg, "sgr")) format = FORMAT_SGR; else { fprintf(stderr, "Unrecognised format '%s'\n", optarg); exit(1); } break; case 'l': rows = atoi(optarg); if(!rows) rows = 25; break; case 'c': cols = atoi(optarg); if(!cols) cols = 80; break; } } const char *file = argv[optind++]; int fd = open(file, O_RDONLY); if(fd == -1) { fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno)); exit(1); } vt = vterm_new(rows, cols); vterm_set_utf8(vt, true); vts = vterm_obtain_screen(vt); vterm_screen_set_callbacks(vts, &cb_screen, NULL); vterm_screen_reset(vts, 1); int len; char buffer[1024]; while((len = read(fd, buffer, sizeof(buffer))) > 0) { vterm_input_write(vt, buffer, len); } for(int row = 0; row < rows; row++) { dump_row(row); } close(fd); vterm_free(vt); return 0; } libvterm-0.1.4/bin/vterm-ctrl.c0000644000175000017500000001664213720230652014504 0ustar leoleo#define _XOPEN_SOURCE 600 /* strdup */ #include #include #include #include #define streq(a,b) (strcmp(a,b)==0) #include static char *getvalue(int *argip, int argc, char *argv[]) { if(*argip >= argc) { fprintf(stderr, "Expected an option value\n"); exit(1); } return argv[(*argip)++]; } static int getchoice(int *argip, int argc, char *argv[], const char *options[]) { const char *arg = getvalue(argip, argc, argv); int value = -1; while(options[++value]) if(streq(arg, options[value])) return value; fprintf(stderr, "Unrecognised option value %s\n", arg); exit(1); } typedef enum { OFF, ON, QUERY, } BoolQuery; static BoolQuery getboolq(int *argip, int argc, char *argv[]) { return getchoice(argip, argc, argv, (const char *[]){"off", "on", "query", NULL}); } static char *helptext[] = { "reset", "s8c1t [off|on]", "keypad [app|num]", "screen [off|on|query]", "cursor [off|on|query]", "curblink [off|on|query]", "curshape [block|under|bar|query]", "mouse [off|click|clickdrag|motion]", "reportfocus [off|on|query]", "altscreen [off|on|query]", "bracketpaste [off|on|query]", "icontitle [STR]", "icon [STR]", "title [STR]", NULL }; static bool seticanon(bool icanon, bool echo) { struct termios termios; tcgetattr(0, &termios); bool ret = (termios.c_lflag & ICANON); if(icanon) termios.c_lflag |= ICANON; else termios.c_lflag &= ~ICANON; if(echo) termios.c_lflag |= ECHO; else termios.c_lflag &= ~ECHO; tcsetattr(0, TCSANOW, &termios); return ret; } static void await_c1(unsigned char c1) { unsigned char c; /* await CSI - 8bit or 2byte 7bit form */ bool in_esc = false; while((c = getchar())) { if(c == c1) break; if(in_esc && c == (char)(c1 - 0x40)) break; if(!in_esc && c == 0x1b) in_esc = true; else in_esc = false; } } static char *read_csi() { await_c1(0x9B); // CSI /* TODO: This really should be a more robust CSI parser */ char csi[32]; int i = 0; for(; i < sizeof(csi)-1; i++) { char c = csi[i] = getchar(); if(c >= 0x40 && c <= 0x7e) break; } csi[++i] = 0; // TODO: returns longer than 32? return strdup(csi); } static char *read_dcs() { await_c1(0x90); char dcs[32]; bool in_esc = false; int i = 0; for(; i < sizeof(dcs)-1; ) { char c = getchar(); if(c == 0x9c) // ST break; if(in_esc && c == 0x5c) break; if(!in_esc && c == 0x1b) in_esc = true; else { dcs[i++] = c; in_esc = false; } } dcs[++i] = 0; return strdup(dcs); } static void usage(int exitcode) { fprintf(stderr, "Control a libvterm-based terminal\n" "\n" "Options:\n"); for(char **p = helptext; *p; p++) fprintf(stderr, " %s\n", *p); exit(exitcode); } static bool query_dec_mode(int mode) { printf("\x1b[?%d$p", mode); char *s = NULL; do { if(s) free(s); s = read_csi(); /* expect "?" mode ";" value "$y" */ int reply_mode, reply_value; char reply_cmd; /* If the sscanf format string ends in a literal, we can't tell from * its return value if it matches. Hence we'll %c the cmd and check it * explicitly */ if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3) continue; if(reply_cmd != 'y') continue; if(reply_mode != mode) continue; free(s); if(reply_value == 1 || reply_value == 3) return true; if(reply_value == 2 || reply_value == 4) return false; printf("Unrecognised reply to DECRQM: %d\n", reply_value); return false; } while(1); } static void do_dec_mode(int mode, BoolQuery val, const char *name) { switch(val) { case OFF: case ON: printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l'); break; case QUERY: if(query_dec_mode(mode)) printf("%s on\n", name); else printf("%s off\n", name); break; } } static int query_rqss_numeric(char *cmd) { printf("\x1bP$q%s\x1b\\", cmd); char *s = NULL; do { if(s) free(s); s = read_dcs(); if(!s) return -1; if(strlen(s) < strlen(cmd)) return -1; if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) { printf("No match\n"); continue; } if(s[0] != '1' || s[1] != '$' || s[2] != 'r') return -1; int num; if(sscanf(s + 3, "%d", &num) != 1) return -1; return num; } while(1); } bool wasicanon; void restoreicanon(void) { seticanon(wasicanon, true); } int main(int argc, char *argv[]) { int argi = 1; if(argc == 1) usage(0); wasicanon = seticanon(false, false); atexit(restoreicanon); while(argi < argc) { const char *arg = argv[argi++]; if(streq(arg, "reset")) { printf("\x1b" "c"); } else if(streq(arg, "s8c1t")) { switch(getchoice(&argi, argc, argv, (const char *[]){"off", "on", NULL})) { case 0: printf("\x1b F"); break; case 1: printf("\x1b G"); break; } } else if(streq(arg, "keypad")) { switch(getchoice(&argi, argc, argv, (const char *[]){"app", "num", NULL})) { case 0: printf("\x1b="); break; case 1: printf("\x1b>"); break; } } else if(streq(arg, "screen")) { do_dec_mode(5, getboolq(&argi, argc, argv), "screen"); } else if(streq(arg, "cursor")) { do_dec_mode(25, getboolq(&argi, argc, argv), "cursor"); } else if(streq(arg, "curblink")) { do_dec_mode(12, getboolq(&argi, argc, argv), "curblink"); } else if(streq(arg, "curshape")) { // TODO: This ought to query the current value of DECSCUSR because it // may need blinking on or off int shape = getchoice(&argi, argc, argv, (const char *[]){"block", "under", "bar", "query", NULL}); switch(shape) { case 3: // query shape = query_rqss_numeric(" q"); switch(shape) { case 1: case 2: printf("curshape block\n"); break; case 3: case 4: printf("curshape under\n"); break; case 5: case 6: printf("curshape bar\n"); break; } break; case 0: case 1: case 2: printf("\x1b[%d q", 1 + (shape * 2)); break; } } else if(streq(arg, "mouse")) { switch(getchoice(&argi, argc, argv, (const char *[]){"off", "click", "clickdrag", "motion", NULL})) { case 0: printf("\x1b[?1000l"); break; case 1: printf("\x1b[?1000h"); break; case 2: printf("\x1b[?1002h"); break; case 3: printf("\x1b[?1003h"); break; } } else if(streq(arg, "reportfocus")) { do_dec_mode(1004, getboolq(&argi, argc, argv), "reportfocus"); } else if(streq(arg, "altscreen")) { do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen"); } else if(streq(arg, "bracketpaste")) { do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste"); } else if(streq(arg, "icontitle")) { printf("\x1b]0;%s\a", getvalue(&argi, argc, argv)); } else if(streq(arg, "icon")) { printf("\x1b]1;%s\a", getvalue(&argi, argc, argv)); } else if(streq(arg, "title")) { printf("\x1b]2;%s\a", getvalue(&argi, argc, argv)); } else { fprintf(stderr, "Unrecognised command %s\n", arg); exit(1); } } return 0; } libvterm-0.1.4/bin/vterm-dump.c0000644000175000017500000001316313720230652014500 0ustar leoleo// Require getopt(3) #define _XOPEN_SOURCE #include #include #define streq(a,b) (strcmp(a,b)==0) #include #include #include #include #include #include "vterm.h" static const char *special_begin = "{"; static const char *special_end = "}"; static int parser_text(const char bytes[], size_t len, void *user) { unsigned char *b = (unsigned char *)bytes; int i; for(i = 0; i < len; /* none */) { if(b[i] < 0x20) // C0 break; else if(b[i] < 0x80) // ASCII i++; else if(b[i] < 0xa0) // C1 break; else if(b[i] < 0xc0) // UTF-8 continuation break; else if(b[i] < 0xe0) { // UTF-8 2-byte // 2-byte UTF-8 if(len < i+2) break; i += 2; } else if(b[i] < 0xf0) { // UTF-8 3-byte if(len < i+3) break; i += 3; } else if(b[i] < 0xf8) { // UTF-8 4-byte if(len < i+4) break; i += 4; } else // otherwise invalid break; } printf("%.*s", i, b); return i; } /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ static const char *name_c0[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "LS0", "LS1", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", }; static const char *name_c1[] = { NULL, NULL, "BPH", "NBH", NULL, "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", NULL, "SCI", "CSI", "ST", "OSC", "PM", "APC", }; static int parser_control(unsigned char control, void *user) { if(control < 0x20) printf("%s%s%s", special_begin, name_c0[control], special_end); else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80]) printf("%s%s%s", special_begin, name_c1[control - 0x80], special_end); else printf("%sCONTROL 0x%02x%s", special_begin, control, special_end); if(control == 0x0a) printf("\n"); return 1; } static int parser_escape(const char bytes[], size_t len, void *user) { if(bytes[0] >= 0x20 && bytes[0] < 0x30) { if(len < 2) return -1; len = 2; } else { len = 1; } printf("%sESC %.*s%s", special_begin, (int)len, bytes, special_end); return len; } /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ static const char *name_csi_plain[] = { "ICH", "CUU", "CUD", "CUF", "CUB", "CNL", "CPL", "CHA", "CUP", "CHT", "ED", "EL", "IL", "DL", "EF", "EA", "DCH", "SSE", "CPR", "SU", "SD", "NP", "PP", "CTC", "ECH", "CVT", "CBT", "SRS", "PTX", "SDS", "SIMD",NULL, "HPA", "HPR", "REP", "DA", "VPA", "VPR", "HVP", "TBC", "SM", "MC", "HPB", "VPB", "RM", "SGR", "DSR", "DAQ", }; /*0 4 8 B */ static const int newline_csi_plain[] = { 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, }; static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) { const char *name = NULL; if(!leader && !intermed && command < 0x70) name = name_csi_plain[command - 0x40]; else if(leader && streq(leader, "?") && !intermed) { /* DEC */ switch(command) { case 'h': name = "DECSM"; break; case 'l': name = "DECRM"; break; } if(name) leader = NULL; } if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40]) printf("\n"); if(name) printf("%s%s", special_begin, name); else printf("%sCSI", special_begin); if(leader && leader[0]) printf(" %s", leader); for(int i = 0; i < argcount; i++) { printf(i ? "," : " "); if(args[i] == CSI_ARG_MISSING) printf("*"); else { while(CSI_ARG_HAS_MORE(args[i])) printf("%ld+", CSI_ARG(args[i++])); printf("%ld", CSI_ARG(args[i])); } } if(intermed && intermed[0]) printf(" %s", intermed); if(name) printf("%s", special_end); else printf(" %c%s", command, special_end); return 1; } static int parser_osc(const char *command, size_t cmdlen, void *user) { printf("%sOSC %.*s%s", special_begin, (int)cmdlen, command, special_end); return 1; } static int parser_dcs(const char *command, size_t cmdlen, void *user) { printf("%sDCS %.*s%s", special_begin, (int)cmdlen, command, special_end); return 1; } static VTermParserCallbacks parser_cbs = { .text = &parser_text, .control = &parser_control, .escape = &parser_escape, .csi = &parser_csi, .osc = &parser_osc, .dcs = &parser_dcs, }; int main(int argc, char *argv[]) { int use_colour = isatty(1); int opt; while((opt = getopt(argc, argv, "c")) != -1) { switch(opt) { case 'c': use_colour = 1; break; } } const char *file = argv[optind++]; int fd; if(!file || streq(file, "-")) fd = 0; // stdin else { fd = open(file, O_RDONLY); if(fd == -1) { fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno)); exit(1); } } if(use_colour) { special_begin = "\x1b[7m{"; special_end = "}\x1b[m"; } /* Size matters not for the parser */ VTerm *vt = vterm_new(25, 80); vterm_set_utf8(vt, 1); vterm_parser_set_callbacks(vt, &parser_cbs, NULL); int len; char buffer[1024]; while((len = read(fd, buffer, sizeof(buffer))) > 0) { vterm_input_write(vt, buffer, len); } printf("\n"); close(fd); vterm_free(vt); return 0; } libvterm-0.1.4/src/0002755000175000017500000000000013720230652012251 5ustar leoleolibvterm-0.1.4/src/unicode.c0000644000175000017500000003447713720230652014060 0ustar leoleo#include "vterm_internal.h" // ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c // With modifications: // made functions static // moved 'combining' table to file scope, so other functions can see it // ################################################################### /* * This is an implementation of wcwidth() and wcswidth() (defined in * IEEE Std 1002.1-2001) for Unicode. * * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html * * In fixed-width output devices, Latin characters all occupy a single * "cell" position of equal width, whereas ideographic CJK characters * occupy two such cells. Interoperability between terminal-line * applications and (teletype-style) character terminals using the * UTF-8 encoding requires agreement on which character should advance * the cursor by how many cell positions. No established formal * standards exist at present on which Unicode character shall occupy * how many cell positions on character terminals. These routines are * a first attempt of defining such behavior based on simple rules * applied to data provided by the Unicode Consortium. * * For some graphical characters, the Unicode standard explicitly * defines a character-cell width via the definition of the East Asian * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. * In all these cases, there is no ambiguity about which width a * terminal shall use. For characters in the East Asian Ambiguous (A) * class, the width choice depends purely on a preference of backward * compatibility with either historic CJK or Western practice. * Choosing single-width for these characters is easy to justify as * the appropriate long-term solution, as the CJK practice of * displaying these characters as double-width comes from historic * implementation simplicity (8-bit encoded characters were displayed * single-width and 16-bit ones double-width, even for Greek, * Cyrillic, etc.) and not any typographic considerations. * * Much less clear is the choice of width for the Not East Asian * (Neutral) class. Existing practice does not dictate a width for any * of these characters. It would nevertheless make sense * typographically to allocate two character cells to characters such * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be * represented adequately with a single-width glyph. The following * routines at present merely assign a single-cell width to all * neutral characters, in the interest of simplicity. This is not * entirely satisfactory and should be reconsidered before * establishing a formal standard in this area. At the moment, the * decision which Not East Asian (Neutral) characters should be * represented by double-width glyphs cannot yet be answered by * applying a simple rule from the Unicode database content. Setting * up a proper standard for the behavior of UTF-8 character terminals * will require a careful analysis not only of each Unicode character, * but also of each presentation form, something the author of these * routines has avoided to do so far. * * http://www.unicode.org/unicode/reports/tr11/ * * Markus Kuhn -- 2007-05-26 (Unicode 5.0) * * Permission to use, copy, modify, and distribute this software * for any purpose and without fee is hereby granted. The author * disclaims all warranties with regard to this software. * * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c */ struct interval { int first; int last; }; /* sorted list of non-overlapping intervals of non-spacing characters */ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ static const struct interval combining[] = { { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } }; /* auxiliary function for binary search in interval table */ static int bisearch(uint32_t ucs, const struct interval *table, int max) { int min = 0; int mid; if (ucs < table[0].first || ucs > table[max].last) return 0; while (max >= min) { mid = (min + max) / 2; if (ucs > table[mid].last) min = mid + 1; else if (ucs < table[mid].first) max = mid - 1; else return 1; } return 0; } /* The following two functions define the column width of an ISO 10646 * character as follows: * * - The null character (U+0000) has a column width of 0. * * - Other C0/C1 control characters and DEL will lead to a return * value of -1. * * - Non-spacing and enclosing combining characters (general * category code Mn or Me in the Unicode database) have a * column width of 0. * * - SOFT HYPHEN (U+00AD) has a column width of 1. * * - Other format characters (general category code Cf in the Unicode * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. * * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) * have a column width of 0. * * - Spacing characters in the East Asian Wide (W) or East Asian * Full-width (F) category as defined in Unicode Technical * Report #11 have a column width of 2. * * - All remaining characters (including all printable * ISO 8859-1 and WGL4 characters, Unicode control characters, * etc.) have a column width of 1. * * This implementation assumes that uint32_t characters are encoded * in ISO 10646. */ static int mk_wcwidth(uint32_t ucs) { /* test for 8-bit control characters */ if (ucs == 0) return 0; if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return -1; /* binary search in table of non-spacing characters */ if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1)) return 0; /* if we arrive here, ucs is not a combining or C0/C1 control character */ return 1 + (ucs >= 0x1100 && (ucs <= 0x115f || /* Hangul Jamo init. consonants */ ucs == 0x2329 || ucs == 0x232a || (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd))); } static int mk_wcswidth(const uint32_t *pwcs, size_t n) { int w, width = 0; for (;*pwcs && n-- > 0; pwcs++) if ((w = mk_wcwidth(*pwcs)) < 0) return -1; else width += w; return width; } /* * The following functions are the same as mk_wcwidth() and * mk_wcswidth(), except that spacing characters in the East Asian * Ambiguous (A) category as defined in Unicode Technical Report #11 * have a column width of 2. This variant might be useful for users of * CJK legacy encodings who want to migrate to UCS without changing * the traditional terminal character-width behaviour. It is not * otherwise recommended for general use. */ static int mk_wcwidth_cjk(uint32_t ucs) { /* sorted list of non-overlapping intervals of East Asian Ambiguous * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ static const struct interval ambiguous[] = { { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } }; /* binary search in table of non-spacing characters */ if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1)) return 2; return mk_wcwidth(ucs); } static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n) { int w, width = 0; for (;*pwcs && n-- > 0; pwcs++) if ((w = mk_wcwidth_cjk(*pwcs)) < 0) return -1; else width += w; return width; } // ################################ // ### The rest added by Paul Evans static const struct interval fullwidth[] = { #include "fullwidth.inc" }; INTERNAL int vterm_unicode_width(uint32_t codepoint) { if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1)) return 2; return mk_wcwidth(codepoint); } INTERNAL int vterm_unicode_is_combining(uint32_t codepoint) { return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); } libvterm-0.1.4/src/utf8.h0000644000175000017500000000202013720230652013300 0ustar leoleo/* The following functions copied and adapted from libtermkey * * http://www.leonerd.org.uk/code/libtermkey/ */ static inline unsigned int utf8_seqlen(long codepoint) { if(codepoint < 0x0000080) return 1; if(codepoint < 0x0000800) return 2; if(codepoint < 0x0010000) return 3; if(codepoint < 0x0200000) return 4; if(codepoint < 0x4000000) return 5; return 6; } /* Does NOT NUL-terminate the buffer */ static int fill_utf8(long codepoint, char *str) { int nbytes = utf8_seqlen(codepoint); // This is easier done backwards int b = nbytes; while(b > 1) { b--; str[b] = 0x80 | (codepoint & 0x3f); codepoint >>= 6; } switch(nbytes) { case 1: str[0] = (codepoint & 0x7f); break; case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; case 4: str[0] = 0xf0 | (codepoint & 0x07); break; case 5: str[0] = 0xf8 | (codepoint & 0x03); break; case 6: str[0] = 0xfc | (codepoint & 0x01); break; } return nbytes; } /* end copy */ libvterm-0.1.4/src/parser.c0000644000175000017500000002162613720230652013716 0ustar leoleo#include "vterm_internal.h" #include #include #undef DEBUG_PARSER static bool is_intermed(unsigned char c) { return c >= 0x20 && c <= 0x2f; } static void do_control(VTerm *vt, unsigned char control) { if(vt->parser.callbacks && vt->parser.callbacks->control) if((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) return; DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); } static void do_csi(VTerm *vt, char command) { #ifdef DEBUG_PARSER printf("Parsed CSI args as:\n", arglen, args); printf(" leader: %s\n", vt->parser.csi_leader); for(int argi = 0; argi < vt->parser.csi_argi; argi++) { printf(" %lu", CSI_ARG(vt->parser.csi_args[argi])); if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi])) printf("\n"); printf(" intermed: %s\n", vt->parser.intermed); } #endif if(vt->parser.callbacks && vt->parser.callbacks->csi) if((*vt->parser.callbacks->csi)( vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL, vt->parser.csi_args, vt->parser.csi_argi, vt->parser.intermedlen ? vt->parser.intermed : NULL, command, vt->parser.cbdata)) return; DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); } static void do_escape(VTerm *vt, char command) { char seq[INTERMED_MAX+1]; size_t len = vt->parser.intermedlen; strncpy(seq, vt->parser.intermed, len); seq[len++] = command; seq[len] = 0; if(vt->parser.callbacks && vt->parser.callbacks->escape) if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) return; DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); } static void append_strbuffer(VTerm *vt, const char *str, size_t len) { if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) { len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur; DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len); } if(len > 0) { strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len); vt->parser.strbuffer_cur += len; } } static void start_string(VTerm *vt, VTermParserStringType type) { vt->parser.stringtype = type; vt->parser.strbuffer_cur = 0; } static void more_string(VTerm *vt, const char *str, size_t len) { append_strbuffer(vt, str, len); } static void done_string(VTerm *vt, const char *str, size_t len) { if(vt->parser.strbuffer_cur) { if(str) append_strbuffer(vt, str, len); str = vt->parser.strbuffer; len = vt->parser.strbuffer_cur; } else if(!str) { DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n"); len = 0; } switch(vt->parser.stringtype) { case VTERM_PARSER_OSC: if(vt->parser.callbacks && vt->parser.callbacks->osc) if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata)) return; DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str); return; case VTERM_PARSER_DCS: if(vt->parser.callbacks && vt->parser.callbacks->dcs) if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata)) return; DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str); return; case VTERM_N_PARSER_TYPES: return; } } size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) { size_t pos = 0; const char *string_start; switch(vt->parser.state) { case NORMAL: case CSI_LEADER: case CSI_ARGS: case CSI_INTERMED: case ESC: string_start = NULL; break; case STRING: case ESC_IN_STRING: string_start = bytes; break; } #define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0) #define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) #define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) for( ; pos < len; pos++) { unsigned char c = bytes[pos]; if(c == 0x00 || c == 0x7f) { // NUL, DEL if(vt->parser.state >= STRING) { more_string(vt, string_start, bytes + pos - string_start); string_start = bytes + pos + 1; } continue; } if(c == 0x18 || c == 0x1a) { // CAN, SUB ENTER_NORMAL_STATE(); continue; } else if(c == 0x1b) { // ESC vt->parser.intermedlen = 0; if(vt->parser.state == STRING) vt->parser.state = ESC_IN_STRING; else ENTER_STATE(ESC); continue; } else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state vt->parser.state == STRING) { // fallthrough } else if(c < 0x20) { // other C0 if(vt->parser.state >= STRING) more_string(vt, string_start, bytes + pos - string_start); do_control(vt, c); if(vt->parser.state >= STRING) string_start = bytes + pos + 1; continue; } // else fallthrough switch(vt->parser.state) { case ESC_IN_STRING: if(c == 0x5c) { // ST vt->parser.state = STRING; done_string(vt, string_start, bytes + pos - string_start - 1); ENTER_NORMAL_STATE(); break; } vt->parser.state = ESC; // else fallthrough case ESC: switch(c) { case 0x50: // DCS start_string(vt, VTERM_PARSER_DCS); ENTER_STRING_STATE(); break; case 0x5b: // CSI vt->parser.csi_leaderlen = 0; ENTER_STATE(CSI_LEADER); break; case 0x5d: // OSC start_string(vt, VTERM_PARSER_OSC); ENTER_STRING_STATE(); break; default: if(is_intermed(c)) { if(vt->parser.intermedlen < INTERMED_MAX-1) vt->parser.intermed[vt->parser.intermedlen++] = c; } else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) { do_control(vt, c + 0x40); ENTER_NORMAL_STATE(); } else if(c >= 0x30 && c < 0x7f) { do_escape(vt, c); ENTER_NORMAL_STATE(); } else { DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); } } break; case CSI_LEADER: /* Extract leader bytes 0x3c to 0x3f */ if(c >= 0x3c && c <= 0x3f) { if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1) vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c; break; } /* else fallthrough */ vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; vt->parser.csi_argi = 0; vt->parser.csi_args[0] = CSI_ARG_MISSING; vt->parser.state = CSI_ARGS; /* fallthrough */ case CSI_ARGS: /* Numerical value of argument */ if(c >= '0' && c <= '9') { if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING) vt->parser.csi_args[vt->parser.csi_argi] = 0; vt->parser.csi_args[vt->parser.csi_argi] *= 10; vt->parser.csi_args[vt->parser.csi_argi] += c - '0'; break; } if(c == ':') { vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; c = ';'; } if(c == ';') { vt->parser.csi_argi++; vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; break; } /* else fallthrough */ vt->parser.csi_argi++; vt->parser.intermedlen = 0; vt->parser.state = CSI_INTERMED; case CSI_INTERMED: if(is_intermed(c)) { if(vt->parser.intermedlen < INTERMED_MAX-1) vt->parser.intermed[vt->parser.intermedlen++] = c; break; } else if(c == 0x1b) { /* ESC in CSI cancels */ } else if(c >= 0x40 && c <= 0x7e) { vt->parser.intermed[vt->parser.intermedlen] = 0; do_csi(vt, c); } /* else was invalid CSI */ ENTER_NORMAL_STATE(); break; case STRING: if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { done_string(vt, string_start, bytes + pos - string_start); ENTER_NORMAL_STATE(); } break; case NORMAL: if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { switch(c) { case 0x90: // DCS start_string(vt, VTERM_PARSER_DCS); ENTER_STRING_STATE(); break; case 0x9b: // CSI ENTER_STATE(CSI_LEADER); break; case 0x9d: // OSC start_string(vt, VTERM_PARSER_OSC); ENTER_STRING_STATE(); break; default: do_control(vt, c); break; } } else { size_t eaten = 0; if(vt->parser.callbacks && vt->parser.callbacks->text) eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); if(!eaten) { DEBUG_LOG("libvterm: Text callback did not consume any input\n"); /* force it to make progress */ eaten = 1; } pos += (eaten - 1); // we'll ++ it again in a moment } break; } } return len; } void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) { vt->parser.callbacks = callbacks; vt->parser.cbdata = user; } void *vterm_parser_get_cbdata(VTerm *vt) { return vt->parser.cbdata; } libvterm-0.1.4/src/rect.h0000644000175000017500000000374613720230652013367 0ustar leoleo/* * Some utility functions on VTermRect structures */ #define STRFrect "(%d,%d-%d,%d)" #define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col /* Expand dst to contain src as well */ static void rect_expand(VTermRect *dst, VTermRect *src) { if(dst->start_row > src->start_row) dst->start_row = src->start_row; if(dst->start_col > src->start_col) dst->start_col = src->start_col; if(dst->end_row < src->end_row) dst->end_row = src->end_row; if(dst->end_col < src->end_col) dst->end_col = src->end_col; } /* Clip the dst to ensure it does not step outside of bounds */ static void rect_clip(VTermRect *dst, VTermRect *bounds) { if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; /* Ensure it doesn't end up negatively-sized */ if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; } /* True if the two rectangles are equal */ static int rect_equal(VTermRect *a, VTermRect *b) { return (a->start_row == b->start_row) && (a->start_col == b->start_col) && (a->end_row == b->end_row) && (a->end_col == b->end_col); } /* True if small is contained entirely within big */ static int rect_contains(VTermRect *big, VTermRect *small) { if(small->start_row < big->start_row) return 0; if(small->start_col < big->start_col) return 0; if(small->end_row > big->end_row) return 0; if(small->end_col > big->end_col) return 0; return 1; } /* True if the rectangles overlap at all */ static int rect_intersects(VTermRect *a, VTermRect *b) { if(a->start_row > b->end_row || b->start_row > a->end_row) return 0; if(a->start_col > b->end_col || b->start_col > a->end_col) return 0; return 1; } libvterm-0.1.4/src/vterm.c0000644000175000017500000002315713720230652013560 0ustar leoleo#include "vterm_internal.h" #include #include #include #include /***************** * API functions * *****************/ static void *default_malloc(size_t size, void *allocdata) { void *ptr = malloc(size); if(ptr) memset(ptr, 0, size); return ptr; } static void default_free(void *ptr, void *allocdata) { free(ptr); } static VTermAllocatorFunctions default_allocator = { .malloc = &default_malloc, .free = &default_free, }; VTerm *vterm_new(int rows, int cols) { return vterm_new_with_allocator(rows, cols, &default_allocator, NULL); } VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) { /* Need to bootstrap using the allocator function directly */ VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata); vt->allocator = funcs; vt->allocdata = allocdata; vt->rows = rows; vt->cols = cols; vt->parser.state = NORMAL; vt->parser.callbacks = NULL; vt->parser.cbdata = NULL; vt->parser.strbuffer_len = 64; vt->parser.strbuffer_cur = 0; vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len); vt->outfunc = NULL; vt->outdata = NULL; vt->outbuffer_len = 64; vt->outbuffer_cur = 0; vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); vt->tmpbuffer_len = 64; vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); return vt; } void vterm_free(VTerm *vt) { if(vt->screen) vterm_screen_free(vt->screen); if(vt->state) vterm_state_free(vt->state); vterm_allocator_free(vt, vt->parser.strbuffer); vterm_allocator_free(vt, vt->outbuffer); vterm_allocator_free(vt, vt->tmpbuffer); vterm_allocator_free(vt, vt); } INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) { return (*vt->allocator->malloc)(size, vt->allocdata); } INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) { (*vt->allocator->free)(ptr, vt->allocdata); } void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) { if(rowsp) *rowsp = vt->rows; if(colsp) *colsp = vt->cols; } void vterm_set_size(VTerm *vt, int rows, int cols) { vt->rows = rows; vt->cols = cols; if(vt->parser.callbacks && vt->parser.callbacks->resize) (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); } int vterm_get_utf8(const VTerm *vt) { return vt->mode.utf8; } void vterm_set_utf8(VTerm *vt, int is_utf8) { vt->mode.utf8 = is_utf8; } void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) { vt->outfunc = func; vt->outdata = user; } INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) { if(vt->outfunc) { (vt->outfunc)(bytes, len, vt->outdata); return; } if(len > vt->outbuffer_len - vt->outbuffer_cur) return; memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); vt->outbuffer_cur += len; } INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) { size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, format, args); vterm_push_output_bytes(vt, vt->tmpbuffer, len); } INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) { va_list args; va_start(args, format); vterm_push_output_vsprintf(vt, format, args); va_end(args); } INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) { size_t cur; if(ctrl >= 0x80 && !vt->mode.ctrl8bit) cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); else cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); if(cur >= vt->tmpbuffer_len) return; va_list args; va_start(args, fmt); cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); va_end(args); if(cur >= vt->tmpbuffer_len) return; vterm_push_output_bytes(vt, vt->tmpbuffer, cur); } INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...) { size_t cur = 0; cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, vt->mode.ctrl8bit ? "\x90" : ESC_S "P"); // DCS if(cur >= vt->tmpbuffer_len) return; va_list args; va_start(args, fmt); cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); va_end(args); if(cur >= vt->tmpbuffer_len) return; cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST if(cur >= vt->tmpbuffer_len) return; vterm_push_output_bytes(vt, vt->tmpbuffer, cur); } size_t vterm_output_get_buffer_size(const VTerm *vt) { return vt->outbuffer_len; } size_t vterm_output_get_buffer_current(const VTerm *vt) { return vt->outbuffer_cur; } size_t vterm_output_get_buffer_remaining(const VTerm *vt) { return vt->outbuffer_len - vt->outbuffer_cur; } size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) { if(len > vt->outbuffer_cur) len = vt->outbuffer_cur; memcpy(buffer, vt->outbuffer, len); if(len < vt->outbuffer_cur) memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); vt->outbuffer_cur -= len; return len; } VTermValueType vterm_get_attr_type(VTermAttr attr) { switch(attr) { case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; case VTERM_N_ATTRS: return 0; } return 0; /* UNREACHABLE */ } VTermValueType vterm_get_prop_type(VTermProp prop) { switch(prop) { case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; case VTERM_N_PROPS: return 0; } return 0; /* UNREACHABLE */ } void vterm_scroll_rect(VTermRect rect, int downward, int rightward, int (*moverect)(VTermRect src, VTermRect dest, void *user), int (*eraserect)(VTermRect rect, int selective, void *user), void *user) { VTermRect src; VTermRect dest; if(abs(downward) >= rect.end_row - rect.start_row || abs(rightward) >= rect.end_col - rect.start_col) { /* Scroll more than area; just erase the lot */ (*eraserect)(rect, 0, user); return; } if(rightward >= 0) { /* rect: [XXX................] * src: [----------------] * dest: [----------------] */ dest.start_col = rect.start_col; dest.end_col = rect.end_col - rightward; src.start_col = rect.start_col + rightward; src.end_col = rect.end_col; } else { /* rect: [................XXX] * src: [----------------] * dest: [----------------] */ int leftward = -rightward; dest.start_col = rect.start_col + leftward; dest.end_col = rect.end_col; src.start_col = rect.start_col; src.end_col = rect.end_col - leftward; } if(downward >= 0) { dest.start_row = rect.start_row; dest.end_row = rect.end_row - downward; src.start_row = rect.start_row + downward; src.end_row = rect.end_row; } else { int upward = -downward; dest.start_row = rect.start_row + upward; dest.end_row = rect.end_row; src.start_row = rect.start_row; src.end_row = rect.end_row - upward; } if(moverect) (*moverect)(dest, src, user); if(downward > 0) rect.start_row = rect.end_row - downward; else if(downward < 0) rect.end_row = rect.start_row - downward; if(rightward > 0) rect.start_col = rect.end_col - rightward; else if(rightward < 0) rect.end_col = rect.start_col - rightward; (*eraserect)(rect, 0, user); } void vterm_copy_cells(VTermRect dest, VTermRect src, void (*copycell)(VTermPos dest, VTermPos src, void *user), void *user) { int downward = src.start_row - dest.start_row; int rightward = src.start_col - dest.start_col; int init_row, test_row, init_col, test_col; int inc_row, inc_col; if(downward < 0) { init_row = dest.end_row - 1; test_row = dest.start_row - 1; inc_row = -1; } else /* downward >= 0 */ { init_row = dest.start_row; test_row = dest.end_row; inc_row = +1; } if(rightward < 0) { init_col = dest.end_col - 1; test_col = dest.start_col - 1; inc_col = -1; } else /* rightward >= 0 */ { init_col = dest.start_col; test_col = dest.end_col; inc_col = +1; } VTermPos pos; for(pos.row = init_row; pos.row != test_row; pos.row += inc_row) for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) { VTermPos srcpos = { pos.row + downward, pos.col + rightward }; (*copycell)(pos, srcpos, user); } } void vterm_check_version(int major, int minor) { if(major != VTERM_VERSION_MAJOR) { fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR); exit(1); } if(minor > VTERM_VERSION_MINOR) { fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR); exit(1); } // Happy } libvterm-0.1.4/src/fullwidth.inc0000644000175000017500000000450613720230652014751 0ustar leoleo { 0x1100, 0x115f }, { 0x231a, 0x231b }, { 0x2329, 0x232a }, { 0x23e9, 0x23ec }, { 0x23f0, 0x23f0 }, { 0x23f3, 0x23f3 }, { 0x25fd, 0x25fe }, { 0x2614, 0x2615 }, { 0x2648, 0x2653 }, { 0x267f, 0x267f }, { 0x2693, 0x2693 }, { 0x26a1, 0x26a1 }, { 0x26aa, 0x26ab }, { 0x26bd, 0x26be }, { 0x26c4, 0x26c5 }, { 0x26ce, 0x26ce }, { 0x26d4, 0x26d4 }, { 0x26ea, 0x26ea }, { 0x26f2, 0x26f3 }, { 0x26f5, 0x26f5 }, { 0x26fa, 0x26fa }, { 0x26fd, 0x26fd }, { 0x2705, 0x2705 }, { 0x270a, 0x270b }, { 0x2728, 0x2728 }, { 0x274c, 0x274c }, { 0x274e, 0x274e }, { 0x2753, 0x2755 }, { 0x2757, 0x2757 }, { 0x2795, 0x2797 }, { 0x27b0, 0x27b0 }, { 0x27bf, 0x27bf }, { 0x2b1b, 0x2b1c }, { 0x2b50, 0x2b50 }, { 0x2b55, 0x2b55 }, { 0x2e80, 0x2e99 }, { 0x2e9b, 0x2ef3 }, { 0x2f00, 0x2fd5 }, { 0x2ff0, 0x2ffb }, { 0x3000, 0x303e }, { 0x3041, 0x3096 }, { 0x3099, 0x30ff }, { 0x3105, 0x312d }, { 0x3131, 0x318e }, { 0x3190, 0x31ba }, { 0x31c0, 0x31e3 }, { 0x31f0, 0x321e }, { 0x3220, 0x3247 }, { 0x3250, 0x32fe }, { 0x3300, 0x4dbf }, { 0x4e00, 0xa48c }, { 0xa490, 0xa4c6 }, { 0xa960, 0xa97c }, { 0xac00, 0xd7a3 }, { 0xf900, 0xfaff }, { 0xfe10, 0xfe19 }, { 0xfe30, 0xfe52 }, { 0xfe54, 0xfe66 }, { 0xfe68, 0xfe6b }, { 0xff01, 0xff60 }, { 0xffe0, 0xffe6 }, { 0x16fe0, 0x16fe0 }, { 0x17000, 0x187ec }, { 0x18800, 0x18af2 }, { 0x1b000, 0x1b001 }, { 0x1f004, 0x1f004 }, { 0x1f0cf, 0x1f0cf }, { 0x1f18e, 0x1f18e }, { 0x1f191, 0x1f19a }, { 0x1f200, 0x1f202 }, { 0x1f210, 0x1f23b }, { 0x1f240, 0x1f248 }, { 0x1f250, 0x1f251 }, { 0x1f300, 0x1f320 }, { 0x1f32d, 0x1f335 }, { 0x1f337, 0x1f37c }, { 0x1f37e, 0x1f393 }, { 0x1f3a0, 0x1f3ca }, { 0x1f3cf, 0x1f3d3 }, { 0x1f3e0, 0x1f3f0 }, { 0x1f3f4, 0x1f3f4 }, { 0x1f3f8, 0x1f43e }, { 0x1f440, 0x1f440 }, { 0x1f442, 0x1f4fc }, { 0x1f4ff, 0x1f53d }, { 0x1f54b, 0x1f54e }, { 0x1f550, 0x1f567 }, { 0x1f57a, 0x1f57a }, { 0x1f595, 0x1f596 }, { 0x1f5a4, 0x1f5a4 }, { 0x1f5fb, 0x1f64f }, { 0x1f680, 0x1f6c5 }, { 0x1f6cc, 0x1f6cc }, { 0x1f6d0, 0x1f6d2 }, { 0x1f6eb, 0x1f6ec }, { 0x1f6f4, 0x1f6f6 }, { 0x1f910, 0x1f91e }, { 0x1f920, 0x1f927 }, { 0x1f930, 0x1f930 }, { 0x1f933, 0x1f93e }, { 0x1f940, 0x1f94b }, { 0x1f950, 0x1f95e }, { 0x1f980, 0x1f991 }, { 0x1f9c0, 0x1f9c0 }, libvterm-0.1.4/src/encoding.c0000644000175000017500000001317013720230652014203 0ustar leoleo#include "vterm_internal.h" #define UNICODE_INVALID 0xFFFD #if defined(DEBUG) && DEBUG > 1 # define DEBUG_PRINT_UTF8 #endif struct UTF8DecoderData { // number of bytes remaining in this codepoint int bytes_remaining; // number of bytes total in this codepoint once it's finished // (for detecting overlongs) int bytes_total; int this_cp; }; static void init_utf8(VTermEncoding *enc, void *data_) { struct UTF8DecoderData *data = data_; data->bytes_remaining = 0; data->bytes_total = 0; } static void decode_utf8(VTermEncoding *enc, void *data_, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { struct UTF8DecoderData *data = data_; #ifdef DEBUG_PRINT_UTF8 printf("BEGIN UTF-8\n"); #endif for(; *pos < bytelen && *cpi < cplen; (*pos)++) { unsigned char c = bytes[*pos]; #ifdef DEBUG_PRINT_UTF8 printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); #endif if(c < 0x20) // C0 return; else if(c >= 0x20 && c < 0x7f) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; cp[(*cpi)++] = c; #ifdef DEBUG_PRINT_UTF8 printf(" UTF-8 char: U+%04x\n", c); #endif data->bytes_remaining = 0; } else if(c == 0x7f) // DEL return; else if(c >= 0x80 && c < 0xc0) { if(!data->bytes_remaining) { cp[(*cpi)++] = UNICODE_INVALID; continue; } data->this_cp <<= 6; data->this_cp |= c & 0x3f; data->bytes_remaining--; if(!data->bytes_remaining) { #ifdef DEBUG_PRINT_UTF8 printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); #endif // Check for overlong sequences switch(data->bytes_total) { case 2: if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; break; case 3: if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; break; case 4: if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; break; case 5: if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; break; case 6: if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; break; } // Now look for plain invalid ones if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || data->this_cp == 0xFFFE || data->this_cp == 0xFFFF) data->this_cp = UNICODE_INVALID; #ifdef DEBUG_PRINT_UTF8 printf(" char: U+%04x\n", data->this_cp); #endif cp[(*cpi)++] = data->this_cp; } } else if(c >= 0xc0 && c < 0xe0) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; data->this_cp = c & 0x1f; data->bytes_total = 2; data->bytes_remaining = 1; } else if(c >= 0xe0 && c < 0xf0) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; data->this_cp = c & 0x0f; data->bytes_total = 3; data->bytes_remaining = 2; } else if(c >= 0xf0 && c < 0xf8) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; data->this_cp = c & 0x07; data->bytes_total = 4; data->bytes_remaining = 3; } else if(c >= 0xf8 && c < 0xfc) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; data->this_cp = c & 0x03; data->bytes_total = 5; data->bytes_remaining = 4; } else if(c >= 0xfc && c < 0xfe) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; data->this_cp = c & 0x01; data->bytes_total = 6; data->bytes_remaining = 5; } else { cp[(*cpi)++] = UNICODE_INVALID; } } } static VTermEncoding encoding_utf8 = { .init = &init_utf8, .decode = &decode_utf8, }; static void decode_usascii(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { int is_gr = bytes[*pos] & 0x80; for(; *pos < bytelen && *cpi < cplen; (*pos)++) { unsigned char c = bytes[*pos] ^ is_gr; if(c < 0x20 || c == 0x7f || c >= 0x80) return; cp[(*cpi)++] = c; } } static VTermEncoding encoding_usascii = { .decode = &decode_usascii, }; struct StaticTableEncoding { const VTermEncoding enc; const uint32_t chars[128]; }; static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; int is_gr = bytes[*pos] & 0x80; for(; *pos < bytelen && *cpi < cplen; (*pos)++) { unsigned char c = bytes[*pos] ^ is_gr; if(c < 0x20 || c == 0x7f || c >= 0x80) return; if(table->chars[c]) cp[(*cpi)++] = table->chars[c]; else cp[(*cpi)++] = c; } } #include "encoding/DECdrawing.inc" #include "encoding/uk.inc" static struct { VTermEncodingType type; char designation; VTermEncoding *enc; } encodings[] = { { ENC_UTF8, 'u', &encoding_utf8 }, { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing }, { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk }, { ENC_SINGLE_94, 'B', &encoding_usascii }, { 0 }, }; /* This ought to be INTERNAL but isn't because it's used by unit testing */ VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) { for(int i = 0; encodings[i].designation; i++) if(encodings[i].type == type && encodings[i].designation == designation) return encodings[i].enc; return NULL; } libvterm-0.1.4/src/state.c0000644000175000017500000014133313720230652013540 0ustar leoleo#include "vterm_internal.h" #include #include #define strneq(a,b,n) (strncmp(a,b,n)==0) #if defined(DEBUG) && DEBUG > 1 # define DEBUG_GLYPH_COMBINE #endif /* Some convenient wrappers to make callback functions easier */ static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) { VTermGlyphInfo info = { .chars = chars, .width = width, .protected_cell = state->protected_cell, .dwl = state->lineinfo[pos.row].doublewidth, .dhl = state->lineinfo[pos.row].doubleheight, }; if(state->callbacks && state->callbacks->putglyph) if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) return; DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); } static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) { if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) return; if(cancel_phantom) state->at_phantom = 0; if(state->callbacks && state->callbacks->movecursor) if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) return; } static void erase(VTermState *state, VTermRect rect, int selective) { if(state->callbacks && state->callbacks->erase) if((*state->callbacks->erase)(rect, selective, state->cbdata)) return; } static VTermState *vterm_state_new(VTerm *vt) { VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); state->vt = vt; state->rows = vt->rows; state->cols = vt->cols; state->mouse_col = 0; state->mouse_row = 0; state->mouse_buttons = 0; state->mouse_protocol = MOUSE_X10; state->callbacks = NULL; state->cbdata = NULL; vterm_state_newpen(state); state->bold_is_highbright = 0; return state; } INTERNAL void vterm_state_free(VTermState *state) { vterm_allocator_free(state->vt, state->tabstops); vterm_allocator_free(state->vt, state->lineinfo); vterm_allocator_free(state->vt, state->combine_chars); vterm_allocator_free(state->vt, state); } static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) { if(!downward && !rightward) return; int rows = rect.end_row - rect.start_row; if(downward > rows) downward = rows; else if(downward < -rows) downward = -rows; int cols = rect.end_col - rect.start_col; if(rightward > cols) rightward = cols; else if(rightward < -cols) rightward = -cols; // Update lineinfo if full line if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { int height = rect.end_row - rect.start_row - abs(downward); if(downward > 0) memmove(state->lineinfo + rect.start_row, state->lineinfo + rect.start_row + downward, height * sizeof(state->lineinfo[0])); else memmove(state->lineinfo + rect.start_row - downward, state->lineinfo + rect.start_row, height * sizeof(state->lineinfo[0])); } if(state->callbacks && state->callbacks->scrollrect) if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) return; if(state->callbacks) vterm_scroll_rect(rect, downward, rightward, state->callbacks->moverect, state->callbacks->erase, state->cbdata); } static void linefeed(VTermState *state) { if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { VTermRect rect = { .start_row = state->scrollregion_top, .end_row = SCROLLREGION_BOTTOM(state), .start_col = SCROLLREGION_LEFT(state), .end_col = SCROLLREGION_RIGHT(state), }; scroll(state, rect, 1, 0); } else if(state->pos.row < state->rows-1) state->pos.row++; } static void grow_combine_buffer(VTermState *state) { size_t new_size = state->combine_chars_size * 2; uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); vterm_allocator_free(state->vt, state->combine_chars); state->combine_chars = new_chars; state->combine_chars_size = new_size; } static void set_col_tabstop(VTermState *state, int col) { unsigned char mask = 1 << (col & 7); state->tabstops[col >> 3] |= mask; } static void clear_col_tabstop(VTermState *state, int col) { unsigned char mask = 1 << (col & 7); state->tabstops[col >> 3] &= ~mask; } static int is_col_tabstop(VTermState *state, int col) { unsigned char mask = 1 << (col & 7); return state->tabstops[col >> 3] & mask; } static int is_cursor_in_scrollregion(const VTermState *state) { if(state->pos.row < state->scrollregion_top || state->pos.row >= SCROLLREGION_BOTTOM(state)) return 0; if(state->pos.col < SCROLLREGION_LEFT(state) || state->pos.col >= SCROLLREGION_RIGHT(state)) return 0; return 1; } static void tab(VTermState *state, int count, int direction) { while(count > 0) { if(direction > 0) { if(state->pos.col >= THISROWWIDTH(state)-1) return; state->pos.col++; } else if(direction < 0) { if(state->pos.col < 1) return; state->pos.col--; } if(is_col_tabstop(state, state->pos.col)) count--; } } #define NO_FORCE 0 #define FORCE 1 #define DWL_OFF 0 #define DWL_ON 1 #define DHL_OFF 0 #define DHL_TOP 1 #define DHL_BOTTOM 2 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) { VTermLineInfo info = state->lineinfo[row]; if(dwl == DWL_OFF) info.doublewidth = DWL_OFF; else if(dwl == DWL_ON) info.doublewidth = DWL_ON; // else -1 to ignore if(dhl == DHL_OFF) info.doubleheight = DHL_OFF; else if(dhl == DHL_TOP) info.doubleheight = DHL_TOP; else if(dhl == DHL_BOTTOM) info.doubleheight = DHL_BOTTOM; if((state->callbacks && state->callbacks->setlineinfo && (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) || force) state->lineinfo[row] = info; } static int on_text(const char bytes[], size_t len, void *user) { VTermState *state = user; VTermPos oldpos = state->pos; // We'll have at most len codepoints uint32_t codepoints[len]; int npoints = 0; size_t eaten = 0; VTermEncodingInstance *encoding = state->gsingle_set ? &state->encoding[state->gsingle_set] : !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : state->vt->mode.utf8 ? &state->encoding_utf8 : &state->encoding[state->gr_set]; (*encoding->enc->decode)(encoding->enc, encoding->data, codepoints, &npoints, state->gsingle_set ? 1 : len, bytes, &eaten, len); /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet * for even a single codepoint */ if(!npoints) return eaten; if(state->gsingle_set && npoints) state->gsingle_set = 0; int i = 0; /* This is a combining char. that needs to be merged with the previous * glyph output */ if(vterm_unicode_is_combining(codepoints[i])) { /* See if the cursor has moved since */ if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { #ifdef DEBUG_GLYPH_COMBINE int printpos; printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); for(printpos = 0; state->combine_chars[printpos]; printpos++) printf("U+%04x ", state->combine_chars[printpos]); printf("} + {"); #endif /* Find where we need to append these combining chars */ int saved_i = 0; while(state->combine_chars[saved_i]) saved_i++; /* Add extra ones */ while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { if(saved_i >= state->combine_chars_size) grow_combine_buffer(state); state->combine_chars[saved_i++] = codepoints[i++]; } if(saved_i >= state->combine_chars_size) grow_combine_buffer(state); state->combine_chars[saved_i] = 0; #ifdef DEBUG_GLYPH_COMBINE for(; state->combine_chars[printpos]; printpos++) printf("U+%04x ", state->combine_chars[printpos]); printf("}\n"); #endif /* Now render it */ putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); } else { DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); } } for(; i < npoints; i++) { // Try to find combining characters following this int glyph_starts = i; int glyph_ends; for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) if(!vterm_unicode_is_combining(codepoints[glyph_ends])) break; int width = 0; uint32_t chars[glyph_ends - glyph_starts + 1]; for( ; i < glyph_ends; i++) { chars[i - glyph_starts] = codepoints[i]; int this_width = vterm_unicode_width(codepoints[i]); #ifdef DEBUG if(this_width < 0) { fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]); abort(); } #endif width += this_width; } chars[glyph_ends - glyph_starts] = 0; i--; #ifdef DEBUG_GLYPH_COMBINE int printpos; printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) printf("U+%04x ", chars[printpos]); printf("}, onscreen width %d\n", width); #endif if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { linefeed(state); state->pos.col = 0; state->at_phantom = 0; } if(state->mode.insert) { /* TODO: This will be a little inefficient for large bodies of text, as * it'll have to 'ICH' effectively before every glyph. We should scan * ahead and ICH as many times as required */ VTermRect rect = { .start_row = state->pos.row, .end_row = state->pos.row + 1, .start_col = state->pos.col, .end_col = THISROWWIDTH(state), }; scroll(state, rect, 0, -1); } putglyph(state, chars, width, state->pos); if(i == npoints - 1) { /* End of the buffer. Save the chars in case we have to combine with * more on the next call */ int save_i; for(save_i = 0; chars[save_i]; save_i++) { if(save_i >= state->combine_chars_size) grow_combine_buffer(state); state->combine_chars[save_i] = chars[save_i]; } if(save_i >= state->combine_chars_size) grow_combine_buffer(state); state->combine_chars[save_i] = 0; state->combine_width = width; state->combine_pos = state->pos; } if(state->pos.col + width >= THISROWWIDTH(state)) { if(state->mode.autowrap) state->at_phantom = 1; } else { state->pos.col += width; } } updatecursor(state, &oldpos, 0); #ifdef DEBUG if(state->pos.row < 0 || state->pos.row >= state->rows || state->pos.col < 0 || state->pos.col >= state->cols) { fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", state->pos.row, state->pos.col); abort(); } #endif return eaten; } static int on_control(unsigned char control, void *user) { VTermState *state = user; VTermPos oldpos = state->pos; switch(control) { case 0x07: // BEL - ECMA-48 8.3.3 if(state->callbacks && state->callbacks->bell) (*state->callbacks->bell)(state->cbdata); break; case 0x08: // BS - ECMA-48 8.3.5 if(state->pos.col > 0) state->pos.col--; break; case 0x09: // HT - ECMA-48 8.3.60 tab(state, 1, +1); break; case 0x0a: // LF - ECMA-48 8.3.74 case 0x0b: // VT case 0x0c: // FF linefeed(state); if(state->mode.newline) state->pos.col = 0; break; case 0x0d: // CR - ECMA-48 8.3.15 state->pos.col = 0; break; case 0x0e: // LS1 - ECMA-48 8.3.76 state->gl_set = 1; break; case 0x0f: // LS0 - ECMA-48 8.3.75 state->gl_set = 0; break; case 0x84: // IND - DEPRECATED but implemented for completeness linefeed(state); break; case 0x85: // NEL - ECMA-48 8.3.86 linefeed(state); state->pos.col = 0; break; case 0x88: // HTS - ECMA-48 8.3.62 set_col_tabstop(state, state->pos.col); break; case 0x8d: // RI - ECMA-48 8.3.104 if(state->pos.row == state->scrollregion_top) { VTermRect rect = { .start_row = state->scrollregion_top, .end_row = SCROLLREGION_BOTTOM(state), .start_col = SCROLLREGION_LEFT(state), .end_col = SCROLLREGION_RIGHT(state), }; scroll(state, rect, -1, 0); } else if(state->pos.row > 0) state->pos.row--; break; case 0x8e: // SS2 - ECMA-48 8.3.141 state->gsingle_set = 2; break; case 0x8f: // SS3 - ECMA-48 8.3.142 state->gsingle_set = 3; break; default: if(state->fallbacks && state->fallbacks->control) if((*state->fallbacks->control)(control, state->fbdata)) return 1; return 0; } updatecursor(state, &oldpos, 1); #ifdef DEBUG if(state->pos.row < 0 || state->pos.row >= state->rows || state->pos.col < 0 || state->pos.col >= state->cols) { fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", control, state->pos.row, state->pos.col); abort(); } #endif return 1; } static int settermprop_bool(VTermState *state, VTermProp prop, int v) { VTermValue val = { .boolean = v }; return vterm_state_set_termprop(state, prop, &val); } static int settermprop_int(VTermState *state, VTermProp prop, int v) { VTermValue val = { .number = v }; return vterm_state_set_termprop(state, prop, &val); } static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) { char strvalue[len+1]; strncpy(strvalue, str, len); strvalue[len] = 0; VTermValue val = { .string = strvalue }; return vterm_state_set_termprop(state, prop, &val); } static void savecursor(VTermState *state, int save) { if(save) { state->saved.pos = state->pos; state->saved.mode.cursor_visible = state->mode.cursor_visible; state->saved.mode.cursor_blink = state->mode.cursor_blink; state->saved.mode.cursor_shape = state->mode.cursor_shape; vterm_state_savepen(state, 1); } else { VTermPos oldpos = state->pos; state->pos = state->saved.pos; settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); vterm_state_savepen(state, 0); updatecursor(state, &oldpos, 1); } } static int on_escape(const char *bytes, size_t len, void *user) { VTermState *state = user; /* Easier to decode this from the first byte, even though the final * byte terminates it */ switch(bytes[0]) { case ' ': if(len != 2) return 0; switch(bytes[1]) { case 'F': // S7C1T state->vt->mode.ctrl8bit = 0; break; case 'G': // S8C1T state->vt->mode.ctrl8bit = 1; break; default: return 0; } return 2; case '#': if(len != 2) return 0; switch(bytes[1]) { case '3': // DECDHL top if(state->mode.leftrightmargin) break; set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); break; case '4': // DECDHL bottom if(state->mode.leftrightmargin) break; set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); break; case '5': // DECSWL if(state->mode.leftrightmargin) break; set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); break; case '6': // DECDWL if(state->mode.leftrightmargin) break; set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); break; case '8': // DECALN { VTermPos pos; uint32_t E[] = { 'E', 0 }; for(pos.row = 0; pos.row < state->rows; pos.row++) for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) putglyph(state, E, 1, pos); break; } default: return 0; } return 2; case '(': case ')': case '*': case '+': // SCS if(len != 2) return 0; { int setnum = bytes[0] - 0x28; VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); if(newenc) { state->encoding[setnum].enc = newenc; if(newenc->init) (*newenc->init)(newenc, state->encoding[setnum].data); } } return 2; case '7': // DECSC savecursor(state, 1); return 1; case '8': // DECRC savecursor(state, 0); return 1; case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 return 1; case '=': // DECKPAM state->mode.keypad = 1; return 1; case '>': // DECKPNM state->mode.keypad = 0; return 1; case 'c': // RIS - ECMA-48 8.3.105 { VTermPos oldpos = state->pos; vterm_state_reset(state, 1); if(state->callbacks && state->callbacks->movecursor) (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); return 1; } case 'n': // LS2 - ECMA-48 8.3.78 state->gl_set = 2; return 1; case 'o': // LS3 - ECMA-48 8.3.80 state->gl_set = 3; return 1; case '~': // LS1R - ECMA-48 8.3.77 state->gr_set = 1; return 1; case '}': // LS2R - ECMA-48 8.3.79 state->gr_set = 2; return 1; case '|': // LS3R - ECMA-48 8.3.81 state->gr_set = 3; return 1; default: return 0; } } static void set_mode(VTermState *state, int num, int val) { switch(num) { case 4: // IRM - ECMA-48 7.2.10 state->mode.insert = val; break; case 20: // LNM - ANSI X3.4-1977 state->mode.newline = val; break; default: DEBUG_LOG("libvterm: Unknown mode %d\n", num); return; } } static void set_dec_mode(VTermState *state, int num, int val) { switch(num) { case 1: state->mode.cursor = val; break; case 5: // DECSCNM - screen mode settermprop_bool(state, VTERM_PROP_REVERSE, val); break; case 6: // DECOM - origin mode { VTermPos oldpos = state->pos; state->mode.origin = val; state->pos.row = state->mode.origin ? state->scrollregion_top : 0; state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; updatecursor(state, &oldpos, 1); } break; case 7: state->mode.autowrap = val; break; case 12: settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); break; case 25: settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); break; case 69: // DECVSSM - vertical split screen mode // DECLRMM - left/right margin mode state->mode.leftrightmargin = val; if(val) { // Setting DECVSSM must clear doublewidth/doubleheight state of every line for(int row = 0; row < state->rows; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); } break; case 1000: case 1002: case 1003: settermprop_int(state, VTERM_PROP_MOUSE, !val ? VTERM_PROP_MOUSE_NONE : (num == 1000) ? VTERM_PROP_MOUSE_CLICK : (num == 1002) ? VTERM_PROP_MOUSE_DRAG : VTERM_PROP_MOUSE_MOVE); break; case 1004: state->mode.report_focus = val; break; case 1005: state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; break; case 1006: state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; break; case 1015: state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; break; case 1047: settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); break; case 1048: savecursor(state, val); break; case 1049: settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); savecursor(state, val); break; case 2004: state->mode.bracketpaste = val; break; default: DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); return; } } static void request_dec_mode(VTermState *state, int num) { int reply; switch(num) { case 1: reply = state->mode.cursor; break; case 5: reply = state->mode.screen; break; case 6: reply = state->mode.origin; break; case 7: reply = state->mode.autowrap; break; case 12: reply = state->mode.cursor_blink; break; case 25: reply = state->mode.cursor_visible; break; case 69: reply = state->mode.leftrightmargin; break; case 1000: reply = state->mouse_flags == MOUSE_WANT_CLICK; break; case 1002: reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); break; case 1003: reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); break; case 1004: reply = state->mode.report_focus; break; case 1005: reply = state->mouse_protocol == MOUSE_UTF8; break; case 1006: reply = state->mouse_protocol == MOUSE_SGR; break; case 1015: reply = state->mouse_protocol == MOUSE_RXVT; break; case 1047: reply = state->mode.alt_screen; break; case 2004: reply = state->mode.bracketpaste; break; default: vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); return; } vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); } static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) { VTermState *state = user; int leader_byte = 0; int intermed_byte = 0; int cancel_phantom = 1; if(leader && leader[0]) { if(leader[1]) // longer than 1 char return 0; switch(leader[0]) { case '?': case '>': leader_byte = leader[0]; break; default: return 0; } } if(intermed && intermed[0]) { if(intermed[1]) // longer than 1 char return 0; switch(intermed[0]) { case ' ': case '"': case '$': case '\'': intermed_byte = intermed[0]; break; default: return 0; } } VTermPos oldpos = state->pos; // Some temporaries for later code int count, val; int row, col; VTermRect rect; int selective; #define LBOUND(v,min) if((v) < (min)) (v) = (min) #define UBOUND(v,max) if((v) > (max)) (v) = (max) #define LEADER(l,b) ((l << 8) | b) #define INTERMED(i,b) ((i << 16) | b) switch(intermed_byte << 16 | leader_byte << 8 | command) { case 0x40: // ICH - ECMA-48 8.3.64 count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; if(state->mode.leftrightmargin) rect.end_col = SCROLLREGION_RIGHT(state); else rect.end_col = THISROWWIDTH(state); scroll(state, rect, 0, -count); break; case 0x41: // CUU - ECMA-48 8.3.22 count = CSI_ARG_COUNT(args[0]); state->pos.row -= count; state->at_phantom = 0; break; case 0x42: // CUD - ECMA-48 8.3.19 count = CSI_ARG_COUNT(args[0]); state->pos.row += count; state->at_phantom = 0; break; case 0x43: // CUF - ECMA-48 8.3.20 count = CSI_ARG_COUNT(args[0]); state->pos.col += count; state->at_phantom = 0; break; case 0x44: // CUB - ECMA-48 8.3.18 count = CSI_ARG_COUNT(args[0]); state->pos.col -= count; state->at_phantom = 0; break; case 0x45: // CNL - ECMA-48 8.3.12 count = CSI_ARG_COUNT(args[0]); state->pos.col = 0; state->pos.row += count; state->at_phantom = 0; break; case 0x46: // CPL - ECMA-48 8.3.13 count = CSI_ARG_COUNT(args[0]); state->pos.col = 0; state->pos.row -= count; state->at_phantom = 0; break; case 0x47: // CHA - ECMA-48 8.3.9 val = CSI_ARG_OR(args[0], 1); state->pos.col = val-1; state->at_phantom = 0; break; case 0x48: // CUP - ECMA-48 8.3.21 row = CSI_ARG_OR(args[0], 1); col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); // zero-based state->pos.row = row-1; state->pos.col = col-1; if(state->mode.origin) { state->pos.row += state->scrollregion_top; state->pos.col += SCROLLREGION_LEFT(state); } state->at_phantom = 0; break; case 0x49: // CHT - ECMA-48 8.3.10 count = CSI_ARG_COUNT(args[0]); tab(state, count, +1); break; case 0x4a: // ED - ECMA-48 8.3.39 case LEADER('?', 0x4a): // DECSED - Selective Erase in Display selective = (leader_byte == '?'); switch(CSI_ARG(args[0])) { case CSI_ARG_MISSING: case 0: rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; rect.end_col = state->cols; if(rect.end_col > rect.start_col) erase(state, rect, selective); rect.start_row = state->pos.row + 1; rect.end_row = state->rows; rect.start_col = 0; for(int row = rect.start_row; row < rect.end_row; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); if(rect.end_row > rect.start_row) erase(state, rect, selective); break; case 1: rect.start_row = 0; rect.end_row = state->pos.row; rect.start_col = 0; rect.end_col = state->cols; for(int row = rect.start_row; row < rect.end_row; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); if(rect.end_col > rect.start_col) erase(state, rect, selective); rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.end_col = state->pos.col + 1; if(rect.end_row > rect.start_row) erase(state, rect, selective); break; case 2: rect.start_row = 0; rect.end_row = state->rows; rect.start_col = 0; rect.end_col = state->cols; for(int row = rect.start_row; row < rect.end_row; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); erase(state, rect, selective); break; } break; case 0x4b: // EL - ECMA-48 8.3.41 case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line selective = (leader_byte == '?'); rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; switch(CSI_ARG(args[0])) { case CSI_ARG_MISSING: case 0: rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; case 1: rect.start_col = 0; rect.end_col = state->pos.col + 1; break; case 2: rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; default: return 0; } if(rect.end_col > rect.start_col) erase(state, rect, selective); break; case 0x4c: // IL - ECMA-48 8.3.67 count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; rect.start_row = state->pos.row; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = SCROLLREGION_LEFT(state); rect.end_col = SCROLLREGION_RIGHT(state); scroll(state, rect, -count, 0); break; case 0x4d: // DL - ECMA-48 8.3.32 count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; rect.start_row = state->pos.row; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = SCROLLREGION_LEFT(state); rect.end_col = SCROLLREGION_RIGHT(state); scroll(state, rect, count, 0); break; case 0x50: // DCH - ECMA-48 8.3.26 count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; if(state->mode.leftrightmargin) rect.end_col = SCROLLREGION_RIGHT(state); else rect.end_col = THISROWWIDTH(state); scroll(state, rect, 0, count); break; case 0x53: // SU - ECMA-48 8.3.147 count = CSI_ARG_COUNT(args[0]); rect.start_row = state->scrollregion_top; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = SCROLLREGION_LEFT(state); rect.end_col = SCROLLREGION_RIGHT(state); scroll(state, rect, count, 0); break; case 0x54: // SD - ECMA-48 8.3.113 count = CSI_ARG_COUNT(args[0]); rect.start_row = state->scrollregion_top; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = SCROLLREGION_LEFT(state); rect.end_col = SCROLLREGION_RIGHT(state); scroll(state, rect, -count, 0); break; case 0x58: // ECH - ECMA-48 8.3.38 count = CSI_ARG_COUNT(args[0]); rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; rect.end_col = state->pos.col + count; UBOUND(rect.end_col, THISROWWIDTH(state)); erase(state, rect, 0); break; case 0x5a: // CBT - ECMA-48 8.3.7 count = CSI_ARG_COUNT(args[0]); tab(state, count, -1); break; case 0x60: // HPA - ECMA-48 8.3.57 col = CSI_ARG_OR(args[0], 1); state->pos.col = col-1; state->at_phantom = 0; break; case 0x61: // HPR - ECMA-48 8.3.59 count = CSI_ARG_COUNT(args[0]); state->pos.col += count; state->at_phantom = 0; break; case 0x62: { // REP - ECMA-48 8.3.103 const int row_width = THISROWWIDTH(state); count = CSI_ARG_COUNT(args[0]); col = state->pos.col + count; UBOUND(col, row_width); while (state->pos.col < col) { putglyph(state, state->combine_chars, state->combine_width, state->pos); state->pos.col += state->combine_width; } if (state->pos.col + state->combine_width >= row_width) { if (state->mode.autowrap) { state->at_phantom = 1; cancel_phantom = 0; } } break; } case 0x63: // DA - ECMA-48 8.3.24 val = CSI_ARG_OR(args[0], 0); if(val == 0) // DEC VT100 response vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); break; case LEADER('>', 0x63): // DEC secondary Device Attributes vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); break; case 0x64: // VPA - ECMA-48 8.3.158 row = CSI_ARG_OR(args[0], 1); state->pos.row = row-1; if(state->mode.origin) state->pos.row += state->scrollregion_top; state->at_phantom = 0; break; case 0x65: // VPR - ECMA-48 8.3.160 count = CSI_ARG_COUNT(args[0]); state->pos.row += count; state->at_phantom = 0; break; case 0x66: // HVP - ECMA-48 8.3.63 row = CSI_ARG_OR(args[0], 1); col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); // zero-based state->pos.row = row-1; state->pos.col = col-1; if(state->mode.origin) { state->pos.row += state->scrollregion_top; state->pos.col += SCROLLREGION_LEFT(state); } state->at_phantom = 0; break; case 0x67: // TBC - ECMA-48 8.3.154 val = CSI_ARG_OR(args[0], 0); switch(val) { case 0: clear_col_tabstop(state, state->pos.col); break; case 3: case 5: for(col = 0; col < state->cols; col++) clear_col_tabstop(state, col); break; case 1: case 2: case 4: break; /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ default: return 0; } break; case 0x68: // SM - ECMA-48 8.3.125 if(!CSI_ARG_IS_MISSING(args[0])) set_mode(state, CSI_ARG(args[0]), 1); break; case LEADER('?', 0x68): // DEC private mode set if(!CSI_ARG_IS_MISSING(args[0])) set_dec_mode(state, CSI_ARG(args[0]), 1); break; case 0x6a: // HPB - ECMA-48 8.3.58 count = CSI_ARG_COUNT(args[0]); state->pos.col -= count; state->at_phantom = 0; break; case 0x6b: // VPB - ECMA-48 8.3.159 count = CSI_ARG_COUNT(args[0]); state->pos.row -= count; state->at_phantom = 0; break; case 0x6c: // RM - ECMA-48 8.3.106 if(!CSI_ARG_IS_MISSING(args[0])) set_mode(state, CSI_ARG(args[0]), 0); break; case LEADER('?', 0x6c): // DEC private mode reset if(!CSI_ARG_IS_MISSING(args[0])) set_dec_mode(state, CSI_ARG(args[0]), 0); break; case 0x6d: // SGR - ECMA-48 8.3.117 vterm_state_setpen(state, args, argcount); break; case 0x6e: // DSR - ECMA-48 8.3.35 case LEADER('?', 0x6e): // DECDSR val = CSI_ARG_OR(args[0], 0); { char *qmark = (leader_byte == '?') ? "?" : ""; switch(val) { case 0: case 1: case 2: case 3: case 4: // ignore - these are replies break; case 5: vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); break; case 6: // CPR - cursor position report vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); break; } } break; case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset vterm_state_reset(state, 0); break; case LEADER('?', INTERMED('$', 0x70)): request_dec_mode(state, CSI_ARG(args[0])); break; case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape val = CSI_ARG_OR(args[0], 1); switch(val) { case 0: case 1: settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); break; case 2: settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); break; case 3: settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); break; case 4: settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); break; case 5: settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); break; case 6: settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); break; } break; case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute val = CSI_ARG_OR(args[0], 0); switch(val) { case 0: case 2: state->protected_cell = 0; break; case 1: state->protected_cell = 1; break; } break; case 0x72: // DECSTBM - DEC custom state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); LBOUND(state->scrollregion_top, 0); UBOUND(state->scrollregion_top, state->rows); LBOUND(state->scrollregion_bottom, -1); if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) state->scrollregion_bottom = -1; else UBOUND(state->scrollregion_bottom, state->rows); if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { // Invalid state->scrollregion_top = 0; state->scrollregion_bottom = -1; } // Setting the scrolling region restores the cursor to the home position state->pos.row = 0; state->pos.col = 0; if(state->mode.origin) { state->pos.row += state->scrollregion_top; state->pos.col += SCROLLREGION_LEFT(state); } break; case 0x73: // DECSLRM - DEC custom // Always allow setting these margins, just they won't take effect without DECVSSM state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); LBOUND(state->scrollregion_left, 0); UBOUND(state->scrollregion_left, state->cols); LBOUND(state->scrollregion_right, -1); if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) state->scrollregion_right = -1; else UBOUND(state->scrollregion_right, state->cols); if(state->scrollregion_right > -1 && state->scrollregion_right <= state->scrollregion_left) { // Invalid state->scrollregion_left = 0; state->scrollregion_right = -1; } // Setting the scrolling region restores the cursor to the home position state->pos.row = 0; state->pos.col = 0; if(state->mode.origin) { state->pos.row += state->scrollregion_top; state->pos.col += SCROLLREGION_LEFT(state); } break; case INTERMED('\'', 0x7D): // DECIC count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; rect.start_row = state->scrollregion_top; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = state->pos.col; rect.end_col = SCROLLREGION_RIGHT(state); scroll(state, rect, 0, -count); break; case INTERMED('\'', 0x7E): // DECDC count = CSI_ARG_COUNT(args[0]); if(!is_cursor_in_scrollregion(state)) break; rect.start_row = state->scrollregion_top; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = state->pos.col; rect.end_col = SCROLLREGION_RIGHT(state); scroll(state, rect, 0, count); break; default: if(state->fallbacks && state->fallbacks->csi) if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) return 1; return 0; } if(state->mode.origin) { LBOUND(state->pos.row, state->scrollregion_top); UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); } else { LBOUND(state->pos.row, 0); UBOUND(state->pos.row, state->rows-1); LBOUND(state->pos.col, 0); UBOUND(state->pos.col, THISROWWIDTH(state)-1); } updatecursor(state, &oldpos, cancel_phantom); #ifdef DEBUG if(state->pos.row < 0 || state->pos.row >= state->rows || state->pos.col < 0 || state->pos.col >= state->cols) { fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", command, state->pos.row, state->pos.col); abort(); } if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); abort(); } if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); abort(); } #endif return 1; } static int on_osc(const char *command, size_t cmdlen, void *user) { VTermState *state = user; if(cmdlen < 2) return 0; if(strneq(command, "0;", 2)) { settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); return 1; } else if(strneq(command, "1;", 2)) { settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); return 1; } else if(strneq(command, "2;", 2)) { settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); return 1; } else if(state->fallbacks && state->fallbacks->osc) if((*state->fallbacks->osc)(command, cmdlen, state->fbdata)) return 1; return 0; } static void request_status_string(VTermState *state, const char *command, size_t cmdlen) { VTerm *vt = state->vt; if(cmdlen == 1) switch(command[0]) { case 'm': // Query SGR { long args[20]; int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); size_t cur = 0; cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... if(cur >= vt->tmpbuffer_len) return; for(int argi = 0; argi < argc; argi++) { cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, argi == argc - 1 ? "%ld" : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : "%ld;", CSI_ARG(args[argi])); if(cur >= vt->tmpbuffer_len) return; } cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST if(cur >= vt->tmpbuffer_len) return; vterm_push_output_bytes(vt, vt->tmpbuffer, cur); } return; case 'r': // Query DECSTBM vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); return; case 's': // Query DECSLRM vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); return; } if(cmdlen == 2) { if(strneq(command, " q", 2)) { int reply; switch(state->mode.cursor_shape) { case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; } if(state->mode.cursor_blink) reply--; vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply); return; } else if(strneq(command, "\"q", 2)) { vterm_push_output_sprintf_dcs(vt, "1$r%d\"q", state->protected_cell ? 1 : 2); return; } } vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command); } static int on_dcs(const char *command, size_t cmdlen, void *user) { VTermState *state = user; if(cmdlen >= 2 && strneq(command, "$q", 2)) { request_status_string(state, command+2, cmdlen-2); return 1; } else if(state->fallbacks && state->fallbacks->dcs) if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata)) return 1; return 0; } static int on_resize(int rows, int cols, void *user) { VTermState *state = user; VTermPos oldpos = state->pos; if(cols != state->cols) { unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); /* TODO: This can all be done much more efficiently bytewise */ int col; for(col = 0; col < state->cols && col < cols; col++) { unsigned char mask = 1 << (col & 7); if(state->tabstops[col >> 3] & mask) newtabstops[col >> 3] |= mask; else newtabstops[col >> 3] &= ~mask; } for( ; col < cols; col++) { unsigned char mask = 1 << (col & 7); if(col % 8 == 0) newtabstops[col >> 3] |= mask; else newtabstops[col >> 3] &= ~mask; } vterm_allocator_free(state->vt, state->tabstops); state->tabstops = newtabstops; } if(rows != state->rows) { VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); int row; for(row = 0; row < state->rows && row < rows; row++) { newlineinfo[row] = state->lineinfo[row]; } for( ; row < rows; row++) { newlineinfo[row] = (VTermLineInfo){ .doublewidth = 0, }; } vterm_allocator_free(state->vt, state->lineinfo); state->lineinfo = newlineinfo; } state->rows = rows; state->cols = cols; if(state->scrollregion_bottom > -1) UBOUND(state->scrollregion_bottom, state->rows); if(state->scrollregion_right > -1) UBOUND(state->scrollregion_right, state->cols); VTermPos delta = { 0, 0 }; if(state->callbacks && state->callbacks->resize) (*state->callbacks->resize)(rows, cols, &delta, state->cbdata); if(state->at_phantom && state->pos.col < cols-1) { state->at_phantom = 0; state->pos.col++; } state->pos.row += delta.row; state->pos.col += delta.col; if(state->pos.row >= rows) state->pos.row = rows - 1; if(state->pos.col >= cols) state->pos.col = cols - 1; updatecursor(state, &oldpos, 1); return 1; } static const VTermParserCallbacks parser_callbacks = { .text = on_text, .control = on_control, .escape = on_escape, .csi = on_csi, .osc = on_osc, .dcs = on_dcs, .resize = on_resize, }; VTermState *vterm_obtain_state(VTerm *vt) { if(vt->state) return vt->state; VTermState *state = vterm_state_new(vt); vt->state = state; state->combine_chars_size = 16; state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); if(*state->encoding_utf8.enc->init) (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); vterm_parser_set_callbacks(vt, &parser_callbacks, state); return state; } void vterm_state_reset(VTermState *state, int hard) { state->scrollregion_top = 0; state->scrollregion_bottom = -1; state->scrollregion_left = 0; state->scrollregion_right = -1; state->mode.keypad = 0; state->mode.cursor = 0; state->mode.autowrap = 1; state->mode.insert = 0; state->mode.newline = 0; state->mode.alt_screen = 0; state->mode.origin = 0; state->mode.leftrightmargin = 0; state->mode.bracketpaste = 0; state->mode.report_focus = 0; state->vt->mode.ctrl8bit = 0; for(int col = 0; col < state->cols; col++) if(col % 8 == 0) set_col_tabstop(state, col); else clear_col_tabstop(state, col); for(int row = 0; row < state->rows; row++) set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); if(state->callbacks && state->callbacks->initpen) (*state->callbacks->initpen)(state->cbdata); vterm_state_resetpen(state); VTermEncoding *default_enc = state->vt->mode.utf8 ? vterm_lookup_encoding(ENC_UTF8, 'u') : vterm_lookup_encoding(ENC_SINGLE_94, 'B'); for(int i = 0; i < 4; i++) { state->encoding[i].enc = default_enc; if(default_enc->init) (*default_enc->init)(default_enc, state->encoding[i].data); } state->gl_set = 0; state->gr_set = 1; state->gsingle_set = 0; state->protected_cell = 0; // Initialise the props settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); if(hard) { state->pos.row = 0; state->pos.col = 0; state->at_phantom = 0; VTermRect rect = { 0, state->rows, 0, state->cols }; erase(state, rect, 0); } } void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) { *cursorpos = state->pos; } void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) { if(callbacks) { state->callbacks = callbacks; state->cbdata = user; if(state->callbacks && state->callbacks->initpen) (*state->callbacks->initpen)(state->cbdata); } else { state->callbacks = NULL; state->cbdata = NULL; } } void *vterm_state_get_cbdata(VTermState *state) { return state->cbdata; } void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user) { if(fallbacks) { state->fallbacks = fallbacks; state->fbdata = user; } else { state->fallbacks = NULL; state->fbdata = NULL; } } void *vterm_state_get_unrecognised_fbdata(VTermState *state) { return state->fbdata; } int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) { /* Only store the new value of the property if usercode said it was happy. * This is especially important for altscreen switching */ if(state->callbacks && state->callbacks->settermprop) if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) return 0; switch(prop) { case VTERM_PROP_TITLE: case VTERM_PROP_ICONNAME: // we don't store these, just transparently pass through return 1; case VTERM_PROP_CURSORVISIBLE: state->mode.cursor_visible = val->boolean; return 1; case VTERM_PROP_CURSORBLINK: state->mode.cursor_blink = val->boolean; return 1; case VTERM_PROP_CURSORSHAPE: state->mode.cursor_shape = val->number; return 1; case VTERM_PROP_REVERSE: state->mode.screen = val->boolean; return 1; case VTERM_PROP_ALTSCREEN: state->mode.alt_screen = val->boolean; if(state->mode.alt_screen) { VTermRect rect = { .start_row = 0, .start_col = 0, .end_row = state->rows, .end_col = state->cols, }; erase(state, rect, 0); } return 1; case VTERM_PROP_MOUSE: state->mouse_flags = 0; if(val->number) state->mouse_flags |= MOUSE_WANT_CLICK; if(val->number == VTERM_PROP_MOUSE_DRAG) state->mouse_flags |= MOUSE_WANT_DRAG; if(val->number == VTERM_PROP_MOUSE_MOVE) state->mouse_flags |= MOUSE_WANT_MOVE; return 1; case VTERM_N_PROPS: return 0; } return 0; } void vterm_state_focus_in(VTermState *state) { if(state->mode.report_focus) vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); } void vterm_state_focus_out(VTermState *state) { if(state->mode.report_focus) vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); } const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) { return state->lineinfo + row; } libvterm-0.1.4/src/vterm_internal.h0000644000175000017500000001400213720230652015446 0ustar leoleo#ifndef __VTERM_INTERNAL_H__ #define __VTERM_INTERNAL_H__ #include "vterm.h" #include #if defined(__GNUC__) # define INTERNAL __attribute__((visibility("internal"))) #else # define INTERNAL #endif #ifdef DEBUG # define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) #else # define DEBUG_LOG(...) #endif #define ESC_S "\x1b" #define INTERMED_MAX 16 #define CSI_ARGS_MAX 16 #define CSI_LEADER_MAX 16 typedef struct VTermEncoding VTermEncoding; typedef struct { VTermEncoding *enc; // This size should be increased if required by other stateful encodings char data[4*sizeof(uint32_t)]; } VTermEncodingInstance; struct VTermPen { VTermColor fg; VTermColor bg; unsigned int bold:1; unsigned int underline:2; unsigned int italic:1; unsigned int blink:1; unsigned int reverse:1; unsigned int strike:1; unsigned int font:4; /* To store 0-9 */ }; struct VTermState { VTerm *vt; const VTermStateCallbacks *callbacks; void *cbdata; const VTermParserCallbacks *fallbacks; void *fbdata; int rows; int cols; /* Current cursor position */ VTermPos pos; int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ int scrollregion_top; int scrollregion_bottom; /* -1 means unbounded */ #define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) int scrollregion_left; #define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) int scrollregion_right; /* -1 means unbounded */ #define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) /* Bitvector of tab stops */ unsigned char *tabstops; VTermLineInfo *lineinfo; #define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) #define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) /* Mouse state */ int mouse_col, mouse_row; int mouse_buttons; int mouse_flags; #define MOUSE_WANT_CLICK 0x01 #define MOUSE_WANT_DRAG 0x02 #define MOUSE_WANT_MOVE 0x04 enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; /* Last glyph output, for Unicode recombining purposes */ uint32_t *combine_chars; size_t combine_chars_size; // Number of ELEMENTS in the above int combine_width; // The width of the glyph above VTermPos combine_pos; // Position before movement struct { unsigned int keypad:1; unsigned int cursor:1; unsigned int autowrap:1; unsigned int insert:1; unsigned int newline:1; unsigned int cursor_visible:1; unsigned int cursor_blink:1; unsigned int cursor_shape:2; unsigned int alt_screen:1; unsigned int origin:1; unsigned int screen:1; unsigned int leftrightmargin:1; unsigned int bracketpaste:1; unsigned int report_focus:1; } mode; VTermEncodingInstance encoding[4], encoding_utf8; int gl_set, gr_set, gsingle_set; struct VTermPen pen; VTermColor default_fg; VTermColor default_bg; VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only int bold_is_highbright; unsigned int protected_cell : 1; /* Saved state under DEC mode 1048/1049 */ struct { VTermPos pos; struct VTermPen pen; struct { unsigned int cursor_visible:1; unsigned int cursor_blink:1; unsigned int cursor_shape:2; } mode; } saved; }; typedef enum { VTERM_PARSER_OSC, VTERM_PARSER_DCS, VTERM_N_PARSER_TYPES } VTermParserStringType; struct VTerm { VTermAllocatorFunctions *allocator; void *allocdata; int rows; int cols; struct { unsigned int utf8:1; unsigned int ctrl8bit:1; } mode; struct { enum VTermParserState { NORMAL, CSI_LEADER, CSI_ARGS, CSI_INTERMED, ESC, /* below here are the "string states" */ STRING, ESC_IN_STRING, } state; int intermedlen; char intermed[INTERMED_MAX]; int csi_leaderlen; char csi_leader[CSI_LEADER_MAX]; int csi_argi; long csi_args[CSI_ARGS_MAX]; const VTermParserCallbacks *callbacks; void *cbdata; VTermParserStringType stringtype; char *strbuffer; size_t strbuffer_len; size_t strbuffer_cur; } parser; /* len == malloc()ed size; cur == number of valid bytes */ VTermOutputCallback *outfunc; void *outdata; char *outbuffer; size_t outbuffer_len; size_t outbuffer_cur; char *tmpbuffer; size_t tmpbuffer_len; VTermState *state; VTermScreen *screen; }; struct VTermEncoding { void (*init) (VTermEncoding *enc, void *data); void (*decode)(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t len); }; typedef enum { ENC_UTF8, ENC_SINGLE_94 } VTermEncodingType; void *vterm_allocator_malloc(VTerm *vt, size_t size); void vterm_allocator_free(VTerm *vt, void *ptr); void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...); void vterm_state_free(VTermState *state); void vterm_state_newpen(VTermState *state); void vterm_state_resetpen(VTermState *state); void vterm_state_setpen(VTermState *state, const long args[], int argcount); int vterm_state_getpen(VTermState *state, long args[], int argcount); void vterm_state_savepen(VTermState *state, int save); enum { C1_SS3 = 0x8f, C1_DCS = 0x90, C1_CSI = 0x9b, C1_ST = 0x9c, }; void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); void vterm_screen_free(VTermScreen *screen); VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); int vterm_unicode_width(uint32_t codepoint); int vterm_unicode_is_combining(uint32_t codepoint); #endif libvterm-0.1.4/src/encoding/0002755000175000017500000000000013720230652014037 5ustar leoleolibvterm-0.1.4/src/encoding/uk.inc0000644000175000017500000000016713720230652015153 0ustar leoleostatic const struct StaticTableEncoding encoding_uk = { { .decode = &decode_table }, { [0x23] = 0x00a3, } }; libvterm-0.1.4/src/encoding/DECdrawing.inc0000644000175000017500000000136513720230652016504 0ustar leoleostatic const struct StaticTableEncoding encoding_DECdrawing = { { .decode = &decode_table }, { [0x60] = 0x25C6, [0x61] = 0x2592, [0x62] = 0x2409, [0x63] = 0x240C, [0x64] = 0x240D, [0x65] = 0x240A, [0x66] = 0x00B0, [0x67] = 0x00B1, [0x68] = 0x2424, [0x69] = 0x240B, [0x6a] = 0x2518, [0x6b] = 0x2510, [0x6c] = 0x250C, [0x6d] = 0x2514, [0x6e] = 0x253C, [0x6f] = 0x23BA, [0x70] = 0x23BB, [0x71] = 0x2500, [0x72] = 0x23BC, [0x73] = 0x23BD, [0x74] = 0x251C, [0x75] = 0x2524, [0x76] = 0x2534, [0x77] = 0x252C, [0x78] = 0x2502, [0x79] = 0x2A7D, [0x7a] = 0x2A7E, [0x7b] = 0x03C0, [0x7c] = 0x2260, [0x7d] = 0x00A3, [0x7e] = 0x00B7, } }; libvterm-0.1.4/src/mouse.c0000644000175000017500000000464413720230652013553 0ustar leoleo#include "vterm_internal.h" #include "utf8.h" static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) { modifiers <<= 2; switch(state->mouse_protocol) { case MOUSE_X10: if(col + 0x21 > 0xff) col = 0xff - 0x21; if(row + 0x21 > 0xff) row = 0xff - 0x21; if(!pressed) code = 3; vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", (code | modifiers) + 0x20, col + 0x21, row + 0x21); break; case MOUSE_UTF8: { char utf8[18]; size_t len = 0; if(!pressed) code = 3; len += fill_utf8((code | modifiers) + 0x20, utf8 + len); len += fill_utf8(col + 0x21, utf8 + len); len += fill_utf8(row + 0x21, utf8 + len); utf8[len] = 0; vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); } break; case MOUSE_SGR: vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); break; case MOUSE_RXVT: if(!pressed) code = 3; vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", code | modifiers, col + 1, row + 1); break; } } void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) { VTermState *state = vt->state; if(col == state->mouse_col && row == state->mouse_row) return; state->mouse_col = col; state->mouse_row = row; if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || (state->mouse_flags & MOUSE_WANT_MOVE)) { int button = state->mouse_buttons & 0x01 ? 1 : state->mouse_buttons & 0x02 ? 2 : state->mouse_buttons & 0x04 ? 3 : 4; output_mouse(state, button-1 + 0x20, 1, mod, col, row); } } void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) { VTermState *state = vt->state; int old_buttons = state->mouse_buttons; if(button > 0 && button <= 3) { if(pressed) state->mouse_buttons |= (1 << (button-1)); else state->mouse_buttons &= ~(1 << (button-1)); } /* Most of the time we don't get button releases from 4/5 */ if(state->mouse_buttons == old_buttons && button < 4) return; if(button < 4) { output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row); } else if(button < 6) { output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); } } libvterm-0.1.4/src/screen.c0000644000175000017500000006251213720230652013700 0ustar leoleo#include "vterm_internal.h" #include #include #include "rect.h" #include "utf8.h" #define UNICODE_SPACE 0x20 #define UNICODE_LINEFEED 0x0a /* State of the pen at some moment in time, also used in a cell */ typedef struct { /* After the bitfield */ VTermColor fg, bg; unsigned int bold : 1; unsigned int underline : 2; unsigned int italic : 1; unsigned int blink : 1; unsigned int reverse : 1; unsigned int strike : 1; unsigned int font : 4; /* 0 to 9 */ /* Extra state storage that isn't strictly pen-related */ unsigned int protected_cell : 1; unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ } ScreenPen; /* Internal representation of a screen cell */ typedef struct { uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; ScreenPen pen; } ScreenCell; static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell); struct VTermScreen { VTerm *vt; VTermState *state; const VTermScreenCallbacks *callbacks; void *cbdata; VTermDamageSize damage_merge; /* start_row == -1 => no damage */ VTermRect damaged; VTermRect pending_scrollrect; int pending_scroll_downward, pending_scroll_rightward; int rows; int cols; int global_reverse; /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ ScreenCell *buffers[2]; /* buffer will == buffers[0] or buffers[1], depending on altscreen */ ScreenCell *buffer; /* buffer for a single screen row used in scrollback storage callbacks */ VTermScreenCell *sb_buffer; ScreenPen pen; }; static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) { if(row < 0 || row >= screen->rows) return NULL; if(col < 0 || col >= screen->cols) return NULL; return screen->buffer + (screen->cols * row) + col; } static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) { ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); for(int row = 0; row < new_rows; row++) { for(int col = 0; col < new_cols; col++) { ScreenCell *new_cell = new_buffer + row*new_cols + col; if(buffer && row < screen->rows && col < screen->cols) *new_cell = buffer[row * screen->cols + col]; else { new_cell->chars[0] = 0; new_cell->pen = screen->pen; } } } if(buffer) vterm_allocator_free(screen->vt, buffer); return new_buffer; } static void damagerect(VTermScreen *screen, VTermRect rect) { VTermRect emit; switch(screen->damage_merge) { case VTERM_DAMAGE_CELL: /* Always emit damage event */ emit = rect; break; case VTERM_DAMAGE_ROW: /* Emit damage longer than one row. Try to merge with existing damage in * the same row */ if(rect.end_row > rect.start_row + 1) { // Bigger than 1 line - flush existing, emit this vterm_screen_flush_damage(screen); emit = rect; } else if(screen->damaged.start_row == -1) { // None stored yet screen->damaged = rect; return; } else if(rect.start_row == screen->damaged.start_row) { // Merge with the stored line if(screen->damaged.start_col > rect.start_col) screen->damaged.start_col = rect.start_col; if(screen->damaged.end_col < rect.end_col) screen->damaged.end_col = rect.end_col; return; } else { // Emit the currently stored line, store a new one emit = screen->damaged; screen->damaged = rect; } break; case VTERM_DAMAGE_SCREEN: case VTERM_DAMAGE_SCROLL: /* Never emit damage event */ if(screen->damaged.start_row == -1) screen->damaged = rect; else { rect_expand(&screen->damaged, &rect); } return; default: DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); return; } if(screen->callbacks && screen->callbacks->damage) (*screen->callbacks->damage)(emit, screen->cbdata); } static void damagescreen(VTermScreen *screen) { VTermRect rect = { .start_row = 0, .end_row = screen->rows, .start_col = 0, .end_col = screen->cols, }; damagerect(screen, rect); } static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) { VTermScreen *screen = user; ScreenCell *cell = getcell(screen, pos.row, pos.col); if(!cell) return 0; int i; for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { cell->chars[i] = info->chars[i]; cell->pen = screen->pen; } if(i < VTERM_MAX_CHARS_PER_CELL) cell->chars[i] = 0; for(int col = 1; col < info->width; col++) getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; VTermRect rect = { .start_row = pos.row, .end_row = pos.row+1, .start_col = pos.col, .end_col = pos.col+info->width, }; cell->pen.protected_cell = info->protected_cell; cell->pen.dwl = info->dwl; cell->pen.dhl = info->dhl; damagerect(screen, rect); return 1; } static int moverect_internal(VTermRect dest, VTermRect src, void *user) { VTermScreen *screen = user; if(screen->callbacks && screen->callbacks->sb_pushline && dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner dest.end_col == screen->cols && // full width screen->buffer == screen->buffers[0]) { // not altscreen VTermPos pos; for(pos.row = 0; pos.row < src.start_row; pos.row++) { for(pos.col = 0; pos.col < screen->cols; pos.col++) vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); } } int cols = src.end_col - src.start_col; int downward = src.start_row - dest.start_row; int init_row, test_row, inc_row; if(downward < 0) { init_row = dest.end_row - 1; test_row = dest.start_row - 1; inc_row = -1; } else { init_row = dest.start_row; test_row = dest.end_row; inc_row = +1; } for(int row = init_row; row != test_row; row += inc_row) memmove(getcell(screen, row, dest.start_col), getcell(screen, row + downward, src.start_col), cols * sizeof(ScreenCell)); return 1; } static int moverect_user(VTermRect dest, VTermRect src, void *user) { VTermScreen *screen = user; if(screen->callbacks && screen->callbacks->moverect) { if(screen->damage_merge != VTERM_DAMAGE_SCROLL) // Avoid an infinite loop vterm_screen_flush_damage(screen); if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) return 1; } damagerect(screen, dest); return 1; } static int erase_internal(VTermRect rect, int selective, void *user) { VTermScreen *screen = user; for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); for(int col = rect.start_col; col < rect.end_col; col++) { ScreenCell *cell = getcell(screen, row, col); if(selective && cell->pen.protected_cell) continue; cell->chars[0] = 0; cell->pen = screen->pen; cell->pen.dwl = info->doublewidth; cell->pen.dhl = info->doubleheight; } } return 1; } static int erase_user(VTermRect rect, int selective, void *user) { VTermScreen *screen = user; damagerect(screen, rect); return 1; } static int erase(VTermRect rect, int selective, void *user) { erase_internal(rect, selective, user); return erase_user(rect, 0, user); } static int scrollrect(VTermRect rect, int downward, int rightward, void *user) { VTermScreen *screen = user; if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { vterm_scroll_rect(rect, downward, rightward, moverect_internal, erase_internal, screen); vterm_screen_flush_damage(screen); vterm_scroll_rect(rect, downward, rightward, moverect_user, erase_user, screen); return 1; } if(screen->damaged.start_row != -1 && !rect_intersects(&rect, &screen->damaged)) { vterm_screen_flush_damage(screen); } if(screen->pending_scrollrect.start_row == -1) { screen->pending_scrollrect = rect; screen->pending_scroll_downward = downward; screen->pending_scroll_rightward = rightward; } else if(rect_equal(&screen->pending_scrollrect, &rect) && ((screen->pending_scroll_downward == 0 && downward == 0) || (screen->pending_scroll_rightward == 0 && rightward == 0))) { screen->pending_scroll_downward += downward; screen->pending_scroll_rightward += rightward; } else { vterm_screen_flush_damage(screen); screen->pending_scrollrect = rect; screen->pending_scroll_downward = downward; screen->pending_scroll_rightward = rightward; } vterm_scroll_rect(rect, downward, rightward, moverect_internal, erase_internal, screen); if(screen->damaged.start_row == -1) return 1; if(rect_contains(&rect, &screen->damaged)) { /* Scroll region entirely contains the damage; just move it */ vterm_rect_move(&screen->damaged, -downward, -rightward); rect_clip(&screen->damaged, &rect); } /* There are a number of possible cases here, but lets restrict this to only * the common case where we might actually gain some performance by * optimising it. Namely, a vertical scroll that neatly cuts the damage * region in half. */ else if(rect.start_col <= screen->damaged.start_col && rect.end_col >= screen->damaged.end_col && rightward == 0) { if(screen->damaged.start_row >= rect.start_row && screen->damaged.start_row < rect.end_row) { screen->damaged.start_row -= downward; if(screen->damaged.start_row < rect.start_row) screen->damaged.start_row = rect.start_row; if(screen->damaged.start_row > rect.end_row) screen->damaged.start_row = rect.end_row; } if(screen->damaged.end_row >= rect.start_row && screen->damaged.end_row < rect.end_row) { screen->damaged.end_row -= downward; if(screen->damaged.end_row < rect.start_row) screen->damaged.end_row = rect.start_row; if(screen->damaged.end_row > rect.end_row) screen->damaged.end_row = rect.end_row; } } else { DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", ARGSrect(screen->damaged), ARGSrect(rect)); } return 1; } static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) { VTermScreen *screen = user; if(screen->callbacks && screen->callbacks->movecursor) return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); return 0; } static int setpenattr(VTermAttr attr, VTermValue *val, void *user) { VTermScreen *screen = user; switch(attr) { case VTERM_ATTR_BOLD: screen->pen.bold = val->boolean; return 1; case VTERM_ATTR_UNDERLINE: screen->pen.underline = val->number; return 1; case VTERM_ATTR_ITALIC: screen->pen.italic = val->boolean; return 1; case VTERM_ATTR_BLINK: screen->pen.blink = val->boolean; return 1; case VTERM_ATTR_REVERSE: screen->pen.reverse = val->boolean; return 1; case VTERM_ATTR_STRIKE: screen->pen.strike = val->boolean; return 1; case VTERM_ATTR_FONT: screen->pen.font = val->number; return 1; case VTERM_ATTR_FOREGROUND: screen->pen.fg = val->color; return 1; case VTERM_ATTR_BACKGROUND: screen->pen.bg = val->color; return 1; case VTERM_N_ATTRS: return 0; } return 0; } static int settermprop(VTermProp prop, VTermValue *val, void *user) { VTermScreen *screen = user; switch(prop) { case VTERM_PROP_ALTSCREEN: if(val->boolean && !screen->buffers[1]) return 0; screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0]; /* only send a damage event on disable; because during enable there's an * erase that sends a damage anyway */ if(!val->boolean) damagescreen(screen); break; case VTERM_PROP_REVERSE: screen->global_reverse = val->boolean; damagescreen(screen); break; default: ; /* ignore */ } if(screen->callbacks && screen->callbacks->settermprop) return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); return 1; } static int bell(void *user) { VTermScreen *screen = user; if(screen->callbacks && screen->callbacks->bell) return (*screen->callbacks->bell)(screen->cbdata); return 0; } static int resize(int new_rows, int new_cols, VTermPos *delta, void *user) { VTermScreen *screen = user; int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]); int old_rows = screen->rows; int old_cols = screen->cols; if(!is_altscreen && new_rows < old_rows) { // Fewer rows - determine if we're going to scroll at all, and if so, push // those lines to scrollback VTermPos pos = { 0, 0 }; VTermPos cursor = screen->state->pos; // Find the first blank row after the cursor. for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--) if(!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row) break; int first_blank_row = pos.row + 1; if(first_blank_row > new_rows) { VTermRect rect = { .start_row = 0, .end_row = old_rows, .start_col = 0, .end_col = old_cols, }; scrollrect(rect, first_blank_row - new_rows, 0, user); vterm_screen_flush_damage(screen); delta->row -= first_blank_row - new_rows; } } screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols); if(screen->buffers[1]) screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols); screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0]; screen->rows = new_rows; screen->cols = new_cols; if(screen->sb_buffer) vterm_allocator_free(screen->vt, screen->sb_buffer); screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); if(new_cols > old_cols) { VTermRect rect = { .start_row = 0, .end_row = old_rows, .start_col = old_cols, .end_col = new_cols, }; damagerect(screen, rect); } if(new_rows > old_rows) { if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) { int rows = new_rows - old_rows; while(rows) { if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata))) break; VTermRect rect = { .start_row = 0, .end_row = screen->rows, .start_col = 0, .end_col = screen->cols, }; scrollrect(rect, -1, 0, user); VTermPos pos = { 0, 0 }; for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width) vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col); rect.end_row = 1; damagerect(screen, rect); vterm_screen_flush_damage(screen); rows--; delta->row++; } } VTermRect rect = { .start_row = old_rows, .end_row = new_rows, .start_col = 0, .end_col = new_cols, }; damagerect(screen, rect); } if(screen->callbacks && screen->callbacks->resize) return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); return 1; } static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) { VTermScreen *screen = user; if(newinfo->doublewidth != oldinfo->doublewidth || newinfo->doubleheight != oldinfo->doubleheight) { for(int col = 0; col < screen->cols; col++) { ScreenCell *cell = getcell(screen, row, col); cell->pen.dwl = newinfo->doublewidth; cell->pen.dhl = newinfo->doubleheight; } VTermRect rect = { .start_row = row, .end_row = row + 1, .start_col = 0, .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, }; damagerect(screen, rect); if(newinfo->doublewidth) { rect.start_col = screen->cols / 2; rect.end_col = screen->cols; erase_internal(rect, 0, user); } } return 1; } static VTermStateCallbacks state_cbs = { .putglyph = &putglyph, .movecursor = &movecursor, .scrollrect = &scrollrect, .erase = &erase, .setpenattr = &setpenattr, .settermprop = &settermprop, .bell = &bell, .resize = &resize, .setlineinfo = &setlineinfo, }; static VTermScreen *screen_new(VTerm *vt) { VTermState *state = vterm_obtain_state(vt); if(!state) return NULL; VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); int rows, cols; vterm_get_size(vt, &rows, &cols); screen->vt = vt; screen->state = state; screen->damage_merge = VTERM_DAMAGE_CELL; screen->damaged.start_row = -1; screen->pending_scrollrect.start_row = -1; screen->rows = rows; screen->cols = cols; screen->callbacks = NULL; screen->cbdata = NULL; screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); screen->buffer = screen->buffers[0]; screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); vterm_state_set_callbacks(screen->state, &state_cbs, screen); return screen; } INTERNAL void vterm_screen_free(VTermScreen *screen) { vterm_allocator_free(screen->vt, screen->buffers[0]); if(screen->buffers[1]) vterm_allocator_free(screen->vt, screen->buffers[1]); vterm_allocator_free(screen->vt, screen->sb_buffer); vterm_allocator_free(screen->vt, screen); } void vterm_screen_reset(VTermScreen *screen, int hard) { screen->damaged.start_row = -1; screen->pending_scrollrect.start_row = -1; vterm_state_reset(screen->state, hard); vterm_screen_flush_damage(screen); } static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) { size_t outpos = 0; int padding = 0; #define PUT(c) \ if(utf8) { \ size_t thislen = utf8_seqlen(c); \ if(buffer && outpos + thislen <= len) \ outpos += fill_utf8((c), (char *)buffer + outpos); \ else \ outpos += thislen; \ } \ else { \ if(buffer && outpos + 1 <= len) \ ((uint32_t*)buffer)[outpos++] = (c); \ else \ outpos++; \ } for(int row = rect.start_row; row < rect.end_row; row++) { for(int col = rect.start_col; col < rect.end_col; col++) { ScreenCell *cell = getcell(screen, row, col); if(cell->chars[0] == 0) // Erased cell, might need a space padding++; else if(cell->chars[0] == (uint32_t)-1) // Gap behind a double-width char, do nothing ; else { while(padding) { PUT(UNICODE_SPACE); padding--; } for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { PUT(cell->chars[i]); } } } if(row < rect.end_row - 1) { PUT(UNICODE_LINEFEED); padding = 0; } } return outpos; } size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) { return _get_chars(screen, 0, chars, len, rect); } size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) { return _get_chars(screen, 1, str, len, rect); } /* Copy internal to external representation of a screen cell */ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) { ScreenCell *intcell = getcell(screen, pos.row, pos.col); if(!intcell) return 0; for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { cell->chars[i] = intcell->chars[i]; if(!intcell->chars[i]) break; } cell->attrs.bold = intcell->pen.bold; cell->attrs.underline = intcell->pen.underline; cell->attrs.italic = intcell->pen.italic; cell->attrs.blink = intcell->pen.blink; cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; cell->attrs.strike = intcell->pen.strike; cell->attrs.font = intcell->pen.font; cell->attrs.dwl = intcell->pen.dwl; cell->attrs.dhl = intcell->pen.dhl; cell->fg = intcell->pen.fg; cell->bg = intcell->pen.bg; if(pos.col < (screen->cols - 1) && getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) cell->width = 2; else cell->width = 1; return 1; } /* Copy external to internal representation of a screen cell */ /* static because it's only used internally for sb_popline during resize */ static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell) { ScreenCell *intcell = getcell(screen, pos.row, pos.col); if(!intcell) return 0; for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { intcell->chars[i] = cell->chars[i]; if(!cell->chars[i]) break; } intcell->pen.bold = cell->attrs.bold; intcell->pen.underline = cell->attrs.underline; intcell->pen.italic = cell->attrs.italic; intcell->pen.blink = cell->attrs.blink; intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse; intcell->pen.strike = cell->attrs.strike; intcell->pen.font = cell->attrs.font; intcell->pen.fg = cell->fg; intcell->pen.bg = cell->bg; if(cell->width == 2) getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1; return 1; } int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) { /* This cell is EOL if this and every cell to the right is black */ for(; pos.col < screen->cols; pos.col++) { ScreenCell *cell = getcell(screen, pos.row, pos.col); if(cell->chars[0] != 0) return 0; } return 1; } VTermScreen *vterm_obtain_screen(VTerm *vt) { if(vt->screen) return vt->screen; VTermScreen *screen = screen_new(vt); vt->screen = screen; return screen; } void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) { if(!screen->buffers[1] && altscreen) { int rows, cols; vterm_get_size(screen->vt, &rows, &cols); screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols); } } void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) { screen->callbacks = callbacks; screen->cbdata = user; } void *vterm_screen_get_cbdata(VTermScreen *screen) { return screen->cbdata; } void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user) { vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); } void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) { return vterm_state_get_unrecognised_fbdata(screen->state); } void vterm_screen_flush_damage(VTermScreen *screen) { if(screen->pending_scrollrect.start_row != -1) { vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, moverect_user, erase_user, screen); screen->pending_scrollrect.start_row = -1; } if(screen->damaged.start_row != -1) { if(screen->callbacks && screen->callbacks->damage) (*screen->callbacks->damage)(screen->damaged, screen->cbdata); screen->damaged.start_row = -1; } } void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) { vterm_screen_flush_damage(screen); screen->damage_merge = size; } static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) { if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) return 1; if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) return 1; if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) return 1; if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) return 1; if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) return 1; if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) return 1; if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) return 1; if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) return 1; if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) return 1; return 0; } int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) { ScreenCell *target = getcell(screen, pos.row, pos.col); // TODO: bounds check extent->start_row = pos.row; extent->end_row = pos.row + 1; if(extent->start_col < 0) extent->start_col = 0; if(extent->end_col < 0) extent->end_col = screen->cols; int col; for(col = pos.col - 1; col >= extent->start_col; col--) if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) break; extent->start_col = col + 1; for(col = pos.col + 1; col < extent->end_col; col++) if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) break; extent->end_col = col - 1; return 1; } void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) { vterm_state_convert_color_to_rgb(screen->state, col); } libvterm-0.1.4/src/pen.c0000644000175000017500000003710713720230652013205 0ustar leoleo#include "vterm_internal.h" #include /** * Structure used to store RGB triples without the additional metadata stored in * VTermColor. */ typedef struct { uint8_t red, green, blue; } VTermRGB; static const VTermRGB ansi_colors[] = { /* R G B */ { 0, 0, 0 }, // black { 224, 0, 0 }, // red { 0, 224, 0 }, // green { 224, 224, 0 }, // yellow { 0, 0, 224 }, // blue { 224, 0, 224 }, // magenta { 0, 224, 224 }, // cyan { 224, 224, 224 }, // white == light grey // high intensity { 128, 128, 128 }, // black { 255, 64, 64 }, // red { 64, 255, 64 }, // green { 255, 255, 64 }, // yellow { 64, 64, 255 }, // blue { 255, 64, 255 }, // magenta { 64, 255, 255 }, // cyan { 255, 255, 255 }, // white for real }; static int ramp6[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, }; static int ramp24[] = { 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, }; static void lookup_default_colour_ansi(long idx, VTermColor *col) { if (idx >= 0 && idx < 16) { vterm_color_rgb( col, ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); } } static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) { if(index >= 0 && index < 16) { *col = state->colors[index]; return true; } return false; } static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) { if(index >= 0 && index < 16) { // Normal 8 colours or high intensity - parse as palette 0 return lookup_colour_ansi(state, index, col); } else if(index >= 16 && index < 232) { // 216-colour cube index -= 16; vterm_color_rgb(col, ramp6[index/6/6 % 6], ramp6[index/6 % 6], ramp6[index % 6]); return true; } else if(index >= 232 && index < 256) { // 24 greyscales index -= 232; vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); return true; } return false; } static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) { switch(palette) { case 2: // RGB mode - 3 args contain colour values directly if(argcount < 3) return argcount; vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); return 3; case 5: // XTerm 256-colour mode if (!argcount || CSI_ARG_IS_MISSING(args[0])) { return argcount ? 1 : 0; } vterm_color_indexed(col, args[0]); return argcount ? 1 : 0; default: DEBUG_LOG("Unrecognised colour palette %d\n", palette); return 0; } } // Some conveniences static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) { #ifdef DEBUG if(type != vterm_get_attr_type(attr)) { DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", attr, vterm_get_attr_type(attr), type); return; } #endif if(state->callbacks && state->callbacks->setpenattr) (*state->callbacks->setpenattr)(attr, val, state->cbdata); } static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) { VTermValue val = { .boolean = boolean }; setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); } static void setpenattr_int(VTermState *state, VTermAttr attr, int number) { VTermValue val = { .number = number }; setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); } static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) { VTermValue val = { .color = color }; setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); } static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) { VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; vterm_color_indexed(colp, col); setpenattr_col(state, attr, *colp); } INTERNAL void vterm_state_newpen(VTermState *state) { // 90% grey so that pure white is brighter vterm_color_rgb(&state->default_fg, 240, 240, 240); vterm_color_rgb(&state->default_bg, 0, 0, 0); vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); for(int col = 0; col < 16; col++) lookup_default_colour_ansi(col, &state->colors[col]); } INTERNAL void vterm_state_resetpen(VTermState *state) { state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0); state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0); state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); } INTERNAL void vterm_state_savepen(VTermState *state, int save) { if(save) { state->saved.pen = state->pen; } else { state->pen = state->saved.pen; setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); setpenattr_int( state, VTERM_ATTR_UNDERLINE, state->pen.underline); setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); setpenattr_int( state, VTERM_ATTR_FONT, state->pen.font); setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg); setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg); } } int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) { /* First make sure that the two colours are of the same type (RGB/Indexed) */ if (a->type != b->type) { return false; } /* Depending on the type inspect the corresponding members */ if (VTERM_COLOR_IS_INDEXED(a)) { return a->indexed.idx == b->indexed.idx; } else if (VTERM_COLOR_IS_RGB(a)) { return (a->rgb.red == b->rgb.red) && (a->rgb.green == b->rgb.green) && (a->rgb.blue == b->rgb.blue); } return 0; } void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) { *default_fg = state->default_fg; *default_bg = state->default_bg; } void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) { lookup_colour_palette(state, index, col); } void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) { /* Copy the given colors */ state->default_fg = *default_fg; state->default_bg = *default_bg; /* Make sure the correct type flags are set */ state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) | VTERM_COLOR_DEFAULT_FG; state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) | VTERM_COLOR_DEFAULT_BG; } void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) { if(index >= 0 && index < 16) state->colors[index] = *col; } void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) { if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ lookup_colour_palette(state, col->indexed.idx, col); } col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ } void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) { state->bold_is_highbright = bold_is_highbright; } INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) { // SGR - ECMA-48 8.3.117 int argi = 0; int value; while(argi < argcount) { // This logic is easier to do 'done' backwards; set it true, and make it // false again in the 'default' case int done = 1; long arg; switch(arg = CSI_ARG(args[argi])) { case CSI_ARG_MISSING: case 0: // Reset vterm_state_resetpen(state); break; case 1: { // Bold on const VTermColor *fg = &state->pen.fg; state->pen.bold = 1; setpenattr_bool(state, VTERM_ATTR_BOLD, 1); if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); break; } case 3: // Italic on state->pen.italic = 1; setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); break; case 4: // Underline state->pen.underline = VTERM_UNDERLINE_SINGLE; if(CSI_ARG_HAS_MORE(args[argi])) { argi++; switch(CSI_ARG(args[argi])) { case 0: state->pen.underline = 0; break; case 1: state->pen.underline = VTERM_UNDERLINE_SINGLE; break; case 2: state->pen.underline = VTERM_UNDERLINE_DOUBLE; break; case 3: state->pen.underline = VTERM_UNDERLINE_CURLY; break; } } setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); break; case 5: // Blink state->pen.blink = 1; setpenattr_bool(state, VTERM_ATTR_BLINK, 1); break; case 7: // Reverse on state->pen.reverse = 1; setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); break; case 9: // Strikethrough on state->pen.strike = 1; setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); break; case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: // Select font state->pen.font = CSI_ARG(args[argi]) - 10; setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); break; case 21: // Underline double state->pen.underline = VTERM_UNDERLINE_DOUBLE; setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); break; case 22: // Bold off state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); break; case 23: // Italic and Gothic (currently unsupported) off state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); break; case 24: // Underline off state->pen.underline = 0; setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); break; case 25: // Blink off state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); break; case 27: // Reverse off state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); break; case 29: // Strikethrough off state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); break; case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: // Foreground colour palette value = CSI_ARG(args[argi]) - 30; if(state->pen.bold && state->bold_is_highbright) value += 8; set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); break; case 38: // Foreground colour alternative palette if(argcount - argi < 1) return; argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg); setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); break; case 39: // Foreground colour default state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); break; case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: // Background colour palette value = CSI_ARG(args[argi]) - 40; set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); break; case 48: // Background colour alternative palette if(argcount - argi < 1) return; argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg); setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); break; case 49: // Default background state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); break; case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette value = CSI_ARG(args[argi]) - 90 + 8; set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); break; case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: // Background colour high-intensity palette value = CSI_ARG(args[argi]) - 100 + 8; set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); break; default: done = 0; break; } if(!done) DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg); while(CSI_ARG_HAS_MORE(args[argi++])); } } static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) { /* Do nothing if the given color is the default color */ if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { return argi; } /* Decide whether to send an indexed color or an RGB color */ if (VTERM_COLOR_IS_INDEXED(col)) { const uint8_t idx = col->indexed.idx; if (idx < 8) { args[argi++] = (idx + (fg ? 30 : 40)); } else if (idx < 16) { args[argi++] = (idx - 8 + (fg ? 90 : 100)); } else { args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); args[argi++] = CSI_ARG_FLAG_MORE | 5; args[argi++] = idx; } } else if (VTERM_COLOR_IS_RGB(col)) { args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); args[argi++] = CSI_ARG_FLAG_MORE | 2; args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; args[argi++] = col->rgb.blue; } return argi; } INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) { int argi = 0; if(state->pen.bold) args[argi++] = 1; if(state->pen.italic) args[argi++] = 3; if(state->pen.underline == VTERM_UNDERLINE_SINGLE) args[argi++] = 4; if(state->pen.underline == VTERM_UNDERLINE_CURLY) args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; if(state->pen.blink) args[argi++] = 5; if(state->pen.reverse) args[argi++] = 7; if(state->pen.strike) args[argi++] = 9; if(state->pen.font) args[argi++] = 10 + state->pen.font; if(state->pen.underline == VTERM_UNDERLINE_DOUBLE) args[argi++] = 21; argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); return argi; } int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) { switch(attr) { case VTERM_ATTR_BOLD: val->boolean = state->pen.bold; return 1; case VTERM_ATTR_UNDERLINE: val->number = state->pen.underline; return 1; case VTERM_ATTR_ITALIC: val->boolean = state->pen.italic; return 1; case VTERM_ATTR_BLINK: val->boolean = state->pen.blink; return 1; case VTERM_ATTR_REVERSE: val->boolean = state->pen.reverse; return 1; case VTERM_ATTR_STRIKE: val->boolean = state->pen.strike; return 1; case VTERM_ATTR_FONT: val->number = state->pen.font; return 1; case VTERM_ATTR_FOREGROUND: val->color = state->pen.fg; return 1; case VTERM_ATTR_BACKGROUND: val->color = state->pen.bg; return 1; case VTERM_N_ATTRS: return 0; } return 0; } libvterm-0.1.4/src/keyboard.c0000644000175000017500000001401713720230652014216 0ustar leoleo#include "vterm_internal.h" #include #include "utf8.h" void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) { /* The shift modifier is never important for Unicode characters * apart from Space */ if(c != ' ') mod &= ~VTERM_MOD_SHIFT; if(mod == 0) { // Normal text - ignore just shift char str[6]; int seqlen = fill_utf8(c, str); vterm_push_output_bytes(vt, str, seqlen); return; } int needs_CSIu; switch(c) { /* Special Ctrl- letters that can't be represented elsewise */ case 'i': case 'j': case 'm': case '[': needs_CSIu = 1; break; /* Ctrl-\ ] ^ _ don't need CSUu */ case '\\': case ']': case '^': case '_': needs_CSIu = 0; break; /* Shift-space needs CSIu */ case ' ': needs_CSIu = !!(mod & VTERM_MOD_SHIFT); break; /* All other characters needs CSIu except for letters a-z */ default: needs_CSIu = (c < 'a' || c > 'z'); } /* ALT we can just prefix with ESC; anything else requires CSI u */ if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1); return; } if(mod & VTERM_MOD_CTRL) c &= 0x1f; vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); } typedef struct { enum { KEYCODE_NONE, KEYCODE_LITERAL, KEYCODE_TAB, KEYCODE_ENTER, KEYCODE_SS3, KEYCODE_CSI, KEYCODE_CSI_CURSOR, KEYCODE_CSINUM, KEYCODE_KEYPAD, } type; char literal; int csinum; } keycodes_s; static keycodes_s keycodes[] = { { KEYCODE_NONE }, // NONE { KEYCODE_ENTER, '\r' }, // ENTER { KEYCODE_TAB, '\t' }, // TAB { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL { KEYCODE_LITERAL, '\x1b' }, // ESCAPE { KEYCODE_CSI_CURSOR, 'A' }, // UP { KEYCODE_CSI_CURSOR, 'B' }, // DOWN { KEYCODE_CSI_CURSOR, 'D' }, // LEFT { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT { KEYCODE_CSINUM, '~', 2 }, // INS { KEYCODE_CSINUM, '~', 3 }, // DEL { KEYCODE_CSI_CURSOR, 'H' }, // HOME { KEYCODE_CSI_CURSOR, 'F' }, // END { KEYCODE_CSINUM, '~', 5 }, // PAGEUP { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN }; static keycodes_s keycodes_fn[] = { { KEYCODE_NONE }, // F0 - shouldn't happen { KEYCODE_SS3, 'P' }, // F1 { KEYCODE_SS3, 'Q' }, // F2 { KEYCODE_SS3, 'R' }, // F3 { KEYCODE_SS3, 'S' }, // F4 { KEYCODE_CSINUM, '~', 15 }, // F5 { KEYCODE_CSINUM, '~', 17 }, // F6 { KEYCODE_CSINUM, '~', 18 }, // F7 { KEYCODE_CSINUM, '~', 19 }, // F8 { KEYCODE_CSINUM, '~', 20 }, // F9 { KEYCODE_CSINUM, '~', 21 }, // F10 { KEYCODE_CSINUM, '~', 23 }, // F11 { KEYCODE_CSINUM, '~', 24 }, // F12 }; static keycodes_s keycodes_kp[] = { { KEYCODE_KEYPAD, '0', 'p' }, // KP_0 { KEYCODE_KEYPAD, '1', 'q' }, // KP_1 { KEYCODE_KEYPAD, '2', 'r' }, // KP_2 { KEYCODE_KEYPAD, '3', 's' }, // KP_3 { KEYCODE_KEYPAD, '4', 't' }, // KP_4 { KEYCODE_KEYPAD, '5', 'u' }, // KP_5 { KEYCODE_KEYPAD, '6', 'v' }, // KP_6 { KEYCODE_KEYPAD, '7', 'w' }, // KP_7 { KEYCODE_KEYPAD, '8', 'x' }, // KP_8 { KEYCODE_KEYPAD, '9', 'y' }, // KP_9 { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL }; void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) { if(key == VTERM_KEY_NONE) return; keycodes_s k; if(key < VTERM_KEY_FUNCTION_0) { if(key >= sizeof(keycodes)/sizeof(keycodes[0])) return; k = keycodes[key]; } else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) return; k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; } else if(key >= VTERM_KEY_KP_0) { if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) return; k = keycodes_kp[key - VTERM_KEY_KP_0]; } switch(k.type) { case KEYCODE_NONE: break; case KEYCODE_TAB: /* Shift-Tab is CSI Z but plain Tab is 0x09 */ if(mod == VTERM_MOD_SHIFT) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); else if(mod & VTERM_MOD_SHIFT) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1); else goto case_LITERAL; break; case KEYCODE_ENTER: /* Enter is CRLF in newline mode, but just LF in linefeed */ if(vt->state->mode.newline) vterm_push_output_sprintf(vt, "\r\n"); else goto case_LITERAL; break; case KEYCODE_LITERAL: case_LITERAL: if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1); else vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); break; case KEYCODE_SS3: case_SS3: if(mod == 0) vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); else goto case_CSI; break; case KEYCODE_CSI: case_CSI: if(mod == 0) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); else vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); break; case KEYCODE_CSINUM: if(mod == 0) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); else vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); break; case KEYCODE_CSI_CURSOR: if(vt->state->mode.cursor) goto case_SS3; else goto case_CSI; case KEYCODE_KEYPAD: if(vt->state->mode.keypad) { k.literal = k.csinum; goto case_SS3; } else goto case_LITERAL; } } void vterm_keyboard_start_paste(VTerm *vt) { if(vt->state->mode.bracketpaste) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); } void vterm_keyboard_end_paste(VTerm *vt) { if(vt->state->mode.bracketpaste) vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); } libvterm-0.1.4/t/0002755000175000017500000000000013720230652011725 5ustar leoleolibvterm-0.1.4/t/31state_rep.test0000644000175000017500000000515513720230652014764 0ustar leoleoINIT UTF8 1 WANTSTATE g !REP no argument RESET PUSH "a\e[b" putglyph 0x61 1 0,0 putglyph 0x61 1 0,1 !REP zero (zero should be interpreted as one) RESET PUSH "a\e[0b" putglyph 0x61 1 0,0 putglyph 0x61 1 0,1 !REP lowercase a times two RESET PUSH "a\e[2b" putglyph 0x61 1 0,0 putglyph 0x61 1 0,1 putglyph 0x61 1 0,2 !REP with UTF-8 1 char # U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE RESET PUSH "\xC3\xA9\e[b" putglyph 0xe9 1 0,0 putglyph 0xe9 1 0,1 !REP with UTF-8 wide char # U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE RESET PUSH "\xEF\xBC\x90\e[b" putglyph 0xff10 2 0,0 putglyph 0xff10 2 0,2 !REP with UTF-8 combining character RESET PUSH "e\xCC\x81\e[b" putglyph 0x65,0x301 1 0,0 putglyph 0x65,0x301 1 0,1 !REP till end of line RESET PUSH "a\e[1000bb" putglyph 0x61 1 0,0 putglyph 0x61 1 0,1 putglyph 0x61 1 0,2 putglyph 0x61 1 0,3 putglyph 0x61 1 0,4 putglyph 0x61 1 0,5 putglyph 0x61 1 0,6 putglyph 0x61 1 0,7 putglyph 0x61 1 0,8 putglyph 0x61 1 0,9 putglyph 0x61 1 0,10 putglyph 0x61 1 0,11 putglyph 0x61 1 0,12 putglyph 0x61 1 0,13 putglyph 0x61 1 0,14 putglyph 0x61 1 0,15 putglyph 0x61 1 0,16 putglyph 0x61 1 0,17 putglyph 0x61 1 0,18 putglyph 0x61 1 0,19 putglyph 0x61 1 0,20 putglyph 0x61 1 0,21 putglyph 0x61 1 0,22 putglyph 0x61 1 0,23 putglyph 0x61 1 0,24 putglyph 0x61 1 0,25 putglyph 0x61 1 0,26 putglyph 0x61 1 0,27 putglyph 0x61 1 0,28 putglyph 0x61 1 0,29 putglyph 0x61 1 0,30 putglyph 0x61 1 0,31 putglyph 0x61 1 0,32 putglyph 0x61 1 0,33 putglyph 0x61 1 0,34 putglyph 0x61 1 0,35 putglyph 0x61 1 0,36 putglyph 0x61 1 0,37 putglyph 0x61 1 0,38 putglyph 0x61 1 0,39 putglyph 0x61 1 0,40 putglyph 0x61 1 0,41 putglyph 0x61 1 0,42 putglyph 0x61 1 0,43 putglyph 0x61 1 0,44 putglyph 0x61 1 0,45 putglyph 0x61 1 0,46 putglyph 0x61 1 0,47 putglyph 0x61 1 0,48 putglyph 0x61 1 0,49 putglyph 0x61 1 0,50 putglyph 0x61 1 0,51 putglyph 0x61 1 0,52 putglyph 0x61 1 0,53 putglyph 0x61 1 0,54 putglyph 0x61 1 0,55 putglyph 0x61 1 0,56 putglyph 0x61 1 0,57 putglyph 0x61 1 0,58 putglyph 0x61 1 0,59 putglyph 0x61 1 0,60 putglyph 0x61 1 0,61 putglyph 0x61 1 0,62 putglyph 0x61 1 0,63 putglyph 0x61 1 0,64 putglyph 0x61 1 0,65 putglyph 0x61 1 0,66 putglyph 0x61 1 0,67 putglyph 0x61 1 0,68 putglyph 0x61 1 0,69 putglyph 0x61 1 0,70 putglyph 0x61 1 0,71 putglyph 0x61 1 0,72 putglyph 0x61 1 0,73 putglyph 0x61 1 0,74 putglyph 0x61 1 0,75 putglyph 0x61 1 0,76 putglyph 0x61 1 0,77 putglyph 0x61 1 0,78 putglyph 0x61 1 0,79 putglyph 0x62 1 1,0 libvterm-0.1.4/t/90vttest_01-movement-2.test0000644000175000017500000000525013720230652016617 0ustar leoleoINIT WANTSTATE WANTSCREEN RESET PUSH "\e[3;21r" PUSH "\e[?6h" PUSH "\e[19;1HA\e[19;80Ha\x0a\e[18;80HaB\e[19;80HB\b b\x0a\e[19;80HC\b\b\t\tc\e[19;2H\bC\x0a\e[19;80H\x0a\e[18;1HD\e[18;80Hd" PUSH "\e[19;1HE\e[19;80He\x0a\e[18;80HeF\e[19;80HF\b f\x0a\e[19;80HG\b\b\t\tg\e[19;2H\bG\x0a\e[19;80H\x0a\e[18;1HH\e[18;80Hh" PUSH "\e[19;1HI\e[19;80Hi\x0a\e[18;80HiJ\e[19;80HJ\b j\x0a\e[19;80HK\b\b\t\tk\e[19;2H\bK\x0a\e[19;80H\x0a\e[18;1HL\e[18;80Hl" PUSH "\e[19;1HM\e[19;80Hm\x0a\e[18;80HmN\e[19;80HN\b n\x0a\e[19;80HO\b\b\t\to\e[19;2H\bO\x0a\e[19;80H\x0a\e[18;1HP\e[18;80Hp" PUSH "\e[19;1HQ\e[19;80Hq\x0a\e[18;80HqR\e[19;80HR\b r\x0a\e[19;80HS\b\b\t\ts\e[19;2H\bS\x0a\e[19;80H\x0a\e[18;1HT\e[18;80Ht" PUSH "\e[19;1HU\e[19;80Hu\x0a\e[18;80HuV\e[19;80HV\b v\x0a\e[19;80HW\b\b\t\tw\e[19;2H\bW\x0a\e[19;80H\x0a\e[18;1HX\e[18;80Hx" PUSH "\e[19;1HY\e[19;80Hy\x0a\e[18;80HyZ\e[19;80HZ\b z\x0a" !Output ?screen_row 2 = "I i" ?screen_row 3 = "J j" ?screen_row 4 = "K k" ?screen_row 5 = "L l" ?screen_row 6 = "M m" ?screen_row 7 = "N n" ?screen_row 8 = "O o" ?screen_row 9 = "P p" ?screen_row 10 = "Q q" ?screen_row 11 = "R r" ?screen_row 12 = "S s" ?screen_row 13 = "T t" ?screen_row 14 = "U u" ?screen_row 15 = "V v" ?screen_row 16 = "W w" ?screen_row 17 = "X x" ?screen_row 18 = "Y y" ?screen_row 19 = "Z z" ?screen_row 20 = "" ?cursor = 20,79 libvterm-0.1.4/t/90vttest_01-movement-1.test0000644000175000017500000000531413720230652016617 0ustar leoleoINIT WANTSTATE WANTSCREEN RESET PUSH "\e#8" PUSH "\e[9;10H\e[1J" PUSH "\e[18;60H\e[0J\e[1K" PUSH "\e[9;71H\e[0K" $SEQ 10 16: PUSH "\e[\#;10H\e[1K\e[\#;71H\e[0K" PUSH "\e[17;30H\e[2K" $SEQ 1 80: PUSH "\e[24;\#f*\e[1;\#f*" PUSH "\e[2;2H" $REP 22: PUSH "+\e[1D\eD" PUSH "\e[23;79H" $REP 22: PUSH "+\e[1D\eM" PUSH "\e[2;1H" $SEQ 2 23: PUSH "*\e[\#;80H*\e[10D\eE" PUSH "\e[2;10H\e[42D\e[2C" $REP 76: PUSH "+\e[0C\e[2D\e[1C" PUSH "\e[23;70H\e[42C\e[2D" $REP 76: PUSH "+\e[1D\e[1C\e[0D\b" PUSH "\e[1;1H" PUSH "\e[10A" PUSH "\e[1A" PUSH "\e[0A" PUSH "\e[24;80H" PUSH "\e[10B" PUSH "\e[1B" PUSH "\e[0B" PUSH "\e[10;12H" $REP 58: PUSH " " PUSH "\e[1B\e[58D" $REP 58: PUSH " " PUSH "\e[1B\e[58D" $REP 58: PUSH " " PUSH "\e[1B\e[58D" $REP 58: PUSH " " PUSH "\e[1B\e[58D" $REP 58: PUSH " " PUSH "\e[1B\e[58D" $REP 58: PUSH " " PUSH "\e[1B\e[58D" PUSH "\e[5A\e[1CThe screen should be cleared, and have an unbroken bor-" PUSH "\e[12;13Hder of *'s and +'s around the edge, and exactly in the" PUSH "\e[13;13Hmiddle there should be a frame of E's around this text" PUSH "\e[14;13Hwith one (1) free position around it. Push " # And the result is... !Output ?screen_row 0 = "********************************************************************************" ?screen_row 1 = "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*" $SEQ 2 7: ?screen_row \# = "*+ +*" ?screen_row 8 = "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*" ?screen_row 9 = "*+ E E +*" ?screen_row 10 = "*+ E The screen should be cleared, and have an unbroken bor- E +*" ?screen_row 11 = "*+ E der of *'s and +'s around the edge, and exactly in the E +*" ?screen_row 12 = "*+ E middle there should be a frame of E's around this text E +*" ?screen_row 13 = "*+ E with one (1) free position around it. Push E +*" ?screen_row 14 = "*+ E E +*" ?screen_row 15 = "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*" $SEQ 16 21: ?screen_row \# = "*+ +*" ?screen_row 22 = "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*" ?screen_row 23 = "********************************************************************************" ?cursor = 13,67 libvterm-0.1.4/t/90vttest_02-screen-4.test0000644000175000017500000000027413720230652016250 0ustar leoleo# Origin mode (2) INIT WANTSCREEN RESET PUSH "\e[?6l" PUSH "\e[23;24r" PUSH "\e[24;1H" PUSH "Bottom" PUSH "\e[1;1H" PUSH "Top" !Output ?screen_row 23 = "Bottom" ?screen_row 0 = "Top" libvterm-0.1.4/t/run-test.pl0000755000175000017500000001210213720230652014040 0ustar leoleo#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use IO::Handle; use IPC::Open2 qw( open2 ); use POSIX qw( WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG ); my $VALGRIND = 0; my $EXECUTABLE = "t/.libs/harness"; GetOptions( 'valgrind|v+' => \$VALGRIND, 'executable|e=s' => \$EXECUTABLE ) or exit 1; my ( $hin, $hout, $hpid ); { local $ENV{LD_LIBRARY_PATH} = ".libs"; my @command = $EXECUTABLE; unshift @command, "valgrind", "--quiet", "--error-exitcode=126" if $VALGRIND; $hpid = open2 $hout, $hin, @command or die "Cannot open2 harness - $!"; } my $exitcode = 0; my $command; my @expect; sub do_onetest { $hin->print( "$command\n" ); undef $command; my $fail_printed = 0; while( my $outline = <$hout> ) { last if $outline eq "DONE\n" or $outline eq "?\n"; chomp $outline; if( !@expect ) { print "# Test failed\n" unless $fail_printed++; print "# expected nothing more\n" . "# Actual: $outline\n"; next; } my $expectation = shift @expect; next if $expectation eq $outline; print "# Test failed\n" unless $fail_printed++; print "# Expected: $expectation\n" . "# Actual: $outline\n"; } if( @expect ) { print "# Test failed\n" unless $fail_printed++; print "# Expected: $_\n" . "# didn't happen\n" for @expect; } $exitcode = 1 if $fail_printed; } sub do_line { my ( $line ) = @_; if( $line =~ m/^!(.*)/ ) { do_onetest if defined $command; print "> $1\n"; } # Commands have capitals elsif( $line =~ m/^([A-Z]+)/ ) { # Some convenience formatting if( $line =~ m/^(PUSH|ENCIN) (.*)$/ ) { # we're evil my $string = eval($2); $line = "$1 " . unpack "H*", $string; } do_onetest if defined $command; $command = $line; undef @expect; } # Expectations have lowercase elsif( $line =~ m/^([a-z]+)/ ) { # Convenience formatting if( $line =~ m/^(text|encout) (.*)$/ ) { $line = "$1 " . join ",", map sprintf("%x", $_), eval($2); } elsif( $line =~ m/^(output) (.*)$/ ) { $line = "$1 " . join ",", map sprintf("%x", $_), unpack "C*", eval($2); } elsif( $line =~ m/^control (.*)$/ ) { $line = sprintf "control %02x", eval($1); } elsif( $line =~ m/^csi (\S+) (.*)$/ ) { $line = sprintf "csi %02x %s", eval($1), $2; # TODO } elsif( $line =~ m/^(escape|osc|dcs) (.*)$/ ) { $line = "$1 " . join "", map sprintf("%02x", $_), unpack "C*", eval($2); } elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) { $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2"; } elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|settermprop|setmousefunc) / ) { # no conversion } else { warn "Unrecognised test expectation '$line'\n"; } push @expect, $line; } # ?screen_row assertion is emulated here elsif( $line =~ s/^\?screen_row\s+(\d+)\s*=\s*// ) { my $row = $1; my $row1 = $row + 1; my $want = eval($line); do_onetest if defined $command; # TODO: may not be 80 $hin->print( "\?screen_chars $row,0,$row1,80\n" ); my $response = <$hout>; chomp $response; $response = pack "C*", map hex, split m/,/, $response; if( $response ne $want ) { print "# Assert ?screen_row $row failed:\n" . "# Expected: $want\n" . "# Actual: $response\n"; $exitcode = 1; } } # Assertions start with '?' elsif( $line =~ s/^\?([a-z]+.*?=)\s+// ) { do_onetest if defined $command; my ( $assertion ) = $1 =~ m/^(.*)\s+=/; $hin->print( "\?$assertion\n" ); my $response = <$hout>; defined $response or wait, die "Test harness failed - $?\n"; chomp $response; if( $response ne $line ) { print "# Assert $assertion failed:\n" . "# Expected: $line\n" . "# Actual: $response\n"; $exitcode = 1; } } # Test controls start with '$' elsif( $line =~ s/\$SEQ\s+(\d+)\s+(\d+):\s*// ) { my ( $low, $high ) = ( $1, $2 ); foreach my $val ( $low .. $high ) { ( my $inner = $line ) =~ s/\\#/$val/g; do_line( $inner ); } } elsif( $line =~ s/\$REP\s+(\d+):\s*// ) { my $count = $1; do_line( $line ) for 1 .. $count; } else { die "Unrecognised TEST line $line\n"; } } open my $test, "<", $ARGV[0] or die "Cannot open test script $ARGV[0] - $!"; while( my $line = <$test> ) { $line =~ s/^\s+//; chomp $line; next if $line =~ m/^(?:#|$)/; last if $line eq "__END__"; do_line( $line ); } do_onetest if defined $command; close $hin; close $hout; waitpid $hpid, 0; if( $? ) { printf STDERR "Harness exited %d\n", WEXITSTATUS($?) if WIFEXITED($?); printf STDERR "Harness exit signal %d\n", WTERMSIG($?) if WIFSIGNALED($?); $exitcode = WIFEXITED($?) ? WEXITSTATUS($?) : 125; } exit $exitcode; libvterm-0.1.4/t/60screen_ascii.test0000644000175000017500000000326013720230652015422 0ustar leoleoINIT WANTSCREEN c !Get RESET PUSH "ABC" movecursor 0,3 ?screen_chars 0,0,1,3 = 0x41,0x42,0x43 ?screen_chars 0,0,1,80 = 0x41,0x42,0x43 ?screen_text 0,0,1,3 = 0x41,0x42,0x43 ?screen_text 0,0,1,80 = 0x41,0x42,0x43 ?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) ?screen_cell 0,1 = {0x42} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) ?screen_cell 0,2 = {0x43} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) ?screen_row 0 = "ABC" ?screen_eol 0,0 = 0 ?screen_eol 0,2 = 0 ?screen_eol 0,3 = 1 PUSH "\e[H" movecursor 0,0 ?screen_chars 0,0,1,80 = 0x41,0x42,0x43 ?screen_text 0,0,1,80 = 0x41,0x42,0x43 PUSH "E" movecursor 0,1 ?screen_chars 0,0,1,80 = 0x45,0x42,0x43 ?screen_text 0,0,1,80 = 0x45,0x42,0x43 WANTSCREEN -c !Erase RESET PUSH "ABCDE\e[H\e[K" ?screen_chars 0,0,1,80 = ?screen_text 0,0,1,80 = !Copycell RESET PUSH "ABC\e[H\e[@" PUSH "1" ?screen_chars 0,0,1,80 = 0x31,0x41,0x42,0x43 RESET PUSH "ABC\e[H\e[P" ?screen_chars 0,0,1,1 = 0x42 ?screen_chars 0,1,1,2 = 0x43 ?screen_chars 0,0,1,80 = 0x42,0x43 !Space padding RESET PUSH "Hello\e[CWorld" ?screen_chars 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64 ?screen_text 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64 !Linefeed padding RESET PUSH "Hello\r\nWorld" ?screen_chars 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64 ?screen_text 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64 !Altscreen RESET PUSH "P" ?screen_chars 0,0,1,80 = 0x50 PUSH "\e[?1049h" ?screen_chars 0,0,1,80 = PUSH "\e[2K\e[HA" ?screen_chars 0,0,1,80 = 0x41 PUSH "\e[?1049l" ?screen_chars 0,0,1,80 = 0x50 libvterm-0.1.4/t/25state_input.test0000644000175000017500000000431113720230652015331 0ustar leoleoINIT WANTSTATE !Unmodified ASCII INCHAR 0 41 output "A" INCHAR 0 61 output "a" !Ctrl modifier on ASCII letters INCHAR C 41 output "\e[65;5u" INCHAR C 61 output "\x01" !Alt modifier on ASCII letters INCHAR A 41 output "\eA" INCHAR A 61 output "\ea" !Ctrl-Alt modifier on ASCII letters INCHAR CA 41 output "\e[65;7u" INCHAR CA 61 output "\e\x01" !Special handling of Ctrl-I INCHAR 0 49 output "I" INCHAR 0 69 output "i" INCHAR C 49 output "\e[73;5u" INCHAR C 69 output "\e[105;5u" INCHAR A 49 output "\eI" INCHAR A 69 output "\ei" INCHAR CA 49 output "\e[73;7u" INCHAR CA 69 output "\e[105;7u" !Special handling of Space INCHAR 0 20 output " " INCHAR S 20 output "\e[32;2u" INCHAR C 20 output "\0" INCHAR SC 20 output "\e[32;6u" INCHAR A 20 output "\e " INCHAR SA 20 output "\e[32;4u" INCHAR CA 20 output "\e\0" INCHAR SCA 20 output "\e[32;8u" !Cursor keys in reset (cursor) mode INKEY 0 Up output "\e[A" INKEY S Up output "\e[1;2A" INKEY C Up output "\e[1;5A" INKEY SC Up output "\e[1;6A" INKEY A Up output "\e[1;3A" INKEY SA Up output "\e[1;4A" INKEY CA Up output "\e[1;7A" INKEY SCA Up output "\e[1;8A" !Cursor keys in application mode PUSH "\e[?1h" # Plain "Up" should be SS3 A now INKEY 0 Up output "\eOA" # Modified keys should still use CSI INKEY S Up output "\e[1;2A" INKEY C Up output "\e[1;5A" !Shift-Tab should be different INKEY 0 Tab output "\x09" INKEY S Tab output "\e[Z" INKEY C Tab output "\e[9;5u" INKEY A Tab output "\e\x09" INKEY CA Tab output "\e[9;7u" !Enter in linefeed mode INKEY 0 Enter output "\x0d" !Enter in newline mode PUSH "\e[20h" INKEY 0 Enter output "\x0d\x0a" !Unmodified F1 is SS3 P INKEY 0 F1 output "\eOP" !Modified F1 is CSI P INKEY S F1 output "\e[1;2P" INKEY A F1 output "\e[1;3P" INKEY C F1 output "\e[1;5P" !Keypad in DECKPNM INKEY 0 KP0 output "0" !Keypad in DECKPAM PUSH "\e=" INKEY 0 KP0 output "\eOp" !Bracketed paste mode off PASTE START PASTE END !Bracketed paste mode on PUSH "\e[?2004h" PASTE START output "\e[200~" PASTE END output "\e[201~" !Focus reporting disabled FOCUS IN FOCUS OUT !Focus reporting enabled PUSH "\e[?1004h" FOCUS IN output "\e[I" FOCUS OUT output "\e[O" libvterm-0.1.4/t/15state_mode.test0000644000175000017500000000260713720230652015123 0ustar leoleoINIT UTF8 1 WANTSTATE gme !Insert/Replace Mode RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "AC\e[DB" putglyph 0x41 1 0,0 putglyph 0x43 1 0,1 putglyph 0x42 1 0,1 PUSH "\e[4h" PUSH "\e[G" PUSH "AC\e[DB" moverect 0..1,0..79 -> 0..1,1..80 erase 0..1,0..1 putglyph 0x41 1 0,0 moverect 0..1,1..79 -> 0..1,2..80 erase 0..1,1..2 putglyph 0x43 1 0,1 moverect 0..1,1..79 -> 0..1,2..80 erase 0..1,1..2 putglyph 0x42 1 0,1 !Insert mode only happens once for UTF-8 combining PUSH "e" moverect 0..1,2..79 -> 0..1,3..80 erase 0..1,2..3 putglyph 0x65 1 0,2 PUSH "\xCC\x81" putglyph 0x65,0x301 1 0,2 !Newline/Linefeed mode RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "\e[5G\n" ?cursor = 1,4 PUSH "\e[20h" PUSH "\e[5G\n" ?cursor = 2,0 !DEC origin mode RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "\e[5;15r" PUSH "\e[H" ?cursor = 0,0 PUSH "\e[3;3H" ?cursor = 2,2 PUSH "\e[?6h" PUSH "\e[H" ?cursor = 4,0 PUSH "\e[3;3H" ?cursor = 6,2 !DECRQM on DECOM PUSH "\e[?6h" PUSH "\e[?6\$p" output "\e[?6;1\$y" PUSH "\e[?6l" PUSH "\e[?6\$p" output "\e[?6;2\$y" !Origin mode with DECSLRM PUSH "\e[?6h" PUSH "\e[?69h" PUSH "\e[20;60s" PUSH "\e[H" ?cursor = 4,19 PUSH "\e[?69l" !Origin mode bounds cursor to scrolling region PUSH "\e[H" PUSH "\e[10A" ?cursor = 4,0 PUSH "\e[20B" ?cursor = 14,0 !Origin mode without scroll region PUSH "\e[?6l" PUSH "\e[r\e[?6h" ?cursor = 0,0 libvterm-0.1.4/t/68screen_termprops.test0000644000175000017500000000037413720230652016400 0ustar leoleoINIT WANTSCREEN p RESET settermprop 1 true settermprop 2 true settermprop 7 1 !Cursor visibility PUSH "\e[?25h" settermprop 1 true PUSH "\e[?25l" settermprop 1 false !Title PUSH "\e]2;Here is my title\a" settermprop 4 "Here is my title" libvterm-0.1.4/t/27state_reset.test0000644000175000017500000000065613720230652015326 0ustar leoleoINIT WANTSTATE RESET !RIS homes cursor PUSH "\e[5;5H" ?cursor = 4,4 WANTSTATE +m PUSH "\ec" ?cursor = 0,0 WANTSTATE -m !RIS cancels scrolling region PUSH "\e[5;10r" WANTSTATE +s PUSH "\ec\e[25H\n" scrollrect 0..25,0..80 => +1,+0 WANTSTATE -s !RIS erases screen PUSH "ABCDE" WANTSTATE +e PUSH "\ec" erase 0..25,0..80 WANTSTATE -e !RIS clears tabstops PUSH "\e[5G\eH\e[G\t" ?cursor = 0,4 PUSH "\ec\t" ?cursor = 0,8 libvterm-0.1.4/t/17state_mouse.test0000644000175000017500000000613213720230652015326 0ustar leoleoINIT WANTSTATE p !DECRQM on with mouse off PUSH "\e[?1000\$p" output "\e[?1000;2\$y" PUSH "\e[?1002\$p" output "\e[?1002;2\$y" PUSH "\e[?1003\$p" output "\e[?1003;2\$y" !Mouse in simple button report mode RESET settermprop 1 true settermprop 2 true settermprop 7 1 PUSH "\e[?1000h" settermprop 8 1 !Press 1 MOUSEMOVE 0,0 0 MOUSEBTN d 1 0 output "\e[M\x20\x21\x21" !Release 1 MOUSEBTN u 1 0 output "\e[M\x23\x21\x21" !Ctrl-Press 1 MOUSEBTN d 1 C output "\e[M\x30\x21\x21" MOUSEBTN u 1 C output "\e[M\x33\x21\x21" !Button 2 MOUSEBTN d 2 0 output "\e[M\x21\x21\x21" MOUSEBTN u 2 0 output "\e[M\x23\x21\x21" !Position MOUSEMOVE 10,20 0 MOUSEBTN d 1 0 output "\e[M\x20\x35\x2b" MOUSEBTN u 1 0 output "\e[M\x23\x35\x2b" MOUSEMOVE 10,21 0 # no output !Wheel events MOUSEBTN d 4 0 output "\e[M\x60\x36\x2b" MOUSEBTN d 4 0 output "\e[M\x60\x36\x2b" MOUSEBTN d 5 0 output "\e[M\x61\x36\x2b" !DECRQM on mouse button mode PUSH "\e[?1000\$p" output "\e[?1000;1\$y" PUSH "\e[?1002\$p" output "\e[?1002;2\$y" PUSH "\e[?1003\$p" output "\e[?1003;2\$y" !Drag events RESET settermprop 1 true settermprop 2 true settermprop 7 1 PUSH "\e[?1002h" settermprop 8 2 MOUSEMOVE 5,5 0 MOUSEBTN d 1 0 output "\e[M\x20\x26\x26" MOUSEMOVE 5,6 0 output "\e[M\x40\x27\x26" MOUSEMOVE 6,6 0 output "\e[M\x40\x27\x27" MOUSEMOVE 6,6 0 # no output MOUSEBTN u 1 0 output "\e[M\x23\x27\x27" MOUSEMOVE 6,7 # no output !DECRQM on mouse drag mode PUSH "\e[?1000\$p" output "\e[?1000;2\$y" PUSH "\e[?1002\$p" output "\e[?1002;1\$y" PUSH "\e[?1003\$p" output "\e[?1003;2\$y" !Non-drag motion events PUSH "\e[?1003h" settermprop 8 3 MOUSEMOVE 6,8 0 output "\e[M\x43\x29\x27" !DECRQM on mouse motion mode PUSH "\e[?1000\$p" output "\e[?1000;2\$y" PUSH "\e[?1002\$p" output "\e[?1002;2\$y" PUSH "\e[?1003\$p" output "\e[?1003;1\$y" !Bounds checking MOUSEMOVE 300,300 0 output "\e[M\x43\xff\xff" MOUSEBTN d 1 0 output "\e[M\x20\xff\xff" MOUSEBTN u 1 0 output "\e[M\x23\xff\xff" !DECRQM on standard encoding mode PUSH "\e[?1005\$p" output "\e[?1005;2\$y" PUSH "\e[?1006\$p" output "\e[?1006;2\$y" PUSH "\e[?1015\$p" output "\e[?1015;2\$y" !UTF-8 extended encoding mode # 300 + 32 + 1 = 333 = U+014d = \xc5\x8d PUSH "\e[?1005h" MOUSEBTN d 1 0 output "\e[M\x20\xc5\x8d\xc5\x8d" MOUSEBTN u 1 0 output "\e[M\x23\xc5\x8d\xc5\x8d" !DECRQM on UTF-8 extended encoding mode PUSH "\e[?1005\$p" output "\e[?1005;1\$y" PUSH "\e[?1006\$p" output "\e[?1006;2\$y" PUSH "\e[?1015\$p" output "\e[?1015;2\$y" !SGR extended encoding mode PUSH "\e[?1006h" MOUSEBTN d 1 0 output "\e[<0;301;301M" MOUSEBTN u 1 0 output "\e[<0;301;301m" !DECRQM on SGR extended encoding mode PUSH "\e[?1005\$p" output "\e[?1005;2\$y" PUSH "\e[?1006\$p" output "\e[?1006;1\$y" PUSH "\e[?1015\$p" output "\e[?1015;2\$y" !rxvt extended encoding mode PUSH "\e[?1015h" MOUSEBTN d 1 0 output "\e[0;301;301M" MOUSEBTN u 1 0 output "\e[3;301;301M" !DECRQM on rxvt extended encoding mode PUSH "\e[?1005\$p" output "\e[?1005;2\$y" PUSH "\e[?1006\$p" output "\e[?1006;2\$y" PUSH "\e[?1015\$p" output "\e[?1015;1\$y" libvterm-0.1.4/t/26state_query.test0000644000175000017500000000165113720230652015344 0ustar leoleoINIT WANTSTATE !DA RESET PUSH "\e[c" output "\e[?1;2c" !DSR RESET PUSH "\e[5n" output "\e[0n" !CPR PUSH "\e[6n" output "\e[1;1R" PUSH "\e[10;10H\e[6n" output "\e[10;10R" !DECCPR PUSH "\e[?6n" output "\e[?10;10R" !DECRQSS on DECSCUSR PUSH "\e[3 q" PUSH "\eP\$q q\e\\" output "\eP1\$r3 q\e\\" !DECRQSS on SGR PUSH "\e[1;5;7m" PUSH "\eP\$qm\e\\" output "\eP1\$r1;5;7m\e\\" !DECRQSS on SGR ANSI colours PUSH "\e[0;31;42m" PUSH "\eP\$qm\e\\" output "\eP1\$r31;42m\e\\" !DECRQSS on SGR ANSI hi-bright colours PUSH "\e[0;93;104m" PUSH "\eP\$qm\e\\" output "\eP1\$r93;104m\e\\" !DECRQSS on SGR 256-palette colours PUSH "\e[0;38:5:56;48:5:78m" PUSH "\eP\$qm\e\\" output "\eP1\$r38:5:56;48:5:78m\e\\" !DECRQSS on SGR RGB8 colours PUSH "\e[0;38:2:24:68:112;48:2:13:57:101m" PUSH "\eP\$qm\e\\" output "\eP1\$r38:2:24:68:112;48:2:13:57:101m\e\\" !S8C1T on DSR PUSH "\e G" PUSH "\e[5n" output "\x{9b}0n" PUSH "\e F" libvterm-0.1.4/t/18state_termprops.test0000644000175000017500000000106713720230652016234 0ustar leoleoINIT WANTSTATE p RESET settermprop 1 true settermprop 2 true settermprop 7 1 !Cursor visibility PUSH "\e[?25h" settermprop 1 true PUSH "\e[?25\$p" output "\e[?25;1\$y" PUSH "\e[?25l" settermprop 1 false PUSH "\e[?25\$p" output "\e[?25;2\$y" !Cursor blink PUSH "\e[?12h" settermprop 2 true PUSH "\e[?12\$p" output "\e[?12;1\$y" PUSH "\e[?12l" settermprop 2 false PUSH "\e[?12\$p" output "\e[?12;2\$y" !Cursor shape PUSH "\e[3 q" settermprop 2 true settermprop 7 2 !Title PUSH "\e]2;Here is my title\a" settermprop 4 "Here is my title" libvterm-0.1.4/t/02parser.test0000644000175000017500000000507413720230652014270 0ustar leoleoINIT UTF8 0 WANTPARSER !Basic text PUSH "hello" text 0x68, 0x65, 0x6c, 0x6c, 0x6f !C0 PUSH "\x03" control 3 PUSH "\x1f" control 0x1f !C1 8bit PUSH "\x83" control 0x83 PUSH "\x9f" control 0x9f !C1 7bit PUSH "\e\x43" control 0x83 PUSH "\e\x5f" control 0x9f !High bytes PUSH "\xa0\xcc\xfe" text 0xa0, 0xcc, 0xfe !Mixed PUSH "1\n2" text 0x31 control 10 text 0x32 !Escape PUSH "\e=" escape "=" !Escape 2-byte PUSH "\e(X" escape "(X" !Split write Escape PUSH "\e(" PUSH "Y" escape "(Y" !Escape cancels Escape, starts another PUSH "\e(\e)Z" escape ")Z" !CAN cancels Escape, returns to normal mode PUSH "\e(\x{18}AB" text 0x41, 0x42 !C0 in Escape interrupts and continues PUSH "\e(\nX" control 10 escape "(X" !CSI 0 args PUSH "\e[a" csi 0x61 * !CSI 1 arg PUSH "\e[9b" csi 0x62 9 !CSI 2 args PUSH "\e[3;4c" csi 0x63 3,4 !CSI 1 arg 1 sub PUSH "\e[1:2c" csi 0x63 1+,2 !CSI many digits PUSH "\e[678d" csi 0x64 678 !CSI leading zero PUSH "\e[007e" csi 0x65 7 !CSI qmark PUSH "\e[?2;7f" csi 0x66 L=3f 2,7 !CSI greater PUSH "\e[>c" csi 0x63 L=3e * !CSI SP PUSH "\e[12 q" csi 0x71 12 I=20 !Mixed CSI PUSH "A\e[8mB" text 0x41 csi 0x6d 8 text 0x42 !Split write PUSH "\e" PUSH "[a" csi 0x61 * PUSH "foo\e[" text 0x66, 0x6f, 0x6f PUSH "4b" csi 0x62 4 PUSH "\e[12;" PUSH "3c" csi 0x63 12,3 !Escape cancels CSI, starts Escape PUSH "\e[123\e9" escape "9" !CAN cancels CSI, returns to normal mode PUSH "\e[12\x{18}AB" text 0x41, 0x42 !C0 in Escape interrupts and continues PUSH "\e[12\n;3X" control 10 csi 0x58 12,3 !OSC BEL PUSH "\e]1;Hello\x07" osc "1;Hello" !OSC ST (7bit) PUSH "\e]1;Hello\e\\" osc "1;Hello" !OSC ST (8bit) PUSH "\x{9d}1;Hello\x9c" osc "1;Hello" !Escape cancels OSC, starts Escape PUSH "\e]Something\e9" escape "9" !CAN cancels OSC, returns to normal mode PUSH "\e]12\x{18}AB" text 0x41, 0x42 !C0 in OSC interrupts and continues PUSH "\e]2;\nBye\x07" control 10 osc "2;Bye" !DCS BEL PUSH "\ePHello\x07" dcs "Hello" !DCS ST (7bit) PUSH "\ePHello\e\\" dcs "Hello" !DCS ST (8bit) PUSH "\x{90}Hello\x9c" dcs "Hello" !Escape cancels DCS, starts Escape PUSH "\ePSomething\e9" escape "9" !CAN cancels DCS, returns to normal mode PUSH "\eP12\x{18}AB" text 0x41, 0x42 !C0 in OSC interrupts and continues PUSH "\ePBy\ne\x07" control 10 dcs "Bye" !NUL ignored PUSH "\x{00}" !NUL ignored within CSI PUSH "\e[12\x{00}3m" csi 0x6d 123 !DEL ignored PUSH "\x{7f}" !DEL ignored within CSI PUSH "\e[12\x{7f}3m" csi 0x6d 123 !DEL inside text" PUSH "AB\x{7f}C" text 0x41,0x42 text 0x43 libvterm-0.1.4/t/67screen_dbl_wh.test0000644000175000017500000000140713720230652015601 0ustar leoleoINIT WANTSCREEN RESET !Single Width, Single Height RESET PUSH "\e#5" PUSH "abcde" ?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !Double Width, Single Height RESET PUSH "\e#6" PUSH "abcde" ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0) !Double Height RESET PUSH "\e#3" PUSH "abcde" PUSH "\r\n\e#4" PUSH "abcde" ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl dhl-top fg=rgb(240,240,240) bg=rgb(0,0,0) ?screen_cell 1,0 = {0x61} width=1 attrs={} dwl dhl-bottom fg=rgb(240,240,240) bg=rgb(0,0,0) !Late change RESET PUSH "abcde" ?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) PUSH "\e#6" ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0) libvterm-0.1.4/t/62screen_damage.test0000644000175000017500000000565513720230652015564 0ustar leoleoINIT WANTSCREEN Db !Putglyph RESET damage 0..25,0..80 PUSH "123" damage 0..1,0..1 = 0<31> damage 0..1,1..2 = 0<32> damage 0..1,2..3 = 0<33> !Erase PUSH "\e[H" PUSH "\e[3X" damage 0..1,0..3 !Scroll damages entire line in two chunks PUSH "\e[H\e[5@" damage 0..1,5..80 damage 0..1,0..5 !Scroll down damages entire screen in two chunks PUSH "\e[T" damage 1..25,0..80 damage 0..1,0..80 !Altscreen damages entire area PUSH "\e[?1049h" damage 0..25,0..80 PUSH "\e[?1049l" damage 0..25,0..80 WANTSCREEN m !Scroll invokes moverect but not damage PUSH "\e[5@" moverect 0..1,0..75 -> 0..1,5..80 damage 0..1,0..5 WANTSCREEN -m !Merge to cells RESET damage 0..25,0..80 DAMAGEMERGE CELL PUSH "A" damage 0..1,0..1 = 0<41> PUSH "B" damage 0..1,1..2 = 0<42> PUSH "C" damage 0..1,2..3 = 0<43> !Merge entire rows RESET damage 0..25,0..80 DAMAGEMERGE ROW PUSH "ABCDE\r\nEFGH" damage 0..1,0..5 = 0<41 42 43 44 45> DAMAGEFLUSH damage 1..2,0..4 = 1<45 46 47 48> PUSH "\e[3;6r\e[6H\eD" damage 2..5,0..80 DAMAGEFLUSH damage 5..6,0..80 !Merge entire screen RESET damage 0..25,0..80 DAMAGEMERGE SCREEN PUSH "ABCDE\r\nEFGH" DAMAGEFLUSH damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48> PUSH "\e[3;6r\e[6H\eD" DAMAGEFLUSH damage 2..6,0..80 !Merge entire screen with moverect WANTSCREEN m RESET damage 0..25,0..80 DAMAGEMERGE SCREEN PUSH "ABCDE\r\nEFGH" PUSH "\e[3;6r\e[6H\eD" damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48> moverect 3..6,0..80 -> 2..5,0..80 DAMAGEFLUSH damage 5..6,0..80 !Merge scroll RESET damage 0..25,0..80 DAMAGEMERGE SCROLL PUSH "\e[H1\r\n2\r\n3" PUSH "\e[25H\n\n\n" sb_pushline 80 = 31 sb_pushline 80 = 32 sb_pushline 80 = 33 DAMAGEFLUSH moverect 3..25,0..80 -> 0..22,0..80 damage 0..25,0..80 !Merge scroll with damage PUSH "\e[25H" PUSH "ABCDE\r\nEFGH\r\n" sb_pushline 80 = sb_pushline 80 = DAMAGEFLUSH moverect 2..25,0..80 -> 0..23,0..80 damage 22..25,0..80 = 22<41 42 43 44 45> 23<45 46 47 48> !Merge scroll with damage past region PUSH "\e[3;6r\e[6H1\r\n2\r\n3\r\n4\r\n5" DAMAGEFLUSH damage 2..6,0..80 = 2<32> 3<33> 4<34> 5<35> !Damage entirely outside scroll region PUSH "\e[HABC\e[3;6r\e[6H\r\n6" damage 0..1,0..3 = 0<41 42 43> DAMAGEFLUSH moverect 3..6,0..80 -> 2..5,0..80 damage 5..6,0..80 = 5<36> !Damage overlapping scroll region PUSH "\e[H\e[2J" DAMAGEFLUSH damage 0..25,0..80 PUSH "\e[HABCD\r\nEFGH\r\nIJKL\e[2;5r\e[5H\r\nMNOP" DAMAGEFLUSH moverect 2..5,0..80 -> 1..4,0..80 damage 0..5,0..80 = 0<41 42 43 44> 1<49 4A 4B 4C> ## TODO: is this right? !Merge scroll*2 with damage RESET damage 0..25,0..80 DAMAGEMERGE SCROLL PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n" sb_pushline 80 = moverect 1..25,0..80 -> 0..24,0..80 damage 24..25,0..80 = 24<41 42 43 44 45> moverect 24..25,4..80 -> 24..25,2..78 damage 24..25,78..80 sb_pushline 80 = DAMAGEFLUSH moverect 1..25,0..80 -> 0..24,0..80 damage 24..25,0..80 ?screen_chars 23,0,24,5 = 0x41,0x42,0x45 libvterm-0.1.4/t/03encoding_utf8.test0000644000175000017500000000622513720230652015530 0ustar leoleoINIT WANTENCODING !Low ENCIN "123" encout 0x31,0x32,0x33 # We want to prove the UTF-8 parser correctly handles all the sequences. # Easy way to do this is to check it does low/high boundary cases, as that # leaves only two for each sequence length # # These ranges are therefore: # # Two bytes: # U+0080 = 000 10000000 => 00010 000000 # => 11000010 10000000 = C2 80 # U+07FF = 111 11111111 => 11111 111111 # => 11011111 10111111 = DF BF # # Three bytes: # U+0800 = 00001000 00000000 => 0000 100000 000000 # => 11100000 10100000 10000000 = E0 A0 80 # U+FFFD = 11111111 11111101 => 1111 111111 111101 # => 11101111 10111111 10111101 = EF BF BD # (We avoid U+FFFE and U+FFFF as they're invalid codepoints) # # Four bytes: # U+10000 = 00001 00000000 00000000 => 000 010000 000000 000000 # => 11110000 10010000 10000000 10000000 = F0 90 80 80 # U+1FFFFF = 11111 11111111 11111111 => 111 111111 111111 111111 # => 11110111 10111111 10111111 10111111 = F7 BF BF BF !2 byte ENCIN "\xC2\x80\xDF\xBF" encout 0x0080, 0x07FF !3 byte ENCIN "\xE0\xA0\x80\xEF\xBF\xBD" encout 0x0800,0xFFFD !4 byte ENCIN "\xF0\x90\x80\x80\xF7\xBF\xBF\xBF" encout 0x10000,0x1fffff # Next up, we check some invalid sequences # + Early termination (back to low bytes too soon) # + Early restart (another sequence introduction before the previous one was finished) !Early termination ENCIN "\xC2!" encout 0xfffd,0x21 ENCIN "\xE0!\xE0\xA0!" encout 0xfffd,0x21,0xfffd,0x21 ENCIN "\xF0!\xF0\x90!\xF0\x90\x80!" encout 0xfffd,0x21,0xfffd,0x21,0xfffd,0x21 !Early restart ENCIN "\xC2\xC2\x90" encout 0xfffd,0x0090 ENCIN "\xE0\xC2\x90\xE0\xA0\xC2\x90" encout 0xfffd,0x0090,0xfffd,0x0090 ENCIN "\xF0\xC2\x90\xF0\x90\xC2\x90\xF0\x90\x80\xC2\x90" encout 0xfffd,0x0090,0xfffd,0x0090,0xfffd,0x0090 # Test the overlong sequences by giving an overlong encoding of U+0000 and # an encoding of the highest codepoint still too short # # Two bytes: # U+0000 = C0 80 # U+007F = 000 01111111 => 00001 111111 => # => 11000001 10111111 => C1 BF # # Three bytes: # U+0000 = E0 80 80 # U+07FF = 00000111 11111111 => 0000 011111 111111 # => 11100000 10011111 10111111 = E0 9F BF # # Four bytes: # U+0000 = F0 80 80 80 # U+FFFF = 11111111 11111111 => 000 001111 111111 111111 # => 11110000 10001111 10111111 10111111 = F0 8F BF BF !Overlong ENCIN "\xC0\x80\xC1\xBF" encout 0xfffd,0xfffd ENCIN "\xE0\x80\x80\xE0\x9F\xBF" encout 0xfffd,0xfffd ENCIN "\xF0\x80\x80\x80\xF0\x8F\xBF\xBF" encout 0xfffd,0xfffd # UTF-16 surrogates U+D800 and U+DFFF !UTF-16 Surrogates ENCIN "\xED\xA0\x80\xED\xBF\xBF" encout 0xfffd,0xfffd !Split write ENCIN "\xC2" ENCIN "\xA0" encout 0x000A0 ENCIN "\xE0" ENCIN "\xA0\x80" encout 0x00800 ENCIN "\xE0\xA0" ENCIN "\x80" encout 0x00800 ENCIN "\xF0" ENCIN "\x90\x80\x80" encout 0x10000 ENCIN "\xF0\x90" ENCIN "\x80\x80" encout 0x10000 ENCIN "\xF0\x90\x80" ENCIN "\x80" encout 0x10000 libvterm-0.1.4/t/29state_fallback.test0000644000175000017500000000036713720230652015744 0ustar leoleoINIT WANTSTATE f RESET !Unrecognised control PUSH "\x03" control 03 !Unrecognised CSI PUSH "\e[?15;2z" csi 0x7a L=3f 15,2 !Unrecognised OSC PUSH "\e]27;Something\e\\" osc "27;Something" !Unrecognised DCS PUSH "\ePz123\e\\" dcs "z123" libvterm-0.1.4/t/61screen_unicode.test0000644000175000017500000000367113720230652015767 0ustar leoleoINIT UTF8 1 WANTSCREEN !Single width UTF-8 # U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE # U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE RESET PUSH "\xC3\x81\xC3\xA9" ?screen_chars 0,0,1,80 = 0xc1,0xe9 ?screen_text 0,0,1,80 = 0xc3,0x81,0xc3,0xa9 ?screen_cell 0,0 = {0xc1} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !Wide char # U+FF10 = 0xEF 0xBC 0x90 name: FULLWIDTH DIGIT ZERO RESET PUSH "0123\e[H" PUSH "\xEF\xBC\x90" ?screen_chars 0,0,1,80 = 0xff10,0x32,0x33 ?screen_text 0,0,1,80 = 0xef,0xbc,0x90,0x32,0x33 ?screen_cell 0,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !Combining char # U+0301 = 0xCC 0x81 name: COMBINING ACUTE RESET PUSH "0123\e[H" PUSH "e\xCC\x81" ?screen_chars 0,0,1,80 = 0x65,0x301,0x31,0x32,0x33 ?screen_text 0,0,1,80 = 0x65,0xcc,0x81,0x31,0x32,0x33 ?screen_cell 0,0 = {0x65,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !10 combining accents should not crash RESET PUSH "e\xCC\x81\xCC\x82\xCC\x83\xCC\x84\xCC\x85\xCC\x86\xCC\x87\xCC\x88\xCC\x89\xCC\x8A" ?screen_cell 0,0 = {0x65,0x301,0x302,0x303,0x304,0x305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !40 combining accents in two split writes of 20 should not crash RESET PUSH "e\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81" PUSH "\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81" ?screen_cell 0,0 = {0x65,0x301,0x301,0x301,0x301,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !Outputing CJK doublewidth in 80th column should wraparound to next line and not crash" RESET PUSH "\e[80G\xEF\xBC\x90" ?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) ?screen_cell 1,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) libvterm-0.1.4/t/90vttest_01-movement-3.test0000644000175000017500000000105713720230652016621 0ustar leoleo# Test of cursor-control characters inside ESC sequences INIT WANTSTATE WANTSCREEN RESET PUSH "A B C D E F G H I" PUSH "\x0d\x0a" PUSH "A\e[2\bCB\e[2\bCC\e[2\bCD\e[2\bCE\e[2\bCF\e[2\bCG\e[2\bCH\e[2\bCI" PUSH "\x0d\x0a" PUSH "A \e[\x0d2CB\e[\x0d4CC\e[\x0d6CD\e[\x0d8CE\e[\x0d10CF\e[\x0d12CG\e[\x0d14CH\e[\x0d16CI" PUSH "\x0d\x0a" PUSH "A \e[1\x0bAB \e[1\x0bAC \e[1\x0bAD \e[1\x0bAE \e[1\x0bAF \e[1\x0bAG \e[1\x0bAH \e[1\x0bAI \e[1\x0bA" !Output $SEQ 0 2: ?screen_row \# = "A B C D E F G H I" ?screen_row 3 = "A B C D E F G H I " ?cursor = 3,18 libvterm-0.1.4/t/11state_movecursor.test0000644000175000017500000000561313720230652016377 0ustar leoleoINIT UTF8 1 WANTSTATE !Implicit PUSH "ABC" ?cursor = 0,3 !Backspace PUSH "\b" ?cursor = 0,2 !Horizontal Tab PUSH "\t" ?cursor = 0,8 !Carriage Return PUSH "\r" ?cursor = 0,0 !Linefeed PUSH "\n" ?cursor = 1,0 !Backspace bounded by lefthand edge PUSH "\e[4;2H" ?cursor = 3,1 PUSH "\b" ?cursor = 3,0 PUSH "\b" ?cursor = 3,0 !Backspace cancels phantom PUSH "\e[4;80H" ?cursor = 3,79 PUSH "X" ?cursor = 3,79 PUSH "\b" ?cursor = 3,78 !HT bounded by righthand edge PUSH "\e[1;78H" ?cursor = 0,77 PUSH "\t" ?cursor = 0,79 PUSH "\t" ?cursor = 0,79 RESET !Index PUSH "ABC\eD" ?cursor = 1,3 !Reverse Index PUSH "\eM" ?cursor = 0,3 !Newline PUSH "\eE" ?cursor = 1,0 RESET !Cursor Forward PUSH "\e[B" ?cursor = 1,0 PUSH "\e[3B" ?cursor = 4,0 PUSH "\e[0B" ?cursor = 5,0 !Cursor Down PUSH "\e[C" ?cursor = 5,1 PUSH "\e[3C" ?cursor = 5,4 PUSH "\e[0C" ?cursor = 5,5 !Cursor Up PUSH "\e[A" ?cursor = 4,5 PUSH "\e[3A" ?cursor = 1,5 PUSH "\e[0A" ?cursor = 0,5 !Cursor Backward PUSH "\e[D" ?cursor = 0,4 PUSH "\e[3D" ?cursor = 0,1 PUSH "\e[0D" ?cursor = 0,0 !Cursor Next Line PUSH " " ?cursor = 0,3 PUSH "\e[E" ?cursor = 1,0 PUSH " " ?cursor = 1,3 PUSH "\e[2E" ?cursor = 3,0 PUSH "\e[0E" ?cursor = 4,0 !Cursor Previous Line PUSH " " ?cursor = 4,3 PUSH "\e[F" ?cursor = 3,0 PUSH " " ?cursor = 3,3 PUSH "\e[2F" ?cursor = 1,0 PUSH "\e[0F" ?cursor = 0,0 !Cursor Horizonal Absolute PUSH "\n" ?cursor = 1,0 PUSH "\e[20G" ?cursor = 1,19 PUSH "\e[G" ?cursor = 1,0 !Cursor Position PUSH "\e[10;5H" ?cursor = 9,4 PUSH "\e[8H" ?cursor = 7,0 PUSH "\e[H" ?cursor = 0,0 !Cursor Position cancels phantom PUSH "\e[10;78H" ?cursor = 9,77 PUSH "ABC" ?cursor = 9,79 PUSH "\e[10;80H" PUSH "C" ?cursor = 9,79 PUSH "X" ?cursor = 10,1 RESET !Bounds Checking PUSH "\e[A" ?cursor = 0,0 PUSH "\e[D" ?cursor = 0,0 PUSH "\e[25;80H" ?cursor = 24,79 PUSH "\e[B" ?cursor = 24,79 PUSH "\e[C" ?cursor = 24,79 PUSH "\e[E" ?cursor = 24,0 PUSH "\e[H" ?cursor = 0,0 PUSH "\e[F" ?cursor = 0,0 PUSH "\e[999G" ?cursor = 0,79 PUSH "\e[99;99H" ?cursor = 24,79 RESET !Horizontal Position Absolute PUSH "\e[5`" ?cursor = 0,4 !Horizontal Position Relative PUSH "\e[3a" ?cursor = 0,7 !Horizontal Position Backward PUSH "\e[3j" ?cursor = 0,4 !Horizontal and Vertical Position PUSH "\e[3;3f" ?cursor = 2,2 !Vertical Position Absolute PUSH "\e[5d" ?cursor = 4,2 !Vertical Position Relative PUSH "\e[2e" ?cursor = 6,2 !Vertical Position Backward PUSH "\e[2k" ?cursor = 4,2 RESET !Horizontal Tab PUSH "\t" ?cursor = 0,8 PUSH " " ?cursor = 0,11 PUSH "\t" ?cursor = 0,16 PUSH " " ?cursor = 0,23 PUSH "\t" ?cursor = 0,24 PUSH " " ?cursor = 0,32 PUSH "\t" ?cursor = 0,40 !Cursor Horizontal Tab PUSH "\e[I" ?cursor = 0,48 PUSH "\e[2I" ?cursor = 0,64 !Cursor Backward Tab PUSH "\e[Z" ?cursor = 0,56 PUSH "\e[2Z" ?cursor = 0,40 libvterm-0.1.4/t/14state_encoding.test0000644000175000017500000000253313720230652015762 0ustar leoleoINIT WANTSTATE g !Default RESET PUSH "#" putglyph 0x23 1 0,0 !Designate G0=UK RESET PUSH "\e(A" PUSH "#" putglyph 0x00a3 1 0,0 !Designate G0=DEC drawing RESET PUSH "\e(0" PUSH "a" putglyph 0x2592 1 0,0 !Designate G1 + LS1 RESET PUSH "\e)0" PUSH "a" putglyph 0x61 1 0,0 PUSH "\x0e" PUSH "a" putglyph 0x2592 1 0,1 !LS0 PUSH "\x0f" PUSH "a" putglyph 0x61 1 0,2 !Designate G2 + LS2 PUSH "\e*0" PUSH "a" putglyph 0x61 1 0,3 PUSH "\en" PUSH "a" putglyph 0x2592 1 0,4 PUSH "\x0f" PUSH "a" putglyph 0x61 1 0,5 !Designate G3 + LS3 PUSH "\e+0" PUSH "a" putglyph 0x61 1 0,6 PUSH "\eo" PUSH "a" putglyph 0x2592 1 0,7 PUSH "\x0f" PUSH "a" putglyph 0x61 1 0,8 !SS2 PUSH "a\x{8e}aa" putglyph 0x61 1 0,9 putglyph 0x2592 1 0,10 putglyph 0x61 1 0,11 !SS3 PUSH "a\x{8f}aa" putglyph 0x61 1 0,12 putglyph 0x2592 1 0,13 putglyph 0x61 1 0,14 !LS1R RESET PUSH "\e~" PUSH "\xe1" putglyph 0x61 1 0,0 PUSH "\e)0" PUSH "\xe1" putglyph 0x2592 1 0,1 !LS2R RESET PUSH "\e}" PUSH "\xe1" putglyph 0x61 1 0,0 PUSH "\e*0" PUSH "\xe1" putglyph 0x2592 1 0,1 !LS3R RESET PUSH "\e|" PUSH "\xe1" putglyph 0x61 1 0,0 PUSH "\e+0" PUSH "\xe1" putglyph 0x2592 1 0,1 UTF8 1 !Mixed US-ASCII and UTF-8 # U+0108 == 0xc4 0x88 RESET PUSH "\e(B" PUSH "AB\xc4\x88D" putglyph 0x0041 1 0,0 putglyph 0x0042 1 0,1 putglyph 0x0108 1 0,2 putglyph 0x0044 1 0,3 libvterm-0.1.4/t/22state_save.test0000644000175000017500000000176313720230652015135 0ustar leoleoINIT WANTSTATE p RESET settermprop 1 true settermprop 2 true settermprop 7 1 !Set up state PUSH "\e[2;2H" ?cursor = 1,1 PUSH "\e[1m" ?pen bold = on !Save PUSH "\e[?1048h" !Change state PUSH "\e[5;5H" ?cursor = 4,4 PUSH "\e[4 q" settermprop 2 false settermprop 7 2 PUSH "\e[22;4m" ?pen bold = off ?pen underline = 1 !Restore PUSH "\e[?1048l" settermprop 1 true settermprop 2 true settermprop 7 1 ?cursor = 1,1 ?pen bold = on ?pen underline = 0 !Save/restore using DECSC/DECRC PUSH "\e[2;2H\e7" ?cursor = 1,1 PUSH "\e[5;5H" ?cursor = 4,4 PUSH "\e8" settermprop 1 true settermprop 2 true settermprop 7 1 ?cursor = 1,1 !Save twice, restore twice happens on both edge transitions PUSH "\e[2;10H\e[?1048h\e[6;10H\e[?1048h" PUSH "\e[H" ?cursor = 0,0 PUSH "\e[?1048l" settermprop 1 true settermprop 2 true settermprop 7 1 ?cursor = 5,9 PUSH "\e[H" ?cursor = 0,0 PUSH "\e[?1048l" settermprop 1 true settermprop 2 true settermprop 7 1 ?cursor = 5,9 libvterm-0.1.4/t/90vttest_02-screen-1.test0000644000175000017500000000052313720230652016242 0ustar leoleo# Test of WRAP AROUND mode setting. INIT WANTSCREEN RESET PUSH "\e[?7h" $REP 170: PUSH "*" PUSH "\e[?7l\e[3;1H" $REP 177: PUSH "*" PUSH "\e[?7h\e[5;1HOK" !Output $SEQ 0 2: ?screen_row \# = "********************************************************************************" ?screen_row 3 = "" ?screen_row 4 = "OK" libvterm-0.1.4/t/90vttest_02-screen-2.test0000644000175000017500000000075213720230652016247 0ustar leoleo# TAB setting/resetting INIT WANTSTATE WANTSCREEN RESET PUSH "\e[2J\e[3g" PUSH "\e[1;1H" $REP 26: PUSH "\e[3C\eH" PUSH "\e[1;4H" $REP 13: PUSH "\e[0g\e[6C" PUSH "\e[1;7H" PUSH "\e[1g\e[2g" PUSH "\e[1;1H" $REP 13: PUSH "\t*" PUSH "\e[2;2H" $REP 13: PUSH " *" !Output ?screen_row 0 = " * * * * * * * * * * * * *" ?screen_row 1 = " * * * * * * * * * * * * *" ?cursor = 1,79 libvterm-0.1.4/t/66screen_extent.test0000644000175000017500000000036513720230652015652 0ustar leoleoINIT WANTSCREEN !Bold extent RESET PUSH "AB\e[1mCD\e[mE" ?screen_attrs_extent 0,0 = 0,0-1,1 ?screen_attrs_extent 0,1 = 0,0-1,1 ?screen_attrs_extent 0,2 = 0,2-1,3 ?screen_attrs_extent 0,3 = 0,2-1,3 ?screen_attrs_extent 0,4 = 0,4-1,79 libvterm-0.1.4/t/92lp1640917.test0000644000175000017500000000040113720230652014161 0ustar leoleoINIT WANTSTATE !Mouse reporting should not break by idempotent DECSM 1002 PUSH "\e[?1002h" MOUSEMOVE 0,0 0 MOUSEBTN d 1 0 output "\e[M\x20\x21\x21" MOUSEMOVE 1,0 0 output "\e[M\x40\x21\x22" PUSH "\e[?1002h" MOUSEMOVE 2,0 0 output "\e[M\x40\x21\x23" libvterm-0.1.4/t/13state_edit.test0000644000175000017500000001116713720230652015123 0ustar leoleoINIT UTF8 1 WANTSTATE se !ICH RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ACD" PUSH "\e[2D" ?cursor = 0,1 PUSH "\e[@" scrollrect 0..1,1..80 => +0,-1 ?cursor = 0,1 PUSH "B" ?cursor = 0,2 PUSH "\e[3@" scrollrect 0..1,2..80 => +0,-3 !ICH with DECSLRM PUSH "\e[?69h" PUSH "\e[;50s" PUSH "\e[20G\e[@" scrollrect 0..1,19..50 => +0,-1 !ICH outside DECSLRM PUSH "\e[70G\e[@" # nothing happens !DCH RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ABBC" PUSH "\e[3D" ?cursor = 0,1 PUSH "\e[P" scrollrect 0..1,1..80 => +0,+1 ?cursor = 0,1 PUSH "\e[3P" scrollrect 0..1,1..80 => +0,+3 ?cursor = 0,1 !DCH with DECSLRM PUSH "\e[?69h" PUSH "\e[;50s" PUSH "\e[20G\e[P" scrollrect 0..1,19..50 => +0,+1 !DCH outside DECSLRM PUSH "\e[70G\e[P" # nothing happens !ECH RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ABC" PUSH "\e[2D" ?cursor = 0,1 PUSH "\e[X" erase 0..1,1..2 ?cursor = 0,1 PUSH "\e[3X" erase 0..1,1..4 ?cursor = 0,1 # ECH more columns than there are should be bounded PUSH "\e[100X" erase 0..1,1..80 !IL RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "A\r\nC" ?cursor = 1,1 PUSH "\e[L" scrollrect 1..25,0..80 => -1,+0 # TODO: ECMA-48 says we should move to line home, but neither xterm nor # xfce4-terminal do this ?cursor = 1,1 PUSH "\rB" ?cursor = 1,1 PUSH "\e[3L" scrollrect 1..25,0..80 => -3,+0 !IL with DECSTBM PUSH "\e[5;15r" PUSH "\e[5H\e[L" scrollrect 4..15,0..80 => -1,+0 !IL outside DECSTBM PUSH "\e[20H\e[L" # nothing happens !IL with DECSTBM+DECSLRM PUSH "\e[?69h" PUSH "\e[10;50s" PUSH "\e[5;10H\e[L" scrollrect 4..15,9..50 => -1,+0 !DL RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "A\r\nB\r\nB\r\nC" ?cursor = 3,1 PUSH "\e[2H" ?cursor = 1,0 PUSH "\e[M" scrollrect 1..25,0..80 => +1,+0 ?cursor = 1,0 PUSH "\e[3M" scrollrect 1..25,0..80 => +3,+0 ?cursor = 1,0 !DL with DECSTBM PUSH "\e[5;15r" PUSH "\e[5H\e[M" scrollrect 4..15,0..80 => +1,+0 !DL outside DECSTBM PUSH "\e[20H\e[M" # nothing happens !DL with DECSTBM+DECSLRM PUSH "\e[?69h" PUSH "\e[10;50s" PUSH "\e[5;10H\e[M" scrollrect 4..15,9..50 => +1,+0 !DECIC RESET erase 0..25,0..80 PUSH "\e[20G\e[5'}" scrollrect 0..25,19..80 => +0,-5 !DECIC with DECSTBM+DECSLRM PUSH "\e[?69h" PUSH "\e[4;20r\e[20;60s" PUSH "\e[4;20H\e[3'}" scrollrect 3..20,19..60 => +0,-3 !DECIC outside DECSLRM PUSH "\e[70G\e['}" # nothing happens !DECDC RESET erase 0..25,0..80 PUSH "\e[20G\e[5'~" scrollrect 0..25,19..80 => +0,+5 !DECDC with DECSTBM+DECSLRM PUSH "\e[?69h" PUSH "\e[4;20r\e[20;60s" PUSH "\e[4;20H\e[3'~" scrollrect 3..20,19..60 => +0,+3 !DECDC outside DECSLRM PUSH "\e[70G\e['~" # nothing happens !EL 0 RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ABCDE" PUSH "\e[3D" ?cursor = 0,2 PUSH "\e[0K" erase 0..1,2..80 ?cursor = 0,2 !EL 1 RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ABCDE" PUSH "\e[3D" ?cursor = 0,2 PUSH "\e[1K" erase 0..1,0..3 ?cursor = 0,2 !EL 2 RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ABCDE" PUSH "\e[3D" ?cursor = 0,2 PUSH "\e[2K" erase 0..1,0..80 ?cursor = 0,2 !SEL RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "\e[11G" ?cursor = 0,10 PUSH "\e[?0K" erase 0..1,10..80 selective ?cursor = 0,10 PUSH "\e[?1K" erase 0..1,0..11 selective ?cursor = 0,10 PUSH "\e[?2K" erase 0..1,0..80 selective ?cursor = 0,10 !ED 0 RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "\e[2;2H" ?cursor = 1,1 PUSH "\e[0J" erase 1..2,1..80 erase 2..25,0..80 ?cursor = 1,1 !ED 1 RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "\e[2;2H" ?cursor = 1,1 PUSH "\e[1J" erase 0..1,0..80 erase 1..2,0..2 ?cursor = 1,1 !ED 2 RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "\e[2;2H" ?cursor = 1,1 PUSH "\e[2J" erase 0..25,0..80 ?cursor = 1,1 !SED RESET erase 0..25,0..80 PUSH "\e[5;5H" ?cursor = 4,4 PUSH "\e[?0J" erase 4..5,4..80 selective erase 5..25,0..80 selective ?cursor = 4,4 PUSH "\e[?1J" erase 0..4,0..80 selective erase 4..5,0..5 selective ?cursor = 4,4 PUSH "\e[?2J" erase 0..25,0..80 selective ?cursor = 4,4 !DECRQSS on DECSCA PUSH "\e[2\"q" PUSH "\eP\$q\"q\e\\" output "\eP1\$r2\"q\e\\" WANTSTATE -s+m !ICH move+erase emuation RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ACD" PUSH "\e[2D" ?cursor = 0,1 PUSH "\e[@" moverect 0..1,1..79 -> 0..1,2..80 erase 0..1,1..2 ?cursor = 0,1 PUSH "B" ?cursor = 0,2 PUSH "\e[3@" moverect 0..1,2..77 -> 0..1,5..80 erase 0..1,2..5 !DCH move+erase emulation RESET erase 0..25,0..80 ?cursor = 0,0 PUSH "ABBC" PUSH "\e[3D" ?cursor = 0,1 PUSH "\e[P" moverect 0..1,2..80 -> 0..1,1..79 erase 0..1,79..80 ?cursor = 0,1 PUSH "\e[3P" moverect 0..1,4..80 -> 0..1,1..77 erase 0..1,77..80 ?cursor = 0,1 libvterm-0.1.4/t/28state_dbl_wh.test0000644000175000017500000000233713720230652015442 0ustar leoleoINIT WANTSTATE g !Single Width, Single Height RESET PUSH "\e#5" PUSH "Hello" putglyph 0x48 1 0,0 putglyph 0x65 1 0,1 putglyph 0x6c 1 0,2 putglyph 0x6c 1 0,3 putglyph 0x6f 1 0,4 !Double Width, Single Height RESET PUSH "\e#6" PUSH "Hello" putglyph 0x48 1 0,0 dwl putglyph 0x65 1 0,1 dwl putglyph 0x6c 1 0,2 dwl putglyph 0x6c 1 0,3 dwl putglyph 0x6f 1 0,4 dwl ?cursor = 0,5 PUSH "\e[40GAB" putglyph 0x41 1 0,39 dwl putglyph 0x42 1 1,0 ?cursor = 1,1 !Double Height RESET PUSH "\e#3" PUSH "Hello" putglyph 0x48 1 0,0 dwl dhl-top putglyph 0x65 1 0,1 dwl dhl-top putglyph 0x6c 1 0,2 dwl dhl-top putglyph 0x6c 1 0,3 dwl dhl-top putglyph 0x6f 1 0,4 dwl dhl-top ?cursor = 0,5 PUSH "\r\n\e#4" PUSH "Hello" putglyph 0x48 1 1,0 dwl dhl-bottom putglyph 0x65 1 1,1 dwl dhl-bottom putglyph 0x6c 1 1,2 dwl dhl-bottom putglyph 0x6c 1 1,3 dwl dhl-bottom putglyph 0x6f 1 1,4 dwl dhl-bottom ?cursor = 1,5 !Double Width scrolling RESET PUSH "\e[20H\e#6ABC" putglyph 0x41 1 19,0 dwl putglyph 0x42 1 19,1 dwl putglyph 0x43 1 19,2 dwl PUSH "\e[25H\n" PUSH "\e[19;4HDE" putglyph 0x44 1 18,3 dwl putglyph 0x45 1 18,4 dwl PUSH "\e[H\eM" PUSH "\e[20;6HFG" putglyph 0x46 1 19,5 dwl putglyph 0x47 1 19,6 dwl libvterm-0.1.4/t/20state_wrapping.test0000644000175000017500000000235313720230652016020 0ustar leoleoINIT UTF8 1 WANTSTATE gm !79th Column PUSH "\e[75G" PUSH "A"x5 putglyph 0x41 1 0,74 putglyph 0x41 1 0,75 putglyph 0x41 1 0,76 putglyph 0x41 1 0,77 putglyph 0x41 1 0,78 ?cursor = 0,79 !80th Column Phantom PUSH "A" putglyph 0x41 1 0,79 ?cursor = 0,79 !Line Wraparound PUSH "B" putglyph 0x42 1 1,0 ?cursor = 1,1 !Line Wraparound during combined write PUSH "\e[78G" PUSH "BBBCC" putglyph 0x42 1 1,77 putglyph 0x42 1 1,78 putglyph 0x42 1 1,79 putglyph 0x43 1 2,0 putglyph 0x43 1 2,1 ?cursor = 2,2 !DEC Auto Wrap Mode RESET PUSH "\e[?7l" PUSH "\e[75G" PUSH "D"x6 putglyph 0x44 1 0,74 putglyph 0x44 1 0,75 putglyph 0x44 1 0,76 putglyph 0x44 1 0,77 putglyph 0x44 1 0,78 putglyph 0x44 1 0,79 ?cursor = 0,79 PUSH "D" putglyph 0x44 1 0,79 ?cursor = 0,79 PUSH "\e[?7h" !80th column causes linefeed on wraparound PUSH "\e[25;78HABC" putglyph 0x41 1 24,77 putglyph 0x42 1 24,78 putglyph 0x43 1 24,79 ?cursor = 24,79 PUSH "D" moverect 1..25,0..80 -> 0..24,0..80 putglyph 0x44 1 24,0 !80th column phantom linefeed phantom cancelled by explicit cursor move PUSH "\e[25;78HABC" putglyph 0x41 1 24,77 putglyph 0x42 1 24,78 putglyph 0x43 1 24,79 ?cursor = 24,79 PUSH "\e[25;1HD" putglyph 0x44 1 24,0 libvterm-0.1.4/t/12state_scroll.test0000644000175000017500000000474613720230652015500 0ustar leoleoINIT UTF8 1 WANTSTATE s !Linefeed PUSH "\n"x24 ?cursor = 24,0 PUSH "\n" scrollrect 0..25,0..80 => +1,+0 ?cursor = 24,0 RESET !Index PUSH "\e[25H" PUSH "\eD" scrollrect 0..25,0..80 => +1,+0 RESET !Reverse Index PUSH "\eM" scrollrect 0..25,0..80 => -1,+0 RESET !Linefeed in DECSTBM PUSH "\e[1;10r" ?cursor = 0,0 PUSH "\n"x9 ?cursor = 9,0 PUSH "\n" scrollrect 0..10,0..80 => +1,+0 ?cursor = 9,0 !Linefeed outside DECSTBM PUSH "\e[20H" ?cursor = 19,0 PUSH "\n" ?cursor = 20,0 !Index in DECSTBM PUSH "\e[9;10r" PUSH "\e[10H" PUSH "\eM" ?cursor = 8,0 PUSH "\eM" scrollrect 8..10,0..80 => -1,+0 !Reverse Index in DECSTBM PUSH "\e[25H" ?cursor = 24,0 PUSH "\n" # no scrollrect ?cursor = 24,0 !Linefeed in DECSTBM+DECSLRM PUSH "\e[?69h" PUSH "\e[3;10r\e[10;40s" PUSH "\e[10;10H\n" scrollrect 2..10,9..40 => +1,+0 !IND/RI in DECSTBM+DECSLRM PUSH "\eD" scrollrect 2..10,9..40 => +1,+0 PUSH "\e[3;10H\eM" scrollrect 2..10,9..40 => -1,+0 !DECRQSS on DECSTBM PUSH "\eP\$qr\e\\" output "\eP1\$r3;10r\e\\" !DECRQSS on DECSLRM PUSH "\eP\$qs\e\\" output "\eP1\$r10;40s\e\\" !Setting invalid DECSLRM with !DECVSSM is still rejected PUSH "\e[?69l\e[;0s\e[?69h" RESET !Scroll Down PUSH "\e[S" scrollrect 0..25,0..80 => +1,+0 ?cursor = 0,0 PUSH "\e[2S" scrollrect 0..25,0..80 => +2,+0 ?cursor = 0,0 PUSH "\e[100S" scrollrect 0..25,0..80 => +25,+0 !Scroll Up PUSH "\e[T" scrollrect 0..25,0..80 => -1,+0 ?cursor = 0,0 PUSH "\e[2T" scrollrect 0..25,0..80 => -2,+0 ?cursor = 0,0 PUSH "\e[100T" scrollrect 0..25,0..80 => -25,+0 !SD/SU in DECSTBM PUSH "\e[5;20r" PUSH "\e[S" scrollrect 4..20,0..80 => +1,+0 PUSH "\e[T" scrollrect 4..20,0..80 => -1,+0 RESET !SD/SU in DECSTBM+DECSLRM PUSH "\e[?69h" PUSH "\e[3;10r\e[10;40s" ?cursor = 0,0 PUSH "\e[3;10H" ?cursor = 2,9 PUSH "\e[S" scrollrect 2..10,9..40 => +1,+0 PUSH "\e[?69l" PUSH "\e[S" scrollrect 2..10,0..80 => +1,+0 !Invalid boundaries RESET PUSH "\e[100;105r\eD" PUSH "\e[5;2r\eD" RESET WANTSTATE -s+me !Scroll Down move+erase emulation PUSH "\e[S" moverect 1..25,0..80 -> 0..24,0..80 erase 24..25,0..80 ?cursor = 0,0 PUSH "\e[2S" moverect 2..25,0..80 -> 0..23,0..80 erase 23..25,0..80 ?cursor = 0,0 !Scroll Up move+erase emulation PUSH "\e[T" moverect 0..24,0..80 -> 1..25,0..80 erase 0..1,0..80 ?cursor = 0,0 PUSH "\e[2T" moverect 0..23,0..80 -> 2..25,0..80 erase 0..2,0..80 ?cursor = 0,0 !DECSTBM resets cursor position PUSH "\e[5;5H" ?cursor = 4,4 PUSH "\e[r" ?cursor = 0,0 libvterm-0.1.4/t/90vttest_02-screen-3.test0000644000175000017500000000026513720230652016247 0ustar leoleo# Origin mode INIT WANTSCREEN RESET PUSH "\e[?6h" PUSH "\e[23;24r" PUSH "\n" PUSH "Bottom" PUSH "\e[1;1H" PUSH "Above" !Output ?screen_row 22 = "Above" ?screen_row 23 = "Bottom" libvterm-0.1.4/t/16state_resize.test0000644000175000017500000000141213720230652015472 0ustar leoleoINIT WANTSTATE g !Placement RESET PUSH "AB\e[79GCDE" putglyph 0x41 1 0,0 putglyph 0x42 1 0,1 putglyph 0x43 1 0,78 putglyph 0x44 1 0,79 putglyph 0x45 1 1,0 !Resize RESET RESIZE 27,85 PUSH "AB\e[79GCDE" putglyph 0x41 1 0,0 putglyph 0x42 1 0,1 putglyph 0x43 1 0,78 putglyph 0x44 1 0,79 putglyph 0x45 1 0,80 ?cursor = 0,81 !Resize without reset RESIZE 28,90 ?cursor = 0,81 PUSH "FGHI" putglyph 0x46 1 0,81 putglyph 0x47 1 0,82 putglyph 0x48 1 0,83 putglyph 0x49 1 0,84 ?cursor = 0,85 !Resize shrink moves cursor RESIZE 25,80 ?cursor = 0,79 !Resize grow doesn't cancel phantom RESET PUSH "\e[79GAB" putglyph 0x41 1 0,78 putglyph 0x42 1 0,79 ?cursor = 0,79 RESIZE 30,100 ?cursor = 0,80 PUSH "C" putglyph 0x43 1 0,80 ?cursor = 0,81 libvterm-0.1.4/t/10state_putglyph.test0000644000175000017500000000230713720230652016043 0ustar leoleoINIT UTF8 1 WANTSTATE g !Low RESET PUSH "ABC" putglyph 0x41 1 0,0 putglyph 0x42 1 0,1 putglyph 0x43 1 0,2 !UTF-8 1 char # U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE # U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE RESET PUSH "\xC3\x81\xC3\xA9" putglyph 0xc1 1 0,0 putglyph 0xe9 1 0,1 !UTF-8 split writes RESET PUSH "\xC3" PUSH "\x81" putglyph 0xc1 1 0,0 !UTF-8 wide char # U+FF10 = 0xEF 0xBC 0x90 name: FULLWIDTH DIGIT ZERO RESET PUSH "\xEF\xBC\x90 " putglyph 0xff10 2 0,0 putglyph 0x20 1 0,2 !UTF-8 emoji wide char # U+1F600 = 0xF0 0x9F 0x98 0x80 name: GRINNING FACE RESET PUSH "\xF0\x9F\x98\x80 " putglyph 0x1f600 2 0,0 putglyph 0x20 1 0,2 !UTF-8 combining chars # U+0301 = 0xCC 0x81 name: COMBINING ACUTE RESET PUSH "e\xCC\x81Z" putglyph 0x65,0x301 1 0,0 putglyph 0x5a 1 0,1 !Combining across buffers RESET PUSH "e" putglyph 0x65 1 0,0 PUSH "\xCC\x81Z" putglyph 0x65,0x301 1 0,0 putglyph 0x5a 1 0,1 RESET PUSH "e" putglyph 0x65 1 0,0 PUSH "\xCC\x81" putglyph 0x65,0x301 1 0,0 PUSH "\xCC\x82" putglyph 0x65,0x301,0x302 1 0,0 !DECSCA protected RESET PUSH "A\e[1\"qB\e[2\"qC" putglyph 0x41 1 0,0 putglyph 0x42 1 0,1 prot putglyph 0x43 1 0,2 libvterm-0.1.4/t/21state_tabstops.test0000644000175000017500000000160213720230652016025 0ustar leoleoINIT WANTSTATE g !Initial RESET PUSH "\tX" putglyph 0x58 1 0,8 PUSH "\tX" putglyph 0x58 1 0,16 ?cursor = 0,17 !HTS PUSH "\e[5G\eH" PUSH "\e[G\tX" putglyph 0x58 1 0,4 ?cursor = 0,5 !TBC 0 PUSH "\e[9G\e[g" PUSH "\e[G\tX\tX" putglyph 0x58 1 0,4 putglyph 0x58 1 0,16 ?cursor = 0,17 !TBC 3 PUSH "\e[3g\e[50G\eH\e[G" ?cursor = 0,0 PUSH "\tX" putglyph 0x58 1 0,49 ?cursor = 0,50 !Tabstops after resize RESET RESIZE 30,100 # Should be 100/8 = 12 tabstops PUSH "\tX" putglyph 0x58 1 0,8 PUSH "\tX" putglyph 0x58 1 0,16 PUSH "\tX" putglyph 0x58 1 0,24 PUSH "\tX" putglyph 0x58 1 0,32 PUSH "\tX" putglyph 0x58 1 0,40 PUSH "\tX" putglyph 0x58 1 0,48 PUSH "\tX" putglyph 0x58 1 0,56 PUSH "\tX" putglyph 0x58 1 0,64 PUSH "\tX" putglyph 0x58 1 0,72 PUSH "\tX" putglyph 0x58 1 0,80 PUSH "\tX" putglyph 0x58 1 0,88 PUSH "\tX" putglyph 0x58 1 0,96 ?cursor = 0,97 libvterm-0.1.4/t/65screen_protect.test0000644000175000017500000000044713720230652016023 0ustar leoleoINIT WANTSCREEN !Selective erase RESET PUSH "A\e[1\"qB\e[\"qC" ?screen_chars 0,0,1,3 = 0x41,0x42,0x43 PUSH "\e[G\e[?J" ?screen_chars 0,0,1,3 = 0x20,0x42 !Non-selective erase RESET PUSH "A\e[1\"qB\e[\"qC" ?screen_chars 0,0,1,3 = 0x41,0x42,0x43 PUSH "\e[G\e[J" ?screen_chars 0,0,1,3 = libvterm-0.1.4/t/30state_pen.test0000644000175000017500000000370313720230652014754 0ustar leoleoINIT UTF8 1 WANTSTATE !Reset PUSH "\e[m" ?pen bold = off ?pen underline = 0 ?pen italic = off ?pen blink = off ?pen reverse = off ?pen font = 0 ?pen foreground = rgb(240,240,240,is_default_fg) ?pen background = rgb(0,0,0,is_default_bg) !Bold PUSH "\e[1m" ?pen bold = on PUSH "\e[22m" ?pen bold = off PUSH "\e[1m\e[m" ?pen bold = off !Underline PUSH "\e[4m" ?pen underline = 1 PUSH "\e[21m" ?pen underline = 2 PUSH "\e[24m" ?pen underline = 0 PUSH "\e[4m\e[4:0m" ?pen underline = 0 PUSH "\e[4:1m" ?pen underline = 1 PUSH "\e[4:2m" ?pen underline = 2 PUSH "\e[4:3m" ?pen underline = 3 PUSH "\e[4m\e[m" ?pen underline = 0 !Italic PUSH "\e[3m" ?pen italic = on PUSH "\e[23m" ?pen italic = off PUSH "\e[3m\e[m" ?pen italic = off !Blink PUSH "\e[5m" ?pen blink = on PUSH "\e[25m" ?pen blink = off PUSH "\e[5m\e[m" ?pen blink = off !Reverse PUSH "\e[7m" ?pen reverse = on PUSH "\e[27m" ?pen reverse = off PUSH "\e[7m\e[m" ?pen reverse = off !Font Selection PUSH "\e[11m" ?pen font = 1 PUSH "\e[19m" ?pen font = 9 PUSH "\e[10m" ?pen font = 0 PUSH "\e[11m\e[m" ?pen font = 0 !Foreground PUSH "\e[31m" ?pen foreground = idx(1) PUSH "\e[32m" ?pen foreground = idx(2) PUSH "\e[34m" ?pen foreground = idx(4) PUSH "\e[91m" ?pen foreground = idx(9) PUSH "\e[38:2:10:20:30m" ?pen foreground = rgb(10,20,30) PUSH "\e[38:5:1m" ?pen foreground = idx(1) PUSH "\e[39m" ?pen foreground = rgb(240,240,240,is_default_fg) !Background PUSH "\e[41m" ?pen background = idx(1) PUSH "\e[42m" ?pen background = idx(2) PUSH "\e[44m" ?pen background = idx(4) PUSH "\e[101m" ?pen background = idx(9) PUSH "\e[48:2:10:20:30m" ?pen background = rgb(10,20,30) PUSH "\e[48:5:1m" ?pen background = idx(1) PUSH "\e[49m" ?pen background = rgb(0,0,0,is_default_bg) !Bold+ANSI colour == highbright PUSH "\e[m\e[1;37m" ?pen bold = on ?pen foreground = idx(15) PUSH "\e[m\e[37;1m" ?pen bold = on ?pen foreground = idx(15) libvterm-0.1.4/t/harness.c0000644000175000017500000006073013720230652013540 0ustar leoleo#include "vterm.h" #include "../src/vterm_internal.h" // We pull in some internal bits too #include #include #define streq(a,b) (!strcmp(a,b)) #define strstartswith(a,b) (!strncmp(a,b,strlen(b))) static size_t inplace_hex2bytes(char *s) { char *inpos = s, *outpos = s; while(*inpos) { unsigned int ch; sscanf(inpos, "%2x", &ch); *outpos = ch; outpos += 1; inpos += 2; } return outpos - s; } static VTermModifier strpe_modifiers(char **strp) { VTermModifier state = 0; while((*strp)[0]) { switch(((*strp)++)[0]) { case 'S': state |= VTERM_MOD_SHIFT; break; case 'C': state |= VTERM_MOD_CTRL; break; case 'A': state |= VTERM_MOD_ALT; break; default: return state; } } return state; } static VTermKey strp_key(char *str) { static struct { char *name; VTermKey key; } keys[] = { { "Up", VTERM_KEY_UP }, { "Tab", VTERM_KEY_TAB }, { "Enter", VTERM_KEY_ENTER }, { "KP0", VTERM_KEY_KP_0 }, { "F1", VTERM_KEY_FUNCTION(1) }, { NULL, VTERM_KEY_NONE }, }; for(int i = 0; keys[i].name; i++) { if(streq(str, keys[i].name)) return keys[i].key; } return VTERM_KEY_NONE; } static void print_color(const VTermColor *col) { if (VTERM_COLOR_IS_RGB(col)) { printf("rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue); } else if (VTERM_COLOR_IS_INDEXED(col)) { printf("idx(%d", col->indexed.idx); } else { printf("invalid(%d", col->type); } if (VTERM_COLOR_IS_DEFAULT_FG(col)) { printf(",is_default_fg"); } if (VTERM_COLOR_IS_DEFAULT_BG(col)) { printf(",is_default_bg"); } printf(")"); } static VTerm *vt; static VTermState *state; static VTermScreen *screen; static VTermEncodingInstance encoding; static void term_output(const char *s, size_t len, void *user) { printf("output "); for(int i = 0; i < len; i++) printf("%x%s", (unsigned char)s[i], i < len-1 ? "," : "\n"); } static int parser_text(const char bytes[], size_t len, void *user) { printf("text "); int i; for(i = 0; i < len; i++) { unsigned char b = bytes[i]; if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) break; printf(i ? ",%x" : "%x", b); } printf("\n"); return i; } static int parser_control(unsigned char control, void *user) { printf("control %02x\n", control); return 1; } static int parser_escape(const char bytes[], size_t len, void *user) { if(bytes[0] >= 0x20 && bytes[0] < 0x30) { if(len < 2) return -1; len = 2; } else { len = 1; } printf("escape "); for(int i = 0; i < len; i++) printf("%02x", bytes[i]); printf("\n"); return len; } static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) { printf("csi %02x", command); if(leader && leader[0]) { printf(" L="); for(int i = 0; leader[i]; i++) printf("%02x", leader[i]); } for(int i = 0; i < argcount; i++) { char sep = i ? ',' : ' '; if(args[i] == CSI_ARG_MISSING) printf("%c*", sep); else printf("%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : ""); } if(intermed && intermed[0]) { printf(" I="); for(int i = 0; intermed[i]; i++) printf("%02x", intermed[i]); } printf("\n"); return 1; } static int parser_osc(const char *command, size_t cmdlen, void *user) { printf("osc "); for(int i = 0; i < cmdlen; i++) printf("%02x", command[i]); printf("\n"); return 1; } static int parser_dcs(const char *command, size_t cmdlen, void *user) { printf("dcs "); for(int i = 0; i < cmdlen; i++) printf("%02x", command[i]); printf("\n"); return 1; } static VTermParserCallbacks parser_cbs = { .text = parser_text, .control = parser_control, .escape = parser_escape, .csi = parser_csi, .osc = parser_osc, .dcs = parser_dcs, }; /* These callbacks are shared by State and Screen */ static int want_movecursor = 0; static VTermPos state_pos; static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) { state_pos = pos; if(want_movecursor) printf("movecursor %d,%d\n", pos.row, pos.col); return 1; } static int want_scrollrect = 0; static int scrollrect(VTermRect rect, int downward, int rightward, void *user) { if(!want_scrollrect) return 0; printf("scrollrect %d..%d,%d..%d => %+d,%+d\n", rect.start_row, rect.end_row, rect.start_col, rect.end_col, downward, rightward); return 1; } static int want_moverect = 0; static int moverect(VTermRect dest, VTermRect src, void *user) { if(!want_moverect) return 0; printf("moverect %d..%d,%d..%d -> %d..%d,%d..%d\n", src.start_row, src.end_row, src.start_col, src.end_col, dest.start_row, dest.end_row, dest.start_col, dest.end_col); return 1; } static int want_settermprop = 0; static int settermprop(VTermProp prop, VTermValue *val, void *user) { if(!want_settermprop) return 1; VTermValueType type = vterm_get_prop_type(prop); switch(type) { case VTERM_VALUETYPE_BOOL: printf("settermprop %d %s\n", prop, val->boolean ? "true" : "false"); return 1; case VTERM_VALUETYPE_INT: printf("settermprop %d %d\n", prop, val->number); return 1; case VTERM_VALUETYPE_STRING: printf("settermprop %d \"%s\"\n", prop, val->string); return 1; case VTERM_VALUETYPE_COLOR: printf("settermprop %d ", prop); print_color(&val->color); printf("\n"); return 1; case VTERM_N_VALUETYPES: return 0; } return 0; } /* These callbacks are for State */ static int want_state_putglyph = 0; static int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) { if(!want_state_putglyph) return 1; printf("putglyph "); for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) printf(i ? ",%x" : "%x", info->chars[i]); printf(" %d %d,%d", info->width, pos.row, pos.col); if(info->protected_cell) printf(" prot"); if(info->dwl) printf(" dwl"); if(info->dhl) printf(" dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" ); printf("\n"); return 1; } static int want_state_erase = 0; static int state_erase(VTermRect rect, int selective, void *user) { if(!want_state_erase) return 1; printf("erase %d..%d,%d..%d%s\n", rect.start_row, rect.end_row, rect.start_col, rect.end_col, selective ? " selective" : ""); return 1; } static struct { int bold; int underline; int italic; int blink; int reverse; int strike; int font; VTermColor foreground; VTermColor background; } state_pen; static int state_setpenattr(VTermAttr attr, VTermValue *val, void *user) { switch(attr) { case VTERM_ATTR_BOLD: state_pen.bold = val->boolean; break; case VTERM_ATTR_UNDERLINE: state_pen.underline = val->number; break; case VTERM_ATTR_ITALIC: state_pen.italic = val->boolean; break; case VTERM_ATTR_BLINK: state_pen.blink = val->boolean; break; case VTERM_ATTR_REVERSE: state_pen.reverse = val->boolean; break; case VTERM_ATTR_STRIKE: state_pen.strike = val->boolean; break; case VTERM_ATTR_FONT: state_pen.font = val->number; break; case VTERM_ATTR_FOREGROUND: state_pen.foreground = val->color; break; case VTERM_ATTR_BACKGROUND: state_pen.background = val->color; break; case VTERM_N_ATTRS: return 0; } return 1; } static int state_setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) { return 1; } VTermStateCallbacks state_cbs = { .putglyph = state_putglyph, .movecursor = movecursor, .scrollrect = scrollrect, .moverect = moverect, .erase = state_erase, .setpenattr = state_setpenattr, .settermprop = settermprop, .setlineinfo = state_setlineinfo, }; static int want_screen_damage = 0; static int want_screen_damage_cells = 0; static int screen_damage(VTermRect rect, void *user) { if(!want_screen_damage) return 1; printf("damage %d..%d,%d..%d", rect.start_row, rect.end_row, rect.start_col, rect.end_col); if(want_screen_damage_cells) { bool equals = false; for(int row = rect.start_row; row < rect.end_row; row++) { int eol = rect.end_col; while(eol > rect.start_col) { VTermScreenCell cell; vterm_screen_get_cell(screen, (VTermPos) { .row = row, .col = eol-1 }, &cell); if(cell.chars[0]) break; eol--; } if(eol == rect.start_col) break; if(!equals) printf(" ="), equals = true; printf(" %d<", row); for(int col = rect.start_col; col < eol; col++) { VTermScreenCell cell; vterm_screen_get_cell(screen, (VTermPos) { .row = row, .col = col }, &cell); printf(col == rect.start_col ? "%02X" : " %02X", cell.chars[0]); } printf(">"); } } printf("\n"); return 1; } static int want_screen_scrollback = 0; static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) { if(!want_screen_scrollback) return 1; int eol = cols; while(eol && !cells[eol-1].chars[0]) eol--; printf("sb_pushline %d =", cols); for(int c = 0; c < eol; c++) printf(" %02X", cells[c].chars[0]); printf("\n"); return 1; } static int screen_sb_popline(int cols, VTermScreenCell *cells, void *user) { if(!want_screen_scrollback) return 0; // All lines of scrollback contain "ABCDE" for(int col = 0; col < cols; col++) { if(col < 5) cells[col].chars[0] = 'A' + col; else cells[col].chars[0] = 0; cells[col].width = 1; } printf("sb_popline %d\n", cols); return 1; } VTermScreenCallbacks screen_cbs = { .damage = screen_damage, .moverect = moverect, .movecursor = movecursor, .settermprop = settermprop, .sb_pushline = screen_sb_pushline, .sb_popline = screen_sb_popline, }; int main(int argc, char **argv) { char line[1024] = {0}; int flag; int err; setvbuf(stdout, NULL, _IONBF, 0); while(fgets(line, sizeof line, stdin)) { err = 0; char *nl; if((nl = strchr(line, '\n'))) *nl = '\0'; if(streq(line, "INIT")) { if(!vt) vt = vterm_new(25, 80); vterm_output_set_callback(vt, term_output, NULL); } else if(streq(line, "WANTPARSER")) { vterm_parser_set_callbacks(vt, &parser_cbs, NULL); } else if(strstartswith(line, "WANTSTATE") && (line[9] == '\0' || line[9] == ' ')) { if(!state) { state = vterm_obtain_state(vt); vterm_state_set_callbacks(state, &state_cbs, NULL); vterm_state_set_bold_highbright(state, 1); vterm_state_reset(state, 1); } int i = 9; int sense = 1; while(line[i] == ' ') i++; for( ; line[i]; i++) switch(line[i]) { case '+': sense = 1; break; case '-': sense = 0; break; case 'g': want_state_putglyph = sense; break; case 's': want_scrollrect = sense; break; case 'm': want_moverect = sense; break; case 'e': want_state_erase = sense; break; case 'p': want_settermprop = sense; break; case 'f': vterm_state_set_unrecognised_fallbacks(state, sense ? &parser_cbs : NULL, NULL); break; default: fprintf(stderr, "Unrecognised WANTSTATE flag '%c'\n", line[i]); } } else if(strstartswith(line, "WANTSCREEN") && (line[10] == '\0' || line[10] == ' ')) { if(!screen) screen = vterm_obtain_screen(vt); vterm_screen_enable_altscreen(screen, 1); vterm_screen_set_callbacks(screen, &screen_cbs, NULL); int i = 10; int sense = 1; while(line[i] == ' ') i++; for( ; line[i]; i++) switch(line[i]) { case '-': sense = 0; break; case 'd': want_screen_damage = sense; break; case 'D': want_screen_damage = sense; want_screen_damage_cells = sense; break; case 'm': want_moverect = sense; break; case 'c': want_movecursor = sense; break; case 'p': want_settermprop = 1; break; case 'b': want_screen_scrollback = sense; break; default: fprintf(stderr, "Unrecognised WANTSCREEN flag '%c'\n", line[i]); } } else if(sscanf(line, "UTF8 %d", &flag)) { vterm_set_utf8(vt, flag); } else if(streq(line, "RESET")) { if(state) { vterm_state_reset(state, 1); vterm_state_get_cursorpos(state, &state_pos); } if(screen) { vterm_screen_reset(screen, 1); } } else if(strstartswith(line, "RESIZE ")) { int rows, cols; char *linep = line + 7; while(linep[0] == ' ') linep++; sscanf(linep, "%d, %d", &rows, &cols); vterm_set_size(vt, rows, cols); } else if(strstartswith(line, "PUSH ")) { char *bytes = line + 5; size_t len = inplace_hex2bytes(bytes); size_t written = vterm_input_write(vt, bytes, len); if(written < len) fprintf(stderr, "! short write\n"); } else if(streq(line, "WANTENCODING")) { /* This isn't really external API but it's hard to get this out any * other way */ encoding.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); if(encoding.enc->init) (*encoding.enc->init)(encoding.enc, encoding.data); } else if(strstartswith(line, "ENCIN ")) { char *bytes = line + 6; size_t len = inplace_hex2bytes(bytes); uint32_t cp[len]; int cpi = 0; size_t pos = 0; (*encoding.enc->decode)(encoding.enc, encoding.data, cp, &cpi, len, bytes, &pos, len); if(cpi > 0) { printf("encout "); for(int i = 0; i < cpi; i++) { printf(i ? ",%x" : "%x", cp[i]); } printf("\n"); } } else if(strstartswith(line, "INCHAR ")) { char *linep = line + 7; unsigned int c = 0; while(linep[0] == ' ') linep++; VTermModifier mod = strpe_modifiers(&linep); sscanf(linep, " %x", &c); vterm_keyboard_unichar(vt, c, mod); } else if(strstartswith(line, "INKEY ")) { char *linep = line + 6; while(linep[0] == ' ') linep++; VTermModifier mod = strpe_modifiers(&linep); while(linep[0] == ' ') linep++; VTermKey key = strp_key(linep); vterm_keyboard_key(vt, key, mod); } else if(strstartswith(line, "PASTE ")) { char *linep = line + 6; if(streq(linep, "START")) vterm_keyboard_start_paste(vt); else if(streq(linep, "END")) vterm_keyboard_end_paste(vt); else goto abort_line; } else if(strstartswith(line, "FOCUS ")) { char *linep = line + 6; if(streq(linep, "IN")) vterm_state_focus_in(state); else if(streq(linep, "OUT")) vterm_state_focus_out(state); else goto abort_line; } else if(strstartswith(line, "MOUSEMOVE ")) { char *linep = line + 10; int row, col, len; while(linep[0] == ' ') linep++; sscanf(linep, "%d,%d%n", &row, &col, &len); linep += len; while(linep[0] == ' ') linep++; VTermModifier mod = strpe_modifiers(&linep); vterm_mouse_move(vt, row, col, mod); } else if(strstartswith(line, "MOUSEBTN ")) { char *linep = line + 9; char press; int button, len; while(linep[0] == ' ') linep++; sscanf(linep, "%c %d%n", &press, &button, &len); linep += len; while(linep[0] == ' ') linep++; VTermModifier mod = strpe_modifiers(&linep); vterm_mouse_button(vt, button, (press == 'd' || press == 'D'), mod); } else if(strstartswith(line, "DAMAGEMERGE ")) { char *linep = line + 12; while(linep[0] == ' ') linep++; if(streq(linep, "CELL")) vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_CELL); else if(streq(linep, "ROW")) vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_ROW); else if(streq(linep, "SCREEN")) vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCREEN); else if(streq(linep, "SCROLL")) vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCROLL); } else if(strstartswith(line, "DAMAGEFLUSH")) { vterm_screen_flush_damage(screen); } else if(line[0] == '?') { if(streq(line, "?cursor")) { VTermPos pos; vterm_state_get_cursorpos(state, &pos); if(pos.row != state_pos.row) printf("! row mismatch: state=%d,%d event=%d,%d\n", pos.row, pos.col, state_pos.row, state_pos.col); else if(pos.col != state_pos.col) printf("! col mismatch: state=%d,%d event=%d,%d\n", pos.row, pos.col, state_pos.row, state_pos.col); else printf("%d,%d\n", state_pos.row, state_pos.col); } else if(strstartswith(line, "?pen ")) { char *linep = line + 5; while(linep[0] == ' ') linep++; VTermValue val; #define BOOLSTR(v) ((v) ? "on" : "off") if(streq(linep, "bold")) { vterm_state_get_penattr(state, VTERM_ATTR_BOLD, &val); if(val.boolean != state_pen.bold) printf("! pen bold mismatch; state=%s, event=%s\n", BOOLSTR(val.boolean), BOOLSTR(state_pen.bold)); else printf("%s\n", BOOLSTR(state_pen.bold)); } else if(streq(linep, "underline")) { vterm_state_get_penattr(state, VTERM_ATTR_UNDERLINE, &val); if(val.boolean != state_pen.underline) printf("! pen underline mismatch; state=%d, event=%d\n", val.boolean, state_pen.underline); else printf("%d\n", state_pen.underline); } else if(streq(linep, "italic")) { vterm_state_get_penattr(state, VTERM_ATTR_ITALIC, &val); if(val.boolean != state_pen.italic) printf("! pen italic mismatch; state=%s, event=%s\n", BOOLSTR(val.boolean), BOOLSTR(state_pen.italic)); else printf("%s\n", BOOLSTR(state_pen.italic)); } else if(streq(linep, "blink")) { vterm_state_get_penattr(state, VTERM_ATTR_BLINK, &val); if(val.boolean != state_pen.blink) printf("! pen blink mismatch; state=%s, event=%s\n", BOOLSTR(val.boolean), BOOLSTR(state_pen.blink)); else printf("%s\n", BOOLSTR(state_pen.blink)); } else if(streq(linep, "reverse")) { vterm_state_get_penattr(state, VTERM_ATTR_REVERSE, &val); if(val.boolean != state_pen.reverse) printf("! pen reverse mismatch; state=%s, event=%s\n", BOOLSTR(val.boolean), BOOLSTR(state_pen.reverse)); else printf("%s\n", BOOLSTR(state_pen.reverse)); } else if(streq(linep, "font")) { vterm_state_get_penattr(state, VTERM_ATTR_FONT, &val); if(val.boolean != state_pen.font) printf("! pen font mismatch; state=%d, event=%d\n", val.boolean, state_pen.font); else printf("%d\n", state_pen.font); } else if(streq(linep, "foreground")) { print_color(&state_pen.foreground); printf("\n"); } else if(streq(linep, "background")) { print_color(&state_pen.background); printf("\n"); } else printf("?\n"); } else if(strstartswith(line, "?screen_chars ")) { char *linep = line + 13; VTermRect rect; size_t len; while(linep[0] == ' ') linep++; if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) { printf("! screen_chars unrecognised input\n"); goto abort_line; } len = vterm_screen_get_chars(screen, NULL, 0, rect); if(len == (size_t)-1) printf("! screen_chars error\n"); else if(len == 0) printf("\n"); else { uint32_t *chars = malloc(sizeof(uint32_t) * len); vterm_screen_get_chars(screen, chars, len, rect); for(size_t i = 0; i < len; i++) { printf("0x%02x%s", chars[i], i < len-1 ? "," : "\n"); } free(chars); } } else if(strstartswith(line, "?screen_text ")) { char *linep = line + 12; VTermRect rect; size_t len; while(linep[0] == ' ') linep++; if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) { printf("! screen_text unrecognised input\n"); goto abort_line; } len = vterm_screen_get_text(screen, NULL, 0, rect); if(len == (size_t)-1) printf("! screen_text error\n"); else if(len == 0) printf("\n"); else { /* Put an overwrite guard at both ends of the buffer */ unsigned char *buffer = malloc(len + 4); unsigned char *text = buffer + 2; text[-2] = 0x55; text[-1] = 0xAA; text[len] = 0x55; text[len+1] = 0xAA; vterm_screen_get_text(screen, (char *)text, len, rect); if(text[-2] != 0x55 || text[-1] != 0xAA) printf("! screen_get_text buffer overrun left [%02x,%02x]\n", text[-2], text[-1]); else if(text[len] != 0x55 || text[len+1] != 0xAA) printf("! screen_get_text buffer overrun right [%02x,%02x]\n", text[len], text[len+1]); else for(size_t i = 0; i < len; i++) { printf("0x%02x%s", text[i], i < len-1 ? "," : "\n"); } free(buffer); } } else if(strstartswith(line, "?screen_cell ")) { char *linep = line + 12; VTermPos pos; while(linep[0] == ' ') linep++; if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) { printf("! screen_cell unrecognised input\n"); goto abort_line; } VTermScreenCell cell; if(!vterm_screen_get_cell(screen, pos, &cell)) goto abort_line; printf("{"); for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell.chars[i]; i++) { printf("%s0x%x", i ? "," : "", cell.chars[i]); } printf("} width=%d attrs={", cell.width); if(cell.attrs.bold) printf("B"); if(cell.attrs.underline) printf("U%d", cell.attrs.underline); if(cell.attrs.italic) printf("I"); if(cell.attrs.blink) printf("K"); if(cell.attrs.reverse) printf("R"); if(cell.attrs.font) printf("F%d", cell.attrs.font); printf("} "); if(cell.attrs.dwl) printf("dwl "); if(cell.attrs.dhl) printf("dhl-%s ", cell.attrs.dhl == 2 ? "bottom" : "top"); printf("fg="); vterm_screen_convert_color_to_rgb(screen, &cell.fg); print_color(&cell.fg); printf(" bg="); vterm_screen_convert_color_to_rgb(screen, &cell.bg); print_color(&cell.bg); printf("\n"); } else if(strstartswith(line, "?screen_eol ")) { char *linep = line + 12; while(linep[0] == ' ') linep++; VTermPos pos; if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) { printf("! screen_eol unrecognised input\n"); goto abort_line; } printf("%d\n", vterm_screen_is_eol(screen, pos)); } else if(strstartswith(line, "?screen_attrs_extent ")) { char *linep = line + 21; while(linep[0] == ' ') linep++; VTermPos pos; if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) { printf("! screen_attrs_extent unrecognised input\n"); goto abort_line; } VTermRect rect = { .start_col = 0, .end_col = -1, }; if(!vterm_screen_get_attrs_extent(screen, &rect, pos, ~0)) { printf("! screen_attrs_extent failed\n"); goto abort_line; } printf("%d,%d-%d,%d\n", rect.start_row, rect.start_col, rect.end_row, rect.end_col); } else printf("?\n"); memset(line, 0, sizeof line); continue; } else abort_line: err = 1; size_t outlen = vterm_output_get_buffer_current(vt); if(outlen > 0) { char outbuff[outlen]; vterm_output_read(vt, outbuff, outlen); term_output(outbuff, outlen, NULL); } printf(err ? "?\n" : "DONE\n"); } vterm_free(vt); return 0; } libvterm-0.1.4/t/64screen_pen.test0000644000175000017500000000323213720230652015117 0ustar leoleoINIT WANTSCREEN RESET !Plain PUSH "A" ?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !Bold PUSH "\e[1mB" ?screen_cell 0,1 = {0x42} width=1 attrs={B} fg=rgb(240,240,240) bg=rgb(0,0,0) !Italic PUSH "\e[3mC" ?screen_cell 0,2 = {0x43} width=1 attrs={BI} fg=rgb(240,240,240) bg=rgb(0,0,0) !Underline PUSH "\e[4mD" ?screen_cell 0,3 = {0x44} width=1 attrs={BU1I} fg=rgb(240,240,240) bg=rgb(0,0,0) !Reset PUSH "\e[mE" ?screen_cell 0,4 = {0x45} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) !Font PUSH "\e[11mF\e[m" ?screen_cell 0,5 = {0x46} width=1 attrs={F1} fg=rgb(240,240,240) bg=rgb(0,0,0) !Foreground PUSH "\e[31mG\e[m" ?screen_cell 0,6 = {0x47} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0) !Background PUSH "\e[42mH\e[m" ?screen_cell 0,7 = {0x48} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,224,0) !EL sets reverse and colours to end of line PUSH "\e[H\e[7;33;44m\e[K" ?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224) ?screen_cell 0,79 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224) !DECSCNM xors reverse for entire screen PUSH "\e[?5h" ?screen_cell 0,0 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224) ?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224) ?screen_cell 1,0 = {} width=1 attrs={R} fg=rgb(240,240,240) bg=rgb(0,0,0) PUSH "\e[?5\$p" output "\e[?5;1\$y" PUSH "\e[?5l" ?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224) ?screen_cell 0,79 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224) ?screen_cell 1,0 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0) PUSH "\e[?5\$p" output "\e[?5;2\$y" libvterm-0.1.4/t/63screen_resize.test0000644000175000017500000000440613720230652015641 0ustar leoleoINIT WANTSTATE WANTSCREEN !Resize wider preserves cells RESET RESIZE 25,80 PUSH "AB\r\nCD" ?screen_chars 0,0,1,80 = 0x41,0x42 ?screen_chars 1,0,2,80 = 0x43,0x44 RESIZE 25,100 ?screen_chars 0,0,1,100 = 0x41,0x42 ?screen_chars 1,0,2,100 = 0x43,0x44 !Resize wider allows print in new area RESET RESIZE 25,80 PUSH "AB\e[79GCD" ?screen_chars 0,0,1,2 = 0x41,0x42 ?screen_chars 0,78,1,80 = 0x43,0x44 RESIZE 25,100 ?screen_chars 0,0,1,2 = 0x41,0x42 ?screen_chars 0,78,1,80 = 0x43,0x44 PUSH "E" ?screen_chars 0,78,1,81 = 0x43,0x44,0x45 !Resize shorter with blanks just truncates RESET RESIZE 25,80 PUSH "Top\e[10HLine 10" ?screen_chars 0,0,1,80 = 0x54,0x6f,0x70 ?screen_chars 9,0,10,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31,0x30 ?cursor = 9,7 RESIZE 20,80 ?screen_chars 0,0,1,80 = 0x54,0x6f,0x70 ?screen_chars 9,0,10,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31,0x30 ?cursor = 9,7 !Resize shorter with content must scroll RESET RESIZE 25,80 PUSH "Top\e[25HLine 25\e[15H" ?screen_chars 0,0,1,80 = 0x54,0x6f,0x70 ?screen_chars 24,0,25,80 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35 ?cursor = 14,0 WANTSCREEN b RESIZE 20,80 sb_pushline 80 = 54 6F 70 sb_pushline 80 = sb_pushline 80 = sb_pushline 80 = sb_pushline 80 = ?screen_chars 0,0,1,80 = ?screen_chars 19,0,20,80 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35 ?cursor = 9,0 !Resize shorter does not lose line with cursor # See also https://github.com/neovim/libvterm/commit/1b745d29d45623aa8d22a7b9288c7b0e331c7088 RESET WANTSCREEN -b RESIZE 25,80 WANTSCREEN b PUSH "\e[24HLine 24\r\nLine 25\r\n" sb_pushline 80 = ?screen_chars 23,0,24,10 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35 ?cursor = 24,0 RESIZE 24,80 sb_pushline 80 = ?screen_chars 22,0,23,10 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35 ?cursor = 23,0 !Resize taller attempts to pop scrollback RESET WANTSCREEN -b RESIZE 25,80 PUSH "Line 1\e[25HBottom\e[15H" ?screen_chars 0,0,1,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31 ?screen_chars 24,0,25,80 = 0x42,0x6f,0x74,0x74,0x6f,0x6d ?cursor = 14,0 WANTSCREEN b RESIZE 30,80 sb_popline 80 sb_popline 80 sb_popline 80 sb_popline 80 sb_popline 80 ?screen_chars 0,0,1,80 = 0x41,0x42,0x43,0x44,0x45 ?screen_chars 5,0,6,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31 ?screen_chars 29,0,30,80 = 0x42,0x6f,0x74,0x74,0x6f,0x6d ?cursor = 19,0 libvterm-0.1.4/t/90vttest_01-movement-4.test0000644000175000017500000000177613720230652016632 0ustar leoleo# Test of leading zeroes in ESC sequences INIT WANTSCREEN RESET PUSH "\e[00000000004;000000001HT" PUSH "\e[00000000004;000000002Hh" PUSH "\e[00000000004;000000003Hi" PUSH "\e[00000000004;000000004Hs" PUSH "\e[00000000004;000000005H " PUSH "\e[00000000004;000000006Hi" PUSH "\e[00000000004;000000007Hs" PUSH "\e[00000000004;000000008H " PUSH "\e[00000000004;000000009Ha" PUSH "\e[00000000004;0000000010H " PUSH "\e[00000000004;0000000011Hc" PUSH "\e[00000000004;0000000012Ho" PUSH "\e[00000000004;0000000013Hr" PUSH "\e[00000000004;0000000014Hr" PUSH "\e[00000000004;0000000015He" PUSH "\e[00000000004;0000000016Hc" PUSH "\e[00000000004;0000000017Ht" PUSH "\e[00000000004;0000000018H " PUSH "\e[00000000004;0000000019Hs" PUSH "\e[00000000004;0000000020He" PUSH "\e[00000000004;0000000021Hn" PUSH "\e[00000000004;0000000022Ht" PUSH "\e[00000000004;0000000023He" PUSH "\e[00000000004;0000000024Hn" PUSH "\e[00000000004;0000000025Hc" PUSH "\e[00000000004;0000000026He" !Output ?screen_row 3 = "This is a correct sentence" libvterm-0.1.4/include/0002755000175000017500000000000013720230652013105 5ustar leoleolibvterm-0.1.4/include/vterm.h0000644000175000017500000004163213720230652014417 0ustar leoleo#ifndef __VTERM_H__ #define __VTERM_H__ #ifdef __cplusplus extern "C" { #endif #include #include #include #include "vterm_keycodes.h" #define VTERM_VERSION_MAJOR 0 #define VTERM_VERSION_MINOR 1 #define VTERM_CHECK_VERSION \ vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) typedef struct VTerm VTerm; typedef struct VTermState VTermState; typedef struct VTermScreen VTermScreen; typedef struct { int row; int col; } VTermPos; /* some small utility functions; we can just keep these static here */ /* order points by on-screen flow order */ static inline int vterm_pos_cmp(VTermPos a, VTermPos b) { return (a.row == b.row) ? a.col - b.col : a.row - b.row; } typedef struct { int start_row; int end_row; int start_col; int end_col; } VTermRect; /* true if the rect contains the point */ static inline int vterm_rect_contains(VTermRect r, VTermPos p) { return p.row >= r.start_row && p.row < r.end_row && p.col >= r.start_col && p.col < r.end_col; } /* move a rect */ static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) { rect->start_row += row_delta; rect->end_row += row_delta; rect->start_col += col_delta; rect->end_col += col_delta; } /** * Bit-field describing the content of the tagged union `VTermColor`. */ typedef enum { /** * If the lower bit of `type` is not set, the colour is 24-bit RGB. */ VTERM_COLOR_RGB = 0x00, /** * The colour is an index into a palette of 256 colours. */ VTERM_COLOR_INDEXED = 0x01, /** * Mask that can be used to extract the RGB/Indexed bit. */ VTERM_COLOR_TYPE_MASK = 0x01, /** * If set, indicates that this colour should be the default foreground * color, i.e. there was no SGR request for another colour. When * rendering this colour it is possible to ignore "idx" and just use a * colour that is not in the palette. */ VTERM_COLOR_DEFAULT_FG = 0x02, /** * If set, indicates that this colour should be the default background * color, i.e. there was no SGR request for another colour. A common * option when rendering this colour is to not render a background at * all, for example by rendering the window transparently at this spot. */ VTERM_COLOR_DEFAULT_BG = 0x04, /** * Mask that can be used to extract the default foreground/background bit. */ VTERM_COLOR_DEFAULT_MASK = 0x06 } VTermColorType; /** * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the * given VTermColor instance is an indexed colour. */ #define VTERM_COLOR_IS_INDEXED(col) \ (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) /** * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that * the given VTermColor instance is an rgb colour. */ #define VTERM_COLOR_IS_RGB(col) \ (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) /** * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating * that the given VTermColor instance corresponds to the default foreground * color. */ #define VTERM_COLOR_IS_DEFAULT_FG(col) \ (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) /** * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating * that the given VTermColor instance corresponds to the default background * color. */ #define VTERM_COLOR_IS_DEFAULT_BG(col) \ (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) /** * Tagged union storing either an RGB color or an index into a colour palette. * In order to convert indexed colours to RGB, you may use the * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() * functions which lookup the RGB colour from the palette maintained by a * VTermState or VTermScreen instance. */ typedef union { /** * Tag indicating which union member is actually valid. This variable * coincides with the `type` member of the `rgb` and the `indexed` struct * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether * a particular type flag is set. */ uint8_t type; /** * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. */ struct { /** * Same as the top-level `type` member stored in VTermColor. */ uint8_t type; /** * The actual 8-bit red, green, blue colour values. */ uint8_t red, green, blue; } rgb; /** * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into * the colour palette. */ struct { /** * Same as the top-level `type` member stored in VTermColor. */ uint8_t type; /** * Index into the colour map. */ uint8_t idx; } indexed; } VTermColor; /** * Constructs a new VTermColor instance representing the given RGB values. */ static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, uint8_t blue) { col->type = VTERM_COLOR_RGB; col->rgb.red = red; col->rgb.green = green; col->rgb.blue = blue; } /** * Construct a new VTermColor instance representing an indexed color with the * given index. */ static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) { col->type = VTERM_COLOR_INDEXED; col->indexed.idx = idx; } /** * Compares two colours. Returns true if the colors are equal, false otherwise. */ int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); typedef enum { /* VTERM_VALUETYPE_NONE = 0 */ VTERM_VALUETYPE_BOOL = 1, VTERM_VALUETYPE_INT, VTERM_VALUETYPE_STRING, VTERM_VALUETYPE_COLOR, VTERM_N_VALUETYPES } VTermValueType; typedef union { int boolean; int number; char *string; VTermColor color; } VTermValue; typedef enum { /* VTERM_ATTR_NONE = 0 */ VTERM_ATTR_BOLD = 1, // bool: 1, 22 VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 VTERM_ATTR_ITALIC, // bool: 3, 23 VTERM_ATTR_BLINK, // bool: 5, 25 VTERM_ATTR_REVERSE, // bool: 7, 27 VTERM_ATTR_STRIKE, // bool: 9, 29 VTERM_ATTR_FONT, // number: 10-19 VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 VTERM_N_ATTRS } VTermAttr; typedef enum { /* VTERM_PROP_NONE = 0 */ VTERM_PROP_CURSORVISIBLE = 1, // bool VTERM_PROP_CURSORBLINK, // bool VTERM_PROP_ALTSCREEN, // bool VTERM_PROP_TITLE, // string VTERM_PROP_ICONNAME, // string VTERM_PROP_REVERSE, // bool VTERM_PROP_CURSORSHAPE, // number VTERM_PROP_MOUSE, // number VTERM_N_PROPS } VTermProp; enum { VTERM_PROP_CURSORSHAPE_BLOCK = 1, VTERM_PROP_CURSORSHAPE_UNDERLINE, VTERM_PROP_CURSORSHAPE_BAR_LEFT, VTERM_N_PROP_CURSORSHAPES }; enum { VTERM_PROP_MOUSE_NONE = 0, VTERM_PROP_MOUSE_CLICK, VTERM_PROP_MOUSE_DRAG, VTERM_PROP_MOUSE_MOVE, VTERM_N_PROP_MOUSES }; typedef struct { const uint32_t *chars; int width; unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ } VTermGlyphInfo; typedef struct { unsigned int doublewidth:1; /* DECDWL or DECDHL line */ unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ } VTermLineInfo; typedef struct { /* libvterm relies on this memory to be zeroed out before it is returned * by the allocator. */ void *(*malloc)(size_t size, void *allocdata); void (*free)(void *ptr, void *allocdata); } VTermAllocatorFunctions; void vterm_check_version(int major, int minor); VTerm *vterm_new(int rows, int cols); VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); void vterm_free(VTerm* vt); void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); void vterm_set_size(VTerm *vt, int rows, int cols); int vterm_get_utf8(const VTerm *vt); void vterm_set_utf8(VTerm *vt, int is_utf8); size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); /* Setting output callback will override the buffer logic */ typedef void VTermOutputCallback(const char *s, size_t len, void *user); void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); /* These buffer functions only work if output callback is NOT set * These are deprecated and will be removed in a later version */ size_t vterm_output_get_buffer_size(const VTerm *vt); size_t vterm_output_get_buffer_current(const VTerm *vt); size_t vterm_output_get_buffer_remaining(const VTerm *vt); /* This too */ size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); void vterm_keyboard_start_paste(VTerm *vt); void vterm_keyboard_end_paste(VTerm *vt); void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); // ------------ // Parser layer // ------------ /* Flag to indicate non-final subparameters in a single CSI parameter. * Consider * CSI 1;2:3:4;5a * 1 4 and 5 are final. * 2 and 3 are non-final and will have this bit set * * Don't confuse this with the final byte of the CSI escape; 'a' in this case. */ #define CSI_ARG_FLAG_MORE (1U<<31) #define CSI_ARG_MASK (~(1U<<31)) #define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) #define CSI_ARG(a) ((a) & CSI_ARG_MASK) /* Can't use -1 to indicate a missing argument; use this instead */ #define CSI_ARG_MISSING ((1UL<<31)-1) #define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) #define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) #define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) typedef struct { int (*text)(const char *bytes, size_t len, void *user); int (*control)(unsigned char control, void *user); int (*escape)(const char *bytes, size_t len, void *user); int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); int (*osc)(const char *command, size_t cmdlen, void *user); int (*dcs)(const char *command, size_t cmdlen, void *user); int (*resize)(int rows, int cols, void *user); } VTermParserCallbacks; void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); void *vterm_parser_get_cbdata(VTerm *vt); // ----------- // State layer // ----------- typedef struct { int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); int (*moverect)(VTermRect dest, VTermRect src, void *user); int (*erase)(VTermRect rect, int selective, void *user); int (*initpen)(void *user); int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, VTermPos *delta, void *user); int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); } VTermStateCallbacks; VTermState *vterm_obtain_state(VTerm *vt); void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); void *vterm_state_get_cbdata(VTermState *state); // Only invokes control, csi, osc, dcs void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user); void *vterm_state_get_unrecognised_fbdata(VTermState *state); void vterm_state_reset(VTermState *state, int hard); void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); void vterm_state_focus_in(VTermState *state); void vterm_state_focus_out(VTermState *state); const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); /** * Makes sure that the given color `col` is indeed an RGB colour. After this * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other * flags stored in `col->type` will have been reset. * * @param state is the VTermState instance from which the colour palette should * be extracted. * @param col is a pointer at the VTermColor instance that should be converted * to an RGB colour. */ void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); // ------------ // Screen layer // ------------ typedef struct { unsigned int bold : 1; unsigned int underline : 2; unsigned int italic : 1; unsigned int blink : 1; unsigned int reverse : 1; unsigned int strike : 1; unsigned int font : 4; /* 0 to 9 */ unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ } VTermScreenCellAttrs; enum { VTERM_UNDERLINE_OFF, VTERM_UNDERLINE_SINGLE, VTERM_UNDERLINE_DOUBLE, VTERM_UNDERLINE_CURLY, }; typedef struct { #define VTERM_MAX_CHARS_PER_CELL 6 uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; char width; VTermScreenCellAttrs attrs; VTermColor fg, bg; } VTermScreenCell; typedef struct { int (*damage)(VTermRect rect, void *user); int (*moverect)(VTermRect dest, VTermRect src, void *user); int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, void *user); int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); } VTermScreenCallbacks; VTermScreen *vterm_obtain_screen(VTerm *vt); void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); void *vterm_screen_get_cbdata(VTermScreen *screen); // Only invokes control, csi, osc, dcs void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user); void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); typedef enum { VTERM_DAMAGE_CELL, /* every cell */ VTERM_DAMAGE_ROW, /* entire rows */ VTERM_DAMAGE_SCREEN, /* entire screen */ VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ VTERM_N_DAMAGES } VTermDamageSize; void vterm_screen_flush_damage(VTermScreen *screen); void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); void vterm_screen_reset(VTermScreen *screen, int hard); /* Neither of these functions NUL-terminate the buffer */ size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); typedef enum { VTERM_ATTR_BOLD_MASK = 1 << 0, VTERM_ATTR_UNDERLINE_MASK = 1 << 1, VTERM_ATTR_ITALIC_MASK = 1 << 2, VTERM_ATTR_BLINK_MASK = 1 << 3, VTERM_ATTR_REVERSE_MASK = 1 << 4, VTERM_ATTR_STRIKE_MASK = 1 << 5, VTERM_ATTR_FONT_MASK = 1 << 6, VTERM_ATTR_FOREGROUND_MASK = 1 << 7, VTERM_ATTR_BACKGROUND_MASK = 1 << 8, VTERM_ALL_ATTRS_MASK = (1 << 9) - 1 } VTermAttrMask; int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); /** * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` * instance. */ void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); // --------- // Utilities // --------- VTermValueType vterm_get_attr_type(VTermAttr attr); VTermValueType vterm_get_prop_type(VTermProp prop); void vterm_scroll_rect(VTermRect rect, int downward, int rightward, int (*moverect)(VTermRect src, VTermRect dest, void *user), int (*eraserect)(VTermRect rect, int selective, void *user), void *user); void vterm_copy_cells(VTermRect dest, VTermRect src, void (*copycell)(VTermPos dest, VTermPos src, void *user), void *user); #ifdef __cplusplus } #endif #endif libvterm-0.1.4/include/vterm_keycodes.h0000644000175000017500000000212213720230652016274 0ustar leoleo#ifndef __VTERM_INPUT_H__ #define __VTERM_INPUT_H__ typedef enum { VTERM_MOD_NONE = 0x00, VTERM_MOD_SHIFT = 0x01, VTERM_MOD_ALT = 0x02, VTERM_MOD_CTRL = 0x04, VTERM_ALL_MODS_MASK = 0x07 } VTermModifier; typedef enum { VTERM_KEY_NONE, VTERM_KEY_ENTER, VTERM_KEY_TAB, VTERM_KEY_BACKSPACE, VTERM_KEY_ESCAPE, VTERM_KEY_UP, VTERM_KEY_DOWN, VTERM_KEY_LEFT, VTERM_KEY_RIGHT, VTERM_KEY_INS, VTERM_KEY_DEL, VTERM_KEY_HOME, VTERM_KEY_END, VTERM_KEY_PAGEUP, VTERM_KEY_PAGEDOWN, VTERM_KEY_FUNCTION_0 = 256, VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, VTERM_KEY_KP_0, VTERM_KEY_KP_1, VTERM_KEY_KP_2, VTERM_KEY_KP_3, VTERM_KEY_KP_4, VTERM_KEY_KP_5, VTERM_KEY_KP_6, VTERM_KEY_KP_7, VTERM_KEY_KP_8, VTERM_KEY_KP_9, VTERM_KEY_KP_MULT, VTERM_KEY_KP_PLUS, VTERM_KEY_KP_COMMA, VTERM_KEY_KP_MINUS, VTERM_KEY_KP_PERIOD, VTERM_KEY_KP_DIVIDE, VTERM_KEY_KP_ENTER, VTERM_KEY_KP_EQUAL, VTERM_KEY_MAX, // Must be last VTERM_N_KEYS = VTERM_KEY_MAX } VTermKey; #define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) #endif libvterm-0.1.4/vterm.pc.in0000644000175000017500000000030713720230652013546 0ustar leoleoprefix=@PREFIX@ libdir=@LIBDIR@ includedir=${prefix}/include Name: vterm Description: Abstract VT220/Xterm/ECMA-48 emulation library Version: 0.1.4 Libs: -L${libdir} -lvterm Cflags: -I${includedir} libvterm-0.1.4/Makefile0000644000175000017500000000532613720230652013126 0ustar leoleoifeq ($(shell uname),Darwin) LIBTOOL ?= glibtool else LIBTOOL ?= libtool endif ifneq ($(VERBOSE),1) LIBTOOL +=--quiet endif override CFLAGS +=-Wall -Iinclude -std=c99 -Wpedantic ifeq ($(shell uname),SunOS) override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX endif ifeq ($(DEBUG),1) override CFLAGS +=-ggdb -DDEBUG endif ifeq ($(PROFILE),1) override CFLAGS +=-pg override LDFLAGS+=-pg endif CFILES=$(sort $(wildcard src/*.c)) HFILES=$(sort $(wildcard include/*.h)) OBJECTS=$(CFILES:.c=.lo) LIBRARY=libvterm.la BINFILES_SRC=$(sort $(wildcard bin/*.c)) BINFILES=$(BINFILES_SRC:.c=) TBLFILES=$(sort $(wildcard src/encoding/*.tbl)) INCFILES=$(TBLFILES:.tbl=.inc) HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES) VERSION_CURRENT=0 VERSION_REVISION=4 VERSION_AGE=0 VERSION=0.1.4 PREFIX=/usr/local BINDIR=$(PREFIX)/bin LIBDIR=$(PREFIX)/lib INCDIR=$(PREFIX)/include MANDIR=$(PREFIX)/share/man MAN3DIR=$(MANDIR)/man3 all: $(LIBRARY) $(BINFILES) $(LIBRARY): $(OBJECTS) @echo LINK $@ @$(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS) src/%.lo: src/%.c $(HFILES_INT) @echo CC $< @$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $< src/encoding/%.inc: src/encoding/%.tbl @echo TBL $< @perl -CSD tbl2inc_c.pl $< >$@ src/fullwidth.inc: @perl find-wide-chars.pl >$@ src/encoding.lo: $(INCFILES) bin/%: bin/%.c $(LIBRARY) @echo CC $< @$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS) t/harness.lo: t/harness.c $(HFILES) @echo CC $< @$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $< t/harness: t/harness.lo $(LIBRARY) @echo LINK $@ @$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) .PHONY: test test: $(LIBRARY) t/harness for T in `ls t/[0-9]*.test`; do echo "** $$T **"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done .PHONY: clean clean: $(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES) $(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness $(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES) .PHONY: install install: install-inc install-lib install-bin install-inc: install -d $(DESTDIR)$(INCDIR) install -m644 $(HFILES) $(DESTDIR)$(INCDIR) install -d $(DESTDIR)$(LIBDIR)/pkgconfig sed -e "s,@PREFIX@,$(PREFIX)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," $(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc install-lib: $(LIBRARY) install -d $(DESTDIR)$(LIBDIR) $(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY) $(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR) install-bin: $(BINFILES) install -d $(DESTDIR)$(BINDIR) $(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/