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 }