1#include "vterm_internal.h"
2
3#include <stdio.h>
4#include <string.h>
5
6#define strneq(a,b,n) (strncmp(a,b,n)==0)
7
8#include "utf8.h"
9
10#ifdef DEBUG
11# define DEBUG_GLYPH_COMBINE
12#endif
13
14#define MOUSE_WANT_DRAG 0x01
15#define MOUSE_WANT_MOVE 0x02
16
17/* Some convenient wrappers to make callback functions easier */
18
19static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
20{
21  if(state->callbacks && state->callbacks->putglyph)
22    if((*state->callbacks->putglyph)(chars, width, pos, state->cbdata))
23      return;
24
25  fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
26}
27
28static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
29{
30  if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
31    return;
32
33  if(cancel_phantom)
34    state->at_phantom = 0;
35
36  if(state->callbacks && state->callbacks->movecursor)
37    if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
38      return;
39}
40
41static void erase(VTermState *state, VTermRect rect)
42{
43  if(state->callbacks && state->callbacks->erase)
44    if((*state->callbacks->erase)(rect, state->cbdata))
45      return;
46}
47
48static VTermState *vterm_state_new(VTerm *vt)
49{
50  VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
51
52  state->vt = vt;
53
54  state->rows = vt->rows;
55  state->cols = vt->cols;
56
57  // 90% grey so that pure white is brighter
58  state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240;
59  state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
60
61  state->bold_is_highbright = 0;
62
63  return state;
64}
65
66void vterm_state_free(VTermState *state)
67{
68  vterm_allocator_free(state->vt, state->combine_chars);
69  vterm_allocator_free(state->vt, state);
70}
71
72static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
73{
74  if(!downward && !rightward)
75    return;
76
77  if(state->callbacks && state->callbacks->scrollrect)
78    if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
79      return;
80
81  if(state->callbacks)
82    vterm_scroll_rect(rect, downward, rightward,
83        state->callbacks->moverect, state->callbacks->erase, state->cbdata);
84}
85
86static void linefeed(VTermState *state)
87{
88  if(state->pos.row == SCROLLREGION_END(state) - 1) {
89    VTermRect rect = {
90      .start_row = state->scrollregion_start,
91      .end_row   = SCROLLREGION_END(state),
92      .start_col = 0,
93      .end_col   = state->cols,
94    };
95
96    scroll(state, rect, 1, 0);
97  }
98  else if(state->pos.row < state->rows-1)
99    state->pos.row++;
100}
101
102static void grow_combine_buffer(VTermState *state)
103{
104  size_t    new_size = state->combine_chars_size * 2;
105  uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
106
107  memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
108
109  vterm_allocator_free(state->vt, state->combine_chars);
110  state->combine_chars = new_chars;
111}
112
113static void set_col_tabstop(VTermState *state, int col)
114{
115  unsigned char mask = 1 << (col & 7);
116  state->tabstops[col >> 3] |= mask;
117}
118
119static void clear_col_tabstop(VTermState *state, int col)
120{
121  unsigned char mask = 1 << (col & 7);
122  state->tabstops[col >> 3] &= ~mask;
123}
124
125static int is_col_tabstop(VTermState *state, int col)
126{
127  unsigned char mask = 1 << (col & 7);
128  return state->tabstops[col >> 3] & mask;
129}
130
131static void tab(VTermState *state, int count, int direction)
132{
133  while(count--)
134    while(state->pos.col >= 0 && state->pos.col < state->cols-1) {
135      state->pos.col += direction;
136
137      if(is_col_tabstop(state, state->pos.col))
138        break;
139    }
140}
141
142static int on_text(const char bytes[], size_t len, void *user)
143{
144  VTermState *state = user;
145  uint32_t* chars;
146
147  VTermPos oldpos = state->pos;
148
149  // We'll have at most len codepoints
150  uint32_t codepoints[len];
151  int npoints = 0;
152  size_t eaten = 0;
153  int i = 0;
154
155  VTermEncodingInstance *encoding =
156    !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
157    state->vt->is_utf8     ? &state->encoding_utf8 :
158                             &state->encoding[state->gr_set];
159
160  (*encoding->enc->decode)(encoding->enc, encoding->data,
161      codepoints, &npoints, len, bytes, &eaten, len);
162
163  /* This is a combining char. that needs to be merged with the previous
164   * glyph output */
165  if(vterm_unicode_is_combining(codepoints[i])) {
166    /* See if the cursor has moved since */
167    if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
168      size_t saved_i = 0;
169#ifdef DEBUG_GLYPH_COMBINE
170      int printpos;
171      printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
172      for(printpos = 0; state->combine_chars[printpos]; printpos++)
173        printf("U+%04x ", state->combine_chars[printpos]);
174      printf("} + {");
175#endif
176
177      /* Find where we need to append these combining chars */
178      while(state->combine_chars[saved_i])
179        saved_i++;
180
181      /* Add extra ones */
182      while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
183        if(saved_i >= state->combine_chars_size)
184          grow_combine_buffer(state);
185        state->combine_chars[saved_i++] = codepoints[i++];
186      }
187      if(saved_i >= state->combine_chars_size)
188        grow_combine_buffer(state);
189      state->combine_chars[saved_i] = 0;
190
191#ifdef DEBUG_GLYPH_COMBINE
192      for(; state->combine_chars[printpos]; printpos++)
193        printf("U+%04x ", state->combine_chars[printpos]);
194      printf("}\n");
195#endif
196
197      /* Now render it */
198      putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
199    }
200    else {
201      fprintf(stderr, "libvterm: TODO: Skip over split char+combining\n");
202    }
203  }
204
205  for(; i < npoints; i++) {
206    // Try to find combining characters following this
207    int glyph_starts = i;
208    int glyph_ends;
209    int width = 0;
210
211    for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
212      if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
213        break;
214
215    chars = alloca(sizeof(uint32_t) * (glyph_ends - glyph_starts + 1));
216
217    for( ; i < glyph_ends; i++) {
218      chars[i - glyph_starts] = codepoints[i];
219      width += vterm_unicode_width(codepoints[i]);
220    }
221
222    chars[glyph_ends - glyph_starts] = 0;
223    i--;
224
225#ifdef DEBUG_GLYPH_COMBINE
226	{
227    int printpos;
228    printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
229    for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
230      printf("U+%04x ", chars[printpos]);
231    printf("}, onscreen width %d\n", width);
232	}
233#endif
234
235    if(state->at_phantom) {
236      linefeed(state);
237      state->pos.col = 0;
238      state->at_phantom = 0;
239    }
240
241    if(state->mode.insert) {
242      /* TODO: This will be a little inefficient for large bodies of text, as
243       * it'll have to 'ICH' effectively before every glyph. We should scan
244       * ahead and ICH as many times as required
245       */
246      VTermRect rect = {
247        .start_row = state->pos.row,
248        .end_row   = state->pos.row + 1,
249        .start_col = state->pos.col,
250        .end_col   = state->cols,
251      };
252      scroll(state, rect, 0, -1);
253    }
254    putglyph(state, chars, width, state->pos);
255
256    if(i == npoints - 1) {
257      /* End of the buffer. Save the chars in case we have to combine with
258       * more on the next call */
259      unsigned int save_i;
260      for(save_i = 0; chars[save_i]; save_i++) {
261        if(save_i >= state->combine_chars_size)
262          grow_combine_buffer(state);
263        state->combine_chars[save_i] = chars[save_i];
264      }
265      if(save_i >= state->combine_chars_size)
266        grow_combine_buffer(state);
267      state->combine_chars[save_i] = 0;
268      state->combine_width = width;
269      state->combine_pos = state->pos;
270    }
271
272    if(state->pos.col + width >= state->cols) {
273      if(state->mode.autowrap)
274        state->at_phantom = 1;
275    }
276    else {
277      state->pos.col += width;
278    }
279  }
280
281  updatecursor(state, &oldpos, 0);
282
283  return eaten;
284}
285
286static int on_control(unsigned char control, void *user)
287{
288  VTermState *state = user;
289
290  VTermPos oldpos = state->pos;
291
292  switch(control) {
293  case 0x07: // BEL - ECMA-48 8.3.3
294    if(state->callbacks && state->callbacks->bell)
295      (*state->callbacks->bell)(state->cbdata);
296    break;
297
298  case 0x08: // BS - ECMA-48 8.3.5
299    if(state->pos.col > 0)
300      state->pos.col--;
301    break;
302
303  case 0x09: // HT - ECMA-48 8.3.60
304    tab(state, 1, +1);
305    break;
306
307  case 0x0a: // LF - ECMA-48 8.3.74
308  case 0x0b: // VT
309  case 0x0c: // FF
310    linefeed(state);
311    if(state->mode.newline)
312      state->pos.col = 0;
313    break;
314
315  case 0x0d: // CR - ECMA-48 8.3.15
316    state->pos.col = 0;
317    break;
318
319  case 0x0e: // LS1 - ECMA-48 8.3.76
320    state->gl_set = 1;
321    break;
322
323  case 0x0f: // LS0 - ECMA-48 8.3.75
324    state->gl_set = 0;
325    break;
326
327  case 0x84: // IND - DEPRECATED but implemented for completeness
328    linefeed(state);
329    break;
330
331  case 0x85: // NEL - ECMA-48 8.3.86
332    linefeed(state);
333    state->pos.col = 0;
334    break;
335
336  case 0x88: // HTS - ECMA-48 8.3.62
337    set_col_tabstop(state, state->pos.col);
338    break;
339
340  case 0x8d: // RI - ECMA-48 8.3.104
341    if(state->pos.row == state->scrollregion_start) {
342      VTermRect rect = {
343        .start_row = state->scrollregion_start,
344        .end_row   = SCROLLREGION_END(state),
345        .start_col = 0,
346        .end_col   = state->cols,
347      };
348
349      scroll(state, rect, -1, 0);
350    }
351    else if(state->pos.row > 0)
352        state->pos.row--;
353    break;
354
355  default:
356    return 0;
357  }
358
359  updatecursor(state, &oldpos, 1);
360
361  return 1;
362}
363
364static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
365{
366  modifiers <<= 2;
367
368  switch(state->mouse_protocol) {
369  case MOUSE_X10:
370    if(col + 0x21 > 0xff)
371      col = 0xff - 0x21;
372    if(row + 0x21 > 0xff)
373      row = 0xff - 0x21;
374
375    if(!pressed)
376      code = 3;
377
378    vterm_push_output_sprintf(state->vt, "\e[M%c%c%c",
379        (code | modifiers) + 0x20, col + 0x21, row + 0x21);
380    break;
381
382  case MOUSE_UTF8:
383    {
384      char utf8[18]; size_t len = 0;
385
386      if(!pressed)
387        code = 3;
388
389      len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
390      len += fill_utf8(col + 0x21, utf8 + len);
391      len += fill_utf8(row + 0x21, utf8 + len);
392
393      vterm_push_output_sprintf(state->vt, "\e[M%s", utf8);
394    }
395    break;
396
397  case MOUSE_SGR:
398    vterm_push_output_sprintf(state->vt, "\e[<%d;%d;%d%c",
399        code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
400    break;
401
402  case MOUSE_RXVT:
403    if(!pressed)
404      code = 3;
405
406    vterm_push_output_sprintf(state->vt, "\e[%d;%d;%dM",
407        code | modifiers, col + 1, row + 1);
408    break;
409  }
410}
411
412static void mousefunc(int col, int row, int button, int pressed, int modifiers, void *data)
413{
414  VTermState *state = data;
415
416  int old_col     = state->mouse_col;
417  int old_row     = state->mouse_row;
418  int old_buttons = state->mouse_buttons;
419
420  state->mouse_col = col;
421  state->mouse_row = row;
422
423  if(button > 0 && button <= 3) {
424    if(pressed)
425      state->mouse_buttons |= (1 << (button-1));
426    else
427      state->mouse_buttons &= ~(1 << (button-1));
428  }
429
430  modifiers &= 0x7;
431
432
433  /* Most of the time we don't get button releases from 4/5 */
434  if(state->mouse_buttons != old_buttons || button >= 4) {
435    if(button < 4) {
436      output_mouse(state, button-1, pressed, modifiers, col, row);
437    }
438    else if(button < 6) {
439      output_mouse(state, button-4 + 0x40, pressed, modifiers, col, row);
440    }
441  }
442  else if(col != old_col || row != old_row) {
443    if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
444       (state->mouse_flags & MOUSE_WANT_MOVE)) {
445      int button = state->mouse_buttons & 0x01 ? 1 :
446                   state->mouse_buttons & 0x02 ? 2 :
447                   state->mouse_buttons & 0x04 ? 3 : 4;
448      output_mouse(state, button-1 + 0x20, 1, modifiers, col, row);
449    }
450  }
451}
452
453static int settermprop_bool(VTermState *state, VTermProp prop, int v)
454{
455  VTermValue val;
456  val.boolean = v;
457
458#ifdef DEBUG
459  if(VTERM_VALUETYPE_BOOL != vterm_get_prop_type(prop)) {
460    fprintf(stderr, "Cannot set prop %d as it has type %d, not type BOOL\n",
461        prop, vterm_get_prop_type(prop));
462    return -1;
463  }
464#endif
465
466  if(state->callbacks && state->callbacks->settermprop)
467    if((*state->callbacks->settermprop)(prop, &val, state->cbdata))
468      return 1;
469
470  return 0;
471}
472
473static int settermprop_int(VTermState *state, VTermProp prop, int v)
474{
475  VTermValue val;
476  val.number = v;
477
478#ifdef DEBUG
479  if(VTERM_VALUETYPE_INT != vterm_get_prop_type(prop)) {
480    fprintf(stderr, "Cannot set prop %d as it has type %d, not type int\n",
481        prop, vterm_get_prop_type(prop));
482    return -1;
483  }
484#endif
485
486  if(state->callbacks && state->callbacks->settermprop)
487    if((*state->callbacks->settermprop)(prop, &val, state->cbdata))
488      return 1;
489
490  return 0;
491}
492
493static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len)
494{
495  char strvalue[len+1];
496  VTermValue val;
497
498  strncpy(strvalue, str, len);
499  strvalue[len] = 0;
500
501  val.string = strvalue;
502
503#ifdef DEBUG
504  if(VTERM_VALUETYPE_STRING != vterm_get_prop_type(prop)) {
505    fprintf(stderr, "Cannot set prop %d as it has type %d, not type STRING\n",
506        prop, vterm_get_prop_type(prop));
507    return -1;
508  }
509#endif
510
511  if(state->callbacks && state->callbacks->settermprop)
512    if((*state->callbacks->settermprop)(prop, &val, state->cbdata))
513      return 1;
514
515  return 0;
516}
517
518static void savecursor(VTermState *state, int save)
519{
520  if(save) {
521    state->saved.pos = state->pos;
522    state->saved.mode.cursor_visible = state->mode.cursor_visible;
523    state->saved.mode.cursor_blink   = state->mode.cursor_blink;
524    state->saved.mode.cursor_shape   = state->mode.cursor_shape;
525
526    vterm_state_savepen(state, 1);
527  }
528  else {
529    VTermPos oldpos = state->pos;
530
531    state->pos = state->saved.pos;
532    state->mode.cursor_visible = state->saved.mode.cursor_visible;
533    state->mode.cursor_blink   = state->saved.mode.cursor_blink;
534    state->mode.cursor_shape   = state->saved.mode.cursor_shape;
535
536    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->mode.cursor_visible);
537    settermprop_bool(state, VTERM_PROP_CURSORBLINK,   state->mode.cursor_blink);
538    settermprop_int (state, VTERM_PROP_CURSORSHAPE,   state->mode.cursor_shape);
539
540    vterm_state_savepen(state, 0);
541
542    updatecursor(state, &oldpos, 1);
543  }
544}
545
546static void altscreen(VTermState *state, int alt)
547{
548  /* Only store that we're on the alternate screen if the usercode said it
549   * switched */
550  if(!settermprop_bool(state, VTERM_PROP_ALTSCREEN, alt))
551    return;
552
553  state->mode.alt_screen = alt;
554  if(alt) {
555    VTermRect rect = {
556      .start_row = 0,
557      .start_col = 0,
558      .end_row = state->rows,
559      .end_col = state->cols,
560    };
561    erase(state, rect);
562  }
563}
564
565static int on_escape(const char *bytes, size_t len, void *user)
566{
567  VTermState *state = user;
568
569  /* Easier to decode this from the first byte, even though the final
570   * byte terminates it
571   */
572  switch(bytes[0]) {
573  case '#':
574    if(len != 2)
575      return 0;
576
577    switch(bytes[1]) {
578      case '8': // DECALN
579      {
580        VTermPos pos;
581        uint32_t E[] = { 'E', 0 };
582        for(pos.row = 0; pos.row < state->rows; pos.row++)
583          for(pos.col = 0; pos.col < state->cols; pos.col++)
584            putglyph(state, E, 1, pos);
585        break;
586      }
587
588      default:
589        return 0;
590    }
591    return 2;
592
593  case '(': case ')': case '*': case '+': // SCS
594    if(len != 2)
595      return 0;
596
597    {
598      int setnum = bytes[0] - 0x28;
599      VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
600
601      if(newenc) {
602        state->encoding[setnum].enc = newenc;
603
604        if(newenc->init)
605          (*newenc->init)(newenc, state->encoding[setnum].data);
606      }
607    }
608
609    return 2;
610
611  case '7': // DECSC
612    savecursor(state, 1);
613    return 1;
614
615  case '8': // DECRC
616    savecursor(state, 0);
617    return 1;
618
619  case '=': // DECKPAM
620    state->mode.keypad = 1;
621    return 1;
622
623  case '>': // DECKPNM
624    state->mode.keypad = 0;
625    return 1;
626
627  case 'c': // RIS - ECMA-48 8.3.105
628  {
629    VTermPos oldpos = state->pos;
630    vterm_state_reset(state, 1);
631    if(state->callbacks && state->callbacks->movecursor)
632      (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
633    return 1;
634  }
635
636  case 'n': // LS2 - ECMA-48 8.3.78
637    state->gl_set = 2;
638    return 1;
639
640  case 'o': // LS3 - ECMA-48 8.3.80
641    state->gl_set = 3;
642    return 1;
643
644  default:
645    return 0;
646  }
647}
648
649static void set_mode(VTermState *state, int num, int val)
650{
651  switch(num) {
652  case 4: // IRM - ECMA-48 7.2.10
653    state->mode.insert = val;
654    break;
655
656  case 20: // LNM - ANSI X3.4-1977
657    state->mode.newline = val;
658    break;
659
660  default:
661    fprintf(stderr, "libvterm: Unknown mode %d\n", num);
662    return;
663  }
664}
665
666static void set_dec_mode(VTermState *state, int num, int val)
667{
668  switch(num) {
669  case 1:
670    state->mode.cursor = val;
671    break;
672
673  case 5:
674    settermprop_bool(state, VTERM_PROP_REVERSE, val);
675    break;
676
677  case 6: // DECOM - origin mode
678    {
679      VTermPos oldpos = state->pos;
680      state->mode.origin = val;
681      state->pos.row = state->mode.origin ? state->scrollregion_start : 0;
682      state->pos.col = 0;
683      updatecursor(state, &oldpos, 1);
684    }
685    break;
686
687  case 7:
688    state->mode.autowrap = val;
689    break;
690
691  case 12:
692    state->mode.cursor_blink = val;
693    settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
694    break;
695
696  case 25:
697    state->mode.cursor_visible = val;
698    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
699    break;
700
701  case 1000:
702  case 1002:
703  case 1003:
704    if(val) {
705      state->mouse_col     = 0;
706      state->mouse_row     = 0;
707      state->mouse_buttons = 0;
708
709      state->mouse_flags = 0;
710      state->mouse_protocol = MOUSE_X10;
711
712      if(num == 1002)
713        state->mouse_flags |= MOUSE_WANT_DRAG;
714      if(num == 1003)
715        state->mouse_flags |= MOUSE_WANT_MOVE;
716    }
717
718    if(state->callbacks && state->callbacks->setmousefunc)
719      (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, state->cbdata);
720
721    break;
722
723  case 1005:
724    state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
725    break;
726
727  case 1006:
728    state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
729    break;
730
731  case 1015:
732    state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
733    break;
734
735  case 1047:
736    altscreen(state, val);
737    break;
738
739  case 1048:
740    savecursor(state, val);
741    break;
742
743  case 1049:
744    altscreen(state, val);
745    savecursor(state, val);
746    break;
747
748  default:
749    fprintf(stderr, "libvterm: Unknown DEC mode %d\n", num);
750    return;
751  }
752}
753
754static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
755{
756  VTermState *state = user;
757  int leader_byte = 0;
758  int intermed_byte = 0;
759  VTermPos oldpos;
760
761  // Some temporaries for later code
762  int count, val;
763  int row, col;
764  VTermRect rect;
765
766  if(leader && leader[0]) {
767    if(leader[1]) // longer than 1 char
768      return 0;
769
770    switch(leader[0]) {
771    case '?':
772    case '>':
773      leader_byte = leader[0];
774      break;
775    default:
776      return 0;
777    }
778  }
779
780  if(intermed && intermed[0]) {
781    if(intermed[1]) // longer than 1 char
782      return 0;
783
784    switch(intermed[0]) {
785    case ' ':
786      intermed_byte = intermed[0];
787      break;
788    default:
789      return 0;
790    }
791  }
792
793  oldpos = state->pos;
794
795#define LEADER(l,b) ((l << 8) | b)
796#define INTERMED(i,b) ((i << 16) | b)
797
798  switch(intermed_byte << 16 | leader_byte << 8 | command) {
799  case 0x40: // ICH - ECMA-48 8.3.64
800    count = CSI_ARG_COUNT(args[0]);
801
802    rect.start_row = state->pos.row;
803    rect.end_row   = state->pos.row + 1;
804    rect.start_col = state->pos.col;
805    rect.end_col   = state->cols;
806
807    scroll(state, rect, 0, -count);
808
809    break;
810
811  case 0x41: // CUU - ECMA-48 8.3.22
812    count = CSI_ARG_COUNT(args[0]);
813    state->pos.row -= count;
814    state->at_phantom = 0;
815    break;
816
817  case 0x42: // CUD - ECMA-48 8.3.19
818    count = CSI_ARG_COUNT(args[0]);
819    state->pos.row += count;
820    state->at_phantom = 0;
821    break;
822
823  case 0x43: // CUF - ECMA-48 8.3.20
824    count = CSI_ARG_COUNT(args[0]);
825    state->pos.col += count;
826    state->at_phantom = 0;
827    break;
828
829  case 0x44: // CUB - ECMA-48 8.3.18
830    count = CSI_ARG_COUNT(args[0]);
831    state->pos.col -= count;
832    state->at_phantom = 0;
833    break;
834
835  case 0x45: // CNL - ECMA-48 8.3.12
836    count = CSI_ARG_COUNT(args[0]);
837    state->pos.col = 0;
838    state->pos.row += count;
839    state->at_phantom = 0;
840    break;
841
842  case 0x46: // CPL - ECMA-48 8.3.13
843    count = CSI_ARG_COUNT(args[0]);
844    state->pos.col = 0;
845    state->pos.row -= count;
846    state->at_phantom = 0;
847    break;
848
849  case 0x47: // CHA - ECMA-48 8.3.9
850    val = CSI_ARG_OR(args[0], 1);
851    state->pos.col = val-1;
852    state->at_phantom = 0;
853    break;
854
855  case 0x48: // CUP - ECMA-48 8.3.21
856    row = CSI_ARG_OR(args[0], 1);
857    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
858    // zero-based
859    state->pos.row = row-1;
860    state->pos.col = col-1;
861    if(state->mode.origin)
862      state->pos.row += state->scrollregion_start;
863    state->at_phantom = 0;
864    break;
865
866  case 0x49: // CHT - ECMA-48 8.3.10
867    count = CSI_ARG_COUNT(args[0]);
868    tab(state, count, +1);
869    break;
870
871  case 0x4a: // ED - ECMA-48 8.3.39
872    switch(CSI_ARG(args[0])) {
873    case CSI_ARG_MISSING:
874    case 0:
875      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
876      rect.start_col = state->pos.col; rect.end_col = state->cols;
877      if(rect.end_col > rect.start_col)
878        erase(state, rect);
879
880      rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
881      rect.start_col = 0;
882      if(rect.end_row > rect.start_row)
883        erase(state, rect);
884      break;
885
886    case 1:
887      rect.start_row = 0; rect.end_row = state->pos.row;
888      rect.start_col = 0; rect.end_col = state->cols;
889      if(rect.end_col > rect.start_col)
890        erase(state, rect);
891
892      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
893                          rect.end_col = state->pos.col + 1;
894      if(rect.end_row > rect.start_row)
895        erase(state, rect);
896      break;
897
898    case 2:
899      rect.start_row = 0; rect.end_row = state->rows;
900      rect.start_col = 0; rect.end_col = state->cols;
901      erase(state, rect);
902      break;
903    }
904    break;
905
906  case 0x4b: // EL - ECMA-48 8.3.41
907    rect.start_row = state->pos.row;
908    rect.end_row   = state->pos.row + 1;
909
910    switch(CSI_ARG(args[0])) {
911    case CSI_ARG_MISSING:
912    case 0:
913      rect.start_col = state->pos.col; rect.end_col = state->cols; break;
914    case 1:
915      rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
916    case 2:
917      rect.start_col = 0; rect.end_col = state->cols; break;
918    default:
919      return 0;
920    }
921
922    if(rect.end_col > rect.start_col)
923      erase(state, rect);
924
925    break;
926
927  case 0x4c: // IL - ECMA-48 8.3.67
928    count = CSI_ARG_COUNT(args[0]);
929
930    rect.start_row = state->pos.row;
931    rect.end_row   = SCROLLREGION_END(state);
932    rect.start_col = 0;
933    rect.end_col   = state->cols;
934
935    scroll(state, rect, -count, 0);
936
937    break;
938
939  case 0x4d: // DL - ECMA-48 8.3.32
940    count = CSI_ARG_COUNT(args[0]);
941
942    rect.start_row = state->pos.row;
943    rect.end_row   = SCROLLREGION_END(state);
944    rect.start_col = 0;
945    rect.end_col   = state->cols;
946
947    scroll(state, rect, count, 0);
948
949    break;
950
951  case 0x50: // DCH - ECMA-48 8.3.26
952    count = CSI_ARG_COUNT(args[0]);
953
954    rect.start_row = state->pos.row;
955    rect.end_row   = state->pos.row + 1;
956    rect.start_col = state->pos.col;
957    rect.end_col   = state->cols;
958
959    scroll(state, rect, 0, count);
960
961    break;
962
963  case 0x53: // SU - ECMA-48 8.3.147
964    count = CSI_ARG_COUNT(args[0]);
965
966    rect.start_row = state->scrollregion_start;
967    rect.end_row   = SCROLLREGION_END(state);
968    rect.start_col = 0;
969    rect.end_col   = state->cols;
970
971    scroll(state, rect, count, 0);
972
973    break;
974
975  case 0x54: // SD - ECMA-48 8.3.113
976    count = CSI_ARG_COUNT(args[0]);
977
978    rect.start_row = state->scrollregion_start;
979    rect.end_row   = SCROLLREGION_END(state);
980    rect.start_col = 0;
981    rect.end_col   = state->cols;
982
983    scroll(state, rect, -count, 0);
984
985    break;
986
987  case 0x58: // ECH - ECMA-48 8.3.38
988    count = CSI_ARG_COUNT(args[0]);
989
990    rect.start_row = state->pos.row;
991    rect.end_row   = state->pos.row + 1;
992    rect.start_col = state->pos.col;
993    rect.end_col   = state->pos.col + count;
994
995    erase(state, rect);
996    break;
997
998  case 0x5a: // CBT - ECMA-48 8.3.7
999    count = CSI_ARG_COUNT(args[0]);
1000    tab(state, count, -1);
1001    break;
1002
1003  case 0x60: // HPA - ECMA-48 8.3.57
1004    col = CSI_ARG_OR(args[0], 1);
1005    state->pos.col = col-1;
1006    state->at_phantom = 0;
1007    break;
1008
1009  case 0x61: // HPR - ECMA-48 8.3.59
1010    count = CSI_ARG_COUNT(args[0]);
1011    state->pos.col += count;
1012    state->at_phantom = 0;
1013    break;
1014
1015  case 0x63: // DA - ECMA-48 8.3.24
1016    val = CSI_ARG_OR(args[0], 0);
1017    if(val == 0)
1018      // DEC VT100 response
1019      vterm_push_output_sprintf(state->vt, "\e[?1;2c");
1020    break;
1021
1022  case LEADER('>', 0x63): // DEC secondary Device Attributes
1023    vterm_push_output_sprintf(state->vt, "\e[>%d;%d;%dc", 0, 100, 0);
1024    break;
1025
1026  case 0x64: // VPA - ECMA-48 8.3.158
1027    row = CSI_ARG_OR(args[0], 1);
1028    state->pos.row = row-1;
1029    if(state->mode.origin)
1030      state->pos.row += state->scrollregion_start;
1031    state->at_phantom = 0;
1032    break;
1033
1034  case 0x65: // VPR - ECMA-48 8.3.160
1035    count = CSI_ARG_COUNT(args[0]);
1036    state->pos.row += count;
1037    state->at_phantom = 0;
1038    break;
1039
1040  case 0x66: // HVP - ECMA-48 8.3.63
1041    row = CSI_ARG_OR(args[0], 1);
1042    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
1043    // zero-based
1044    state->pos.row = row-1;
1045    state->pos.col = col-1;
1046    if(state->mode.origin)
1047      state->pos.row += state->scrollregion_start;
1048    state->at_phantom = 0;
1049    break;
1050
1051  case 0x67: // TBC - ECMA-48 8.3.154
1052    val = CSI_ARG_OR(args[0], 0);
1053
1054    switch(val) {
1055    case 0:
1056      clear_col_tabstop(state, state->pos.col);
1057      break;
1058    case 3:
1059    case 5:
1060      for(col = 0; col < state->cols; col++)
1061        clear_col_tabstop(state, col);
1062      break;
1063    case 1:
1064    case 2:
1065    case 4:
1066      break;
1067    /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
1068    default:
1069      return 0;
1070    }
1071    break;
1072
1073  case 0x68: // SM - ECMA-48 8.3.125
1074    if(!CSI_ARG_IS_MISSING(args[0]))
1075      set_mode(state, CSI_ARG(args[0]), 1);
1076    break;
1077
1078  case LEADER('?', 0x68): // DEC private mode set
1079    if(!CSI_ARG_IS_MISSING(args[0]))
1080      set_dec_mode(state, CSI_ARG(args[0]), 1);
1081    break;
1082
1083  case 0x6a: // HPB - ECMA-48 8.3.58
1084    count = CSI_ARG_COUNT(args[0]);
1085    state->pos.col -= count;
1086    state->at_phantom = 0;
1087    break;
1088
1089  case 0x6b: // VPB - ECMA-48 8.3.159
1090    count = CSI_ARG_COUNT(args[0]);
1091    state->pos.row -= count;
1092    state->at_phantom = 0;
1093    break;
1094
1095  case 0x6c: // RM - ECMA-48 8.3.106
1096    if(!CSI_ARG_IS_MISSING(args[0]))
1097      set_mode(state, CSI_ARG(args[0]), 0);
1098    break;
1099
1100  case LEADER('?', 0x6c): // DEC private mode reset
1101    if(!CSI_ARG_IS_MISSING(args[0]))
1102      set_dec_mode(state, CSI_ARG(args[0]), 0);
1103    break;
1104
1105  case 0x6d: // SGR - ECMA-48 8.3.117
1106    vterm_state_setpen(state, args, argcount);
1107    break;
1108
1109  case 0x6e: // DSR - ECMA-48 8.3.35
1110    val = CSI_ARG_OR(args[0], 0);
1111
1112    switch(val) {
1113    case 0: case 1: case 2: case 3: case 4:
1114      // ignore - these are replies
1115      break;
1116    case 5:
1117      vterm_push_output_sprintf(state->vt, "\e[0n");
1118      break;
1119    case 6:
1120      vterm_push_output_sprintf(state->vt, "\e[%d;%dR", state->pos.row + 1, state->pos.col + 1);
1121      break;
1122    }
1123    break;
1124
1125  case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
1126    vterm_state_reset(state, 0);
1127    break;
1128
1129  case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
1130    val = CSI_ARG_OR(args[0], 1);
1131
1132    switch(val) {
1133    case 0: case 1:
1134      state->mode.cursor_blink = 1;
1135      state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
1136      break;
1137    case 2:
1138      state->mode.cursor_blink = 0;
1139      state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
1140      break;
1141    case 3:
1142      state->mode.cursor_blink = 1;
1143      state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_UNDERLINE;
1144      break;
1145    case 4:
1146      state->mode.cursor_blink = 0;
1147      state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_UNDERLINE;
1148      break;
1149    }
1150
1151    settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink);
1152    settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape);
1153    break;
1154
1155  case 0x72: // DECSTBM - DEC custom
1156    state->scrollregion_start = CSI_ARG_OR(args[0], 1) - 1;
1157    state->scrollregion_end = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1158    if(state->scrollregion_start == 0 && state->scrollregion_end == state->rows)
1159      state->scrollregion_end = -1;
1160    break;
1161
1162  case 0x73: // ANSI SAVE
1163    savecursor(state, 1);
1164    break;
1165
1166  case 0x75: // ANSI RESTORE
1167    savecursor(state, 0);
1168    break;
1169
1170  default:
1171    return 0;
1172  }
1173
1174#define LBOUND(v,min) if((v) < (min)) (v) = (min)
1175#define UBOUND(v,max) if((v) > (max)) (v) = (max)
1176
1177  LBOUND(state->pos.col, 0);
1178  UBOUND(state->pos.col, state->cols-1);
1179
1180  if(state->mode.origin) {
1181    LBOUND(state->pos.row, state->scrollregion_start);
1182    UBOUND(state->pos.row, state->scrollregion_end-1);
1183  }
1184  else {
1185    LBOUND(state->pos.row, 0);
1186    UBOUND(state->pos.row, state->rows-1);
1187  }
1188
1189  updatecursor(state, &oldpos, 1);
1190
1191  return 1;
1192}
1193
1194static int on_osc(const char *command, size_t cmdlen, void *user)
1195{
1196  VTermState *state = user;
1197
1198  if(cmdlen < 2)
1199    return 0;
1200
1201  if(strneq(command, "0;", 2)) {
1202    settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
1203    settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
1204    return 1;
1205  }
1206  else if(strneq(command, "1;", 2)) {
1207    settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
1208    return 1;
1209  }
1210  else if(strneq(command, "2;", 2)) {
1211    settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
1212    return 1;
1213  }
1214
1215  return 0;
1216}
1217
1218static void request_status_string(VTermState *state, const char *command, size_t cmdlen)
1219{
1220  if(cmdlen == 1)
1221    switch(command[0]) {
1222      case 'r': // Query DECSTBM
1223        vterm_push_output_sprintf(state->vt, "\eP1$r%d;%dr\e\\", state->scrollregion_start+1, SCROLLREGION_END(state));
1224        return;
1225    }
1226
1227  if(cmdlen == 2)
1228    if(strneq(command, " q", 2)) {
1229      int reply = 0;
1230      switch(state->mode.cursor_shape) {
1231        case VTERM_PROP_CURSORSHAPE_BLOCK:     reply = 2; break;
1232        case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
1233      }
1234      if(state->mode.cursor_blink)
1235        reply--;
1236      vterm_push_output_sprintf(state->vt, "\eP1$r%d q\e\\", reply);
1237      return;
1238    }
1239
1240  vterm_push_output_sprintf(state->vt, "\eP0$r%.s\e\\", (int)cmdlen, command);
1241}
1242
1243static int on_dcs(const char *command, size_t cmdlen, void *user)
1244{
1245  VTermState *state = user;
1246
1247  if(cmdlen >= 2 && strneq(command, "$q", 2)) {
1248    request_status_string(state, command+2, cmdlen-2);
1249    return 1;
1250  }
1251
1252  return 0;
1253}
1254
1255static int on_resize(int rows, int cols, void *user)
1256{
1257  VTermState *state = user;
1258  VTermPos oldpos = state->pos;
1259
1260  if(cols != state->cols) {
1261    unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
1262
1263    /* TODO: This can all be done much more efficiently bytewise */
1264    int col;
1265    for(col = 0; col < state->cols && col < cols; col++) {
1266      unsigned char mask = 1 << (col & 7);
1267      if(state->tabstops[col >> 3] & mask)
1268        newtabstops[col >> 3] |= mask;
1269      else
1270        newtabstops[col >> 3] &= ~mask;
1271      }
1272
1273    for( ; col < cols; col++) {
1274      unsigned char mask = 1 << (col & 7);
1275      if(col % 8 == 0)
1276        newtabstops[col >> 3] |= mask;
1277      else
1278        newtabstops[col >> 3] &= ~mask;
1279    }
1280
1281    vterm_allocator_free(state->vt, state->tabstops);
1282    state->tabstops = newtabstops;
1283  }
1284
1285  state->rows = rows;
1286  state->cols = cols;
1287
1288  if(state->pos.row >= rows)
1289    state->pos.row = rows - 1;
1290  if(state->pos.col >= cols)
1291    state->pos.col = cols - 1;
1292
1293  if(state->at_phantom && state->pos.col < cols-1) {
1294    state->at_phantom = 0;
1295    state->pos.col++;
1296  }
1297
1298  if(state->callbacks && state->callbacks->resize)
1299    (*state->callbacks->resize)(rows, cols, state->cbdata);
1300
1301  updatecursor(state, &oldpos, 1);
1302
1303  return 1;
1304}
1305
1306static const VTermParserCallbacks parser_callbacks = {
1307  .text    = on_text,
1308  .control = on_control,
1309  .escape  = on_escape,
1310  .csi     = on_csi,
1311  .osc     = on_osc,
1312  .dcs     = on_dcs,
1313  .resize  = on_resize,
1314};
1315
1316VTermState *vterm_obtain_state(VTerm *vt)
1317{
1318  VTermState *state;
1319  if(vt->state)
1320    return vt->state;
1321
1322  state = vterm_state_new(vt);
1323  vt->state = state;
1324
1325  state->combine_chars_size = 16;
1326  state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
1327
1328  state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
1329
1330  state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
1331  if(*state->encoding_utf8.enc->init)
1332    (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
1333
1334  vterm_set_parser_callbacks(vt, &parser_callbacks, state);
1335
1336  return state;
1337}
1338
1339void vterm_state_reset(VTermState *state, int hard)
1340{
1341  int col, i;
1342  VTermEncoding *default_enc;
1343
1344  state->scrollregion_start = 0;
1345  state->scrollregion_end = -1;
1346
1347  state->mode.keypad         = 0;
1348  state->mode.cursor         = 0;
1349  state->mode.autowrap       = 1;
1350  state->mode.insert         = 0;
1351  state->mode.newline        = 0;
1352  state->mode.cursor_visible = 1;
1353  state->mode.cursor_blink   = 1;
1354  state->mode.cursor_shape   = VTERM_PROP_CURSORSHAPE_BLOCK;
1355  state->mode.alt_screen     = 0;
1356  state->mode.origin         = 0;
1357
1358  for(col = 0; col < state->cols; col++)
1359    if(col % 8 == 0)
1360      set_col_tabstop(state, col);
1361    else
1362      clear_col_tabstop(state, col);
1363
1364  if(state->callbacks && state->callbacks->initpen)
1365    (*state->callbacks->initpen)(state->cbdata);
1366
1367  vterm_state_resetpen(state);
1368
1369  default_enc = state->vt->is_utf8 ?
1370      vterm_lookup_encoding(ENC_UTF8,      'u') :
1371      vterm_lookup_encoding(ENC_SINGLE_94, 'B');
1372
1373  for(i = 0; i < 4; i++) {
1374    state->encoding[i].enc = default_enc;
1375    if(default_enc->init)
1376      (*default_enc->init)(default_enc, state->encoding[i].data);
1377  }
1378
1379  state->gl_set = 0;
1380  state->gr_set = 0;
1381
1382  // Initialise the props
1383  settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->mode.cursor_visible);
1384  settermprop_bool(state, VTERM_PROP_CURSORBLINK,   state->mode.cursor_blink);
1385  settermprop_int (state, VTERM_PROP_CURSORSHAPE,   state->mode.cursor_shape);
1386
1387  if(hard) {
1388	VTermRect rect = { 0, state->rows, 0, state->cols };
1389
1390    state->pos.row = 0;
1391    state->pos.col = 0;
1392    state->at_phantom = 0;
1393
1394    erase(state, rect);
1395  }
1396}
1397
1398void vterm_state_get_cursorpos(VTermState *state, VTermPos *cursorpos)
1399{
1400  *cursorpos = state->pos;
1401}
1402
1403void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
1404{
1405  if(callbacks) {
1406    state->callbacks = callbacks;
1407    state->cbdata = user;
1408
1409    if(state->callbacks && state->callbacks->initpen)
1410      (*state->callbacks->initpen)(state->cbdata);
1411  }
1412  else {
1413    state->callbacks = NULL;
1414    state->cbdata = NULL;
1415  }
1416}
1417