config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

vterm-module.c (51362B)


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