diff options
Diffstat (limited to 'src')
63 files changed, 1172 insertions, 797 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 82a88c6774..db79de63cc 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -230,6 +230,11 @@ if(WIN32) endif() elseif(APPLE) target_link_libraries(nvim PRIVATE "-framework CoreServices") + + # Actually export symbols - symbols may not be visible even though + # ENABLE_EXPORTS is set to true. See + # https://github.com/neovim/neovim/issues/25295 + set_target_properties(nvim PROPERTIES LINK_FLAGS "-Wl,-export_dynamic") endif() if(UNIX) @@ -879,7 +884,13 @@ set(VIMDOC_FILES ) glob_wrapper(API_SOURCES ${PROJECT_SOURCE_DIR}/src/nvim/api/*.c) -glob_wrapper(LUA_SOURCES ${NVIM_RUNTIME_DIR}/lua/vim/*.lua) + +glob_wrapper(LUA_SOURCES + ${NVIM_RUNTIME_DIR}/lua/vim/*.lua + ${NVIM_RUNTIME_DIR}/lua/vim/filetype/*.lua + ${NVIM_RUNTIME_DIR}/lua/vim/lsp/*.lua + ${NVIM_RUNTIME_DIR}/lua/vim/treesitter/*.lua +) add_custom_command( OUTPUT ${VIMDOC_FILES} diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index f5a77ece7e..f345edc52c 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -518,6 +518,7 @@ static int64_t convert_index(int64_t index) /// @return Option Information Dictionary nvim_get_option_info(String name, Error *err) FUNC_API_SINCE(7) + FUNC_API_DEPRECATED_SINCE(11) { return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err); } @@ -531,6 +532,7 @@ Dictionary nvim_get_option_info(String name, Error *err) /// @param[out] err Error details, if any void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err); } @@ -543,6 +545,7 @@ void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) /// @return Option value (global) Object nvim_get_option(String name, Arena *arena, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { return get_option_from(NULL, SREQ_GLOBAL, name, err); } @@ -556,6 +559,7 @@ Object nvim_get_option(String name, Arena *arena, Error *err) /// @return Option value Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -577,6 +581,7 @@ Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) /// @param[out] err Error details, if any void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -596,6 +601,7 @@ void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object /// @return Option value Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { win_T *win = find_window_by_handle(window, err); @@ -617,6 +623,7 @@ Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) /// @param[out] err Error details, if any void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { win_T *win = find_window_by_handle(window, err); diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index faab6e593c..91e197bea7 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -113,6 +113,36 @@ static Object hl_group_name(int hl_id, bool hl_name) } } +Array virt_text_to_array(VirtText vt, bool hl_name) +{ + Array chunks = ARRAY_DICT_INIT; + Array hl_array = ARRAY_DICT_INIT; + for (size_t i = 0; i < kv_size(vt); i++) { + char *text = kv_A(vt, i).text; + int hl_id = kv_A(vt, i).hl_id; + if (text == NULL) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + continue; + } + Array chunk = ARRAY_DICT_INIT; + ADD(chunk, CSTR_TO_OBJ(text)); + if (hl_array.size > 0) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + ADD(chunk, ARRAY_OBJ(hl_array)); + hl_array = (Array)ARRAY_DICT_INIT; + } else if (hl_id > 0) { + ADD(chunk, hl_group_name(hl_id, hl_name)); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + assert(hl_array.size == 0); + return chunks; +} + static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict, bool hl_name) { Array rv = ARRAY_DICT_INIT; @@ -145,16 +175,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict } if (kv_size(decor->virt_text)) { - Array chunks = ARRAY_DICT_INIT; - for (size_t i = 0; i < decor->virt_text.size; i++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &decor->virt_text.items[i]; - ADD(chunk, CSTR_TO_OBJ(vtc->text)); - if (vtc->hl_id > 0) { - ADD(chunk, hl_group_name(vtc->hl_id, hl_name)); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } + Array chunks = virt_text_to_array(decor->virt_text, hl_name); PUT(dict, "virt_text", ARRAY_OBJ(chunks)); PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); if (decor->virt_text_pos == kVTWinCol) { @@ -171,19 +192,9 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict if (kv_size(decor->virt_lines)) { Array all_chunks = ARRAY_DICT_INIT; bool virt_lines_leftcol = false; - for (size_t i = 0; i < decor->virt_lines.size; i++) { - Array chunks = ARRAY_DICT_INIT; - VirtText *vt = &decor->virt_lines.items[i].line; - virt_lines_leftcol = decor->virt_lines.items[i].left_col; - for (size_t j = 0; j < vt->size; j++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &vt->items[j]; - ADD(chunk, CSTR_TO_OBJ(vtc->text)); - if (vtc->hl_id > 0) { - ADD(chunk, hl_group_name(vtc->hl_id, hl_name)); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } + for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { + virt_lines_leftcol = kv_A(decor->virt_lines, i).left_col; + Array chunks = virt_text_to_array(kv_A(decor->virt_lines, i).line, hl_name); ADD(all_chunks, ARRAY_OBJ(chunks)); } PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks)); @@ -1188,8 +1199,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) goto free_exit; } if (j < arr.size - 1) { - kv_push(virt_text, ((VirtTextChunk){ .text = NULL, - .hl_id = hl_id })); + kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id })); } } } else { diff --git a/src/nvim/api/keysets.h b/src/nvim/api/keysets.h index a98cbe276f..236e75983e 100644 --- a/src/nvim/api/keysets.h +++ b/src/nvim/api/keysets.h @@ -112,6 +112,7 @@ typedef struct { String footer_pos; String style; Boolean noautocmd; + Boolean fixed; } Dict(float_config); typedef struct { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8fcabb3605..5463578b56 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -21,8 +21,10 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/ex_eval.h" #include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/map.h" @@ -235,8 +237,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva // Delete the key if (di == NULL) { // Doesn't exist, fail - api_set_error(err, kErrorTypeValidation, "Key not found: %s", - key.data); + api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); } else { // Notify watchers if (watched) { @@ -265,13 +266,23 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { - if (watched) { - tv_copy(&di->di_tv, &oldtv); - } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); } + bool type_error = false; + if (dict == &vimvardict + && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) { + tv_clear(&tv); + if (type_error) { + api_set_error(err, kErrorTypeValidation, + "Setting v:%s to value with wrong type", key.data); + } + return rv; + } + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } tv_clear(&di->di_tv); } diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 70c97be984..0ea2310042 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -833,8 +833,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int bool was_space = false; for (size_t i = 0; i < ncells; i++) { repeat++; - if (i == ncells - 1 || attrs[i] != attrs[i + 1] - || strcmp(chunk[i], chunk[i + 1]) != 0) { + if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) { if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) { // close to overflowing the redraw buffer. finish this event, // flush, and start a new "grid_line" event at the current position. @@ -859,7 +858,9 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1); nelem++; mpack_array(buf, csize); - mpack_str(buf, chunk[i]); + char sc_buf[MAX_SCHAR_SIZE]; + schar_get(sc_buf, chunk[i]); + mpack_str(buf, sc_buf); if (csize >= 2) { mpack_uint(buf, (uint32_t)attrs[i]); if (csize >= 3) { @@ -869,7 +870,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int data->ncells_pending += MIN(repeat, 2); last_hl = attrs[i]; repeat = 0; - was_space = strequal(chunk[i], " "); + was_space = chunk[i] == schar_from_ascii(' '); } } // If the last chunk was all spaces, add a clearing chunk even if there are @@ -893,8 +894,10 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int for (int i = 0; i < endcol - startcol; i++) { remote_ui_cursor_goto(ui, row, startcol + i); remote_ui_highlight_set(ui, attrs[i]); - remote_ui_put(ui, chunk[i]); - if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) { + char sc_buf[MAX_SCHAR_SIZE]; + schar_get(sc_buf, chunk[i]); + remote_ui_put(ui, sc_buf); + if (utf_ambiguous_width(utf_ptr2char(sc_buf))) { data->client_col = -1; // force cursor update } } diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index fc70215352..6ca5024a04 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -167,4 +167,7 @@ void msg_history_show(Array entries) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void msg_history_clear(void) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; + +void error_exit(Integer status) + FUNC_API_SINCE(12); #endif // NVIM_API_UI_EVENTS_IN_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9d08e9b6c7..00641c633d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1442,7 +1442,7 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|, /// values are booleans (default false). Also: -/// - "noremap" non-recursive mapping |:noremap| +/// - "noremap" disables |recursive_mapping|, like |:noremap| /// - "desc" human-readable description. /// - "callback" Lua function called in place of {rhs}. /// - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the @@ -1710,6 +1710,8 @@ static void write_msg(String message, bool to_err, bool writeln) msg_didout = true; \ kv_drop(line_buf, kv_size(line_buf)); \ kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \ + } else if (c == NUL) { \ + kv_push(line_buf, NL); \ } else { \ kv_push(line_buf, c); \ } @@ -1948,7 +1950,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E } ret = arena_array(arena, 3); size_t off = g->line_offset[(size_t)row] + (size_t)col; - ADD_C(ret, CSTR_AS_OBJ((char *)g->chars[off])); + char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false); + schar_get(sc_buf, g->chars[off]); + ADD_C(ret, CSTR_AS_OBJ(sc_buf)); int attr = g->attrs[off]; ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err))); // will not work first time @@ -1964,6 +1968,11 @@ void nvim__screenshot(String path) ui_call_screenshot(path); } +void nvim__invalidate_glyph_cache(void) +{ + schar_cache_clear_force(); +} + Object nvim__unpack(String str, Error *err) FUNC_API_FAST { diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 63cf3bb701..ea0b7ce512 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -16,7 +16,7 @@ #include "nvim/drawscreen.h" #include "nvim/extmark_defs.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" #include "nvim/highlight_group.h" #include "nvim/macros.h" #include "nvim/mbyte.h" @@ -163,6 +163,8 @@ /// - noautocmd: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. +/// - fixed: If true when anchor is NW or SW, the float window +/// would be kept fixed even if the window would be truncated. /// /// @param[out] err Error details, if any /// @@ -195,6 +197,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (win_valid(wp) && buffer > 0) { Boolean noautocmd = !enter || fconfig.noautocmd; win_set_buf(wp, buf, noautocmd, err); + if (!fconfig.noautocmd) { + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, buf); + } } if (!win_valid(wp)) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); @@ -254,34 +259,28 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig, BorderTextType bordertext_type) { - VirtText chunks; + VirtText vt; AlignTextPos align; char *field_name; char *field_pos_name; switch (bordertext_type) { case kBorderTextTitle: - chunks = fconfig->title_chunks; + vt = fconfig->title_chunks; align = fconfig->title_pos; field_name = "title"; field_pos_name = "title_pos"; break; case kBorderTextFooter: - chunks = fconfig->footer_chunks; + vt = fconfig->footer_chunks; align = fconfig->footer_pos; field_name = "footer"; field_pos_name = "footer_pos"; break; + default: + abort(); } - Array bordertext = ARRAY_DICT_INIT; - for (size_t i = 0; i < chunks.size; i++) { - Array tuple = ARRAY_DICT_INIT; - ADD(tuple, CSTR_TO_OBJ(chunks.items[i].text)); - if (chunks.items[i].hl_id > 0) { - ADD(tuple, CSTR_TO_OBJ(syn_id2name(chunks.items[i].hl_id))); - } - ADD(bordertext, ARRAY_OBJ(tuple)); - } + Array bordertext = virt_text_to_array(vt, true); PUT(config, field_name, ARRAY_OBJ(bordertext)); char *pos; @@ -348,7 +347,7 @@ Dictionary nvim_win_get_config(Window window, Error *err) for (size_t i = 0; i < 8; i++) { Array tuple = ARRAY_DICT_INIT; - String s = cstrn_to_string(config->border_chars[i], sizeof(schar_T)); + String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE); int hi_id = config->border_hl_ids[i]; char *hi_name = syn_id2name(hi_id); @@ -520,7 +519,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { struct { const char *name; - schar_T chars[8]; + char chars[8][MAX_SCHAR_SIZE]; bool shadow_color; } defaults[] = { { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, @@ -531,7 +530,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { NULL, { { NUL } }, false }, }; - schar_T *chars = fconfig->border_chars; + char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars; int *hl_ids = fconfig->border_hl_ids; fconfig->border = true; @@ -845,6 +844,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, fconfig->noautocmd = config->noautocmd; } + if (HAS_KEY_X(config, fixed)) { + fconfig->fixed = config->fixed; + } + return true; #undef HAS_KEY_X } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index 74348052b0..9c2b3ba6d8 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -857,12 +857,17 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) while (true) { win_T *wpnext = NULL; tabpage_T *tpnext = curtab->tp_next; - for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { + // Try to close floating windows first + for (win_T *wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) { int i; - wpnext = wp->w_next; + wpnext = wp->w_floating + ? wp->w_prev->w_floating ? wp->w_prev : firstwin + : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next; buf_T *buf = wp->w_buffer; if (buf->b_ffname == NULL - || (!aall->keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { + || (!aall->keep_tabs + && (buf->b_nwindows > 1 || wp->w_width != Columns + || (wp->w_floating && !is_aucmd_win(wp))))) { i = aall->opened_len; } else { // check if the buffer in this window is in the arglist @@ -917,7 +922,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) (void)autowrite(buf, false); // Check if autocommands removed the window. if (!win_valid(wp) || !bufref_valid(&bufref)) { - wpnext = firstwin; // Start all over... + wpnext = lastwin->w_floating ? lastwin : firstwin; // Start all over... continue; } } @@ -930,7 +935,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) // check if autocommands removed the next window if (!win_valid(wpnext)) { // start all over... - wpnext = firstwin; + wpnext = lastwin->w_floating ? lastwin : firstwin; } } } @@ -977,6 +982,8 @@ static void arg_all_open_windows(arg_all_state_T *aall, int count) if (aall->keep_tabs) { aall->new_curwin = wp; aall->new_curtab = curtab; + } else if (wp->w_floating) { + break; } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { emsg(_(e_window_layout_changed_unexpectedly)); i = count; @@ -1098,7 +1105,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs) autocmd_no_leave++; last_curwin = curwin; last_curtab = curtab; - win_enter(lastwin, false); + // lastwin may be aucmd_win + win_enter(lastwin_nofloating(), false); // Open up to "count" windows. arg_all_open_windows(&aall, count); diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3fa20f4e48..ff0c2f063f 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1341,7 +1341,6 @@ void aucmd_restbuf(aco_save_T *aco) if (aco->use_aucmd_win_idx >= 0) { win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; - curbuf->b_nwindows--; // Find "awp", it can't be closed, but it may be in another tab page. // Do not trigger autocommands here. block_autocmds(); @@ -1357,7 +1356,7 @@ void aucmd_restbuf(aco_save_T *aco) } } win_found: - ; + curbuf->b_nwindows--; const bool save_stop_insert_mode = stop_insert_mode; // May need to stop Insert mode if we were in a prompt buffer. leaving_window(curwin); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index d2a5eab0a5..7a3e65e10e 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3182,7 +3182,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) (curbuf->b_flags & BF_NOTEDITED) && !dontwrite ? _("[Not edited]") : "", (curbuf->b_flags & BF_NEW) && !dontwrite - ? new_file_message() : "", + ? _("[New]") : "", (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", curbuf->b_p_ro @@ -3221,7 +3221,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); } - (void)append_arg_number(curwin, buffer, IOSIZE, !shortmess(SHM_FILE)); + (void)append_arg_number(curwin, buffer, IOSIZE); if (dont_truncate) { // Temporarily set msg_scroll to avoid the message being truncated. @@ -3372,7 +3372,7 @@ void maketitle(void) *buf_p = NUL; } - append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)), false); + append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf))); xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf))); @@ -3509,15 +3509,14 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) } } -/// Append (file 2 of 8) to "buf[buflen]", if editing more than one file. +/// Append (2 of 8) to "buf[buflen]", if editing more than one file. /// /// @param wp window whose buffers to check /// @param[in,out] buf string buffer to add the text to /// @param buflen length of the string buffer -/// @param add_file if true, add "file" before the arg number /// /// @return true if it was appended. -bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) +bool append_arg_number(win_T *wp, char *buf, int buflen) FUNC_ATTR_NONNULL_ALL { // Nothing to do @@ -3525,17 +3524,7 @@ bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) return false; } - const char *msg; - switch ((wp->w_arg_idx_invalid ? 1 : 0) + (add_file ? 2 : 0)) { - case 0: - msg = _(" (%d of %d)"); break; - case 1: - msg = _(" ((%d) of %d)"); break; - case 2: - msg = _(" (file %d of %d)"); break; - case 3: - msg = _(" (file (%d) of %d)"); break; - } + const char *msg = wp->w_arg_idx_invalid ? _(" ((%d) of %d)") : _(" (%d of %d)"); char *p = buf + strlen(buf); // go to the end of the buffer vim_snprintf(p, (size_t)(buflen - (p - buf)), msg, wp->w_arg_idx + 1, ARGCOUNT); @@ -3616,21 +3605,28 @@ void ex_buffer_all(exarg_T *eap) } while (true) { tpnext = curtab->tp_next; - for (wp = firstwin; wp != NULL; wp = wpnext) { - wpnext = wp->w_next; + // Try to close floating windows first + for (wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) { + wpnext = wp->w_floating + ? wp->w_prev->w_floating ? wp->w_prev : firstwin + : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next; if ((wp->w_buffer->b_nwindows > 1 + || wp->w_floating || ((cmdmod.cmod_split & WSP_VERT) ? wp->w_height + wp->w_hsep_height + wp->w_status_height < Rows - p_ch - tabline_height() - global_stl_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) && !ONE_WINDOW - && !(wp->w_closing - || wp->w_buffer->b_locked > 0)) { - win_close(wp, false, false); - wpnext = firstwin; // just in case an autocommand does - // something strange with windows - tpnext = first_tabpage; // start all over... + && !(wp->w_closing || wp->w_buffer->b_locked > 0) + && !is_aucmd_win(wp)) { + if (win_close(wp, false, false) == FAIL) { + break; + } + // Just in case an autocommand does something strange with + // windows: start all over... + wpnext = lastwin->w_floating ? lastwin : firstwin; + tpnext = first_tabpage; open_wins = 0; } else { open_wins++; @@ -3650,7 +3646,8 @@ void ex_buffer_all(exarg_T *eap) // // Don't execute Win/Buf Enter/Leave autocommands here. autocmd_no_enter++; - win_enter(lastwin, false); + // lastwin may be aucmd_win + win_enter(lastwin_nofloating(), false); autocmd_no_leave++; for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) { // Check if this buffer needs a window @@ -3668,7 +3665,7 @@ void ex_buffer_all(exarg_T *eap) } else { // Check if this buffer already has a window for (wp = firstwin; wp != NULL; wp = wp->w_next) { - if (wp->w_buffer == buf) { + if (!wp->w_floating && wp->w_buffer == buf) { break; } } @@ -3742,7 +3739,7 @@ void ex_buffer_all(exarg_T *eap) // Close superfluous windows. for (wp = lastwin; open_wins > count;) { r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) - || autowrite(wp->w_buffer, false) == OK); + || autowrite(wp->w_buffer, false) == OK) && !is_aucmd_win(wp); if (!win_valid(wp)) { // BufWrite Autocommands made the window invalid, start over wp = lastwin; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3f55dbbc00..ff0fca1a56 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -954,7 +954,7 @@ typedef struct { WinStyle style; bool border; bool shadow; - schar_T border_chars[8]; + char border_chars[8][MAX_SCHAR_SIZE]; int border_hl_ids[8]; int border_attr[8]; bool title; @@ -966,6 +966,7 @@ typedef struct { VirtText footer_chunks; int footer_width; bool noautocmd; + bool fixed; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -975,7 +976,8 @@ typedef struct { .focusable = true, \ .zindex = kZIndexFloatDefault, \ .style = kWinStyleUnused, \ - .noautocmd = false }) + .noautocmd = false, \ + .fixed = false }) // Structure to store last cursor position and topline. Used by check_lnums() // and reset_lnums(). @@ -1241,6 +1243,8 @@ struct window_S { // this window, w_allbuf_opt is for all buffers in this window. winopt_T w_onebuf_opt; winopt_T w_allbuf_opt; + // transform a pointer to a "onebuf" option into a "allbuf" option +#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) // A few options have local flags for P_INSECURE. uint32_t w_p_stl_flags; // flags for 'statusline' @@ -1256,9 +1260,6 @@ struct window_S { int w_briopt_list; // additional indent for lists int w_briopt_vcol; // indent for specific column - // transform a pointer to a "onebuf" option into a "allbuf" option -#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) - long w_scbind_pos; ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary. diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 445e946543..0c95314c52 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1766,11 +1766,11 @@ restore_backup: xstrlcat(IObuff, _("[Device]"), IOSIZE); insert_space = true; } else if (newfile) { - xstrlcat(IObuff, new_file_message(), IOSIZE); + xstrlcat(IObuff, _("[New]"), IOSIZE); insert_space = true; } if (no_eol) { - msg_add_eol(); + xstrlcat(IObuff, _("[noeol]"), IOSIZE); insert_space = true; } // may add [unix/dos/mac] diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 9470f77ab5..1e088ec7fc 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -601,10 +601,10 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_grid; - grid_puts(grid, buf, row, 0, attr); + grid_puts(grid, buf, -1, row, 0, attr); if (selstart != NULL && highlight) { *selend = NUL; - grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + grid_puts(grid, selstart, -1, row, selstart_col, HL_ATTR(HLF_WM)); } grid_fill(grid, row, row + 1, clen, Columns, diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f4ca31040a..9d391cde8c 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -153,6 +153,24 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } +/// Get the next chunk of a virtual text item. +/// +/// @param[in] vt The virtual text item +/// @param[in,out] pos Position in the virtual text item +/// @param[in,out] attr Highlight attribute +/// +/// @return The text of the chunk, or NULL if there are no more chunks +char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) +{ + char *text = NULL; + for (; text == NULL && *pos < kv_size(vt); (*pos)++) { + text = kv_A(vt, *pos).text; + int hl_id = kv_A(vt, *pos).hl_id; + *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0); + } + return text; +} + Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) { MarkTreeIter itr[1] = { 0 }; @@ -254,7 +272,7 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r DecorRange range = { start_row, start_col, end_row, end_col, *decor, attr_id, - kv_size(decor->virt_text) && owned, -1, ns_id, mark_id }; + kv_size(decor->virt_text) && owned, -10, ns_id, mark_id }; kv_pushp(state->active); size_t index; @@ -268,10 +286,10 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } -/// Initialize the draw_col of a newly-added non-inline virtual text item. +/// Initialize the draw_col of a newly-added virtual text item. static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) { - if (win_col < 0) { + if (win_col < 0 && item->decor.virt_text_pos != kVTInline) { item->draw_col = win_col; } else if (item->decor.virt_text_pos == kVTOverlay) { item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; @@ -371,8 +389,7 @@ next_mark: spell = item.decor.spell; } if (item.start_row == state->row && item.start_col <= col - && decor_virt_pos(&item.decor) && item.draw_col == -1 - && item.decor.virt_text_pos != kVTInline) { + && decor_virt_pos(&item.decor) && item.draw_col == -10) { decor_init_draw_col(win_col, hidden, &item); } if (keep) { diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 0f191aa870..fef5ff7c2a 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -85,6 +85,7 @@ typedef struct { /// Screen column to draw the virtual text. /// When -1, the virtual text may be drawn after deciding where. /// When -3, the virtual text should be drawn on the next screen line. + /// When -10, the virtual text has just been added. /// When INT_MIN, the virtual text should no longer be drawn. int draw_col; uint64_t ns_id; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 811cfc1eb2..1e5798db32 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -106,6 +106,8 @@ typedef struct { int c_extra; ///< extra chars, all the same int c_final; ///< final char, mandatory if set + int n_closing; ///< number of chars in fdc which will be closing + bool extra_for_extmark; ///< n_extra set for inline virtual text // saved "extra" items for when draw_state becomes WL_LINE (again) @@ -221,11 +223,11 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b if (*p == TAB) { cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); for (int c = 0; c < cells; c++) { - schar_from_ascii(dest[c], ' '); + dest[rl ? -c : c] = schar_from_ascii(' '); } goto done; } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) { - schar_from_ascii(dest[0], *p); + dest[0] = schar_from_ascii(*p); s->prev_c = u8c; } else { if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { @@ -252,10 +254,10 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b } else { s->prev_c = u8c; } - schar_from_cc(dest[0], u8c, u8cc); + dest[0] = schar_from_cc(u8c, u8cc); } if (cells > 1) { - dest[1][0] = 0; + dest[rl ? -1 : 1] = 0; } done: s->p += c_len; @@ -275,12 +277,20 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int } if (item->draw_col == -1) { if (item->decor.virt_text_pos == kVTRightAlign) { - right_pos -= item->decor.virt_text_width; + if (wp->w_p_rl) { + right_pos += item->decor.virt_text_width; + } else { + right_pos -= item->decor.virt_text_width; + } item->draw_col = right_pos; } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { item->draw_col = state->eol_col; } else if (item->decor.virt_text_pos == kVTWinCol) { - item->draw_col = MAX(item->decor.col + col_off, 0); + if (wp->w_p_rl) { + item->draw_col = MIN(col_off - item->decor.col, wp->w_grid.cols - 1); + } else { + item->draw_col = MAX(col_off + item->decor.col, 0); + } } } if (item->draw_col < 0) { @@ -295,42 +305,44 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int } if (kv_size(item->decor.virt_text)) { col = draw_virt_text_item(buf, item->draw_col, item->decor.virt_text, - item->decor.hl_mode, max_col, item->draw_col - col_off); + item->decor.hl_mode, max_col, item->draw_col - col_off, wp->w_p_rl); } item->draw_col = INT_MIN; // deactivate if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - state->eol_col = col + 1; + if (wp->w_p_rl) { + state->eol_col = col - 1; + } else { + state->eol_col = col + 1; + } } - *end_col = MAX(*end_col, col); + if (wp->w_p_rl) { + *end_col = MIN(*end_col, col); + } else { + *end_col = MAX(*end_col, col); + } } } static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, - int vcol) + int vcol, bool rl) { LineState s = LINE_STATE(""); int virt_attr = 0; size_t virt_pos = 0; - while (col < max_col) { - if (!*s.p) { + while (rl ? col > max_col : col < max_col) { + if (*s.p == NUL) { if (virt_pos >= kv_size(vt)) { break; } virt_attr = 0; - do { - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_combine_attr(virt_attr, - hl_id > 0 ? syn_id2attr(hl_id) : 0); - virt_pos++; - } while (!s.p && virt_pos < kv_size(vt)); - if (!s.p) { + s.p = next_virt_text_chunk(vt, &virt_pos, &virt_attr); + if (s.p == NULL) { break; } } - if (!*s.p) { + if (*s.p == NUL) { continue; } int attr; @@ -344,21 +356,28 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, attr = virt_attr; } schar_T dummy[2]; + bool rl_overwrote_double_width = linebuf_char[col] == 0; int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], - max_col - col, false, vcol); + rl ? col - max_col : max_col - col, rl, vcol); // If we failed to emit a char, we still need to put a space and advance. if (cells < 1) { - schar_from_ascii(linebuf_char[col], ' '); + linebuf_char[col] = schar_from_ascii(' '); cells = 1; } for (int c = 0; c < cells; c++) { - linebuf_attr[col++] = attr; + linebuf_attr[col] = attr; + if (rl) { + col--; + } else { + col++; + } } - if (col < max_col && linebuf_char[col][0] == 0) { - // If the left half of a double-width char is overwritten, - // change the right half to a space so that grid redraws properly, - // but don't advance the current column. - schar_from_ascii(linebuf_char[col], ' '); + // If one half of a double-width char is overwritten, + // change the other half to a space so that grid redraws properly, + // but don't advance the current column. + if ((rl && col > max_col && rl_overwrote_double_width) + || (!rl && col < max_col && linebuf_char[col] == 0)) { + linebuf_char[col] = schar_from_ascii(' '); } vcol += cells; } @@ -384,7 +403,8 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv) // Allocate a buffer, "wlv->extra[]" may already be in use. xfree(wlv->p_extra_free); wlv->p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1); - wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum); + wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum, + &wlv->n_closing); wlv->p_extra_free[wlv->n_extra] = NUL; wlv->p_extra = wlv->p_extra_free; wlv->c_extra = NUL; @@ -405,7 +425,7 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv) /// /// Assume monocell characters /// @return number of chars added to \param p -size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) +size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing) { int i = 0; int level; @@ -447,16 +467,23 @@ size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) } } + int n_closing_val = i; + if (closed) { if (symbol != 0) { // rollback previous write char_counter -= (size_t)len; memset(&p[char_counter], ' ', (size_t)len); + n_closing_val--; } len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); char_counter += (size_t)len; } + if (n_closing) { + *n_closing = n_closing_val; + } + return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc); } @@ -917,17 +944,20 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t } } else { // already inside existing inline virtual text with multiple chunks - VirtTextChunk vtc = kv_A(wlv->virt_inline, wlv->virt_inline_i); - wlv->virt_inline_i++; - wlv->p_extra = vtc.text; - wlv->n_extra = (int)strlen(vtc.text); + int attr = 0; + char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr); + if (text == NULL) { + continue; + } + wlv->p_extra = text; + wlv->n_extra = (int)strlen(text); if (wlv->n_extra == 0) { continue; } wlv->c_extra = NUL; wlv->c_final = NUL; - wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; - wlv->n_attr = mb_charlen(vtc.text); + wlv->extra_attr = attr; + wlv->n_attr = mb_charlen(text); // If the text didn't reach until the first window // column we need to skip cells. if (wlv->skip_cells > 0) { @@ -1377,8 +1407,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl statuscol.width = win_col_off(wp) - (cmdwin_type != 0 && wp == curwin); statuscol.use_cul = use_cursor_line_highlight(wp, lnum); statuscol.sign_cul_id = statuscol.use_cul ? sign_cul.hl_id : 0; - statuscol.num_attr = sign_num.hl_id ? syn_id2attr(sign_num.hl_id) - : get_line_number_attr(wp, &wlv); + statuscol.num_attr = sign_num.hl_id > 0 ? syn_id2attr(sign_num.hl_id) : 0; } else { if (sign_cul.hl_id > 0) { sign_cul_attr = syn_id2attr(sign_cul.hl_id); @@ -1510,6 +1539,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl cts.cts_vcol += charsize; prev_ptr = cts.cts_ptr; MB_PTR_ADV(cts.cts_ptr); + if (wp->w_p_list) { + in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' ' + || (prev_ptr > line && prev_ptr[-1] == ' ')); + if (!in_multispace) { + multispace_pos = 0; + } else if (cts.cts_ptr >= line + leadcol + && wp->w_p_lcs_chars.multispace != NULL) { + multispace_pos++; + if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } else if (cts.cts_ptr < line + leadcol + && wp->w_p_lcs_chars.leadmultispace != NULL) { + multispace_pos++; + if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } + } } wlv.vcol = cts.cts_vcol; ptr = cts.cts_ptr; @@ -1665,13 +1713,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp); } } - if (!virt_line_offset) { + if (virt_line_offset == 0) { // Skip the column states if there is a "virt_left_col" line. wlv.draw_state = WL_BRI - 1; } else if (statuscol.draw) { // Skip fold, sign and number states if 'statuscolumn' is set. wlv.draw_state = WL_STC - 1; } + if (virt_line_offset >= 0 && wp->w_p_rl) { + virt_line_offset = wp->w_grid.cols - 1 - virt_line_offset; + } } if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) { @@ -1704,6 +1755,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.draw_state = WL_STC; // Draw the 'statuscolumn' if option is set. if (statuscol.draw) { + if (sign_num.hl_id == 0) { + statuscol.num_attr = get_line_number_attr(wp, &wlv); + } if (statuscol.textp == NULL) { v = (ptr - line); get_statuscol_str(wp, lnum, wlv.row - startrow - wlv.filler_lines, &statuscol); @@ -1759,7 +1813,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && wlv.vcol >= wp->w_virtcol) || (number_only && wlv.draw_state > WL_STC)) && wlv.filler_todo <= 0) { - draw_virt_text(wp, buf, win_col_offset, &wlv.col, grid->cols, wlv.row); + draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row); grid_put_linebuf(grid, wlv.row, 0, wlv.col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. @@ -2332,9 +2386,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } } - in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); - if (!in_multispace) { - multispace_pos = 0; + if (wp->w_p_list) { + in_multispace = c == ' ' && (*ptr == ' ' + || (prev_ptr > line && prev_ptr[-1] == ' ')); + if (!in_multispace) { + multispace_pos = 0; + } } // 'list': Change char 160 to 'nbsp' and space to 'space'. @@ -2737,7 +2794,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.col += n; } else { // Add a blank character to highlight. - schar_from_ascii(linebuf_char[wlv.off], ' '); + linebuf_char[wlv.off] = schar_from_ascii(' '); } if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among @@ -2795,7 +2852,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl ? 1 : 0); if (has_decor) { - has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip); + has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, + wlv.col + (wp->w_p_rl ? -eol_skip : eol_skip)); } if (((wp->w_p_cuc @@ -2839,7 +2897,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl int col_stride = wp->w_p_rl ? -1 : 1; while (wp->w_p_rl ? wlv.col >= 0 : wlv.col < grid->cols) { - schar_from_ascii(linebuf_char[wlv.off], ' '); + linebuf_char[wlv.off] = schar_from_ascii(' '); linebuf_vcol[wlv.off] = MAXCOL; wlv.col += col_stride; if (draw_color_col) { @@ -2873,7 +2931,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl // logical line int n = wp->w_p_rl ? -1 : 1; while (wlv.col >= 0 && wlv.col < grid->cols) { - schar_from_ascii(linebuf_char[wlv.off], ' '); + linebuf_char[wlv.off] = schar_from_ascii(' '); linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol]; linebuf_vcol[wlv.off] = wlv.vcol; wlv.off += n; @@ -2883,9 +2941,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } if (kv_size(fold_vt) > 0) { - draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0); + draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, + wp->w_p_rl ? -1 : grid->cols, 0, wp->w_p_rl); } - draw_virt_text(wp, buf, win_col_offset, &wlv.col, grid->cols, wlv.row); + draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row); grid_put_linebuf(grid, wlv.row, 0, wlv.col, grid->cols, wp->w_p_rl, wp, bg_attr, false); wlv.row++; @@ -2973,9 +3032,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.col--; } if (mb_utf8) { - schar_from_cc(linebuf_char[wlv.off], mb_c, u8cc); + linebuf_char[wlv.off] = schar_from_cc(mb_c, u8cc); } else { - schar_from_ascii(linebuf_char[wlv.off], (char)c); + linebuf_char[wlv.off] = schar_from_ascii((char)c); } if (multi_attr) { linebuf_attr[wlv.off] = multi_attr; @@ -2984,26 +3043,39 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl linebuf_attr[wlv.off] = wlv.char_attr; } - linebuf_vcol[wlv.off] = wlv.vcol; + if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) { + linebuf_vcol[wlv.off] = wlv.vcol; + } else if (wlv.draw_state == WL_FOLD) { + if (wlv.n_closing > 0) { + linebuf_vcol[wlv.off] = -3; + wlv.n_closing--; + } else { + linebuf_vcol[wlv.off] = -2; + } + } else { + linebuf_vcol[wlv.off] = -1; + } if (utf_char2cells(mb_c) > 1) { // Need to fill two screen columns. wlv.off++; wlv.col++; // UTF-8: Put a 0 in the second screen char. - linebuf_char[wlv.off][0] = 0; + linebuf_char[wlv.off] = 0; linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1]; + if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) { - wlv.vcol++; + linebuf_vcol[wlv.off] = ++wlv.vcol; + } else { + linebuf_vcol[wlv.off] = -1; } + // When "wlv.tocol" is halfway through a character, set it to the end // of the character, otherwise highlighting won't stop. if (wlv.tocol == wlv.vcol) { wlv.tocol++; } - linebuf_vcol[wlv.off] = wlv.vcol; - if (wp->w_p_rl) { // now it's time to backup one cell wlv.off--; @@ -3141,9 +3213,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl int draw_col = wlv.col - wlv.boguscols; if (virt_line_offset >= 0) { draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, - kHlModeReplace, grid->cols, 0); + kHlModeReplace, wp->w_p_rl ? -1 : grid->cols, 0, wp->w_p_rl); } else if (wlv.filler_todo <= 0) { - draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, wlv.row); + draw_virt_text(wp, buf, win_col_offset, &draw_col, wp->w_p_rl ? -1 : grid->cols, wlv.row); } grid_put_linebuf(grid, wlv.row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap); @@ -3154,9 +3226,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl // Force a redraw of the first column of the next line. current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; - - // Remember that the line wraps, used for modeless copy. - current_grid->line_wraps[current_row] = true; } wlv.boguscols = 0; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index f71a47a596..04918e9979 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -215,7 +215,6 @@ void screenclear(void) for (int i = 0; i < default_grid.rows; i++) { grid_clear_line(&default_grid, default_grid.line_offset[i], default_grid.cols, true); - default_grid.line_wraps[i] = false; } ui_call_grid_clear(1); // clear the display @@ -444,6 +443,15 @@ int update_screen(void) display_tick++; // let syntax code know we're in a next round of // display updating + // glyph cache full, very rare + if (schar_cache_clear_if_full()) { + // must use CLEAR, as the contents of screen buffers cannot be + // compared to their previous state here. + // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs) + // we need to revalidate these here as well! + type = MAX(type, UPD_CLEAR); + } + // Tricky: vim code can reset msg_scrolled behind our back, so need // separate bookkeeping for now. if (msg_did_scroll) { @@ -463,7 +471,7 @@ int update_screen(void) // non-displayed part of msg_grid is considered invalid. for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { grid_clear_line(&msg_grid, msg_grid.line_offset[i], - msg_grid.cols, false); + msg_grid.cols, i < p_ch); } } msg_grid.throttled = false; @@ -701,15 +709,15 @@ void end_search_hl(void) screen_search_hl.rm.regprog = NULL; } -static void win_redr_bordertext(win_T *wp, ScreenGrid *grid, VirtText text_chunks, int row, int col) +static void win_redr_bordertext(win_T *wp, VirtText vt, int col) { - for (size_t i = 0; i < text_chunks.size; i++) { - char *text = text_chunks.items[i].text; - int cell = (int)mb_string2cells(text); - int hl_id = text_chunks.items[i].hl_id; - int attr = hl_id ? syn_id2attr(hl_id) : 0; - grid_puts(grid, text, row, col, attr); - col += cell; + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *text = next_virt_text_chunk(vt, &i, &attr); + if (text == NULL) { + break; + } + col += grid_line_puts(col, text, -1, attr); } } @@ -736,67 +744,70 @@ static void win_redr_border(win_T *wp) ScreenGrid *grid = &wp->w_grid_alloc; - schar_T *chars = wp->w_float_config.border_chars; + schar_T chars[8]; + for (int i = 0; i < 8; i++) { + chars[i] = schar_from_str(wp->w_float_config.border_chars[i]); + } int *attrs = wp->w_float_config.border_attr; int *adj = wp->w_border_adj; int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; if (adj[0]) { - grid_puts_line_start(grid, 0); + grid_line_start(grid, 0); if (adj[3]) { - grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + grid_line_put_schar(0, chars[0], attrs[0]); } for (int i = 0; i < icol; i++) { - grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); + grid_line_put_schar(i + adj[3], chars[1], attrs[1]); } if (wp->w_float_config.title) { int title_col = win_get_bordertext_col(icol, wp->w_float_config.title_width, wp->w_float_config.title_pos); - win_redr_bordertext(wp, grid, wp->w_float_config.title_chunks, 0, title_col); + win_redr_bordertext(wp, wp->w_float_config.title_chunks, title_col); } if (adj[1]) { - grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); + grid_line_put_schar(icol + adj[3], chars[2], attrs[2]); } - grid_puts_line_flush(false); + grid_line_flush(false); } for (int i = 0; i < irow; i++) { if (adj[3]) { - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); - grid_puts_line_flush(false); + grid_line_start(grid, i + adj[0]); + grid_line_put_schar(0, chars[7], attrs[7]); + grid_line_flush(false); } if (adj[1]) { - int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); - grid_puts_line_flush(false); + int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3; + grid_line_start(grid, i + adj[0]); + grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]); + grid_line_flush(false); } } if (adj[2]) { - grid_puts_line_start(grid, irow + adj[0]); + grid_line_start(grid, irow + adj[0]); if (adj[3]) { - grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); + grid_line_put_schar(0, chars[6], attrs[6]); } for (int i = 0; i < icol; i++) { - int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; - grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); + int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5; + grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]); } if (wp->w_float_config.footer) { int footer_col = win_get_bordertext_col(icol, wp->w_float_config.footer_width, wp->w_float_config.footer_pos); - win_redr_bordertext(wp, grid, wp->w_float_config.footer_chunks, grid->rows - 1, footer_col); + win_redr_bordertext(wp, wp->w_float_config.footer_chunks, footer_col); } if (adj[1]) { - grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); + grid_line_put_schar(icol + adj[3], chars[4], attrs[4]); } - grid_puts_line_flush(false); + grid_line_flush(false); } } @@ -919,7 +930,9 @@ int showmode(void) || (State & MODE_INSERT) || restart_edit != NUL || VIsual_active)); - if (do_mode || reg_recording != 0) { + + bool can_show_mode = (p_ch != 0 || ui_has(kUIMessages)); + if ((do_mode || reg_recording != 0) && can_show_mode) { int sub_attr; if (skip_showmode()) { return 0; // show mode later @@ -1294,23 +1307,25 @@ static void draw_hsep_win(win_T *wp) } /// Get the separator connector for specified window corner of window "wp" -static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +static schar_T get_corner_sep_connector(win_T *wp, WindowCorner corner) { // It's impossible for windows to be connected neither vertically nor horizontally // So if they're not vertically connected, assume they're horizontally connected + int c; if (vsep_connected(wp, corner)) { if (hsep_connected(wp, corner)) { - return wp->w_p_fcs_chars.verthoriz; + c = wp->w_p_fcs_chars.verthoriz; } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { - return wp->w_p_fcs_chars.vertright; + c = wp->w_p_fcs_chars.vertright; } else { - return wp->w_p_fcs_chars.vertleft; + c = wp->w_p_fcs_chars.vertleft; } } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { - return wp->w_p_fcs_chars.horizdown; + c = wp->w_p_fcs_chars.horizdown; } else { - return wp->w_p_fcs_chars.horizup; + c = wp->w_p_fcs_chars.horizup; } + return schar_from_char(c); } /// Draw separator connecting characters on the corners of window "wp" @@ -1346,21 +1361,31 @@ static void draw_sep_connectors_win(win_T *wp) win_at_left = frp->fr_parent == NULL; // Draw the appropriate separator connector in every corner where drawing them is necessary - if (!(win_at_top || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), - wp->w_winrow - 1, wp->w_wincol - 1, hl); - } - if (!(win_at_top || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), - wp->w_winrow - 1, W_ENDCOL(wp), hl); - } - if (!(win_at_bottom || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), - W_ENDROW(wp), wp->w_wincol - 1, hl); + // Make sure not to send cursor position updates to ui. + bool top_left = !(win_at_top || win_at_left); + bool top_right = !(win_at_top || win_at_right); + bool bot_left = !(win_at_bottom || win_at_left); + bool bot_right = !(win_at_bottom || win_at_right); + + if (top_left || top_right) { + grid_line_start(&default_grid, wp->w_winrow - 1); + if (top_left) { + grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl); + } + if (top_right) { + grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl); + } + grid_line_flush(false); } - if (!(win_at_bottom || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), - W_ENDROW(wp), W_ENDCOL(wp), hl); + if (bot_left || bot_right) { + grid_line_start(&default_grid, W_ENDROW(wp)); + if (bot_left) { + grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl); + } + if (bot_right) { + grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl); + } + grid_line_flush(false); } } @@ -2357,7 +2382,7 @@ static void win_update(win_T *wp, DecorProviders *providers) utf_char2bytes(symbol, &fillbuf[charlen]); // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); + grid_puts(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 216f8a67db..520d3bc2b3 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1449,6 +1449,7 @@ void edit_putchar(int c, bool highlight) // save the character to be able to put it back if (pc_status == PC_STATUS_UNSET) { + // TODO(bfredl): save the schar_T instead grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr); pc_status = PC_STATUS_SET; } @@ -1532,7 +1533,7 @@ void edit_unputchar(void) if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) { redrawWinline(curwin, curwin->w_cursor.lnum); } else { - grid_puts(&curwin->w_grid, pc_bytes, pc_row, pc_col, pc_attr); + grid_puts(&curwin->w_grid, pc_bytes, -1, pc_row, pc_col, pc_attr); } } } @@ -3485,7 +3486,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Otherwise remove the mode message. if (reg_recording != 0 || restart_edit != NUL) { showmode(); - } else if (p_smd && (got_int || !skip_showmode())) { + } else if (p_smd && (got_int || !skip_showmode()) + && !(p_ch == 0 && !ui_has(kUIMessages))) { msg(""); } // Exit Insert mode diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a279b6d051..21c7cdee7d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1271,8 +1271,11 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv) /// it in "*cp". Doesn't give error messages. int eval_foldexpr(win_T *wp, int *cp) { + const sctx_T saved_sctx = current_sctx; const bool use_sandbox = was_set_insecurely(wp, "foldexpr", OPT_LOCAL); + char *arg = wp->w_p_fde; + current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx; emsg_off++; if (use_sandbox) { @@ -1309,6 +1312,7 @@ int eval_foldexpr(win_T *wp, int *cp) } textlock--; clear_evalarg(&EVALARG_EVALUATE, NULL); + current_sctx = saved_sctx; return (int)retval; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 2137dc07e4..858f7c8afd 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -5572,7 +5572,7 @@ M.funcs = { jobstart = { args = { 1, 2 }, desc = [=[ - Note: Prefer |vim.system()| in Lua. + Note: Prefer |vim.system()| in Lua (unless using the `pty` option). Spawns {cmd} as a job. If {cmd} is a List it runs directly (no 'shell'). @@ -10973,7 +10973,7 @@ M.funcs = { echo strutf16len('😊') " returns 2 echo strutf16len('ą́') " returns 1 echo strutf16len('ą́', v:true) " returns 3 - + < ]=], name = 'strutf16len', params = { { 'string', 'string' }, { 'countcc', '0|1' } }, diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index e5b1b88eef..5d1da956ee 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -52,8 +52,8 @@ static const char *e_letunexp = N_("E18: Unexpected characters in :let"); static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); -static const char e_setting_str_to_value_with_wrong_type[] - = N_("E963: Setting %s to value with wrong type"); +static const char e_setting_v_str_to_value_with_wrong_type[] + = N_("E963: Setting v:%s to value with wrong type"); static const char e_cannot_use_heredoc_here[] = N_("E991: Cannot use =<< here"); @@ -1389,6 +1389,62 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t } } +/// Additional handling for setting a v: variable. +/// +/// @return true if the variable should be set normally, +/// false if nothing else needs to be done. +bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv, + const bool copy, const bool watched, bool *const type_error) +{ + if (di->di_tv.v_type == VAR_STRING) { + typval_T oldtv = TV_INITIAL_VALUE; + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + XFREE_CLEAR(di->di_tv.vval.v_string); + if (copy || tv->v_type != VAR_STRING) { + const char *const val = tv_get_string(tv); + // Careful: when assigning to v:errmsg and tv_get_string() + // causes an error message the variable will already be set. + if (di->di_tv.vval.v_string == NULL) { + di->di_tv.vval.v_string = xstrdup(val); + } + } else { + // Take over the string to avoid an extra alloc/free. + di->di_tv.vval.v_string = tv->vval.v_string; + tv->vval.v_string = NULL; + } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv); + tv_clear(&oldtv); + } + return false; + } else if (di->di_tv.v_type == VAR_NUMBER) { + typval_T oldtv = TV_INITIAL_VALUE; + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + di->di_tv.vval.v_number = tv_get_number(tv); + if (strcmp(varname, "searchforward") == 0) { + set_search_direction(di->di_tv.vval.v_number ? '/' : '?'); + } else if (strcmp(varname, "hlsearch") == 0) { + no_hlsearch = !di->di_tv.vval.v_number; + redraw_all_later(UPD_SOME_VALID); + } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv); + tv_clear(&oldtv); + } + return false; + } else if (di->di_tv.v_type != tv->v_type) { + *type_error = true; + return false; + } + return true; +} + /// Set variable to the given value /// /// If the variable already exists, the value is updated. Otherwise the variable @@ -1418,31 +1474,29 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, const bool is_const) FUNC_ATTR_NONNULL_ALL { - dictitem_T *v; - hashtab_T *ht; - dict_T *dict; - const char *varname; - ht = find_var_ht_dict(name, name_len, &varname, &dict); + dict_T *dict; + hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict); const bool watched = tv_dict_is_watched(dict); if (ht == NULL || *varname == NUL) { semsg(_(e_illvar), name); return; } - v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true); + const size_t varname_len = name_len - (size_t)(varname - name); + dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true); // Search in parent scope which is possible to reference from lambda - if (v == NULL) { - v = find_var_in_scoped_ht(name, name_len, true); + if (di == NULL) { + di = find_var_in_scoped_ht(name, name_len, true); } - if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) { + if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) { return; } typval_T oldtv = TV_INITIAL_VALUE; - if (v != NULL) { + if (di != NULL) { if (is_const) { emsg(_(e_cannot_mod)); return; @@ -1452,9 +1506,9 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // - Whether the variable is read-only // - Whether the variable value is locked // - Whether the variable is locked - if (var_check_ro(v->di_flags, name, name_len) - || value_check_lock(v->di_tv.v_lock, name, name_len) - || var_check_lock(v->di_flags, name, name_len)) { + if (var_check_ro(di->di_flags, name, name_len) + || value_check_lock(di->di_tv.v_lock, name, name_len) + || var_check_lock(di->di_flags, name, name_len)) { return; } @@ -1462,42 +1516,19 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Handle setting internal v: variables separately where needed to // prevent changing the type. - if (is_vimvarht(ht)) { - if (v->di_tv.v_type == VAR_STRING) { - XFREE_CLEAR(v->di_tv.vval.v_string); - if (copy || tv->v_type != VAR_STRING) { - const char *const val = tv_get_string(tv); - - // Careful: when assigning to v:errmsg and tv_get_string() - // causes an error message the variable will already be set. - if (v->di_tv.vval.v_string == NULL) { - v->di_tv.vval.v_string = xstrdup(val); - } - } else { - // Take over the string to avoid an extra alloc/free. - v->di_tv.vval.v_string = tv->vval.v_string; - tv->vval.v_string = NULL; - } - return; - } else if (v->di_tv.v_type == VAR_NUMBER) { - v->di_tv.vval.v_number = tv_get_number(tv); - if (strcmp(varname, "searchforward") == 0) { - set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); - } else if (strcmp(varname, "hlsearch") == 0) { - no_hlsearch = !v->di_tv.vval.v_number; - redraw_all_later(UPD_SOME_VALID); - } - return; - } else if (v->di_tv.v_type != tv->v_type) { - semsg(_(e_setting_str_to_value_with_wrong_type), name); - return; + bool type_error = false; + if (is_vimvarht(ht) + && !before_set_vvar(varname, di, tv, copy, watched, &type_error)) { + if (type_error) { + semsg(_(e_setting_v_str_to_value_with_wrong_type), varname); } + return; } if (watched) { - tv_copy(&v->di_tv, &oldtv); + tv_copy(&di->di_tv, &oldtv); } - tv_clear(&v->di_tv); + tv_clear(&di->di_tv); } else { // Add a new variable. // Can't add "v:" or "a:" variable. if (is_vimvarht(ht) || ht == get_funccal_args_ht()) { @@ -1513,28 +1544,28 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Make sure dict is valid assert(dict != NULL); - v = xmalloc(offsetof(dictitem_T, di_key) + strlen(varname) + 1); - STRCPY(v->di_key, varname); - if (hash_add(ht, v->di_key) == FAIL) { - xfree(v); + di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1); + memcpy(di->di_key, varname, varname_len + 1); + if (hash_add(ht, di->di_key) == FAIL) { + xfree(di); return; } - v->di_flags = DI_FLAGS_ALLOC; + di->di_flags = DI_FLAGS_ALLOC; if (is_const) { - v->di_flags |= DI_FLAGS_LOCK; + di->di_flags |= DI_FLAGS_LOCK; } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { - tv_copy(tv, &v->di_tv); + tv_copy(tv, &di->di_tv); } else { - v->di_tv = *tv; - v->di_tv.v_lock = VAR_UNLOCKED; + di->di_tv = *tv; + di->di_tv.v_lock = VAR_UNLOCKED; tv_init(tv); } if (watched) { - tv_dict_watcher_notify(dict, v->di_key, &v->di_tv, &oldtv); + tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv); tv_clear(&oldtv); } @@ -1542,7 +1573,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Like :lockvar! name: lock the value and what it contains, but only // if the reference count is up to one. That locks only literal // values. - tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true); + tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true); } } diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 95bf4d1c3b..00ba1334b0 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -423,6 +423,7 @@ static void exit_event(void **argv) if (!exiting) { if (ui_client_channel_id) { + ui_client_exit_status = status; os_exit(status); } else { assert(status == 0); // Called from rpc_close(), which passes 0 as status. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4f6b8f2c8f..dc7136196e 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2689,7 +2689,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Obey the 'O' flag in 'cpoptions': overwrite any previous file // message. - if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { + if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) { msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index b37076c62e..86934a645a 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -455,6 +455,10 @@ void ex_listdo(exarg_T *eap) tabpage_T *tp; char *save_ei = NULL; + // Temporarily override SHM_OVER and SHM_OVERALL to avoid that file + // message overwrites output from the command. + msg_listdo_overwrite++; + if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo) { // Don't do syntax HL autocommands. Skipping the syntax file is a // great speed improvement. @@ -518,9 +522,7 @@ void ex_listdo(exarg_T *eap) if (qf_size == 0 || (size_t)eap->line1 > qf_size) { buf = NULL; } else { - save_clear_shm_value(); ex_cc(eap); - restore_shm_value(); buf = curbuf; i = (int)eap->line1 - 1; @@ -547,9 +549,7 @@ void ex_listdo(exarg_T *eap) if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { // Clear 'shm' to avoid that the file message overwrites // any output from the command. - save_clear_shm_value(); do_argfile(eap, i); - restore_shm_value(); } if (curwin->w_arg_idx != i) { break; @@ -612,11 +612,8 @@ void ex_listdo(exarg_T *eap) break; } - // Go to the next buffer. Clear 'shm' to avoid that the file - // message overwrites any output from the command. - save_clear_shm_value(); + // Go to the next buffer. goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); - restore_shm_value(); // If autocommands took us elsewhere, quit here. if (curbuf->b_fnum != next_fnum) { @@ -633,11 +630,7 @@ void ex_listdo(exarg_T *eap) size_t qf_idx = qf_get_cur_idx(eap); - // Clear 'shm' to avoid that the file message overwrites - // any output from the command. - save_clear_shm_value(); ex_cnext(eap); - restore_shm_value(); // If jumping to the next quickfix entry fails, quit here. if (qf_get_cur_idx(eap) == qf_idx) { @@ -664,6 +657,7 @@ void ex_listdo(exarg_T *eap) listcmd_busy = false; } + msg_listdo_overwrite--; if (save_ei != NULL) { buf_T *bnext; aco_save_T aco; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 44610c81d8..ea93d0fe91 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -906,7 +906,7 @@ void handle_did_throw(void) if (messages != NULL) { do { msglist_T *next = messages->next; - emsg(messages->msg); + emsg_multiline(messages->msg, messages->multiline); xfree(messages->msg); xfree(messages->sfile); xfree(messages); @@ -4591,7 +4591,9 @@ static void ex_cquit(exarg_T *eap) FUNC_ATTR_NORETURN { // this does not always pass on the exit code to the Manx compiler. why? - getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); + int status = eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE; + ui_call_error_exit(status); + getout(status); } /// Do preparations for "qall" and "wqall". diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 1b150ef75d..0704b47d40 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -157,7 +157,7 @@ int aborted_in_try(void) /// When several messages appear in the same command, the first is usually the /// most specific one and used as the exception value. The "severe" flag can be /// set to true, if a later but severer message should be used instead. -bool cause_errthrow(const char *mesg, bool severe, bool *ignore) +bool cause_errthrow(const char *mesg, bool multiline, bool severe, bool *ignore) FUNC_ATTR_NONNULL_ALL { msglist_T *elem; @@ -249,6 +249,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) elem = xmalloc(sizeof(msglist_T)); elem->msg = xstrdup(mesg); + elem->multiline = multiline; elem->next = NULL; elem->throw_msg = NULL; *plist = elem; diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h index 6b3c426722..3ad3368900 100644 --- a/src/nvim/ex_eval_defs.h +++ b/src/nvim/ex_eval_defs.h @@ -1,6 +1,8 @@ #ifndef NVIM_EX_EVAL_DEFS_H #define NVIM_EX_EVAL_DEFS_H +#include <stdbool.h> + #include "nvim/pos.h" /// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" @@ -41,11 +43,12 @@ enum { /// message in the list. See cause_errthrow(). typedef struct msglist msglist_T; struct msglist { + msglist_T *next; ///< next of several messages in a row char *msg; ///< original message, allocated char *throw_msg; ///< msg to throw: usually original one char *sfile; ///< value from estack_sfile(), allocated linenr_T slnum; ///< line number for "sfile" - msglist_T *next; ///< next of several messages in a row + bool multiline; ///< whether this is a multiline message }; /// The exception types. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 5d7ddb657a..7ff3e0ec6e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -105,7 +105,7 @@ void filemess(buf_T *buf, char *name, char *s, int attr) // For further ones overwrite the previous one, reset msg_scroll before // calling filemess(). msg_scroll_save = msg_scroll; - if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { + if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) { msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg @@ -316,7 +316,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } - if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { + if (((shortmess(SHM_OVER) && !msg_listdo_overwrite) || curbuf->b_help) && p_verbose == 0) { msg_scroll = false; // overwrite previous file message } else { msg_scroll = true; // don't overwrite previous file message @@ -459,7 +459,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } if (!silent) { if (dir_of_file_exists(fname)) { - filemess(curbuf, sfname, new_file_message(), 0); + filemess(curbuf, sfname, _("[New]"), 0); } else { filemess(curbuf, sfname, _("[New DIRECTORY]"), 0); } @@ -1718,7 +1718,7 @@ failed: c = true; } if (read_no_eol_lnum) { - msg_add_eol(); + xstrlcat(IObuff, _("[noeol]"), IOSIZE); c = true; } if (ff_error == EOL_DOS) { @@ -2064,11 +2064,6 @@ static void check_marks_read(void) curbuf->b_marks_read = true; } -char *new_file_message(void) -{ - return shortmess(SHM_NEW) ? _("[New]") : _("[New File]"); -} - /// Set the name of the current buffer. Use when the buffer doesn't have a /// name and a ":r" or ":w" command with a file name is used. int set_rw_fname(char *fname, char *sfname) @@ -2142,17 +2137,17 @@ bool msg_add_fileformat(int eol_type) { #ifndef USE_CRNL if (eol_type == EOL_DOS) { - xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"), IOSIZE); + xstrlcat(IObuff, _("[dos]"), IOSIZE); return true; } #endif if (eol_type == EOL_MAC) { - xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"), IOSIZE); + xstrlcat(IObuff, _("[mac]"), IOSIZE); return true; } #ifdef USE_CRNL if (eol_type == EOL_UNIX) { - xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"), IOSIZE); + xstrlcat(IObuff, _("[unix]"), IOSIZE); return true; } #endif @@ -2181,12 +2176,6 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) } } -/// Append message for missing line separator to IObuff. -void msg_add_eol(void) -{ - xstrlcat(IObuff, shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"), IOSIZE); -} - bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST { #if defined(__linux__) || defined(MSWIN) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 1d5ba49301..d874e904d0 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -3344,8 +3344,13 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (kv_size(vt) > 0) { assert(*text == NUL); - for (size_t i = 0; i < kv_size(vt); i++) { - char *new_text = concat_str(text, kv_A(vt, i).text); + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *new_text = next_virt_text_chunk(vt, &i, &attr); + if (new_text == NULL) { + break; + } + new_text = concat_str(text, new_text); xfree(text); text = new_text; } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index fa7f270172..2eeefab27d 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -17,6 +17,7 @@ #include "nvim/arabic.h" #include "nvim/buffer_defs.h" +#include "nvim/drawscreen.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" @@ -36,6 +37,15 @@ // Per-cell attributes static size_t linebuf_size = 0; +// Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string. +// Then it instead stores an index into glyph_cache.keys[] which is a flat char array. +// The hash part is used by schar_from_buf() to quickly lookup glyphs which already +// has been interned. schar_get() should used to convert a schar_T value +// back to a string buffer. +// +// The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL). +static Set(glyph) glyph_cache = SET_INIT; + /// Determine if dedicated window grid should be used or the default_grid /// /// If UI did not request multigrid support, draw all windows on the @@ -56,25 +66,119 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off) } /// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell. -int schar_from_cc(char *p, int c, int u8cc[MAX_MCO]) +schar_T schar_from_cc(int c, int u8cc[MAX_MCO]) { - int len = utf_char2bytes(c, p); + char buf[MAX_SCHAR_SIZE]; + int len = utf_char2bytes(c, buf); for (int i = 0; i < MAX_MCO; i++) { if (u8cc[i] == 0) { break; } - len += utf_char2bytes(u8cc[i], p + len); + len += utf_char2bytes(u8cc[i], buf + len); + } + buf[len] = 0; + return schar_from_buf(buf, (size_t)len); +} + +schar_T schar_from_str(char *str) +{ + if (str == NULL) { + return 0; + } + return schar_from_buf(str, strlen(str)); +} + +/// @param buf need not be NUL terminated, but may not contain embedded NULs. +/// +/// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte) +schar_T schar_from_buf(const char *buf, size_t len) +{ + assert(len < MAX_SCHAR_SIZE); + if (len <= 4) { + schar_T sc = 0; + memcpy((char *)&sc, buf, len); + return sc; + } else { + String str = { .data = (char *)buf, .size = len }; + + MHPutStatus status; + uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status); + assert(idx < 0xFFFFFF); +#ifdef ORDER_BIG_ENDIAN + return idx + ((uint32_t)0xFF << 24); +#else + return 0xFF + (idx << 8); +#endif } - p[len] = 0; - return len; } +/// Check if cache is full, and if it is, clear it. +/// +/// This should normally only be called in update_screen() +/// +/// @return true if cache was clered, and all your screen buffers now are hosed +/// and you need to use UPD_CLEAR +bool schar_cache_clear_if_full(void) +{ + // note: critical max is really (1<<24)-1. This gives us some marginal + // until next time update_screen() is called + if (glyph_cache.h.n_keys > (1<<21)) { + set_clear(glyph, &glyph_cache); + return true; + } + return false; +} + +/// For testing. The condition in schar_cache_clear_force is hard to +/// reach, so this function can be used to force a cache clear in a test. +void schar_cache_clear_force(void) +{ + set_clear(glyph, &glyph_cache); + must_redraw = UPD_CLEAR; +} + +bool schar_high(schar_T sc) +{ +#ifdef ORDER_BIG_ENDIAN + return ((sc & 0xFF000000) == 0xFF000000); +#else + return ((sc & 0xFF) == 0xFF); +#endif +} + +void schar_get(char *buf_out, schar_T sc) +{ + if (schar_high(sc)) { +#ifdef ORDER_BIG_ENDIAN + uint32_t idx = sc & (0x00FFFFFF); +#else + uint32_t idx = sc >> 8; +#endif + if (idx >= glyph_cache.h.n_keys) { + abort(); + } + xstrlcpy(buf_out, &glyph_cache.keys[idx], 32); + } else { + memcpy(buf_out, (char *)&sc, 4); + buf_out[4] = NUL; + } +} + +/// @return ascii char or NUL if not ascii +char schar_get_ascii(schar_T sc) +{ +#ifdef ORDER_BIG_ENDIAN + return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL; +#else + return (sc < 0x80) ? (char)sc : NUL; +#endif +} /// clear a line in the grid starting at "off" until "width" characters /// are cleared. void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid) { for (int col = 0; col < width; col++) { - schar_from_ascii(grid->chars[off + (size_t)col], ' '); + grid->chars[off + (size_t)col] = schar_from_ascii(' '); } int fill = valid ? 0 : -1; (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); @@ -93,7 +197,7 @@ bool grid_invalid_row(ScreenGrid *grid, int row) static int line_off2cells(schar_T *line, size_t off, size_t max_off) { - return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1; + return (off + 1 < max_off && line[off + 1] == 0) ? 2 : 1; } /// Return number of display cells for char at grid->chars[off]. @@ -124,7 +228,7 @@ int grid_fix_col(ScreenGrid *grid, int col, int row) col += coloff; if (grid->chars != NULL && col > 0 - && grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) { + && grid->chars[grid->line_offset[row] + (size_t)col] == 0) { return col - 1 - coloff; } return col - coloff; @@ -135,8 +239,7 @@ void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) { char buf[MB_MAXBYTES + 1]; - buf[utf_char2bytes(c, buf)] = NUL; - grid_puts(grid, buf, row, col, attr); + grid_puts(grid, buf, utf_char2bytes(c, buf), row, col, attr); } /// Get a single character directly from grid.chars into "bytes", which must @@ -155,54 +258,84 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp) if (attrp != NULL) { *attrp = grid->attrs[off]; } - schar_copy(bytes, grid->chars[off]); + schar_get(bytes, grid->chars[off]); } -/// put string '*text' on the window grid at position 'row' and 'col', with -/// attributes 'attr', and update chars[] and attrs[]. -/// Note: only outputs within one row, message is truncated at grid boundary! -/// Note: if grid, row and/or col is invalid, nothing is done. -int grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr) +static bool check_grid(ScreenGrid *grid, int row, int col) { - return grid_puts_len(grid, text, -1, row, col, attr); + grid_adjust(&grid, &row, &col); + // Safety check. The check for negative row and column is to fix issue + // vim/vim#4102. TODO(neovim): find out why row/col could be negative. + if (grid->chars == NULL + || row >= grid->rows || row < 0 + || col >= grid->cols || col < 0) { + return false; + } + return true; } -static ScreenGrid *put_dirty_grid = NULL; -static int put_dirty_row = -1; -static int put_dirty_first = INT_MAX; -static int put_dirty_last = 0; +/// put string 'text' on the window grid at position 'row' and 'col', with +/// attributes 'attr', and update contents of 'grid' +/// @param textlen length of string or -1 to use strlen(text) +/// Note: only outputs within one row! +int grid_puts(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr) +{ + if (!check_grid(grid, row, col)) { + if (rdb_flags & RDB_INVALID) { + abort(); + } + return 0; + } + + grid_line_start(grid, row); + int len = grid_line_puts(col, text, textlen, attr); + grid_line_flush(true); + return len; +} -/// Start a group of grid_puts_len calls that builds a single grid line. +static ScreenGrid *grid_line_grid = NULL; +static int grid_line_row = -1; +static int grid_line_coloff = 0; +static int grid_line_first = INT_MAX; +static int grid_line_last = 0; +static bool grid_line_was_invalid = false; + +/// Start a group of grid_line_puts calls that builds a single grid line. /// -/// Must be matched with a grid_puts_line_flush call before moving to +/// Must be matched with a grid_line_flush call before moving to /// another line. -void grid_puts_line_start(ScreenGrid *grid, int row) +void grid_line_start(ScreenGrid *grid, int row) { - int col = 0; // unused + int col = 0; grid_adjust(&grid, &row, &col); - assert(put_dirty_row == -1); - put_dirty_row = row; - put_dirty_grid = grid; + assert(grid_line_row == -1); + grid_line_row = row; + grid_line_grid = grid; + grid_line_coloff = col; + // TODO(bfredl): ugly hackaround, will be fixed in STAGE 2 + grid_line_was_invalid = grid != &default_grid && grid_invalid_row(grid, row); } -void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr) +void grid_line_put_schar(int col, schar_T schar, int attr) { - assert(put_dirty_row == row); - size_t off = grid->line_offset[row] + (size_t)col; - if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) { - schar_copy(grid->chars[off], schar); + assert(grid_line_row >= 0); + ScreenGrid *grid = grid_line_grid; + + size_t off = grid->line_offset[grid_line_row] + (size_t)col; + if (grid->attrs[off] != attr || grid->chars[off] != schar || rdb_flags & RDB_NODELTA) { + grid->chars[off] = schar; grid->attrs[off] = attr; - put_dirty_first = MIN(put_dirty_first, col); + grid_line_first = MIN(grid_line_first, col); // TODO(bfredl): Y U NO DOUBLEWIDTH? - put_dirty_last = MAX(put_dirty_last, col + 1); + grid_line_last = MAX(grid_line_last, col + 1); } grid->vcols[off] = -1; } /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. -int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr) +int grid_line_puts(int col, const char *text, int textlen, int attr) { size_t off; const char *ptr = text; @@ -214,37 +347,15 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int int prev_c = 0; // previous Arabic character int pc, nc, nc1; int pcc[MAX_MCO]; - bool do_flush = false; - - grid_adjust(&grid, &row, &col); - // Safety check. The check for negative row and column is to fix issue - // vim/vim#4102. TODO(neovim): find out why row/col could be negative. - if (grid->chars == NULL - || row >= grid->rows || row < 0 - || col >= grid->cols || col < 0) { - return 0; - } + assert(grid_line_row >= 0); + ScreenGrid *grid = grid_line_grid; + int row = grid_line_row; + col += grid_line_coloff; - if (put_dirty_row == -1) { - grid_puts_line_start(grid, row); - do_flush = true; - } else { - if (grid != put_dirty_grid || row != put_dirty_row) { - abort(); - } - } off = grid->line_offset[row] + (size_t)col; int start_col = col; - // When drawing over the right half of a double-wide char clear out the - // left half. Only needed in a terminal. - if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { - // redraw the previous cell, make it empty - put_dirty_first = -1; - put_dirty_last = MAX(put_dirty_last, 1); - } - max_off = grid->line_offset[row] + (size_t)grid->cols; while (col < grid->cols && (len < 0 || (int)(ptr - text) < len) @@ -293,10 +404,12 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int } schar_T buf; - schar_from_cc(buf, u8c, u8cc); + // TODO(bfredl): why not just keep the original byte sequence. arabshape is + // an edge case, treat it as such.. + buf = schar_from_cc(u8c, u8cc); - int need_redraw = schar_cmp(grid->chars[off], buf) - || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) + int need_redraw = grid->chars[off] != buf + || (mbyte_cells == 2 && grid->chars[off + 1] != 0) || grid->attrs[off] != attr || exmode_active || rdb_flags & RDB_NODELTA; @@ -320,20 +433,20 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int // When at the start of the text and overwriting the right half of a // two-cell character in the same grid, truncate that into a '>'. - if (ptr == text && col > 0 && grid->chars[off][0] == 0) { - schar_from_ascii(grid->chars[off - 1], '>'); + if (ptr == text && col > 0 && grid->chars[off] == 0) { + grid->chars[off - 1] = schar_from_ascii('>'); } - schar_copy(grid->chars[off], buf); + grid->chars[off] = buf; grid->attrs[off] = attr; grid->vcols[off] = -1; if (mbyte_cells == 2) { - grid->chars[off + 1][0] = 0; + grid->chars[off + 1] = 0; grid->attrs[off + 1] = attr; grid->vcols[off + 1] = -1; } - put_dirty_first = MIN(put_dirty_first, col); - put_dirty_last = MAX(put_dirty_last, col + mbyte_cells); + grid_line_first = MIN(grid_line_first, col); + grid_line_last = MAX(grid_line_last, col + mbyte_cells); } off += (size_t)mbyte_cells; @@ -346,39 +459,61 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int } } - if (do_flush) { - grid_puts_line_flush(true); - } return col - start_col; } -/// End a group of grid_puts_len calls and send the screen buffer to the UI -/// layer. +void grid_line_fill(int start_col, int end_col, int c, int attr) +{ + ScreenGrid *grid = grid_line_grid; + size_t lineoff = grid->line_offset[grid_line_row]; + start_col += grid_line_coloff; + end_col += grid_line_coloff; + + schar_T sc = schar_from_char(c); + for (int col = start_col; col < end_col; col++) { + size_t off = lineoff + (size_t)col; + if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { + grid->chars[off] = sc; + grid->attrs[off] = attr; + grid_line_first = MIN(grid_line_first, col); + grid_line_last = MAX(grid_line_last, col + 1); + } + grid->vcols[off] = -1; + } +} + +/// End a group of grid_line_puts calls and send the screen buffer to the UI layer. /// /// @param set_cursor Move the visible cursor to the end of the changed region. /// This is a workaround for not yet refactored code paths /// and shouldn't be used in new code. -void grid_puts_line_flush(bool set_cursor) +void grid_line_flush(bool set_cursor) { - assert(put_dirty_row != -1); - if (put_dirty_first < put_dirty_last) { + assert(grid_line_row != -1); + if (grid_line_first < grid_line_last) { + // When drawing over the right half of a double-wide char clear out the + // left half. Only needed in a terminal. + if (grid_line_was_invalid && grid_line_first == 0) { + // redraw the previous cell, make it empty + grid_line_first = -1; + } if (set_cursor) { - ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, - MIN(put_dirty_last, put_dirty_grid->cols - 1)); + ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, + MIN(grid_line_last, grid_line_grid->cols - 1)); } - if (!put_dirty_grid->throttled) { - ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, - put_dirty_last, 0, false); - } else if (put_dirty_grid->dirty_col) { - if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) { - put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last; + if (!grid_line_grid->throttled) { + ui_line(grid_line_grid, grid_line_row, grid_line_first, grid_line_last, + grid_line_last, 0, false); + } else if (grid_line_grid->dirty_col) { + if (grid_line_last > grid_line_grid->dirty_col[grid_line_row]) { + grid_line_grid->dirty_col[grid_line_row] = grid_line_last; } } - put_dirty_first = INT_MAX; - put_dirty_last = 0; + grid_line_first = INT_MAX; + grid_line_last = 0; } - put_dirty_row = -1; - put_dirty_grid = NULL; + grid_line_row = -1; + grid_line_grid = NULL; } /// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" @@ -415,11 +550,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, " ", 1, row, start_col - 1, 0); + grid_puts(grid, " ", 1, row, start_col - 1, 0); } if (end_col < grid->cols && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, " ", 1, row, end_col, 0); + grid_puts(grid, " ", 1, row, end_col, 0); } // if grid was resized (in ext_multigrid mode), the UI has no redraw updates @@ -429,12 +564,12 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int int dirty_last = 0; int col = start_col; - schar_from_char(sc, c1); + sc = schar_from_char(c1); size_t lineoff = grid->line_offset[row]; for (col = start_col; col < end_col; col++) { size_t off = lineoff + (size_t)col; - if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { - schar_copy(grid->chars[off], sc); + if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { + grid->chars[off] = sc; grid->attrs[off] = attr; if (dirty_first == INT_MAX) { dirty_first = col; @@ -443,15 +578,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int } grid->vcols[off] = -1; if (col == start_col) { - schar_from_char(sc, c2); + sc = schar_from_char(c2); } } if (dirty_last > dirty_first) { - // TODO(bfredl): support a cleared suffix even with a batched line? - if (put_dirty_row == row) { - put_dirty_first = MIN(put_dirty_first, dirty_first); - put_dirty_last = MAX(put_dirty_last, dirty_last); - } else if (grid->throttled) { + if (grid->throttled) { // Note: assumes msg_grid is the only throttled grid assert(grid == &msg_grid); int dirty = 0; @@ -468,10 +599,6 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int ui_line(grid, row, dirty_first, last, dirty_last, attr, false); } } - - if (end_col == grid->cols) { - grid->line_wraps[row] = false; - } } } @@ -483,11 +610,10 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols) { return (cols > 0 - && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to]) + && ((linebuf_char[off_from] != grid->chars[off_to] || linebuf_attr[off_from] != grid->attrs[off_to] || (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1 - && schar_cmp(linebuf_char[off_from + 1], - grid->chars[off_to + 1]))) + && linebuf_char[off_from + 1] != grid->chars[off_to + 1])) || rdb_flags & RDB_NODELTA)); } @@ -544,7 +670,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (wp->w_p_nu && wp->w_p_rnu) { // do not overwrite the line number, change "123 text" to // "123<<<xt". - while (skip < max_off_from && ascii_isdigit(*linebuf_char[off])) { + while (skip < max_off_from && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) { off++; skip++; } @@ -554,9 +680,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (line_off2cells(linebuf_char, off, max_off_from) > 1) { // When the first half of a double-width character is // overwritten, change the second half to a space. - schar_from_ascii(linebuf_char[off + 1], ' '); + linebuf_char[off + 1] = schar_from_ascii(' '); } - schar_from_ascii(linebuf_char[off], '<'); + linebuf_char[off] = schar_from_ascii('<'); linebuf_attr[off] = HL_ATTR(HLF_AT); off++; } @@ -565,8 +691,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (rlflag) { // Clear rest first, because it's left of the text. if (clear_width > 0) { - while (col <= endcol && grid->chars[off_to][0] == ' ' - && grid->chars[off_to][1] == NUL + while (col <= endcol && grid->chars[off_to] == schar_from_ascii(' ') && grid->attrs[off_to] == bg_attr) { off_to++; col++; @@ -619,9 +744,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle clear_next = true; } - schar_copy(grid->chars[off_to], linebuf_char[off_from]); + grid->chars[off_to] = linebuf_char[off_from]; if (char_cells == 2) { - schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]); + grid->chars[off_to + 1] = linebuf_char[off_from + 1]; } grid->attrs[off_to] = linebuf_attr[off_from]; @@ -645,7 +770,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (clear_next) { // Clear the second half of a double-wide character of which the left // half was overwritten with a single-wide character. - schar_from_ascii(grid->chars[off_to], ' '); + grid->chars[off_to] = schar_from_ascii(' '); end_dirty++; } @@ -654,12 +779,10 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle // blank out the rest of the line // TODO(bfredl): we could cache winline widths while (col < clear_width) { - if (grid->chars[off_to][0] != ' ' - || grid->chars[off_to][1] != NUL + if (grid->chars[off_to] != schar_from_ascii(' ') || grid->attrs[off_to] != bg_attr || rdb_flags & RDB_NODELTA) { - grid->chars[off_to][0] = ' '; - grid->chars[off_to][1] = NUL; + grid->chars[off_to] = schar_from_ascii(' '); grid->attrs[off_to] = bg_attr; if (start_dirty == -1) { start_dirty = col; @@ -675,12 +798,6 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle } } - if (clear_width > 0 || wp->w_width != grid->cols) { - // If we cleared after the end of the line, it did not wrap. - // For vsplit, line wrapping is not possible. - grid->line_wraps[row] = false; - } - if (clear_end < end_dirty) { clear_end = end_dirty; } @@ -704,14 +821,12 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) ngrid.vcols = xmalloc(ncells * sizeof(colnr_T)); memset(ngrid.vcols, -1, ncells * sizeof(colnr_T)); ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset)); - ngrid.line_wraps = xmalloc((size_t)rows * sizeof(*ngrid.line_wraps)); ngrid.rows = rows; ngrid.cols = columns; for (new_row = 0; new_row < ngrid.rows; new_row++) { ngrid.line_offset[new_row] = (size_t)new_row * (size_t)ngrid.cols; - ngrid.line_wraps[new_row] = false; grid_clear_line(&ngrid, ngrid.line_offset[new_row], columns, valid); @@ -756,13 +871,11 @@ void grid_free(ScreenGrid *grid) xfree(grid->attrs); xfree(grid->vcols); xfree(grid->line_offset); - xfree(grid->line_wraps); grid->chars = NULL; grid->attrs = NULL; grid->vcols = NULL; grid->line_offset = NULL; - grid->line_wraps = NULL; } /// Doesn't allow reinit, so must only be called by free_all_mem! @@ -885,16 +998,13 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } j += line_count; grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; } else { j = end - 1 - i; temp = (unsigned)grid->line_offset[j]; while ((j -= line_count) >= row) { grid->line_offset[j + line_count] = grid->line_offset[j]; - grid->line_wraps[j + line_count] = grid->line_wraps[j]; } grid->line_offset[j + line_count] = temp; - grid->line_wraps[j + line_count] = false; grid_clear_line(grid, temp, grid->cols, false); } } @@ -933,17 +1043,14 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } j -= line_count; grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; } else { // whole width, moving the line pointers is faster j = row + i; temp = (unsigned)grid->line_offset[j]; while ((j += line_count) <= end - 1) { grid->line_offset[j - line_count] = grid->line_offset[j]; - grid->line_wraps[j - line_count] = grid->line_wraps[j]; } grid->line_offset[j - line_count] = temp; - grid->line_wraps[j - line_count] = false; grid_clear_line(grid, temp, grid->cols, false); } } diff --git a/src/nvim/grid.h b/src/nvim/grid.h index 0db97345d1..9cdc6c6677 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -33,30 +33,25 @@ EXTERN colnr_T *linebuf_vcol INIT(= NULL); // screen grid. /// Put a ASCII character in a screen cell. -static inline void schar_from_ascii(char *p, const char c) -{ - p[0] = c; - p[1] = 0; -} +/// +/// If `x` is a compile time constant, schar_from_ascii(x) will also be. +/// But the specific value varies per plattform. +#ifdef ORDER_BIG_ENDIAN +# define schar_from_ascii(x) ((schar_T)((x) << 24)) +#else +# define schar_from_ascii(x) ((schar_T)(x)) +#endif /// Put a unicode character in a screen cell. -static inline int schar_from_char(char *p, int c) -{ - int len = utf_char2bytes(c, p); - p[len] = NUL; - return len; -} - -/// compare the contents of two screen cells. -static inline int schar_cmp(char *sc1, char *sc2) -{ - return strncmp(sc1, sc2, sizeof(schar_T)); -} - -/// copy the contents of screen cell `sc2` into cell `sc1` -static inline void schar_copy(char *sc1, char *sc2) +static inline schar_T schar_from_char(int c) { - xstrlcpy(sc1, sc2, sizeof(schar_T)); + schar_T sc = 0; + if (c >= 0x200000) { + // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences + c = 0xFFFD; + } + utf_char2bytes(c, (char *)&sc); + return sc; } #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 4ad7d4cdb4..100d648263 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -9,9 +9,13 @@ #include "nvim/types.h" #define MAX_MCO 6 // fixed value for 'maxcombine' +// Includes final NUL. at least 4*(MAX_MCO+1)+1 +#define MAX_SCHAR_SIZE 32 -// The characters and attributes drawn on grids. -typedef char schar_T[(MAX_MCO + 1) * 4 + 1]; +// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianess) +// otherwise it must be a UTF-8 string of length maximum 4 (no NUL when n=4) + +typedef uint32_t schar_T; typedef int sattr_T; enum { @@ -40,15 +44,14 @@ enum { /// attrs[] contains the highlighting attribute for each cell. /// /// vcols[] contains the virtual columns in the line. -1 means not available -/// (below last line), MAXCOL means after the end of the line. +/// or before buffer text, MAXCOL means after the end of the line. +/// -2 or -3 means in fold column and a mouse click should: +/// -2: open a fold +/// -3: close a fold /// /// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start /// of line 'n'. These offsets are in general not linear, as full screen scrolling /// is implemented by rotating the offsets in the line_offset array. -/// -/// line_wraps[] is an array of boolean flags indicating if the screen line -/// wraps to the next line. It can only be true if a window occupies the entire -/// screen width. typedef struct ScreenGrid ScreenGrid; struct ScreenGrid { handle_T handle; @@ -57,7 +60,6 @@ struct ScreenGrid { sattr_T *attrs; colnr_T *vcols; size_t *line_offset; - char *line_wraps; // last column that was drawn (not cleared with the default background). // only used when "throttled" is set. Not allocated by grid_alloc! @@ -113,7 +115,7 @@ struct ScreenGrid { bool comp_disabled; }; -#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ false, 0, 0, NULL, false, true, 0, \ 0, 0, 0, 0, 0, false } diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index df4bffdac3..3728db31d8 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -86,7 +86,7 @@ static int get_attr_entry(HlEntry entry) } retry: {} - MhPutStatus status; + MHPutStatus status; uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status); if (status == kMHExisting) { return (int)k; diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 4f9677650f..f175a7abb0 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -24,6 +24,7 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/ex_eval.h" #include "nvim/fold.h" #include "nvim/globals.h" @@ -359,6 +360,9 @@ int nlua_setvar(lua_State *lstate) Error err = ERROR_INIT; dictitem_T *di = dict_check_writable(dict, key, del, &err); if (ERROR_SET(&err)) { + nlua_push_errstr(lstate, "%s", err.msg); + api_clear_error(&err); + lua_error(lstate); return 0; } @@ -394,6 +398,15 @@ int nlua_setvar(lua_State *lstate) di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { + bool type_error = false; + if (dict == &vimvardict + && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) { + tv_clear(&tv); + if (type_error) { + return luaL_error(lstate, "Setting v:%s to value with wrong type", key.data); + } + return 0; + } if (watched) { tv_copy(&di->di_tv, &oldtv); } diff --git a/src/nvim/main.c b/src/nvim/main.c index d9ca82784f..4b3244de12 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -341,7 +341,7 @@ int main(int argc, char **argv) uint64_t rv = ui_client_start_server(params.argc, params.argv); if (!rv) { os_errmsg("Failed to start Nvim server!\n"); - getout(1); + os_exit(1); } ui_client_channel_id = rv; } @@ -659,6 +659,9 @@ void os_exit(int r) if (ui_client_channel_id) { ui_client_stop(); + if (r == 0) { + r = ui_client_exit_status; + } } else { ui_flush(); ui_call_stop(); @@ -1579,6 +1582,7 @@ static void handle_tag(char *tagname) // If the user doesn't want to edit the file then we quit here. if (swap_exists_did_quit) { + ui_call_error_exit(1); getout(1); } } @@ -1722,6 +1726,7 @@ static void create_windows(mparm_T *parmp) if (got_int || only_one_window()) { // abort selected or quit and only one window did_emsg = false; // avoid hit-enter prompt + ui_call_error_exit(1); getout(1); } // We can't close the window, it would disturb what @@ -1825,6 +1830,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd) if (got_int || only_one_window()) { // abort selected and only one window did_emsg = false; // avoid hit-enter prompt + ui_call_error_exit(1); getout(1); } win_close(curwin, true, false); @@ -2235,6 +2241,7 @@ static void usage(void) static void check_swap_exists_action(void) { if (swap_exists_action == SEA_QUIT) { + ui_call_error_exit(1); getout(1); } handle_swap_exists(NULL); diff --git a/src/nvim/map.c b/src/nvim/map.c index e1d0646083..54f1969df8 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -47,25 +47,6 @@ static inline uint32_t hash_cstr_t(const char *s) #define equal_cstr_t strequal -// when used as a key, String doesn't need to be NUL terminated, -// and can also contain embedded NUL:s as part of the data. -static inline uint32_t hash_String(String s) -{ - uint32_t h = 0; - for (size_t i = 0; i < s.size; i++) { - h = (h << 5) - h + (uint8_t)s.data[i]; - } - return h; -} - -static inline bool equal_String(String a, String b) -{ - if (a.size != b.size) { - return false; - } - return memcmp(a.data, b.data, a.size) == 0; -} - static inline uint32_t hash_HlEntry(HlEntry ae) { const uint8_t *data = (const uint8_t *)&ae; diff --git a/src/nvim/map.h b/src/nvim/map.h index 23a5ea36a3..2d5517c552 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -18,6 +18,25 @@ typedef const char *cstr_t; typedef void *ptr_t; +// when used as a key, String doesn't need to be NUL terminated, +// and can also contain embedded NUL:s as part of the data. +static inline uint32_t hash_String(String s) +{ + uint32_t h = 0; + for (size_t i = 0; i < s.size; i++) { + h = (h << 5) - h + (uint8_t)s.data[i]; + } + return h; +} + +static inline bool equal_String(String a, String b) +{ + if (a.size != b.size) { + return false; + } + return memcmp(a.data, b.data, a.size) == 0; +} + #define Set(type) Set_##type #define Map(T, U) Map_##T##U #define PMap(T) Map(T, ptr_t) @@ -57,7 +76,7 @@ typedef enum { kMHExisting = 0, kMHNewKeyDidFit, kMHNewKeyRealloc, -} MhPutStatus; +} MHPutStatus; void mh_clear(MapHash *h); void mh_realloc(MapHash *h, uint32_t n_min_buckets); @@ -65,20 +84,22 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets); // layer 1: key type specific defs // This is all need for sets. -#define KEY_DECLS(T) \ +#define MH_DECLS(T, K, K_query) \ typedef struct { \ MapHash h; \ - T *keys; \ + K *keys; \ } Set(T); \ \ - uint32_t mh_find_bucket_##T(Set(T) *set, T key, bool put); \ - uint32_t mh_get_##T(Set(T) *set, T key); \ + uint32_t mh_find_bucket_##T(Set(T) *set, K_query key, bool put); \ + uint32_t mh_get_##T(Set(T) *set, K_query key); \ void mh_rehash_##T(Set(T) *set); \ - uint32_t mh_put_##T(Set(T) *set, T key, MhPutStatus *new); \ + uint32_t mh_put_##T(Set(T) *set, K_query key, MHPutStatus *new); \ + +#define KEY_DECLS(T) \ + MH_DECLS(T, T, T) \ uint32_t mh_delete_##T(Set(T) *set, T *key); \ - \ static inline bool set_put_##T(Set(T) *set, T key, T **key_alloc) { \ - MhPutStatus status; \ + MHPutStatus status; \ uint32_t k = mh_put_##T(set, key, &status); \ if (key_alloc) { \ *key_alloc = &set->keys[k]; \ @@ -120,6 +141,7 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets); #define quasiquote(x, y) x##y +MH_DECLS(glyph, char, String) KEY_DECLS(int) KEY_DECLS(cstr_t) KEY_DECLS(ptr_t) diff --git a/src/nvim/map_glyph_cache.c b/src/nvim/map_glyph_cache.c new file mode 100644 index 0000000000..6dcbfe0532 --- /dev/null +++ b/src/nvim/map_glyph_cache.c @@ -0,0 +1,102 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// Specialized version of Set() where interned strings is stored in a compact, +// NUL-separated char array. +// `String key` lookup keys don't need to be NULL terminated, but they +// must not contain embedded NUL:s. When reading a key from set->keys, they +// are always NUL terminated, though. Thus, it is enough to store an index into +// this array, and use strlen(), to retrive an interned key. + +#include "nvim/api/private/helpers.h" +#include "nvim/map.h" + +uint32_t mh_find_bucket_glyph(Set(glyph) *set, String key, bool put) +{ + MapHash *h = &set->h; + uint32_t step = 0; + uint32_t mask = h->n_buckets - 1; + uint32_t k = hash_String(key); + uint32_t i = k & mask; + uint32_t last = i; + uint32_t site = put ? last : MH_TOMBSTONE; + while (!mh_is_empty(h, i)) { + if (mh_is_del(h, i)) { + if (site == last) { + site = i; + } + } else if (equal_String(cstr_as_string(&set->keys[h->hash[i] - 1]), key)) { + return i; + } + i = (i + (++step)) & mask; + if (i == last) { + abort(); + } + } + if (site == last) { + site = i; + } + return site; +} + +/// @return index into set->keys if found, MH_TOMBSTONE otherwise +uint32_t mh_get_glyph(Set(glyph) *set, String key) +{ + if (set->h.n_buckets == 0) { + return MH_TOMBSTONE; + } + uint32_t idx = mh_find_bucket_glyph(set, key, false); + return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE; +} + +void mh_rehash_glyph(Set(glyph) *set) +{ + // assume the format of set->keys, i e NUL terminated strings + for (uint32_t k = 0; k < set->h.n_keys; k += (uint32_t)strlen(&set->keys[k]) + 1) { + uint32_t idx = mh_find_bucket_glyph(set, cstr_as_string(&set->keys[k]), true); + // there must be tombstones when we do a rehash + if (!mh_is_empty((&set->h), idx)) { + abort(); + } + set->h.hash[idx] = k + 1; + } + set->h.n_occupied = set->h.size = set->h.n_keys; +} + +uint32_t mh_put_glyph(Set(glyph) *set, String key, MHPutStatus *new) +{ + MapHash *h = &set->h; + // Might rehash ahead of time if "key" already existed. But it was + // going to happen soon anyway. + if (h->n_occupied >= h->upper_bound) { + mh_realloc(h, h->n_buckets + 1); + mh_rehash_glyph(set); + } + + uint32_t idx = mh_find_bucket_glyph(set, key, true); + + if (mh_is_either(h, idx)) { + h->size++; + h->n_occupied++; + + uint32_t size = (uint32_t)key.size + 1; // NUL takes space + uint32_t pos = h->n_keys; + h->n_keys += size; + if (h->n_keys > h->keys_capacity) { + h->keys_capacity = MAX(h->keys_capacity * 2, 64); + set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(char)); + *new = kMHNewKeyRealloc; + } else { + *new = kMHNewKeyDidFit; + } + memcpy(&set->keys[pos], key.data, key.size); + set->keys[pos + key.size] = NUL; + h->hash[idx] = pos + 1; + return pos; + } else { + *new = kMHExisting; + uint32_t pos = h->hash[idx] - 1; + assert(equal_String(cstr_as_string(&set->keys[pos]), key)); + return pos; + } +} diff --git a/src/nvim/map_key_impl.c.h b/src/nvim/map_key_impl.c.h index 7e7b2f74fe..4d060f5fb8 100644 --- a/src/nvim/map_key_impl.c.h +++ b/src/nvim/map_key_impl.c.h @@ -80,7 +80,7 @@ void KEY_NAME(mh_rehash_)(SET_TYPE *set) /// if new item, indicates if keys[] was resized. /// /// @return keys index -uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MhPutStatus *new) +uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MHPutStatus *new) { MapHash *h = &set->h; // Might rehash ahead of time if "key" already existed. But it was diff --git a/src/nvim/map_value_impl.c.h b/src/nvim/map_value_impl.c.h index fffda280f0..8f07bd8fa7 100644 --- a/src/nvim/map_value_impl.c.h +++ b/src/nvim/map_value_impl.c.h @@ -28,7 +28,7 @@ VALUE_TYPE *MAP_NAME(map_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc VALUE_TYPE *MAP_NAME(map_put_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc, bool *new_item) { - MhPutStatus status; + MHPutStatus status; uint32_t k = KEY_NAME(mh_put_)(&map->set, key, &status); if (status != kMHExisting) { if (status == kMHNewKeyRealloc) { diff --git a/src/nvim/message.c b/src/nvim/message.c index 81760dd017..dba4dba600 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -640,7 +640,7 @@ int emsg_not_now(void) return false; } -static bool emsg_multiline(const char *s, bool multiline) +bool emsg_multiline(const char *s, bool multiline) { int attr; bool ignore = false; @@ -663,7 +663,7 @@ static bool emsg_multiline(const char *s, bool multiline) // be found, the message will be displayed later on.) "ignore" is set // when the message should be ignored completely (used for the // interrupt message). - if (cause_errthrow(s, severe, &ignore)) { + if (cause_errthrow(s, multiline, severe, &ignore)) { if (!ignore) { did_emsg++; } @@ -1907,10 +1907,13 @@ void msg_prt_line(const char *s, int list) continue; } else { attr = 0; - c = (unsigned char)(*s++); - in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' '); - if (!in_multispace) { - multispace_pos = 0; + c = (uint8_t)(*s++); + if (list) { + in_multispace = c == ' ' && (*s == ' ' + || (col > 0 && s[-2] == ' ')); + if (!in_multispace) { + multispace_pos = 0; + } } if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) { // tab amount depends on current column @@ -1950,7 +1953,7 @@ void msg_prt_line(const char *s, int list) // the same in plain text. attr = HL_ATTR(HLF_0); } else if (c == ' ') { - if (list && lead != NULL && s <= lead && in_multispace + if (lead != NULL && s <= lead && in_multispace && curwin->w_p_lcs_chars.leadmultispace != NULL) { c = curwin->w_p_lcs_chars.leadmultispace[multispace_pos++]; if (curwin->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { @@ -1963,7 +1966,7 @@ void msg_prt_line(const char *s, int list) } else if (trail != NULL && s > trail) { c = curwin->w_p_lcs_chars.trail; attr = HL_ATTR(HLF_0); - } else if (list && in_multispace + } else if (in_multispace && curwin->w_p_lcs_chars.multispace != NULL) { c = curwin->w_p_lcs_chars.multispace[multispace_pos++]; if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) { @@ -2004,7 +2007,7 @@ static const char *screen_puts_mbyte(const char *s, int l, int attr) return s; } - grid_puts_len(&msg_grid_adj, s, l, msg_row, msg_col, attr); + grid_puts(&msg_grid_adj, s, l, msg_row, msg_col, attr); if (cmdmsg_rl) { msg_col -= cw; if (msg_col == 0) { @@ -2705,7 +2708,7 @@ static void t_puts(int *t_col, const char *t_s, const char *s, int attr) attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); // Output postponed text. msg_didout = true; // Remember that line is not empty. - grid_puts_len(&msg_grid_adj, t_s, (int)(s - t_s), msg_row, msg_col, attr); + grid_puts(&msg_grid_adj, t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; // If the string starts with a composing character don't increment the @@ -3091,9 +3094,9 @@ void msg_moremsg(int full) char *s = _("-- More --"); attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); - grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr); + grid_puts(&msg_grid_adj, s, -1, Rows - 1, 0, attr); if (full) { - grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), + grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), -1, Rows - 1, vim_strsize(s), attr); } } diff --git a/src/nvim/message.h b/src/nvim/message.h index 191d3b8da7..78cd5b7d0e 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -68,6 +68,8 @@ EXTERN int msg_scrolled_at_flush INIT(= 0); EXTERN int msg_grid_scroll_discount INIT(= 0); +EXTERN int msg_listdo_overwrite INIT(= 0); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.h.generated.h" #endif diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b8c80cadf5..9b09a3fdf3 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1849,74 +1849,66 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp) int click_grid = mouse_grid; int click_row = mouse_row; int click_col = mouse_col; - int mouse_char = ' '; - int max_row = Rows; - int max_col = Columns; - bool multigrid = ui_has(kUIMultigrid); - colnr_T col_from_screen = -1; + // XXX: this doesn't change click_grid if it is 1, even with multigrid win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); - if (wp && multigrid) { - max_row = wp->w_grid_alloc.rows; - max_col = wp->w_grid_alloc.cols; - } - - if (wp && mouse_row >= 0 && mouse_row < max_row - && mouse_col >= 0 && mouse_col < max_col) { - ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; - int fdc = win_fdccol_count(wp); - int use_row = multigrid && mouse_grid == 0 ? click_row : mouse_row; - int use_col = multigrid && mouse_grid == 0 ? click_col : mouse_col; - - if (gp->chars != NULL) { - const size_t off = gp->line_offset[use_row] + (size_t)use_col; - - // Only use vcols[] after the window was redrawn. Mainly matters - // for tests, a user would not click before redrawing. - if (wp->w_redr_type == 0) { - col_from_screen = gp->vcols[off]; - } - - if (col_from_screen == MAXCOL) { - // When clicking after end of line, still need to set correct curswant - size_t off_l = gp->line_offset[use_row]; - if (gp->vcols[off_l] < MAXCOL) { - // Binary search to find last char in line - size_t off_r = off; - while (off_l < off_r) { - size_t off_m = (off_l + off_r + 1) / 2; - if (gp->vcols[off_m] < MAXCOL) { - off_l = off_m; - } else { - off_r = off_m - 1; - } - } - *vcolp = gp->vcols[off_r] + (int)(off - off_r); + // Only use vcols[] after the window was redrawn. Mainly matters + // for tests, a user would not click before redrawing. + if (wp == NULL || wp->w_redr_type != 0) { + return; + } + ScreenGrid *gp = &wp->w_grid; + int start_row = 0; + int start_col = 0; + grid_adjust(&gp, &start_row, &start_col); + if (gp->handle != click_grid || gp->chars == NULL) { + return; + } + click_row += start_row; + click_col += start_col; + if (click_row < 0 || click_row >= gp->rows + || click_col < 0 || click_col >= gp->cols) { + return; + } + + const size_t off = gp->line_offset[click_row] + (size_t)click_col; + colnr_T col_from_screen = gp->vcols[off]; + + if (col_from_screen == MAXCOL) { + // When clicking after end of line, still need to set correct curswant + size_t off_l = gp->line_offset[click_row] + (size_t)start_col; + if (gp->vcols[off_l] < MAXCOL) { + // Binary search to find last char in line + size_t off_r = off; + while (off_l < off_r) { + size_t off_m = (off_l + off_r + 1) / 2; + if (gp->vcols[off_m] < MAXCOL) { + off_l = off_m; } else { - // Shouldn't normally happen - *vcolp = MAXCOL; + off_r = off_m - 1; } - } else if (col_from_screen >= 0) { - // Use the virtual column from vcols[], it is accurate also after - // concealed characters. - *vcolp = col_from_screen; } - - // Remember the character under the mouse, might be one of foldclose or - // foldopen fillchars in the fold column. - mouse_char = utf_ptr2char((char *)gp->chars[off]); - } - - // Check for position outside of the fold column. - if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc : - click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { - mouse_char = ' '; + colnr_T eol_vcol = gp->vcols[off_r]; + assert(eol_vcol < MAXCOL); + if (eol_vcol < 0) { + // Empty line or whole line before w_leftcol, + // with columns before buffer text + eol_vcol = wp->w_leftcol - 1; + } + *vcolp = eol_vcol + (int)(off - off_r); + } else { + // Empty line or whole line before w_leftcol + *vcolp = click_col - start_col + wp->w_leftcol; } + } else if (col_from_screen >= 0) { + // Use the virtual column from vcols[], it is accurate also after + // concealed characters. + *vcolp = col_from_screen; } - if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) { + if (col_from_screen == -2) { *flagsp |= MOUSE_FOLD_OPEN; - } else if (mouse_char != ' ') { + } else if (col_from_screen == -3) { *flagsp |= MOUSE_FOLD_CLOSE; } } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index c3b1022db2..37e32729cc 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -9,6 +9,7 @@ #include "mpack/conv.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/grid.h" #include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel_defs.h" @@ -497,13 +498,13 @@ redo: if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) { g->clear_width = repeat; } else { + schar_T sc = schar_from_buf(cellbuf, cellsize); for (int r = 0; r < repeat; r++) { if (g->coloff >= (int)grid_line_buf_size) { p->state = -1; return false; } - memcpy(grid_line_buf_char[g->coloff], cellbuf, cellsize); - grid_line_buf_char[g->coloff][cellsize] = NUL; + grid_line_buf_char[g->coloff] = sc; grid_line_buf_attr[g->coloff++] = g->cur_attr; } } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 4280d62a95..309b6e2568 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2078,18 +2078,16 @@ static void display_showcmd(void) msg_grid_validate(); int showcmd_row = Rows - 1; - grid_puts_line_start(&msg_grid_adj, showcmd_row); + grid_line_start(&msg_grid_adj, showcmd_row); if (!showcmd_is_clear) { - grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col, - HL_ATTR(HLF_MSG)); + grid_line_puts(sc_col, showcmd_buf, -1, HL_ATTR(HLF_MSG)); } // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces - grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row, - sc_col + len, HL_ATTR(HLF_MSG)); + grid_line_puts(sc_col + len, (char *)" " + len, -1, HL_ATTR(HLF_MSG)); - grid_puts_line_flush(false); + grid_line_flush(false); } /// When "check" is false, prepare for commands that scroll the window. @@ -4513,7 +4511,7 @@ static void nv_replace(cmdarg_T *cap) // Visual mode "r" if (VIsual_active) { if (got_int) { - reset_VIsual(); + got_int = false; } if (had_ctrl_v) { // Use a special (negative) number to make a difference between a diff --git a/src/nvim/option.c b/src/nvim/option.c index ae4e7c1fda..c34999ed32 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1915,6 +1915,9 @@ bool parse_winhl_opt(win_T *wp) char *commap = xstrchrnul(hi, ','); size_t len = (size_t)(commap - hi); int hl_id = len ? syn_check_group(hi, len) : -1; + if (hl_id == 0) { + return false; + } int hl_id_link = nlen ? syn_check_group(p, nlen) : 0; HlAttrs attrs = HLATTRS_INIT; @@ -1967,6 +1970,10 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) curbuf->b_p_script_ctx[indir & PV_MASK] = last_set; } else if (indir & PV_WIN) { curwin->w_p_script_ctx[indir & PV_MASK] = last_set; + if (both) { + // also setting the "all buffers" value + curwin->w_allbuf_opt.wo_script_ctx[indir & PV_MASK] = last_set; + } } } } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 14f29682e1..317bc989e5 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -240,11 +240,7 @@ typedef enum { enum { SHM_RO = 'r', ///< Readonly. SHM_MOD = 'm', ///< Modified. - SHM_FILE = 'f', ///< (file 1 of 2) - SHM_LAST = 'i', ///< Last line incomplete. - SHM_TEXT = 'x', ///< tx instead of textmode. SHM_LINES = 'l', ///< "L" instead of "lines". - SHM_NEW = 'n', ///< "[New]" instead of "[New file]". SHM_WRI = 'w', ///< "[w]" instead of "written". SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS. SHM_WRITE = 'W', ///< Don't use "written" at all. @@ -260,11 +256,10 @@ enum { SHM_RECORDING = 'q', ///< Short recording message. SHM_FILEINFO = 'F', ///< No file info messages. SHM_SEARCHCOUNT = 'S', ///< No search stats: '[1/10]' - SHM_LEN = 30, ///< Max length of all flags together plus a NUL character. }; /// Represented by 'a' flag. #define SHM_ALL_ABBREVIATIONS ((char[]) { \ - SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ + SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, \ 0 }) // characters for p_go: diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 3221e5b6e9..429a70eb38 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2790,8 +2790,8 @@ return { and the value of that item: item default Used for ~ - stl ' ' or '^' statusline of the current window - stlnc ' ' or '=' statusline of the non-current windows + stl ' ' statusline of the current window + stlnc ' ' statusline of the non-current windows wbr ' ' window bar horiz '─' or '-' horizontal separators |:split| horizup '┴' or '-' upwards facing horizontal separator @@ -2810,9 +2810,7 @@ return { eob '~' empty lines at the end of a buffer lastline '@' 'display' contains lastline/truncate - Any one that is omitted will fall back to the default. For "stl" and - "stlnc" the space will be used when there is highlighting, '^' or '=' - otherwise. + Any one that is omitted will fall back to the default. Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and "verthoriz" are only used when 'laststatus' is 3, since only vertical @@ -3938,9 +3936,8 @@ return { cb = 'did_set_ignorecase', defaults = { if_true = false }, desc = [=[ - Ignore case in search patterns. Also used when searching in the tags - file. - Also see 'smartcase' and 'tagcase'. + Ignore case in search patterns, completion, and when searching the tags file. + See also 'smartcase' and 'tagcase'. Can be overruled by using "\c" or "\C" in the pattern, see |/ignorecase|. ]=], @@ -4444,12 +4441,11 @@ return { deny_duplicates = true, desc = [=[ List of words that change the behavior of the |jumplist|. - stack Make the jumplist behave like the tagstack or like a - web browser. Relative location of entries in the - jumplist is preserved at the cost of discarding - subsequent entries when navigating backwards in the - jumplist and then jumping to a location. - |jumplist-stack| + stack Make the jumplist behave like the tagstack. + Relative location of entries in the jumplist is + preserved at the cost of discarding subsequent entries + when navigating backwards in the jumplist and then + jumping to a location. |jumplist-stack| view When moving through the jumplist, |changelist|, |alternate-file| or using |mark-motions| try to @@ -7243,23 +7239,17 @@ return { { abbreviation = 'shm', cb = 'did_set_shortmess', - defaults = { if_true = 'filnxtToOCF' }, + defaults = { if_true = 'ltToOCF' }, desc = [=[ This option helps to avoid all the |hit-enter| prompts caused by file messages, for example with CTRL-G, and to avoid some other messages. It is a list of flags: flag meaning when present ~ - f use "(3 of 5)" instead of "(file 3 of 5)" *shm-f* - i use "[noeol]" instead of "[Incomplete last line]" *shm-i* l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l* m use "[+]" instead of "[Modified]" *shm-m* - n use "[New]" instead of "[New File]" *shm-n* r use "[RO]" instead of "[readonly]" *shm-r* w use "[w]" instead of "written" for file write message *shm-w* and "[a]" instead of "appended" for ':w >> file' command - x use "[dos]" instead of "[dos format]", "[unix]" *shm-x* - instead of "[unix format]" and "[mac]" instead of "[mac - format]" a all of the above abbreviations *shm-a* o overwrite message for writing a file with subsequent *shm-o* diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 17afd10ee3..797c3cb554 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -69,8 +69,6 @@ static const char e_backupext_and_patchmode_are_equal[] = N_("E589: 'backupext' and 'patchmode' are equal"); static const char e_showbreak_contains_unprintable_or_wide_character[] = N_("E595: 'showbreak' contains unprintable or wide character"); -static const char e_internal_error_shortmess_too_long[] - = N_("E1336: Internal error: shortmess too long"); static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -134,11 +132,12 @@ static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelt static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL }; /// All possible flags for 'shm'. -static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, +/// the literal chars before 0 are removed flags. these are safely ignored +static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO, - SHM_SEARCHCOUNT, 0, }; + SHM_SEARCHCOUNT, 'n', 'f', 'x', 'i', 0, }; /// After setting various option values: recompute variables that depend on /// option values. @@ -151,13 +150,13 @@ void didset_string_options(void) (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); - (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); } @@ -2269,36 +2268,6 @@ int check_ff_value(char *p) return check_opt_strings(p, p_ff_values, false); } -static char shm_buf[SHM_LEN]; -static int set_shm_recursive = 0; - -/// Save the actual shortmess Flags and clear them temporarily to avoid that -/// file messages overwrites any output from the following commands. -/// -/// Caller must make sure to first call save_clear_shm_value() and then -/// restore_shm_value() exactly the same number of times. -void save_clear_shm_value(void) -{ - if (strlen(p_shm) >= SHM_LEN) { - iemsg(e_internal_error_shortmess_too_long); - return; - } - - if (++set_shm_recursive == 1) { - STRCPY(shm_buf, p_shm); - set_option_value_give_err("shm", STATIC_CSTR_AS_OPTVAL(""), 0); - } -} - -/// Restore the shortmess Flags set from the save_clear_shm_value() function. -void restore_shm_value(void) -{ - if (--set_shm_recursive == 0) { - set_option_value_give_err("shm", CSTR_AS_OPTVAL(shm_buf), 0); - memset(shm_buf, 0, SHM_LEN); - } -} - static const char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'"); static const char e_conflicts_with_value_of_fillchars[] diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index abeb020645..763d30d4a2 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -256,9 +256,9 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) QUEUE_FOREACH(q, &args_q, { ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node); xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); + QUEUE_REMOVE(q); xfree(arg_node->arg); xfree(arg_node); - QUEUE_REMOVE(q); if (!QUEUE_EMPTY(&args_q)) { xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); } diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index fe31c70d5d..488c893bd8 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -507,14 +507,14 @@ void pum_redraw(void) const int *const attrs = (idx == pum_selected) ? attrsSel : attrsNorm; int attr = attrs[0]; // start with "word" highlight - grid_puts_line_start(&pum_grid, row); + grid_line_start(&pum_grid, row); // prepend a space if there is room if (extra_space) { if (pum_rl) { - grid_putchar(&pum_grid, ' ', row, col_off + 1, attr); + grid_line_puts(col_off + 1, " ", 1, attr); } else { - grid_putchar(&pum_grid, ' ', row, col_off - 1, attr); + grid_line_puts(col_off - 1, " ", 1, attr); } } @@ -580,13 +580,13 @@ void pum_redraw(void) size++; } } - grid_puts_len(&pum_grid, rt, (int)strlen(rt), row, grid_col - size + 1, attr); + grid_line_puts(grid_col - size + 1, rt, -1, attr); xfree(rt_start); xfree(st); grid_col -= width; } else { - // use grid_puts_len() to truncate the text - grid_puts(&pum_grid, st, row, grid_col, attr); + // use grid_line_puts() to truncate the text + grid_line_puts(grid_col, st, -1, attr); xfree(st); grid_col += width; } @@ -597,11 +597,10 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1, - attr); + grid_line_puts(grid_col - 1, " ", 2, attr); grid_col -= 2; } else { - grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr); + grid_line_puts(grid_col, " ", 2, attr); grid_col += 2; } totwidth += 2; @@ -632,37 +631,31 @@ void pum_redraw(void) } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, - grid_col + 1, ' ', ' ', attr); + grid_line_fill(col_off - pum_base_width - n + 1, grid_col + 1, ' ', attr); grid_col = col_off - pum_base_width - n + 1; } else { - grid_fill(&pum_grid, row, row + 1, grid_col, - col_off + pum_base_width + n, ' ', ' ', attr); + grid_line_fill(grid_col, col_off + pum_base_width + n, ' ', attr); grid_col = col_off + pum_base_width + n; } totwidth = pum_base_width + n; } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1, - ' ', ' ', attr); + grid_line_fill(col_off - pum_width + 1, grid_col + 1, ' ', attr); } else { - grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ', - attr); + grid_line_fill(grid_col, col_off + pum_width, ' ', attr); } if (pum_scrollbar > 0) { if (pum_rl) { - grid_putchar(&pum_grid, ' ', row, col_off - pum_width, - i >= thumb_pos && i < thumb_pos + thumb_height - ? attr_thumb : attr_scroll); + grid_line_puts(col_off - pum_width, " ", 1, + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } else { - grid_putchar(&pum_grid, ' ', row, col_off + pum_width, - i >= thumb_pos && i < thumb_pos + thumb_height - ? attr_thumb : attr_scroll); + grid_line_puts(col_off + pum_width, " ", 1, + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } } - grid_puts_line_flush(false); + grid_line_flush(false); row++; } } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 1d0a987780..14c7e56e97 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -59,11 +59,7 @@ #define un_Magic(x) ((x) + 256) #define is_Magic(x) ((x) < 0) -// We should define ftpr as a pointer to a function returning a pointer to -// a function returning a pointer to a function ... -// This is impossible, so we declare a pointer to a function returning a -// pointer to a function returning void. This should work for all compilers. -typedef void (*(*fptr_T)(int *, int))(void); +typedef void (*fptr_T)(int *, int); static int no_Magic(int x) { @@ -1494,34 +1490,14 @@ static inline char *cstrchr(const char *const s, const int c) // regsub stuff // //////////////////////////////////////////////////////////////// -// This stuff below really confuses cc on an SGI -- webb - -static fptr_T do_upper(int *d, int c) +static void do_upper(int *d, int c) { *d = mb_toupper(c); - - return (fptr_T)NULL; -} - -static fptr_T do_Upper(int *d, int c) -{ - *d = mb_toupper(c); - - return (fptr_T)do_Upper; } -static fptr_T do_lower(int *d, int c) +static void do_lower(int *d, int c) { *d = mb_tolower(c); - - return (fptr_T)NULL; -} - -static fptr_T do_Lower(int *d, int c) -{ - *d = mb_tolower(c); - - return (fptr_T)do_Lower; } /// regtilde(): Replace tildes in the pattern by the old pattern. @@ -1886,16 +1862,16 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen } else if (vim_strchr("uUlLeE", (uint8_t)(*src))) { switch (*src++) { case 'u': - func_one = (fptr_T)do_upper; + func_one = do_upper; continue; case 'U': - func_all = (fptr_T)do_Upper; + func_all = do_upper; continue; case 'l': - func_one = (fptr_T)do_lower; + func_one = do_lower; continue; case 'L': - func_all = (fptr_T)do_Lower; + func_all = do_lower; continue; case 'e': case 'E': @@ -1954,11 +1930,13 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen } else { c = utf_ptr2char(src - 1); } + // Write to buffer, if copy is set. if (func_one != NULL) { - func_one = (fptr_T)(func_one(&cc, c)); + func_one(&cc, c); + func_one = NULL; } else if (func_all != NULL) { - func_all = (fptr_T)(func_all(&cc, c)); + func_all(&cc, c); } else { // just copy cc = c; @@ -2061,11 +2039,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen c = utf_ptr2char(s); if (func_one != (fptr_T)NULL) { - // Turbo C complains without the typecast - func_one = (fptr_T)(func_one(&cc, c)); + func_one(&cc, c); + func_one = NULL; } else if (func_all != (fptr_T)NULL) { - // Turbo C complains without the typecast - func_all = (fptr_T)(func_all(&cc, c)); + func_all(&cc, c); } else { // just copy cc = c; } diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 30c98a2f1e..4d318f8c59 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -153,13 +153,13 @@ void win_redr_status(win_T *wp) row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); col = is_stl_global ? 0 : wp->w_wincol; - int width = grid_puts(&default_grid, p, row, col, attr); + int width = grid_puts(&default_grid, p, -1, row, col, attr); grid_fill(&default_grid, row, row + 1, width + col, this_ru_col + col, fillchar, fillchar, attr); if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL) && this_ru_col - len > (int)(strlen(NameBuff) + 1)) { - grid_puts(&default_grid, NameBuff, row, + grid_puts(&default_grid, NameBuff, -1, row, (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr); } @@ -170,8 +170,8 @@ void win_redr_status(win_T *wp) const int sc_width = MIN(10, this_ru_col - len - 2); if (sc_width > 0) { - grid_puts_len(&default_grid, showcmd_buf, sc_width, row, - wp->w_wincol + this_ru_col - sc_width - 1, attr); + grid_puts(&default_grid, showcmd_buf, sc_width, row, + wp->w_wincol + this_ru_col - sc_width - 1, attr); } } } @@ -419,7 +419,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) int start_col = col; // Draw each snippet with the specified highlighting. - grid_puts_line_start(grid, row); + grid_line_start(grid, row); int curattr = attr; char *p = buf; @@ -427,7 +427,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) int textlen = (int)(hltab[n].start - p); // Make all characters printable. size_t tsize = transstr_buf(p, textlen, transbuf, sizeof transbuf, true); - col += grid_puts_len(grid, transbuf, (int)tsize, row, col, curattr); + col += grid_line_puts(col, transbuf, (int)tsize, curattr); p = hltab[n].start; if (hltab[n].userhl == 0) { @@ -442,13 +442,13 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) } // Make sure to use an empty string instead of p, if p is beyond buf + len. size_t tsize = transstr_buf(p >= buf + len ? "" : p, -1, transbuf, sizeof transbuf, true); - col += grid_puts_len(grid, transbuf, (int)tsize, row, col, curattr); + col += grid_line_puts(col, transbuf, (int)tsize, curattr); int maxcol = start_col + maxwidth; // fill up with "fillchar" - grid_fill(grid, row, row + 1, col, maxcol, fillchar, fillchar, curattr); + grid_line_fill(col, maxcol, fillchar, curattr); - grid_puts_line_flush(false); + grid_line_flush(false); // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking // in the tab page line, status line or window bar @@ -618,7 +618,7 @@ void win_redr_ruler(win_T *wp) } ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; - grid_puts(grid, buffer, row, this_ru_col + off, attr); + grid_puts(grid, buffer, -1, row, this_ru_col + off, attr); grid_fill(grid, row, row + 1, this_ru_col + off + (int)strlen(buffer), off + width, fillchar, fillchar, attr); @@ -637,18 +637,7 @@ int fillchar_status(int *attr, win_T *wp) *attr = win_hl_attr(wp, HLF_SNC); fill = wp->w_p_fcs_chars.stlnc; } - // Use fill when there is highlighting, and highlighting of current - // window differs, or the fillchars differ, or this is not the - // current window - if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || ONE_WINDOW) - || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { - return fill; - } - if (is_curwin) { - return '^'; - } - return '='; + return fill; } /// Redraw the status line according to 'statusline' and take care of any @@ -810,12 +799,12 @@ void draw_tabline(void) if (col + len >= Columns - 3) { break; } - grid_puts_len(&default_grid, NameBuff, len, 0, col, - hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); + grid_puts(&default_grid, NameBuff, len, 0, col, + hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); col += len; } if (modified) { - grid_puts_len(&default_grid, "+", 1, 0, col++, attr); + grid_puts(&default_grid, "+", 1, 0, col++, attr); } grid_putchar(&default_grid, ' ', 0, col++, attr); } @@ -835,7 +824,7 @@ void draw_tabline(void) len = Columns - col - 1; } - grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr); + grid_puts(&default_grid, p, (int)strlen(p), 0, col, attr); col += len; } grid_putchar(&default_grid, ' ', 0, col++, attr); @@ -864,8 +853,8 @@ void draw_tabline(void) const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3); if (sc_width > 0) { - grid_puts_len(&default_grid, showcmd_buf, sc_width, 0, - Columns - sc_width - (tabcount > 1) * 2, attr_nosel); + grid_puts(&default_grid, showcmd_buf, sc_width, 0, + Columns - sc_width - (tabcount > 1) * 2, attr_nosel); } } @@ -1583,7 +1572,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n // Note: The call will only return true if it actually // appended data to the `buf_tmp` buffer. - if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp))) { str = buf_tmp; } break; @@ -1663,7 +1652,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n char *p = NULL; if (fold) { - size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM)); + size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, + (linenr_T)get_vim_var_nr(VV_LNUM), NULL); stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1); p = out_p; p[n] = NUL; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0983667695..db15cdb053 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -24,6 +24,7 @@ #include "nvim/event/signal.h" #include "nvim/event/stream.h" #include "nvim/globals.h" +#include "nvim/grid.h" #include "nvim/grid_defs.h" #include "nvim/highlight_defs.h" #include "nvim/log.h" @@ -144,6 +145,7 @@ struct TUIData { } unibi_ext; char *space_buf; bool stopped; + int seen_error_exit; int width; int height; bool rgb; @@ -161,6 +163,7 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term) tui->is_starting = true; tui->screenshot = NULL; tui->stopped = false; + tui->seen_error_exit = 0; tui->loop = &main_loop; kv_init(tui->invalid_regions); signal_watcher_init(tui->loop, &tui->winch_handle, tui); @@ -383,8 +386,16 @@ static void terminfo_stop(TUIData *tui) unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); // May restore old title before exiting alternate screen. tui_set_title(tui, (String)STRING_INIT); - // Exit alternate screen. - unibi_out(tui, unibi_exit_ca_mode); + if (ui_client_exit_status == 0) { + ui_client_exit_status = tui->seen_error_exit; + } + // if nvim exited with nonzero status, without indicated this was an + // intentional exit (like `:1cquit`), it likely was an internal failure. + // Don't clobber the stderr error message in this case. + if (ui_client_exit_status == tui->seen_error_exit) { + // Exit alternate screen. + unibi_out(tui, unibi_exit_ca_mode); + } if (tui->cursor_color_changed) { unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } @@ -442,6 +453,11 @@ static void tui_terminal_stop(TUIData *tui) terminfo_stop(tui); } +void tui_error_exit(TUIData *tui, Integer status) +{ + tui->seen_error_exit = (int)status; +} + void tui_stop(TUIData *tui) { tui_terminal_stop(tui); @@ -675,15 +691,15 @@ static void final_column_wrap(TUIData *tui) /// It is undocumented, but in the majority of terminals and terminal emulators /// printing at the right margin does not cause an automatic wrap until the /// next character is printed, holding the cursor in place until then. -static void print_cell(TUIData *tui, UCell *ptr) +static void print_cell(TUIData *tui, char *buf, sattr_T attr) { UGrid *grid = &tui->grid; if (!tui->immediate_wrap_after_last_column) { // Printing the next character finally advances the cursor. final_column_wrap(tui); } - update_attrs(tui, ptr->attr); - out(tui, ptr->data, strlen(ptr->data)); + update_attrs(tui, attr); + out(tui, buf, strlen(buf)); grid->col++; if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. @@ -703,8 +719,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next) return false; } } - if (strlen(cell->data) > 1) { - return false; + if (schar_get_ascii(cell->data) == 0) { + return false; // not ascii } cell++; } @@ -831,14 +847,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool { UGrid *grid = &tui->grid; - if (grid->row == -1 && cell->data[0] == NUL) { + if (grid->row == -1 && cell->data == NUL) { // If cursor needs to repositioned and there is nothing to print, don't move cursor. return; } cursor_goto(tui, row, col); - bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data)); + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, cell->data); + bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf)); if (is_ambiwidth && is_doublewidth) { // Clear the two screen cells. // If the character is single-width in the host terminal it won't change the second cell. @@ -847,7 +865,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool cursor_goto(tui, row, col); } - print_cell(tui, cell); + print_cell(tui, buf, cell->attr); if (is_ambiwidth) { // Force repositioning cursor after printing an ambiguous-width character. @@ -976,6 +994,8 @@ void tui_grid_clear(TUIData *tui, Integer g) { UGrid *grid = &tui->grid; ugrid_clear(grid); + // safe to clear cache at this point + schar_cache_clear_if_full(); kv_size(tui->invalid_regions) = 0; clear_region(tui, 0, tui->height, 0, tui->width, 0); } @@ -1273,7 +1293,7 @@ void tui_flush(TUIData *tui) int clear_col; for (clear_col = r.right; clear_col > 0; clear_col--) { UCell *cell = &grid->cells[row][clear_col - 1]; - if (!(cell->data[0] == ' ' && cell->data[1] == NUL + if (!(cell->data == schar_from_ascii(' ') && cell->attr == clear_attr)) { break; } @@ -1281,7 +1301,7 @@ void tui_flush(TUIData *tui) UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { print_cell_at_pos(tui, row, curcol, cell, - curcol < clear_col - 1 && (cell + 1)->data[0] == NUL); + curcol < clear_col - 1 && (cell + 1)->data == NUL); }); if (clear_col < r.right) { clear_region(tui, row, row + 1, clear_col, r.right, clear_attr); @@ -1399,7 +1419,10 @@ void tui_screenshot(TUIData *tui, String path) for (int i = 0; i < grid->height; i++) { cursor_goto(tui, i, 0); for (int j = 0; j < grid->width; j++) { - print_cell(tui, &grid->cells[i][j]); + UCell cell = grid->cells[i][j]; + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, cell.data); + print_cell(tui, buf, cell.attr); } } flush_buf(tui); @@ -1446,13 +1469,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In { UGrid *grid = &tui->grid; for (Integer c = startcol; c < endcol; c++) { - memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T)); + grid->cells[linerow][c].data = chunk[c - startcol]; assert((size_t)attrs[c - startcol] < kv_size(tui->attrs)); grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { print_cell_at_pos(tui, (int)linerow, curcol, cell, - curcol < endcol - 1 && (cell + 1)->data[0] == NUL); + curcol < endcol - 1 && (cell + 1)->data == NUL); }); if (clearcol > endcol) { @@ -1469,7 +1492,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In if (endcol != grid->width) { // Print the last char of the row, if we haven't already done so. - int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1; + int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1; print_cell_at_pos(tui, (int)linerow, grid->width - size, &grid->cells[linerow][grid->width - size], size == 2); } diff --git a/src/nvim/types.h b/src/nvim/types.h index 3a05223023..afc1b3f5fe 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -46,4 +46,10 @@ typedef enum { typedef struct Decoration Decoration; +#ifndef ORDER_BIG_ENDIAN +# if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define ORDER_BIG_ENDIAN +# endif +#endif + #endif // NVIM_TYPES_H diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index be1746983c..d3a1097aba 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -4,6 +4,7 @@ #include <assert.h> #include <string.h> +#include "nvim/grid.h" #include "nvim/memory.h" #include "nvim/ugrid.h" @@ -79,8 +80,7 @@ static void clear_region(UGrid *grid, int top, int bot, int left, int right, sat { for (int row = top; row <= bot; row++) { UGRID_FOREACH_CELL(grid, row, left, right + 1, { - cell->data[0] = ' '; - cell->data[1] = 0; + cell->data = schar_from_ascii(' '); cell->attr = attr; }); } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index a85a6fb4a8..1c73c43867 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -11,10 +11,8 @@ struct ugrid; typedef struct ucell UCell; typedef struct ugrid UGrid; -#define CELLBYTES (sizeof(schar_T)) - struct ucell { - char data[CELLBYTES + 1]; + schar_T data; sattr_T attr; }; diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h index 7e5f847039..05964422f3 100644 --- a/src/nvim/ui_client.h +++ b/src/nvim/ui_client.h @@ -23,6 +23,9 @@ EXTERN sattr_T *grid_line_buf_attr INIT(= NULL); // ID of the ui client channel. If zero, the client is not running. EXTERN uint64_t ui_client_channel_id INIT(= 0); +// exit status from embedded nvim process +EXTERN int ui_client_exit_status INIT(= 0); + // TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward. // This will be restructured as part of The UI Devirtualization Project. diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index e9b23d1298..b88c355f42 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -55,7 +55,7 @@ static int msg_current_row = INT_MAX; static bool msg_was_scrolled = false; static int msg_sep_row = -1; -static schar_T msg_sep_char = { ' ', NUL }; +static schar_T msg_sep_char = schar_from_ascii(' '); static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; @@ -354,7 +354,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag grid = &msg_grid; sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP); for (int i = col; i < until; i++) { - memcpy(linebuf[i - startcol], msg_sep_char, sizeof(*linebuf)); + linebuf[i - startcol] = msg_sep_char; attrbuf[i - startcol] = msg_sep_attr; } } else { @@ -363,9 +363,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf)); memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf)); if (grid->comp_col + grid->cols > until - && grid->chars[off + n][0] == NUL) { - linebuf[until - 1 - startcol][0] = ' '; - linebuf[until - 1 - startcol][1] = '\0'; + && grid->chars[off + n] == NUL) { + linebuf[until - 1 - startcol] = schar_from_ascii(' '); if (col == startcol && n == 1) { skipstart = 0; } @@ -378,10 +377,10 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag for (int i = col - (int)startcol; i < until - startcol; i += width) { width = 1; // negative space - bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL; - if (i + 1 < endcol - startcol && bg_line[i + 1][0] == NUL) { + bool thru = linebuf[i] == schar_from_ascii(' ') && bg_line[i] != NUL; + if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) { width = 2; - thru &= strequal((char *)linebuf[i + 1], " "); + thru &= linebuf[i + 1] == schar_from_ascii(' '); } attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru); if (width == 2) { @@ -396,28 +395,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag // Tricky: if overlap caused a doublewidth char to get cut-off, must // replace the visible half with a space. - if (linebuf[col - startcol][0] == NUL) { - linebuf[col - startcol][0] = ' '; - linebuf[col - startcol][1] = NUL; + if (linebuf[col - startcol] == NUL) { + linebuf[col - startcol] = schar_from_ascii(' '); if (col == endcol - 1) { skipend = 0; } - } else if (col == startcol && n > 1 && linebuf[1][0] == NUL) { + } else if (col == startcol && n > 1 && linebuf[1] == NUL) { skipstart = 0; } col = until; } - if (linebuf[endcol - startcol - 1][0] == NUL) { + if (linebuf[endcol - startcol - 1] == NUL) { skipend = 0; } assert(endcol <= chk_width); assert(row < chk_height); - if (!(grid && grid == &default_grid)) { - // TODO(bfredl): too conservative, need check - // grid->line_wraps if grid->Width == Width + if (!(grid && (grid == &default_grid || (grid->comp_col == 0 && grid->cols == Columns)))) { flags = flags & ~kLineFlagWrap; } @@ -568,7 +564,7 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep if (scrolled && row > 0) { msg_sep_row = (int)row - 1; if (sep_char.data) { - xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); + msg_sep_char = schar_from_buf(sep_char.data, sep_char.size); } } else { msg_sep_row = -1; diff --git a/src/nvim/version.c b/src/nvim/version.c index b6861142cc..0744a601bb 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2837,12 +2837,6 @@ void intro_message(int colon) } } } - - // Make the wait-return message appear just below the text. - if (colon) { - assert(row <= INT_MAX); - msg_row = (int)row; - } } static void do_intro_line(long row, char *mesg, int attr) @@ -2871,8 +2865,8 @@ static void do_intro_line(long row, char *mesg, int attr) l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); - grid_puts_len(&default_grid, p, l, (int)row, (int)col, - *p == '<' ? HL_ATTR(HLF_8) : attr); + grid_puts(&default_grid, p, l, (int)row, (int)col, + *p == '<' ? HL_ATTR(HLF_8) : attr); col += clen; } } diff --git a/src/nvim/window.c b/src/nvim/window.c index c0d399c4c4..1208494eaf 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1009,7 +1009,9 @@ void ui_ext_win_position(win_T *wp, bool validate) comp_row += grid->comp_row; comp_col += grid->comp_col; comp_row = MAX(MIN(comp_row, Rows - wp->w_height_outer - (p_ch > 0 ? 1 : 0)), 0); - comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0); + if (!c.fixed || east) { + comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0); + } wp->w_winrow = comp_row; wp->w_wincol = comp_col; ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, |