dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

vterm-module.c (50865B)


      1 #include "vterm-module.h"
      2 #include "elisp.h"
      3 #include "utf8.h"
      4 #include <assert.h>
      5 #include <fcntl.h>
      6 #include <limits.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 #include <termios.h>
     10 #include <unistd.h>
     11 #include <vterm.h>
     12 
     13 static LineInfo *alloc_lineinfo() {
     14   LineInfo *info = malloc(sizeof(LineInfo));
     15   info->directory = NULL;
     16   info->prompt_col = -1;
     17   return info;
     18 }
     19 void free_lineinfo(LineInfo *line) {
     20   if (line == NULL) {
     21     return;
     22   }
     23   if (line->directory != NULL) {
     24     free(line->directory);
     25     line->directory = NULL;
     26   }
     27   free(line);
     28 }
     29 static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) {
     30   Term *term = (Term *)data;
     31 
     32   if (!term->sb_size) {
     33     return 0;
     34   }
     35 
     36   // copy vterm cells into sb_buffer
     37   size_t c = (size_t)cols;
     38   ScrollbackLine *sbrow = NULL;
     39   if (term->sb_current == term->sb_size) {
     40     if (term->sb_buffer[term->sb_current - 1]->cols == c) {
     41       // Recycle old row if it's the right size
     42       sbrow = term->sb_buffer[term->sb_current - 1];
     43     } else {
     44       if (term->sb_buffer[term->sb_current - 1]->info != NULL) {
     45         free_lineinfo(term->sb_buffer[term->sb_current - 1]->info);
     46         term->sb_buffer[term->sb_current - 1]->info = NULL;
     47       }
     48       free(term->sb_buffer[term->sb_current - 1]);
     49     }
     50 
     51     // Make room at the start by shifting to the right.
     52     memmove(term->sb_buffer + 1, term->sb_buffer,
     53             sizeof(term->sb_buffer[0]) * (term->sb_current - 1));
     54 
     55   } else if (term->sb_current > 0) {
     56     // Make room at the start by shifting to the right.
     57     memmove(term->sb_buffer + 1, term->sb_buffer,
     58             sizeof(term->sb_buffer[0]) * term->sb_current);
     59   }
     60 
     61   if (!sbrow) {
     62     sbrow = malloc(sizeof(ScrollbackLine) + c * sizeof(sbrow->cells[0]));
     63     sbrow->cols = c;
     64     sbrow->info = NULL;
     65   }
     66   if (sbrow->info != NULL) {
     67     free_lineinfo(sbrow->info);
     68   }
     69   sbrow->info = term->lines[0];
     70   memmove(term->lines, term->lines + 1,
     71           sizeof(term->lines[0]) * (term->lines_len - 1));
     72   if (term->resizing) {
     73     /* pushed by window height decr */
     74     if (term->lines[term->lines_len - 1] != NULL) {
     75       /* do not need free here ,it is reused ,we just need set null */
     76       term->lines[term->lines_len - 1] = NULL;
     77     }
     78     term->lines_len--;
     79   } else {
     80     LineInfo *lastline = term->lines[term->lines_len - 1];
     81     if (lastline != NULL) {
     82       LineInfo *line = alloc_lineinfo();
     83       if (lastline->directory != NULL) {
     84         line->directory = malloc(1 + strlen(lastline->directory));
     85         strcpy(line->directory, lastline->directory);
     86       }
     87       term->lines[term->lines_len - 1] = line;
     88     }
     89   }
     90 
     91   // New row is added at the start of the storage buffer.
     92   term->sb_buffer[0] = sbrow;
     93   if (term->sb_current < term->sb_size) {
     94     term->sb_current++;
     95   }
     96 
     97   if (term->sb_pending < term->sb_size) {
     98     term->sb_pending++;
     99     /* when window height decreased */
    100     if (term->height_resize < 0 &&
    101         term->sb_pending_by_height_decr < -term->height_resize) {
    102       term->sb_pending_by_height_decr++;
    103     }
    104   }
    105 
    106   memcpy(sbrow->cells, cells, c * sizeof(cells[0]));
    107 
    108   return 1;
    109 }
    110 /// Scrollback pop handler (from pangoterm).
    111 ///
    112 /// @param cols
    113 /// @param cells  VTerm state to update.
    114 /// @param data   Term
    115 static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) {
    116   Term *term = (Term *)data;
    117 
    118   if (!term->sb_current) {
    119     return 0;
    120   }
    121 
    122   if (term->sb_pending) {
    123     term->sb_pending--;
    124   }
    125 
    126   ScrollbackLine *sbrow = term->sb_buffer[0];
    127   term->sb_current--;
    128   // Forget the "popped" row by shifting the rest onto it.
    129   memmove(term->sb_buffer, term->sb_buffer + 1,
    130           sizeof(term->sb_buffer[0]) * (term->sb_current));
    131 
    132   size_t cols_to_copy = (size_t)cols;
    133   if (cols_to_copy > sbrow->cols) {
    134     cols_to_copy = sbrow->cols;
    135   }
    136 
    137   // copy to vterm state
    138   memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
    139   size_t col;
    140   for (col = cols_to_copy; col < (size_t)cols; col++) {
    141     cells[col].chars[0] = 0;
    142     cells[col].width = 1;
    143   }
    144 
    145   LineInfo **lines = malloc(sizeof(LineInfo *) * (term->lines_len + 1));
    146 
    147   memmove(lines + 1, term->lines, sizeof(term->lines[0]) * term->lines_len);
    148   lines[0] = sbrow->info;
    149   free(sbrow);
    150   term->lines_len += 1;
    151   free(term->lines);
    152   term->lines = lines;
    153 
    154   return 1;
    155 }
    156 
    157 static int row_to_linenr(Term *term, int row) {
    158   return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX;
    159 }
    160 
    161 static int linenr_to_row(Term *term, int linenr) {
    162   return linenr - (int)term->sb_current - 1;
    163 }
    164 
    165 static void fetch_cell(Term *term, int row, int col, VTermScreenCell *cell) {
    166   if (row < 0) {
    167     ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
    168     if ((size_t)col < sbrow->cols) {
    169       *cell = sbrow->cells[col];
    170     } else {
    171       // fill the pointer with an empty cell
    172       VTermColor fg, bg;
    173       VTermState *state = vterm_obtain_state(term->vt);
    174       vterm_state_get_default_colors(state, &fg, &bg);
    175 
    176       *cell = (VTermScreenCell){.chars = {0}, .width = 1, .bg = bg};
    177     }
    178   } else {
    179     vterm_screen_get_cell(term->vts, (VTermPos){.row = row, .col = col}, cell);
    180   }
    181 }
    182 
    183 static char *get_row_directory(Term *term, int row) {
    184   if (row < 0) {
    185     ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
    186     return sbrow->info->directory;
    187     /* return term->dirs[0]; */
    188   } else {
    189     LineInfo *line = term->lines[row];
    190     return line ? line->directory : NULL;
    191   }
    192 }
    193 static LineInfo *get_lineinfo(Term *term, int row) {
    194   if (row < 0) {
    195     ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
    196     return sbrow->info;
    197     /* return term->dirs[0]; */
    198   } else {
    199     return term->lines[row];
    200   }
    201 }
    202 static bool is_eol(Term *term, int end_col, int row, int col) {
    203   /* This cell is EOL if this and every cell to the right is black */
    204   if (row >= 0) {
    205     VTermPos pos = {.row = row, .col = col};
    206     return vterm_screen_is_eol(term->vts, pos);
    207   }
    208 
    209   ScrollbackLine *sbrow = term->sb_buffer[-row - 1];
    210   int c;
    211   for (c = col; c < end_col && c < sbrow->cols;) {
    212     if (sbrow->cells[c].chars[0]) {
    213       return 0;
    214     }
    215     c += sbrow->cells[c].width;
    216   }
    217   return 1;
    218 }
    219 static int is_end_of_prompt(Term *term, int end_col, int row, int col) {
    220   LineInfo *info = get_lineinfo(term, row);
    221   if (info == NULL) {
    222     return 0;
    223   }
    224   if (info->prompt_col < 0) {
    225     return 0;
    226   }
    227   if (info->prompt_col == col) {
    228     return 1;
    229   }
    230   if (is_eol(term, end_col, row, col) && info->prompt_col >= col) {
    231     return 1;
    232   }
    233   return 0;
    234 }
    235 
    236 static void goto_col(Term *term, emacs_env *env, int row, int end_col) {
    237   int col = 0;
    238   size_t offset = 0;
    239   size_t beyond_eol = 0;
    240 
    241   int height;
    242   int width;
    243   vterm_get_size(term->vt, &height, &width);
    244 
    245   while (col < end_col) {
    246     VTermScreenCell cell;
    247     fetch_cell(term, row, col, &cell);
    248     if (cell.chars[0]) {
    249       if (cell.width > 1) {
    250         offset += cell.width - 1;
    251       }
    252     } else {
    253       if (is_eol(term, term->width, row, col)) {
    254         offset += cell.width;
    255         beyond_eol += cell.width;
    256       }
    257     }
    258     col += cell.width;
    259   }
    260 
    261   forward_char(env, env->make_integer(env, end_col - offset));
    262   emacs_value space = env->make_string(env, " ", 1);
    263   for (int i = 0; i < beyond_eol; i += 1)
    264     insert(env, space);
    265 }
    266 
    267 static void refresh_lines(Term *term, emacs_env *env, int start_row,
    268                           int end_row, int end_col) {
    269   if (end_row < start_row) {
    270     return;
    271   }
    272   int i, j;
    273 
    274 #define PUSH_BUFFER(c)                                                         \
    275   do {                                                                         \
    276     if (length == capacity) {                                                  \
    277       capacity += end_col * 4;                                                 \
    278       buffer = realloc(buffer, capacity * sizeof(char));                       \
    279     }                                                                          \
    280     buffer[length] = (c);                                                      \
    281     length++;                                                                  \
    282   } while (0)
    283 
    284   int capacity = ((end_row - start_row + 1) * end_col) * 4;
    285   int length = 0;
    286   char *buffer = malloc(capacity * sizeof(char));
    287   VTermScreenCell cell;
    288   VTermScreenCell lastCell;
    289   fetch_cell(term, start_row, 0, &lastCell);
    290 
    291   for (i = start_row; i < end_row; i++) {
    292 
    293     int newline = 0;
    294     int isprompt = 0;
    295     for (j = 0; j < end_col; j++) {
    296       fetch_cell(term, i, j, &cell);
    297       if (isprompt && length > 0) {
    298         emacs_value text = render_text(env, term, buffer, length, &lastCell);
    299         insert(env, render_prompt(env, text));
    300         length = 0;
    301       }
    302 
    303       isprompt = is_end_of_prompt(term, end_col, i, j);
    304       if (isprompt && length > 0) {
    305         insert(env, render_text(env, term, buffer, length, &lastCell));
    306         length = 0;
    307       }
    308 
    309       if (!compare_cells(&cell, &lastCell)) {
    310         emacs_value text = render_text(env, term, buffer, length, &lastCell);
    311         insert(env, text);
    312         length = 0;
    313       }
    314 
    315       lastCell = cell;
    316       if (cell.chars[0] == 0) {
    317         if (is_eol(term, end_col, i, j)) {
    318           /* This cell is EOL if this and every cell to the right is black */
    319           PUSH_BUFFER('\n');
    320           newline = 1;
    321           break;
    322         }
    323         PUSH_BUFFER(' ');
    324       } else {
    325         for (int k = 0; k < VTERM_MAX_CHARS_PER_CELL && cell.chars[k]; ++k) {
    326           unsigned char bytes[4];
    327           size_t count = codepoint_to_utf8(cell.chars[k], bytes);
    328           for (int l = 0; l < count; l++) {
    329             PUSH_BUFFER(bytes[l]);
    330           }
    331         }
    332       }
    333 
    334       if (cell.width > 1) {
    335         int w = cell.width - 1;
    336         j = j + w;
    337       }
    338     }
    339     if (isprompt && length > 0) {
    340       emacs_value text = render_text(env, term, buffer, length, &lastCell);
    341       insert(env, render_prompt(env, text));
    342       length = 0;
    343       isprompt = 0;
    344     }
    345 
    346     if (!newline) {
    347       emacs_value text = render_text(env, term, buffer, length, &lastCell);
    348       insert(env, text);
    349       length = 0;
    350       text = render_fake_newline(env, term);
    351       insert(env, text);
    352     }
    353   }
    354   emacs_value text = render_text(env, term, buffer, length, &lastCell);
    355   insert(env, text);
    356 
    357 #undef PUSH_BUFFER
    358   free(buffer);
    359 
    360   return;
    361 }
    362 // Refresh the screen (visible part of the buffer when the terminal is
    363 // focused) of a invalidated terminal
    364 static void refresh_screen(Term *term, emacs_env *env) {
    365   // Term height may have decreased before `invalid_end` reflects it.
    366   term->invalid_end = MIN(term->invalid_end, term->height);
    367 
    368   if (term->invalid_end >= term->invalid_start) {
    369     int startrow = -(term->height - term->invalid_start - term->linenum_added);
    370     /* startrow is negative,so we backward  -startrow lines from end of buffer
    371        then delete lines there.
    372      */
    373     goto_line(env, startrow);
    374     delete_lines(env, startrow, term->invalid_end - term->invalid_start, true);
    375     refresh_lines(term, env, term->invalid_start, term->invalid_end,
    376                   term->width);
    377 
    378     /* term->linenum_added is lines added  by window height increased */
    379     term->linenum += term->linenum_added;
    380     term->linenum_added = 0;
    381   }
    382 
    383   term->invalid_start = INT_MAX;
    384   term->invalid_end = -1;
    385 }
    386 
    387 static int term_resize(int rows, int cols, void *user_data) {
    388   /* can not use invalidate_terminal here */
    389   /* when the window height decreased, */
    390   /*  the value of term->invalid_end can't bigger than window height */
    391   Term *term = (Term *)user_data;
    392   term->invalid_start = 0;
    393   term->invalid_end = rows;
    394 
    395   /* if rows=term->lines_len, that means term_sb_pop already resize term->lines
    396    */
    397   /* if rows<term->lines_len, term_sb_push would resize term->lines there */
    398   /* we noly need to take care of rows>term->height */
    399 
    400   if (rows > term->height) {
    401     if (rows > term->lines_len) {
    402       LineInfo **infos = term->lines;
    403       term->lines = malloc(sizeof(LineInfo *) * rows);
    404       memmove(term->lines, infos, sizeof(infos[0]) * term->lines_len);
    405 
    406       LineInfo *lastline = term->lines[term->lines_len - 1];
    407       for (int i = term->lines_len; i < rows; i++) {
    408         if (lastline != NULL) {
    409           LineInfo *line = alloc_lineinfo();
    410           if (lastline->directory != NULL) {
    411             line->directory =
    412                 malloc(1 + strlen(term->lines[term->lines_len - 1]->directory));
    413             strcpy(line->directory,
    414                    term->lines[term->lines_len - 1]->directory);
    415           }
    416           term->lines[i] = line;
    417         } else {
    418           term->lines[i] = NULL;
    419         }
    420       }
    421       term->lines_len = rows;
    422       free(infos);
    423     }
    424   }
    425 
    426   term->width = cols;
    427   term->height = rows;
    428 
    429   invalidate_terminal(term, -1, -1);
    430   term->resizing = false;
    431 
    432   return 1;
    433 }
    434 
    435 // Refresh the scrollback of an invalidated terminal.
    436 static void refresh_scrollback(Term *term, emacs_env *env) {
    437   int max_line_count = (int)term->sb_current + term->height;
    438   int del_cnt = 0;
    439   if (term->sb_pending > 0) {
    440     // This means that either the window height has decreased or the screen
    441     // became full and libvterm had to push all rows up. Convert the first
    442     // pending scrollback row into a string and append it just above the visible
    443     // section of the buffer
    444 
    445     del_cnt = term->linenum - term->height - (int)term->sb_size +
    446               term->sb_pending - term->sb_pending_by_height_decr;
    447     if (del_cnt > 0) {
    448       delete_lines(env, 1, del_cnt, true);
    449       term->linenum -= del_cnt;
    450     }
    451 
    452     term->linenum += term->sb_pending;
    453     del_cnt = term->linenum - max_line_count; /* extra lines at the bottom */
    454     /* buf_index is negative,so we move to end of buffer,then backward
    455        -buf_index lines. goto lines backward is effectively when
    456        vterm-max-scrollback is a large number.
    457      */
    458     int buf_index = -(term->height + del_cnt);
    459     goto_line(env, buf_index);
    460     refresh_lines(term, env, -term->sb_pending, 0, term->width);
    461 
    462     term->sb_pending = 0;
    463   }
    464 
    465   // Remove extra lines at the bottom
    466   del_cnt = term->linenum - max_line_count;
    467   if (del_cnt > 0) {
    468     term->linenum -= del_cnt;
    469     /* -del_cnt is negative,so we delete_lines from end of buffer.
    470        this line means: delete del_cnt count of lines at end of buffer.
    471      */
    472     delete_lines(env, -del_cnt, del_cnt, true);
    473   }
    474 
    475   term->sb_pending_by_height_decr = 0;
    476   term->height_resize = 0;
    477 }
    478 
    479 static void adjust_topline(Term *term, emacs_env *env) {
    480   VTermState *state = vterm_obtain_state(term->vt);
    481   VTermPos pos;
    482   vterm_state_get_cursorpos(state, &pos);
    483 
    484   /* pos.row-term->height is negative,so we backward term->height-pos.row
    485    * lines from end of buffer
    486    */
    487 
    488   goto_line(env, pos.row - term->height);
    489   goto_col(term, env, pos.row, pos.col);
    490 
    491   emacs_value windows = get_buffer_window_list(env);
    492   emacs_value swindow = selected_window(env);
    493   int winnum = env->extract_integer(env, length(env, windows));
    494   for (int i = 0; i < winnum; i++) {
    495     emacs_value window = nth(env, i, windows);
    496     if (eq(env, window, swindow)) {
    497       int win_body_height =
    498           env->extract_integer(env, window_body_height(env, window));
    499 
    500       /* recenter:If ARG is negative, it counts up from the bottom of the
    501        * window.  (ARG should be less than the height of the window ) */
    502       if (term->height - pos.row <= win_body_height) {
    503         recenter(env, env->make_integer(env, pos.row - term->height));
    504       } else {
    505         recenter(env, env->make_integer(env, pos.row));
    506       }
    507     } else {
    508       if (env->is_not_nil(env, window)) {
    509         set_window_point(env, window, point(env));
    510       }
    511     }
    512   }
    513 }
    514 
    515 static void invalidate_terminal(Term *term, int start_row, int end_row) {
    516   if (start_row != -1 && end_row != -1) {
    517     term->invalid_start = MIN(term->invalid_start, start_row);
    518     term->invalid_end = MAX(term->invalid_end, end_row);
    519   }
    520   term->is_invalidated = true;
    521 }
    522 
    523 static int term_damage(VTermRect rect, void *data) {
    524   invalidate_terminal(data, rect.start_row, rect.end_row);
    525   return 1;
    526 }
    527 
    528 static int term_moverect(VTermRect dest, VTermRect src, void *data) {
    529   invalidate_terminal(data, MIN(dest.start_row, src.start_row),
    530                       MAX(dest.end_row, src.end_row));
    531   return 1;
    532 }
    533 
    534 static int term_movecursor(VTermPos new, VTermPos old, int visible,
    535                            void *data) {
    536   Term *term = data;
    537   term->cursor.row = new.row;
    538   term->cursor.col = new.col;
    539   invalidate_terminal(term, old.row, old.row + 1);
    540   invalidate_terminal(term, new.row, new.row + 1);
    541 
    542   return 1;
    543 }
    544 
    545 static void term_redraw_cursor(Term *term, emacs_env *env) {
    546   if (term->cursor.cursor_blink_changed) {
    547     term->cursor.cursor_blink_changed = false;
    548     set_cursor_blink(env, term->cursor.cursor_blink);
    549   }
    550 
    551   if (term->cursor.cursor_type_changed) {
    552     term->cursor.cursor_type_changed = false;
    553 
    554     if (!term->cursor.cursor_visible) {
    555       set_cursor_type(env, Qnil);
    556       return;
    557     }
    558 
    559     switch (term->cursor.cursor_type) {
    560     case VTERM_PROP_CURSORSHAPE_BLOCK:
    561       set_cursor_type(env, Qbox);
    562       break;
    563     case VTERM_PROP_CURSORSHAPE_UNDERLINE:
    564       set_cursor_type(env, Qhbar);
    565       break;
    566     case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
    567       set_cursor_type(env, Qbar);
    568       break;
    569     default:
    570       set_cursor_type(env, Qt);
    571       break;
    572     }
    573   }
    574 }
    575 
    576 static void term_redraw(Term *term, emacs_env *env) {
    577   term_redraw_cursor(term, env);
    578 
    579   if (term->is_invalidated) {
    580     int oldlinenum = term->linenum;
    581     refresh_scrollback(term, env);
    582     refresh_screen(term, env);
    583     term->linenum_added = term->linenum - oldlinenum;
    584     adjust_topline(term, env);
    585     term->linenum_added = 0;
    586   }
    587 
    588   if (term->title_changed) {
    589     set_title(env, env->make_string(env, term->title, strlen(term->title)));
    590     term->title_changed = false;
    591   }
    592 
    593   if (term->directory_changed) {
    594     set_directory(
    595         env, env->make_string(env, term->directory, strlen(term->directory)));
    596     term->directory_changed = false;
    597   }
    598 
    599   while (term->elisp_code_first) {
    600     ElispCodeListNode *node = term->elisp_code_first;
    601     term->elisp_code_first = node->next;
    602     emacs_value elisp_code = env->make_string(env, node->code, node->code_len);
    603     vterm_eval(env, elisp_code);
    604 
    605     free(node->code);
    606     free(node);
    607   }
    608   term->elisp_code_p_insert = &term->elisp_code_first;
    609 
    610   if (term->selection_data) {
    611     emacs_value selection_target = env->make_string(
    612         env, &term->selection_target[0], strlen(&term->selection_target[0]));
    613     emacs_value selection_data = env->make_string(env, term->selection_data,
    614                                                   strlen(term->selection_data));
    615     vterm_selection(env, selection_target, selection_data);
    616     free(term->selection_data);
    617     term->selection_data = NULL;
    618   }
    619 
    620   term->is_invalidated = false;
    621 }
    622 
    623 static VTermScreenCallbacks vterm_screen_callbacks = {
    624     .damage = term_damage,
    625     .moverect = term_moverect,
    626     .movecursor = term_movecursor,
    627     .settermprop = term_settermprop,
    628     .resize = term_resize,
    629     .sb_pushline = term_sb_push,
    630     .sb_popline = term_sb_pop,
    631 };
    632 
    633 static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b) {
    634   bool equal = true;
    635   equal = equal && vterm_color_is_equal(&a->fg, &b->fg);
    636   equal = equal && vterm_color_is_equal(&a->bg, &b->bg);
    637   equal = equal && (a->attrs.bold == b->attrs.bold);
    638   equal = equal && (a->attrs.underline == b->attrs.underline);
    639   equal = equal && (a->attrs.italic == b->attrs.italic);
    640   equal = equal && (a->attrs.reverse == b->attrs.reverse);
    641   equal = equal && (a->attrs.strike == b->attrs.strike);
    642   return equal;
    643 }
    644 
    645 static bool is_key(unsigned char *key, size_t len, char *key_description) {
    646   return (len == strlen(key_description) &&
    647           memcmp(key, key_description, len) == 0);
    648 }
    649 
    650 /* str1=concat(str1,str2,str2_len,true); */
    651 /* str1 can be NULL */
    652 static char *concat(char *str1, const char *str2, size_t str2_len,
    653                     bool free_str1) {
    654   if (str1 == NULL) {
    655     str1 = malloc(str2_len + 1);
    656     memcpy(str1, str2, str2_len);
    657     str1[str2_len] = '\0';
    658     return str1;
    659   }
    660   size_t str1_len = strlen(str1);
    661   char *buf = malloc(str1_len + str2_len + 1);
    662   memcpy(buf, str1, str1_len);
    663   memcpy(&buf[str1_len], str2, str2_len);
    664   buf[str1_len + str2_len] = '\0';
    665   if (free_str1) {
    666     free(str1);
    667   }
    668   return buf;
    669 }
    670 static void term_set_title(Term *term, const char *title, size_t len,
    671                            bool initial, bool final) {
    672   if (term->title && initial) {
    673     free(term->title);
    674     term->title = NULL;
    675     term->title_changed = false;
    676   }
    677   term->title = concat(term->title, title, len, true);
    678   if (final) {
    679     term->title_changed = true;
    680   }
    681   return;
    682 }
    683 
    684 static int term_settermprop(VTermProp prop, VTermValue *val, void *user_data) {
    685   Term *term = (Term *)user_data;
    686   switch (prop) {
    687   case VTERM_PROP_CURSORVISIBLE:
    688     invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
    689     term->cursor.cursor_visible = val->boolean;
    690     term->cursor.cursor_type_changed = true;
    691     break;
    692   case VTERM_PROP_CURSORBLINK:
    693     if (term->ignore_blink_cursor)
    694       break;
    695     invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
    696     term->cursor.cursor_blink = val->boolean;
    697     term->cursor.cursor_blink_changed = true;
    698     break;
    699   case VTERM_PROP_CURSORSHAPE:
    700     invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
    701     term->cursor.cursor_type = val->number;
    702     term->cursor.cursor_type_changed = true;
    703     break;
    704   case VTERM_PROP_TITLE:
    705 #ifdef VTermStringFragmentNotExists
    706     term_set_title(term, val->string, strlen(val->string), true, true);
    707 #else
    708     term_set_title(term, val->string.str, val->string.len, val->string.initial,
    709                    val->string.final);
    710 #endif
    711     break;
    712   case VTERM_PROP_ALTSCREEN:
    713     invalidate_terminal(term, 0, term->height);
    714     break;
    715   default:
    716     return 0;
    717   }
    718 
    719   return 1;
    720 }
    721 
    722 static emacs_value render_text(emacs_env *env, Term *term, char *buffer,
    723                                int len, VTermScreenCell *cell) {
    724   emacs_value text;
    725   if (len == 0) {
    726     text = env->make_string(env, "", 0);
    727     return text;
    728   } else {
    729     text = env->make_string(env, buffer, len);
    730   }
    731 
    732   emacs_value fg = cell_rgb_color(env, term, cell, true);
    733   emacs_value bg = cell_rgb_color(env, term, cell, false);
    734   /* With vterm-disable-bold-font, vterm-disable-underline,
    735    * vterm-disable-inverse-video, users can disable some text properties.
    736    * Here, we check whether the text would require adding such properties.
    737    * In case it does, and the user does not disable the attribute, we later
    738    * append the property to the list props.  If the text does not require
    739    * such property, or the user disable it, we set the variable to nil.
    740    * Properties that are marked as nil are not added to the text. */
    741   emacs_value bold =
    742       cell->attrs.bold && !term->disable_bold_font ? Qbold : Qnil;
    743   emacs_value underline =
    744       cell->attrs.underline && !term->disable_underline ? Qt : Qnil;
    745   emacs_value italic = cell->attrs.italic ? Qitalic : Qnil;
    746   emacs_value reverse =
    747       cell->attrs.reverse && !term->disable_inverse_video ? Qt : Qnil;
    748   emacs_value strike = cell->attrs.strike ? Qt : Qnil;
    749 
    750   // TODO: Blink, font, dwl, dhl is missing
    751   int emacs_major_version =
    752       env->extract_integer(env, symbol_value(env, Qemacs_major_version));
    753   emacs_value properties;
    754   emacs_value props[64];
    755   int props_len = 0;
    756   if (env->is_not_nil(env, fg))
    757     props[props_len++] = Qforeground, props[props_len++] = fg;
    758   if (env->is_not_nil(env, bg))
    759     props[props_len++] = Qbackground, props[props_len++] = bg;
    760   if (bold != Qnil)
    761     props[props_len++] = Qweight, props[props_len++] = bold;
    762   if (underline != Qnil)
    763     props[props_len++] = Qunderline, props[props_len++] = underline;
    764   if (italic != Qnil)
    765     props[props_len++] = Qslant, props[props_len++] = italic;
    766   if (reverse != Qnil)
    767     props[props_len++] = Qreverse, props[props_len++] = reverse;
    768   if (strike != Qnil)
    769     props[props_len++] = Qstrike, props[props_len++] = strike;
    770   if (emacs_major_version >= 27)
    771     props[props_len++] = Qextend, props[props_len++] = Qt;
    772 
    773   properties = list(env, props, props_len);
    774 
    775   if (props_len)
    776     put_text_property(env, text, Qface, properties);
    777 
    778   return text;
    779 }
    780 static emacs_value render_prompt(emacs_env *env, emacs_value text) {
    781 
    782   emacs_value properties;
    783 
    784   properties =
    785       list(env, (emacs_value[]){Qvterm_prompt, Qt, Qrear_nonsticky, Qt}, 4);
    786 
    787   add_text_properties(env, text, properties);
    788 
    789   return text;
    790 }
    791 
    792 static emacs_value render_fake_newline(emacs_env *env, Term *term) {
    793 
    794   emacs_value text;
    795   text = env->make_string(env, "\n", 1);
    796 
    797   emacs_value properties;
    798 
    799   properties =
    800       list(env, (emacs_value[]){Qvterm_line_wrap, Qt, Qrear_nonsticky, Qt}, 4);
    801 
    802   add_text_properties(env, text, properties);
    803 
    804   return text;
    805 }
    806 
    807 static emacs_value cell_rgb_color(emacs_env *env, Term *term,
    808                                   VTermScreenCell *cell, bool is_foreground) {
    809   VTermColor *color = is_foreground ? &cell->fg : &cell->bg;
    810 
    811   /** NOTE: -10 is used as index offset for special indexes,
    812    * see C-h f vterm--get-color RET
    813    */
    814   if (VTERM_COLOR_IS_DEFAULT_FG(color)) {
    815     return vterm_get_color(env, -1 + (cell->attrs.underline ? -10 : 0));
    816   }
    817   if (VTERM_COLOR_IS_DEFAULT_BG(color)) {
    818     return vterm_get_color(env, -2 + (cell->attrs.reverse ? -10 : 0));
    819   }
    820   if (VTERM_COLOR_IS_INDEXED(color)) {
    821     if (color->indexed.idx < 16) {
    822       return vterm_get_color(env, color->indexed.idx);
    823     } else {
    824       VTermState *state = vterm_obtain_state(term->vt);
    825       vterm_state_get_palette_color(state, color->indexed.idx, color);
    826     }
    827   } else if (VTERM_COLOR_IS_RGB(color)) {
    828     /* do nothing just use the argument color directly */
    829   }
    830 
    831   char buffer[8];
    832   snprintf(buffer, 8, "#%02X%02X%02X", color->rgb.red, color->rgb.green,
    833            color->rgb.blue);
    834   return env->make_string(env, buffer, 7);
    835 }
    836 
    837 static void term_flush_output(Term *term, emacs_env *env) {
    838   size_t len = vterm_output_get_buffer_current(term->vt);
    839   if (len) {
    840     char buffer[len];
    841     len = vterm_output_read(term->vt, buffer, len);
    842 
    843     emacs_value output = env->make_string(env, buffer, len);
    844     env->funcall(env, Fvterm_flush_output, 1, (emacs_value[]){output});
    845   }
    846 }
    847 
    848 static void term_clear_scrollback(Term *term, emacs_env *env) {
    849   vterm_screen_flush_damage(term->vts);
    850   term_redraw(term, env);
    851   if (term->sb_pending > 0) { // Pending rows must be processed first.
    852     return;
    853   }
    854   for (int i = 0; i < term->sb_current; i++) {
    855     if (term->sb_buffer[i]->info != NULL) {
    856       free_lineinfo(term->sb_buffer[i]->info);
    857       term->sb_buffer[i]->info = NULL;
    858     }
    859     free(term->sb_buffer[i]);
    860   }
    861   free(term->sb_buffer);
    862   term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size);
    863   delete_lines(env, 1, term->sb_current, true);
    864   term->linenum -= term->sb_current;
    865   term->sb_current = 0;
    866 }
    867 static void term_process_key(Term *term, emacs_env *env, unsigned char *key,
    868                              size_t len, VTermModifier modifier) {
    869   if (is_key(key, len, "<clear_scrollback>")) {
    870     term_clear_scrollback(term, env);
    871   } else if (is_key(key, len, "<start>")) {
    872     tcflow(term->pty_fd, TCOON);
    873   } else if (is_key(key, len, "<stop>")) {
    874     tcflow(term->pty_fd, TCOOFF);
    875   } else if (is_key(key, len, "<start_paste>")) {
    876     vterm_keyboard_start_paste(term->vt);
    877   } else if (is_key(key, len, "<end_paste>")) {
    878     vterm_keyboard_end_paste(term->vt);
    879   } else if (is_key(key, len, "<tab>")) {
    880     vterm_keyboard_key(term->vt, VTERM_KEY_TAB, modifier);
    881   } else if (is_key(key, len, "<backtab>") ||
    882              is_key(key, len, "<iso-lefttab>")) {
    883     vterm_keyboard_key(term->vt, VTERM_KEY_TAB, VTERM_MOD_SHIFT);
    884   } else if (is_key(key, len, "<backspace>")) {
    885     vterm_keyboard_key(term->vt, VTERM_KEY_BACKSPACE, modifier);
    886   } else if (is_key(key, len, "<escape>")) {
    887     vterm_keyboard_key(term->vt, VTERM_KEY_ESCAPE, modifier);
    888   } else if (is_key(key, len, "<up>")) {
    889     vterm_keyboard_key(term->vt, VTERM_KEY_UP, modifier);
    890   } else if (is_key(key, len, "<down>")) {
    891     vterm_keyboard_key(term->vt, VTERM_KEY_DOWN, modifier);
    892   } else if (is_key(key, len, "<left>")) {
    893     vterm_keyboard_key(term->vt, VTERM_KEY_LEFT, modifier);
    894   } else if (is_key(key, len, "<right>")) {
    895     vterm_keyboard_key(term->vt, VTERM_KEY_RIGHT, modifier);
    896   } else if (is_key(key, len, "<insert>")) {
    897     vterm_keyboard_key(term->vt, VTERM_KEY_INS, modifier);
    898   } else if (is_key(key, len, "<delete>")) {
    899     vterm_keyboard_key(term->vt, VTERM_KEY_DEL, modifier);
    900   } else if (is_key(key, len, "<home>")) {
    901     vterm_keyboard_key(term->vt, VTERM_KEY_HOME, modifier);
    902   } else if (is_key(key, len, "<end>")) {
    903     vterm_keyboard_key(term->vt, VTERM_KEY_END, modifier);
    904   } else if (is_key(key, len, "<prior>")) {
    905     vterm_keyboard_key(term->vt, VTERM_KEY_PAGEUP, modifier);
    906   } else if (is_key(key, len, "<next>")) {
    907     vterm_keyboard_key(term->vt, VTERM_KEY_PAGEDOWN, modifier);
    908   } else if (is_key(key, len, "<f0>")) {
    909     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(0), modifier);
    910   } else if (is_key(key, len, "<f1>")) {
    911     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(1), modifier);
    912   } else if (is_key(key, len, "<f2>")) {
    913     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(2), modifier);
    914   } else if (is_key(key, len, "<f3>")) {
    915     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(3), modifier);
    916   } else if (is_key(key, len, "<f4>")) {
    917     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(4), modifier);
    918   } else if (is_key(key, len, "<f5>")) {
    919     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(5), modifier);
    920   } else if (is_key(key, len, "<f6>")) {
    921     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(6), modifier);
    922   } else if (is_key(key, len, "<f7>")) {
    923     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(7), modifier);
    924   } else if (is_key(key, len, "<f8>")) {
    925     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(8), modifier);
    926   } else if (is_key(key, len, "<f9>")) {
    927     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(9), modifier);
    928   } else if (is_key(key, len, "<f10>")) {
    929     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(10), modifier);
    930   } else if (is_key(key, len, "<f11>")) {
    931     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(11), modifier);
    932   } else if (is_key(key, len, "<f12>")) {
    933     vterm_keyboard_key(term->vt, VTERM_KEY_FUNCTION(12), modifier);
    934   } else if (is_key(key, len, "<kp-0>")) {
    935     vterm_keyboard_key(term->vt, VTERM_KEY_KP_0, modifier);
    936   } else if (is_key(key, len, "<kp-1>")) {
    937     vterm_keyboard_key(term->vt, VTERM_KEY_KP_1, modifier);
    938   } else if (is_key(key, len, "<kp-2>")) {
    939     vterm_keyboard_key(term->vt, VTERM_KEY_KP_2, modifier);
    940   } else if (is_key(key, len, "<kp-3>")) {
    941     vterm_keyboard_key(term->vt, VTERM_KEY_KP_3, modifier);
    942   } else if (is_key(key, len, "<kp-4>")) {
    943     vterm_keyboard_key(term->vt, VTERM_KEY_KP_4, modifier);
    944   } else if (is_key(key, len, "<kp-5>")) {
    945     vterm_keyboard_key(term->vt, VTERM_KEY_KP_5, modifier);
    946   } else if (is_key(key, len, "<kp-6>")) {
    947     vterm_keyboard_key(term->vt, VTERM_KEY_KP_6, modifier);
    948   } else if (is_key(key, len, "<kp-7>")) {
    949     vterm_keyboard_key(term->vt, VTERM_KEY_KP_7, modifier);
    950   } else if (is_key(key, len, "<kp-8>")) {
    951     vterm_keyboard_key(term->vt, VTERM_KEY_KP_8, modifier);
    952   } else if (is_key(key, len, "<kp-9>")) {
    953     vterm_keyboard_key(term->vt, VTERM_KEY_KP_9, modifier);
    954   } else if (is_key(key, len, "<kp-add>")) {
    955     vterm_keyboard_key(term->vt, VTERM_KEY_KP_PLUS, modifier);
    956   } else if (is_key(key, len, "<kp-subtract>")) {
    957     vterm_keyboard_key(term->vt, VTERM_KEY_KP_MINUS, modifier);
    958   } else if (is_key(key, len, "<kp-multiply>")) {
    959     vterm_keyboard_key(term->vt, VTERM_KEY_KP_MULT, modifier);
    960   } else if (is_key(key, len, "<kp-divide>")) {
    961     vterm_keyboard_key(term->vt, VTERM_KEY_KP_DIVIDE, modifier);
    962   } else if (is_key(key, len, "<kp-equal>")) {
    963     vterm_keyboard_key(term->vt, VTERM_KEY_KP_EQUAL, modifier);
    964   } else if (is_key(key, len, "<kp-decimal>")) {
    965     vterm_keyboard_key(term->vt, VTERM_KEY_KP_PERIOD, modifier);
    966   } else if (is_key(key, len, "<kp-separator>")) {
    967     vterm_keyboard_key(term->vt, VTERM_KEY_KP_COMMA, modifier);
    968   } else if (is_key(key, len, "<kp-enter>")) {
    969     vterm_keyboard_key(term->vt, VTERM_KEY_KP_ENTER, modifier);
    970   } else if (is_key(key, len, "j") && (modifier == VTERM_MOD_CTRL)) {
    971     vterm_keyboard_unichar(term->vt, '\n', 0);
    972   } else if (is_key(key, len, "SPC")) {
    973     vterm_keyboard_unichar(term->vt, ' ', modifier);
    974   } else if (len <= 4) {
    975     uint32_t codepoint;
    976     if (utf8_to_codepoint(key, len, &codepoint)) {
    977       vterm_keyboard_unichar(term->vt, codepoint, modifier);
    978     }
    979   }
    980 }
    981 
    982 void term_finalize(void *object) {
    983   Term *term = (Term *)object;
    984   for (int i = 0; i < term->sb_current; i++) {
    985     if (term->sb_buffer[i]->info != NULL) {
    986       free_lineinfo(term->sb_buffer[i]->info);
    987       term->sb_buffer[i]->info = NULL;
    988     }
    989     free(term->sb_buffer[i]);
    990   }
    991   if (term->title) {
    992     free(term->title);
    993     term->title = NULL;
    994   }
    995 
    996   if (term->directory) {
    997     free(term->directory);
    998     term->directory = NULL;
    999   }
   1000 
   1001   while (term->elisp_code_first) {
   1002     ElispCodeListNode *node = term->elisp_code_first;
   1003     term->elisp_code_first = node->next;
   1004     free(node->code);
   1005     free(node);
   1006   }
   1007   term->elisp_code_p_insert = &term->elisp_code_first;
   1008 
   1009   if (term->cmd_buffer) {
   1010     free(term->cmd_buffer);
   1011     term->cmd_buffer = NULL;
   1012   }
   1013   if (term->selection_data) {
   1014     free(term->selection_data);
   1015     term->selection_data = NULL;
   1016   }
   1017 
   1018   for (int i = 0; i < term->lines_len; i++) {
   1019     if (term->lines[i] != NULL) {
   1020       free_lineinfo(term->lines[i]);
   1021       term->lines[i] = NULL;
   1022     }
   1023   }
   1024 
   1025   if (term->pty_fd > 0) {
   1026     close(term->pty_fd);
   1027   }
   1028 
   1029   free(term->sb_buffer);
   1030   free(term->lines);
   1031   vterm_free(term->vt);
   1032   free(term);
   1033 }
   1034 
   1035 static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) {
   1036   if (subCmd == 'A') {
   1037     /* "51;A" sets the current directory */
   1038     /* "51;A" has also the role of identifying the end of the prompt */
   1039     if (term->directory != NULL) {
   1040       free(term->directory);
   1041       term->directory = NULL;
   1042     }
   1043     term->directory = malloc(strlen(buffer) + 1);
   1044     strcpy(term->directory, buffer);
   1045     term->directory_changed = true;
   1046 
   1047     for (int i = term->cursor.row; i < term->lines_len; i++) {
   1048       if (term->lines[i] == NULL) {
   1049         term->lines[i] = alloc_lineinfo();
   1050       }
   1051 
   1052       if (term->lines[i]->directory != NULL) {
   1053         free(term->lines[i]->directory);
   1054       }
   1055       term->lines[i]->directory = malloc(strlen(buffer) + 1);
   1056       strcpy(term->lines[i]->directory, buffer);
   1057       if (i == term->cursor.row) {
   1058         term->lines[i]->prompt_col = term->cursor.col;
   1059       } else {
   1060         term->lines[i]->prompt_col = -1;
   1061       }
   1062     }
   1063     return 1;
   1064   } else if (subCmd == 'E') {
   1065     /* "51;E" executes elisp code */
   1066     /* The elisp code is executed in term_redraw */
   1067     ElispCodeListNode *node = malloc(sizeof(ElispCodeListNode));
   1068     node->code_len = strlen(buffer);
   1069     node->code = malloc(node->code_len + 1);
   1070     strcpy(node->code, buffer);
   1071     node->next = NULL;
   1072 
   1073     *(term->elisp_code_p_insert) = node;
   1074     term->elisp_code_p_insert = &(node->next);
   1075     return 1;
   1076   }
   1077   return 0;
   1078 }
   1079 static int handle_osc_cmd_52(Term *term, char *buffer) {
   1080   /* OSC 52 ; Pc ; Pd BEL */
   1081   /* Manipulate Selection Data  */
   1082   /* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */
   1083   /* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */
   1084 
   1085   for (int i = 0; i < SELECTION_TARGET_MAX; i++) { /* reset Pc */
   1086     term->selection_target[i] = 0;
   1087   }
   1088   int selection_target_idx = 0;
   1089   size_t cmdlen = strlen(buffer);
   1090 
   1091   for (int i = 0; i < cmdlen; i++) {
   1092     /* OSC 52 ; Pc ; Pd BEL */
   1093     if (buffer[i] == ';') { /* find the second ";" */
   1094       term->selection_data = malloc(cmdlen - i);
   1095       strcpy(term->selection_data, &buffer[i + 1]);
   1096       break;
   1097     }
   1098     if (selection_target_idx < SELECTION_TARGET_MAX) {
   1099       /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
   1100       /* for clipboard, primary, secondary, select, or cut buffers 0 through 7
   1101        * respectively */
   1102       term->selection_target[selection_target_idx] = buffer[i];
   1103       selection_target_idx++;
   1104     } else { /* len of Pc should not >12 just ignore this cmd,am I wrong? */
   1105       return 0;
   1106     }
   1107   }
   1108   return 1;
   1109 }
   1110 static int handle_osc_cmd(Term *term, int cmd, char *buffer) {
   1111   if (cmd == 51) {
   1112     char subCmd = '0';
   1113     if (strlen(buffer) == 0) {
   1114       return 0;
   1115     }
   1116     subCmd = buffer[0];
   1117     /* ++ skip the subcmd char */
   1118     return handle_osc_cmd_51(term, subCmd, ++buffer);
   1119   } else if (cmd == 52) {
   1120     return handle_osc_cmd_52(term, buffer);
   1121   }
   1122   return 0;
   1123 }
   1124 #ifdef VTermStringFragmentNotExists
   1125 static int osc_callback(const char *command, size_t cmdlen, void *user) {
   1126   Term *term = (Term *)user;
   1127   char buffer[cmdlen + 1];
   1128   buffer[cmdlen] = '\0';
   1129   memcpy(buffer, command, cmdlen);
   1130 
   1131   if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' && buffer[2] == ';' &&
   1132       buffer[3] == 'A') {
   1133     return handle_osc_cmd_51(term, 'A', &buffer[4]);
   1134   } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' &&
   1135              buffer[2] == ';' && buffer[3] == 'E') {
   1136     return handle_osc_cmd_51(term, 'E', &buffer[4]);
   1137   } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '2' &&
   1138              buffer[2] == ';') {
   1139     /* OSC 52 ; Pc ; Pd BEL */
   1140     return handle_osc_cmd_52(term, &buffer[3]);
   1141   }
   1142   return 0;
   1143 }
   1144 static VTermParserCallbacks parser_callbacks = {
   1145     .text = NULL,
   1146     .control = NULL,
   1147     .escape = NULL,
   1148     .csi = NULL,
   1149     .osc = &osc_callback,
   1150     .dcs = NULL,
   1151 };
   1152 #else
   1153 
   1154 static int osc_callback(int cmd, VTermStringFragment frag, void *user) {
   1155   /* osc_callback (OSC = Operating System Command) */
   1156 
   1157   /* We interpret escape codes that start with "51;" */
   1158   /* "51;A" sets the current directory */
   1159   /* "51;A" has also the role of identifying the end of the prompt */
   1160   /* "51;E" executes elisp code */
   1161   /* The elisp code is executed in term_redraw */
   1162 
   1163   /* "52;[cpqs01234567];data" Manipulate Selection Data */
   1164   /* I think libvterm has bug ,sometimes when the data is long enough ,the final
   1165    * fragment is missed */
   1166   /* printf "\033]52;c;$(printf "%s" $(ruby -e 'print "x"*999999')|base64)\a"
   1167    */
   1168 
   1169   Term *term = (Term *)user;
   1170 
   1171   if (frag.initial) {
   1172     /* drop old fragment,because this is a initial fragment */
   1173     if (term->cmd_buffer) {
   1174       free(term->cmd_buffer);
   1175       term->cmd_buffer = NULL;
   1176     }
   1177   }
   1178 
   1179   if (!frag.initial && !frag.final && frag.len == 0) {
   1180     return 0;
   1181   }
   1182 
   1183   term->cmd_buffer = concat(term->cmd_buffer, frag.str, frag.len, true);
   1184   if (frag.final) {
   1185     handle_osc_cmd(term, cmd, term->cmd_buffer);
   1186     free(term->cmd_buffer);
   1187     term->cmd_buffer = NULL;
   1188   }
   1189   return 0;
   1190 }
   1191 static VTermStateFallbacks parser_callbacks = {
   1192     .control = NULL,
   1193     .csi = NULL,
   1194     .osc = &osc_callback,
   1195     .dcs = NULL,
   1196 };
   1197 
   1198 #endif
   1199 
   1200 emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
   1201                        void *data) {
   1202   Term *term = malloc(sizeof(Term));
   1203 
   1204   int rows = env->extract_integer(env, args[0]);
   1205   int cols = env->extract_integer(env, args[1]);
   1206   int sb_size = env->extract_integer(env, args[2]);
   1207   int disable_bold_font = env->is_not_nil(env, args[3]);
   1208   int disable_underline = env->is_not_nil(env, args[4]);
   1209   int disable_inverse_video = env->is_not_nil(env, args[5]);
   1210   int ignore_blink_cursor = env->is_not_nil(env, args[6]);
   1211   int set_bold_hightbright = env->is_not_nil(env, args[7]);
   1212 
   1213   term->vt = vterm_new(rows, cols);
   1214   vterm_set_utf8(term->vt, 1);
   1215 
   1216   term->vts = vterm_obtain_screen(term->vt);
   1217 
   1218   VTermState *state = vterm_obtain_state(term->vt);
   1219   vterm_state_set_unrecognised_fallbacks(state, &parser_callbacks, term);
   1220   vterm_state_set_bold_highbright(state, set_bold_hightbright);
   1221 
   1222   vterm_screen_reset(term->vts, 1);
   1223   vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term);
   1224   vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL);
   1225   vterm_screen_enable_altscreen(term->vts, true);
   1226   term->sb_size = MIN(SB_MAX, sb_size);
   1227   term->sb_current = 0;
   1228   term->sb_pending = 0;
   1229   term->sb_pending_by_height_decr = 0;
   1230   term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size);
   1231   term->invalid_start = 0;
   1232   term->invalid_end = rows;
   1233   term->width = cols;
   1234   term->height = rows;
   1235   term->height_resize = 0;
   1236   term->disable_bold_font = disable_bold_font;
   1237   term->disable_underline = disable_underline;
   1238   term->disable_inverse_video = disable_inverse_video;
   1239   term->ignore_blink_cursor = ignore_blink_cursor;
   1240   emacs_value newline = env->make_string(env, "\n", 1);
   1241   for (int i = 0; i < term->height; i++) {
   1242     insert(env, newline);
   1243   }
   1244   term->linenum = term->height;
   1245   term->linenum_added = 0;
   1246   term->resizing = false;
   1247 
   1248   term->pty_fd = -1;
   1249 
   1250   term->title = NULL;
   1251   term->title_changed = false;
   1252 
   1253   term->cursor.row = 0;
   1254   term->cursor.col = 0;
   1255   term->cursor.cursor_type = -1;
   1256   term->cursor.cursor_visible = true;
   1257   term->cursor.cursor_type_changed = false;
   1258   term->cursor.cursor_blink = false;
   1259   term->cursor.cursor_blink_changed = false;
   1260   term->directory = NULL;
   1261   term->directory_changed = false;
   1262   term->elisp_code_first = NULL;
   1263   term->elisp_code_p_insert = &term->elisp_code_first;
   1264   term->selection_data = NULL;
   1265 
   1266   term->cmd_buffer = NULL;
   1267 
   1268   term->lines = malloc(sizeof(LineInfo *) * rows);
   1269   term->lines_len = rows;
   1270   for (int i = 0; i < rows; i++) {
   1271     term->lines[i] = NULL;
   1272   }
   1273 
   1274   return env->make_user_ptr(env, term_finalize, term);
   1275 }
   1276 
   1277 emacs_value Fvterm_update(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
   1278                           void *data) {
   1279   Term *term = env->get_user_ptr(env, args[0]);
   1280 
   1281   // Process keys
   1282   if (nargs > 1) {
   1283     ptrdiff_t len = string_bytes(env, args[1]);
   1284     unsigned char key[len];
   1285     env->copy_string_contents(env, args[1], (char *)key, &len);
   1286     VTermModifier modifier = VTERM_MOD_NONE;
   1287     if (nargs > 2 && env->is_not_nil(env, args[2]))
   1288       modifier = modifier | VTERM_MOD_SHIFT;
   1289     if (nargs > 3 && env->is_not_nil(env, args[3]))
   1290       modifier = modifier | VTERM_MOD_ALT;
   1291     if (nargs > 4 && env->is_not_nil(env, args[4]))
   1292       modifier = modifier | VTERM_MOD_CTRL;
   1293 
   1294     // Ignore the final zero byte
   1295     term_process_key(term, env, key, len - 1, modifier);
   1296   }
   1297 
   1298   // Flush output
   1299   term_flush_output(term, env);
   1300   if (term->is_invalidated) {
   1301     vterm_invalidate(env);
   1302   }
   1303 
   1304   return env->make_integer(env, 0);
   1305 }
   1306 
   1307 emacs_value Fvterm_redraw(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
   1308                           void *data) {
   1309   Term *term = env->get_user_ptr(env, args[0]);
   1310   term_redraw(term, env);
   1311   return env->make_integer(env, 0);
   1312 }
   1313 
   1314 emacs_value Fvterm_write_input(emacs_env *env, ptrdiff_t nargs,
   1315                                emacs_value args[], void *data) {
   1316   Term *term = env->get_user_ptr(env, args[0]);
   1317   ptrdiff_t len = string_bytes(env, args[1]);
   1318   char bytes[len];
   1319 
   1320   env->copy_string_contents(env, args[1], bytes, &len);
   1321 
   1322   vterm_input_write(term->vt, bytes, len);
   1323   vterm_screen_flush_damage(term->vts);
   1324 
   1325   return env->make_integer(env, 0);
   1326 }
   1327 
   1328 emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
   1329                             void *data) {
   1330   Term *term = env->get_user_ptr(env, args[0]);
   1331   int rows = env->extract_integer(env, args[1]);
   1332   int cols = env->extract_integer(env, args[2]);
   1333 
   1334   if (cols != term->width || rows != term->height) {
   1335     term->height_resize = rows - term->height;
   1336     if (rows > term->height) {
   1337       if (rows - term->height > term->sb_current) {
   1338         term->linenum_added = rows - term->height - term->sb_current;
   1339       }
   1340     }
   1341     term->resizing = true;
   1342     vterm_set_size(term->vt, rows, cols);
   1343     vterm_screen_flush_damage(term->vts);
   1344 
   1345     term_redraw(term, env);
   1346   }
   1347 
   1348   return Qnil;
   1349 }
   1350 
   1351 emacs_value Fvterm_set_pty_name(emacs_env *env, ptrdiff_t nargs,
   1352                                 emacs_value args[], void *data) {
   1353   Term *term = env->get_user_ptr(env, args[0]);
   1354 
   1355   if (nargs > 1) {
   1356     ptrdiff_t len = string_bytes(env, args[1]);
   1357     char filename[len];
   1358 
   1359     env->copy_string_contents(env, args[1], filename, &len);
   1360 
   1361     term->pty_fd = open(filename, O_RDONLY);
   1362   }
   1363   return Qnil;
   1364 }
   1365 emacs_value Fvterm_get_pwd(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
   1366                            void *data) {
   1367   Term *term = env->get_user_ptr(env, args[0]);
   1368   int linenum = env->extract_integer(env, args[1]);
   1369   int row = linenr_to_row(term, linenum);
   1370   char *dir = get_row_directory(term, row);
   1371 
   1372   return dir ? env->make_string(env, dir, strlen(dir)) : Qnil;
   1373 }
   1374 
   1375 emacs_value Fvterm_get_icrnl(emacs_env *env, ptrdiff_t nargs,
   1376                              emacs_value args[], void *data) {
   1377   Term *term = env->get_user_ptr(env, args[0]);
   1378 
   1379   if (term->pty_fd > 0) {
   1380     struct termios keys;
   1381     tcgetattr(term->pty_fd, &keys);
   1382 
   1383     if (keys.c_iflag & ICRNL)
   1384       return Qt;
   1385     else
   1386       return Qnil;
   1387   }
   1388   return Qnil;
   1389 }
   1390 
   1391 emacs_value Fvterm_reset_cursor_point(emacs_env *env, ptrdiff_t nargs,
   1392                                       emacs_value args[], void *data) {
   1393   Term *term = env->get_user_ptr(env, args[0]);
   1394   int line = row_to_linenr(term, term->cursor.row);
   1395   goto_line(env, line);
   1396   goto_col(term, env, term->cursor.row, term->cursor.col);
   1397   return point(env);
   1398 }
   1399 
   1400 int emacs_module_init(struct emacs_runtime *ert) {
   1401   emacs_env *env = ert->get_environment(ert);
   1402 
   1403   // Symbols;
   1404   Qt = env->make_global_ref(env, env->intern(env, "t"));
   1405   Qnil = env->make_global_ref(env, env->intern(env, "nil"));
   1406   Qnormal = env->make_global_ref(env, env->intern(env, "normal"));
   1407   Qbold = env->make_global_ref(env, env->intern(env, "bold"));
   1408   Qitalic = env->make_global_ref(env, env->intern(env, "italic"));
   1409   Qforeground = env->make_global_ref(env, env->intern(env, ":foreground"));
   1410   Qbackground = env->make_global_ref(env, env->intern(env, ":background"));
   1411   Qweight = env->make_global_ref(env, env->intern(env, ":weight"));
   1412   Qunderline = env->make_global_ref(env, env->intern(env, ":underline"));
   1413   Qslant = env->make_global_ref(env, env->intern(env, ":slant"));
   1414   Qreverse = env->make_global_ref(env, env->intern(env, ":inverse-video"));
   1415   Qstrike = env->make_global_ref(env, env->intern(env, ":strike-through"));
   1416   Qextend = env->make_global_ref(env, env->intern(env, ":extend"));
   1417   Qemacs_major_version =
   1418       env->make_global_ref(env, env->intern(env, "emacs-major-version"));
   1419   Qvterm_line_wrap =
   1420       env->make_global_ref(env, env->intern(env, "vterm-line-wrap"));
   1421   Qrear_nonsticky =
   1422       env->make_global_ref(env, env->intern(env, "rear-nonsticky"));
   1423   Qvterm_prompt = env->make_global_ref(env, env->intern(env, "vterm-prompt"));
   1424 
   1425   Qface = env->make_global_ref(env, env->intern(env, "font-lock-face"));
   1426   Qbox = env->make_global_ref(env, env->intern(env, "box"));
   1427   Qbar = env->make_global_ref(env, env->intern(env, "bar"));
   1428   Qhbar = env->make_global_ref(env, env->intern(env, "hbar"));
   1429   Qcursor_type = env->make_global_ref(env, env->intern(env, "cursor-type"));
   1430 
   1431   // Functions
   1432   Fblink_cursor_mode =
   1433       env->make_global_ref(env, env->intern(env, "blink-cursor-mode"));
   1434   Fsymbol_value = env->make_global_ref(env, env->intern(env, "symbol-value"));
   1435   Flength = env->make_global_ref(env, env->intern(env, "length"));
   1436   Flist = env->make_global_ref(env, env->intern(env, "list"));
   1437   Fnth = env->make_global_ref(env, env->intern(env, "nth"));
   1438   Ferase_buffer = env->make_global_ref(env, env->intern(env, "erase-buffer"));
   1439   Finsert = env->make_global_ref(env, env->intern(env, "vterm--insert"));
   1440   Fgoto_char = env->make_global_ref(env, env->intern(env, "goto-char"));
   1441   Fput_text_property =
   1442       env->make_global_ref(env, env->intern(env, "put-text-property"));
   1443   Fadd_text_properties =
   1444       env->make_global_ref(env, env->intern(env, "add-text-properties"));
   1445   Fset = env->make_global_ref(env, env->intern(env, "set"));
   1446   Fvterm_flush_output =
   1447       env->make_global_ref(env, env->intern(env, "vterm--flush-output"));
   1448   Fforward_line = env->make_global_ref(env, env->intern(env, "forward-line"));
   1449   Fgoto_line = env->make_global_ref(env, env->intern(env, "vterm--goto-line"));
   1450   Fdelete_lines =
   1451       env->make_global_ref(env, env->intern(env, "vterm--delete-lines"));
   1452   Frecenter = env->make_global_ref(env, env->intern(env, "recenter"));
   1453   Fset_window_point =
   1454       env->make_global_ref(env, env->intern(env, "set-window-point"));
   1455   Fwindow_body_height =
   1456       env->make_global_ref(env, env->intern(env, "window-body-height"));
   1457 
   1458   Fpoint = env->make_global_ref(env, env->intern(env, "point"));
   1459   Fforward_char = env->make_global_ref(env, env->intern(env, "forward-char"));
   1460   Fget_buffer_window_list =
   1461       env->make_global_ref(env, env->intern(env, "get-buffer-window-list"));
   1462   Fselected_window =
   1463       env->make_global_ref(env, env->intern(env, "selected-window"));
   1464 
   1465   Fvterm_set_title =
   1466       env->make_global_ref(env, env->intern(env, "vterm--set-title"));
   1467   Fvterm_set_directory =
   1468       env->make_global_ref(env, env->intern(env, "vterm--set-directory"));
   1469   Fvterm_invalidate =
   1470       env->make_global_ref(env, env->intern(env, "vterm--invalidate"));
   1471   Feq = env->make_global_ref(env, env->intern(env, "eq"));
   1472   Fvterm_get_color =
   1473       env->make_global_ref(env, env->intern(env, "vterm--get-color"));
   1474   Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval"));
   1475   Fvterm_selection =
   1476       env->make_global_ref(env, env->intern(env, "vterm--selection"));
   1477 
   1478   // Exported functions
   1479   emacs_value fun;
   1480   fun =
   1481       env->make_function(env, 4, 8, Fvterm_new, "Allocate a new vterm.", NULL);
   1482   bind_function(env, "vterm--new", fun);
   1483 
   1484   fun = env->make_function(env, 1, 5, Fvterm_update,
   1485                            "Process io and update the screen.", NULL);
   1486   bind_function(env, "vterm--update", fun);
   1487 
   1488   fun =
   1489       env->make_function(env, 1, 1, Fvterm_redraw, "Redraw the screen.", NULL);
   1490   bind_function(env, "vterm--redraw", fun);
   1491 
   1492   fun = env->make_function(env, 2, 2, Fvterm_write_input,
   1493                            "Write input to vterm.", NULL);
   1494   bind_function(env, "vterm--write-input", fun);
   1495 
   1496   fun = env->make_function(env, 3, 3, Fvterm_set_size,
   1497                            "Set the size of the terminal.", NULL);
   1498   bind_function(env, "vterm--set-size", fun);
   1499 
   1500   fun = env->make_function(env, 2, 2, Fvterm_set_pty_name,
   1501                            "Set the name of the pty.", NULL);
   1502   bind_function(env, "vterm--set-pty-name", fun);
   1503   fun = env->make_function(env, 2, 2, Fvterm_get_pwd,
   1504                            "Get the working directory of at line n.", NULL);
   1505   bind_function(env, "vterm--get-pwd-raw", fun);
   1506   fun = env->make_function(env, 1, 1, Fvterm_reset_cursor_point,
   1507                            "Reset cursor postion.", NULL);
   1508   bind_function(env, "vterm--reset-point", fun);
   1509 
   1510   fun = env->make_function(env, 1, 1, Fvterm_get_icrnl,
   1511                            "Get the icrnl state of the pty", NULL);
   1512   bind_function(env, "vterm--get-icrnl", fun);
   1513 
   1514   provide(env, "vterm-module");
   1515 
   1516   return 0;
   1517 }