# HG changeset patch # User Bram Moolenaar # Date 1589916607 -7200 # Node ID 1d595fada80413c9d856f9171fab29a5bdf65844 # Parent f4ada29cdf4bc8c3f36c67a0ef4c893fe8d84356 patch 8.2.0798: libvterm code lags behind the upstream version Commit: https://github.com/vim/vim/commit/be593bf135f6967335b14ba188bd5f8f32175c75 Author: Bram Moolenaar Date: Tue May 19 21:20:04 2020 +0200 patch 8.2.0798: libvterm code lags behind the upstream version Problem: Libvterm code lags behind the upstream version. Solution: Include revisions 755 - 758. diff --git a/src/libvterm/include/vterm.h b/src/libvterm/include/vterm.h --- a/src/libvterm/include/vterm.h +++ b/src/libvterm/include/vterm.h @@ -107,10 +107,17 @@ typedef enum { VTERM_N_VALUETYPES } VTermValueType; +typedef struct { + const char *str; + size_t len : 30; + unsigned int initial : 1; + unsigned int final : 1; +} VTermStringFragment; + typedef union { int boolean; int number; - char *string; + VTermStringFragment string; VTermColor color; } VTermValue; @@ -257,8 +264,8 @@ typedef struct { 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 (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); int (*resize)(int rows, int cols, void *user); } VTermParserCallbacks; diff --git a/src/libvterm/src/parser.c b/src/libvterm/src/parser.c --- a/src/libvterm/src/parser.c +++ b/src/libvterm/src/parser.c @@ -23,10 +23,10 @@ static void do_csi(VTerm *vt, char comma { #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(" leader: %s\n", vt->parser.v.csi.leader); + for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi])); + if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi])) printf("\n"); printf(" intermed: %s\n", vt->parser.intermed); } @@ -34,9 +34,9 @@ static void do_csi(VTerm *vt, char comma 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.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, + vt->parser.v.csi.args, + vt->parser.v.csi.argi, vt->parser.intermedlen ? vt->parser.intermed : NULL, command, vt->parser.cbdata)) @@ -61,65 +61,36 @@ static void do_escape(VTerm *vt, char co DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", command); } -static void append_strbuffer(VTerm *vt, const char *str, size_t len) +static void string_fragment(VTerm *vt, const char *str, size_t len, int final) { - if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) { - len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur; - DEBUG_LOG1("Truncating strbuffer preserve to %zu bytes\n", len); + VTermStringFragment frag; + + frag.str = str; + frag.len = len; + frag.initial = vt->parser.string_initial; + frag.final = final; + + switch(vt->parser.state) { + case OSC: + if(vt->parser.callbacks && vt->parser.callbacks->osc) + (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata); + break; + + case DCS: + if(len && vt->parser.callbacks && vt->parser.callbacks->dcs) + (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata); + break; + + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + break; } - 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_LOG2("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_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str); - return; - - case VTERM_N_PARSER_TYPES: - return; - } + vt->parser.string_initial = FALSE; } size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) @@ -135,43 +106,45 @@ size_t vterm_input_write(VTerm *vt, cons case CSI_LEADER: case CSI_ARGS: case CSI_INTERMED: - case ESC: + case OSC_COMMAND: + case DCS_COMMAND: string_start = NULL; break; - case STRING: - case ESC_IN_STRING: + case OSC: + case DCS: string_start = bytes; break; } -#define ENTER_STRING_STATE() 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]; + int c1_allowed = !vt->mode.utf8; + size_t string_len; if(c == 0x00 || c == 0x7f) { // NUL, DEL - if(vt->parser.state >= STRING) { - more_string(vt, string_start, bytes + pos - string_start); + if(vt->parser.state >= OSC) { + string_fragment(vt, string_start, bytes + pos - string_start, FALSE); string_start = bytes + pos + 1; } continue; } if(c == 0x18 || c == 0x1a) { // CAN, SUB + vt->parser.in_esc = FALSE; 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); + if(vt->parser.state < OSC) + vt->parser.state = NORMAL; + vt->parser.in_esc = TRUE; continue; } else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state - vt->parser.state == STRING) { + vt->parser.state >= OSC) { // fallthrough } else if(c < 0x20) { // other C0 @@ -182,96 +155,72 @@ size_t vterm_input_write(VTerm *vt, cons if(pos + 2 < len && bytes[pos + 1] == 0x20 && bytes[pos + 2] == 0x08) vt->in_backspace = 2; // Trigger when count down to 1 } - if(vt->parser.state >= STRING) - more_string(vt, string_start, bytes + pos - string_start); + if(vt->parser.state >= OSC) + string_fragment(vt, string_start, bytes + pos - string_start, FALSE); do_control(vt, c); - if(vt->parser.state >= STRING) + if(vt->parser.state >= OSC) 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 + string_len = bytes + pos - string_start; - 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_LOG1("TODO: Unhandled byte %02x in Escape\n", c); - } + if(vt->parser.in_esc) { + // Hoist an ESC letter into a C1 if we're not in a string mode + // Always accept ESC \ == ST even in string mode + if(!vt->parser.intermedlen && + c >= 0x40 && c < 0x60 && + ((vt->parser.state < OSC || c == 0x5c))) { + c += 0x40; + c1_allowed = TRUE; + string_len -= 1; + vt->parser.in_esc = FALSE; } - break; + else { + string_start = NULL; + vt->parser.state = NORMAL; + } + } + switch(vt->parser.state) { 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; + if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1) + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c; break; } // else fallthrough - vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; - vt->parser.csi_argi = 0; - vt->parser.csi_args[0] = CSI_ARG_MISSING; + vt->parser.v.csi.argi = 0; + vt->parser.v.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'; + if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) + vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; + vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; + vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; break; } if(c == ':') { - vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; + vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; c = ';'; } if(c == ';') { - vt->parser.csi_argi++; - vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; + vt->parser.v.csi.argi++; + vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; break; } // else fallthrough - vt->parser.csi_argi++; + vt->parser.v.csi.argi++; vt->parser.intermedlen = 0; vt->parser.state = CSI_INTERMED; // fallthrough @@ -293,31 +242,77 @@ size_t vterm_input_write(VTerm *vt, cons 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(); + case OSC_COMMAND: + /* Numerical value of command */ + if(c >= '0' && c <= '9') { + if(vt->parser.v.osc.command == -1) + vt->parser.v.osc.command = 0; + else + vt->parser.v.osc.command *= 10; + vt->parser.v.osc.command += c - '0'; + break; + } + if(c == ';') { + vt->parser.state = OSC; + string_start = bytes + pos + 1; + break; } - else if (pos + 1 == len) { - // end of input but OSC string isn't finished yet, copy it to - // vt->parser.strbuffer to continue it later - more_string(vt, string_start, bytes + pos + 1 - string_start); + + /* else fallthrough */ + string_start = bytes + pos; + vt->parser.state = OSC; + goto string_state; + + case DCS_COMMAND: + if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) + vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c; + + if(c >= 0x40 && c<= 0x7e) { + string_start = bytes + pos + 1; + vt->parser.state = DCS; + } + break; + +string_state: + case OSC: + case DCS: + if(c == 0x07 || (c1_allowed && c == 0x9c)) { + string_fragment(vt, string_start, string_len, TRUE); + ENTER_NORMAL_STATE(); } break; case NORMAL: - if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { + if(vt->parser.in_esc) { + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } + else if(c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + vt->parser.in_esc = 0; + ENTER_NORMAL_STATE(); + } + else { + DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c); + } + break; + } + if(c1_allowed && c >= 0x80 && c < 0xa0) { switch(c) { case 0x90: // DCS - start_string(vt, VTERM_PARSER_DCS); - ENTER_STRING_STATE(); + vt->parser.string_initial = TRUE; + vt->parser.v.dcs.commandlen = 0; + ENTER_STATE(DCS_COMMAND); break; case 0x9b: // CSI + vt->parser.v.csi.leaderlen = 0; ENTER_STATE(CSI_LEADER); break; case 0x9d: // OSC - start_string(vt, VTERM_PARSER_OSC); - ENTER_STRING_STATE(); + vt->parser.v.osc.command = -1; + vt->parser.string_initial = TRUE; + ENTER_STATE(OSC_COMMAND); break; default: do_control(vt, c); @@ -341,6 +336,9 @@ size_t vterm_input_write(VTerm *vt, cons } } + if(string_start) + string_fragment(vt, string_start, bytes + pos - string_start, FALSE); + return len; } diff --git a/src/libvterm/src/screen.c b/src/libvterm/src/screen.c --- a/src/libvterm/src/screen.c +++ b/src/libvterm/src/screen.c @@ -533,7 +533,7 @@ static void resize_buffer(VTermScreen *s ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; int i; - for(i = 0; ; i++) { + for(i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { dst->chars[i] = src->chars[i]; if(!src->chars[i]) break; @@ -804,7 +804,7 @@ int vterm_screen_get_cell(const VTermScr if(!intcell) return 0; - for(i = 0; ; i++) { + for(i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { cell->chars[i] = intcell->chars[i]; if(!intcell->chars[i]) break; diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c --- a/src/libvterm/src/state.c +++ b/src/libvterm/src/state.c @@ -582,19 +582,12 @@ static int settermprop_int(VTermState *s return vterm_state_set_termprop(state, prop, &val); } -static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) +static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) { - char *strvalue; - int r; VTermValue val; - strvalue = vterm_allocator_malloc(state->vt, (len+1) * sizeof(char)); - strncpy(strvalue, str, len); - strvalue[len] = 0; - val.string = strvalue; - r = vterm_state_set_termprop(state, prop, &val); - vterm_allocator_free(state->vt, strvalue); - return r; + val.string = frag; + return vterm_state_set_termprop(state, prop, &val); } static void savecursor(VTermState *state, int save) @@ -1602,100 +1595,121 @@ static int on_csi(const char *leader, co return 1; } -static int on_osc(const char *command, size_t cmdlen, void *user) +static int on_osc(int command, VTermStringFragment frag, void *user) { VTermState *state = user; - if(cmdlen < 2) - return 0; + switch(command) { + case 0: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 1: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + return 1; + + case 2: + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; - 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; + case 10: + { + // request foreground color: ]10;?<0x07> + int red = state->default_fg.red; + int blue = state->default_fg.blue; + int green = state->default_fg.green; + vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue); + return 1; + } + + case 11: + { + // request background color: ]11;?<0x07> + int red = state->default_bg.red; + int blue = state->default_bg.blue; + int green = state->default_bg.green; + vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue); + return 1; + } + case 12: + settermprop_string(state, VTERM_PROP_CURSORCOLOR, frag); + return 1; + + default: + if(state->fallbacks && state->fallbacks->osc) + if((*state->fallbacks->osc)(command, frag, state->fbdata)) + return 1; } - else if(strneq(command, "10;", 3)) { - // request foreground color: ]10;?<0x07> - int red = state->default_fg.red; - int blue = state->default_fg.blue; - int green = state->default_fg.green; - vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue); - return 1; - } - else if(strneq(command, "11;", 3)) { - // request background color: ]11;?<0x07> - int red = state->default_bg.red; - int blue = state->default_bg.blue; - int green = state->default_bg.green; - vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue); - return 1; - } - else if(strneq(command, "12;", 3)) { - settermprop_string(state, VTERM_PROP_CURSORCOLOR, command + 3, cmdlen - 3); - 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) +static void request_status_string(VTermState *state, VTermStringFragment frag) { 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])); - int argi; - size_t cur = 0; + char *tmp = state->tmp.decrqss; + size_t i = 0; + + if(frag.initial) + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 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; + while(i < sizeof(state->tmp.decrqss)-1 && tmp[i]) + i++; + while(i < sizeof(state->tmp.decrqss)-1 && frag.len--) + tmp[i++] = (frag.str++)[0]; + tmp[i] = 0; + + if(!frag.final) + return; + + fprintf(stderr, "DECRQSS on <%s>\n", tmp); - for(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])); + switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) { + case 'm': { + // Query SGR + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); + size_t cur = 0; + int argi; - if(cur >= vt->tmpbuffer_len) - return; - } + 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; - 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; + for(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; + } - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); - } + 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; - 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; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + return; } - if(cmdlen == 2) { - if(strneq(command, " q", 2)) { + 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; + + case ' '|('q'<<8): { + // Query DECSCUSR int reply; switch(state->mode.cursor_shape) { case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; @@ -1707,27 +1721,29 @@ static void request_status_string(VTermS vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply); return; } - else if(strneq(command, "\"q", 2)) { + + case '\"'|('q'<<8): + // Query DECSCA 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); + vterm_push_output_sprintf_dcs(state->vt, "0$r%s", tmp); } -static int on_dcs(const char *command, size_t cmdlen, void *user) +static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) { VTermState *state = user; - if(cmdlen >= 2 && strneq(command, "$q", 2)) { - request_status_string(state, command+2, cmdlen-2); + if(commandlen == 2 && strneq(command, "$q", 2)) { + request_status_string(state, frag); return 1; } else if(state->fallbacks && state->fallbacks->dcs) - if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata)) + if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) return 1; + DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); return 0; } diff --git a/src/libvterm/src/vterm.c b/src/libvterm/src/vterm.c --- a/src/libvterm/src/vterm.c +++ b/src/libvterm/src/vterm.c @@ -55,35 +55,23 @@ VTerm *vterm_new_with_allocator(int rows vt->parser.callbacks = NULL; vt->parser.cbdata = NULL; - vt->parser.strbuffer_len = 500; // should be able to hold an OSC string - vt->parser.strbuffer_cur = 0; - vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len); - if (vt->parser.strbuffer == NULL) - { - vterm_allocator_free(vt, vt); - return NULL; - } - vt->outfunc = NULL; vt->outdata = NULL; vt->outbuffer_len = 200; vt->outbuffer_cur = 0; vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); - if (vt->outbuffer == NULL) - { - vterm_allocator_free(vt, vt->parser.strbuffer); - vterm_allocator_free(vt, vt); - return NULL; - } vt->tmpbuffer_len = 64; vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); - if (vt->tmpbuffer == NULL) + + if (vt->tmpbuffer == NULL + || vt->outbuffer == NULL + || vt->tmpbuffer == NULL) { - vterm_allocator_free(vt, vt->parser.strbuffer); + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); vterm_allocator_free(vt, vt); - vterm_allocator_free(vt, vt->outbuffer); return NULL; } @@ -98,7 +86,6 @@ void vterm_free(VTerm *vt) 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); diff --git a/src/libvterm/src/vterm_internal.h b/src/libvterm/src/vterm_internal.h --- a/src/libvterm/src/vterm_internal.h +++ b/src/libvterm/src/vterm_internal.h @@ -160,15 +160,13 @@ struct VTermState unsigned int cursor_shape:2; } mode; } saved; + + /* Temporary state for DECRQSS parsing */ + union { + char decrqss[4]; + } tmp; }; -typedef enum { - VTERM_PARSER_OSC, - VTERM_PARSER_DCS, - - VTERM_N_PARSER_TYPES -} VTermParserStringType; - struct VTerm { VTermAllocatorFunctions *allocator; @@ -188,28 +186,39 @@ struct VTerm CSI_LEADER, CSI_ARGS, CSI_INTERMED, - ESC, + OSC_COMMAND, + DCS_COMMAND, // below here are the "string states" - STRING, - ESC_IN_STRING, + OSC, + DCS, } state; + unsigned int in_esc : 1; + int intermedlen; char intermed[INTERMED_MAX]; - int csi_leaderlen; - char csi_leader[CSI_LEADER_MAX]; + union { + struct { + int leaderlen; + char leader[CSI_LEADER_MAX]; - int csi_argi; - long csi_args[CSI_ARGS_MAX]; + int argi; + long args[CSI_ARGS_MAX]; + } csi; + struct { + int command; + } osc; + struct { + int commandlen; + char command[CSI_LEADER_MAX]; + } dcs; + } v; const VTermParserCallbacks *callbacks; void *cbdata; - VTermParserStringType stringtype; - char *strbuffer; - size_t strbuffer_len; - size_t strbuffer_cur; + int string_initial; } parser; // len == malloc()ed size; cur == number of valid bytes diff --git a/src/libvterm/t/02parser.test b/src/libvterm/t/02parser.test --- a/src/libvterm/t/02parser.test +++ b/src/libvterm/t/02parser.test @@ -132,15 +132,23 @@ PUSH "\e[12\n;3X" !OSC BEL PUSH "\e]1;Hello\x07" - osc "1;Hello" + osc [1 "Hello"] !OSC ST (7bit) PUSH "\e]1;Hello\e\\" - osc "1;Hello" + osc [1 "Hello"] !OSC ST (8bit) PUSH "\x{9d}1;Hello\x9c" - osc "1;Hello" + osc [1 "Hello"] + +!OSC in parts +PUSH "\e]52;abc" + osc [52 "abc" +PUSH "def" + osc "def" +PUSH "ghi\e\\" + osc "ghi"] !Escape cancels OSC, starts Escape PUSH "\e]Something\e9" @@ -152,20 +160,21 @@ PUSH "\e]12\x{18}AB" !C0 in OSC interrupts and continues PUSH "\e]2;\nBye\x07" + osc [2 "" control 10 - osc "2;Bye" + osc "Bye"] !DCS BEL PUSH "\ePHello\x07" - dcs "Hello" + dcs ["Hello"] !DCS ST (7bit) PUSH "\ePHello\e\\" - dcs "Hello" + dcs ["Hello"] !DCS ST (8bit) PUSH "\x{90}Hello\x9c" - dcs "Hello" + dcs ["Hello"] !Escape cancels DCS, starts Escape PUSH "\ePSomething\e9" @@ -177,8 +186,9 @@ PUSH "\eP12\x{18}AB" !C0 in OSC interrupts and continues PUSH "\ePBy\ne\x07" + dcs ["By" control 10 - dcs "Bye" + dcs "e"] !NUL ignored PUSH "\x{00}" diff --git a/src/libvterm/t/18state_termprops.test b/src/libvterm/t/18state_termprops.test --- a/src/libvterm/t/18state_termprops.test +++ b/src/libvterm/t/18state_termprops.test @@ -33,4 +33,10 @@ PUSH "\e[3 q" !Title PUSH "\e]2;Here is my title\a" - settermprop 4 "Here is my title" + settermprop 4 ["Here is my title"] + +!Title split write +PUSH "\e]2;Here is" + settermprop 4 ["Here is" +PUSH " another title\a" + settermprop 4 " another title"] diff --git a/src/libvterm/t/29state_fallback.test b/src/libvterm/t/29state_fallback.test --- a/src/libvterm/t/29state_fallback.test +++ b/src/libvterm/t/29state_fallback.test @@ -12,8 +12,8 @@ PUSH "\e[?15;2z" !Unrecognised OSC PUSH "\e]27;Something\e\\" - osc "27;Something" + osc [27 "Something"] !Unrecognised DCS PUSH "\ePz123\e\\" - dcs "z123" + dcs ["z123"] diff --git a/src/libvterm/t/68screen_termprops.test b/src/libvterm/t/68screen_termprops.test --- a/src/libvterm/t/68screen_termprops.test +++ b/src/libvterm/t/68screen_termprops.test @@ -14,4 +14,4 @@ PUSH "\e[?25l" !Title PUSH "\e]2;Here is my title\a" - settermprop 4 "Here is my title" + settermprop 4 ["Here is my title"] diff --git a/src/libvterm/t/harness.c b/src/libvterm/t/harness.c --- a/src/libvterm/t/harness.c +++ b/src/libvterm/t/harness.c @@ -153,21 +153,44 @@ static int parser_csi(const char *leader return 1; } -static int parser_osc(const char *command, size_t cmdlen, void *user UNUSED) +static int parser_osc(int command, VTermStringFragment frag, void *user UNUSED) { printf("osc "); - printhex(command, cmdlen); + + if(frag.initial) { + if(command == -1) + printf("["); + else + printf("[%d;", command); + } + + printhex(frag.str, frag.len); + + if(frag.final) + printf("]"); + printf("\n"); return 1; } -static int parser_dcs(const char *command, size_t cmdlen, void *user UNUSED) +static int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user UNUSED) { + printf("dcs "); - printf("dcs "); - printhex(command, cmdlen); + if(frag.initial) { + size_t i; + printf("["); + for(i = 0; i < commandlen; i++) + printf("%02x", command[i]); + } + + printhex(frag.str, frag.len); + + if(frag.final) + printf("]"); + printf("\n"); return 1; @@ -239,7 +262,8 @@ static int settermprop(VTermProp prop, V printf("settermprop %d %d\n", prop, val->number); return 1; case VTERM_VALUETYPE_STRING: - printf("settermprop %d \"%s\"\n", prop, val->string); + printf("settermprop %d %s\"%.*s\"%s\n", prop, + val->string.initial ? "[" : "", val->string.len, val->string.str, val->string.final ? "]" : ""); return 1; case VTERM_VALUETYPE_COLOR: printf("settermprop %d rgb(%d,%d,%d)\n", prop, val->color.red, val->color.green, val->color.blue); @@ -262,7 +286,7 @@ static int state_putglyph(VTermGlyphInfo return 1; printf("putglyph "); - for(i = 0; info->chars[i]; i++) + for(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) diff --git a/src/libvterm/t/run-test.pl b/src/libvterm/t/run-test.pl --- a/src/libvterm/t/run-test.pl +++ b/src/libvterm/t/run-test.pl @@ -11,7 +11,8 @@ my $VALGRIND = 0; my $EXECUTABLE = "t/.libs/harness"; GetOptions( 'valgrind|v+' => \$VALGRIND, - 'executable|e=s' => \$EXECUTABLE + 'executable|e=s' => \$EXECUTABLE, + 'fail-early|F' => \(my $FAIL_EARLY), ) or exit 1; my ( $hin, $hout, $hpid ); @@ -65,6 +66,7 @@ sub do_onetest } $exitcode = 1 if $fail_printed; + exit $exitcode if $exitcode and $FAIL_EARLY; } sub do_line @@ -105,8 +107,15 @@ sub do_line 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/^(osc) (\[\d+)? *(.*?)(\]?)$/ ) { + my ( $cmd, $initial, $data, $final ) = ( $1, $2, $3, $4 ); + $initial //= ""; + $initial .= ";" if $initial =~ m/\d+/; + + $line = "$cmd $initial" . join( "", map sprintf("%02x", $_), unpack "C*", eval($data) ) . "$final"; + } + elsif( $line =~ m/^(escape|dcs) (\[?)(.*?)(\]?)$/ ) { + $line = "$1 $2" . join( "", map sprintf("%02x", $_), unpack "C*", eval($3) ) . "$4"; } elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) { $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2"; @@ -139,6 +148,7 @@ sub do_line "# Expected: $want\n" . "# Actual: $response\n"; $exitcode = 1; + exit $exitcode if $exitcode and $FAIL_EARLY; } } # Assertions start with '?' @@ -162,6 +172,7 @@ sub do_line "# Expected: $expectation\n" . "# Actual: $response\n"; $exitcode = 1; + exit $exitcode if $exitcode and $FAIL_EARLY; } } # Test controls start with '$' diff --git a/src/terminal.c b/src/terminal.c --- a/src/terminal.c +++ b/src/terminal.c @@ -2998,22 +2998,27 @@ handle_settermprop( void *user) { term_T *term = (term_T *)user; + char_u *strval = NULL; switch (prop) { case VTERM_PROP_TITLE: + strval = vim_strnsave((char_u *)value->string.str, + (int)value->string.len); + if (strval == NULL) + break; vim_free(term->tl_title); // a blank title isn't useful, make it empty, so that "running" is // displayed - if (*skipwhite((char_u *)value->string) == NUL) + if (*skipwhite(strval) == NUL) term->tl_title = NULL; // Same as blank else if (term->tl_arg0_cmd != NULL - && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string, + && STRNCMP(term->tl_arg0_cmd, strval, (int)STRLEN(term->tl_arg0_cmd)) == 0) term->tl_title = NULL; // Empty corrupted data of winpty - else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0) + else if (STRNCMP(" - ", strval, 4) == 0) term->tl_title = NULL; #ifdef MSWIN else if (!enc_utf8 && enc_codepage > 0) @@ -3022,8 +3027,8 @@ handle_settermprop( int length = 0; MultiByteToWideChar_alloc(CP_UTF8, 0, - (char*)value->string, (int)STRLEN(value->string), - &ret, &length); + (char*)value->string.str, + (int)value->string.len, &ret, &length); if (ret != NULL) { WideCharToMultiByte_alloc(enc_codepage, 0, @@ -3034,7 +3039,10 @@ handle_settermprop( } #endif else - term->tl_title = vim_strsave((char_u *)value->string); + { + term->tl_title = vim_strsave(strval); + strval = NULL; + } VIM_CLEAR(term->tl_status_text); if (term == curbuf->b_term) maketitle(); @@ -3057,7 +3065,11 @@ handle_settermprop( break; case VTERM_PROP_CURSORCOLOR: - cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string); + strval = vim_strnsave((char_u *)value->string.str, + (int)value->string.len); + if (strval == NULL) + break; + cursor_color_copy(&term->tl_cursor_color, strval); may_set_cursor_props(term); break; @@ -3069,6 +3081,8 @@ handle_settermprop( default: break; } + vim_free(strval); + // Always return 1, otherwise vterm doesn't store the value internally. return 1; } @@ -4181,7 +4195,7 @@ handle_call_command(term_T *term, channe * We recognize a terminal API command. */ static int -parse_osc(const char *command, size_t cmdlen, void *user) +parse_osc(int command, VTermStringFragment frag, void *user) { term_T *term = (term_T *)user; js_read_T reader; @@ -4190,10 +4204,10 @@ parse_osc(const char *command, size_t cm : term->tl_job->jv_channel; // We recognize only OSC 5 1 ; {command} - if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0) - return 0; // not handled - - reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3)); + if (command != 51) + return 0; + + reader.js_buf = vim_strnsave((char_u *)frag.str, (int)(frag.len)); if (reader.js_buf == NULL) return 1; reader.js_fill = NULL; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 798, +/**/ 797, /**/ 796,