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 }