diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/edit.c | 11 | ||||
-rw-r--r-- | src/nvim/eval.c | 4 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 13 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/path.c | 11 | ||||
-rw-r--r-- | src/nvim/regexp_nfa.c | 1 | ||||
-rw-r--r-- | src/nvim/search.c | 2 | ||||
-rw-r--r-- | src/nvim/terminal.c | 112 | ||||
-rw-r--r-- | src/nvim/ugrid.h | 3 | ||||
-rw-r--r-- | src/nvim/ui_bridge.c | 8 |
10 files changed, 110 insertions, 57 deletions
diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 6bcf5e804a..427623e052 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -254,7 +254,16 @@ edit ( ) { if (curbuf->terminal) { - terminal_enter(); + if (ex_normal_busy) { + // don't enter terminal mode from `ex_normal`, which can result in all + // kinds of havoc(such as terminal mode recursiveness). Instead, set a + // flag that allow us to force-set the value of `restart_edit` before + // `ex_normal` returns + restart_edit = 'i'; + force_restart_edit = true; + } else { + terminal_enter(); + } return false; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bbe45480d2..9e61afa53d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19832,7 +19832,9 @@ void ex_delfunction(exarg_T *eap) EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); return; } - if (fp->uf_refcount > 1) { + // check `uf_refcount > 2` because deleting a function should also reduce + // the reference count, and 1 is the initial refcount. + if (fp->uf_refcount > 2) { EMSG2(_("Cannot delete function %s: It is being used internally"), eap->arg); return; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d69d106dc6..3c09a3a2f8 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7624,6 +7624,10 @@ void update_topline_cursor(void) */ static void ex_normal(exarg_T *eap) { + if (curbuf->terminal) { + EMSG("Can't re-enter normal mode from terminal mode"); + return; + } int save_msg_scroll = msg_scroll; int save_restart_edit = restart_edit; int save_msg_didout = msg_didout; @@ -7715,7 +7719,14 @@ static void ex_normal(exarg_T *eap) --ex_normal_busy; msg_scroll = save_msg_scroll; - restart_edit = save_restart_edit; + if (force_restart_edit) { + force_restart_edit = false; + } else { + // some function called was aware of ex_normal and decided to override the + // value of restart_edit anyway. So far only used in terminal mode(see + // terminal_enter() in edit.c) + restart_edit = save_restart_edit; + } p_im = save_insertmode; finish_op = save_finish_op; opcount = save_opcount; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 183e8fcff2..60d03cec0c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -823,6 +823,8 @@ EXTERN int no_u_sync INIT(= 0); /* Don't call u_sync() */ EXTERN int u_sync_once INIT(= 0); /* Call u_sync() once when evaluating an expression. */ +EXTERN bool force_restart_edit INIT(= false); // force restart_edit after + // ex_normal returns EXTERN int restart_edit INIT(= 0); /* call edit when next cmd finished */ EXTERN int arrow_used; /* Normally FALSE, set to TRUE after * hitting cursor key in insert mode. diff --git a/src/nvim/path.c b/src/nvim/path.c index 1a1f15e8f3..45ae12b78a 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2089,8 +2089,15 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, int len, int // expand it if forced or not an absolute path if (force || !path_is_absolute_path(fname)) { if ((p = vim_strrchr(fname, '/')) != NULL) { - STRNCPY(relative_directory, fname, p-fname); - relative_directory[p-fname] = NUL; + // relative to root + if (p == fname) { + // only one path component + relative_directory[0] = '/'; + relative_directory[1] = NUL; + } else { + STRNCPY(relative_directory, fname, p-fname); + relative_directory[p-fname] = NUL; + } end_of_path = (char *) (p + 1); } else { relative_directory[0] = NUL; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 484dfe1e1f..4cd422400f 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4781,6 +4781,7 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) if (match /* check that no composing char follows */ && !(enc_utf8 + && STRLEN(regline) > (size_t)(col + len2) && utf_iscomposing(PTR2CHAR(regline + col + len2))) ) { cleanup_subexpr(); diff --git a/src/nvim/search.c b/src/nvim/search.c index ff5c5f834f..a758e02105 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -653,7 +653,7 @@ int searchit( } if (matchcol == 0 && (options & SEARCH_START)) break; - if (ptr[matchcol] == NUL + if (STRLEN(ptr) <= (size_t)matchcol || ptr[matchcol] == NUL || (nmatched = vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, matchcol, diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 028c956f3c..82b9599051 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -71,6 +71,7 @@ #include "nvim/event/time.h" #include "nvim/os/input.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/handle.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "terminal.c.generated.h" @@ -113,7 +114,10 @@ struct terminal { // window height has increased) and must be deleted from the terminal buffer int sb_pending; // buf_T instance that acts as a "drawing surface" for libvterm - buf_T *buf; + // we can't store a direct reference to the buffer because the + // refresh_timer_cb may be called after the buffer was freed, and there's + // no way to know if the memory was reused. + uint64_t buf_handle; // program exited bool closed, destroy; // some vterm properties @@ -155,6 +159,8 @@ void terminal_init(void) { invalidated_terminals = pmap_new(ptr_t)(); time_watcher_init(&loop, &refresh_timer, NULL); + // refresh_timer_cb will redraw the screen which can call vimscript + refresh_timer.events = queue_new_child(loop.events); // initialize a rgb->color index map for cterm attributes(VTermScreenCell // only has RGB information and we need color indexes for terminal UIs) @@ -180,6 +186,7 @@ void terminal_init(void) void terminal_teardown(void) { time_watcher_stop(&refresh_timer); + queue_free(refresh_timer.events); time_watcher_close(&refresh_timer, NULL); pmap_free(ptr_t)(invalidated_terminals); map_free(int, int)(color_indexes); @@ -194,7 +201,7 @@ Terminal *terminal_open(TerminalOptions opts) rv->opts = opts; rv->cursor.visible = true; // Associate the terminal instance with the new buffer - rv->buf = curbuf; + rv->buf_handle = curbuf->handle; curbuf->terminal = rv; // Create VTerm rv->vt = vterm_new(opts.height, opts.width); @@ -212,7 +219,7 @@ Terminal *terminal_open(TerminalOptions opts) // have as many lines as screen rows when refresh_scrollback is called rv->invalid_start = 0; rv->invalid_end = opts.height; - refresh_screen(rv); + refresh_screen(rv, curbuf); set_option_value((uint8_t *)"buftype", 0, (uint8_t *)"terminal", OPT_LOCAL); // some sane settings for terminal buffers set_option_value((uint8_t *)"wrap", false, NULL, OPT_LOCAL); @@ -229,7 +236,7 @@ Terminal *terminal_open(TerminalOptions opts) // - SCROLLBACK_BUFFER_DEFAULT_SIZE // // but limit to 100k. - int size = get_config_int(rv, "terminal_scrollback_buffer_size"); + int size = get_config_int("terminal_scrollback_buffer_size"); rv->sb_size = size > 0 ? (size_t)size : SCROLLBACK_BUFFER_DEFAULT_SIZE; rv->sb_size = MIN(rv->sb_size, 100000); rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); @@ -243,7 +250,7 @@ Terminal *terminal_open(TerminalOptions opts) RgbValue color_val = -1; char var[64]; snprintf(var, sizeof(var), "terminal_color_%d", i); - char *name = get_config_string(rv, var); + char *name = get_config_string(var); if (name) { color_val = name_to_color((uint8_t *)name); xfree(name); @@ -273,11 +280,14 @@ void terminal_close(Terminal *term, char *msg) term->forward_mouse = false; term->closed = true; if (!msg || exiting) { + buf_T *buf = handle_get_buffer(term->buf_handle); // If no msg was given, this was called by close_buffer(buffer.c). Or if // exiting, we must inform the buffer the terminal no longer exists so that // close_buffer() doesn't call this again. - term->buf->terminal = NULL; - term->buf = NULL; + term->buf_handle = 0; + if (buf) { + buf->terminal = NULL; + } if (!term->refcount) { // We should not wait for the user to press a key. term->opts.close_cb(term->opts.data); @@ -308,7 +318,7 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) // The new width/height are the minimum for all windows that display the // terminal in the current tab. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (!wp->w_closing && wp->w_buffer == term->buf) { + if (!wp->w_closing && wp->w_buffer->terminal == term) { width = (uint16_t)MIN(width, (uint16_t)(wp->w_width - win_col_off(wp))); height = (uint16_t)MIN(height, (uint16_t)wp->w_height); } @@ -330,7 +340,8 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) void terminal_enter(void) { - Terminal *term = curbuf->terminal; + buf_T *buf = curbuf; + Terminal *term = buf->terminal; assert(term && "should only be called when curbuf has a terminal"); // Ensure the terminal is properly sized. @@ -345,7 +356,7 @@ void terminal_enter(void) bool save_mapped_ctrl_c = mapped_ctrl_c; mapped_ctrl_c = true; // go to the bottom when the terminal is focused - adjust_topline(term, false); + adjust_topline(term, buf, false); // erase the unfocused cursor invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); showmode(); @@ -356,7 +367,7 @@ void terminal_enter(void) bool got_bs = false; // True if the last input was <C-\> - while (term->buf == curbuf) { + while (curbuf->handle == term->buf_handle) { input_enable_events(); c = safe_vgetc(); input_disable_events(); @@ -383,7 +394,7 @@ void terminal_enter(void) term->refcount++; queue_process_events(loop.events); term->refcount--; - if (term->buf == NULL) { + if (term->buf_handle == 0) { close = true; goto end; } @@ -418,10 +429,10 @@ end: invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); mapped_ctrl_c = save_mapped_ctrl_c; unshowmode(true); - redraw(term->buf != curbuf); + redraw(buf != curbuf); ui_busy_stop(); if (close) { - bool wipe = term->buf != NULL; + bool wipe = term->buf_handle != 0; term->opts.close_cb(term->opts.data); if (wipe) { do_cmdline_cmd("bwipeout!"); @@ -431,10 +442,11 @@ end: void terminal_destroy(Terminal *term) { - if (term->buf) { - term->buf->terminal = NULL; + buf_T *buf = handle_get_buffer(term->buf_handle); + if (buf) { + term->buf_handle = 0; + buf->terminal = NULL; } - term->buf = NULL; if (!term->refcount) { if (pmap_has(ptr_t)(invalidated_terminals, term)) { @@ -589,8 +601,9 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) break; case VTERM_PROP_TITLE: { + buf_T *buf = handle_get_buffer(term->buf_handle); Error err; - api_free_object(dict_set_value(term->buf->b_vars, + api_free_object(dict_set_value(buf->b_vars, cstr_as_string("term_title"), STRING_OBJ(cstr_as_string(val->string)), &err)); @@ -778,7 +791,7 @@ static bool send_mouse_event(Terminal *term, int c) int row = mouse_row, col = mouse_col; win_T *mouse_win = mouse_find_win(&row, &col); - if (term->forward_mouse && mouse_win->w_buffer == term->buf) { + if (term->forward_mouse && mouse_win->w_buffer->terminal == term) { // event in the terminal window and mouse events was enabled by the // program. translate and forward the event int button; @@ -903,22 +916,23 @@ static void invalidate_terminal(Terminal *term, int start_row, int end_row) static void refresh_terminal(Terminal *term) { // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid. + buf_T *buf = handle_get_buffer(term->buf_handle); bool valid = true; - if (!term->buf || !(valid = buf_valid(term->buf))) { + if (!buf || !(valid = buf_valid(buf))) { // destroyed by `close_buffer`. Dont do anything else if (!valid) { - term->buf = NULL; + term->buf_handle = 0; } return; } bool pending_resize = term->pending_resize; - WITH_BUFFER(term->buf, { - refresh_size(term); - refresh_scrollback(term); - refresh_screen(term); - redraw_buf_later(term->buf, NOT_VALID); + WITH_BUFFER(buf, { + refresh_size(term, buf); + refresh_scrollback(term, buf); + refresh_screen(term, buf); + redraw_buf_later(buf, NOT_VALID); }); - adjust_topline(term, pending_resize); + adjust_topline(term, buf, pending_resize); } // libuv timer callback. This will enqueue on_refresh to be processed as an // event. @@ -943,7 +957,7 @@ end: refresh_pending = false; } -static void refresh_size(Terminal *term) +static void refresh_size(Terminal *term, buf_T *buf) { if (!term->pending_resize || term->closed) { return; @@ -958,7 +972,7 @@ static void refresh_size(Terminal *term) } // Refresh the scrollback of a invalidated terminal -static void refresh_scrollback(Terminal *term) +static void refresh_scrollback(Terminal *term, buf_T *buf) { int width, height; vterm_get_size(term->vt, &height, &width); @@ -968,13 +982,13 @@ static void refresh_scrollback(Terminal *term) // 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 - if (((int)term->buf->b_ml.ml_line_count - height) >= (int)term->sb_size) { + if (((int)buf->b_ml.ml_line_count - height) >= (int)term->sb_size) { // scrollback full, delete lines at the top ml_delete(1, false); deleted_lines(1, 1); } fetch_row(term, -term->sb_pending, width); - int buf_index = (int)term->buf->b_ml.ml_line_count - height; + int buf_index = (int)buf->b_ml.ml_line_count - height; ml_append(buf_index, (uint8_t *)term->textbuf, 0, false); appended_lines(buf_index, 1); term->sb_pending--; @@ -982,15 +996,15 @@ static void refresh_scrollback(Terminal *term) // Remove extra lines at the bottom int max_line_count = (int)term->sb_current + height; - while (term->buf->b_ml.ml_line_count > max_line_count) { - ml_delete(term->buf->b_ml.ml_line_count, false); - deleted_lines(term->buf->b_ml.ml_line_count, 1); + while (buf->b_ml.ml_line_count > max_line_count) { + ml_delete(buf->b_ml.ml_line_count, false); + deleted_lines(buf->b_ml.ml_line_count, 1); } } // Refresh the screen(visible part of the buffer when the terminal is // focused) of a invalidated terminal -static void refresh_screen(Terminal *term) +static void refresh_screen(Terminal *term, buf_T *buf) { int changed = 0; int added = 0; @@ -1005,7 +1019,7 @@ static void refresh_screen(Terminal *term) r < term->invalid_end; r++, linenr++) { fetch_row(term, r, width); - if (linenr <= term->buf->b_ml.ml_line_count) { + if (linenr <= buf->b_ml.ml_line_count) { ml_replace(linenr, (uint8_t *)term->textbuf, true); changed++; } else { @@ -1069,20 +1083,20 @@ static void redraw(bool restore_cursor) ui_flush(); } -static void adjust_topline(Terminal *term, bool force) +static void adjust_topline(Terminal *term, buf_T *buf, bool force) { int height, width; vterm_get_size(term->vt, &height, &width); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == term->buf) { + if (wp->w_buffer == buf) { // for every window that displays a terminal, ensure the cursor is in a // valid line - wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, term->buf->b_ml.ml_line_count); - if (force || curbuf != term->buf || is_focused(term)) { + wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, buf->b_ml.ml_line_count); + if (force || curbuf != buf || is_focused(term)) { // if the terminal is not in the current window or if it's focused, // adjust topline/cursor so the window will "follow" the terminal // output - wp->w_cursor.lnum = term->buf->b_ml.ml_line_count; + wp->w_cursor.lnum = buf->b_ml.ml_line_count; set_topline(wp, MAX(wp->w_cursor.lnum - height + 1, 1)); } } @@ -1101,22 +1115,24 @@ static int linenr_to_row(Terminal *term, int linenr) static bool is_focused(Terminal *term) { - return State & TERM_FOCUS && curbuf == term->buf; + return State & TERM_FOCUS && curbuf->terminal == term; } -#define GET_CONFIG_VALUE(t, k, o) \ +#define GET_CONFIG_VALUE(k, o) \ do { \ Error err; \ - o = dict_get_value(t->buf->b_vars, cstr_as_string(k), &err); \ + /* Only called from terminal_open where curbuf->terminal is the */ \ + /* context */ \ + o = dict_get_value(curbuf->b_vars, cstr_as_string(k), &err); \ if (o.type == kObjectTypeNil) { \ o = dict_get_value(&globvardict, cstr_as_string(k), &err); \ } \ } while (0) -static char *get_config_string(Terminal *term, char *key) +static char *get_config_string(char *key) { Object obj; - GET_CONFIG_VALUE(term, key, obj); + GET_CONFIG_VALUE(key, obj); if (obj.type == kObjectTypeString) { return obj.data.string.data; } @@ -1124,10 +1140,10 @@ static char *get_config_string(Terminal *term, char *key) return NULL; } -static int get_config_int(Terminal *term, char *key) +static int get_config_int(char *key) { Object obj; - GET_CONFIG_VALUE(term, key, obj); + GET_CONFIG_VALUE(key, obj); if (obj.type == kObjectTypeInteger) { return (int)obj.data.integer; } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index e41461fa16..df51e1fced 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -2,12 +2,13 @@ #define NVIM_UGRID_H #include "nvim/ui.h" +#include "nvim/globals.h" typedef struct ucell UCell; typedef struct ugrid UGrid; struct ucell { - char data[7]; + char data[6 * MAX_MCO + 1]; HlAttrs attrs; }; diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 2ec31de5e1..836339a887 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -9,6 +9,7 @@ #include "nvim/ui.h" #include "nvim/memory.h" #include "nvim/ui_bridge.h" +#include "nvim/ugrid.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_bridge.c.generated.h" @@ -233,8 +234,11 @@ static void ui_bridge_highlight_set_event(void **argv) static void ui_bridge_put(UI *b, uint8_t *text, size_t size) { - uint8_t *t = xmalloc(8); - memcpy(t, text, size); + uint8_t *t = NULL; + if (text) { + t = xmalloc(sizeof(((UCell *)0)->data)); + memcpy(t, text, size); + } UI_CALL(b, put, 3, b, t, INT2PTR(size)); } static void ui_bridge_put_event(void **argv) |