diff options
Diffstat (limited to 'src')
44 files changed, 822 insertions, 487 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index aa0c2695ad..2e4d2a622d 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -49,19 +49,20 @@ static int64_t next_autocmd_id = 1; /// Get all autocommands that match the corresponding {opts}. /// /// These examples will get autocommands matching ALL the given criteria: -/// <pre>lua -/// -- Matches all criteria -/// autocommands = vim.api.nvim_get_autocmds({ -/// group = "MyGroup", -/// event = {"BufEnter", "BufWinEnter"}, -/// pattern = {"*.c", "*.h"} -/// }) /// -/// -- All commands from one group -/// autocommands = vim.api.nvim_get_autocmds({ -/// group = "MyGroup", -/// }) -/// </pre> +/// ```lua +/// -- Matches all criteria +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// event = {"BufEnter", "BufWinEnter"}, +/// pattern = {"*.c", "*.h"} +/// }) +/// +/// -- All commands from one group +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// }) +/// ``` /// /// NOTE: When multiple patterns or events are provided, it will find all the autocommands that /// match any combination of them. @@ -344,28 +345,31 @@ cleanup: /// function _name_ string) or `command` (Ex command string). /// /// Example using Lua callback: -/// <pre>lua -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// callback = function(ev) -/// print(string.format('event fired: \%s', vim.inspect(ev))) -/// end -/// }) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// callback = function(ev) +/// print(string.format('event fired: %s', vim.inspect(ev))) +/// end +/// }) +/// ``` /// /// Example using an Ex command as the handler: -/// <pre>lua -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// command = "echo 'Entering a C or C++ file'", -/// }) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// command = "echo 'Entering a C or C++ file'", +/// }) +/// ``` /// /// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME" /// and "~" must be expanded explicitly: -/// <pre>lua -/// pattern = vim.fn.expand("~") .. "/some/path/*.py" -/// </pre> +/// +/// ```lua +/// pattern = vim.fn.expand("~") .. "/some/path/*.py" +/// ``` /// /// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`). /// @param opts Options dict: @@ -619,11 +623,12 @@ cleanup: /// Create or get an autocommand group |autocmd-groups|. /// /// To get an existing group id, do: -/// <pre>lua -/// local id = vim.api.nvim_create_augroup("MyGroup", { -/// clear = false -/// }) -/// </pre> +/// +/// ```lua +/// local id = vim.api.nvim_create_augroup("MyGroup", { +/// clear = false +/// }) +/// ``` /// /// @param name String: The name of the group /// @param opts Dictionary Parameters diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index baac694848..e8f9f809f2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -85,11 +85,15 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// /// Example (Lua): capture buffer updates in a global `events` variable /// (use "vim.print(events)" to see its contents): -/// <pre>lua -/// events = {} -/// vim.api.nvim_buf_attach(0, false, { -/// on_lines=function(...) table.insert(events, {...}) end}) -/// </pre> +/// +/// ```lua +/// events = {} +/// vim.api.nvim_buf_attach(0, false, { +/// on_lines = function(...) +/// table.insert(events, {...}) +/// end, +/// }) +/// ``` /// /// @see |nvim_buf_detach()| /// @see |api-buffer-updates-lua| diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 2b09cfc4b2..808d4e0b8d 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -860,11 +860,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// For Lua usage see |lua-guide-commands-create|. /// /// Example: -/// <pre>vim -/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) -/// :SayHello -/// Hello world! -/// </pre> +/// +/// ```vim +/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) +/// :SayHello +/// Hello world! +/// ``` /// /// @param name Name of the new user command. Must begin with an uppercase letter. /// @param command Replacement command to execute when this user command is executed. When called diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index b76a275c0d..faab6e593c 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -300,10 +300,11 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) /// respectively, thus the following are equivalent: -/// <pre>lua -/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) +/// ``` /// /// If `end` is less than `start`, traversal works backwards. (Useful /// with `limit`, to get the first marks prior to a given position.) @@ -313,20 +314,21 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// of an extmark will be considered. /// /// Example: -/// <pre>lua -/// local api = vim.api -/// local pos = api.nvim_win_get_cursor(0) -/// local ns = api.nvim_create_namespace('my-plugin') -/// -- Create new extmark at line 1, column 1. -/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) -/// -- Create new extmark at line 3, column 1. -/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) -/// -- Get extmarks only from line 3. -/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) -/// -- Get all marks in this buffer + namespace. -/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) -/// vim.print(ms) -/// </pre> +/// +/// ```lua +/// local api = vim.api +/// local pos = api.nvim_win_get_cursor(0) +/// local ns = api.nvim_create_namespace('my-plugin') +/// -- Create new extmark at line 1, column 1. +/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) +/// -- Create new extmark at line 3, column 1. +/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) +/// -- Get extmarks only from line 3. +/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) +/// -- Get all marks in this buffer + namespace. +/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) +/// vim.print(ms) +/// ``` /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| or -1 for all namespaces @@ -1204,7 +1206,9 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); } - *width = w; + if (width != NULL) { + *width = w; + } return virt_text; free_exit: diff --git a/src/nvim/api/keysets.h b/src/nvim/api/keysets.h index 4e5e7af619..736ca9ce07 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/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/vim.c b/src/nvim/api/vim.c index da10ab5bd4..0a94b8aafc 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -212,10 +212,11 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err) /// nvim_feedkeys(). /// /// Example: -/// <pre>vim -/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) -/// :call nvim_feedkeys(key, 'n', v:false) -/// </pre> +/// +/// ```vim +/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) +/// :call nvim_feedkeys(key, 'n', v:false) +/// ``` /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| @@ -1280,10 +1281,11 @@ void nvim_unsubscribe(uint64_t channel_id, String event) /// "#rrggbb" hexadecimal string. /// /// Example: -/// <pre>vim -/// :echo nvim_get_color_by_name("Pink") -/// :echo nvim_get_color_by_name("#cbcbcb") -/// </pre> +/// +/// ```vim +/// :echo nvim_get_color_by_name("Pink") +/// :echo nvim_get_color_by_name("#cbcbcb") +/// ``` /// /// @param name Color name or "#rrggbb" string /// @return 24-bit RGB value, or -1 for invalid argument. @@ -1420,14 +1422,16 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. /// /// Example: -/// <pre>vim -/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) -/// </pre> +/// +/// ```vim +/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) +/// ``` /// /// is equivalent to: -/// <pre>vim -/// nmap <nowait> <Space><NL> <Nop> -/// </pre> +/// +/// ```vim +/// nmap <nowait> <Space><NL> <Nop> +/// ``` /// /// @param channel_id /// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …) @@ -1943,7 +1947,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 @@ -1959,6 +1965,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 ac27554172..3d77d65fbc 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" @@ -56,16 +56,19 @@ /// this should not be used to specify arbitrary WM screen positions. /// /// Example (Lua): window-relative float -/// <pre>lua -/// vim.api.nvim_open_win(0, false, -/// {relative='win', row=3, col=3, width=12, height=3}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_open_win(0, false, +/// {relative='win', row=3, col=3, width=12, height=3}) +/// ``` /// /// Example (Lua): buffer-relative float (travels as buffer is scrolled) -/// <pre>lua -/// vim.api.nvim_open_win(0, false, -/// {relative='win', width=12, height=3, bufpos={100,10}}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_open_win(0, false, +/// {relative='win', width=12, height=3, bufpos={100,10}}) +/// }) +/// ``` /// /// @param buffer Buffer to display, or 0 for current buffer /// @param enter Enter the window (make it the current window) @@ -160,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 /// @@ -190,7 +195,8 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E } // autocmds in win_enter or win_set_buf below may close the window if (win_valid(wp) && buffer > 0) { - win_set_buf(wp, buf, fconfig.noautocmd, err); + Boolean noautocmd = !enter || fconfig.noautocmd; + win_set_buf(wp, buf, noautocmd, err); } if (!win_valid(wp)) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); @@ -344,7 +350,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); @@ -516,7 +522,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 }, @@ -527,7 +533,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; @@ -841,6 +847,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/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/decoration.c b/src/nvim/decoration.c index 265bc11661..f4ca31040a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -268,6 +268,28 @@ 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. +static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) +{ + if (win_col < 0) { + 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; + } else { + item->draw_col = -1; + } +} + +void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state) +{ + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (item->draw_col == -3) { + decor_init_draw_col(win_col, hidden, item); + } + } +} + int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state) { buf_T *buf = wp->w_buffer; @@ -348,19 +370,10 @@ next_mark: if (active && item.decor.spell != kNone) { spell = item.decor.spell; } - if (item.start_row == state->row && decor_virt_pos(&item.decor) - && item.draw_col != INT_MIN) { - if (item.start_col <= col) { - if (item.decor.virt_text_pos == kVTOverlay && item.draw_col == -1) { - item.draw_col = (item.decor.virt_text_hide && hidden) ? INT_MIN : win_col; - } else if (item.draw_col == -3) { - item.draw_col = -1; - } - } else if (wp->w_p_wrap - && (item.decor.virt_text_pos == kVTRightAlign - || item.decor.virt_text_pos == kVTWinCol)) { - item.draw_col = -3; - } + 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_init_draw_col(win_col, hidden, &item); } if (keep) { kv_A(state->active, j++) = item; diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 3d16aa803e..0f191aa870 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -84,7 +84,7 @@ typedef struct { bool virt_text_owned; /// 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 a later screen line. + /// When -3, the virtual text should be drawn on the next screen line. /// 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 969021ef2c..8b4786a98e 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) @@ -136,8 +138,6 @@ typedef struct { ///< or w_skipcol or concealing int skipped_cells; ///< nr of skipped cells for virtual text ///< to be added to wlv.vcol later - bool more_virt_inline_chunks; ///< indicates if there is more inline virtual text - ///< after n_extra } winlinevars_T; /// for line_putchar. Contains the state that needs to be remembered from @@ -223,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[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)) { @@ -254,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[1] = 0; } done: s->p += c_len; @@ -350,17 +350,17 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, max_col - col, false, 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; } - if (col < max_col && linebuf_char[col][0] == 0) { + if (col < max_col && linebuf_char[col] == 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], ' '); + linebuf_char[col] = schar_from_ascii(' '); } vcol += cells; } @@ -386,7 +386,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; @@ -407,7 +408,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; @@ -449,16 +450,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); } @@ -868,10 +876,12 @@ static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv) } } -/// Checks if there is more inline virtual text that need to be drawn -/// and sets has_more_virt_inline_chunks to reflect that. +/// Checks if there is more inline virtual text that need to be drawn. static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) { + if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) { + return true; + } DecorState *state = &decor_state; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange *item = &kv_A(state->active, i); @@ -911,7 +921,6 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t break; } } - wlv->more_virt_inline_chunks = has_more_inline_virt(wlv, v); if (!kv_size(wlv->virt_inline)) { // no more inline virtual text here break; @@ -929,11 +938,6 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t wlv->c_final = NUL; wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; wlv->n_attr = mb_charlen(vtc.text); - - // Checks if there is more inline virtual text chunks that need to be drawn. - wlv->more_virt_inline_chunks = has_more_inline_virt(wlv, v) - || wlv->virt_inline_i < kv_size(wlv->virt_inline); - // If the text didn't reach until the first window // column we need to skip cells. if (wlv->skip_cells > 0) { @@ -1147,8 +1151,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl bool saved_search_attr_from_match = false; int win_col_offset = 0; // offset for window columns + bool area_active = false; // whether in Visual selection, for virtual text + bool decor_need_recheck = false; // call decor_recheck_draw_col() at next char char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext + VirtText fold_vt = VIRTTEXT_EMPTY; // 'cursorlineopt' has "screenline" and cursor is in this line bool cul_screenline = false; @@ -1788,9 +1795,27 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } if (has_decor && wlv.n_extra == 0) { - bool selected = (area_highlighting - && ((wlv.vcol >= wlv.fromcol && wlv.vcol < wlv.tocol) - || (noinvcur && wlv.vcol == wp->w_virtcol))); + // Duplicate the Visual area check after this block, + // but don't check inside p_extra here. + if (wlv.vcol == wlv.fromcol + || (wlv.vcol + 1 == wlv.fromcol + && (wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1)) + || (vcol_prev == fromcol_prev + && vcol_prev < wlv.vcol + && wlv.vcol < wlv.tocol)) { + area_active = true; + } else if (area_active + && (wlv.vcol == wlv.tocol + || (noinvcur && wlv.vcol == wp->w_virtcol))) { + area_active = false; + } + + bool selected = (area_active || (area_highlighting && noinvcur + && wlv.vcol == wp->w_virtcol)); + if (decor_need_recheck) { + decor_recheck_draw_col(wlv.off, selected, &decor_state); + decor_need_recheck = false; + } extmark_attr = decor_redraw_col(wp, (colnr_T)v, wlv.off, selected, &decor_state); if (!has_fold && wp->w_buffer->b_virt_text_inline > 0) { @@ -1824,10 +1849,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && vcol_prev < wlv.vcol // not at margin && wlv.vcol < wlv.tocol)) { *area_attr_p = vi_attr; // start highlighting + area_active = true; } else if (*area_attr_p != 0 && (wlv.vcol == wlv.tocol || (noinvcur && wlv.vcol == wp->w_virtcol))) { *area_attr_p = 0; // stop highlighting + area_active = false; } if (!has_fold && wlv.n_extra == 0) { @@ -1900,7 +1927,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl if (draw_folded && wlv.n_extra == 0 && wlv.col == win_col_offset) { linenr_T lnume = lnum + foldinfo.fi_lines - 1; memset(buf_fold, ' ', FOLD_TEXT_LEN); - wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold); + wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold, &fold_vt); wlv.n_extra = (int)strlen(wlv.p_extra); if (wlv.p_extra != buf_fold) { @@ -2720,7 +2747,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 @@ -2822,7 +2849,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) { @@ -2856,7 +2883,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; @@ -2865,6 +2892,9 @@ 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(wp, buf, win_col_offset, &wlv.col, 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++; @@ -2890,15 +2920,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && wlv.filler_todo <= 0 && (wp->w_p_rl ? wlv.col == 0 : wlv.col == grid->cols - 1) && !has_fold) { - if (*ptr == NUL && lcs_eol_one == 0 && has_decor) { + if (has_decor && *ptr == NUL && lcs_eol_one == 0) { // Tricky: there might be a virtual text just _after_ the last char decor_redraw_col(wp, (colnr_T)v, wlv.off, false, &decor_state); - handle_inline_virtual_text(wp, &wlv, v); } if (*ptr != NUL || lcs_eol_one > 0 || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) - || wlv.more_virt_inline_chunks) { + || has_more_inline_virt(&wlv, v)) { c = wp->w_p_lcs_chars.ext; wlv.char_attr = win_hl_attr(wp, HLF_AT); mb_c = c; @@ -2954,9 +2983,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; @@ -2967,12 +2996,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl linebuf_vcol[wlv.off] = wlv.vcol; + if (wlv.draw_state == WL_FOLD) { + linebuf_vcol[wlv.off] = -2; + if (wlv.n_closing > 0) { + linebuf_vcol[wlv.off] = -3; + wlv.n_closing--; + } + } + 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++; @@ -3085,13 +3122,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.char_attr = saved_attr2; } - if ((wp->w_p_rl ? (wlv.col < 0) : (wlv.col >= grid->cols)) && has_decor) { + if (has_decor && wlv.filler_todo <= 0 + && (wp->w_p_rl ? (wlv.col < 0) : (wlv.col >= grid->cols))) { // At the end of screen line: might need to peek for decorations just after - // this position. Without wrapping, we might need to display win_pos overlays - // from the entire text line. - colnr_T nextpos = wp->w_p_wrap ? (colnr_T)(ptr - line) : (colnr_T)strlen(line); - decor_redraw_col(wp, nextpos, wlv.off, true, &decor_state); - handle_inline_virtual_text(wp, &wlv, v); + // this position. + if (!has_fold && wp->w_p_wrap && wlv.n_extra == 0) { + decor_redraw_col(wp, (int)(ptr - line), -3, false, &decor_state); + // Check position/hiding of virtual text again on next screen line. + decor_need_recheck = true; + } else if (has_fold || !wp->w_p_wrap) { + // Without wrapping, we might need to display right_align and win_col + // virt_text for the entire text line. + decor_redraw_col(wp, MAXCOL, -1, true, &decor_state); + } } // At end of screen line and there is more to come: Display the line @@ -3104,7 +3147,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && wlv.p_extra != at_end_str) || (wlv.n_extra != 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) - || wlv.more_virt_inline_chunks)) { + || has_more_inline_virt(&wlv, v))) { bool wrap = wp->w_p_wrap // Wrapping enabled. && wlv.filler_todo <= 0 // Not drawing diff filler lines. && lcs_eol_one != -1 // Haven't printed the lcs_eol character. @@ -3117,7 +3160,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl 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); - } else { + } else if (wlv.filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, wlv.row); } @@ -3186,6 +3229,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } } // for every character in the line + clear_virttext(&fold_vt); kv_destroy(virt_lines); xfree(wlv.p_extra_free); xfree(wlv.saved_p_extra_free); diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index f71a47a596..e3e9fc8170 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -444,6 +444,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) { @@ -736,7 +745,10 @@ 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; @@ -770,7 +782,7 @@ static void win_redr_border(win_T *wp) grid_puts_line_flush(false); } if (adj[1]) { - int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; + int ic = (i == 0 && !adj[0] && chars[2]) ? 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); @@ -784,7 +796,7 @@ static void win_redr_border(win_T *wp) } for (int i = 0; i < icol; i++) { - int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; + int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5; grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 06eb81be92..216f8a67db 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4429,9 +4429,8 @@ static bool ins_tab(void) } } if (!(State & VREPLACE_FLAG)) { - extmark_splice_cols(curbuf, (int)fpos.lnum - 1, change_col, - cursor->col - change_col, fpos.col - change_col, - kExtmarkUndo); + inserted_bytes(fpos.lnum, change_col, + cursor->col - change_col, fpos.col - change_col); } } cursor->col -= i; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9be4cea059..21c7cdee7d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12,7 +12,9 @@ #include <string.h> #include "auto/config.h" +#include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" @@ -947,38 +949,42 @@ int skip_expr(char **pp, evalarg_T *const evalarg) return res; } +/// Convert "tv" to a string. +/// +/// @param convert when true convert a List into a sequence of lines. +/// +/// @return an allocated string. +static char *typval2string(typval_T *tv, bool convert) +{ + if (convert && tv->v_type == VAR_LIST) { + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + if (tv->vval.v_list != NULL) { + tv_list_join(&ga, tv->vval.v_list, "\n"); + if (tv_list_len(tv->vval.v_list) > 0) { + ga_append(&ga, NL); + } + } + ga_append(&ga, NUL); + return (char *)ga.ga_data; + } + return xstrdup(tv_get_string(tv)); +} + /// Top level evaluation function, returning a string. /// -/// @param convert when true convert a List into a sequence of lines and convert -/// a Float to a String. +/// @param convert when true convert a List into a sequence of lines. /// -/// @return pointer to allocated memory, or NULL for failure. +/// @return pointer to allocated memory, or NULL for failure. char *eval_to_string(char *arg, bool convert) { typval_T tv; char *retval; - garray_T ga; if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { retval = NULL; } else { - if (convert && tv.v_type == VAR_LIST) { - ga_init(&ga, (int)sizeof(char), 80); - if (tv.vval.v_list != NULL) { - tv_list_join(&ga, tv.vval.v_list, "\n"); - if (tv_list_len(tv.vval.v_list) > 0) { - ga_append(&ga, NL); - } - } - ga_append(&ga, NUL); - retval = (char *)ga.ga_data; - } else if (convert && tv.v_type == VAR_FLOAT) { - char numbuf[NUMBUFLEN]; - vim_snprintf(numbuf, NUMBUFLEN, "%g", tv.vval.v_float); - retval = xstrdup(numbuf); - } else { - retval = xstrdup(tv_get_string(&tv)); - } + retval = typval2string(&tv, convert); tv_clear(&tv); } clear_evalarg(&EVALARG_EVALUATE, NULL); @@ -990,7 +996,7 @@ char *eval_to_string(char *arg, bool convert) /// textlock. /// /// @param use_sandbox when true, use the sandbox. -char *eval_to_string_safe(char *arg, int use_sandbox) +char *eval_to_string_safe(char *arg, const bool use_sandbox) { char *retval; funccal_entry_T funccal_entry; @@ -1263,11 +1269,13 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv) /// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding /// it in "*cp". Doesn't give error messages. -int eval_foldexpr(char *arg, int *cp) +int eval_foldexpr(win_T *wp, int *cp) { - typval_T tv; - varnumber_T retval; - int use_sandbox = was_set_insecurely(curwin, "foldexpr", OPT_LOCAL); + 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) { @@ -1275,6 +1283,9 @@ int eval_foldexpr(char *arg, int *cp) } textlock++; *cp = NUL; + + typval_T tv; + varnumber_T retval; if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { retval = 0; } else { @@ -1294,16 +1305,54 @@ int eval_foldexpr(char *arg, int *cp) } tv_clear(&tv); } + emsg_off--; if (use_sandbox) { sandbox--; } textlock--; clear_evalarg(&EVALARG_EVALUATE, NULL); + current_sctx = saved_sctx; return (int)retval; } +/// Evaluate 'foldtext', returning an Array or a String (NULL_STRING on failure). +Object eval_foldtext(win_T *wp) +{ + const bool use_sandbox = was_set_insecurely(wp, "foldtext", OPT_LOCAL); + char *arg = wp->w_p_fdt; + funccal_entry_T funccal_entry; + + save_funccal(&funccal_entry); + if (use_sandbox) { + sandbox++; + } + textlock++; + + typval_T tv; + Object retval; + if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { + retval = STRING_OBJ(NULL_STRING); + } else { + if (tv.v_type == VAR_LIST) { + retval = vim_to_object(&tv); + } else { + retval = STRING_OBJ(cstr_to_string(tv_get_string(&tv))); + } + tv_clear(&tv); + } + clear_evalarg(&EVALARG_EVALUATE, NULL); + + if (use_sandbox) { + sandbox--; + } + textlock--; + restore_funccal(); + + return retval; +} + /// Get an lvalue /// /// Lvalue may be diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e6efbe85d8..2137dc07e4 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -2111,7 +2111,11 @@ M.funcs = { ]=], name = 'execute', - params = { { 'command', 'any' }, { 'silent', 'boolean' } }, + params = { + { 'command', 'string|string[]' }, + { 'silent', "''|'silent'|'silent!'" } + }, + returns = 'string', signature = 'execute({command} [, {silent}])', }, exepath = { diff --git a/src/nvim/fold.c b/src/nvim/fold.c index a6cb0b568c..1d5ba49301 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -12,12 +12,14 @@ #include <stdlib.h> #include <string.h> +#include "nvim/api/extmark.h" #include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" @@ -1702,8 +1704,9 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker /// @return the text for a closed fold /// /// Otherwise the result is in allocated memory. -char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf) - FUNC_ATTR_NONNULL_ARG(1) +char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf, + VirtText *vt) + FUNC_ATTR_NONNULL_ALL { char *text = NULL; // an error occurred when evaluating 'fdt' setting @@ -1750,8 +1753,22 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo current_sctx = wp->w_p_script_ctx[WV_FDT].script_ctx; emsg_off++; // handle exceptions, but don't display errors - text = eval_to_string_safe(wp->w_p_fdt, - was_set_insecurely(wp, "foldtext", OPT_LOCAL)); + + Object obj = eval_foldtext(wp); + if (obj.type == kObjectTypeArray) { + Error err = ERROR_INIT; + *vt = parse_virt_text(obj.data.array, &err, NULL); + if (!ERROR_SET(&err)) { + *buf = NUL; + text = buf; + } + api_clear_error(&err); + } else if (obj.type == kObjectTypeString) { + text = obj.data.string.data; + obj = NIL; + } + api_free_object(obj); + emsg_off--; if (text == NULL || did_emsg) { @@ -2929,7 +2946,7 @@ static void foldlevelExpr(fline_T *flp) const bool save_keytyped = KeyTyped; int c; - const int n = eval_foldexpr(flp->wp->w_p_fde, &c); + const int n = eval_foldexpr(flp->wp, &c); KeyTyped = save_keytyped; switch (c) { @@ -3320,10 +3337,20 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) foldinfo_T info = fold_info(curwin, lnum); if (info.fi_lines > 0) { - char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); + VirtText vt = VIRTTEXT_EMPTY; + char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf, &vt); if (text == buf) { text = xstrdup(text); } + 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); + xfree(text); + text = new_text; + } + } + clear_virttext(&vt); rettv->vval.v_string = text; } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index fa7f270172..cf6cd2f04e 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 + } +} + +/// 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; } - p[len] = 0; - return len; } +/// @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; @@ -155,7 +259,7 @@ 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 @@ -185,12 +289,12 @@ void grid_puts_line_start(ScreenGrid *grid, int row) put_dirty_grid = grid; } -void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr) +void grid_put_schar(ScreenGrid *grid, int row, 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); + 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); @@ -293,10 +397,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,15 +426,15 @@ 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; } @@ -429,12 +535,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,7 +549,7 @@ 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) { @@ -483,11 +589,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 +649,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 +659,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 +670,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 +723,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 +749,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 +758,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; 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..b0d1dafd29 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 { @@ -41,6 +45,9 @@ enum { /// /// vcols[] contains the virtual columns in the line. -1 means not available /// (below last line), 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 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/highlight_group.c b/src/nvim/highlight_group.c index b970e752bb..84cf19ba69 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -824,7 +824,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) g->sg_link = 0; } - g->sg_gui = attrs.rgb_ae_attr; + g->sg_gui = attrs.rgb_ae_attr &~HL_DEFAULT; g->sg_rgb_fg = attrs.rgb_fg_color; g->sg_rgb_bg = attrs.rgb_bg_color; @@ -851,7 +851,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) } } - g->sg_cterm = attrs.cterm_ae_attr; + g->sg_cterm = attrs.cterm_ae_attr &~HL_DEFAULT; g->sg_cterm_bg = attrs.cterm_bg_color; g->sg_cterm_fg = attrs.cterm_fg_color; g->sg_cterm_bold = g->sg_cterm & HL_BOLD; @@ -1441,7 +1441,7 @@ void restore_cterm_colors(void) /// @param check_link if true also check for an existing link. /// /// @return true if highlight group "idx" has any settings. -static int hl_has_settings(int idx, bool check_link) +static bool hl_has_settings(int idx, bool check_link) { return hl_table[idx].sg_cleared == 0 && (hl_table[idx].sg_attr != 0 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/mapping.c b/src/nvim/mapping.c index ab528f8865..35a728314c 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -148,9 +148,7 @@ mapblock_T *get_maphash(int index, buf_T *buf) /// "mpp" is a pointer to the m_next field of the PREVIOUS entry! static void mapblock_free(mapblock_T **mpp) { - mapblock_T *mp; - - mp = *mpp; + mapblock_T *mp = *mpp; xfree(mp->m_keys); if (!mp->m_simplified) { NLUA_CLEAR_REF(mp->m_luaref); @@ -212,8 +210,6 @@ static char *map_mode_to_chars(int mode) /// @param local true for buffer-local map static void showmap(mapblock_T *mp, bool local) { - size_t len = 1; - if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) && (mp->m_desc == NULL || message_filtered(mp->m_desc))) { return; @@ -226,12 +222,10 @@ static void showmap(mapblock_T *mp, bool local) } } - { - char *const mapchars = map_mode_to_chars(mp->m_mode); - msg_puts(mapchars); - len = strlen(mapchars); - xfree(mapchars); - } + char *const mapchars = map_mode_to_chars(mp->m_mode); + msg_puts(mapchars); + size_t len = strlen(mapchars); + xfree(mapchars); while (++len <= 3) { msg_putchar(' '); @@ -572,33 +566,16 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, /// @param buf Target Buffer static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf) { - mapblock_T *mp, **mpp; - const char *p; - int n; int retval = 0; - mapblock_T **abbr_table; - mapblock_T **map_table; - int noremap; - map_table = maphash; - abbr_table = &first_abbr; + // If <buffer> was given, we'll be searching through the buffer's + // mappings/abbreviations, not the globals. + mapblock_T **map_table = args->buffer ? buf->b_maphash : maphash; + mapblock_T **abbr_table = args->buffer ? &buf->b_first_abbr : &first_abbr; // For ":noremap" don't remap, otherwise do remap. - if (maptype == MAPTYPE_NOREMAP) { - noremap = REMAP_NONE; - } else { - noremap = REMAP_YES; - } - - if (args->buffer) { - // If <buffer> was given, we'll be searching through the buffer's - // mappings/abbreviations, not the globals. - map_table = buf->b_maphash; - abbr_table = &buf->b_first_abbr; - } - if (args->script) { - noremap = REMAP_SCRIPT; - } + int noremap = args->script ? REMAP_SCRIPT : + maptype == MAPTYPE_NOREMAP ? REMAP_NONE : REMAP_YES; const bool has_lhs = (args->lhs[0] != NUL); const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop; @@ -648,8 +625,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, const int first = vim_iswordp(lhs); int last = first; - p = lhs + utfc_ptr2len(lhs); - n = 1; + const char *p = lhs + utfc_ptr2len(lhs); + int n = 1; while (p < lhs + len) { n++; // nr of (multi-byte) chars last = vim_iswordp(p); // type of last char @@ -685,6 +662,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, && maptype != MAPTYPE_UNMAP) { // need to loop over all global hash lists for (int hash = 0; hash < 256 && !got_int; hash++) { + mapblock_T *mp; if (is_abbrev) { if (hash != 0) { // there is only one abbreviation list break; @@ -714,6 +692,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, if (map_table != buf->b_maphash && !has_rhs && maptype != MAPTYPE_UNMAP) { // need to loop over all global hash lists for (int hash = 0; hash < 256 && !got_int; hash++) { + mapblock_T *mp; if (is_abbrev) { if (hash != 0) { // there is only one abbreviation list break; @@ -729,7 +708,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, showmap(mp, true); did_local = true; } else { - n = mp->m_keylen; + int n = mp->m_keylen; if (strncmp(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) { showmap(mp, true); did_local = true; @@ -759,8 +738,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, hash_end = 256; } for (int hash = hash_start; hash < hash_end && !got_int; hash++) { - mpp = is_abbrev ? abbr_table : &(map_table[hash]); - for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) { + mapblock_T **mpp = is_abbrev ? abbr_table : &(map_table[hash]); + for (mapblock_T *mp = *mpp; mp != NULL && !got_int; mp = *mpp) { if ((mp->m_mode & mode) == 0) { // skip entries with wrong mode mpp = &(mp->m_next); @@ -772,6 +751,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, did_it = true; } } else { // do we have a match? + int n; + const char *p; if (round) { // second round: Try unmap "rhs" string n = (int)strlen(mp->m_str); p = mp->m_str; @@ -994,12 +975,10 @@ free_and_return: /// Get the mapping mode from the command name. static int get_map_mode(char **cmdp, bool forceit) { - char *p; - int modec; int mode; - p = *cmdp; - modec = (uint8_t)(*p++); + char *p = *cmdp; + int modec = (uint8_t)(*p++); if (modec == 'i') { mode = MODE_INSERT; // :imap } else if (modec == 'l') { @@ -1036,16 +1015,13 @@ static int get_map_mode(char **cmdp, bool forceit) /// This function used to be called map_clear(). static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr) { - int mode; - int local; - - local = (strcmp(arg, "<buffer>") == 0); + bool local = strcmp(arg, "<buffer>") == 0; if (!local && *arg != NUL) { emsg(_(e_invarg)); return; } - mode = get_map_mode(&cmdp, forceit); + int mode = get_map_mode(&cmdp, forceit); map_clear_mode(curbuf, mode, local, abbr); } @@ -1057,11 +1033,8 @@ static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr) /// @param abbr true for abbreviations void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr) { - mapblock_T *mp, **mpp; - int hash; - int new_hash; - - for (hash = 0; hash < 256; hash++) { + for (int hash = 0; hash < 256; hash++) { + mapblock_T **mpp; if (abbr) { if (hash > 0) { // there is only one abbrlist break; @@ -1079,7 +1052,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr) } } while (*mpp != NULL) { - mp = *mpp; + mapblock_T *mp = *mpp; if (mp->m_mode & mode) { mp->m_mode &= ~mode; if (mp->m_mode == 0) { // entry can be deleted @@ -1087,7 +1060,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr) continue; } // May need to put this entry into another hash list. - new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); + int new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); if (!abbr && new_hash != hash) { *mpp = mp->m_next; if (local) { @@ -1119,7 +1092,6 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { int mode = 0; - int retval; char *buf = NULL; const char *const rhs = replace_termcodes(str, strlen(str), &buf, 0, @@ -1141,7 +1113,7 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo MAPMODE(mode, modechars, 'c', MODE_CMDLINE); #undef MAPMODE - retval = map_to_exists_mode(rhs, mode, abbr); + int retval = map_to_exists_mode(rhs, mode, abbr); xfree(buf); return retval; @@ -1159,13 +1131,12 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo /// @return true if there is at least one mapping with given parameters. int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) { - mapblock_T *mp; - int hash; bool exp_buffer = false; // Do it twice: once for global maps and once for local maps. while (true) { - for (hash = 0; hash < 256; hash++) { + for (int hash = 0; hash < 256; hash++) { + mapblock_T *mp; if (abbr) { if (hash > 0) { // There is only one abbr list. break; @@ -1486,10 +1457,7 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat // Return true if there is an abbreviation, false if not. bool check_abbr(int c, char *ptr, int col, int mincol) { - int scol; // starting column of the abbr. uint8_t tb[MB_MAXBYTES + 4]; - mapblock_T *mp; - mapblock_T *mp2; int clen = 0; // length in characters if (typebuf.tb_no_abbr_cnt) { // abbrev. are not recursive @@ -1509,6 +1477,8 @@ bool check_abbr(int c, char *ptr, int col, int mincol) return false; } + int scol; // starting column of the abbr. + { bool is_id = true; bool vim_abbr; @@ -1539,8 +1509,8 @@ bool check_abbr(int c, char *ptr, int col, int mincol) if (scol < col) { // there is a word in front of the cursor ptr += scol; int len = col - scol; - mp = curbuf->b_first_abbr; - mp2 = first_abbr; + mapblock_T *mp = curbuf->b_first_abbr; + mapblock_T *mp2 = first_abbr; if (mp == NULL) { mp = mp2; mp2 = NULL; @@ -1715,18 +1685,13 @@ char *eval_map_expr(mapblock_T *mp, int c) /// @param buf buffer for local mappings or NULL int makemap(FILE *fd, buf_T *buf) { - mapblock_T *mp; - char c1, c2, c3; - char *p; - char *cmd; - int abbr; - int hash; bool did_cpo = false; // Do the loop twice: Once for mappings, once for abbreviations. // Then loop over all map hash lists. - for (abbr = 0; abbr < 2; abbr++) { - for (hash = 0; hash < 256; hash++) { + for (int abbr = 0; abbr < 2; abbr++) { + for (int hash = 0; hash < 256; hash++) { + mapblock_T *mp; if (abbr) { if (hash > 0) { // there is only one abbr list break; @@ -1755,6 +1720,7 @@ int makemap(FILE *fd, buf_T *buf) if (mp->m_luaref != LUA_NOREF) { continue; } + char *p; for (p = mp->m_str; *p != NUL; p++) { if ((uint8_t)p[0] == K_SPECIAL && (uint8_t)p[1] == KS_EXTRA && p[2] == KE_SNR) { @@ -1768,14 +1734,10 @@ int makemap(FILE *fd, buf_T *buf) // It's possible to create a mapping and then ":unmap" certain // modes. We recreate this here by mapping the individual // modes, which requires up to three of them. - c1 = NUL; - c2 = NUL; - c3 = NUL; - if (abbr) { - cmd = "abbr"; - } else { - cmd = "map"; - } + char c1 = NUL; + char c2 = NUL; + char c3 = NUL; + char *cmd = abbr ? "abbr" : "map"; switch (mp->m_mode) { case MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING: break; @@ -1929,7 +1891,6 @@ int makemap(FILE *fd, buf_T *buf) int put_escstr(FILE *fd, char *strstart, int what) { uint8_t *str = (uint8_t *)strstart; - int c; // :map xx <Nop> if (*str == NUL && what == 1) { @@ -1953,7 +1914,7 @@ int put_escstr(FILE *fd, char *strstart, int what) continue; } - c = *str; + int c = *str; // Special key codes have to be translated to be able to make sense // when they are read back. if (c == K_SPECIAL && what != 2) { @@ -2030,14 +1991,13 @@ int put_escstr(FILE *fd, char *strstart, int what) char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, int *local_ptr, int *rhs_lua) { - int len, minlen; - mapblock_T *mp; *rhs_lua = LUA_NOREF; - len = (int)strlen(keys); + int len = (int)strlen(keys); for (int local = 1; local >= 0; local--) { // loop over all hash lists for (int hash = 0; hash < 256; hash++) { + mapblock_T *mp; if (abbr) { if (hash > 0) { // there is only one list. break; @@ -2062,7 +2022,7 @@ char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock s += 3; keylen -= 3; } - minlen = keylen < len ? keylen : len; + int minlen = keylen < len ? keylen : len; if (strncmp(s, keys, (size_t)minlen) == 0) { if (mp_ptr != NULL) { *mp_ptr = mp; @@ -2097,11 +2057,7 @@ void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } - if (map_to_exists(name, mode, abbr)) { - rettv->vval.v_number = true; - } else { - rettv->vval.v_number = false; - } + rettv->vval.v_number = map_to_exists(name, mode, abbr); } /// Fill a Dictionary with all applicable maparg() like dictionaries @@ -2439,14 +2395,11 @@ void langmap_init(void) /// changed at any time! const char *did_set_langmap(optset_T *args) { - char *p; - char *p2; - int from, to; - - ga_clear(&langmap_mapga); // clear the previous map first - langmap_init(); // back to one-to-one map + ga_clear(&langmap_mapga); // clear the previous map first + langmap_init(); // back to one-to-one map - for (p = p_langmap; p[0] != NUL;) { + for (char *p = p_langmap; p[0] != NUL;) { + char *p2; for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';'; MB_PTR_ADV(p2)) { if (p2[0] == '\\' && p2[1] != NUL) { @@ -2466,8 +2419,8 @@ const char *did_set_langmap(optset_T *args) if (p[0] == '\\' && p[1] != NUL) { p++; } - from = utf_ptr2char(p); - to = NUL; + int from = utf_ptr2char(p); + int to = NUL; if (p2 == NULL) { MB_PTR_ADV(p); if (p[0] != ',') { @@ -2524,9 +2477,8 @@ const char *did_set_langmap(optset_T *args) static void do_exmap(exarg_T *eap, int isabbrev) { - int mode; char *cmdp = eap->cmd; - mode = get_map_mode(&cmdp, eap->forceit || isabbrev); + int mode = get_map_mode(&cmdp, eap->forceit || isabbrev); switch (do_map((*cmdp == 'n') ? MAPTYPE_NOREMAP : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP, diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index d8b8dbba29..627efa9e96 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -1178,6 +1178,9 @@ void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) } if (internal) { + if (key.pos.row == newpos.row && key.pos.col == newpos.col) { + return; + } key.pos = newpos; bool match; // tricky: could minimize movement in either direction better @@ -1185,14 +1188,14 @@ void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) if (!match) { new_i++; } - if (new_i == itr->i || key_cmp(key, x->key[new_i]) == 0) { + if (new_i == itr->i) { x->key[itr->i].pos = newpos; } else if (new_i < itr->i) { memmove(&x->key[new_i + 1], &x->key[new_i], sizeof(MTKey) * (size_t)(itr->i - new_i)); x->key[new_i] = key; } else if (new_i > itr->i) { - memmove(&x->key[itr->i], &x->key[itr->i + 1], sizeof(MTKey) * (size_t)(new_i - itr->i)); - x->key[new_i] = key; + memmove(&x->key[itr->i], &x->key[itr->i + 1], sizeof(MTKey) * (size_t)(new_i - itr->i - 1)); + x->key[new_i - 1] = key; } return; } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b8c80cadf5..e35385dd43 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1849,7 +1849,6 @@ 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); @@ -1864,7 +1863,6 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp) 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; @@ -1901,22 +1899,12 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp) // 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 = ' '; } } - 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..d730f247a9 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4513,7 +4513,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 df90fadc97..c34999ed32 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1970,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/options.lua b/src/nvim/options.lua index f0666101b8..3221e5b6e9 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -566,7 +566,7 @@ return { backups if you don't care about losing the file. Note that environment variables are not expanded. If you want to use - $HOME you must expand it explicitly, e.g.: > + $HOME you must expand it explicitly, e.g.: >vim :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*' < Note that the default also makes sure that "crontab -e" works (when a 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/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/spell.c b/src/nvim/spell.c index 72e21a9130..38e045a08b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1280,7 +1280,7 @@ static TriState decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_ln decor_redraw_line(wp, lnum - 1, &decor_state); *decor_lnum = lnum; } - decor_redraw_col(wp, col, col, false, &decor_state); + decor_redraw_col(wp, col, 0, false, &decor_state); return decor_state.spell; } diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 30c98a2f1e..4e000b8d29 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -1663,7 +1663,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..9fea6442a9 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" @@ -675,15 +676,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 +704,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 +832,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 +850,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 +979,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 +1278,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 +1286,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 +1404,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 +1454,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 +1477,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_compositor.c b/src/nvim/ui_compositor.c index e9b23d1298..fcb63801e5 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,19 +395,18 @@ 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; } @@ -568,7 +566,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/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, |