neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

state.c (64961B)


      1 #include <assert.h>
      2 #include <stdio.h>
      3 #include <string.h>
      4 
      5 #include "nvim/grid.h"
      6 #include "nvim/mbyte.h"
      7 #include "nvim/vterm/encoding.h"
      8 #include "nvim/vterm/parser.h"
      9 #include "nvim/vterm/pen.h"
     10 #include "nvim/vterm/state.h"
     11 #include "nvim/vterm/vterm.h"
     12 #include "nvim/vterm/vterm_internal_defs.h"
     13 
     14 #include "vterm/state.c.generated.h"
     15 
     16 #define strneq(a, b, n) (strncmp(a, b, n) == 0)
     17 
     18 // Primary Device Attributes (DA1) response.
     19 // We make this a global (extern) variable so that we can override it with FFI
     20 // in tests.
     21 DLLEXPORT char vterm_primary_device_attr[] = "61;22;52";
     22 
     23 // Some convenient wrappers to make callback functions easier
     24 
     25 static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos)
     26 {
     27  VTermGlyphInfo info = {
     28    .schar = schar,
     29    .width = width,
     30    .protected_cell = state->protected_cell,
     31    .dwl = state->lineinfo[pos.row].doublewidth,
     32    .dhl = state->lineinfo[pos.row].doubleheight,
     33  };
     34 
     35  if (state->callbacks && state->callbacks->putglyph) {
     36    if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) {
     37      return;
     38    }
     39  }
     40 
     41  DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
     42 }
     43 
     44 static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
     45 {
     46  if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) {
     47    return;
     48  }
     49 
     50  if (cancel_phantom) {
     51    state->at_phantom = 0;
     52  }
     53 
     54  if (state->callbacks && state->callbacks->movecursor) {
     55    if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible,
     56                                        state->cbdata)) {
     57      return;
     58    }
     59  }
     60 }
     61 
     62 static void erase(VTermState *state, VTermRect rect, int selective)
     63 {
     64  if (rect.end_col == state->cols) {
     65    // If we're erasing the final cells of any lines, cancel the continuation marker on the
     66    // subsequent line
     67    for (int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) {
     68      state->lineinfo[row].continuation = 0;
     69    }
     70  }
     71 
     72  if (state->callbacks && state->callbacks->erase) {
     73    if ((*state->callbacks->erase)(rect, selective, state->cbdata)) {
     74      return;
     75    }
     76  }
     77 }
     78 
     79 static VTermState *vterm_state_new(VTerm *vt)
     80 {
     81  VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
     82 
     83  state->vt = vt;
     84 
     85  state->rows = vt->rows;
     86  state->cols = vt->cols;
     87 
     88  state->mouse_col = 0;
     89  state->mouse_row = 0;
     90  state->mouse_buttons = 0;
     91 
     92  state->mouse_protocol = MOUSE_X10;
     93 
     94  state->callbacks = NULL;
     95  state->cbdata = NULL;
     96 
     97  state->selection.callbacks = NULL;
     98  state->selection.user = NULL;
     99  state->selection.buffer = NULL;
    100 
    101  vterm_state_newpen(state);
    102 
    103  state->bold_is_highbright = 0;
    104 
    105  state->combine_pos.row = -1;
    106 
    107  state->tabstops = vterm_allocator_malloc(state->vt, ((size_t)state->cols + 7) / 8);
    108 
    109  state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt,
    110                                                            (size_t)state->rows *
    111                                                            sizeof(VTermLineInfo));
    112  // TODO(vterm): Make an 'enable' function
    113  state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt,
    114                                                              (size_t)state->rows *
    115                                                              sizeof(VTermLineInfo));
    116  state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];
    117 
    118  state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
    119  if (*state->encoding_utf8.enc->init) {
    120    (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
    121  }
    122 
    123  for (size_t i = 0; i < ARRAY_SIZE(state->key_encoding_stacks); i++) {
    124    struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[i];
    125    for (size_t j = 0; j < ARRAY_SIZE(stack->items); j++) {
    126      memset(&stack->items[j], 0, sizeof(stack->items[j]));
    127    }
    128 
    129    stack->size = 1;
    130  }
    131 
    132  return state;
    133 }
    134 
    135 void vterm_state_free(VTermState *state)
    136 {
    137  vterm_allocator_free(state->vt, state->tabstops);
    138  vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
    139  if (state->lineinfos[BUFIDX_ALTSCREEN]) {
    140    vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
    141  }
    142  vterm_allocator_free(state->vt, state);
    143 }
    144 
    145 static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
    146 {
    147  if (!downward && !rightward) {
    148    return;
    149  }
    150 
    151  int rows = rect.end_row - rect.start_row;
    152  if (downward > rows) {
    153    downward = rows;
    154  } else if (downward < -rows) {
    155    downward = -rows;
    156  }
    157 
    158  int cols = rect.end_col - rect.start_col;
    159  if (rightward > cols) {
    160    rightward = cols;
    161  } else if (rightward < -cols) {
    162    rightward = -cols;
    163  }
    164 
    165  // Update lineinfo if full line
    166  if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
    167    int height = rect.end_row - rect.start_row - abs(downward);
    168 
    169    if (downward > 0) {
    170      memmove(state->lineinfo + rect.start_row,
    171              state->lineinfo + rect.start_row + downward,
    172              (size_t)height * sizeof(state->lineinfo[0]));
    173      for (int row = rect.end_row - downward; row < rect.end_row; row++) {
    174        state->lineinfo[row] = (VTermLineInfo){ 0 };
    175      }
    176    } else {
    177      memmove(state->lineinfo + rect.start_row - downward,
    178              state->lineinfo + rect.start_row,
    179              (size_t)height * sizeof(state->lineinfo[0]));
    180      for (int row = rect.start_row; row < rect.start_row - downward; row++) {
    181        state->lineinfo[row] = (VTermLineInfo){ 0 };
    182      }
    183    }
    184  }
    185 
    186  if (state->callbacks && state->callbacks->scrollrect) {
    187    if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) {
    188      return;
    189    }
    190  }
    191 
    192  if (state->callbacks) {
    193    vterm_scroll_rect(rect, downward, rightward,
    194                      state->callbacks->moverect, state->callbacks->erase, state->cbdata);
    195  }
    196 }
    197 
    198 static void linefeed(VTermState *state)
    199 {
    200  if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
    201    VTermRect rect = {
    202      .start_row = state->scrollregion_top,
    203      .end_row = SCROLLREGION_BOTTOM(state),
    204      .start_col = SCROLLREGION_LEFT(state),
    205      .end_col = SCROLLREGION_RIGHT(state),
    206    };
    207 
    208    scroll(state, rect, 1, 0);
    209  } else if (state->pos.row < state->rows - 1) {
    210    state->pos.row++;
    211  }
    212 }
    213 
    214 static void set_col_tabstop(VTermState *state, int col)
    215 {
    216  uint8_t mask = (uint8_t)(1 << (col & 7));
    217  state->tabstops[col >> 3] |= mask;
    218 }
    219 
    220 static void clear_col_tabstop(VTermState *state, int col)
    221 {
    222  uint8_t mask = (uint8_t)(1 << (col & 7));
    223  state->tabstops[col >> 3] &= ~mask;
    224 }
    225 
    226 static int is_col_tabstop(VTermState *state, int col)
    227 {
    228  uint8_t mask = (uint8_t)(1 << (col & 7));
    229  return state->tabstops[col >> 3] & mask;
    230 }
    231 
    232 static int is_cursor_in_scrollregion(const VTermState *state)
    233 {
    234  if (state->pos.row < state->scrollregion_top
    235      || state->pos.row >= SCROLLREGION_BOTTOM(state)) {
    236    return 0;
    237  }
    238  if (state->pos.col < SCROLLREGION_LEFT(state)
    239      || state->pos.col >= SCROLLREGION_RIGHT(state)) {
    240    return 0;
    241  }
    242 
    243  return 1;
    244 }
    245 
    246 static void tab(VTermState *state, int count, int direction)
    247 {
    248  while (count > 0) {
    249    if (direction > 0) {
    250      if (state->pos.col >= THISROWWIDTH(state) - 1) {
    251        return;
    252      }
    253 
    254      state->pos.col++;
    255    } else if (direction < 0) {
    256      if (state->pos.col < 1) {
    257        return;
    258      }
    259 
    260      state->pos.col--;
    261    }
    262 
    263    if (is_col_tabstop(state, state->pos.col)) {
    264      count--;
    265    }
    266  }
    267 }
    268 
    269 #define NO_FORCE 0
    270 #define FORCE    1
    271 
    272 #define DWL_OFF 0
    273 #define DWL_ON  1
    274 
    275 #define DHL_OFF    0
    276 #define DHL_TOP    1
    277 #define DHL_BOTTOM 2
    278 
    279 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
    280 {
    281  VTermLineInfo info = state->lineinfo[row];
    282 
    283  if (dwl == DWL_OFF) {
    284    info.doublewidth = DWL_OFF;
    285  } else if (dwl == DWL_ON) {
    286    info.doublewidth = DWL_ON;
    287  }
    288  // else -1 to ignore
    289 
    290  if (dhl == DHL_OFF) {
    291    info.doubleheight = DHL_OFF;
    292  } else if (dhl == DHL_TOP) {
    293    info.doubleheight = DHL_TOP;
    294  } else if (dhl == DHL_BOTTOM) {
    295    info.doubleheight = DHL_BOTTOM;
    296  }
    297 
    298  if ((state->callbacks
    299       && state->callbacks->setlineinfo
    300       && (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
    301      || force) {
    302    state->lineinfo[row] = info;
    303  }
    304 }
    305 
    306 static int on_text(const char bytes[], size_t len, void *user)
    307 {
    308  VTermState *state = user;
    309 
    310  VTermPos oldpos = state->pos;
    311 
    312  uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
    313  size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);
    314 
    315  int npoints = 0;
    316  size_t eaten = 0;
    317 
    318  VTermEncodingInstance *encoding =
    319    state->gsingle_set ? &state->encoding[state->gsingle_set]
    320                       : !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set]
    321                                                : state->vt->mode.utf8 ? &state->encoding_utf8
    322                                                                       : &state->encoding[state->
    323                                                                                          gr_set];
    324  if (encoding->enc == state->encoding_utf8.enc) {
    325    encoding = &state->encoding_utf8;  // Only use one UTF-8 encoding state.
    326  }
    327 
    328  (*encoding->enc->decode)(encoding->enc, encoding->data,
    329                           codepoints, &npoints, state->gsingle_set ? 1 : (int)maxpoints,
    330                           bytes, &eaten, len);
    331 
    332  // There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet for even a single codepoint
    333  if (!npoints) {
    334    return (int)eaten;
    335  }
    336 
    337  if (state->gsingle_set && npoints) {
    338    state->gsingle_set = 0;
    339  }
    340 
    341  int i = 0;
    342  GraphemeState grapheme_state = GRAPHEME_STATE_INIT;
    343  size_t grapheme_len = 0;
    344  bool recombine = false;
    345 
    346  // See if the cursor has moved since
    347  if (state->pos.row == state->combine_pos.row
    348      && state->pos.col >= state->combine_pos.col
    349      && state->pos.col <= state->combine_pos.col + state->combine_width) {
    350    // This is a combining char. that needs to be merged with the previous glyph output
    351    if (utf_iscomposing((int)state->grapheme_last, (int)codepoints[i], &state->grapheme_state)) {
    352      // Find where we need to append these combining chars
    353      grapheme_len = state->grapheme_len;
    354      grapheme_state = state->grapheme_state;
    355      state->pos.col = state->combine_pos.col;
    356      state->at_phantom = 0;
    357      recombine = true;
    358    } else {
    359      DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
    360    }
    361  }
    362 
    363  while (i < npoints) {
    364    // Try to find combining characters following this
    365    do {
    366      if (grapheme_len < sizeof(state->grapheme_buf) - 4) {
    367        grapheme_len += (size_t)utf_char2bytes((int)codepoints[i],
    368                                               state->grapheme_buf + grapheme_len);
    369      }
    370      i++;
    371    } while (i < npoints && utf_iscomposing((int)codepoints[i - 1], (int)codepoints[i],
    372                                            &grapheme_state));
    373 
    374    int width = utf_ptr2cells_len(state->grapheme_buf, (int)grapheme_len);
    375 
    376    if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
    377      linefeed(state);
    378      state->pos.col = 0;
    379      state->at_phantom = 0;
    380      state->lineinfo[state->pos.row].continuation = 1;
    381    }
    382 
    383    if (state->mode.insert && !recombine) {
    384      // TODO(vterm): This will be a little inefficient for large bodies of text, as it'll have to
    385      // 'ICH' effectively before every glyph. We should scan ahead and ICH as many times as
    386      // required
    387      VTermRect rect = {
    388        .start_row = state->pos.row,
    389        .end_row = state->pos.row + 1,
    390        .start_col = state->pos.col,
    391        .end_col = THISROWWIDTH(state),
    392      };
    393      scroll(state, rect, 0, -1);
    394    }
    395 
    396    schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len);
    397    putglyph(state, sc, width, state->pos);
    398 
    399    if (i == npoints) {
    400      // End of the buffer. Save the chars in case we have to combine with more on the next call
    401      state->grapheme_len = grapheme_len;
    402      state->grapheme_last = codepoints[i - 1];
    403      state->grapheme_state = grapheme_state;
    404      state->combine_width = width;
    405      state->combine_pos = state->pos;
    406    } else {
    407      grapheme_len = 0;
    408      recombine = false;
    409    }
    410 
    411    if (state->pos.col + width >= THISROWWIDTH(state)) {
    412      if (state->mode.autowrap) {
    413        state->at_phantom = 1;
    414      }
    415    } else {
    416      state->pos.col += width;
    417    }
    418  }
    419 
    420  updatecursor(state, &oldpos, 0);
    421 
    422 #ifdef DEBUG
    423  if (state->pos.row < 0 || state->pos.row >= state->rows
    424      || state->pos.col < 0 || state->pos.col >= state->cols) {
    425    fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
    426            state->pos.row, state->pos.col);
    427    abort();
    428  }
    429 #endif
    430 
    431  return (int)eaten;
    432 }
    433 
    434 static int on_control(uint8_t control, void *user)
    435 {
    436  VTermState *state = user;
    437 
    438  VTermPos oldpos = state->pos;
    439 
    440  switch (control) {
    441  case 0x07:  // BEL - ECMA-48 8.3.3
    442    if (state->callbacks && state->callbacks->bell) {
    443      (*state->callbacks->bell)(state->cbdata);
    444    }
    445    break;
    446 
    447  case 0x08:  // BS - ECMA-48 8.3.5
    448    if (state->pos.col > 0) {
    449      state->pos.col--;
    450    }
    451    break;
    452 
    453  case 0x09:  // HT - ECMA-48 8.3.60
    454    tab(state, 1, +1);
    455    break;
    456 
    457  case 0x0a:  // LF - ECMA-48 8.3.74
    458  case 0x0b:  // VT
    459  case 0x0c:  // FF
    460    linefeed(state);
    461    if (state->mode.newline) {
    462      state->pos.col = 0;
    463    }
    464    break;
    465 
    466  case 0x0d:  // CR - ECMA-48 8.3.15
    467    state->pos.col = 0;
    468    break;
    469 
    470  case 0x0e:  // LS1 - ECMA-48 8.3.76
    471    state->gl_set = 1;
    472    break;
    473 
    474  case 0x0f:  // LS0 - ECMA-48 8.3.75
    475    state->gl_set = 0;
    476    break;
    477 
    478  case 0x84:  // IND - DEPRECATED but implemented for completeness
    479    linefeed(state);
    480    break;
    481 
    482  case 0x85:  // NEL - ECMA-48 8.3.86
    483    linefeed(state);
    484    state->pos.col = 0;
    485    break;
    486 
    487  case 0x88:  // HTS - ECMA-48 8.3.62
    488    set_col_tabstop(state, state->pos.col);
    489    break;
    490 
    491  case 0x8d:  // RI - ECMA-48 8.3.104
    492    if (state->pos.row == state->scrollregion_top) {
    493      VTermRect rect = {
    494        .start_row = state->scrollregion_top,
    495        .end_row = SCROLLREGION_BOTTOM(state),
    496        .start_col = SCROLLREGION_LEFT(state),
    497        .end_col = SCROLLREGION_RIGHT(state),
    498      };
    499 
    500      scroll(state, rect, -1, 0);
    501    } else if (state->pos.row > 0) {
    502      state->pos.row--;
    503    }
    504    break;
    505 
    506  case 0x8e:  // SS2 - ECMA-48 8.3.141
    507    state->gsingle_set = 2;
    508    break;
    509 
    510  case 0x8f:  // SS3 - ECMA-48 8.3.142
    511    state->gsingle_set = 3;
    512    break;
    513 
    514  default:
    515    if (state->fallbacks && state->fallbacks->control) {
    516      if ((*state->fallbacks->control)(control, state->fbdata)) {
    517        return 1;
    518      }
    519    }
    520 
    521    return 0;
    522  }
    523 
    524  updatecursor(state, &oldpos, 1);
    525 
    526 #ifdef DEBUG
    527  if (state->pos.row < 0 || state->pos.row >= state->rows
    528      || state->pos.col < 0 || state->pos.col >= state->cols) {
    529    fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
    530            control, state->pos.row, state->pos.col);
    531    abort();
    532  }
    533 #endif
    534 
    535  return 1;
    536 }
    537 
    538 static int settermprop_bool(VTermState *state, VTermProp prop, int v)
    539 {
    540  VTermValue val = { .boolean = v };
    541  return vterm_state_set_termprop(state, prop, &val);
    542 }
    543 
    544 static int settermprop_int(VTermState *state, VTermProp prop, int v)
    545 {
    546  VTermValue val = { .number = v };
    547  return vterm_state_set_termprop(state, prop, &val);
    548 }
    549 
    550 static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)
    551 {
    552  VTermValue val = { .string = frag };
    553  return vterm_state_set_termprop(state, prop, &val);
    554 }
    555 
    556 static void savecursor(VTermState *state, int save)
    557 {
    558  if (save) {
    559    state->saved.pos = state->pos;
    560    state->saved.mode.cursor_visible = state->mode.cursor_visible;
    561    state->saved.mode.cursor_blink = state->mode.cursor_blink;
    562    state->saved.mode.cursor_shape = state->mode.cursor_shape;
    563 
    564    vterm_state_savepen(state, 1);
    565  } else {
    566    VTermPos oldpos = state->pos;
    567 
    568    state->pos = state->saved.pos;
    569 
    570    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
    571    settermprop_bool(state, VTERM_PROP_CURSORBLINK,   state->saved.mode.cursor_blink);
    572    settermprop_int(state, VTERM_PROP_CURSORSHAPE,   state->saved.mode.cursor_shape);
    573 
    574    vterm_state_savepen(state, 0);
    575 
    576    updatecursor(state, &oldpos, 1);
    577  }
    578 }
    579 
    580 static int on_escape(const char *bytes, size_t len, void *user)
    581 {
    582  VTermState *state = user;
    583 
    584  // Easier to decode this from the first byte, even though the final byte terminates it
    585  switch (bytes[0]) {
    586  case ' ':
    587    if (len != 2) {
    588      return 0;
    589    }
    590 
    591    switch (bytes[1]) {
    592    case 'F':  // S7C1T
    593      state->vt->mode.ctrl8bit = 0;
    594      break;
    595 
    596    case 'G':  // S8C1T
    597      state->vt->mode.ctrl8bit = 1;
    598      break;
    599 
    600    default:
    601      return 0;
    602    }
    603    return 2;
    604 
    605  case '#':
    606    if (len != 2) {
    607      return 0;
    608    }
    609 
    610    switch (bytes[1]) {
    611    case '3':  // DECDHL top
    612      if (state->mode.leftrightmargin) {
    613        break;
    614      }
    615      set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
    616      break;
    617 
    618    case '4':  // DECDHL bottom
    619      if (state->mode.leftrightmargin) {
    620        break;
    621      }
    622      set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
    623      break;
    624 
    625    case '5':  // DECSWL
    626      if (state->mode.leftrightmargin) {
    627        break;
    628      }
    629      set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
    630      break;
    631 
    632    case '6':  // DECDWL
    633      if (state->mode.leftrightmargin) {
    634        break;
    635      }
    636      set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
    637      break;
    638 
    639    case '8':  // DECALN
    640    {
    641      VTermPos pos;
    642      schar_T E = schar_from_ascii('E');  // E
    643      for (pos.row = 0; pos.row < state->rows; pos.row++) {
    644        for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) {
    645          putglyph(state, E, 1, pos);
    646        }
    647      }
    648      break;
    649    }
    650 
    651    default:
    652      return 0;
    653    }
    654    return 2;
    655 
    656  case '(':
    657  case ')':
    658  case '*':
    659  case '+':  // SCS
    660    if (len != 2) {
    661      return 0;
    662    }
    663 
    664    {
    665      int setnum = bytes[0] - 0x28;
    666      VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
    667 
    668      if (newenc) {
    669        state->encoding[setnum].enc = newenc;
    670 
    671        if (newenc->init) {
    672          (*newenc->init)(newenc, state->encoding[setnum].data);
    673        }
    674      }
    675    }
    676 
    677    return 2;
    678 
    679  case '7':  // DECSC
    680    savecursor(state, 1);
    681    return 1;
    682 
    683  case '8':  // DECRC
    684    savecursor(state, 0);
    685    return 1;
    686 
    687  case '<':  // Ignored by VT100. Used in VT52 mode to switch up to VT100
    688    return 1;
    689 
    690  case '=':  // DECKPAM
    691    state->mode.keypad = 1;
    692    return 1;
    693 
    694  case '>':  // DECKPNM
    695    state->mode.keypad = 0;
    696    return 1;
    697 
    698  case 'c':  // RIS - ECMA-48 8.3.105
    699  {
    700    VTermPos oldpos = state->pos;
    701    vterm_state_reset(state, 1);
    702    if (state->callbacks && state->callbacks->movecursor) {
    703      (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible,
    704                                      state->cbdata);
    705    }
    706    return 1;
    707  }
    708 
    709  case 'n':  // LS2 - ECMA-48 8.3.78
    710    state->gl_set = 2;
    711    return 1;
    712 
    713  case 'o':  // LS3 - ECMA-48 8.3.80
    714    state->gl_set = 3;
    715    return 1;
    716 
    717  case '~':  // LS1R - ECMA-48 8.3.77
    718    state->gr_set = 1;
    719    return 1;
    720 
    721  case '}':  // LS2R - ECMA-48 8.3.79
    722    state->gr_set = 2;
    723    return 1;
    724 
    725  case '|':  // LS3R - ECMA-48 8.3.81
    726    state->gr_set = 3;
    727    return 1;
    728 
    729  default:
    730    return 0;
    731  }
    732 }
    733 
    734 static void set_mode(VTermState *state, int num, int val)
    735 {
    736  switch (num) {
    737  case 4:  // IRM - ECMA-48 7.2.10
    738    state->mode.insert = (unsigned)val;
    739    break;
    740 
    741  case 20:  // LNM - ANSI X3.4-1977
    742    state->mode.newline = (unsigned)val;
    743    break;
    744 
    745  default:
    746    DEBUG_LOG("libvterm: Unknown mode %d\n", num);
    747    return;
    748  }
    749 }
    750 
    751 static void set_dec_mode(VTermState *state, int num, int val)
    752 {
    753  switch (num) {
    754  case 1:
    755    state->mode.cursor = (unsigned)val;
    756    break;
    757 
    758  case 5:  // DECSCNM - screen mode
    759    settermprop_bool(state, VTERM_PROP_REVERSE, val);
    760    break;
    761 
    762  case 6:  // DECOM - origin mode
    763  {
    764    VTermPos oldpos = state->pos;
    765    state->mode.origin = (unsigned)val;
    766    state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
    767    state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
    768    updatecursor(state, &oldpos, 1);
    769  }
    770  break;
    771 
    772  case 7:
    773    state->mode.autowrap = (unsigned)val;
    774    break;
    775 
    776  case 12:
    777    settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
    778    break;
    779 
    780  case 25:
    781    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
    782    break;
    783 
    784  case 69:  // DECVSSM - vertical split screen mode
    785            // DECLRMM - left/right margin mode
    786    state->mode.leftrightmargin = (unsigned)val;
    787    if (val) {
    788      // Setting DECVSSM must clear doublewidth/doubleheight state of every line
    789      for (int row = 0; row < state->rows; row++) {
    790        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
    791      }
    792    }
    793 
    794    break;
    795 
    796  case 1000:
    797  case 1002:
    798  case 1003:
    799    settermprop_int(state, VTERM_PROP_MOUSE,
    800                    !val ? VTERM_PROP_MOUSE_NONE
    801                         : (num == 1000) ? VTERM_PROP_MOUSE_CLICK
    802                                         : (num == 1002) ? VTERM_PROP_MOUSE_DRAG
    803                                                         : VTERM_PROP_MOUSE_MOVE);
    804    break;
    805 
    806  case 1004:
    807    settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val);
    808    state->mode.report_focus = (unsigned)val;
    809    break;
    810 
    811  case 1005:
    812    state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
    813    break;
    814 
    815  case 1006:
    816    state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
    817    break;
    818 
    819  case 1015:
    820    state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
    821    break;
    822 
    823  case 1047:
    824    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
    825    break;
    826 
    827  case 1048:
    828    savecursor(state, val);
    829    break;
    830 
    831  case 1049:
    832    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
    833    savecursor(state, val);
    834    break;
    835 
    836  case 2004:
    837    state->mode.bracketpaste = (unsigned)val;
    838    break;
    839 
    840  case 2031:
    841    settermprop_bool(state, VTERM_PROP_THEMEUPDATES, val);
    842    break;
    843 
    844  default:
    845    DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num);
    846    return;
    847  }
    848 }
    849 
    850 static void request_dec_mode(VTermState *state, int num)
    851 {
    852  int reply;
    853 
    854  switch (num) {
    855  case 1:
    856    reply = state->mode.cursor;
    857    break;
    858 
    859  case 5:
    860    reply = state->mode.screen;
    861    break;
    862 
    863  case 6:
    864    reply = state->mode.origin;
    865    break;
    866 
    867  case 7:
    868    reply = state->mode.autowrap;
    869    break;
    870 
    871  case 12:
    872    reply = state->mode.cursor_blink;
    873    break;
    874 
    875  case 25:
    876    reply = state->mode.cursor_visible;
    877    break;
    878 
    879  case 69:
    880    reply = state->mode.leftrightmargin;
    881    break;
    882 
    883  case 1000:
    884    reply = state->mouse_flags == MOUSE_WANT_CLICK;
    885    break;
    886 
    887  case 1002:
    888    reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
    889    break;
    890 
    891  case 1003:
    892    reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
    893    break;
    894 
    895  case 1004:
    896    reply = state->mode.report_focus;
    897    break;
    898 
    899  case 1005:
    900    reply = state->mouse_protocol == MOUSE_UTF8;
    901    break;
    902 
    903  case 1006:
    904    reply = state->mouse_protocol == MOUSE_SGR;
    905    break;
    906 
    907  case 1015:
    908    reply = state->mouse_protocol == MOUSE_RXVT;
    909    break;
    910 
    911  case 1047:
    912    reply = state->mode.alt_screen;
    913    break;
    914 
    915  case 2004:
    916    reply = state->mode.bracketpaste;
    917    break;
    918 
    919  case 2031:
    920    reply = state->mode.theme_updates;
    921    break;
    922 
    923  default:
    924    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
    925    return;
    926  }
    927 
    928  vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
    929 }
    930 
    931 static void request_version_string(VTermState *state)
    932 {
    933  vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)",
    934                                VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
    935 }
    936 
    937 static void request_key_encoding_flags(VTermState *state)
    938 {
    939  int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
    940  struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
    941 
    942  int reply = 0;
    943 
    944  assert(stack->size > 0);
    945  VTermKeyEncodingFlags flags = stack->items[stack->size - 1];
    946 
    947  if (flags.disambiguate) {
    948    reply |= KEY_ENCODING_DISAMBIGUATE;
    949  }
    950 
    951  if (flags.report_events) {
    952    reply |= KEY_ENCODING_REPORT_EVENTS;
    953  }
    954 
    955  if (flags.report_alternate) {
    956    reply |= KEY_ENCODING_REPORT_ALTERNATE;
    957  }
    958 
    959  if (flags.report_all_keys) {
    960    reply |= KEY_ENCODING_REPORT_ALL_KEYS;
    961  }
    962 
    963  if (flags.report_associated) {
    964    reply |= KEY_ENCODING_REPORT_ASSOCIATED;
    965  }
    966 
    967  vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du", reply);
    968 }
    969 
    970 static void set_key_encoding_flags(VTermState *state, int arg, int mode)
    971 {
    972  // When mode is 3, bits set in arg reset the corresponding mode
    973  bool set = mode != 3;
    974 
    975  // When mode is 1, unset bits are reset
    976  bool reset_unset = mode == 1;
    977 
    978  struct VTermKeyEncodingFlags flags = { 0 };
    979  if (arg & KEY_ENCODING_DISAMBIGUATE) {
    980    flags.disambiguate = set;
    981  } else if (reset_unset) {
    982    flags.disambiguate = false;
    983  }
    984 
    985  if (arg & KEY_ENCODING_REPORT_EVENTS) {
    986    flags.report_events = set;
    987  } else if (reset_unset) {
    988    flags.report_events = false;
    989  }
    990 
    991  if (arg & KEY_ENCODING_REPORT_ALTERNATE) {
    992    flags.report_alternate = set;
    993  } else if (reset_unset) {
    994    flags.report_alternate = false;
    995  }
    996  if (arg & KEY_ENCODING_REPORT_ALL_KEYS) {
    997    flags.report_all_keys = set;
    998  } else if (reset_unset) {
    999    flags.report_all_keys = false;
   1000  }
   1001 
   1002  if (arg & KEY_ENCODING_REPORT_ASSOCIATED) {
   1003    flags.report_associated = set;
   1004  } else if (reset_unset) {
   1005    flags.report_associated = false;
   1006  }
   1007 
   1008  int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
   1009  struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
   1010  assert(stack->size > 0);
   1011  stack->items[stack->size - 1] = flags;
   1012 }
   1013 
   1014 static void push_key_encoding_flags(VTermState *state, int arg)
   1015 {
   1016  int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
   1017  struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
   1018  assert(stack->size <= ARRAY_SIZE(stack->items));
   1019 
   1020  if (stack->size == ARRAY_SIZE(stack->items)) {
   1021    // Evict oldest entry when stack is full
   1022    for (size_t i = 0; i < ARRAY_SIZE(stack->items) - 1; i++) {
   1023      stack->items[i] = stack->items[i + 1];
   1024    }
   1025  } else {
   1026    stack->size++;
   1027  }
   1028 
   1029  set_key_encoding_flags(state, arg, 1);
   1030 }
   1031 
   1032 static void pop_key_encoding_flags(VTermState *state, int arg)
   1033 {
   1034  int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
   1035  struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
   1036  if (arg >= stack->size) {
   1037    stack->size = 1;
   1038 
   1039    // If a pop request is received that empties the stack, all flags are reset.
   1040    memset(&stack->items[0], 0, sizeof(stack->items[0]));
   1041  } else if (arg > 0) {
   1042    stack->size -= arg;
   1043  }
   1044 }
   1045 
   1046 static int on_csi(const char *leader, const long args[], int argcount, const char *intermed,
   1047                  char command, void *user)
   1048 {
   1049  VTermState *state = user;
   1050  int leader_byte = 0;
   1051  int intermed_byte = 0;
   1052  int cancel_phantom = 1;
   1053 
   1054  if (leader && leader[0]) {
   1055    if (leader[1]) {  // longer than 1 char
   1056      return 0;
   1057    }
   1058 
   1059    switch (leader[0]) {
   1060    case '?':
   1061    case '>':
   1062    case '<':
   1063    case '=':
   1064      leader_byte = (int)leader[0];
   1065      break;
   1066    default:
   1067      return 0;
   1068    }
   1069  }
   1070 
   1071  if (intermed && intermed[0]) {
   1072    if (intermed[1]) {  // longer than 1 char
   1073      return 0;
   1074    }
   1075 
   1076    switch (intermed[0]) {
   1077    case ' ':
   1078    case '!':
   1079    case '"':
   1080    case '$':
   1081    case '\'':
   1082      intermed_byte = (int)intermed[0];
   1083      break;
   1084    default:
   1085      return 0;
   1086    }
   1087  }
   1088 
   1089  VTermPos oldpos = state->pos;
   1090 
   1091  // Some temporaries for later code
   1092  int count, val;
   1093  int row, col;
   1094  VTermRect rect;
   1095  int selective;
   1096 
   1097 #define LBOUND(v, min) if ((v) < (min))(v) = (min)
   1098 #define UBOUND(v, max) if ((v) > (max))(v) = (max)
   1099 
   1100 #define LEADER(l, b) ((l << 8) | b)
   1101 #define INTERMED(i, b) ((i << 16) | b)
   1102 
   1103  switch (intermed_byte << 16 | leader_byte << 8 | command) {
   1104  case 0x40:  // ICH - ECMA-48 8.3.64
   1105    count = CSI_ARG_COUNT(args[0]);
   1106 
   1107    if (!is_cursor_in_scrollregion(state)) {
   1108      break;
   1109    }
   1110 
   1111    rect.start_row = state->pos.row;
   1112    rect.end_row = state->pos.row + 1;
   1113    rect.start_col = state->pos.col;
   1114    if (state->mode.leftrightmargin) {
   1115      rect.end_col = SCROLLREGION_RIGHT(state);
   1116    } else {
   1117      rect.end_col = THISROWWIDTH(state);
   1118    }
   1119 
   1120    scroll(state, rect, 0, -count);
   1121 
   1122    break;
   1123 
   1124  case 0x41:  // CUU - ECMA-48 8.3.22
   1125    count = CSI_ARG_COUNT(args[0]);
   1126    state->pos.row -= count;
   1127    state->at_phantom = 0;
   1128    break;
   1129 
   1130  case 0x42:  // CUD - ECMA-48 8.3.19
   1131    count = CSI_ARG_COUNT(args[0]);
   1132    state->pos.row += count;
   1133    state->at_phantom = 0;
   1134    break;
   1135 
   1136  case 0x43:  // CUF - ECMA-48 8.3.20
   1137    count = CSI_ARG_COUNT(args[0]);
   1138    state->pos.col += count;
   1139    state->at_phantom = 0;
   1140    break;
   1141 
   1142  case 0x44:  // CUB - ECMA-48 8.3.18
   1143    count = CSI_ARG_COUNT(args[0]);
   1144    state->pos.col -= count;
   1145    state->at_phantom = 0;
   1146    break;
   1147 
   1148  case 0x45:  // CNL - ECMA-48 8.3.12
   1149    count = CSI_ARG_COUNT(args[0]);
   1150    state->pos.col = 0;
   1151    state->pos.row += count;
   1152    state->at_phantom = 0;
   1153    break;
   1154 
   1155  case 0x46:  // CPL - ECMA-48 8.3.13
   1156    count = CSI_ARG_COUNT(args[0]);
   1157    state->pos.col = 0;
   1158    state->pos.row -= count;
   1159    state->at_phantom = 0;
   1160    break;
   1161 
   1162  case 0x47:  // CHA - ECMA-48 8.3.9
   1163    val = CSI_ARG_OR(args[0], 1);
   1164    state->pos.col = val - 1;
   1165    state->at_phantom = 0;
   1166    break;
   1167 
   1168  case 0x48:  // CUP - ECMA-48 8.3.21
   1169    row = CSI_ARG_OR(args[0], 1);
   1170    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
   1171    // zero-based
   1172    state->pos.row = row - 1;
   1173    state->pos.col = col - 1;
   1174    if (state->mode.origin) {
   1175      state->pos.row += state->scrollregion_top;
   1176      state->pos.col += SCROLLREGION_LEFT(state);
   1177    }
   1178    state->at_phantom = 0;
   1179    break;
   1180 
   1181  case 0x49:  // CHT - ECMA-48 8.3.10
   1182    count = CSI_ARG_COUNT(args[0]);
   1183    tab(state, count, +1);
   1184    break;
   1185 
   1186  case 0x4a:  // ED - ECMA-48 8.3.39
   1187  case LEADER('?', 0x4a):  // DECSED - Selective Erase in Display
   1188    selective = (leader_byte == '?');
   1189    switch (CSI_ARG(args[0])) {
   1190    case CSI_ARG_MISSING:
   1191    case 0:
   1192      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
   1193      rect.start_col = state->pos.col; rect.end_col = state->cols;
   1194      if (rect.end_col > rect.start_col) {
   1195        erase(state, rect, selective);
   1196      }
   1197 
   1198      rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
   1199      rect.start_col = 0;
   1200      for (int row_ = rect.start_row; row_ < rect.end_row; row_++) {
   1201        set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF);
   1202      }
   1203      if (rect.end_row > rect.start_row) {
   1204        erase(state, rect, selective);
   1205      }
   1206      break;
   1207 
   1208    case 1:
   1209      rect.start_row = 0; rect.end_row = state->pos.row;
   1210      rect.start_col = 0; rect.end_col = state->cols;
   1211      for (int row_ = rect.start_row; row_ < rect.end_row; row_++) {
   1212        set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF);
   1213      }
   1214      if (rect.end_col > rect.start_col) {
   1215        erase(state, rect, selective);
   1216      }
   1217 
   1218      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
   1219      rect.end_col = state->pos.col + 1;
   1220      if (rect.end_row > rect.start_row) {
   1221        erase(state, rect, selective);
   1222      }
   1223      break;
   1224 
   1225    case 2:
   1226      rect.start_row = 0; rect.end_row = state->rows;
   1227      rect.start_col = 0; rect.end_col = state->cols;
   1228      for (int row_ = rect.start_row; row_ < rect.end_row; row_++) {
   1229        set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF);
   1230      }
   1231      erase(state, rect, selective);
   1232      break;
   1233 
   1234    case 3:
   1235      if (state->callbacks && state->callbacks->sb_clear) {
   1236        if ((*state->callbacks->sb_clear)(state->cbdata)) {
   1237          return 1;
   1238        }
   1239      }
   1240      break;
   1241    }
   1242    break;
   1243 
   1244  case 0x4b:  // EL - ECMA-48 8.3.41
   1245  case LEADER('?', 0x4b):  // DECSEL - Selective Erase in Line
   1246    selective = (leader_byte == '?');
   1247    rect.start_row = state->pos.row;
   1248    rect.end_row = state->pos.row + 1;
   1249 
   1250    switch (CSI_ARG(args[0])) {
   1251    case CSI_ARG_MISSING:
   1252    case 0:
   1253      rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
   1254    case 1:
   1255      rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
   1256    case 2:
   1257      rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
   1258    default:
   1259      return 0;
   1260    }
   1261 
   1262    if (rect.end_col > rect.start_col) {
   1263      erase(state, rect, selective);
   1264    }
   1265 
   1266    break;
   1267 
   1268  case 0x4c:  // IL - ECMA-48 8.3.67
   1269    count = CSI_ARG_COUNT(args[0]);
   1270 
   1271    if (!is_cursor_in_scrollregion(state)) {
   1272      break;
   1273    }
   1274 
   1275    rect.start_row = state->pos.row;
   1276    rect.end_row = SCROLLREGION_BOTTOM(state);
   1277    rect.start_col = SCROLLREGION_LEFT(state);
   1278    rect.end_col = SCROLLREGION_RIGHT(state);
   1279 
   1280    scroll(state, rect, -count, 0);
   1281 
   1282    break;
   1283 
   1284  case 0x4d:  // DL - ECMA-48 8.3.32
   1285    count = CSI_ARG_COUNT(args[0]);
   1286 
   1287    if (!is_cursor_in_scrollregion(state)) {
   1288      break;
   1289    }
   1290 
   1291    rect.start_row = state->pos.row;
   1292    rect.end_row = SCROLLREGION_BOTTOM(state);
   1293    rect.start_col = SCROLLREGION_LEFT(state);
   1294    rect.end_col = SCROLLREGION_RIGHT(state);
   1295 
   1296    scroll(state, rect, count, 0);
   1297 
   1298    break;
   1299 
   1300  case 0x50:  // DCH - ECMA-48 8.3.26
   1301    count = CSI_ARG_COUNT(args[0]);
   1302 
   1303    if (!is_cursor_in_scrollregion(state)) {
   1304      break;
   1305    }
   1306 
   1307    rect.start_row = state->pos.row;
   1308    rect.end_row = state->pos.row + 1;
   1309    rect.start_col = state->pos.col;
   1310    if (state->mode.leftrightmargin) {
   1311      rect.end_col = SCROLLREGION_RIGHT(state);
   1312    } else {
   1313      rect.end_col = THISROWWIDTH(state);
   1314    }
   1315 
   1316    scroll(state, rect, 0, count);
   1317 
   1318    break;
   1319 
   1320  case 0x53:  // SU - ECMA-48 8.3.147
   1321    count = CSI_ARG_COUNT(args[0]);
   1322 
   1323    rect.start_row = state->scrollregion_top;
   1324    rect.end_row = SCROLLREGION_BOTTOM(state);
   1325    rect.start_col = SCROLLREGION_LEFT(state);
   1326    rect.end_col = SCROLLREGION_RIGHT(state);
   1327 
   1328    scroll(state, rect, count, 0);
   1329 
   1330    break;
   1331 
   1332  case 0x54:  // SD - ECMA-48 8.3.113
   1333    count = CSI_ARG_COUNT(args[0]);
   1334 
   1335    rect.start_row = state->scrollregion_top;
   1336    rect.end_row = SCROLLREGION_BOTTOM(state);
   1337    rect.start_col = SCROLLREGION_LEFT(state);
   1338    rect.end_col = SCROLLREGION_RIGHT(state);
   1339 
   1340    scroll(state, rect, -count, 0);
   1341 
   1342    break;
   1343 
   1344  case 0x58:  // ECH - ECMA-48 8.3.38
   1345    count = CSI_ARG_COUNT(args[0]);
   1346 
   1347    rect.start_row = state->pos.row;
   1348    rect.end_row = state->pos.row + 1;
   1349    rect.start_col = state->pos.col;
   1350    rect.end_col = state->pos.col + count;
   1351    UBOUND(rect.end_col, THISROWWIDTH(state));
   1352 
   1353    erase(state, rect, 0);
   1354    break;
   1355 
   1356  case 0x5a:  // CBT - ECMA-48 8.3.7
   1357    count = CSI_ARG_COUNT(args[0]);
   1358    tab(state, count, -1);
   1359    break;
   1360 
   1361  case 0x60:  // HPA - ECMA-48 8.3.57
   1362    col = CSI_ARG_OR(args[0], 1);
   1363    state->pos.col = col - 1;
   1364    state->at_phantom = 0;
   1365    break;
   1366 
   1367  case 0x61:  // HPR - ECMA-48 8.3.59
   1368    count = CSI_ARG_COUNT(args[0]);
   1369    state->pos.col += count;
   1370    state->at_phantom = 0;
   1371    break;
   1372 
   1373  case 0x62: {  // REP - ECMA-48 8.3.103
   1374    const int row_width = THISROWWIDTH(state);
   1375    count = CSI_ARG_COUNT(args[0]);
   1376    col = state->pos.col + count;
   1377    UBOUND(col, row_width);
   1378    schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len);
   1379    while (state->pos.col < col) {
   1380      putglyph(state, sc, state->combine_width, state->pos);
   1381      state->pos.col += state->combine_width;
   1382    }
   1383    if (state->pos.col + state->combine_width >= row_width) {
   1384      if (state->mode.autowrap) {
   1385        state->at_phantom = 1;
   1386        cancel_phantom = 0;
   1387      }
   1388    }
   1389    break;
   1390  }
   1391 
   1392  case 0x63:  // DA - ECMA-48 8.3.24
   1393    val = CSI_ARG_OR(args[0], 0);
   1394    if (val == 0) {
   1395      // DEC VT100 response
   1396      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%sc", vterm_primary_device_attr);
   1397    }
   1398    break;
   1399 
   1400  case LEADER('>', 0x63):  // DEC secondary Device Attributes
   1401    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
   1402    break;
   1403 
   1404  case 0x64:  // VPA - ECMA-48 8.3.158
   1405    row = CSI_ARG_OR(args[0], 1);
   1406    state->pos.row = row - 1;
   1407    if (state->mode.origin) {
   1408      state->pos.row += state->scrollregion_top;
   1409    }
   1410    state->at_phantom = 0;
   1411    break;
   1412 
   1413  case 0x65:  // VPR - ECMA-48 8.3.160
   1414    count = CSI_ARG_COUNT(args[0]);
   1415    state->pos.row += count;
   1416    state->at_phantom = 0;
   1417    break;
   1418 
   1419  case 0x66:  // HVP - ECMA-48 8.3.63
   1420    row = CSI_ARG_OR(args[0], 1);
   1421    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
   1422    // zero-based
   1423    state->pos.row = row - 1;
   1424    state->pos.col = col - 1;
   1425    if (state->mode.origin) {
   1426      state->pos.row += state->scrollregion_top;
   1427      state->pos.col += SCROLLREGION_LEFT(state);
   1428    }
   1429    state->at_phantom = 0;
   1430    break;
   1431 
   1432  case 0x67:  // TBC - ECMA-48 8.3.154
   1433    val = CSI_ARG_OR(args[0], 0);
   1434 
   1435    switch (val) {
   1436    case 0:
   1437      clear_col_tabstop(state, state->pos.col);
   1438      break;
   1439    case 3:
   1440    case 5:
   1441      for (col = 0; col < state->cols; col++) {
   1442        clear_col_tabstop(state, col);
   1443      }
   1444      break;
   1445    case 1:
   1446    case 2:
   1447    case 4:
   1448      break;
   1449    // TODO(vterm): 1, 2 and 4 aren't meaningful yet without line tab stops
   1450    default:
   1451      return 0;
   1452    }
   1453    break;
   1454 
   1455  case 0x68:  // SM - ECMA-48 8.3.125
   1456    if (!CSI_ARG_IS_MISSING(args[0])) {
   1457      set_mode(state, CSI_ARG(args[0]), 1);
   1458    }
   1459    break;
   1460 
   1461  case LEADER('?', 0x68):  // DEC private mode set
   1462    for (int i = 0; i < argcount; i++) {
   1463      if (!CSI_ARG_IS_MISSING(args[i])) {
   1464        set_dec_mode(state, CSI_ARG(args[i]), 1);
   1465      }
   1466    }
   1467    break;
   1468 
   1469  case 0x6a:  // HPB - ECMA-48 8.3.58
   1470    count = CSI_ARG_COUNT(args[0]);
   1471    state->pos.col -= count;
   1472    state->at_phantom = 0;
   1473    break;
   1474 
   1475  case 0x6b:  // VPB - ECMA-48 8.3.159
   1476    count = CSI_ARG_COUNT(args[0]);
   1477    state->pos.row -= count;
   1478    state->at_phantom = 0;
   1479    break;
   1480 
   1481  case 0x6c:  // RM - ECMA-48 8.3.106
   1482    if (!CSI_ARG_IS_MISSING(args[0])) {
   1483      set_mode(state, CSI_ARG(args[0]), 0);
   1484    }
   1485    break;
   1486 
   1487  case LEADER('?', 0x6c):  // DEC private mode reset
   1488    for (int i = 0; i < argcount; i++) {
   1489      if (!CSI_ARG_IS_MISSING(args[i])) {
   1490        set_dec_mode(state, CSI_ARG(args[i]), 0);
   1491      }
   1492    }
   1493    break;
   1494 
   1495  case 0x6d:  // SGR - ECMA-48 8.3.117
   1496    vterm_state_setpen(state, args, argcount);
   1497    break;
   1498 
   1499  case LEADER('?', 0x6d):  // DECSGR
   1500    // No actual DEC terminal recognised these, but some printers did. These are alternative ways to
   1501    // request subscript/superscript/off
   1502    for (int argi = 0; argi < argcount; argi++) {
   1503      long arg;
   1504      switch (arg = CSI_ARG(args[argi])) {
   1505      case 4:  // Superscript on
   1506        arg = 73;
   1507        vterm_state_setpen(state, &arg, 1);
   1508        break;
   1509      case 5:  // Subscript on
   1510        arg = 74;
   1511        vterm_state_setpen(state, &arg, 1);
   1512        break;
   1513      case 24:  // Super+subscript off
   1514        arg = 75;
   1515        vterm_state_setpen(state, &arg, 1);
   1516        break;
   1517      }
   1518    }
   1519    break;
   1520 
   1521  case 0x6e:  // DSR - ECMA-48 8.3.35
   1522  case LEADER('?', 0x6e):  // DECDSR
   1523    val = CSI_ARG_OR(args[0], 0);
   1524 
   1525    {
   1526      char *qmark = (leader_byte == '?') ? "?" : "";
   1527      bool dark = false;
   1528 
   1529      switch (val) {
   1530      case 0:
   1531      case 1:
   1532      case 2:
   1533      case 3:
   1534      case 4:
   1535        // ignore - these are replies
   1536        break;
   1537      case 5:
   1538        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
   1539        break;
   1540      case 6:  // CPR - cursor position report
   1541        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1,
   1542                                       state->pos.col + 1);
   1543        break;
   1544      case 996:
   1545        if (state->callbacks && state->callbacks->theme) {
   1546          if (state->callbacks->theme(&dark, state->cbdata)) {
   1547            vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?997;%cn", dark ? '1' : '2');
   1548          }
   1549        }
   1550        break;
   1551      }
   1552    }
   1553    break;
   1554 
   1555  case INTERMED('!', 0x70):  // DECSTR - DEC soft terminal reset
   1556    vterm_state_reset(state, 0);
   1557    break;
   1558 
   1559  case LEADER('?', INTERMED('$', 0x70)):
   1560    request_dec_mode(state, CSI_ARG(args[0]));
   1561    break;
   1562 
   1563  case LEADER('>', 0x71):  // XTVERSION - xterm query version string
   1564    request_version_string(state);
   1565    break;
   1566 
   1567  case INTERMED(' ', 0x71):  // DECSCUSR - DEC set cursor shape
   1568    val = CSI_ARG_OR(args[0], 1);
   1569 
   1570    switch (val) {
   1571    case 0:
   1572    case 1:
   1573      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
   1574      settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
   1575      break;
   1576    case 2:
   1577      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
   1578      settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
   1579      break;
   1580    case 3:
   1581      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
   1582      settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
   1583      break;
   1584    case 4:
   1585      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
   1586      settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
   1587      break;
   1588    case 5:
   1589      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
   1590      settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
   1591      break;
   1592    case 6:
   1593      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
   1594      settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
   1595      break;
   1596    }
   1597 
   1598    break;
   1599 
   1600  case INTERMED('"', 0x71):  // DECSCA - DEC select character protection attribute
   1601    val = CSI_ARG_OR(args[0], 0);
   1602 
   1603    switch (val) {
   1604    case 0:
   1605    case 2:
   1606      state->protected_cell = 0;
   1607      break;
   1608    case 1:
   1609      state->protected_cell = 1;
   1610      break;
   1611    }
   1612 
   1613    break;
   1614 
   1615  case 0x72:  // DECSTBM - DEC custom
   1616    state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
   1617    state->scrollregion_bottom = argcount < 2
   1618                                 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
   1619    LBOUND(state->scrollregion_top, 0);
   1620    UBOUND(state->scrollregion_top, state->rows);
   1621    LBOUND(state->scrollregion_bottom, -1);
   1622    if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) {
   1623      state->scrollregion_bottom = -1;
   1624    } else {
   1625      UBOUND(state->scrollregion_bottom, state->rows);
   1626    }
   1627 
   1628    if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
   1629      // Invalid
   1630      state->scrollregion_top = 0;
   1631      state->scrollregion_bottom = -1;
   1632    }
   1633 
   1634    // Setting the scrolling region restores the cursor to the home position
   1635    state->pos.row = 0;
   1636    state->pos.col = 0;
   1637    if (state->mode.origin) {
   1638      state->pos.row += state->scrollregion_top;
   1639      state->pos.col += SCROLLREGION_LEFT(state);
   1640    }
   1641 
   1642    break;
   1643 
   1644  case 0x73:  // DECSLRM - DEC custom
   1645    // Always allow setting these margins, just they won't take effect without DECVSSM
   1646    state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
   1647    state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
   1648    LBOUND(state->scrollregion_left, 0);
   1649    UBOUND(state->scrollregion_left, state->cols);
   1650    LBOUND(state->scrollregion_right, -1);
   1651    if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) {
   1652      state->scrollregion_right = -1;
   1653    } else {
   1654      UBOUND(state->scrollregion_right, state->cols);
   1655    }
   1656 
   1657    if (state->scrollregion_right > -1
   1658        && state->scrollregion_right <= state->scrollregion_left) {
   1659      // Invalid
   1660      state->scrollregion_left = 0;
   1661      state->scrollregion_right = -1;
   1662    }
   1663 
   1664    // Setting the scrolling region restores the cursor to the home position
   1665    state->pos.row = 0;
   1666    state->pos.col = 0;
   1667    if (state->mode.origin) {
   1668      state->pos.row += state->scrollregion_top;
   1669      state->pos.col += SCROLLREGION_LEFT(state);
   1670    }
   1671 
   1672    break;
   1673 
   1674  case LEADER('?', 0x75):  // Kitty query
   1675    request_key_encoding_flags(state);
   1676    break;
   1677 
   1678  case LEADER('>', 0x75):  // Kitty push flags
   1679    push_key_encoding_flags(state, CSI_ARG_OR(args[0], 0));
   1680    break;
   1681 
   1682  case LEADER('<', 0x75):  // Kitty pop flags
   1683    pop_key_encoding_flags(state, CSI_ARG_OR(args[0], 1));
   1684    break;
   1685 
   1686  case LEADER('=', 0x75):  // Kitty set flags
   1687    val = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
   1688    set_key_encoding_flags(state, CSI_ARG_OR(args[0], 0), val);
   1689    break;
   1690 
   1691  case INTERMED('\'', 0x7D):  // DECIC
   1692    count = CSI_ARG_COUNT(args[0]);
   1693 
   1694    if (!is_cursor_in_scrollregion(state)) {
   1695      break;
   1696    }
   1697 
   1698    rect.start_row = state->scrollregion_top;
   1699    rect.end_row = SCROLLREGION_BOTTOM(state);
   1700    rect.start_col = state->pos.col;
   1701    rect.end_col = SCROLLREGION_RIGHT(state);
   1702 
   1703    scroll(state, rect, 0, -count);
   1704 
   1705    break;
   1706 
   1707  case INTERMED('\'', 0x7E):  // DECDC
   1708    count = CSI_ARG_COUNT(args[0]);
   1709 
   1710    if (!is_cursor_in_scrollregion(state)) {
   1711      break;
   1712    }
   1713 
   1714    rect.start_row = state->scrollregion_top;
   1715    rect.end_row = SCROLLREGION_BOTTOM(state);
   1716    rect.start_col = state->pos.col;
   1717    rect.end_col = SCROLLREGION_RIGHT(state);
   1718 
   1719    scroll(state, rect, 0, count);
   1720 
   1721    break;
   1722 
   1723  default:
   1724    if (state->fallbacks && state->fallbacks->csi) {
   1725      if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) {
   1726        return 1;
   1727      }
   1728    }
   1729 
   1730    return 0;
   1731  }
   1732 
   1733  if (state->mode.origin) {
   1734    LBOUND(state->pos.row, state->scrollregion_top);
   1735    UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1);
   1736    LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
   1737    UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1);
   1738  } else {
   1739    LBOUND(state->pos.row, 0);
   1740    UBOUND(state->pos.row, state->rows - 1);
   1741    LBOUND(state->pos.col, 0);
   1742    UBOUND(state->pos.col, THISROWWIDTH(state) - 1);
   1743  }
   1744 
   1745  updatecursor(state, &oldpos, cancel_phantom);
   1746 
   1747 #ifdef DEBUG
   1748  if (state->pos.row < 0 || state->pos.row >= state->rows
   1749      || state->pos.col < 0 || state->pos.col >= state->cols) {
   1750    fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
   1751            command, state->pos.row, state->pos.col);
   1752    abort();
   1753  }
   1754 
   1755  if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
   1756    fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
   1757            command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
   1758    abort();
   1759  }
   1760 
   1761  if (SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
   1762    fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
   1763            command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
   1764    abort();
   1765  }
   1766 #endif
   1767 
   1768  return 1;
   1769 }
   1770 
   1771 static uint8_t unbase64one(char c)
   1772 {
   1773  if (c >= 'A' && c <= 'Z') {
   1774    return (uint8_t)c - 'A';
   1775  } else if (c >= 'a' && c <= 'z') {
   1776    return (uint8_t)c - 'a' + 26;
   1777  } else if (c >= '0' && c <= '9') {
   1778    return (uint8_t)c - '0' + 52;
   1779  } else if (c == '+') {
   1780    return 62;
   1781  } else if (c == '/') {
   1782    return 63;
   1783  }
   1784 
   1785  return 0xFF;
   1786 }
   1787 
   1788 static void osc_selection(VTermState *state, VTermStringFragment frag)
   1789 {
   1790  if (frag.initial) {
   1791    state->tmp.selection.mask = 0;
   1792    state->tmp.selection.state = SELECTION_INITIAL;
   1793  }
   1794 
   1795  while (!state->tmp.selection.state && frag.len) {
   1796    // Parse selection parameter
   1797    switch (frag.str[0]) {
   1798    case 'c':
   1799      state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
   1800      break;
   1801    case 'p':
   1802      state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
   1803      break;
   1804    case 'q':
   1805      state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
   1806      break;
   1807    case 's':
   1808      state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
   1809      break;
   1810    case '0':
   1811    case '1':
   1812    case '2':
   1813    case '3':
   1814    case '4':
   1815    case '5':
   1816    case '6':
   1817    case '7':
   1818      state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
   1819      break;
   1820 
   1821    case ';':
   1822      state->tmp.selection.state = SELECTION_SELECTED;
   1823      if (!state->tmp.selection.mask) {
   1824        state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
   1825      }
   1826      break;
   1827    }
   1828 
   1829    frag.str++;
   1830    frag.len--;
   1831  }
   1832 
   1833  if (!frag.len) {
   1834    // Clear selection if we're already finished but didn't do anything
   1835    if (frag.final && state->selection.callbacks->set) {
   1836      (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
   1837        .str = NULL,
   1838        .len = 0,
   1839        .initial = state->tmp.selection.state != SELECTION_SET,
   1840        .final = true,
   1841      }, state->selection.user);
   1842    }
   1843    return;
   1844  }
   1845 
   1846  if (state->tmp.selection.state == SELECTION_SELECTED) {
   1847    if (frag.str[0] == '?') {
   1848      state->tmp.selection.state = SELECTION_QUERY;
   1849    } else {
   1850      state->tmp.selection.state = SELECTION_SET_INITIAL;
   1851      state->tmp.selection.recvpartial = 0;
   1852    }
   1853  }
   1854 
   1855  if (state->tmp.selection.state == SELECTION_QUERY) {
   1856    if (state->selection.callbacks->query) {
   1857      (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
   1858    }
   1859    return;
   1860  }
   1861 
   1862  if (state->tmp.selection.state == SELECTION_INVALID) {
   1863    return;
   1864  }
   1865 
   1866  if (state->selection.callbacks->set) {
   1867    size_t bufcur = 0;
   1868    char *buffer = state->selection.buffer;
   1869 
   1870    uint32_t x = 0;  // Current decoding value
   1871    int n = 0;      // Number of sextets consumed
   1872 
   1873    if (state->tmp.selection.recvpartial) {
   1874      n = state->tmp.selection.recvpartial >> 24;
   1875      x = state->tmp.selection.recvpartial & 0x03FFFF;  // could be up to 18 bits of state in here
   1876 
   1877      state->tmp.selection.recvpartial = 0;
   1878    }
   1879 
   1880    while ((state->selection.buflen - bufcur) >= 3 && frag.len) {
   1881      if (frag.str[0] == '=') {
   1882        if (n == 2) {
   1883          buffer[0] = (char)(x >> 4 & 0xFF);
   1884          buffer += 1, bufcur += 1;
   1885        }
   1886        if (n == 3) {
   1887          buffer[0] = (char)(x >> 10 & 0xFF);
   1888          buffer[1] = (char)(x >>  2 & 0xFF);
   1889          buffer += 2, bufcur += 2;
   1890        }
   1891 
   1892        while (frag.len && frag.str[0] == '=') {
   1893          frag.str++, frag.len--;
   1894        }
   1895 
   1896        n = 0;
   1897      } else {
   1898        uint8_t b = unbase64one(frag.str[0]);
   1899        if (b == 0xFF) {
   1900          DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
   1901 
   1902          state->tmp.selection.state = SELECTION_INVALID;
   1903          if (state->selection.callbacks->set) {
   1904            (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
   1905              .str = NULL,
   1906              .len = 0,
   1907              .initial = true,
   1908              .final = true,
   1909            }, state->selection.user);
   1910          }
   1911          break;
   1912        }
   1913 
   1914        x = (x << 6) | b;
   1915        n++;
   1916        frag.str++, frag.len--;
   1917 
   1918        if (n == 4) {
   1919          buffer[0] = (char)(x >> 16 & 0xFF);
   1920          buffer[1] = (char)(x >>  8 & 0xFF);
   1921          buffer[2] = (char)(x >>  0 & 0xFF);
   1922 
   1923          buffer += 3, bufcur += 3;
   1924          x = 0;
   1925          n = 0;
   1926        }
   1927      }
   1928 
   1929      if (!frag.len || (state->selection.buflen - bufcur) < 3) {
   1930        if (bufcur) {
   1931          (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
   1932            .str = state->selection.buffer,
   1933            .len = bufcur,
   1934            .initial = state->tmp.selection.state == SELECTION_SET_INITIAL,
   1935            .final = frag.final && !frag.len,
   1936          }, state->selection.user);
   1937          state->tmp.selection.state = SELECTION_SET;
   1938        }
   1939 
   1940        buffer = state->selection.buffer;
   1941        bufcur = 0;
   1942      }
   1943    }
   1944 
   1945    if (n) {
   1946      state->tmp.selection.recvpartial = (uint32_t)(n << 24) | x;
   1947    }
   1948  }
   1949 }
   1950 
   1951 static int on_osc(int command, VTermStringFragment frag, void *user)
   1952 {
   1953  VTermState *state = user;
   1954 
   1955  switch (command) {
   1956  case 0:
   1957    settermprop_string(state, VTERM_PROP_ICONNAME, frag);
   1958    settermprop_string(state, VTERM_PROP_TITLE, frag);
   1959    break;
   1960 
   1961  case 1:
   1962    settermprop_string(state, VTERM_PROP_ICONNAME, frag);
   1963    break;
   1964 
   1965  case 2:
   1966    settermprop_string(state, VTERM_PROP_TITLE, frag);
   1967    break;
   1968 
   1969  case 52:
   1970    if (state->selection.callbacks) {
   1971      osc_selection(state, frag);
   1972    }
   1973    break;
   1974  }
   1975 
   1976  if (state->fallbacks && state->fallbacks->osc) {
   1977    if ((*state->fallbacks->osc)(command, frag, state->fbdata)) {
   1978      return 1;
   1979    }
   1980  }
   1981 
   1982  return 0;
   1983 }
   1984 
   1985 static void request_status_string(VTermState *state, VTermStringFragment frag)
   1986 {
   1987  VTerm *vt = state->vt;
   1988 
   1989  char *tmp = state->tmp.decrqss;
   1990 
   1991  if (frag.initial) {
   1992    tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
   1993  }
   1994 
   1995  size_t i = 0;
   1996  while (i < sizeof(state->tmp.decrqss) - 1 && tmp[i]) {
   1997    i++;
   1998  }
   1999  while (i < sizeof(state->tmp.decrqss) - 1 && frag.len--) {
   2000    tmp[i++] = (frag.str++)[0];
   2001  }
   2002  tmp[i] = 0;
   2003 
   2004  if (!frag.final) {
   2005    return;
   2006  }
   2007 
   2008  switch (tmp[0] | tmp[1] << 8 | tmp[2] << 16) {
   2009  case 'm': {
   2010    // Query SGR
   2011    long args[20];
   2012    int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
   2013    size_t cur = 0;
   2014 
   2015    cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
   2016                            vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r");  // DCS 1$r ...
   2017    if (cur >= vt->tmpbuffer_len) {
   2018      return;
   2019    }
   2020 
   2021    for (int argi = 0; argi < argc; argi++) {
   2022      cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
   2023                              argi == argc - 1 ? "%ld"
   2024                                               : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:"
   2025                                                                              : "%ld;",
   2026                              CSI_ARG(args[argi]));
   2027      if (cur >= vt->tmpbuffer_len) {
   2028        return;
   2029      }
   2030    }
   2031 
   2032    cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
   2033                            vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\");  // ... m ST
   2034    if (cur >= vt->tmpbuffer_len) {
   2035      return;
   2036    }
   2037 
   2038    vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
   2039    return;
   2040  }
   2041 
   2042  case 'r':
   2043    // Query DECSTBM
   2044    vterm_push_output_sprintf_str(vt, C1_DCS, true,
   2045                                  "1$r%d;%dr", state->scrollregion_top + 1,
   2046                                  SCROLLREGION_BOTTOM(state));
   2047    return;
   2048 
   2049  case 's':
   2050    // Query DECSLRM
   2051    vterm_push_output_sprintf_str(vt, C1_DCS, true,
   2052                                  "1$r%d;%ds", SCROLLREGION_LEFT(state) + 1,
   2053                                  SCROLLREGION_RIGHT(state));
   2054    return;
   2055 
   2056  case ' '|('q' << 8): {
   2057    // Query DECSCUSR
   2058    int reply = 0;
   2059    switch (state->mode.cursor_shape) {
   2060    case VTERM_PROP_CURSORSHAPE_BLOCK:
   2061      reply = 2; break;
   2062    case VTERM_PROP_CURSORSHAPE_UNDERLINE:
   2063      reply = 4; break;
   2064    case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
   2065      reply = 6; break;
   2066    }
   2067    if (state->mode.cursor_blink) {
   2068      reply--;
   2069    }
   2070    vterm_push_output_sprintf_str(vt, C1_DCS, true,
   2071                                  "1$r%d q", reply);
   2072    return;
   2073  }
   2074 
   2075  case '\"'|('q' << 8):
   2076    // Query DECSCA
   2077    vterm_push_output_sprintf_str(vt, C1_DCS, true,
   2078                                  "1$r%d\"q", state->protected_cell ? 1 : 2);
   2079    return;
   2080  }
   2081 
   2082  vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r");
   2083 }
   2084 
   2085 static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
   2086 {
   2087  VTermState *state = user;
   2088 
   2089  if (commandlen == 2 && strneq(command, "$q", 2)) {
   2090    request_status_string(state, frag);
   2091    return 1;
   2092  } else if (state->fallbacks && state->fallbacks->dcs) {
   2093    if ((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) {
   2094      return 1;
   2095    }
   2096  }
   2097 
   2098  DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command);
   2099  return 0;
   2100 }
   2101 
   2102 static int on_apc(VTermStringFragment frag, void *user)
   2103 {
   2104  VTermState *state = user;
   2105 
   2106  if (state->fallbacks && state->fallbacks->apc) {
   2107    if ((*state->fallbacks->apc)(frag, state->fbdata)) {
   2108      return 1;
   2109    }
   2110  }
   2111 
   2112  // No DEBUG_LOG because all APCs are unhandled
   2113  return 0;
   2114 }
   2115 
   2116 static int on_pm(VTermStringFragment frag, void *user)
   2117 {
   2118  VTermState *state = user;
   2119 
   2120  if (state->fallbacks && state->fallbacks->pm) {
   2121    if ((*state->fallbacks->pm)(frag, state->fbdata)) {
   2122      return 1;
   2123    }
   2124  }
   2125 
   2126  // No DEBUG_LOG because all PMs are unhandled
   2127  return 0;
   2128 }
   2129 
   2130 static int on_sos(VTermStringFragment frag, void *user)
   2131 {
   2132  VTermState *state = user;
   2133 
   2134  if (state->fallbacks && state->fallbacks->sos) {
   2135    if ((*state->fallbacks->sos)(frag, state->fbdata)) {
   2136      return 1;
   2137    }
   2138  }
   2139 
   2140  // No DEBUG_LOG because all SOSs are unhandled
   2141  return 0;
   2142 }
   2143 
   2144 static int on_resize(int rows, int cols, void *user)
   2145 {
   2146  VTermState *state = user;
   2147  VTermPos oldpos = state->pos;
   2148 
   2149  if (cols != state->cols) {
   2150    uint8_t *newtabstops = vterm_allocator_malloc(state->vt, ((size_t)cols + 7) / 8);
   2151 
   2152    // TODO(vterm): This can all be done much more efficiently bytewise
   2153    int col;
   2154    for (col = 0; col < state->cols && col < cols; col++) {
   2155      uint8_t mask = (uint8_t)(1 << (col & 7));
   2156      if (state->tabstops[col >> 3] & mask) {
   2157        newtabstops[col >> 3] |= mask;
   2158      } else {
   2159        newtabstops[col >> 3] &= ~mask;
   2160      }
   2161    }
   2162 
   2163    for (; col < cols; col++) {
   2164      uint8_t mask = (uint8_t)(1 << (col & 7));
   2165      if (col % 8 == 0) {
   2166        newtabstops[col >> 3] |= mask;
   2167      } else {
   2168        newtabstops[col >> 3] &= ~mask;
   2169      }
   2170    }
   2171 
   2172    vterm_allocator_free(state->vt, state->tabstops);
   2173    state->tabstops = newtabstops;
   2174  }
   2175 
   2176  state->rows = rows;
   2177  state->cols = cols;
   2178 
   2179  if (state->scrollregion_bottom > -1) {
   2180    UBOUND(state->scrollregion_bottom, state->rows);
   2181  }
   2182  if (state->scrollregion_right > -1) {
   2183    UBOUND(state->scrollregion_right, state->cols);
   2184  }
   2185 
   2186  VTermStateFields fields = {
   2187    .pos = state->pos,
   2188    .lineinfos = {[0] = state->lineinfos[0], [1] = state->lineinfos[1] },
   2189  };
   2190 
   2191  if (state->callbacks && state->callbacks->resize) {
   2192    (*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
   2193    state->pos = fields.pos;
   2194 
   2195    state->lineinfos[0] = fields.lineinfos[0];
   2196    state->lineinfos[1] = fields.lineinfos[1];
   2197  } else {
   2198    if (rows != state->rows) {
   2199      for (int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
   2200        VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
   2201        if (!oldlineinfo) {
   2202          continue;
   2203        }
   2204 
   2205        VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt,
   2206                                                            (size_t)rows * sizeof(VTermLineInfo));
   2207 
   2208        int row;
   2209        for (row = 0; row < state->rows && row < rows; row++) {
   2210          newlineinfo[row] = oldlineinfo[row];
   2211        }
   2212 
   2213        for (; row < rows; row++) {
   2214          newlineinfo[row] = (VTermLineInfo){
   2215            .doublewidth = 0,
   2216          };
   2217        }
   2218 
   2219        vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
   2220        state->lineinfos[bufidx] = newlineinfo;
   2221      }
   2222    }
   2223  }
   2224 
   2225  state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
   2226 
   2227  if (state->at_phantom && state->pos.col < cols - 1) {
   2228    state->at_phantom = 0;
   2229    state->pos.col++;
   2230  }
   2231 
   2232  if (state->pos.row < 0) {
   2233    state->pos.row = 0;
   2234  }
   2235  if (state->pos.row >= rows) {
   2236    state->pos.row = rows - 1;
   2237  }
   2238  if (state->pos.col < 0) {
   2239    state->pos.col = 0;
   2240  }
   2241  if (state->pos.col >= cols) {
   2242    state->pos.col = cols - 1;
   2243  }
   2244 
   2245  updatecursor(state, &oldpos, 1);
   2246 
   2247  return 1;
   2248 }
   2249 
   2250 static const VTermParserCallbacks parser_callbacks = {
   2251  .text = on_text,
   2252  .control = on_control,
   2253  .escape = on_escape,
   2254  .csi = on_csi,
   2255  .osc = on_osc,
   2256  .dcs = on_dcs,
   2257  .apc = on_apc,
   2258  .pm = on_pm,
   2259  .sos = on_sos,
   2260  .resize = on_resize,
   2261 };
   2262 
   2263 VTermState *vterm_obtain_state(VTerm *vt)
   2264 {
   2265  if (vt->state) {
   2266    return vt->state;
   2267  }
   2268 
   2269  VTermState *state = vterm_state_new(vt);
   2270  vt->state = state;
   2271 
   2272  vterm_parser_set_callbacks(vt, &parser_callbacks, state);
   2273 
   2274  return state;
   2275 }
   2276 
   2277 void vterm_state_reset(VTermState *state, int hard)
   2278 {
   2279  state->scrollregion_top = 0;
   2280  state->scrollregion_bottom = -1;
   2281  state->scrollregion_left = 0;
   2282  state->scrollregion_right = -1;
   2283 
   2284  state->mode.keypad = 0;
   2285  state->mode.cursor = 0;
   2286  state->mode.autowrap = 1;
   2287  state->mode.insert = 0;
   2288  state->mode.newline = 0;
   2289  state->mode.alt_screen = 0;
   2290  state->mode.origin = 0;
   2291  state->mode.leftrightmargin = 0;
   2292  state->mode.bracketpaste = 0;
   2293  state->mode.report_focus = 0;
   2294 
   2295  state->mouse_flags = 0;
   2296 
   2297  state->vt->mode.ctrl8bit = 0;
   2298 
   2299  for (int col = 0; col < state->cols; col++) {
   2300    if (col % 8 == 0) {
   2301      set_col_tabstop(state, col);
   2302    } else {
   2303      clear_col_tabstop(state, col);
   2304    }
   2305  }
   2306 
   2307  for (int row = 0; row < state->rows; row++) {
   2308    set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
   2309  }
   2310 
   2311  if (state->callbacks && state->callbacks->initpen) {
   2312    (*state->callbacks->initpen)(state->cbdata);
   2313  }
   2314 
   2315  vterm_state_resetpen(state);
   2316 
   2317  VTermEncoding *default_enc = state->vt->mode.utf8
   2318                               ? vterm_lookup_encoding(ENC_UTF8,      'u')
   2319                               : vterm_lookup_encoding(ENC_SINGLE_94, 'B');
   2320 
   2321  for (int i = 0; i < 4; i++) {
   2322    state->encoding[i].enc = default_enc;
   2323    if (default_enc->init) {
   2324      (*default_enc->init)(default_enc, state->encoding[i].data);
   2325    }
   2326  }
   2327 
   2328  state->gl_set = 0;
   2329  state->gr_set = 1;
   2330  state->gsingle_set = 0;
   2331 
   2332  state->protected_cell = 0;
   2333 
   2334  // Initialise the props
   2335  settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
   2336  settermprop_bool(state, VTERM_PROP_CURSORBLINK,   1);
   2337  settermprop_int(state, VTERM_PROP_CURSORSHAPE,   VTERM_PROP_CURSORSHAPE_BLOCK);
   2338 
   2339  if (hard) {
   2340    state->pos.row = 0;
   2341    state->pos.col = 0;
   2342    state->at_phantom = 0;
   2343 
   2344    VTermRect rect = { 0, state->rows, 0, state->cols };
   2345    erase(state, rect, 0);
   2346  }
   2347 }
   2348 
   2349 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
   2350 {
   2351  if (callbacks) {
   2352    state->callbacks = callbacks;
   2353    state->cbdata = user;
   2354 
   2355    if (state->callbacks && state->callbacks->initpen) {
   2356      (*state->callbacks->initpen)(state->cbdata);
   2357    }
   2358  } else {
   2359    state->callbacks = NULL;
   2360    state->cbdata = NULL;
   2361  }
   2362 }
   2363 
   2364 void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks,
   2365                                            void *user)
   2366 {
   2367  if (fallbacks) {
   2368    state->fallbacks = fallbacks;
   2369    state->fbdata = user;
   2370  } else {
   2371    state->fallbacks = NULL;
   2372    state->fbdata = NULL;
   2373  }
   2374 }
   2375 
   2376 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
   2377 {
   2378  // Only store the new value of the property if usercode said it was happy. This is especially
   2379  // important for altscreen switching
   2380  if (state->callbacks && state->callbacks->settermprop) {
   2381    if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) {
   2382      return 0;
   2383    }
   2384  }
   2385 
   2386  switch (prop) {
   2387  case VTERM_PROP_TITLE:
   2388  case VTERM_PROP_ICONNAME:
   2389    // we don't store these, just transparently pass through
   2390    return 1;
   2391  case VTERM_PROP_CURSORVISIBLE:
   2392    state->mode.cursor_visible = (unsigned)val->boolean;
   2393    return 1;
   2394  case VTERM_PROP_CURSORBLINK:
   2395    state->mode.cursor_blink = (unsigned)val->boolean;
   2396    return 1;
   2397  case VTERM_PROP_CURSORSHAPE:
   2398    state->mode.cursor_shape = (unsigned)val->number;
   2399    return 1;
   2400  case VTERM_PROP_REVERSE:
   2401    state->mode.screen = (unsigned)val->boolean;
   2402    return 1;
   2403  case VTERM_PROP_ALTSCREEN:
   2404    state->mode.alt_screen = (unsigned)val->boolean;
   2405    state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
   2406    if (state->mode.alt_screen) {
   2407      VTermRect rect = {
   2408        .start_row = 0,
   2409        .start_col = 0,
   2410        .end_row = state->rows,
   2411        .end_col = state->cols,
   2412      };
   2413      erase(state, rect, 0);
   2414    }
   2415    return 1;
   2416  case VTERM_PROP_MOUSE:
   2417    state->mouse_flags = 0;
   2418    if (val->number) {
   2419      state->mouse_flags |= MOUSE_WANT_CLICK;
   2420    }
   2421    if (val->number == VTERM_PROP_MOUSE_DRAG) {
   2422      state->mouse_flags |= MOUSE_WANT_DRAG;
   2423    }
   2424    if (val->number == VTERM_PROP_MOUSE_MOVE) {
   2425      state->mouse_flags |= MOUSE_WANT_MOVE;
   2426    }
   2427    return 1;
   2428  case VTERM_PROP_FOCUSREPORT:
   2429    state->mode.report_focus = (unsigned)val->boolean;
   2430    return 1;
   2431  case VTERM_PROP_THEMEUPDATES:
   2432    state->mode.theme_updates = (unsigned)val->boolean;
   2433    return 1;
   2434 
   2435  case VTERM_N_PROPS:
   2436    return 0;
   2437  }
   2438 
   2439  return 0;
   2440 }
   2441 
   2442 void vterm_state_focus_in(VTermState *state)
   2443 {
   2444  if (state->mode.report_focus) {
   2445    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
   2446  }
   2447 }
   2448 
   2449 void vterm_state_focus_out(VTermState *state)
   2450 {
   2451  if (state->mode.report_focus) {
   2452    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
   2453  }
   2454 }
   2455 
   2456 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
   2457 {
   2458  return state->lineinfo + row;
   2459 }
   2460 
   2461 void vterm_state_set_selection_callbacks(VTermState *state,
   2462                                         const VTermSelectionCallbacks *callbacks, void *user,
   2463                                         char *buffer, size_t buflen)
   2464 {
   2465  if (buflen && !buffer) {
   2466    buffer = vterm_allocator_malloc(state->vt, buflen);
   2467  }
   2468 
   2469  state->selection.callbacks = callbacks;
   2470  state->selection.user = user;
   2471  state->selection.buffer = buffer;
   2472  state->selection.buflen = buflen;
   2473 }