diff options
| author | jixiufeng <jixiufeng@luojilab.com> | 2018-10-25 00:08:23 +0800 |
|---|---|---|
| committer | jixiufeng <jixiufeng@luojilab.com> | 2018-10-25 21:33:44 +0800 |
| commit | a18e037ee61afa5e91e955e26710d068d3ee1f9c (patch) | |
| tree | 95697e12bc66066ee2f57775c4639bd60a065f36 | |
| parent | 319f99f6ecc0babba7257cf9fbd156733dd83f62 (diff) | |
try support scroll
| -rw-r--r-- | elisp.c | 30 | ||||
| -rw-r--r-- | elisp.h | 13 | ||||
| -rw-r--r-- | vterm-module.c | 408 | ||||
| -rw-r--r-- | vterm-module.h | 33 | ||||
| -rw-r--r-- | vterm.el | 80 |
5 files changed, 490 insertions, 74 deletions
@@ -86,6 +86,35 @@ void goto_char(emacs_env *env, int pos) { env->funcall(env, Fgoto_char, 1, (emacs_value[]){point}); } +void forward_line(emacs_env *env, int n) { + emacs_value nline = env->make_integer(env, n ); + env->funcall(env, Fforward_line, 1, (emacs_value[]){nline}); +} +void goto_line(emacs_env *env, int n) { + emacs_value nline = env->make_integer(env, n ); + env->funcall(env, Fgoto_line, 1, (emacs_value[]){nline}); +} +void delete_lines(emacs_env *env ,int linenum,int count ,bool del_whole_line){ + emacs_value Qlinenum = env->make_integer(env, linenum); + emacs_value Qcount = env->make_integer(env, count); + if(del_whole_line){ + env->funcall(env, Fdelete_lines, 3, (emacs_value[]){Qlinenum,Qcount,Qt}); + }else{ + env->funcall(env, Fdelete_lines, 3, (emacs_value[]){Qlinenum,Qcount,Qnil}); + } +} +void recenter(emacs_env *env, emacs_value pos){ + env->funcall(env, Frecenter, 1, + (emacs_value[]){pos}); +} +void forward_char(emacs_env *env, emacs_value n){ + env->funcall(env, Fforward_char, 1, + (emacs_value[]){n}); +} +emacs_value buffer_line_number(emacs_env *env){ + return env->funcall(env, Fbuffer_line_number, 0, (emacs_value[]){}); +} + void toggle_cursor(emacs_env *env, bool visible) { emacs_value Qvisible = visible ? Qt : Qnil; env->funcall(env, Fset, 2, (emacs_value[]){Qcursor_type, Qvisible}); @@ -100,3 +129,4 @@ emacs_value get_hex_color_bg(emacs_env *env, emacs_value face) { return env->funcall(env, Fvterm_face_color_hex, 2, (emacs_value[]){face, Qbackground}); } + @@ -26,6 +26,12 @@ emacs_value Flist; emacs_value Ferase_buffer; emacs_value Finsert; emacs_value Fgoto_char; +emacs_value Fforward_char; +emacs_value Fforward_line; +emacs_value Fgoto_line; +emacs_value Fdelete_lines; +emacs_value Fbuffer_line_number; +emacs_value Frecenter; emacs_value Fput_text_property; emacs_value Fset; emacs_value Fvterm_face_color_hex; @@ -46,8 +52,13 @@ VTermColor rgb_string_to_color(emacs_env *env, emacs_value string); void erase_buffer(emacs_env *env); void insert(emacs_env *env, emacs_value string); void goto_char(emacs_env *env, int pos); +void forward_line(emacs_env *env, int n) ; +void goto_line(emacs_env *env, int n) ; void toggle_cursor(emacs_env *env, bool visible); +void delete_lines(emacs_env *env ,int linenum,int count ,bool del_whole_line); emacs_value get_hex_color_fg(emacs_env *env, emacs_value face); emacs_value get_hex_color_bg(emacs_env *env, emacs_value face); - +emacs_value buffer_line_number(emacs_env *env); +void recenter(emacs_env *env, emacs_value pos); +void forward_char(emacs_env *env, emacs_value n); #endif /* ELISP_H */ diff --git a/vterm-module.c b/vterm-module.c index 58e457f..fcd8cb3 100644 --- a/vterm-module.c +++ b/vterm-module.c @@ -3,7 +3,316 @@ #include "utf8.h" #include <string.h> #include <unistd.h> +#include <limits.h> #include <vterm.h> +#include <assert.h> + +static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) { + Term *term = (Term *)data; + + if (!term->sb_size) { + return 0; + } + + // copy vterm cells into sb_buffer + size_t c = (size_t)cols; + ScrollbackLine *sbrow = NULL; + if (term->sb_current == term->sb_size) { + if (term->sb_buffer[term->sb_current - 1]->cols == c) { + // Recycle old row if it's the right size + sbrow = term->sb_buffer[term->sb_current - 1]; + } else { + free(term->sb_buffer[term->sb_current - 1]); + } + + // Make room at the start by shifting to the right. + memmove(term->sb_buffer + 1, term->sb_buffer, + sizeof(term->sb_buffer[0]) * (term->sb_current - 1)); + + } else if (term->sb_current > 0) { + // Make room at the start by shifting to the right. + memmove(term->sb_buffer + 1, term->sb_buffer, + sizeof(term->sb_buffer[0]) * term->sb_current); + } + + if (!sbrow) { + sbrow = malloc(sizeof(ScrollbackLine) + c * sizeof(sbrow->cells[0])); + sbrow->cols = c; + } + + // New row is added at the start of the storage buffer. + term->sb_buffer[0] = sbrow; + if (term->sb_current < term->sb_size) { + term->sb_current++; + } + + if (term->sb_pending < (int)term->sb_size) { + term->sb_pending++; + } + + memcpy(sbrow->cells, cells, sizeof(cells[0]) * c); + + return 1; +} +/// Scrollback pop handler (from pangoterm). +/// +/// @param cols +/// @param cells VTerm state to update. +/// @param data Term +static int term_sb_pop(int cols, VTermScreenCell *cells, void *data){ + Term *term = (Term *)data; + + if (!term->sb_current) { + return 0; + } + + if (term->sb_pending) { + term->sb_pending--; + } + + ScrollbackLine *sbrow = term->sb_buffer[0]; + term->sb_current--; + // Forget the "popped" row by shifting the rest onto it. + memmove(term->sb_buffer, term->sb_buffer + 1, + sizeof(term->sb_buffer[0]) * (term->sb_current)); + + size_t cols_to_copy = (size_t)cols; + if (cols_to_copy > sbrow->cols) { + cols_to_copy = sbrow->cols; + } + + // copy to vterm state + memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); + size_t col ; + for (col = cols_to_copy; col < (size_t)cols; col++) { + cells[col].chars[0] = 0; + cells[col].width = 1; + } + + free(sbrow); + + return 1; +} + +static int row_to_linenr(Term *term, int row){ + return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX; +} + +static int linenr_to_row(Term *term, int linenr){ + return linenr - (int)term->sb_current - 1; +} + +static void fetch_cell(Term *term, int row, int col, + VTermScreenCell *cell){ + if (row < 0) { + ScrollbackLine *sbrow = term->sb_buffer[-row - 1]; + if ((size_t)col < sbrow->cols) { + *cell = sbrow->cells[col]; + } else { + // fill the pointer with an empty cell + VTermColor fg, bg; + VTermState *state = vterm_obtain_state(term->vt); + vterm_state_get_default_colors(state, &fg, &bg); + + *cell = (VTermScreenCell) { + .chars = { 0 }, + .width = 1, + .bg = bg + }; + } + } else { + vterm_screen_get_cell(term->vts, (VTermPos){.row = row, .col = col}, + cell); + } +} + +static size_t get_col_offset(Term *term, int row, int end_col){ + int col = 0; + size_t offset = 0; + unsigned char buf[4]; + + while (col < end_col) { + VTermScreenCell cell; + fetch_cell(term, row, col, &cell); + if (cell.chars[0]) { + if(cell.width>1){ + offset+=cell.width-1; + } + } + col += cell.width; + + } + return offset; +} + +static size_t refresh_row(Term *term,emacs_env* env, int row, + int end_col,bool append_newline){ + int j; + char *ptr = term->textbuf; + int length = 0; + VTermScreenCell cell; + VTermScreenCell lastCell; + fetch_cell(term, row, 0, &lastCell); + + for (j = 0; j < end_col; j++) { + VTermPos pos = {.row = row, .col = j}; + fetch_cell(term, row, j, &cell); + + if (!compare_cells(&cell, &lastCell)) { + ptr[length]='\0'; + emacs_value text = render_text(env, ptr, length, &lastCell); + insert(env,text); + ptr+=length; + length = 0; + } + + lastCell = cell; + if (cell.chars[0] == 0) { + ptr[length] = ' '; + length++; + } else { + unsigned char bytes[4]; + size_t count = codepoint_to_utf8(cell.chars[0], bytes); + int k; + for (k = 0; k < count; k++) { + ptr[length] = bytes[k]; + length++; + } + } + + if (cell.width > 1) { + int w = cell.width - 1; + j = j + w; + } + + } + if(length>0){ + emacs_value text = render_text(env, ptr, length, &lastCell); + insert(env,text); + ptr+=length; + } + if(append_newline){ + *ptr='\n'; + ptr+=1; + insert(env,env->make_string(env, "\n", 1)); + } + *ptr=0; + return ptr-term->textbuf; +} + +// Refresh the screen (visible part of the buffer when the terminal is +// focused) of a invalidated terminal +static void refresh_screen(Term *term,emacs_env *env){ + int height; + int width; + vterm_get_size(term->vt, &height, &width); + // Term height may have decreased before `invalid_end` reflects it. + /* refresh full screen now */ + /* TODO: only refresh invalid lines */ + term->invalid_start = 0; + term->invalid_end = height; + + int line_start=row_to_linenr(term, term->invalid_start); + goto_line(env,line_start-1); + int liner ; + int r; + for (r = term->invalid_start; r < term->invalid_end; r++) { + liner =row_to_linenr(term,r); + int buffer_lnum = env->extract_integer(env, buffer_line_number(env)); + if(liner>buffer_lnum){ + goto_line(env,buffer_lnum); /* maybe should goto end of buffer */ + int i; + for (i = 0; i < liner-buffer_lnum; i++){ + insert(env,env->make_string(env,"\n",1)); + } + } + delete_lines(env,liner,1,false); + goto_line(env,liner); + refresh_row(term,env,r,width,false); + } + term->invalid_start = INT_MAX; + term->invalid_end = -1; +} + + +// Refresh the scrollback of an invalidated terminal. +static void refresh_scrollback(Term *term,emacs_env *env) { + int width, height; + int buffer_lnum ; + vterm_get_size(term->vt, &height, &width); + + while (term->sb_pending > 0) { + // This means that either the window height has decreased or the screen + // became full and libvterm had to push all rows up. Convert the first + // pending scrollback row into a string and append it just above the visible + // section of the buffer + buffer_lnum = env->extract_integer(env, buffer_line_number(env)); + + if ((buffer_lnum- height) >= (int)term->sb_size) { + // scrollback full, delete lines at the top + delete_lines(env,1, 1,true); + /* insert(env,env->make_string(env, "\n", 1)); */ + } + buffer_lnum = env->extract_integer(env, buffer_line_number(env)); + int buf_index = buffer_lnum- height+1; + goto_line(env,buf_index); + size_t length=refresh_row(term,env,-term->sb_pending,width,true); + term->sb_pending--; + } + // Remove extra lines at the bottom + int max_line_count = (int)term->sb_current + height; + buffer_lnum=env->extract_integer(env, buffer_line_number(env)); + + // Remove extra lines at the bottom + if (buffer_lnum> max_line_count) { + delete_lines(env,max_line_count,buffer_lnum-max_line_count, true); + } +} + +static void adjust_topline(Term *term,emacs_env *env, long added){ + int height, width; + vterm_get_size(term->vt, &height, &width); + /* FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { */ + int buffer_lnum = env->extract_integer(env, buffer_line_number(env)); + VTermState *state = vterm_obtain_state(term->vt); + VTermPos pos; + vterm_state_get_cursorpos(state, &pos); + int cursor_lnum=row_to_linenr(term,pos.row); + bool following = buffer_lnum ==cursor_lnum + added; // cursor at end? + + + if (following ) { /* || (wp == curwin && is_focused(term)) */ + // "Follow" the terminal output + goto_line(env,MIN(cursor_lnum,buffer_lnum)); + size_t offset=get_col_offset(term,pos.row,pos.col); + forward_char(env,env->make_integer(env,pos.col-offset)); + recenter(env,env->make_integer(env,-1)); /* make current lien at the screen bottom */ + + } else { + goto_line(env,MIN(cursor_lnum,buffer_lnum)); + size_t offset=get_col_offset(term,pos.row,pos.col); + forward_char(env,env->make_integer(env,pos.col-offset)); + recenter(env,env->make_integer(env,pos.row)); + } +} +static void term_redraw(Term *term,emacs_env *env) { + long ml_before = env->extract_integer(env, buffer_line_number(env)); + refresh_scrollback(term,env); + refresh_screen(term,env); + long ml_added = env->extract_integer(env, buffer_line_number(env))- ml_before; + adjust_topline(term,env, ml_added); +} + +static VTermScreenCallbacks vterm_screen_callbacks = { + /* .damage = term_damage, */ + /* .moverect = term_moverect, */ + /* .movecursor = term_movecursor, */ + /* .settermprop = term_settermprop, */ + /* .bell = term_bell, */ + .sb_pushline = term_sb_push, + .sb_popline = term_sb_pop, +}; + static bool compare_cells(VTermScreenCell *a, VTermScreenCell *b) { bool equal = true; @@ -41,7 +350,14 @@ static int set_term_prop_cb(VTermProp prop, VTermValue *val, void *user_data) { static emacs_value render_text(emacs_env *env, char *buffer, int len, VTermScreenCell *cell) { - emacs_value text = env->make_string(env, buffer, len); + emacs_value text; + if(len==0){ + text= env->make_string(env, "", 0); + return text; + /* return NULL; */ + }else{ + text= env->make_string(env, buffer, len); + } emacs_value foreground = color_to_rgb_string(env, cell->fg); emacs_value background = color_to_rgb_string(env, cell->bg); @@ -64,64 +380,7 @@ static emacs_value render_text(emacs_env *env, char *buffer, int len, return text; } -static void term_redraw(Term *term, emacs_env *env) { - int i, j; - int rows, cols; - VTermScreen *screen = vterm_obtain_screen(term->vt); - vterm_get_size(term->vt, &rows, &cols); - - erase_buffer(env); - - char buffer[((rows + 1) * cols) * 4]; - int length = 0; - VTermScreenCell cell; - VTermScreenCell lastCell; - VTermPos first = {.row = 0, .col = 0}; - vterm_screen_get_cell(screen, first, &lastCell); - - int offset = 0; - for (i = 0; i < rows; i++) { - for (j = 0; j < cols; j++) { - VTermPos pos = {.row = i, .col = j}; - vterm_screen_get_cell(screen, pos, &cell); - - if (!compare_cells(&cell, &lastCell)) { - emacs_value text = render_text(env, buffer, length, &lastCell); - insert(env, text); - length = 0; - } - - lastCell = cell; - if (cell.chars[0] == 0) { - buffer[length] = ' '; - length++; - } else { - unsigned char bytes[4]; - size_t count = codepoint_to_utf8(cell.chars[0], bytes); - for (int k = 0; k < count; k++) { - buffer[length] = bytes[k]; - length++; - } - } - - if (cell.width > 1) { - int w = cell.width - 1; - offset += w; - j = j + w; - } - } - - buffer[length] = '\n'; - length++; - } - emacs_value text = render_text(env, buffer, length, &lastCell); - insert(env, text); - VTermState *state = vterm_obtain_state(term->vt); - VTermPos pos; - vterm_state_get_cursorpos(state, &pos); - term_put_caret(term, env, pos.row, pos.col, -offset); -} static void term_setup_colors(Term *term, emacs_env *env) { VTermColor fg, bg; @@ -253,7 +512,7 @@ static void term_put_caret(Term *term, emacs_env *env, int row, int col, vterm_get_size(term->vt, &rows, &cols); // row * (cols + 1) because of newline character // col + 1 because (goto-char 1) sets point to first position - int point = (row * (cols + 1)) + col + 1 + offset; + int point = ((row+term->sb_current) * (cols + 1)) + col + 1 + offset; goto_char(env, point); } @@ -269,14 +528,26 @@ static emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, int rows = env->extract_integer(env, args[0]); int cols = env->extract_integer(env, args[1]); + int sb_size = env->extract_integer(env, args[2]); term->vt = vterm_new(rows, cols); vterm_set_utf8(term->vt, 1); term_setup_colors(term, env); - VTermScreen *screen = vterm_obtain_screen(term->vt); - vterm_screen_reset(screen, 1); + + term->vts = vterm_obtain_screen(term->vt); + vterm_screen_reset(term->vts , 1); + vterm_screen_set_callbacks(term->vts , &vterm_screen_callbacks, term); + vterm_screen_set_damage_merge(term->vts , VTERM_DAMAGE_SCROLL); + term->sb_size = MIN(SB_MAX,sb_size); + term->sb_current = 0; + term->sb_pending = 0; + term->sb_buffer = malloc(sizeof(ScrollbackLine *) * term->sb_size); + term->invalid_start=0; + term->invalid_end=cols; + + return env->make_user_ptr(env, term_finalize, term); } @@ -334,11 +605,11 @@ static emacs_value Fvterm_set_size(emacs_env *env, ptrdiff_t nargs, if (cols != old_cols || rows != old_rows) { vterm_set_size(term->vt, rows, cols); + term_redraw(term,env); } return Qnil; } - int emacs_module_init(struct emacs_runtime *ert) { emacs_env *env = ert->get_environment(ert); @@ -368,6 +639,15 @@ int emacs_module_init(struct emacs_runtime *ert) { Fset = env->make_global_ref(env, env->intern(env, "set")); Fvterm_face_color_hex = env->make_global_ref(env, env->intern(env, "vterm--face-color-hex")); Fvterm_flush_output = env->make_global_ref(env, env->intern(env, "vterm--flush-output")); + Fforward_line = env->make_global_ref(env, env->intern(env, "forward-line")); + Fgoto_line = env->make_global_ref(env, env->intern(env, "vterm--goto-line")); + Fbuffer_line_number = env->make_global_ref(env, env->intern(env, "vterm--buffer-line-num")); + Fdelete_lines = env->make_global_ref(env, env->intern(env, "vterm--delete-lines")); + Frecenter = env->make_global_ref(env,env->intern(env, "vterm--recenter")); + Fforward_char = env->make_global_ref(env,env->intern(env, "vterm--forward-char")); + + + // Faces Qterm = env->make_global_ref(env, env->intern(env, "vterm")); @@ -383,7 +663,7 @@ int emacs_module_init(struct emacs_runtime *ert) { // Exported functions emacs_value fun; fun = - env->make_function(env, 2, 2, Fvterm_new, "Allocates a new vterm.", NULL); + env->make_function(env, 3, 3, Fvterm_new, "Allocates a new vterm.", NULL); bind_function(env, "vterm--new", fun); fun = env->make_function(env, 1, 5, Fvterm_update, @@ -399,6 +679,6 @@ int emacs_module_init(struct emacs_runtime *ert) { bind_function(env, "vterm--set-size", fun); provide(env, "vterm-module"); - return 0; } + diff --git a/vterm-module.h b/vterm-module.h index 808fb02..775b59d 100644 --- a/vterm-module.h +++ b/vterm-module.h @@ -7,10 +7,41 @@ #include <vterm.h> int plugin_is_GPL_compatible; +typedef struct { + size_t cols; + VTermScreenCell cells[]; +} ScrollbackLine; + +#define SB_MAX 100000 // Maximum 'scrollback' value. +static bool refresh_pending = false; + +#ifndef MIN +# define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif +#ifndef MAX +# define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#endif + -struct Term { struct term { VTerm *vt; + VTermScreen *vts; + // buffer used to: + // - convert VTermScreen cell arrays into utf8 strings + // - receive data from libvterm as a result of key presses. + char textbuf[0x1fff]; + ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm + size_t sb_current; // number of rows pushed to sb_buffer + size_t sb_size; // sb_buffer size + // "virtual index" that points to the first sb_buffer row that we need to + // push to the terminal buffer when refreshing the scrollback. When negative, + // it actually points to entries that are no longer in sb_buffer (because the + // window height has increased) and must be deleted from the terminal buffer + int sb_pending; + + + int invalid_start, invalid_end; // invalid rows in libvterm screen + }; @@ -37,13 +37,20 @@ (defcustom vterm-shell (getenv "SHELL") "The shell that gets run in the vterm." + :type 'string :group 'vterm) -(defcustom vterm-keymap-exceptions '("C-x" "C-u" "C-g" "C-h" "M-x" "M-o") +(defcustom vterm-max-scrollback 1000 + "Maximum 'scrollback' value." + :type 'number + :group 'vterm) + +(defcustom vterm-keymap-exceptions '("C-x" "C-u" "C-g" "C-h" "M-x" "M-o" "C-v" "M-v") "Exceptions for vterm-keymap. If you use a keybinding with a prefix-key that prefix-key cannot be send to the terminal." + :type '(repeat string) :group 'vterm) (defface vterm @@ -102,15 +109,21 @@ be send to the terminal." (define-derived-mode vterm-mode fundamental-mode "VTerm" "Mayor mode for vterm buffer." (buffer-disable-undo) - (setq vterm--term (vterm--new (window-body-height) (window-body-width)) - buffer-read-only t) + (setq vterm--term (vterm--new (window-body-height) + (window-body-width) + vterm-max-scrollback)) + (cl-loop repeat (1- (window-body-height)) do + (insert "\n")) + + (setq buffer-read-only t) (setq-local scroll-conservatively 101) (setq-local scroll-margin 0) + (add-hook 'window-size-change-functions #'vterm--window-size-change t t) (let ((process-environment (append '("TERM=xterm") process-environment))) (setq vterm--process (make-process :name "vterm" - :buffer buffer + :buffer (current-buffer) :command `("/bin/sh" "-c" ,(format "stty -nl sane iutf8 rows %d columns %d >/dev/null && exec %s" (window-body-height) (window-body-width) vterm-shell)) :coding 'no-conversion :connection-type 'pty @@ -152,16 +165,17 @@ be send to the terminal." "Create a new vterm." (interactive) (let ((buffer (generate-new-buffer "vterm"))) - (set-buffer buffer) - (vterm-mode) + (with-current-buffer buffer + (vterm-mode)) (switch-to-buffer buffer))) (defun vterm-other-window () "Create a new vterm." (interactive) (let ((buffer (generate-new-buffer "vterm"))) - (set-buffer buffer) - (vterm-mode) + (with-current-buffer buffer + (vterm-mode)) + (pop-to-buffer buffer))) (defun vterm--flush-output (output) @@ -197,5 +211,55 @@ Feeds the size change to the virtual terminal." (apply #'color-rgb-to-hex (color-name-to-rgb (face-attribute face attr nil 'default))) (apply #'color-rgb-to-hex (append (color-name-to-rgb (face-attribute face attr nil 'default)) '(2))))) + +(defun vterm--delete-lines (line-num count &optional delete-whole-line) + "Delete lines from line-num. If option ‘kill-whole-line’ is non-nil, + then this command kills the whole line including its terminating newline" + (ignore-errors + (save-excursion + (when (vterm--goto-line line-num) + (delete-region (point) (point-at-eol)) + (when delete-whole-line + (when (looking-at "\n") + (delete-char 1)) + (when (and (eobp) (looking-back "\n")) + (delete-char -1)) + + ) + (cl-loop repeat (1- count) do + (when (or delete-whole-line (forward-line 1)) + (delete-region (point) (point-at-eol)) + (when delete-whole-line + (when (looking-at "\n") + (delete-char 1)) + (when (and (eobp) (looking-back "\n")) + (delete-char -1))) + )))))) + + + +(defun vterm--recenter(&optional arg) + (when (get-buffer-window) + (with-current-buffer (window-buffer) + (when vterm--term + (recenter arg))))) + +(defun vterm--goto-line(n) + "If move succ return t" + (ignore-errors + (goto-char (point-min)) + (let ((succ (eq 0 (forward-line (1- n))))) + succ))) + +(defun vterm--buffer-line-num() + (ignore-errors + (save-excursion + (goto-char (point-max)) + (line-number-at-pos)))) + +(defun vterm--forward-char(n ) + (ignore-errors + (forward-char n))) + (provide 'vterm) ;;; vterm.el ends here |
