diff options
author | Josh Rahm <rahm@google.com> | 2022-08-19 12:26:08 -0600 |
---|---|---|
committer | Josh Rahm <rahm@google.com> | 2022-08-19 13:06:41 -0600 |
commit | a7237662f96933efe29eed8212464571e3778cd0 (patch) | |
tree | 27930202726b4251437c8cfa53069f65b4db90dc /src/nvim | |
parent | 02292344929069ea63c0bb872cc22d552d86b67f (diff) | |
parent | b2f979b30beac67906b2dd717fcb6a34f46f5e54 (diff) | |
download | rneovim-tmp.tar.gz rneovim-tmp.tar.bz2 rneovim-tmp.zip |
Merge branch 'master' of https://github.com/neovim/neovim into rahmtmp
Diffstat (limited to 'src/nvim')
189 files changed, 20384 insertions, 19119 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index b743e9923f..635833748d 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -676,12 +676,17 @@ function(get_test_target prefix sfile relative_path_var target_var) endif() endfunction() -set(NO_SINGLE_CHECK_HEADERS - os/win_defs.h - os/pty_process_win.h - os/pty_conpty_win.h - os/os_win_console.h -) +if(WIN32) + set(NO_SINGLE_CHECK_HEADERS + os/pty_process_unix.h + os/unix_defs.h) +else() + set(NO_SINGLE_CHECK_HEADERS + os/win_defs.h + os/pty_process_win.h + os/pty_conpty_win.h + os/os_win_console.h) +endif() foreach(hfile ${NVIM_HEADERS}) get_test_target(test-includes "${hfile}" relative_path texe) diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index bf6402f938..79ae7994f7 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -9,9 +9,9 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/eval/typval.h" -#include "nvim/fileio.h" #include "nvim/lua/executor.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index d3895d31cf..5e90e40dd3 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -18,6 +18,7 @@ #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/decoration.h" +#include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/extmark.h" diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index bc766ff39c..1323fc347b 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -11,6 +11,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/autocmd.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/lua/executor.h" #include "nvim/ops.h" #include "nvim/regexp.h" @@ -300,10 +301,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error FUNC_API_SINCE(10) { exarg_T ea; - memset(&ea, 0, sizeof(ea)); + CLEAR_FIELD(ea); CmdParseInfo cmdinfo; - memset(&cmdinfo, 0, sizeof(cmdinfo)); + CLEAR_FIELD(cmdinfo); char *cmdline = NULL; char *cmdname = NULL; @@ -625,6 +626,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error garray_T capture_local; const int save_msg_silent = msg_silent; garray_T * const save_capture_ga = capture_ga; + const int save_msg_col = msg_col; if (output) { ga_init(&capture_local, 1, 80); @@ -635,6 +637,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error try_start(); if (output) { msg_silent++; + msg_col = 0; // prevent leading spaces } WITH_SCRIPT_CONTEXT(channel_id, { @@ -644,6 +647,8 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; + // Put msg_col back where it was, since nothing should have been written. + msg_col = save_msg_col; } try_end(err); @@ -819,9 +824,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin char *p = replace_makeprg(eap, eap->arg, cmdlinep); if (p != eap->arg) { // If replace_makeprg modified the cmdline string, correct the argument pointers. - assert(argc == 1); eap->arg = p; - eap->args[0] = p; + // We can only know the position of the first argument because the argument list can be used + // multiple times in makeprg / grepprg. + if (argc >= 1) { + eap->args[0] = p; + } } } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index da1b6beeda..933aa85530 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -10,11 +10,11 @@ #include "nvim/api/private/helpers.h" #include "nvim/charset.h" #include "nvim/decoration_provider.h" +#include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" -#include "nvim/screen.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -441,8 +441,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// the extmark end position (if it exists) will be shifted /// in when new text is inserted (true for right, false /// for left). Defaults to false. -/// - priority: a priority value for the highlight group. For -/// example treesitter highlighting uses a value of 100. +/// - priority: a priority value for the highlight group or sign +/// attribute. For example treesitter highlighting uses a +/// value of 100. /// - strict: boolean that indicates extmark should not be placed /// if the line or column value is past the end of the /// buffer or end of the line respectively. Defaults to true. @@ -1030,6 +1031,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro } p->active = true; + p->hl_valid++; + p->hl_cached = false; return; error: decor_provider_clear(p); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index a764fb069b..32104ef6dc 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -106,7 +106,6 @@ return { "reverse"; "nocombine"; "default"; - "global"; "cterm"; "foreground"; "fg"; "background"; "bg"; @@ -114,9 +113,9 @@ return { "ctermbg"; "special"; "sp"; "link"; + "global_link"; "fallback"; "blend"; - "temp"; }; highlight_cterm = { "bold"; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fad75d55be..c466fc53e1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -19,8 +19,8 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_eval.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" @@ -54,7 +54,7 @@ void try_enter(TryState *const tstate) // save_dbg_stuff()/restore_dbg_stuff(). *tstate = (TryState) { .current_exception = current_exception, - .msg_list = (const struct msglist *const *)msg_list, + .msg_list = (const msglist_T *const *)msg_list, .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, @@ -89,7 +89,7 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); assert(current_exception == NULL); - msg_list = (struct msglist **)tstate->msg_list; + msg_list = (msglist_T **)tstate->msg_list; current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 1441da853c..4608554448 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -3,7 +3,7 @@ #include "nvim/api/private/defs.h" #include "nvim/decoration.h" -#include "nvim/ex_eval.h" +#include "nvim/ex_eval_defs.h" #include "nvim/getchar.h" #include "nvim/lib/kvec.h" #include "nvim/memory.h" @@ -130,8 +130,8 @@ EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT); /// processed and that “other VimL code” must not be affected. typedef struct { except_T *current_exception; - struct msglist *private_msg_list; - const struct msglist *const *msg_list; + msglist_T *private_msg_list; + const msglist_T *const *msg_list; int trylevel; int got_int; int need_rethrow; @@ -144,8 +144,8 @@ typedef struct { // TODO(bfredl): prepare error-handling at "top level" (nv_event). #define TRY_WRAP(code) \ do { \ - struct msglist **saved_msg_list = msg_list; \ - struct msglist *private_msg_list; \ + msglist_T **saved_msg_list = msg_list; \ + msglist_T *private_msg_list; \ msg_list = &private_msg_list; \ private_msg_list = NULL; \ code \ diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 6239e414a7..6f7bfa244a 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -11,14 +11,14 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/cursor_shape.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -224,7 +224,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->event = remote_ui_event; ui->inspect = remote_ui_inspect; - memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + CLEAR_FIELD(ui->ui_ext); for (size_t i = 0; i < options.size; i++) { ui_set_option(ui, true, options.items[i].key, options.items[i].value, err); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e2f58dba62..e4dc219e9a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -23,17 +23,19 @@ #include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/globals.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" @@ -52,8 +54,8 @@ #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/process.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" +#include "nvim/runtime.h" #include "nvim/state.h" #include "nvim/types.h" #include "nvim/ui.h" @@ -91,7 +93,6 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) } /// Gets a highlight definition by id. |hlID()| -/// /// @param hl_id Highlight id as returned by |hlID()| /// @param rgb Export RGB colors /// @param[out] err Error details, if any @@ -180,35 +181,38 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) } } -/// Set active namespace for highlights. -/// -/// NB: this function can be called from async contexts, but the -/// semantics are not yet well-defined. To start with -/// |nvim_set_decoration_provider| on_win and on_line callbacks -/// are explicitly allowed to change the namespace during a redraw cycle. +/// Set active namespace for highlights. This can be set for a single window, +/// see |nvim_win_set_hl_ns|. /// -/// @param ns_id the namespace to activate +/// @param ns_id the namespace to use /// @param[out] err Error details, if any -void nvim__set_hl_ns(Integer ns_id, Error *err) - FUNC_API_FAST +void nvim_set_hl_ns(Integer ns_id, Error *err) + FUNC_API_SINCE(10) { - if (ns_id >= 0) { - ns_hl_active = (NS)ns_id; + if (ns_id < 0) { + api_set_error(err, kErrorTypeValidation, "no such namespace"); + return; } - // TODO(bfredl): this is a little bit hackish. Eventually we want a standard - // event path for redraws caused by "fast" events. This could tie in with - // better throttling of async events causing redraws, such as non-batched - // nvim_buf_set_extmark calls from async contexts. - if (!provider_active && !ns_hl_changed && must_redraw < NOT_VALID) { - multiqueue_put(main_loop.events, on_redraw_event, 0); - } - ns_hl_changed = true; + ns_hl_global = (NS)ns_id; + hl_check_ns(); + redraw_all_later(NOT_VALID); } -static void on_redraw_event(void **argv) +/// Set active namespace for highlights while redrawing. +/// +/// This function meant to be called while redrawing, primarily from +/// |nvim_set_decoration_provider| on_win and on_line callbacks, which +/// are allowed to change the namespace during a redraw cycle. +/// +/// @param ns_id the namespace to activate +/// @param[out] err Error details, if any +void nvim_set_hl_ns_fast(Integer ns_id, Error *err) + FUNC_API_SINCE(10) + FUNC_API_FAST { - redraw_all_later(NOT_VALID); + ns_hl_fast = (NS)ns_id; + hl_check_ns(); } /// Sends input-keys to Nvim, subject to various quirks controlled by `mode` @@ -478,7 +482,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) ADD_C(args, INTEGER_OBJ(log_level)); ADD_C(args, DICTIONARY_OBJ(opts)); - return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err); + return NLUA_EXEC_STATIC("return vim.notify(...)", args, err); } /// Calculates the number of display cells occupied by `text`. @@ -1833,11 +1837,9 @@ Array nvim_get_proc_children(Integer pid, Error *err) if (rv == 2) { // syscall failed (possibly because of kernel options), try shelling out. DLOG("fallback to vim._os_proc_children()"); - Array a = ARRAY_DICT_INIT; + MAXSIZE_TEMP_ARRAY(a, 1); ADD(a, INTEGER_OBJ(pid)); - String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)"); - Object o = nlua_exec(s, a, err); - api_free_array(a); + Object o = NLUA_EXEC_STATIC("return vim._os_proc_children(...)", a, err); if (o.type == kObjectTypeArray) { rvobj = o.data.array; } else if (!ERROR_SET(err)) { @@ -1878,12 +1880,9 @@ Object nvim_get_proc(Integer pid, Error *err) } #else // Cross-platform process info APIs are miserable, so use `ps` instead. - Array a = ARRAY_DICT_INIT; + MAXSIZE_TEMP_ARRAY(a, 1); ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); - Object o = nlua_exec(s, a, err); - api_free_string(s); - api_free_array(a); + Object o = NLUA_EXEC_STATIC("return vim._os_proc_info(...)", a, err); if (o.type == kObjectTypeArray && o.data.array.size == 0) { return NIL; // Process not found. } else if (o.type == kObjectTypeDictionary) { diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 478e146781..a28bfd2ab9 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -14,8 +14,9 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" #include "nvim/ops.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" @@ -48,6 +49,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) { const int save_msg_silent = msg_silent; garray_T *const save_capture_ga = capture_ga; + const int save_msg_col = msg_col; garray_T capture_local; if (output) { ga_init(&capture_local, 1, 80); @@ -57,6 +59,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) try_start(); if (output) { msg_silent++; + msg_col = 0; // prevent leading spaces } const sctx_T save_current_sctx = api_set_sctx(channel_id); @@ -65,6 +68,8 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; + // Put msg_col back where it was, since nothing should have been written. + msg_col = save_msg_col; } current_sctx = save_current_sctx; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index d36c5bfb95..0c89726d71 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -10,9 +10,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/drawscreen.h" #include "nvim/highlight_group.h" #include "nvim/option.h" -#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -167,7 +167,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(wp); - didset_window_options(wp); + didset_window_options(wp, true); } return wp->handle; } @@ -209,7 +209,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) } if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(win); - didset_window_options(win); + didset_window_options(win, true); } } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 5a4ff70257..580dfd8639 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -12,12 +12,12 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/ex_docmd.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -426,3 +426,28 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) try_end(err); return res; } + +/// Set highlight namespace for a window. This will use highlights defined in +/// this namespace, but fall back to global highlights (ns=0) when missing. +/// +/// This takes predecence over the 'winhighlight' option. +/// +/// @param ns_id the namespace to use +/// @param[out] err Error details, if any +void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) + FUNC_API_SINCE(10) +{ + win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + + // -1 is allowed as inherit global namespace + if (ns_id < -1) { + api_set_error(err, kErrorTypeValidation, "no such namespace"); + } + + win->w_ns_hl = (NS)ns_id; + win->w_hl_needs_update = true; + redraw_later(win, NOT_VALID); +} diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c new file mode 100644 index 0000000000..7d8917cc73 --- /dev/null +++ b/src/nvim/arglist.c @@ -0,0 +1,1157 @@ +// 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 + +// arglist.c: functions for dealing with the argument list + +#include <assert.h> +#include <stdbool.h> + +#include "nvim/arglist.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_getln.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/mark.h" +#include "nvim/memory.h" +#include "nvim/os/input.h" +#include "nvim/path.h" +#include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "arglist.c.generated.h" +#endif + +enum { + AL_SET = 1, + AL_ADD = 2, + AL_DEL = 3, +}; + +/// Clear an argument list: free all file names and reset it to zero entries. +void alist_clear(alist_T *al) +{ +#define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname) + GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); +} + +/// Init an argument list. +void alist_init(alist_T *al) +{ + ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); +} + +/// Remove a reference from an argument list. +/// Ignored when the argument list is the global one. +/// If the argument list is no longer used by any window, free it. +void alist_unlink(alist_T *al) +{ + if (al != &global_alist && --al->al_refcount <= 0) { + alist_clear(al); + xfree(al); + } +} + +/// Create a new argument list and use it for the current window. +void alist_new(void) +{ + curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); + curwin->w_alist->al_refcount = 1; + curwin->w_alist->id = ++max_alist_id; + alist_init(curwin->w_alist); +} + +#if !defined(UNIX) + +/// Expand the file names in the global argument list. +/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer +/// numbers to be re-used. +void alist_expand(int *fnum_list, int fnum_len) +{ + char *save_p_su = p_su; + + // Don't use 'suffixes' here. This should work like the shell did the + // expansion. Also, the vimrc file isn't read yet, thus the user + // can't set the options. + p_su = empty_option; + char **old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); + for (int i = 0; i < GARGCOUNT; i++) { + old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); + } + int old_arg_count = GARGCOUNT; + char **new_arg_files; + int new_arg_file_count; + if (expand_wildcards(old_arg_count, old_arg_files, + &new_arg_file_count, &new_arg_files, + EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK + && new_arg_file_count > 0) { + alist_set(&global_alist, new_arg_file_count, new_arg_files, + true, fnum_list, fnum_len); + FreeWild(old_arg_count, old_arg_files); + } + p_su = save_p_su; +} +#endif + +/// Set the argument list for the current window. +/// Takes over the allocated files[] and the allocated fnames in it. +void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len) +{ + static int recursive = 0; + + if (recursive) { + emsg(_(e_au_recursive)); + return; + } + recursive++; + + alist_clear(al); + ga_grow(&al->al_ga, count); + { + for (int i = 0; i < count; i++) { + if (got_int) { + // When adding many buffers this can take a long time. Allow + // interrupting here. + while (i < count) { + xfree(files[i++]); + } + break; + } + + // May set buffer name of a buffer previously used for the + // argument list, so that it's re-used by alist_add. + if (fnum_list != NULL && i < fnum_len) { + buf_set_name(fnum_list[i], files[i]); + } + + alist_add(al, files[i], use_curbuf ? 2 : 1); + os_breakcheck(); + } + xfree(files); + } + + if (al == &global_alist) { + arg_had_last = false; + } + recursive--; +} + +/// Add file "fname" to argument list "al". +/// "fname" must have been allocated and "al" must have been checked for room. +/// +/// @param set_fnum 1: set buffer number; 2: re-use curbuf +void alist_add(alist_T *al, char *fname, int set_fnum) +{ + if (fname == NULL) { // don't add NULL file names + return; + } +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(fname); +#endif + AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname; + if (set_fnum > 0) { + AARGLIST(al)[al->al_ga.ga_len].ae_fnum = + buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); + } + al->al_ga.ga_len++; +} + +#if defined(BACKSLASH_IN_FILENAME) + +/// Adjust slashes in file names. Called after 'shellslash' was set. +void alist_slash_adjust(void) +{ + for (int i = 0; i < GARGCOUNT; i++) { + if (GARGLIST[i].ae_fname != NULL) { + slash_adjust(GARGLIST[i].ae_fname); + } + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_alist != &global_alist) { + for (int i = 0; i < WARGCOUNT(wp); i++) { + if (WARGLIST(wp)[i].ae_fname != NULL) { + slash_adjust(WARGLIST(wp)[i].ae_fname); + } + } + } + } +} + +#endif + +/// Isolate one argument, taking backticks. +/// Changes the argument in-place, puts a NUL after it. Backticks remain. +/// +/// @return a pointer to the start of the next argument. +static char *do_one_arg(char *str) +{ + char *p; + bool inbacktick; + + inbacktick = false; + for (p = str; *str; str++) { + // When the backslash is used for escaping the special meaning of a + // character we need to keep it until wildcard expansion. + if (rem_backslash((char_u *)str)) { + *p++ = *str++; + *p++ = *str; + } else { + // An item ends at a space not in backticks + if (!inbacktick && ascii_isspace(*str)) { + break; + } + if (*str == '`') { + inbacktick ^= true; + } + *p++ = *str; + } + } + str = skipwhite(str); + *p = NUL; + + return str; +} + +/// Separate the arguments in "str" and return a list of pointers in the +/// growarray "gap". +static void get_arglist(garray_T *gap, char *str, int escaped) +{ + ga_init(gap, (int)sizeof(char_u *), 20); + while (*str != NUL) { + GA_APPEND(char *, gap, str); + + // If str is escaped, don't handle backslashes or spaces + if (!escaped) { + return; + } + + // Isolate one argument, change it in-place, put a NUL after it. + str = do_one_arg(str); + } +} + +/// Parse a list of arguments (file names), expand them and return in +/// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. +/// +/// @return FAIL or OK. +int get_arglist_exp(char_u *str, int *fcountp, char ***fnamesp, bool wig) +{ + garray_T ga; + int i; + + get_arglist(&ga, (char *)str, true); + + if (wig) { + i = expand_wildcards(ga.ga_len, ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); + } else { + i = gen_expand_wildcards(ga.ga_len, ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); + } + + ga_clear(&ga); + return i; +} + +/// Check the validity of the arg_idx for each other window. +static void alist_check_arg_idx(void) +{ + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_alist == curwin->w_alist) { + check_arg_idx(win); + } + } +} + +/// Add files[count] to the arglist of the current window after arg "after". +/// The file names in files[count] must have been allocated and are taken over. +/// Files[] itself is not taken over. +/// +/// @param after: where to add: 0 = before first one +/// @param will_edit will edit adding argument +static void alist_add_list(int count, char **files, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL +{ + int old_argcount = ARGCOUNT; + ga_grow(&ALIST(curwin)->al_ga, count); + { + if (after < 0) { + after = 0; + } + if (after > ARGCOUNT) { + after = ARGCOUNT; + } + if (after < ARGCOUNT) { + memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), + (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); + } + for (int i = 0; i < count; i++) { + const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); + ARGLIST[after + i].ae_fname = (char_u *)files[i]; + ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); + } + ALIST(curwin)->al_ga.ga_len += count; + if (old_argcount > 0 && curwin->w_arg_idx >= after) { + curwin->w_arg_idx += count; + } + return; + } +} + +/// @param str +/// @param what +/// AL_SET: Redefine the argument list to 'str'. +/// AL_ADD: add files in 'str' to the argument list after "after". +/// AL_DEL: remove files in 'str' from the argument list. +/// @param after +/// 0 means before first one +/// @param will_edit will edit added argument +/// +/// @return FAIL for failure, OK otherwise. +static int do_arglist(char *str, int what, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL +{ + garray_T new_ga; + int exp_count; + char **exp_files; + char *p; + int match; + int arg_escaped = true; + + // Set default argument for ":argadd" command. + if (what == AL_ADD && *str == NUL) { + if (curbuf->b_ffname == NULL) { + return FAIL; + } + str = curbuf->b_fname; + arg_escaped = false; + } + + // Collect all file name arguments in "new_ga". + get_arglist(&new_ga, str, arg_escaped); + + if (what == AL_DEL) { + regmatch_T regmatch; + bool didone; + + // Delete the items: use each item as a regexp and find a match in the + // argument list. + regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set + for (int i = 0; i < new_ga.ga_len && !got_int; i++) { + p = ((char **)new_ga.ga_data)[i]; + p = file_pat_to_reg_pat(p, NULL, NULL, false); + if (p == NULL) { + break; + } + regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + xfree(p); + break; + } + + didone = false; + for (match = 0; match < ARGCOUNT; match++) { + if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { + didone = true; + xfree(ARGLIST[match].ae_fname); + memmove(ARGLIST + match, ARGLIST + match + 1, + (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); + ALIST(curwin)->al_ga.ga_len--; + if (curwin->w_arg_idx > match) { + curwin->w_arg_idx--; + } + match--; + } + } + + vim_regfree(regmatch.regprog); + xfree(p); + if (!didone) { + semsg(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]); + } + } + ga_clear(&new_ga); + } else { + int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data, + &exp_count, &exp_files, + EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); + ga_clear(&new_ga); + if (i == FAIL || exp_count == 0) { + emsg(_(e_nomatch)); + return FAIL; + } + + if (what == AL_ADD) { + alist_add_list(exp_count, exp_files, after, will_edit); + xfree(exp_files); + } else { + assert(what == AL_SET); + alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); + } + } + + alist_check_arg_idx(); + + return OK; +} + +/// Redefine the argument list. +void set_arglist(char *str) +{ + do_arglist(str, AL_SET, 0, false); +} + +/// @return true if window "win" is editing the file at the current argument +/// index. +bool editing_arg_idx(win_T *win) +{ + return !(win->w_arg_idx >= WARGCOUNT(win) + || (win->w_buffer->b_fnum + != WARGLIST(win)[win->w_arg_idx].ae_fnum + && (win->w_buffer->b_ffname == NULL + || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]), + win->w_buffer->b_ffname, true, + true) & kEqualFiles)))); +} + +/// Check if window "win" is editing the w_arg_idx file in its argument list. +void check_arg_idx(win_T *win) +{ + if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) { + // We are not editing the current entry in the argument list. + // Set "arg_had_last" if we are editing the last one. + win->w_arg_idx_invalid = true; + if (win->w_arg_idx != WARGCOUNT(win) - 1 + && arg_had_last == false + && ALIST(win) == &global_alist + && GARGCOUNT > 0 + && win->w_arg_idx < GARGCOUNT + && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum + || (win->w_buffer->b_ffname != NULL + && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), + win->w_buffer->b_ffname, true, true) + & kEqualFiles)))) { + arg_had_last = true; + } + } else { + // We are editing the current entry in the argument list. + // Set "arg_had_last" if it's also the last one + win->w_arg_idx_invalid = false; + if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { + arg_had_last = true; + } + } +} + +/// ":args", ":argslocal" and ":argsglobal". +void ex_args(exarg_T *eap) +{ + if (eap->cmdidx != CMD_args) { + alist_unlink(ALIST(curwin)); + if (eap->cmdidx == CMD_argglobal) { + ALIST(curwin) = &global_alist; + } else { // eap->cmdidx == CMD_arglocal + alist_new(); + } + } + + if (*eap->arg != NUL) { + // ":args file ..": define new argument list, handle like ":next" + // Also for ":argslocal file .." and ":argsglobal file ..". + ex_next(eap); + } else if (eap->cmdidx == CMD_args) { + // ":args": list arguments. + if (ARGCOUNT > 0) { + char **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT); + // Overwrite the command, for a short list there is no scrolling + // required and no wait_return(). + gotocmdline(true); + for (int i = 0; i < ARGCOUNT; i++) { + items[i] = alist_name(&ARGLIST[i]); + } + list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); + xfree(items); + } + } else if (eap->cmdidx == CMD_arglocal) { + garray_T *gap = &curwin->w_alist->al_ga; + + // ":argslocal": make a local copy of the global argument list. + ga_grow(gap, GARGCOUNT); + for (int i = 0; i < GARGCOUNT; i++) { + if (GARGLIST[i].ae_fname != NULL) { + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = + vim_strsave(GARGLIST[i].ae_fname); + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = + GARGLIST[i].ae_fnum; + gap->ga_len++; + } + } + } +} + +/// ":previous", ":sprevious", ":Next" and ":sNext". +void ex_previous(exarg_T *eap) +{ + // If past the last one already, go to the last one. + if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) { + do_argfile(eap, ARGCOUNT - 1); + } else { + do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); + } +} + +/// ":rewind", ":first", ":sfirst" and ":srewind". +void ex_rewind(exarg_T *eap) +{ + do_argfile(eap, 0); +} + +/// ":last" and ":slast". +void ex_last(exarg_T *eap) +{ + do_argfile(eap, ARGCOUNT - 1); +} + +/// ":argument" and ":sargument". +void ex_argument(exarg_T *eap) +{ + int i; + + if (eap->addr_count > 0) { + i = (int)eap->line2 - 1; + } else { + i = curwin->w_arg_idx; + } + do_argfile(eap, i); +} + +/// Edit file "argn" of the argument lists. +void do_argfile(exarg_T *eap, int argn) +{ + int other; + char *p; + int old_arg_idx = curwin->w_arg_idx; + + if (argn < 0 || argn >= ARGCOUNT) { + if (ARGCOUNT <= 1) { + emsg(_("E163: There is only one file to edit")); + } else if (argn < 0) { + emsg(_("E164: Cannot go before first file")); + } else { + emsg(_("E165: Cannot go beyond last file")); + } + } else { + setpcmark(); + + // split window or create new tab page first + if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { + if (win_split(0, 0) == FAIL) { + return; + } + RESET_BINDING(curwin); + } else { + // if 'hidden' set, only check for changed file when re-editing + // the same buffer + other = true; + if (buf_hide(curbuf)) { + p = fix_fname(alist_name(&ARGLIST[argn])); + other = otherfile(p); + xfree(p); + } + if ((!buf_hide(curbuf) || !other) + && check_changed(curbuf, CCGD_AW + | (other ? 0 : CCGD_MULTWIN) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + return; + } + } + + curwin->w_arg_idx = argn; + if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { + arg_had_last = true; + } + + // Edit the file; always use the last known line number. + // When it fails (e.g. Abort for already edited file) restore the + // argument index. + if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, + eap, ECMD_LAST, + (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { + curwin->w_arg_idx = old_arg_idx; + } else if (eap->cmdidx != CMD_argdo) { + // like Vi: set the mark where the cursor is in the file. + setmark('\''); + } + } +} + +/// ":next", and commands that behave like it. +void ex_next(exarg_T *eap) +{ + int i; + + // check for changed buffer now, if this fails the argument list is not + // redefined. + if (buf_hide(curbuf) + || eap->cmdidx == CMD_snext + || !check_changed(curbuf, CCGD_AW + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + if (*eap->arg != NUL) { // redefine file list + if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { + return; + } + i = 0; + } else { + i = curwin->w_arg_idx + (int)eap->line2; + } + do_argfile(eap, i); + } +} + +/// ":argdedupe" +void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) +{ + for (int i = 0; i < ARGCOUNT; i++) { + for (int j = i + 1; j < ARGCOUNT; j++) { + if (FNAMECMP(ARGLIST[i].ae_fname, ARGLIST[j].ae_fname) == 0) { + xfree(ARGLIST[j].ae_fname); + memmove(ARGLIST + j, ARGLIST + j + 1, + (size_t)(ARGCOUNT - j - 1) * sizeof(aentry_T)); + ARGCOUNT--; + + if (curwin->w_arg_idx == j) { + curwin->w_arg_idx = i; + } else if (curwin->w_arg_idx > j) { + curwin->w_arg_idx--; + } + + j--; + } + } + } +} + +/// ":argedit" +void ex_argedit(exarg_T *eap) +{ + int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; + // Whether curbuf will be reused, curbuf->b_ffname will be set. + bool curbuf_is_reusable = curbuf_reusable(); + + if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { + return; + } + maketitle(); + + if (curwin->w_arg_idx == 0 + && (curbuf->b_ml.ml_flags & ML_EMPTY) + && (curbuf->b_ffname == NULL || curbuf_is_reusable)) { + i = 0; + } + // Edit the argument. + if (i < ARGCOUNT) { + do_argfile(eap, i); + } +} + +/// ":argadd" +void ex_argadd(exarg_T *eap) +{ + do_arglist(eap->arg, AL_ADD, + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, + false); + maketitle(); +} + +/// ":argdelete" +void ex_argdelete(exarg_T *eap) +{ + if (eap->addr_count > 0 || *eap->arg == NUL) { + // ":argdel" works like ":.argdel" + if (eap->addr_count == 0) { + if (curwin->w_arg_idx >= ARGCOUNT) { + emsg(_("E610: No argument to delete")); + return; + } + eap->line1 = eap->line2 = curwin->w_arg_idx + 1; + } else if (eap->line2 > ARGCOUNT) { + // ":1,4argdel": Delete all arguments in the range. + eap->line2 = ARGCOUNT; + } + linenr_T n = eap->line2 - eap->line1 + 1; + if (*eap->arg != NUL) { + // Can't have both a range and an argument. + emsg(_(e_invarg)); + } else if (n <= 0) { + // Don't give an error for ":%argdel" if the list is empty. + if (eap->line1 != 1 || eap->line2 != 0) { + emsg(_(e_invrange)); + } + } else { + for (linenr_T i = eap->line1; i <= eap->line2; i++) { + xfree(ARGLIST[i - 1].ae_fname); + } + memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, + (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T)); + ALIST(curwin)->al_ga.ga_len -= (int)n; + if (curwin->w_arg_idx >= eap->line2) { + curwin->w_arg_idx -= (int)n; + } else if (curwin->w_arg_idx > eap->line1) { + curwin->w_arg_idx = (int)eap->line1; + } + if (ARGCOUNT == 0) { + curwin->w_arg_idx = 0; + } else if (curwin->w_arg_idx >= ARGCOUNT) { + curwin->w_arg_idx = ARGCOUNT - 1; + } + } + } else { + do_arglist(eap->arg, AL_DEL, 0, false); + } + maketitle(); +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// argedit and argdelete commands. +char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= ARGCOUNT) { + return NULL; + } + return alist_name(&ARGLIST[idx]); +} + +/// Get the file name for an argument list entry. +char *alist_name(aentry_T *aep) +{ + buf_T *bp; + + // Use the name from the associated buffer if it exists. + bp = buflist_findnr(aep->ae_fnum); + if (bp == NULL || bp->b_fname == NULL) { + return (char *)aep->ae_fname; + } + return bp->b_fname; +} + +/// do_arg_all(): Open up to 'count' windows, one for each argument. +/// +/// @param forceit hide buffers in current windows +/// @param keep_tabs keep current tabs, for ":tab drop file" +static void do_arg_all(int count, int forceit, int keep_tabs) +{ + uint8_t *opened; // Array of weight for which args are open: + // 0: not opened + // 1: opened in other tab + // 2: opened in curtab + // 3: opened in curtab and curwin + + int opened_len; // length of opened[] + int use_firstwin = false; // use first window for arglist + bool tab_drop_empty_window = false; + int split_ret = OK; + bool p_ea_save; + alist_T *alist; // argument list to be used + buf_T *buf; + tabpage_T *tpnext; + int had_tab = cmdmod.cmod_tab; + win_T *old_curwin, *last_curwin; + tabpage_T *old_curtab, *last_curtab; + win_T *new_curwin = NULL; + tabpage_T *new_curtab = NULL; + + assert(firstwin != NULL); // satisfy coverity + + if (ARGCOUNT <= 0) { + // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. + return; + } + setpcmark(); + + opened_len = ARGCOUNT; + opened = xcalloc((size_t)opened_len, 1); + + // Autocommands may do anything to the argument list. Make sure it's not + // freed while we are working here by "locking" it. We still have to + // watch out for its size to be changed. + alist = curwin->w_alist; + alist->al_refcount++; + + old_curwin = curwin; + old_curtab = curtab; + + // Try closing all windows that are not in the argument list. + // Also close windows that are not full width; + // When 'hidden' or "forceit" set the buffer becomes hidden. + // Windows that have a changed buffer and can't be hidden won't be closed. + // When the ":tab" modifier was used do this for all tab pages. + if (had_tab > 0) { + goto_tabpage_tp(first_tabpage, true, true); + } + for (;;) { + win_T *wpnext = NULL; + tpnext = curtab->tp_next; + for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { + int i; + wpnext = wp->w_next; + buf = wp->w_buffer; + if (buf->b_ffname == NULL + || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { + i = opened_len; + } else { + // check if the buffer in this window is in the arglist + for (i = 0; i < opened_len; i++) { + if (i < alist->al_ga.ga_len + && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum + || path_full_compare(alist_name(&AARGLIST(alist)[i]), + buf->b_ffname, + true, true) & kEqualFiles)) { + int weight = 1; + + if (old_curtab == curtab) { + weight++; + if (old_curwin == wp) { + weight++; + } + } + + if (weight > (int)opened[i]) { + opened[i] = (uint8_t)weight; + if (i == 0) { + if (new_curwin != NULL) { + new_curwin->w_arg_idx = opened_len; + } + new_curwin = wp; + new_curtab = curtab; + } + } else if (keep_tabs) { + i = opened_len; + } + + if (wp->w_alist != alist) { + // Use the current argument list for all windows containing a file from it. + alist_unlink(wp->w_alist); + wp->w_alist = alist; + wp->w_alist->al_refcount++; + } + break; + } + } + } + wp->w_arg_idx = i; + + if (i == opened_len && !keep_tabs) { // close this window + if (buf_hide(buf) || forceit || buf->b_nwindows > 1 + || !bufIsChanged(buf)) { + // If the buffer was changed, and we would like to hide it, try autowriting. + if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { + bufref_T bufref; + set_bufref(&bufref, buf); + (void)autowrite(buf, false); + // Check if autocommands removed the window. + if (!win_valid(wp) || !bufref_valid(&bufref)) { + wpnext = firstwin; // Start all over... + continue; + } + } + // don't close last window + if (ONE_WINDOW + && (first_tabpage->tp_next == NULL || !had_tab)) { + use_firstwin = true; + } else { + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); + // check if autocommands removed the next window + if (!win_valid(wpnext)) { + // start all over... + wpnext = firstwin; + } + } + } + } + } + + // Without the ":tab" modifier only do the current tab page. + if (had_tab == 0 || tpnext == NULL) { + break; + } + + // check if autocommands removed the next tab page + if (!valid_tabpage(tpnext)) { + tpnext = first_tabpage; // start all over... + } + goto_tabpage_tp(tpnext, true, true); + } + + // Open a window for files in the argument list that don't have one. + // ARGCOUNT may change while doing this, because of autocommands. + if (count > opened_len || count <= 0) { + count = opened_len; + } + + // Don't execute Win/Buf Enter/Leave autocommands here. + autocmd_no_enter++; + autocmd_no_leave++; + last_curwin = curwin; + last_curtab = curtab; + win_enter(lastwin, false); + // ":tab drop file" should re-use an empty window to avoid "--remote-tab" + // leaving an empty tab page when executed locally. + if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) { + use_firstwin = true; + tab_drop_empty_window = true; + } + + for (int i = 0; i < count && !got_int; i++) { + if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { + arg_had_last = true; + } + if (opened[i] > 0) { + // Move the already present window to below the current window + if (curwin->w_arg_idx != i) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_arg_idx == i) { + if (keep_tabs) { + new_curwin = wp; + new_curtab = curtab; + } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { + emsg(_("E249: window layout changed unexpectedly")); + i = count; + break; + } else { + win_move_after(wp, curwin); + } + break; + } + } + } + } else if (split_ret == OK) { + // trigger events for tab drop + if (tab_drop_empty_window && i == count - 1) { + autocmd_no_enter--; + } + if (!use_firstwin) { // split current window + p_ea_save = p_ea; + p_ea = true; // use space from all windows + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + p_ea = p_ea_save; + if (split_ret == FAIL) { + continue; + } + } else { // first window: do autocmd for leaving this buffer + autocmd_no_leave--; + } + + // edit file "i" + curwin->w_arg_idx = i; + if (i == 0) { + new_curwin = curwin; + new_curtab = curtab; + } + (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, + ((buf_hide(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) + ? ECMD_HIDE : 0) + ECMD_OLDBUF, + curwin); + if (tab_drop_empty_window && i == count - 1) { + autocmd_no_enter++; + } + if (use_firstwin) { + autocmd_no_leave++; + } + use_firstwin = false; + } + os_breakcheck(); + + // When ":tab" was used open a new tab for a new window repeatedly. + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { + cmdmod.cmod_tab = 9999; + } + } + + // Remove the "lock" on the argument list. + alist_unlink(alist); + + autocmd_no_enter--; + // restore last referenced tabpage's curwin + if (last_curtab != new_curtab) { + if (valid_tabpage(last_curtab)) { + goto_tabpage_tp(last_curtab, true, true); + } + if (win_valid(last_curwin)) { + win_enter(last_curwin, false); + } + } + // to window with first arg + if (valid_tabpage(new_curtab)) { + goto_tabpage_tp(new_curtab, true, true); + } + if (win_valid(new_curwin)) { + win_enter(new_curwin, false); + } + + autocmd_no_leave--; + xfree(opened); +} + +/// ":all" and ":sall". +/// Also used for ":tab drop file ..." after setting the argument list. +void ex_all(exarg_T *eap) +{ + if (eap->addr_count == 0) { + eap->line2 = 9999; + } + do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); +} + +/// Concatenate all files in the argument list, separated by spaces, and return +/// it in one allocated string. +/// Spaces and backslashes in the file names are escaped with a backslash. +char *arg_all(void) +{ + char *retval = NULL; + + // Do this loop two times: + // first time: compute the total length + // second time: concatenate the names + for (;;) { + int len = 0; + for (int idx = 0; idx < ARGCOUNT; idx++) { + char *p = alist_name(&ARGLIST[idx]); + if (p == NULL) { + continue; + } + if (len > 0) { + // insert a space in between names + if (retval != NULL) { + retval[len] = ' '; + } + len++; + } + for (; *p != NUL; p++) { + if (*p == ' ' +#ifndef BACKSLASH_IN_FILENAME + || *p == '\\' +#endif + || *p == '`') { + // insert a backslash + if (retval != NULL) { + retval[len] = '\\'; + } + len++; + } + if (retval != NULL) { + retval[len] = *p; + } + len++; + } + } + + // second time: break here + if (retval != NULL) { + retval[len] = NUL; + break; + } + + // allocate memory + retval = xmalloc((size_t)len + 1); + } + + return retval; +} + +/// "argc([window id])" function +void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type == VAR_UNKNOWN) { + // use the current window + rettv->vval.v_number = ARGCOUNT; + } else if (argvars[0].v_type == VAR_NUMBER + && tv_get_number(&argvars[0]) == -1) { + // use the global argument list + rettv->vval.v_number = GARGCOUNT; + } else { + // use the argument list of the specified window + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + rettv->vval.v_number = WARGCOUNT(wp); + } else { + rettv->vval.v_number = -1; + } + } +} + +/// "argidx()" function +void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/// "arglistid()" function +void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + win_T *wp = find_tabwin(&argvars[0], &argvars[1]); + if (wp != NULL) { + rettv->vval.v_number = wp->w_alist->id; + } +} + +/// Get the argument list for a given window +static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) +{ + tv_list_alloc_ret(rettv, argcount); + if (arglist != NULL) { + for (int idx = 0; idx < argcount; idx++) { + tv_list_append_string(rettv->vval.v_list, + (const char *)alist_name(&arglist[idx]), -1); + } + } +} + +/// "argv(nr)" function +void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + aentry_T *arglist = NULL; + int argcount = -1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type == VAR_UNKNOWN) { + arglist = ARGLIST; + argcount = ARGCOUNT; + } else if (argvars[1].v_type == VAR_NUMBER + && tv_get_number(&argvars[1]) == -1) { + arglist = GARGLIST; + argcount = GARGCOUNT; + } else { + win_T *wp = find_win_by_nr_or_id(&argvars[1]); + if (wp != NULL) { + // Use the argument list of the specified window + arglist = WARGLIST(wp); + argcount = WARGCOUNT(wp); + } + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + int idx = (int)tv_get_number_chk(&argvars[0], NULL); + if (arglist != NULL && idx >= 0 && idx < argcount) { + rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); + } else if (idx == -1) { + get_arglist_as_rettv(arglist, argcount, rettv); + } + } else { + get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); + } +} diff --git a/src/nvim/arglist.h b/src/nvim/arglist.h new file mode 100644 index 0000000000..b2e0f411d4 --- /dev/null +++ b/src/nvim/arglist.h @@ -0,0 +1,11 @@ +#ifndef NVIM_ARGLIST_H +#define NVIM_ARGLIST_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "arglist.h.generated.h" +#endif + +#endif // NVIM_ARGLIST_H diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index bbb044fba3..b5b2a73be1 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -11,20 +11,25 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/grid.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/map.h" #include "nvim/option.h" #include "nvim/os/input.h" +#include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/ui_compositor.h" @@ -1141,7 +1146,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group ac->id = id; ac->exec = aucmd_exec_copy(aucmd); ac->script_ctx = current_sctx; - ac->script_ctx.sc_lnum += sourcing_lnum; + ac->script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&ac->script_ctx); ac->next = NULL; ac->once = once; @@ -1769,10 +1774,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force // Don't redraw while doing autocommands. RedrawingDisabled++; - char *save_sourcing_name = sourcing_name; - sourcing_name = NULL; // don't free this one - linenr_T save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 0; // no line number here + + // name and lnum are filled in later + estack_push(ETYPE_AUCMD, NULL, 0); const sctx_T save_current_sctx = current_sctx; @@ -1876,9 +1880,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force autocmd_busy = save_autocmd_busy; filechangeshell_busy = false; autocmd_nested = save_autocmd_nested; - xfree(sourcing_name); - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + xfree(SOURCING_NAME); + estack_pop(); xfree(autocmd_fname); autocmd_fname = save_autocmd_fname; autocmd_bufnr = save_autocmd_bufnr; @@ -1981,8 +1984,9 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) AutoPat *ap; AutoCmd *cp; char *s; + char **const sourcing_namep = &SOURCING_NAME; - XFREE_CLEAR(sourcing_name); + XFREE_CLEAR(*sourcing_namep); for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { apc->curpat = NULL; @@ -2007,11 +2011,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) const size_t sourcing_name_len = (STRLEN(s) + strlen(name) + (size_t)ap->patlen + 1); - sourcing_name = xmalloc(sourcing_name_len); - snprintf(sourcing_name, sourcing_name_len, s, name, ap->pat); + *sourcing_namep = xmalloc(sourcing_name_len); + snprintf(*sourcing_namep, sourcing_name_len, s, name, ap->pat); if (p_verbose >= 8) { verbose_enter(); - smsg(_("Executing %s"), sourcing_name); + smsg(_("Executing %s"), *sourcing_namep); verbose_leave(); } diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index a085a03455..d559d8c3d2 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -22,7 +22,8 @@ typedef struct { bool save_VIsual_active; ///< saved VIsual_active } aco_save_T; -typedef struct AutoCmd { +typedef struct AutoCmd_S AutoCmd; +struct AutoCmd_S { AucmdExecutable exec; bool once; // "One shot": removed after execution bool nested; // If autocommands nest here @@ -30,11 +31,12 @@ typedef struct AutoCmd { int64_t id; // ID used for uniquely tracking an autocmd. sctx_T script_ctx; // script context where defined char *desc; // Description for the autocmd. - struct AutoCmd *next; // Next AutoCmd in list -} AutoCmd; + AutoCmd *next; // Next AutoCmd in list +}; -typedef struct AutoPat { - struct AutoPat *next; // next AutoPat in AutoPat list; MUST +typedef struct AutoPat_S AutoPat; +struct AutoPat_S { + AutoPat *next; // next AutoPat in AutoPat list; MUST // be the first entry char *pat; // pattern as typed (NULL when pattern // has been removed) @@ -45,10 +47,11 @@ typedef struct AutoPat { int buflocal_nr; // !=0 for buffer-local AutoPat char allow_dirs; // Pattern may match whole path char last; // last pattern for apply_autocmds() -} AutoPat; +}; /// Struct used to keep status while executing autocommands for an event. -typedef struct AutoPatCmd { +typedef struct AutoPatCmd_S AutoPatCmd; +struct AutoPatCmd_S { AutoPat *curpat; // next AutoPat to examine AutoCmd *nextcmd; // next AutoCmd to execute int group; // group being used @@ -58,8 +61,8 @@ typedef struct AutoPatCmd { event_T event; // current event int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted Object *data; // arbitrary data - struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation -} AutoPatCmd; + AutoPatCmd *next; // chain of active apc-s for auto-invalidation +}; // Set by the apply_autocmds_group function if the given event is equal to // EVENT_FILETYPE. Used by the readfile function in order to determine if diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f23a1caf8b..f9bce2476f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -25,8 +25,10 @@ #include <string.h> #include "nvim/api/private/helpers.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" @@ -36,6 +38,7 @@ #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" @@ -50,6 +53,7 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/help.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" @@ -69,7 +73,7 @@ #include "nvim/plines.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/strings.h" @@ -793,8 +797,8 @@ static void free_buffer(buf_T *buf) if (autocmd_busy) { // Do not free the buffer structure while autocommands are executing, // it's still needed. Free it when autocmd_busy is reset. - memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm)); - memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist)); + CLEAR_FIELD(buf->b_namedm); + CLEAR_FIELD(buf->b_changelist); buf->b_next = au_pending_free_buf; au_pending_free_buf = buf; } else { @@ -1532,6 +1536,15 @@ void set_curbuf(buf_T *buf, int action) /// be pointing to freed memory. void enter_buffer(buf_T *buf) { + // when closing the current buffer stop Visual mode + if (VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) { + end_visual_mode(); + } + // Get the buffer in the current window. curwin->w_buffer = buf; curbuf = buf; @@ -2595,7 +2608,7 @@ void get_winopts(buf_T *buf) } if (curwin->w_float_config.style == kWinStyleMinimal) { - didset_window_options(curwin); + didset_window_options(curwin, false); win_set_minimal_style(curwin); } @@ -2603,7 +2616,7 @@ void get_winopts(buf_T *buf) if (p_fdls >= 0) { curwin->w_p_fdl = p_fdls; } - didset_window_options(curwin); + didset_window_options(curwin, false); } /// Find the mark for the buffer 'buf' for the current window. @@ -4628,279 +4641,6 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname) #endif } -/// Get the file name for an argument list entry. -char *alist_name(aentry_T *aep) -{ - buf_T *bp; - - // Use the name from the associated buffer if it exists. - bp = buflist_findnr(aep->ae_fnum); - if (bp == NULL || bp->b_fname == NULL) { - return (char *)aep->ae_fname; - } - return bp->b_fname; -} - -/// do_arg_all(): Open up to 'count' windows, one for each argument. -/// -/// @param forceit hide buffers in current windows -/// @param keep_tabs keep current tabs, for ":tab drop file" -void do_arg_all(int count, int forceit, int keep_tabs) -{ - uint8_t *opened; // Array of weight for which args are open: - // 0: not opened - // 1: opened in other tab - // 2: opened in curtab - // 3: opened in curtab and curwin - - int opened_len; // length of opened[] - int use_firstwin = false; // use first window for arglist - bool tab_drop_empty_window = false; - int split_ret = OK; - bool p_ea_save; - alist_T *alist; // argument list to be used - buf_T *buf; - tabpage_T *tpnext; - int had_tab = cmdmod.cmod_tab; - win_T *old_curwin, *last_curwin; - tabpage_T *old_curtab, *last_curtab; - win_T *new_curwin = NULL; - tabpage_T *new_curtab = NULL; - - assert(firstwin != NULL); // satisfy coverity - - if (ARGCOUNT <= 0) { - // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. - return; - } - setpcmark(); - - opened_len = ARGCOUNT; - opened = xcalloc((size_t)opened_len, 1); - - // Autocommands may do anything to the argument list. Make sure it's not - // freed while we are working here by "locking" it. We still have to - // watch out for its size to be changed. - alist = curwin->w_alist; - alist->al_refcount++; - - old_curwin = curwin; - old_curtab = curtab; - - // Try closing all windows that are not in the argument list. - // Also close windows that are not full width; - // When 'hidden' or "forceit" set the buffer becomes hidden. - // Windows that have a changed buffer and can't be hidden won't be closed. - // When the ":tab" modifier was used do this for all tab pages. - if (had_tab > 0) { - goto_tabpage_tp(first_tabpage, true, true); - } - for (;;) { - win_T *wpnext = NULL; - tpnext = curtab->tp_next; - for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { - int i; - wpnext = wp->w_next; - buf = wp->w_buffer; - if (buf->b_ffname == NULL - || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { - i = opened_len; - } else { - // check if the buffer in this window is in the arglist - for (i = 0; i < opened_len; i++) { - if (i < alist->al_ga.ga_len - && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum - || path_full_compare(alist_name(&AARGLIST(alist)[i]), - buf->b_ffname, - true, true) & kEqualFiles)) { - int weight = 1; - - if (old_curtab == curtab) { - weight++; - if (old_curwin == wp) { - weight++; - } - } - - if (weight > (int)opened[i]) { - opened[i] = (uint8_t)weight; - if (i == 0) { - if (new_curwin != NULL) { - new_curwin->w_arg_idx = opened_len; - } - new_curwin = wp; - new_curtab = curtab; - } - } else if (keep_tabs) { - i = opened_len; - } - - if (wp->w_alist != alist) { - // Use the current argument list for all windows containing a file from it. - alist_unlink(wp->w_alist); - wp->w_alist = alist; - wp->w_alist->al_refcount++; - } - break; - } - } - } - wp->w_arg_idx = i; - - if (i == opened_len && !keep_tabs) { // close this window - if (buf_hide(buf) || forceit || buf->b_nwindows > 1 - || !bufIsChanged(buf)) { - // If the buffer was changed, and we would like to hide it, try autowriting. - if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { - bufref_T bufref; - set_bufref(&bufref, buf); - (void)autowrite(buf, false); - // Check if autocommands removed the window. - if (!win_valid(wp) || !bufref_valid(&bufref)) { - wpnext = firstwin; // Start all over... - continue; - } - } - // don't close last window - if (ONE_WINDOW - && (first_tabpage->tp_next == NULL || !had_tab)) { - use_firstwin = true; - } else { - win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); - // check if autocommands removed the next window - if (!win_valid(wpnext)) { - // start all over... - wpnext = firstwin; - } - } - } - } - } - - // Without the ":tab" modifier only do the current tab page. - if (had_tab == 0 || tpnext == NULL) { - break; - } - - // check if autocommands removed the next tab page - if (!valid_tabpage(tpnext)) { - tpnext = first_tabpage; // start all over... - } - goto_tabpage_tp(tpnext, true, true); - } - - // Open a window for files in the argument list that don't have one. - // ARGCOUNT may change while doing this, because of autocommands. - if (count > opened_len || count <= 0) { - count = opened_len; - } - - // Don't execute Win/Buf Enter/Leave autocommands here. - autocmd_no_enter++; - autocmd_no_leave++; - last_curwin = curwin; - last_curtab = curtab; - win_enter(lastwin, false); - // ":tab drop file" should re-use an empty window to avoid "--remote-tab" - // leaving an empty tab page when executed locally. - if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 - && curbuf->b_ffname == NULL && !curbuf->b_changed) { - use_firstwin = true; - tab_drop_empty_window = true; - } - - for (int i = 0; i < count && !got_int; i++) { - if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { - arg_had_last = true; - } - if (opened[i] > 0) { - // Move the already present window to below the current window - if (curwin->w_arg_idx != i) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_arg_idx == i) { - if (keep_tabs) { - new_curwin = wp; - new_curtab = curtab; - } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { - emsg(_("E249: window layout changed unexpectedly")); - i = count; - break; - } else { - win_move_after(wp, curwin); - } - break; - } - } - } - } else if (split_ret == OK) { - // trigger events for tab drop - if (tab_drop_empty_window && i == count - 1) { - autocmd_no_enter--; - } - if (!use_firstwin) { // split current window - p_ea_save = p_ea; - p_ea = true; // use space from all windows - split_ret = win_split(0, WSP_ROOM | WSP_BELOW); - p_ea = p_ea_save; - if (split_ret == FAIL) { - continue; - } - } else { // first window: do autocmd for leaving this buffer - autocmd_no_leave--; - } - - // edit file "i" - curwin->w_arg_idx = i; - if (i == 0) { - new_curwin = curwin; - new_curtab = curtab; - } - (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, - ((buf_hide(curwin->w_buffer) - || bufIsChanged(curwin->w_buffer)) - ? ECMD_HIDE : 0) + ECMD_OLDBUF, - curwin); - if (tab_drop_empty_window && i == count - 1) { - autocmd_no_enter++; - } - if (use_firstwin) { - autocmd_no_leave++; - } - use_firstwin = false; - } - os_breakcheck(); - - // When ":tab" was used open a new tab for a new window repeatedly. - if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { - cmdmod.cmod_tab = 9999; - } - } - - // Remove the "lock" on the argument list. - alist_unlink(alist); - - autocmd_no_enter--; - // restore last referenced tabpage's curwin - if (last_curtab != new_curtab) { - if (valid_tabpage(last_curtab)) { - goto_tabpage_tp(last_curtab, true, true); - } - if (win_valid(last_curwin)) { - win_enter(last_curwin, false); - } - } - // to window with first arg - if (valid_tabpage(new_curtab)) { - goto_tabpage_tp(new_curtab, true, true); - } - if (win_valid(new_curwin)) { - win_enter(new_curwin, false); - } - - autocmd_no_leave--; - xfree(opened); -} - /// @return true if "buf" is a prompt buffer. bool bt_prompt(buf_T *buf) FUNC_ATTR_PURE @@ -5139,8 +4879,6 @@ static int chk_modeline(linenr_T lnum, int flags) intmax_t vers; int end; int retval = OK; - char *save_sourcing_name; - linenr_T save_sourcing_lnum; prev = -1; for (s = (char *)ml_get(lnum); *s != NUL; s++) { @@ -5185,10 +4923,8 @@ static int chk_modeline(linenr_T lnum, int flags) s = linecopy = xstrdup(s); // copy the line, it will change - save_sourcing_lnum = sourcing_lnum; - save_sourcing_name = sourcing_name; - sourcing_lnum = lnum; // prepare for emsg() - sourcing_name = "modelines"; + // prepare for emsg() + estack_push(ETYPE_MODELINE, "modelines", lnum); end = false; while (end == false) { @@ -5228,7 +4964,7 @@ static int chk_modeline(linenr_T lnum, int flags) const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_MODELINE; current_sctx.sc_seq = 0; - current_sctx.sc_lnum = 0; + current_sctx.sc_lnum = lnum; // Make sure no risky things are executed as a side effect. secure = 1; @@ -5243,9 +4979,7 @@ static int chk_modeline(linenr_T lnum, int flags) s = e + 1; // advance to next part } - sourcing_lnum = save_sourcing_lnum; - sourcing_name = save_sourcing_name; - + estack_pop(); xfree(linecopy); return retval; diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index b452eb227e..7627b6a596 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -1,12 +1,13 @@ #ifndef NVIM_BUFFER_H #define NVIM_BUFFER_H -#include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/func_attr.h" +#include "nvim/grid_defs.h" // for StlClickRecord #include "nvim/macros.h" #include "nvim/memline.h" #include "nvim/pos.h" // for linenr_T -#include "nvim/screen.h" // for StlClickRecord // Values for buflist_getfile() enum getf_values { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index b7f66e6dba..769788a63e 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -30,14 +30,12 @@ typedef struct { #include "nvim/option_defs.h" // for jump list and tag stack sizes in a buffer and mark types #include "nvim/mark_defs.h" -// for u_header_T; needs buf_T. +// for u_header_T #include "nvim/undo_defs.h" // for hashtab_T #include "nvim/hashtab.h" // for dict_T #include "nvim/eval/typval.h" -// for proftime_T -#include "nvim/profile.h" // for String #include "nvim/api/private/defs.h" // for Map(K, V) @@ -433,7 +431,7 @@ typedef struct { typedef struct { hashtab_T b_keywtab; // syntax keywords hash table hashtab_T b_keywtab_ic; // idem, ignore case - int b_syn_error; // TRUE when error occurred in HL + bool b_syn_error; // true when error occurred in HL bool b_syn_slow; // true when 'redrawtime' reached int b_syn_ic; // ignore case for :syn cmds int b_syn_foldlevel; // how to compute foldlevel on a line @@ -1150,43 +1148,6 @@ typedef struct { pos_T w_cursor_corr; // corrected cursor position } pos_save_T; -/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode -/// \addtogroup MENU_INDEX -/// @{ -enum { - MENU_INDEX_INVALID = -1, - MENU_INDEX_NORMAL = 0, - MENU_INDEX_VISUAL = 1, - MENU_INDEX_SELECT = 2, - MENU_INDEX_OP_PENDING = 3, - MENU_INDEX_INSERT = 4, - MENU_INDEX_CMDLINE = 5, - MENU_INDEX_TERMINAL = 6, - MENU_INDEX_TIP = 7, - MENU_MODES = 8, -}; - -typedef struct VimMenu vimmenu_T; - -struct VimMenu { - int modes; ///< Which modes is this menu visible for - int enabled; ///< for which modes the menu is enabled - char *name; ///< Name of menu, possibly translated - char *dname; ///< Displayed Name ("name" without '&') - char *en_name; ///< "name" untranslated, NULL when - ///< was not translated - char *en_dname; ///< NULL when "dname" untranslated - int mnemonic; ///< mnemonic key (after '&') - char *actext; ///< accelerator text (after TAB) - long priority; ///< Menu order priority - char *strings[MENU_MODES]; ///< Mapped string for each mode - int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode - bool silent[MENU_MODES]; ///< A silent flag for each mode - vimmenu_T *children; ///< Children of sub-menu - vimmenu_T *parent; ///< Parent of menu - vimmenu_T *next; ///< Next item in menu -}; - /// Structure which contains all information that belongs to a window. /// /// All row numbers are relative to the start of the window, except w_winrow. @@ -1198,11 +1159,14 @@ struct window_S { synblock_T *w_s; ///< for :ownsyntax + int w_ns_hl; + int w_ns_hl_winhl; + int w_ns_hl_active; + int *w_ns_hl_attr; + int w_hl_id_normal; ///< 'winhighlight' normal id int w_hl_attr_normal; ///< 'winhighlight' normal final attrs - - int w_hl_ids[HLF_COUNT]; ///< 'winhighlight' id - int w_hl_attrs[HLF_COUNT]; ///< 'winhighlight' final attrs + int w_hl_attr_normalnc; ///< 'winhighlight' NormalNC final attrs int w_hl_needs_update; ///< attrs need to be recalculated @@ -1525,11 +1489,6 @@ struct window_S { size_t w_winbar_click_defs_size; }; -static inline int win_hl_attr(win_T *wp, int hlf) -{ - return wp->w_hl_attrs[hlf]; -} - /// Macros defined in Vim, but not in Neovim #define CHANGEDTICK(buf) \ (=== Include buffer.h & use buf_(get|set|inc) _changedtick ===) diff --git a/src/nvim/change.c b/src/nvim/change.c index c063ece907..5184dc0689 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -10,6 +10,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/extmark.h" @@ -23,7 +24,6 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/ui.h" @@ -421,12 +421,12 @@ void deleted_lines(linenr_T lnum, linenr_T count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - // if we deleted the entire buffer, we need to implicitly add a new empty line bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY; - mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, - -(linenr_T)count + (made_empty?1:0), - kExtmarkUndo); + mark_adjust(lnum, (linenr_T)(lnum + count - 1), MAXLNUM, -(linenr_T)count, kExtmarkNOOP); + // if we deleted the entire buffer, we need to implicitly add a new empty line + extmark_adjust(curbuf, lnum, (linenr_T)(lnum + count - 1), MAXLNUM, + -(linenr_T)count + (made_empty ? 1 : 0), kExtmarkUndo); changed_lines(lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true); } diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 20fae3a206..5910053025 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,14 +4,15 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/autocmd.h" #include "nvim/channel.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/event/socket.h" -#include "nvim/fileio.h" #include "nvim/lua/executor.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" +#include "nvim/os/fs.h" #include "nvim/os/shell.h" #ifdef WIN32 # include "nvim/os/os_win_console.h" @@ -314,8 +315,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader ChannelStdinMode stdin_mode, const char *cwd, uint16_t pty_width, uint16_t pty_height, dict_T *env, varnumber_T *status_out) { - assert(cwd == NULL || os_isdir_executable(cwd)); - Channel *chan = channel_alloc(kChannelStreamProc); chan->on_data = on_stdout; chan->on_stderr = on_stderr; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index a26a7f6aaf..6238d85b3a 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -126,7 +126,7 @@ int buf_init_chartab(buf_T *buf, int global) } // Init word char flags all to false - memset(buf->b_chartab, 0, (size_t)32); + CLEAR_FIELD(buf->b_chartab); // In lisp mode the '-' character is included in keywords. if (buf->b_p_lisp) { diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c new file mode 100644 index 0000000000..5725a6655d --- /dev/null +++ b/src/nvim/cmdhist.c @@ -0,0 +1,743 @@ +// 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 + +// cmdhist.c: Functions for the history of the command-line. + +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/cmdhist.h" +#include "nvim/ex_getln.h" +#include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdhist.c.generated.h" +#endif + +static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL }; +static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; ///< lastused entry +/// identifying (unique) number of newest history entry +static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 }; +static int hislen = 0; ///< actual length of history tables + +/// Return the length of the history tables +int get_hislen(void) +{ + return hislen; +} + +/// Return a pointer to a specified history table +histentry_T *get_histentry(int hist_type) +{ + return history[hist_type]; +} + +void set_histentry(int hist_type, histentry_T *entry) +{ + history[hist_type] = entry; +} + +int *get_hisidx(int hist_type) +{ + return &hisidx[hist_type]; +} + +int *get_hisnum(int hist_type) +{ + return &hisnum[hist_type]; +} + +/// Translate a history character to the associated type number +HistoryType hist_char2type(const int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (c) { + case ':': + return HIST_CMD; + case '=': + return HIST_EXPR; + case '@': + return HIST_INPUT; + case '>': + return HIST_DEBUG; + case NUL: + case '/': + case '?': + return HIST_SEARCH; + default: + return HIST_INVALID; + } + // Silence -Wreturn-type + return 0; +} + +/// Table of history names. +/// These names are used in :history and various hist...() functions. +/// It is sufficient to give the significant prefix of a history name. +static char *(history_names[]) = { + "cmd", + "search", + "expr", + "input", + "debug", + NULL +}; + +/// Function given to ExpandGeneric() to obtain the possible first +/// arguments of the ":history command. +char *get_history_arg(expand_T *xp, int idx) +{ + static char_u compl[2] = { NUL, NUL }; + char *short_names = ":=@>?/"; + int short_names_count = (int)STRLEN(short_names); + int history_name_count = ARRAY_SIZE(history_names) - 1; + + if (idx < short_names_count) { + compl[0] = (char_u)short_names[idx]; + return (char *)compl; + } + if (idx < short_names_count + history_name_count) { + return history_names[idx - short_names_count]; + } + if (idx == short_names_count + history_name_count) { + return "all"; + } + return NULL; +} + +/// Initialize command line history. +/// Also used to re-allocate history tables when size changes. +void init_history(void) +{ + assert(p_hi >= 0 && p_hi <= INT_MAX); + int newlen = (int)p_hi; + int oldlen = hislen; + + // If history tables size changed, reallocate them. + // Tables are circular arrays (current position marked by hisidx[type]). + // On copying them to the new arrays, we take the chance to reorder them. + if (newlen != oldlen) { + for (int type = 0; type < HIST_COUNT; type++) { + histentry_T *temp = (newlen + ? xmalloc((size_t)newlen * sizeof(*temp)) + : NULL); + + int j = hisidx[type]; + if (j >= 0) { + // old array gets partitioned this way: + // [0 , i1 ) --> newest entries to be deleted + // [i1 , i1 + l1) --> newest entries to be copied + // [i1 + l1 , i2 ) --> oldest entries to be deleted + // [i2 , i2 + l2) --> oldest entries to be copied + int l1 = MIN(j + 1, newlen); // how many newest to copy + int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy + int i1 = j + 1 - l1; // copy newest from here + int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here + + // copy as much entries as they fit to new table, reordering them + if (newlen) { + // copy oldest entries + memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp)); + // copy newest entries + memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp)); + } + + // delete entries that don't fit in newlen, if any + for (int i = 0; i < i1; i++) { + hist_free_entry(history[type] + i); + } + for (int i = i1 + l1; i < i2; i++) { + hist_free_entry(history[type] + i); + } + } + + // clear remaining space, if any + int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries + if (newlen) { + memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp)); + } + + hisidx[type] = l3 - 1; + xfree(history[type]); + history[type] = temp; + } + hislen = newlen; + } +} + +static inline void hist_free_entry(histentry_T *hisptr) + FUNC_ATTR_NONNULL_ALL +{ + xfree(hisptr->hisstr); + tv_list_unref(hisptr->additional_elements); + clear_hist_entry(hisptr); +} + +static inline void clear_hist_entry(histentry_T *hisptr) + FUNC_ATTR_NONNULL_ALL +{ + CLEAR_POINTER(hisptr); +} + +/// Check if command line 'str' is already in history. +/// If 'move_to_front' is true, matching entry is moved to end of history. +/// +/// @param move_to_front Move the entry to the front if it exists +static int in_history(int type, char_u *str, int move_to_front, int sep) +{ + int last_i = -1; + + if (hisidx[type] < 0) { + return false; + } + int i = hisidx[type]; + do { + if (history[type][i].hisstr == NULL) { + return false; + } + + // For search history, check that the separator character matches as + // well. + char_u *p = history[type][i].hisstr; + if (STRCMP(str, p) == 0 + && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { + if (!move_to_front) { + return true; + } + last_i = i; + break; + } + if (--i < 0) { + i = hislen - 1; + } + } while (i != hisidx[type]); + + if (last_i >= 0) { + list_T *const list = history[type][i].additional_elements; + str = history[type][i].hisstr; + while (i != hisidx[type]) { + if (++i >= hislen) { + i = 0; + } + history[type][last_i] = history[type][i]; + last_i = i; + } + tv_list_unref(list); + history[type][i].hisnum = ++hisnum[type]; + history[type][i].hisstr = str; + history[type][i].timestamp = os_time(); + history[type][i].additional_elements = NULL; + return true; + } + return false; +} + +/// Convert history name to its HIST_ equivalent +/// +/// Names are taken from the table above. When `name` is empty returns currently +/// active history or HIST_DEFAULT, depending on `return_default` argument. +/// +/// @param[in] name Converted name. +/// @param[in] len Name length. +/// @param[in] return_default Determines whether HIST_DEFAULT should be +/// returned or value based on `ccline.cmdfirstc`. +/// +/// @return Any value from HistoryType enum, including HIST_INVALID. May not +/// return HIST_DEFAULT unless return_default is true. +static HistoryType get_histtype(const char *const name, const size_t len, const bool return_default) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + // No argument: use current history. + if (len == 0) { + return return_default ? HIST_DEFAULT : hist_char2type(get_cmdline_firstc()); + } + + for (HistoryType i = 0; history_names[i] != NULL; i++) { + if (STRNICMP(name, history_names[i], len) == 0) { + return i; + } + } + + if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) { + return hist_char2type(name[0]); + } + + return HIST_INVALID; +} + +static int last_maptick = -1; // last seen maptick + +/// Add the given string to the given history. If the string is already in the +/// history then it is moved to the front. +/// +/// @param histype may be one of the HIST_ values. +/// @param in_map consider maptick when inside a mapping +/// @param sep separator character used (search hist) +void add_to_history(int histype, char_u *new_entry, int in_map, int sep) +{ + histentry_T *hisptr; + + if (hislen == 0 || histype == HIST_INVALID) { // no history + return; + } + assert(histype != HIST_DEFAULT); + + if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) { + return; + } + + // Searches inside the same mapping overwrite each other, so that only + // the last line is kept. Be careful not to remove a line that was moved + // down, only lines that were added. + if (histype == HIST_SEARCH && in_map) { + if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) { + // Current line is from the same mapping, remove it + hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; + hist_free_entry(hisptr); + hisnum[histype]--; + if (--hisidx[HIST_SEARCH] < 0) { + hisidx[HIST_SEARCH] = hislen - 1; + } + } + last_maptick = -1; + } + if (!in_history(histype, new_entry, true, sep)) { + if (++hisidx[histype] == hislen) { + hisidx[histype] = 0; + } + hisptr = &history[histype][hisidx[histype]]; + hist_free_entry(hisptr); + + // Store the separator after the NUL of the string. + size_t len = STRLEN(new_entry); + hisptr->hisstr = vim_strnsave(new_entry, len + 2); + hisptr->timestamp = os_time(); + hisptr->additional_elements = NULL; + hisptr->hisstr[len + 1] = (char_u)sep; + + hisptr->hisnum = ++hisnum[histype]; + if (histype == HIST_SEARCH && in_map) { + last_maptick = maptick; + } + } +} + +/// Get identifier of newest history entry. +/// +/// @param histype may be one of the HIST_ values. +static int get_history_idx(int histype) +{ + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || hisidx[histype] < 0) { + return -1; + } + + return history[histype][hisidx[histype]].hisnum; +} + +/// Calculate history index from a number: +/// +/// @param num > 0: seen as identifying number of a history entry +/// < 0: relative position in history wrt newest entry +/// @param histype may be one of the HIST_ values. +static int calc_hist_idx(int histype, int num) +{ + int i; + int wrapped = false; + + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || (i = hisidx[histype]) < 0 || num == 0) { + return -1; + } + + histentry_T *hist = history[histype]; + if (num > 0) { + while (hist[i].hisnum > num) { + if (--i < 0) { + if (wrapped) { + break; + } + i += hislen; + wrapped = true; + } + } + if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) { + return i; + } + } else if (-num <= hislen) { + i += num + 1; + if (i < 0) { + i += hislen; + } + if (hist[i].hisstr != NULL) { + return i; + } + } + return -1; +} + +/// Get a history entry by its index. +/// +/// @param histype may be one of the HIST_ values. +static char_u *get_history_entry(int histype, int idx) +{ + idx = calc_hist_idx(histype, idx); + if (idx >= 0) { + return history[histype][idx].hisstr; + } else { + return (char_u *)""; + } +} + +/// Clear all entries in a history +/// +/// @param[in] histype One of the HIST_ values. +/// +/// @return OK if there was something to clean and histype was one of HIST_ +/// values, FAIL otherwise. +int clr_history(const int histype) +{ + if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) { + histentry_T *hisptr = history[histype]; + for (int i = hislen; i--; hisptr++) { + hist_free_entry(hisptr); + } + hisidx[histype] = -1; // mark history as cleared + hisnum[histype] = 0; // reset identifier counter + return OK; + } + return FAIL; +} + +/// Remove all entries matching {str} from a history. +/// +/// @param histype may be one of the HIST_ values. +static int del_history_entry(int histype, char_u *str) +{ + regmatch_T regmatch; + histentry_T *hisptr; + int idx; + int i; + int last; + bool found = false; + + regmatch.regprog = NULL; + regmatch.rm_ic = false; // always match case + if (hislen != 0 + && histype >= 0 + && histype < HIST_COUNT + && *str != NUL + && (idx = hisidx[histype]) >= 0 + && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING)) != NULL) { + i = last = idx; + do { + hisptr = &history[histype][i]; + if (hisptr->hisstr == NULL) { + break; + } + if (vim_regexec(®match, (char *)hisptr->hisstr, (colnr_T)0)) { + found = true; + hist_free_entry(hisptr); + } else { + if (i != last) { + history[histype][last] = *hisptr; + clear_hist_entry(hisptr); + } + if (--last < 0) { + last += hislen; + } + } + if (--i < 0) { + i += hislen; + } + } while (i != idx); + if (history[histype][idx].hisstr == NULL) { + hisidx[histype] = -1; + } + } + vim_regfree(regmatch.regprog); + return found; +} + +/// Remove an indexed entry from a history. +/// +/// @param histype may be one of the HIST_ values. +static int del_history_idx(int histype, int idx) +{ + int i = calc_hist_idx(histype, idx); + if (i < 0) { + return false; + } + idx = hisidx[histype]; + hist_free_entry(&history[histype][i]); + + // When deleting the last added search string in a mapping, reset + // last_maptick, so that the last added search string isn't deleted again. + if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) { + last_maptick = -1; + } + + while (i != idx) { + int j = (i + 1) % hislen; + history[histype][i] = history[histype][j]; + i = j; + } + clear_hist_entry(&history[histype][idx]); + if (--i < 0) { + i += hislen; + } + hisidx[histype] = i; + return true; +} + +/// "histadd()" function +void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + HistoryType histype; + + rettv->vval.v_number = false; + if (check_secure()) { + return; + } + const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error + histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; + if (histype != HIST_INVALID) { + char buf[NUMBUFLEN]; + str = tv_get_string_buf(&argvars[1], buf); + if (*str != NUL) { + init_history(); + add_to_history(histype, (char_u *)str, false, NUL); + rettv->vval.v_number = true; + return; + } + } +} + +/// "histdel()" function +void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int n; + const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { + n = 0; + } else if (argvars[1].v_type == VAR_UNKNOWN) { + // only one argument: clear entire history + n = clr_history(get_histtype(str, strlen(str), false)); + } else if (argvars[1].v_type == VAR_NUMBER) { + // index given: remove that entry + n = del_history_idx(get_histtype(str, strlen(str), false), + (int)tv_get_number(&argvars[1])); + } else { + // string given: remove all matching entries + char buf[NUMBUFLEN]; + n = del_history_entry(get_histtype(str, strlen(str), false), + (char_u *)tv_get_string_buf(&argvars[1], buf)); + } + rettv->vval.v_number = n; +} + +/// "histget()" function +void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + HistoryType type; + int idx; + + const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { + rettv->vval.v_string = NULL; + } else { + type = get_histtype(str, strlen(str), false); + if (argvars[1].v_type == VAR_UNKNOWN) { + idx = get_history_idx(type); + } else { + idx = (int)tv_get_number_chk(&argvars[1], NULL); + } + // -1 on type error + rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx)); + } + rettv->v_type = VAR_STRING; +} + +/// "histnr()" function +void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const histname = tv_get_string_chk(&argvars[0]); + HistoryType i = histname == NULL + ? HIST_INVALID + : get_histtype(histname, strlen(histname), false); + if (i != HIST_INVALID) { + i = get_history_idx(i); + } + rettv->vval.v_number = i; +} + +/// :history command - print a history +void ex_history(exarg_T *eap) +{ + histentry_T *hist; + int histype1 = HIST_CMD; + int histype2 = HIST_CMD; + int hisidx1 = 1; + int hisidx2 = -1; + int idx; + int i, j, k; + char *end; + char_u *arg = (char_u *)eap->arg; + + if (hislen == 0) { + msg(_("'history' option is zero")); + return; + } + + if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) { + end = (char *)arg; + while (ASCII_ISALPHA(*end) + || vim_strchr(":=@>/?", *end) != NULL) { + end++; + } + histype1 = get_histtype((const char *)arg, (size_t)(end - (char *)arg), false); + if (histype1 == HIST_INVALID) { + if (STRNICMP(arg, "all", end - (char *)arg) == 0) { + histype1 = 0; + histype2 = HIST_COUNT - 1; + } else { + semsg(_(e_trailing_arg), arg); + return; + } + } else { + histype2 = histype1; + } + } else { + end = (char *)arg; + } + if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { + semsg(_(e_trailing_arg), end); + return; + } + + for (; !got_int && histype1 <= histype2; histype1++) { + STRCPY(IObuff, "\n # "); + assert(history_names[histype1] != NULL); + STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); + msg_puts_title((char *)IObuff); + idx = hisidx[histype1]; + hist = history[histype1]; + j = hisidx1; + k = hisidx2; + if (j < 0) { + j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum; + } + if (k < 0) { + k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum; + } + if (idx >= 0 && j <= k) { + for (i = idx + 1; !got_int; i++) { + if (i == hislen) { + i = 0; + } + if (hist[i].hisstr != NULL + && hist[i].hisnum >= j && hist[i].hisnum <= k) { + msg_putchar('\n'); + snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', + hist[i].hisnum); + if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) { + trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff), + Columns - 10, IOSIZE - (int)STRLEN(IObuff)); + } else { + STRCAT(IObuff, hist[i].hisstr); + } + msg_outtrans((char *)IObuff); + ui_flush(); + } + if (i == idx) { + break; + } + } + } + } +} + +/// Iterate over history items +/// +/// @warning No history-editing functions must be run while iteration is in +/// progress. +/// +/// @param[in] iter Pointer to the last history entry. +/// @param[in] history_type Type of the history (HIST_*). Ignored if iter +/// parameter is not NULL. +/// @param[in] zero If true then zero (but not free) returned items. +/// +/// @warning When using this parameter user is +/// responsible for calling clr_history() +/// itself after iteration is over. If +/// clr_history() is not called behaviour is +/// undefined. No functions that work with +/// history must be called during iteration +/// in this case. +/// @param[out] hist Next history entry. +/// +/// @return Pointer used in next iteration or NULL to indicate that iteration +/// was finished. +const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero, + histentry_T *const hist) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4) +{ + *hist = (histentry_T) { + .hisstr = NULL + }; + if (hisidx[history_type] == -1) { + return NULL; + } + histentry_T *const hstart = &(history[history_type][0]); + histentry_T *const hlast = &(history[history_type][hisidx[history_type]]); + const histentry_T *const hend = &(history[history_type][hislen - 1]); + histentry_T *hiter; + if (iter == NULL) { + histentry_T *hfirst = hlast; + do { + hfirst++; + if (hfirst > hend) { + hfirst = hstart; + } + if (hfirst->hisstr != NULL) { + break; + } + } while (hfirst != hlast); + hiter = hfirst; + } else { + hiter = (histentry_T *)iter; + } + if (hiter == NULL) { + return NULL; + } + *hist = *hiter; + if (zero) { + CLEAR_POINTER(hiter); + } + if (hiter == hlast) { + return NULL; + } + hiter++; + return (const void *)((hiter > hend) ? hstart : hiter); +} + +/// Get array of history items +/// +/// @param[in] history_type Type of the history to get array for. +/// @param[out] new_hisidx Location where last index in the new array should +/// be saved. +/// @param[out] new_hisnum Location where last history number in the new +/// history should be saved. +/// +/// @return Pointer to the array or NULL. +histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx, + int **const new_hisnum) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + init_history(); + *new_hisidx = &(hisidx[history_type]); + *new_hisnum = &(hisnum[history_type]); + return history[history_type]; +} diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h new file mode 100644 index 0000000000..797b79a5f0 --- /dev/null +++ b/src/nvim/cmdhist.h @@ -0,0 +1,33 @@ +#ifndef NVIM_CMDHIST_H +#define NVIM_CMDHIST_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/os/time.h" + +/// Present history tables +typedef enum { + HIST_DEFAULT = -2, ///< Default (current) history. + HIST_INVALID = -1, ///< Unknown history. + HIST_CMD = 0, ///< Colon commands. + HIST_SEARCH, ///< Search commands. + HIST_EXPR, ///< Expressions (e.g. from entering = register). + HIST_INPUT, ///< input() lines. + HIST_DEBUG, ///< Debug commands. +} HistoryType; + +/// Number of history tables +#define HIST_COUNT (HIST_DEBUG + 1) + +/// History entry definition +typedef struct hist_entry { + int hisnum; ///< Entry identifier number. + char_u *hisstr; ///< Actual entry, separator char after the NUL. + Timestamp timestamp; ///< Time when entry was added. + list_T *additional_elements; ///< Additional entries from ShaDa file. +} histentry_T; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdhist.h.generated.h" +#endif +#endif // NVIM_CMDHIST_H diff --git a/src/nvim/context.c b/src/nvim/context.c index db26667009..e3ae9355bf 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -9,6 +9,7 @@ #include "nvim/api/vimscript.h" #include "nvim/context.h" #include "nvim/eval/encode.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" #include "nvim/option.h" #include "nvim/shada.h" @@ -249,7 +250,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) ctx->funcs = (Array)ARRAY_DICT_INIT; Error err = ERROR_INIT; - HASHTAB_ITER(&func_hashtab, hi, { + HASHTAB_ITER(func_tbl_get(), hi, { const char_u *const name = hi->hi_key; bool islambda = (STRNCMP(name, "<lambda>", 8) == 0); bool isscript = (name[0] == K_SPECIAL); diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 1446257f7e..d4670dedc7 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -9,6 +9,7 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/fold.h" #include "nvim/mark.h" @@ -17,7 +18,6 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/vim.h" diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 0eaff06833..a061bd961b 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -8,6 +8,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/debugger.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" @@ -17,7 +18,7 @@ #include "nvim/os/os.h" #include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/types.h" #include "nvim/vim.h" @@ -98,14 +99,17 @@ void do_debug(char_u *cmd) xfree(debug_newval); debug_newval = NULL; } - if (sourcing_name != NULL) { - msg(sourcing_name); + char *sname = estack_sfile(ESTACK_NONE); + if (sname != NULL) { + msg(sname); } - if (sourcing_lnum != 0) { - smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd); + xfree(sname); + if (SOURCING_LNUM != 0) { + smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd); } else { smsg(_("cmd: %s"), cmd); } + // Repeat getting a command and executing it. for (;;) { msg_scroll = true; @@ -287,12 +291,12 @@ void do_debug(char_u *cmd) debug_did_msg = true; } -static int get_maxbacktrace_level(void) +static int get_maxbacktrace_level(char *sname) { int maxbacktrace = 0; - if (sourcing_name != NULL) { - char *p = sourcing_name; + if (sname != NULL) { + char *p = sname; char *q; while ((q = strstr(p, "..")) != NULL) { p = q + 2; @@ -320,20 +324,24 @@ static void do_checkbacktracelevel(void) debug_backtrace_level = 0; msg(_("frame is zero")); } else { - int max = get_maxbacktrace_level(); + char *sname = estack_sfile(ESTACK_NONE); + int max = get_maxbacktrace_level(sname); + if (debug_backtrace_level > max) { debug_backtrace_level = max; smsg(_("frame at highest level: %d"), max); } + xfree(sname); } } static void do_showbacktrace(char_u *cmd) { - if (sourcing_name != NULL) { + char *sname = estack_sfile(ESTACK_NONE); + int max = get_maxbacktrace_level(sname); + if (sname != NULL) { int i = 0; - int max = get_maxbacktrace_level(); - char *cur = sourcing_name; + char *cur = sname; while (!got_int) { char *next = strstr(cur, ".."); if (next != NULL) { @@ -351,9 +359,11 @@ static void do_showbacktrace(char_u *cmd) *next = '.'; cur = next + 2; } + xfree(sname); } - if (sourcing_lnum != 0) { - smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd); + + if (SOURCING_LNUM != 0) { + smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd); } else { smsg(_("cmd: %s"), cmd); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e7c76fe38e..a93fb599c4 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -4,12 +4,12 @@ #include "nvim/api/ui.h" #include "nvim/buffer.h" #include "nvim/decoration.h" +#include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/move.h" -#include "nvim/screen.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -358,7 +358,8 @@ next_mark: return attr; } -void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs[]) +void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[], + HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs) { if (!buf->b_signs) { return; @@ -383,30 +384,37 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs goto next_mark; } - int j; - for (j = (*num_signs); j > 0; j--) { - if (sattrs[j].sat_prio <= decor->priority) { - break; - } - sattrs[j] = sattrs[j - 1]; - } - if (j < SIGN_SHOW_MAX) { - memset(&sattrs[j], 0, sizeof(sign_attrs_T)); - sattrs[j].sat_text = decor->sign_text; - if (decor->sign_hl_id != 0) { - sattrs[j].sat_texthl = syn_id2attr(decor->sign_hl_id); - } - if (decor->number_hl_id != 0) { - sattrs[j].sat_numhl = syn_id2attr(decor->number_hl_id); + if (decor->sign_text) { + int j; + for (j = (*num_signs); j > 0; j--) { + if (sattrs[j - 1].priority >= decor->priority) { + break; + } + sattrs[j] = sattrs[j - 1]; } - if (decor->line_hl_id != 0) { - sattrs[j].sat_linehl = syn_id2attr(decor->line_hl_id); + if (j < SIGN_SHOW_MAX) { + sattrs[j] = (SignTextAttrs) { + .text = decor->sign_text, + .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id), + .priority = decor->priority + }; + (*num_signs)++; } - if (decor->cursorline_hl_id != 0) { - sattrs[j].sat_culhl = syn_id2attr(decor->cursorline_hl_id); + } + + struct { HlPriAttr *dest; int hl; } cattrs[] = { + { line_attrs, decor->line_hl_id }, + { num_attrs, decor->number_hl_id }, + { cul_attrs, decor->cursorline_hl_id }, + { NULL, -1 }, + }; + for (int i = 0; cattrs[i].dest; i++) { + if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) { + *cattrs[i].dest = (HlPriAttr) { + .attr_id = syn_id2attr(cattrs[i].hl), + .priority = decor->priority + }; } - sattrs[j].sat_prio = decor->priority; - (*num_signs)++; } next_mark: diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 04d875c4e3..95e13b4240 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -14,7 +14,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; #define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ { ns_id, false, LUA_NOREF, LUA_NOREF, \ LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, -1 } + LUA_NOREF, -1, false } static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args, bool default_true, char **perr) @@ -107,8 +107,6 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, } } } - - win_check_ns_hl(wp); } /// For each provider invoke the 'line' callback for a given window row. @@ -135,7 +133,7 @@ void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool * kv_A(*providers, k) = NULL; } - win_check_ns_hl(wp); + hl_check_ns(); } } } diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h index 3ec7c80357..dd1ed6c581 100644 --- a/src/nvim/decoration_provider.h +++ b/src/nvim/decoration_provider.h @@ -13,6 +13,7 @@ typedef struct { LuaRef redraw_end; LuaRef hl_def; int hl_valid; + bool hl_cached; } DecorProvider; typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 849204f789..c1fdbc1b9a 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -14,11 +14,13 @@ #include <stdbool.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" @@ -35,7 +37,6 @@ #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" -#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/undo.h" @@ -956,13 +957,13 @@ void ex_diffupdate(exarg_T *eap) // Only use the internal method if it did not fail for one of the buffers. diffio_T diffio; - memset(&diffio, 0, sizeof(diffio)); + CLEAR_FIELD(diffio); diffio.dio_internal = diff_internal() && !diff_internal_failed(); diff_try_update(&diffio, idx_orig, eap); if (diffio.dio_internal && diff_internal_failed()) { // Internal diff failed, use external diff instead. - memset(&diffio, 0, sizeof(diffio)); + CLEAR_FIELD(diffio); diff_try_update(&diffio, idx_orig, eap); } @@ -1075,9 +1076,9 @@ static int diff_file_internal(diffio_T *diffio) xdemitconf_t emit_cfg; xdemitcb_t emit_cb; - memset(¶m, 0, sizeof(param)); - memset(&emit_cfg, 0, sizeof(emit_cfg)); - memset(&emit_cb, 0, sizeof(emit_cb)); + CLEAR_FIELD(param); + CLEAR_FIELD(emit_cfg); + CLEAR_FIELD(emit_cb); param.flags = (unsigned long)diff_algorithm; @@ -2143,14 +2144,14 @@ int diffopt_changed(void) long diff_algorithm_new = 0; long diff_indent_heuristic = 0; - char_u *p = p_dip; + char *p = (char *)p_dip; while (*p != NUL) { if (STRNCMP(p, "filler", 6) == 0) { p += 6; diff_flags_new |= DIFF_FILLER; } else if ((STRNCMP(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { p += 8; - diff_context_new = getdigits_int((char **)&p, false, diff_context_new); + diff_context_new = getdigits_int(&p, false, diff_context_new); } else if (STRNCMP(p, "iblank", 6) == 0) { p += 6; diff_flags_new |= DIFF_IBLANK; @@ -2174,7 +2175,7 @@ int diffopt_changed(void) diff_flags_new |= DIFF_VERTICAL; } else if ((STRNCMP(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { p += 11; - diff_foldcolumn_new = getdigits_int((char **)&p, false, diff_foldcolumn_new); + diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new); } else if (STRNCMP(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 733b3d3d5d..0f511bd37c 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -12,8 +12,8 @@ #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" @@ -24,7 +24,7 @@ #include "nvim/message.h" #include "nvim/normal.h" #include "nvim/os/input.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/vim.h" diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c new file mode 100644 index 0000000000..95026ff8ed --- /dev/null +++ b/src/nvim/drawline.c @@ -0,0 +1,2706 @@ +// 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 + +// drawline.c: Functions for drawing window lines on the screen. +// This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level. + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/arabic.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/cursor_shape.h" +#include "nvim/diff.h" +#include "nvim/drawline.h" +#include "nvim/fold.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/indent.h" +#include "nvim/match.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/plines.h" +#include "nvim/quickfix.h" +#include "nvim/search.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/state.h" +#include "nvim/syntax.h" +#include "nvim/undo.h" +#include "nvim/window.h" + +#define MB_FILLER_CHAR '<' // character used when a double-width character + // doesn't fit. + +/// for line_putchar. Contains the state that needs to be remembered from +/// putting one character to the next. +typedef struct { + const char *p; + int prev_c; ///< previous Arabic character + int prev_c1; ///< first composing char for prev_c +} LineState; +#define LINE_STATE(p) { p, 0, 0 } + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawline.c.generated.h" +#endif + +/// Advance **color_cols +/// +/// @return true when there are columns to draw. +static bool advance_color_col(int vcol, int **color_cols) +{ + while (**color_cols >= 0 && vcol > **color_cols) { + (*color_cols)++; + } + return **color_cols >= 0; +} + +/// Used when 'cursorlineopt' contains "screenline": compute the margins between +/// which the highlighting is used. +static void margin_columns_win(win_T *wp, int *left_col, int *right_col) +{ + // cache previous calculations depending on w_virtcol + static int saved_w_virtcol; + static win_T *prev_wp; + static int prev_left_col; + static int prev_right_col; + static int prev_col_off; + + int cur_col_off = win_col_off(wp); + int width1; + int width2; + + if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp + && prev_col_off == cur_col_off) { + *right_col = prev_right_col; + *left_col = prev_left_col; + return; + } + + width1 = wp->w_width - cur_col_off; + width2 = width1 + win_col_off2(wp); + + *left_col = 0; + *right_col = width1; + + if (wp->w_virtcol >= (colnr_T)width1) { + *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; + } + if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { + *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; + } + + // cache values + prev_left_col = *left_col; + prev_right_col = *right_col; + prev_wp = wp; + saved_w_virtcol = wp->w_virtcol; + prev_col_off = cur_col_off; +} + +/// Put a single char from an UTF-8 buffer into a line buffer. +/// +/// Handles composing chars and arabic shaping state. +static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol) +{ + const char_u *p = (char_u *)s->p; + int cells = utf_ptr2cells((char *)p); + int c_len = utfc_ptr2len((char *)p); + int u8c, u8cc[MAX_MCO]; + if (cells > maxcells) { + return -1; + } + u8c = utfc_ptr2char(p, u8cc); + 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], ' '); + } + goto done; + } else if (*p < 0x80 && u8cc[0] == 0) { + schar_from_ascii(dest[0], (char)(*p)); + s->prev_c = u8c; + } else { + if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { + // Do Arabic shaping. + int pc, pc1, nc; + int pcc[MAX_MCO]; + int firstbyte = *p; + + // The idea of what is the previous and next + // character depends on 'rightleft'. + if (rl) { + pc = s->prev_c; + pc1 = s->prev_c1; + nc = utf_ptr2char((char *)p + c_len); + s->prev_c1 = u8cc[0]; + } else { + pc = utfc_ptr2char(p + c_len, pcc); + nc = s->prev_c; + pc1 = pcc[0]; + } + s->prev_c = u8c; + + u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc); + } else { + s->prev_c = u8c; + } + schar_from_cc(dest[0], u8c, u8cc); + } + if (cells > 1) { + dest[1][0] = 0; + } +done: + s->p += c_len; + return cells; +} + +static inline void provider_err_virt_text(linenr_T lnum, char *err) +{ + Decoration err_decor = DECORATION_INIT; + int hl_err = syn_check_group(S_LEN("ErrorMsg")); + kv_push(err_decor.virt_text, + ((VirtTextChunk){ .text = err, + .hl_id = hl_err })); + err_decor.virt_text_width = (int)mb_string2cells(err); + decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0); +} + +static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, + int win_row) +{ + DecorState *state = &decor_state; + int right_pos = max_col; + bool do_eol = state->eol_col > -1; + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (!(item->start_row == state->row + && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) { + continue; + } + if (item->win_col == -1) { + if (item->decor.virt_text_pos == kVTRightAlign) { + right_pos -= item->decor.virt_text_width; + item->win_col = right_pos; + } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + item->win_col = state->eol_col; + } else if (item->decor.virt_text_pos == kVTWinCol) { + item->win_col = MAX(item->decor.col + col_off, 0); + } + } + if (item->win_col < 0) { + continue; + } + int col; + if (item->decor.ui_watched) { + // send mark position to UI + col = item->win_col; + WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col }; + kv_push(win_extmark_arr, m); + } + if (kv_size(item->decor.virt_text)) { + col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, + item->decor.hl_mode, max_col, item->win_col - col_off); + } + item->win_col = -2; // deactivate + if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + state->eol_col = col + 1; + } + + *end_col = MAX(*end_col, col); + } +} + +static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, + int vcol) +{ + LineState s = LINE_STATE(""); + int virt_attr = 0; + size_t virt_pos = 0; + + while (col < max_col) { + if (!*s.p) { + if (virt_pos >= kv_size(vt)) { + break; + } + virt_attr = 0; + do { + s.p = kv_A(vt, virt_pos).text; + int hl_id = kv_A(vt, virt_pos).hl_id; + virt_attr = hl_combine_attr(virt_attr, + hl_id > 0 ? syn_id2attr(hl_id) : 0); + virt_pos++; + } while (!s.p && virt_pos < kv_size(vt)); + if (!s.p) { + break; + } + } + if (!*s.p) { + continue; + } + int attr; + bool through = false; + if (hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (hl_mode == kHlModeBlend) { + through = (*s.p == ' '); + attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); + } else { + attr = virt_attr; + } + schar_T dummy[2]; + int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], + max_col - col, false, vcol); + // if we failed to emit a char, we still need to advance + cells = MAX(cells, 1); + + for (int c = 0; c < cells; c++) { + linebuf_attr[col++] = attr; + } + vcol += cells; + } + return col; +} + +/// Return true if CursorLineSign highlight is to be used. +static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR); +} + +// Get information needed to display the sign in line 'lnum' in window 'wp'. +// If 'nrcol' is TRUE, the sign is going to be displayed in the number column. +// Otherwise the sign is going to be displayed in the sign column. +// +// @param count max number of signs +// @param[out] n_extrap number of characters from pp_extra to display +// @param sign_idxp Index of the displayed sign +static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs sattrs[], + int row, int startrow, int filler_lines, int filler_todo, + int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, + char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx, + int cul_attr) +{ + // Draw cells with the sign value or blank. + *c_extrap = ' '; + *c_finalp = NUL; + if (nrcol) { + *n_extrap = number_width(wp) + 1; + } else { + if (use_cursor_line_sign(wp, lnum)) { + *char_attrp = win_hl_attr(wp, HLF_CLS); + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + } + *n_extrap = win_signcol_width(wp); + } + + if (row == startrow + filler_lines && filler_todo <= 0) { + SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth); + if (sattr != NULL) { + *pp_extra = sattr->text; + if (*pp_extra != NULL) { + *c_extrap = NUL; + *c_finalp = NUL; + + if (nrcol) { + int n, width = number_width(wp) - 2; + for (n = 0; n < width; n++) { + extra[n] = ' '; + } + extra[n] = NUL; + STRCAT(extra, *pp_extra); + STRCAT(extra, " "); + *pp_extra = extra; + *n_extrap = (int)STRLEN(*pp_extra); + } else { + size_t symbol_blen = STRLEN(*pp_extra); + + // TODO(oni-link): Is sign text already extended to + // full cell width? + assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra))); + // symbol(s) bytes + (filling spaces) (one byte each) + *n_extrap = (int)symbol_blen + win_signcol_width(wp) - + (int)mb_string2cells((char *)(*pp_extra)); + + assert(extra_size > symbol_blen); + memset(extra, ' ', extra_size); + memcpy(extra, *pp_extra, symbol_blen); + + *pp_extra = extra; + (*pp_extra)[*n_extrap] = NUL; + } + } + + if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) { + *char_attrp = cul_attr; + } else { + *char_attrp = sattr->hl_attr_id; + } + } + } +} + +static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr, + int *num_attr, int *cul_attr) +{ + HlPriAttr line_attrs = { *line_attr, 0 }; + HlPriAttr num_attrs = { *num_attr, 0 }; + HlPriAttr cul_attrs = { *cul_attr, 0 }; + + // TODO(bfredl, vigoux): line_attr should not take priority over decoration! + int num_signs = buf_get_signattrs(buf, lnum, sattrs, &num_attrs, &line_attrs, &cul_attrs); + decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs, &num_attrs, &line_attrs, &cul_attrs); + + *line_attr = line_attrs.attr_id; + *num_attr = num_attrs.attr_id; + *cul_attr = cul_attrs.attr_id; + + return num_signs; +} + +/// Return true if CursorLineNr highlight is to be used for the number column. +/// +/// - 'cursorline' must be set +/// - lnum must be the cursor line +/// - 'cursorlineopt' has "number" +/// - don't highlight filler lines (when in diff mode) +/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number +/// itself on the first screenline of the wrapped line, otherwise highlight the number column of +/// all screenlines of the wrapped line. +static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR) + && (row == startrow + filler_lines + || (row > startrow + filler_lines + && (wp->w_p_culopt_flags & CULOPT_LINE))); +} + +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +{ + long num; + char *fmt = "%*ld "; + + if (wp->w_p_nu && !wp->w_p_rnu) { + // 'number' + 'norelativenumber' + num = (long)lnum; + } else { + // 'relativenumber', don't use negative numbers + num = labs((long)get_cursor_rel_lnum(wp, lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { + // 'number' + 'relativenumber' + num = lnum; + fmt = "%-*ld "; + } + } + + snprintf((char *)buf, buf_len, fmt, number_width(wp), num); +} + +static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +{ + if (wp->w_p_rnu) { + if (lnum < wp->w_cursor.lnum) { + // Use LineNrAbove + return win_hl_attr(wp, HLF_LNA); + } + if (lnum > wp->w_cursor.lnum) { + // Use LineNrBelow + return win_hl_attr(wp, HLF_LNB); + } + } + + if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + return win_hl_attr(wp, HLF_CLN); + } + + return win_hl_attr(wp, HLF_N); +} + +static void apply_cursorline_highlight(win_T *wp, linenr_T lnum, int *line_attr, int *cul_attr, + int *line_attr_lowprio) +{ + *cul_attr = win_hl_attr(wp, HLF_CUL); + HlAttrs ae = syn_attr2entry(*cul_attr); + // We make a compromise here (#7383): + // * low-priority CursorLine if fg is not set + // * high-priority ("same as Vim" priority) CursorLine if fg is set + if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { + *line_attr_lowprio = *cul_attr; + } else { + if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) + && qf_current_entry(wp) == lnum) { + *line_attr = hl_combine_attr(*cul_attr, *line_attr); + } else { + *line_attr = *cul_attr; + } + } +} + +/// Display line "lnum" of window 'wp' on the screen. +/// wp->w_virtcol needs to be valid. +/// +/// @param lnum line to display +/// @param startrow first row relative to window grid +/// @param endrow last grid row to be redrawn +/// @param nochange not updating for changed text +/// @param number_only only update the number column +/// @param foldinfo fold info for this line +/// @param[in, out] providers decoration providers active this line +/// items will be disables if they cause errors +/// or explicitly return `false`. +/// +/// @return the number of last row the line occupies. +int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only, + foldinfo_T foldinfo, DecorProviders *providers, char **provider_err) +{ + int c = 0; // init for GCC + long vcol = 0; // virtual column (for tabs) + long vcol_sbr = -1; // virtual column after showbreak + long vcol_prev = -1; // "vcol" of previous character + char_u *line; // current line + char_u *ptr; // current position in "line" + int row; // row in the window, excl w_winrow + ScreenGrid *grid = &wp->w_grid; // grid specific to the window + + char_u extra[57]; // sign, line number and 'fdc' must + // fit in here + int n_extra = 0; // number of extra chars + char_u *p_extra = NULL; // string of extra chars, plus NUL + char_u *p_extra_free = NULL; // p_extra needs to be freed + int c_extra = NUL; // extra chars, all the same + int c_final = NUL; // final char, mandatory if set + int extra_attr = 0; // attributes when n_extra != 0 + static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying + // curwin->w_p_lcs_chars.eol at + // end-of-line + int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used + int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used + bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; + + // saved "extra" items for when draw_state becomes WL_LINE (again) + int saved_n_extra = 0; + char_u *saved_p_extra = NULL; + int saved_c_extra = 0; + int saved_c_final = 0; + int saved_char_attr = 0; + + int n_attr = 0; // chars with special attr + int saved_attr2 = 0; // char_attr saved for n_attr + int n_attr3 = 0; // chars with overruling special attr + int saved_attr3 = 0; // char_attr saved for n_attr3 + + int n_skip = 0; // nr of chars to skip for 'nowrap' + + int fromcol = -10; // start of inverting + int tocol = MAXCOL; // end of inverting + int fromcol_prev = -2; // start of inverting after cursor + bool noinvcur = false; // don't invert the cursor + bool lnum_in_visual_area = false; + pos_T pos; + long v; + + int char_attr = 0; // attributes for next character + bool attr_pri = false; // char_attr has priority + bool area_highlighting = false; // Visual or incsearch highlighting in this line + int attr = 0; // attributes for area highlighting + int area_attr = 0; // attributes desired by highlighting + int search_attr = 0; // attributes desired by 'hlsearch' + int vcol_save_attr = 0; // saved attr for 'cursorcolumn' + int syntax_attr = 0; // attributes desired by syntax + bool has_syntax = false; // this buffer has syntax highl. + int save_did_emsg; + int eol_hl_off = 0; // 1 if highlighted char after EOL + bool draw_color_col = false; // highlight colorcolumn + int *color_cols = NULL; // pointer to according columns array + bool has_spell = false; // this buffer has spell checking +#define SPWORDLEN 150 + char_u nextline[SPWORDLEN * 2]; // text with start of the next line + int nextlinecol = 0; // column where nextline[] starts + int nextline_idx = 0; // index in nextline[] where next line + // starts + int spell_attr = 0; // attributes desired by spelling + int word_end = 0; // last byte with same spell_attr + static linenr_T checked_lnum = 0; // line number for "checked_col" + static int checked_col = 0; // column in "checked_lnum" up to which + // there are no spell errors + static int cap_col = -1; // column to check for Cap word + static linenr_T capcol_lnum = 0; // line number where "cap_col" + int cur_checked_col = 0; // checked column for current line + int extra_check = 0; // has syntax or linebreak + int multi_attr = 0; // attributes desired by multibyte + int mb_l = 1; // multi-byte byte length + int mb_c = 0; // decoded multi-byte character + bool mb_utf8 = false; // screen char is UTF-8 char + int u8cc[MAX_MCO]; // composing UTF-8 chars + int filler_lines; // nr of filler lines to be drawn + int filler_todo; // nr of filler lines still to do + 1 + hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting + int change_start = MAXCOL; // first col of changed area + int change_end = -1; // last col of changed area + colnr_T trailcol = MAXCOL; // start of trailing spaces + colnr_T leadcol = 0; // start of leading spaces + bool in_multispace = false; // in multiple consecutive spaces + int multispace_pos = 0; // position in lcs-multispace string + bool need_showbreak = false; // overlong line, skip first x chars + int line_attr = 0; // attribute for the whole line + int line_attr_save; + int line_attr_lowprio = 0; // low-priority attribute for the line + int line_attr_lowprio_save; + int prev_c = 0; // previous Arabic character + int prev_c1 = 0; // first composing char for prev_c + + bool search_attr_from_match = false; // if search_attr is from :match + bool has_decor = false; // this buffer has decoration + int win_col_offset = 0; // offset for window columns + + char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext + + bool area_active = false; + + int cul_attr = 0; // set when 'cursorline' active + // 'cursorlineopt' has "screenline" and cursor is in this line + bool cul_screenline = false; + // margin columns for the screen line, needed for when 'cursorlineopt' + // contains "screenline" + int left_curline_col = 0; + int right_curline_col = 0; + + // draw_state: items that are drawn in sequence: +#define WL_START 0 // nothing done yet +#define WL_CMDLINE (WL_START + 1) // cmdline window column +#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' +#define WL_SIGN (WL_FOLD + 1) // column for signs +#define WL_NR (WL_SIGN + 1) // line number +#define WL_BRI (WL_NR + 1) // 'breakindent' +#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' +#define WL_LINE (WL_SBR + 1) // text in the line + int draw_state = WL_START; // what to draw next + + int syntax_flags = 0; + int syntax_seqnr = 0; + int prev_syntax_id = 0; + int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); + bool is_concealing = false; + int boguscols = 0; ///< nonexistent columns added to + ///< force wrapping + int vcol_off = 0; ///< offset for concealed characters + int did_wcol = false; + int match_conc = 0; ///< cchar for match functions + int old_boguscols = 0; +#define VCOL_HLC (vcol - vcol_off) +#define FIX_FOR_BOGUSCOLS \ + { \ + n_extra += vcol_off; \ + vcol -= vcol_off; \ + vcol_off = 0; \ + col -= boguscols; \ + old_boguscols = boguscols; \ + boguscols = 0; \ + } + + if (startrow > endrow) { // past the end already! + return startrow; + } + + row = startrow; + + buf_T *buf = wp->w_buffer; + bool end_fill = (lnum == buf->b_ml.ml_line_count + 1); + + if (!number_only) { + // To speed up the loop below, set extra_check when there is linebreak, + // trailing white space and/or syntax processing to be done. + extra_check = wp->w_p_lbr; + if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow + && !has_fold && !end_fill) { + // Prepare for syntax highlighting in this line. When there is an + // error, stop syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = false; + syntax_start(wp, lnum); + if (did_emsg) { + wp->w_s->b_syn_error = true; + } else { + did_emsg = save_did_emsg; + if (!wp->w_s->b_syn_slow) { + has_syntax = true; + extra_check = true; + } + } + } + + has_decor = decor_redraw_line(buf, lnum - 1, &decor_state); + + providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err); + + if (*provider_err) { + provider_err_virt_text(lnum, *provider_err); + has_decor = true; + *provider_err = NULL; + } + + if (has_decor) { + extra_check = true; + } + + // Check for columns to display for 'colorcolumn'. + color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; + if (color_cols != NULL) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + if (wp->w_p_spell + && !has_fold + && !end_fill + && *wp->w_s->b_p_spl != NUL + && !GA_EMPTY(&wp->w_s->b_langp) + && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { + // Prepare for spell checking. + has_spell = true; + extra_check = true; + + // Get the start of the next line, so that words that wrap to the next + // line are found too: "et<line-break>al.". + // Trick: skip a few chars for C/shell/Vim comments + nextline[SPWORDLEN] = NUL; + if (lnum < wp->w_buffer->b_ml.ml_line_count) { + line = ml_get_buf(wp->w_buffer, lnum + 1, false); + spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); + } + + // When a word wrapped from the previous line the start of the current + // line is valid. + if (lnum == checked_lnum) { + cur_checked_col = checked_col; + } + checked_lnum = 0; + + // When there was a sentence end in the previous line may require a + // word starting with capital in this line. In line 1 always check + // the first word. + if (lnum != capcol_lnum) { + cap_col = -1; + } + if (lnum == 1) { + cap_col = 0; + } + capcol_lnum = 0; + } + + // handle Visual active in this window + if (VIsual_active && wp->w_buffer == curwin->w_buffer) { + pos_T *top, *bot; + + if (ltoreq(curwin->w_cursor, VIsual)) { + // Visual is after curwin->w_cursor + top = &curwin->w_cursor; + bot = &VIsual; + } else { + // Visual is before curwin->w_cursor + top = &VIsual; + bot = &curwin->w_cursor; + } + lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); + if (VIsual_mode == Ctrl_V) { + // block mode + if (lnum_in_visual_area) { + fromcol = wp->w_old_cursor_fcol; + tocol = wp->w_old_cursor_lcol; + } + } else { + // non-block mode + if (lnum > top->lnum && lnum <= bot->lnum) { + fromcol = 0; + } else if (lnum == top->lnum) { + if (VIsual_mode == 'V') { // linewise + fromcol = 0; + } else { + getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL); + if (gchar_pos(top) == NUL) { + tocol = fromcol + 1; + } + } + } + if (VIsual_mode != 'V' && lnum == bot->lnum) { + if (*p_sel == 'e' && bot->col == 0 + && bot->coladd == 0) { + fromcol = -10; + tocol = MAXCOL; + } else if (bot->col == MAXCOL) { + tocol = MAXCOL; + } else { + pos = *bot; + if (*p_sel == 'e') { + getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL); + } else { + getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol); + tocol++; + } + } + } + } + + // Check if the char under the cursor should be inverted (highlighted). + if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin + && cursor_is_block_during_visual(*p_sel == 'e')) { + noinvcur = true; + } + + // if inverting in this line set area_highlighting + if (fromcol >= 0) { + area_highlighting = true; + attr = win_hl_attr(wp, HLF_V); + } + // handle 'incsearch' and ":s///c" highlighting + } else if (highlight_match + && wp == curwin + && !has_fold + && lnum >= curwin->w_cursor.lnum + && lnum <= curwin->w_cursor.lnum + search_match_lines) { + if (lnum == curwin->w_cursor.lnum) { + getvcol(curwin, &(curwin->w_cursor), + (colnr_T *)&fromcol, NULL, NULL); + } else { + fromcol = 0; + } + if (lnum == curwin->w_cursor.lnum + search_match_lines) { + pos.lnum = lnum; + pos.col = search_match_endcol; + getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); + } + // do at least one character; happens when past end of line + if (fromcol == tocol && search_match_endcol) { + tocol = fromcol + 1; + } + area_highlighting = true; + attr = win_hl_attr(wp, HLF_I); + } + } + + int bg_attr = win_bg_attr(wp); + + filler_lines = diff_check(wp, lnum); + if (filler_lines < 0) { + if (filler_lines == -1) { + if (diff_find_change(wp, lnum, &change_start, &change_end)) { + diff_hlf = HLF_ADD; // added line + } else if (change_start == 0) { + diff_hlf = HLF_TXD; // changed text + } else { + diff_hlf = HLF_CHD; // changed line + } + } else { + diff_hlf = HLF_ADD; // added line + } + filler_lines = 0; + area_highlighting = true; + } + VirtLines virt_lines = KV_INITIAL_VALUE; + int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); + filler_lines += n_virt_lines; + if (lnum == wp->w_topline) { + filler_lines = wp->w_topfill; + n_virt_lines = MIN(n_virt_lines, filler_lines); + } + filler_todo = filler_lines; + + // Cursor line highlighting for 'cursorline' in the current window. + if (lnum == wp->w_cursor.lnum) { + // Do not show the cursor line in the text when Visual mode is active, + // because it's not clear what is selected then. + if (wp->w_p_cul && !(wp == curwin && VIsual_active) + && wp->w_p_culopt_flags != CULOPT_NBR) { + cul_screenline = (wp->w_p_wrap + && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); + if (!cul_screenline) { + apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio); + } else { + margin_columns_win(wp, &left_curline_col, &right_curline_col); + } + area_highlighting = true; + } + } + + SignTextAttrs sattrs[SIGN_SHOW_MAX]; // sign attributes for the sign column + int sign_num_attr = 0; // sign attribute for the number column + int sign_cul_attr = 0; // sign attribute for cursorline + CLEAR_FIELD(sattrs); + int num_signs = get_sign_attrs(buf, lnum, sattrs, &line_attr, &sign_num_attr, &sign_cul_attr); + + // Highlight the current line in the quickfix window. + if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { + line_attr = win_hl_attr(wp, HLF_QFL); + } + + if (line_attr_lowprio || line_attr) { + area_highlighting = true; + } + + if (cul_screenline) { + line_attr_save = line_attr; + line_attr_lowprio_save = line_attr_lowprio; + } + + line = end_fill ? (char_u *)"" : ml_get_buf(wp->w_buffer, lnum, false); + ptr = line; + + if (has_spell && !number_only) { + // For checking first word with a capital skip white space. + if (cap_col == 0) { + cap_col = (int)getwhitecols(line); + } + + // To be able to spell-check over line boundaries copy the end of the + // current line into nextline[]. Above the start of the next line was + // copied to nextline[SPWORDLEN]. + if (nextline[SPWORDLEN] == NUL) { + // No next line or it is empty. + nextlinecol = MAXCOL; + nextline_idx = 0; + } else { + v = (long)STRLEN(line); + if (v < SPWORDLEN) { + // Short line, use it completely and append the start of the + // next line. + nextlinecol = 0; + memmove(nextline, line, (size_t)v); + STRMOVE(nextline + v, nextline + SPWORDLEN); + nextline_idx = (int)v + 1; + } else { + // Long line, use only the last SPWORDLEN bytes. + nextlinecol = (int)v - SPWORDLEN; + memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 + nextline_idx = SPWORDLEN + 1; + } + } + } + + if (wp->w_p_list && !has_fold && !end_fill) { + if (wp->w_p_lcs_chars.space + || wp->w_p_lcs_chars.multispace != NULL + || wp->w_p_lcs_chars.leadmultispace != NULL + || wp->w_p_lcs_chars.trail + || wp->w_p_lcs_chars.lead + || wp->w_p_lcs_chars.nbsp) { + extra_check = true; + } + // find start of trailing whitespace + if (wp->w_p_lcs_chars.trail) { + trailcol = (colnr_T)STRLEN(ptr); + while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { + trailcol--; + } + trailcol += (colnr_T)(ptr - line); + } + // find end of leading whitespace + if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { + leadcol = 0; + while (ascii_iswhite(ptr[leadcol])) { + leadcol++; + } + if (ptr[leadcol] == NUL) { + // in a line full of spaces all of them are treated as trailing + leadcol = (colnr_T)0; + } else { + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line) + 1; + } + } + } + + // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the + // first character to be displayed. + if (wp->w_p_wrap) { + v = wp->w_skipcol; + } else { + v = wp->w_leftcol; + } + if (v > 0 && !number_only) { + char_u *prev_ptr = ptr; + while (vcol < v && *ptr != NUL) { + c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); + vcol += c; + prev_ptr = ptr; + MB_PTR_ADV(ptr); + } + + // When: + // - 'cuc' is set, or + // - 'colorcolumn' is set, or + // - 'virtualedit' is set, or + // - the visual mode is active, + // the end of the line may be before the start of the displayed part. + if (vcol < v && (wp->w_p_cuc + || draw_color_col + || virtual_active() + || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { + vcol = v; + } + + // Handle a character that's not completely on the screen: Put ptr at + // that character but skip the first few screen characters. + if (vcol > v) { + vcol -= c; + ptr = prev_ptr; + // If the character fits on the screen, don't need to skip it. + // Except for a TAB. + if (utf_ptr2cells((char *)ptr) >= c || *ptr == TAB) { + n_skip = (int)(v - vcol); + } + } + + // Adjust for when the inverted text is before the screen, + // and when the start of the inverted text is before the screen. + if (tocol <= vcol) { + fromcol = 0; + } else if (fromcol >= 0 && fromcol < vcol) { + fromcol = (int)vcol; + } + + // When w_skipcol is non-zero, first line needs 'showbreak' + if (wp->w_p_wrap) { + need_showbreak = true; + } + // When spell checking a word we need to figure out the start of the + // word and if it's badly spelled or not. + if (has_spell) { + size_t len; + colnr_T linecol = (colnr_T)(ptr - line); + hlf_T spell_hlf = HLF_COUNT; + + pos = wp->w_cursor; + wp->w_cursor.lnum = lnum; + wp->w_cursor.col = linecol; + len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); + + // spell_move_to() may call ml_get() and make "line" invalid + line = ml_get_buf(wp->w_buffer, lnum, false); + ptr = line + linecol; + + if (len == 0 || (int)wp->w_cursor.col > ptr - line) { + // no bad word found at line start, don't check until end of a + // word + spell_hlf = HLF_COUNT; + word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); + } else { + // bad word found, use attributes until end of word + assert(len <= INT_MAX); + word_end = wp->w_cursor.col + (int)len + 1; + + // Turn index into actual attributes. + if (spell_hlf != HLF_COUNT) { + spell_attr = highlight_attr[spell_hlf]; + } + } + wp->w_cursor = pos; + + // Need to restart syntax highlighting for this line. + if (has_syntax) { + syntax_start(wp, lnum); + } + } + } + + // Correct highlighting for cursor that can't be disabled. + // Avoids having to check this for each character. + if (fromcol >= 0) { + if (noinvcur) { + if ((colnr_T)fromcol == wp->w_virtcol) { + // highlighting starts at cursor, let it start just after the + // cursor + fromcol_prev = fromcol; + fromcol = -1; + } else if ((colnr_T)fromcol < wp->w_virtcol) { + // restart highlighting after the cursor + fromcol_prev = wp->w_virtcol; + } + } + if (fromcol >= tocol) { + fromcol = -1; + } + } + + if (!number_only && !has_fold && !end_fill) { + v = ptr - line; + area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, + &line, &screen_search_hl, &search_attr, + &search_attr_from_match); + ptr = line + v; // "line" may have been updated + } + + int off = 0; // Offset relative start of line + int col = 0; // Visual column on screen. + if (wp->w_p_rl) { + // Rightleft window: process the text in the normal direction, but put + // it in linebuf_char[off] from right to left. Start at the + // rightmost column of the window. + col = grid->cols - 1; + off += col; + } + + // won't highlight after TERM_ATTRS_MAX columns + int term_attrs[TERM_ATTRS_MAX] = { 0 }; + if (wp->w_buffer->terminal) { + terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs); + extra_check = true; + } + + int sign_idx = 0; + // Repeat for the whole displayed line. + for (;;) { + int has_match_conc = 0; ///< match wants to conceal + int decor_conceal = 0; + + bool did_decrement_ptr = false; + + // Skip this quickly when working on the text. + if (draw_state != WL_LINE) { + if (cul_screenline) { + cul_attr = 0; + line_attr = line_attr_save; + line_attr_lowprio = line_attr_lowprio_save; + } + + if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { + draw_state = WL_CMDLINE; + if (cmdwin_type != 0 && wp == curwin) { + // Draw the cmdline character. + n_extra = 1; + c_extra = cmdwin_type; + c_final = NUL; + char_attr = win_hl_attr(wp, HLF_AT); + } + } + + if (draw_state == WL_FOLD - 1 && n_extra == 0) { + int fdc = compute_foldcolumn(wp, 0); + + draw_state = WL_FOLD; + if (fdc > 0) { + // Draw the 'foldcolumn'. Allocate a buffer, "extra" may + // already be in use. + xfree(p_extra_free); + p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1); + n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); + p_extra_free[n_extra] = NUL; + p_extra = p_extra_free; + c_extra = NUL; + c_final = NUL; + if (use_cursor_line_sign(wp, lnum)) { + char_attr = win_hl_attr(wp, HLF_CLF); + } else { + char_attr = win_hl_attr(wp, HLF_FC); + } + } + } + + // sign column, this is hit until sign_idx reaches count + if (draw_state == WL_SIGN - 1 && n_extra == 0) { + draw_state = WL_SIGN; + // Show the sign column when there are any signs in this buffer + if (wp->w_scwidth > 0) { + get_sign_display_info(false, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, &char_attr, sign_idx, + sign_cul_attr); + sign_idx++; + if (sign_idx < wp->w_scwidth) { + draw_state = WL_SIGN - 1; + } else { + sign_idx = 0; + } + } + } + + if (draw_state == WL_NR - 1 && n_extra == 0) { + draw_state = WL_NR; + // Display the absolute or relative line number. After the + // first fill with blanks when the 'n' flag isn't in 'cpo' + if ((wp->w_p_nu || wp->w_p_rnu) + && (row == startrow + filler_lines + || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { + // If 'signcolumn' is set to 'number' and a sign is present + // in 'lnum', then display the sign instead of the line + // number. + if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) { + get_sign_display_info(true, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, &char_attr, sign_idx, + sign_cul_attr); + } else { + // Draw the line number (empty space after wrapping). + if (row == startrow + filler_lines) { + get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra)); + if (wp->w_skipcol > 0) { + for (p_extra = extra; *p_extra == ' '; p_extra++) { + *p_extra = '-'; + } + } + if (wp->w_p_rl) { // reverse line numbers + // like rl_mirror(), but keep the space at the end + char_u *p2 = (char_u *)skipwhite((char *)extra); + p2 = skiptowhite(p2) - 1; + for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) { + const char_u t = *p1; + *p1 = *p2; + *p2 = t; + } + } + p_extra = extra; + c_extra = NUL; + } else { + c_extra = ' '; + } + c_final = NUL; + n_extra = number_width(wp) + 1; + if (sign_num_attr > 0) { + char_attr = sign_num_attr; + } else { + char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines); + } + } + } + } + + if (draw_state == WL_NR && n_extra == 0) { + win_col_offset = off; + } + + if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 + && n_extra == 0 && *get_showbreak_value(wp) != NUL) { + // draw indent after showbreak value + draw_state = WL_BRI; + } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) { + // after the showbreak, draw the breakindent + draw_state = WL_BRI - 1; + } + + // draw 'breakindent': indent wrapped text accordingly + if (draw_state == WL_BRI - 1 && n_extra == 0) { + draw_state = WL_BRI; + // if need_showbreak is set, breakindent also applies + if (wp->w_p_bri && (row != startrow || need_showbreak) + && filler_lines == 0) { + char_attr = 0; + + if (diff_hlf != (hlf_T)0) { + char_attr = win_hl_attr(wp, (int)diff_hlf); + } + p_extra = NULL; + c_extra = ' '; + c_final = NUL; + n_extra = + get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); + if (row == startrow) { + n_extra -= win_col_off2(wp); + if (n_extra < 0) { + n_extra = 0; + } + } + if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { + need_showbreak = false; + } + // Correct end of highlighted area for 'breakindent', + // required wen 'linebreak' is also set. + if (tocol == vcol) { + tocol += n_extra; + } + } + } + + if (draw_state == WL_SBR - 1 && n_extra == 0) { + draw_state = WL_SBR; + if (filler_todo > filler_lines - n_virt_lines) { + // TODO(bfredl): check this doesn't inhibit TUI-style + // clear-to-end-of-line. + c_extra = ' '; + c_final = NUL; + if (wp->w_p_rl) { + n_extra = col + 1; + } else { + n_extra = grid->cols - col; + } + char_attr = 0; + } else if (filler_todo > 0) { + // draw "deleted" diff line(s) + if (char2cells(wp->w_p_fcs_chars.diff) > 1) { + c_extra = '-'; + c_final = NUL; + } else { + c_extra = wp->w_p_fcs_chars.diff; + c_final = NUL; + } + if (wp->w_p_rl) { + n_extra = col + 1; + } else { + n_extra = grid->cols - col; + } + char_attr = win_hl_attr(wp, HLF_DED); + } + char_u *const sbr = get_showbreak_value(wp); + if (*sbr != NUL && need_showbreak) { + // Draw 'showbreak' at the start of each broken line. + p_extra = sbr; + c_extra = NUL; + c_final = NUL; + n_extra = (int)STRLEN(sbr); + char_attr = win_hl_attr(wp, HLF_AT); + if (wp->w_skipcol == 0 || !wp->w_p_wrap) { + need_showbreak = false; + } + vcol_sbr = vcol + mb_charlen(sbr); + // Correct end of highlighted area for 'showbreak', + // required when 'linebreak' is also set. + if (tocol == vcol) { + tocol += n_extra; + } + // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. + if (cul_attr) { + char_attr = hl_combine_attr(cul_attr, char_attr); + } + } + } + + if (draw_state == WL_LINE - 1 && n_extra == 0) { + sign_idx = 0; + draw_state = WL_LINE; + + if (has_decor && row == startrow + filler_lines) { + // hide virt_text on text hidden by 'nowrap' + decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state); + } + + if (saved_n_extra) { + // Continue item from end of wrapped line. + n_extra = saved_n_extra; + c_extra = saved_c_extra; + c_final = saved_c_final; + p_extra = saved_p_extra; + char_attr = saved_char_attr; + } else { + char_attr = 0; + } + } + } + + if (cul_screenline && draw_state == WL_LINE + && vcol >= left_curline_col + && vcol < right_curline_col) { + apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio); + } + + // When still displaying '$' of change command, stop at cursor + if (((dollar_vcol >= 0 + && wp == curwin + && lnum == wp->w_cursor.lnum + && vcol >= (long)wp->w_virtcol) + || (number_only && draw_state > WL_NR)) + && filler_todo <= 0) { + draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); + grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); + // Pretend we have finished updating the window. Except when + // 'cursorcolumn' is set. + if (wp->w_p_cuc) { + row = wp->w_cline_row + wp->w_cline_height; + } else { + row = grid->rows; + } + break; + } + + if (draw_state == WL_LINE + && has_fold + && col == win_col_offset + && n_extra == 0 + && row == startrow) { + char_attr = win_hl_attr(wp, HLF_FL); + + linenr_T lnume = lnum + foldinfo.fi_lines - 1; + memset(buf_fold, ' ', FOLD_TEXT_LEN); + p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold); + n_extra = (int)STRLEN(p_extra); + + if (p_extra != buf_fold) { + xfree(p_extra_free); + p_extra_free = p_extra; + } + c_extra = NUL; + c_final = NUL; + p_extra[n_extra] = NUL; + } + + if (draw_state == WL_LINE + && has_fold + && col < grid->cols + && n_extra == 0 + && row == startrow) { + // fill rest of line with 'fold' + c_extra = wp->w_p_fcs_chars.fold; + c_final = NUL; + + n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col); + } + + if (draw_state == WL_LINE + && has_fold + && col >= grid->cols + && n_extra != 0 + && row == startrow) { + // Truncate the folding. + n_extra = 0; + } + + if (draw_state == WL_LINE && (area_highlighting || has_spell)) { + // handle Visual or match highlighting in this line + if (vcol == fromcol + || (vcol + 1 == fromcol && n_extra == 0 + && utf_ptr2cells((char *)ptr) > 1) + || ((int)vcol_prev == fromcol_prev + && vcol_prev < vcol // not at margin + && vcol < tocol)) { + area_attr = attr; // start highlighting + if (area_highlighting) { + area_active = true; + } + } else if (area_attr != 0 && (vcol == tocol + || (noinvcur + && (colnr_T)vcol == wp->w_virtcol))) { + area_attr = 0; // stop highlighting + area_active = false; + } + + if (!n_extra) { + // Check for start/end of 'hlsearch' and other matches. + // After end, check for start/end of next match. + // When another match, have to check for start again. + v = (ptr - line); + search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &screen_search_hl, + &has_match_conc, + &match_conc, lcs_eol_one, &search_attr_from_match); + ptr = line + v; // "line" may have been changed + + // Do not allow a conceal over EOL otherwise EOL will be missed + // and bad things happen. + if (*ptr == NUL) { + has_match_conc = 0; + } + } + + if (diff_hlf != (hlf_T)0) { + if (diff_hlf == HLF_CHD && ptr - line >= change_start + && n_extra == 0) { + diff_hlf = HLF_TXD; // changed text + } + if (diff_hlf == HLF_TXD && ptr - line > change_end + && n_extra == 0) { + diff_hlf = HLF_CHD; // changed line + } + line_attr = win_hl_attr(wp, (int)diff_hlf); + // Overlay CursorLine onto diff-mode highlight. + if (cul_attr) { + line_attr = 0 != line_attr_lowprio // Low-priority CursorLine + ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr), + hl_get_underline()) + : hl_combine_attr(line_attr, cul_attr); + } + } + + // Decide which of the highlight attributes to use. + attr_pri = true; + + if (area_attr != 0) { + char_attr = hl_combine_attr(line_attr, area_attr); + if (!highlight_match) { + // let search highlight show in Visual area if possible + char_attr = hl_combine_attr(search_attr, char_attr); + } + } else if (search_attr != 0) { + char_attr = hl_combine_attr(line_attr, search_attr); + } else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL) + || vcol < fromcol || vcol_prev < fromcol_prev + || vcol >= tocol)) { + // Use line_attr when not in the Visual or 'incsearch' area + // (area_attr may be 0 when "noinvcur" is set). + char_attr = line_attr; + } else { + attr_pri = false; + if (has_syntax) { + char_attr = syntax_attr; + } else { + char_attr = 0; + } + } + } + + // Get the next character to put on the screen. + // + // The "p_extra" points to the extra stuff that is inserted to + // represent special characters (non-printable stuff) and other + // things. When all characters are the same, c_extra is used. + // If c_final is set, it will compulsorily be used at the end. + // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past + // "p_extra[n_extra]". + // For the '$' of the 'list' option, n_extra == 1, p_extra == "". + if (n_extra > 0) { + if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { + c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; + mb_c = c; // doesn't handle non-utf-8 multi-byte! + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; + } + } else { + assert(p_extra != NULL); + c = *p_extra; + mb_c = c; + // If the UTF-8 character is more than one byte: + // Decode it into "mb_c". + mb_l = utfc_ptr2len((char *)p_extra); + mb_utf8 = false; + if (mb_l > n_extra) { + mb_l = 1; + } else if (mb_l > 1) { + mb_c = utfc_ptr2char(p_extra, u8cc); + mb_utf8 = true; + c = 0xc0; + } + if (mb_l == 0) { // at the NUL at end-of-line + mb_l = 1; + } + + // If a double-width char doesn't fit display a '>' in the last column. + if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1)) + && utf_char2cells(mb_c) == 2) { + c = '>'; + mb_c = c; + mb_l = 1; + (void)mb_l; + multi_attr = win_hl_attr(wp, HLF_AT); + + if (cul_attr) { + multi_attr = 0 != line_attr_lowprio + ? hl_combine_attr(cul_attr, multi_attr) + : hl_combine_attr(multi_attr, cul_attr); + } + + // put the pointer back to output the double-width + // character at the start of the next line. + n_extra++; + p_extra--; + } else { + n_extra -= mb_l - 1; + p_extra += mb_l - 1; + } + p_extra++; + } + n_extra--; + } else if (foldinfo.fi_lines > 0) { + // skip writing the buffer line itself + c = NUL; + XFREE_CLEAR(p_extra_free); + } else { + int c0; + + XFREE_CLEAR(p_extra_free); + + // Get a character from the line itself. + c0 = c = *ptr; + mb_c = c; + // If the UTF-8 character is more than one byte: Decode it + // into "mb_c". + mb_l = utfc_ptr2len((char *)ptr); + mb_utf8 = false; + if (mb_l > 1) { + mb_c = utfc_ptr2char(ptr, u8cc); + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; + + // At start of the line we can have a composing char. + // Draw it as a space with a composing char. + if (utf_iscomposing(mb_c)) { + int i; + + for (i = MAX_MCO - 1; i > 0; i--) { + u8cc[i] = u8cc[i - 1]; + } + u8cc[0] = mb_c; + mb_c = ' '; + } + } + + if ((mb_l == 1 && c >= 0x80) + || (mb_l >= 1 && mb_c == 0) + || (mb_l > 1 && (!vim_isprintc(mb_c)))) { + // Illegal UTF-8 byte: display as <xx>. + // Non-BMP character : display as ? or fullwidth ?. + transchar_hex((char *)extra, mb_c); + if (wp->w_p_rl) { // reverse + rl_mirror(extra); + } + + p_extra = extra; + c = *p_extra; + mb_c = mb_ptr2char_adv((const char_u **)&p_extra); + mb_utf8 = (c >= 0x80); + n_extra = (int)STRLEN(p_extra); + c_extra = NUL; + c_final = NUL; + if (area_attr == 0 && search_attr == 0) { + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_8); + saved_attr2 = char_attr; // save current attr + } + } else if (mb_l == 0) { // at the NUL at end-of-line + mb_l = 1; + } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) { + // Do Arabic shaping. + int pc, pc1, nc; + int pcc[MAX_MCO]; + + // The idea of what is the previous and next + // character depends on 'rightleft'. + if (wp->w_p_rl) { + pc = prev_c; + pc1 = prev_c1; + nc = utf_ptr2char((char *)ptr + mb_l); + prev_c1 = u8cc[0]; + } else { + pc = utfc_ptr2char(ptr + mb_l, pcc); + nc = prev_c; + pc1 = pcc[0]; + } + prev_c = mb_c; + + mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); + } else { + prev_c = mb_c; + } + // If a double-width char doesn't fit display a '>' in the + // last column; the character is displayed at the start of the + // next line. + if ((wp->w_p_rl ? (col <= 0) : + (col >= grid->cols - 1)) + && utf_char2cells(mb_c) == 2) { + c = '>'; + mb_c = c; + mb_utf8 = false; + mb_l = 1; + multi_attr = win_hl_attr(wp, HLF_AT); + // Put pointer back so that the character will be + // displayed at the start of the next line. + ptr--; + did_decrement_ptr = true; + } else if (*ptr != NUL) { + ptr += mb_l - 1; + } + + // If a double-width char doesn't fit at the left side display a '<' in + // the first column. Don't do this for unprintable characters. + if (n_skip > 0 && mb_l > 1 && n_extra == 0) { + n_extra = 1; + c_extra = MB_FILLER_CHAR; + c_final = NUL; + c = ' '; + if (area_attr == 0 && search_attr == 0) { + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_AT); + saved_attr2 = char_attr; // save current attr + } + mb_c = c; + mb_utf8 = false; + mb_l = 1; + } + ptr++; + + if (extra_check) { + bool can_spell = true; + + // Get syntax attribute, unless still at the start of the line + // (double-wide char that doesn't fit). + v = (ptr - line); + if (has_syntax && v > 0) { + // Get the syntax attribute for the character. If there + // is an error, disable syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = false; + + syntax_attr = get_syntax_attr((colnr_T)v - 1, + has_spell ? &can_spell : NULL, false); + + if (did_emsg) { + wp->w_s->b_syn_error = true; + has_syntax = false; + } else { + did_emsg = save_did_emsg; + } + + if (wp->w_s->b_syn_slow) { + has_syntax = false; + } + + // Need to get the line again, a multi-line regexp may + // have made it invalid. + line = ml_get_buf(wp->w_buffer, lnum, false); + ptr = line + v; + + if (!attr_pri) { + if (cul_attr) { + char_attr = 0 != line_attr_lowprio + ? hl_combine_attr(cul_attr, syntax_attr) + : hl_combine_attr(syntax_attr, cul_attr); + } else { + char_attr = syntax_attr; + } + } else { + char_attr = hl_combine_attr(syntax_attr, char_attr); + } + // no concealing past the end of the line, it interferes + // with line highlighting. + if (c == NUL) { + syntax_flags = 0; + } else { + syntax_flags = get_syntax_info(&syntax_seqnr); + } + } else if (!attr_pri) { + char_attr = 0; + } + + // Check spelling (unless at the end of the line). + // Only do this when there is no syntax highlighting, the + // @Spell cluster is not used or the current syntax item + // contains the @Spell cluster. + v = (ptr - line); + if (has_spell && v >= word_end && v > cur_checked_col) { + spell_attr = 0; + if (!attr_pri) { + char_attr = syntax_attr; + } + if (c != 0 && (!has_syntax || can_spell)) { + char_u *prev_ptr; + char_u *p; + int len; + hlf_T spell_hlf = HLF_COUNT; + prev_ptr = ptr - mb_l; + v -= mb_l - 1; + + // Use nextline[] if possible, it has the start of the + // next line concatenated. + if ((prev_ptr - line) - nextlinecol >= 0) { + p = nextline + ((prev_ptr - line) - nextlinecol); + } else { + p = prev_ptr; + } + cap_col -= (int)(prev_ptr - line); + size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); + assert(tmplen <= INT_MAX); + len = (int)tmplen; + word_end = (int)v + len; + + // In Insert mode only highlight a word that + // doesn't touch the cursor. + if (spell_hlf != HLF_COUNT + && (State & MODE_INSERT) + && wp->w_cursor.lnum == lnum + && wp->w_cursor.col >= + (colnr_T)(prev_ptr - line) + && wp->w_cursor.col < (colnr_T)word_end) { + spell_hlf = HLF_COUNT; + spell_redraw_lnum = lnum; + } + + if (spell_hlf == HLF_COUNT && p != prev_ptr + && (p - nextline) + len > nextline_idx) { + // Remember that the good word continues at the + // start of the next line. + checked_lnum = lnum + 1; + checked_col = (int)((p - nextline) + len - nextline_idx); + } + + // Turn index into actual attributes. + if (spell_hlf != HLF_COUNT) { + spell_attr = highlight_attr[spell_hlf]; + } + + if (cap_col > 0) { + if (p != prev_ptr + && (p - nextline) + cap_col >= nextline_idx) { + // Remember that the word in the next line + // must start with a capital. + capcol_lnum = lnum + 1; + cap_col = (int)((p - nextline) + cap_col + - nextline_idx); + } else { + // Compute the actual column. + cap_col += (int)(prev_ptr - line); + } + } + } + } + if (spell_attr != 0) { + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, spell_attr); + } else { + char_attr = hl_combine_attr(spell_attr, char_attr); + } + } + + if (wp->w_buffer->terminal) { + char_attr = hl_combine_attr(term_attrs[vcol], char_attr); + } + + if (has_decor && v > 0) { + bool selected = (area_active || (area_highlighting && noinvcur + && (colnr_T)vcol == wp->w_virtcol)); + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off, + selected, &decor_state); + if (extmark_attr != 0) { + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, extmark_attr); + } else { + char_attr = hl_combine_attr(extmark_attr, char_attr); + } + } + + decor_conceal = decor_state.conceal; + if (decor_conceal && decor_state.conceal_char) { + decor_conceal = 2; // really?? + } + } + + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) + && !vim_isbreak((int)(*ptr))) { + int mb_off = utf_head_off(line, ptr - 1); + char_u *p = ptr - (mb_off + 1); + // TODO(neovim): is passing p for start of the line OK? + n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; + + // We have just drawn the showbreak value, no need to add + // space for it again. + if (vcol == vcol_sbr) { + n_extra -= mb_charlen(get_showbreak_value(wp)); + if (n_extra < 0) { + n_extra = 0; + } + } + + if (c == TAB && n_extra + col > grid->cols) { + n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; + } + c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; + c_final = NUL; + if (ascii_iswhite(c)) { + if (c == TAB) { + // See "Tab alignment" below. + FIX_FOR_BOGUSCOLS; + } + if (!wp->w_p_list) { + c = ' '; + } + } + } + + in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); + if (!in_multispace) { + multispace_pos = 0; + } + + // 'list': Change char 160 to 'nbsp' and space to 'space'. + // But not when the character is followed by a composing + // character (use mb_l to check that). + if (wp->w_p_list + && ((((c == 160 && mb_l == 1) + || (mb_utf8 + && ((mb_c == 160 && mb_l == 2) + || (mb_c == 0x202f && mb_l == 3)))) + && wp->w_p_lcs_chars.nbsp) + || (c == ' ' + && mb_l == 1 + && (wp->w_p_lcs_chars.space + || (in_multispace && wp->w_p_lcs_chars.multispace != NULL)) + && ptr - line >= leadcol + && ptr - line <= trailcol))) { + if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) { + c = wp->w_p_lcs_chars.multispace[multispace_pos++]; + if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } else { + c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; + } + n_attr = 1; + extra_attr = win_hl_attr(wp, HLF_0); + saved_attr2 = char_attr; // save current attr + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; + } + } + + if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol) + || (leadcol != 0 && ptr < line + leadcol))) { + if (leadcol != 0 && in_multispace && ptr < line + leadcol + && wp->w_p_lcs_chars.leadmultispace != NULL) { + c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++]; + if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) { + c = wp->w_p_lcs_chars.trail; + } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) { + c = wp->w_p_lcs_chars.lead; + } else if (leadcol != 0 && wp->w_p_lcs_chars.space) { + c = wp->w_p_lcs_chars.space; + } + + n_attr = 1; + extra_attr = win_hl_attr(wp, HLF_0); + saved_attr2 = char_attr; // save current attr + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; + } + } + } + + // Handling of non-printable characters. + if (!vim_isprintc(c)) { + // when getting a character from the file, we may have to + // turn it into something else on the way to putting it on the screen. + if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { + int tab_len = 0; + long vcol_adjusted = vcol; // removed showbreak length + char_u *const sbr = get_showbreak_value(wp); + + // Only adjust the tab_len, when at the first column after the + // showbreak value was drawn. + if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { + vcol_adjusted = vcol - mb_charlen(sbr); + } + // tab amount depends on current column + tab_len = tabstop_padding((colnr_T)vcol_adjusted, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; + + if (!wp->w_p_lbr || !wp->w_p_list) { + n_extra = tab_len; + } else { + char_u *p; + int i; + int saved_nextra = n_extra; + + if (vcol_off > 0) { + // there are characters to conceal + tab_len += vcol_off; + } + // boguscols before FIX_FOR_BOGUSCOLS macro from above. + if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0 + && n_extra > tab_len) { + tab_len += n_extra - tab_len; + } + + // If n_extra > 0, it gives the number of chars + // to use for a tab, else we need to calculate the width + // for a tab. + int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); + if (wp->w_p_lcs_chars.tab3) { + len += utf_char2len(wp->w_p_lcs_chars.tab3); + } + if (n_extra > 0) { + len += n_extra - tab_len; + } + c = wp->w_p_lcs_chars.tab1; + p = xmalloc((size_t)len + 1); + memset(p, ' ', (size_t)len); + p[len] = NUL; + xfree(p_extra_free); + p_extra_free = p; + for (i = 0; i < tab_len; i++) { + if (*p == NUL) { + tab_len = i; + break; + } + int lcs = wp->w_p_lcs_chars.tab2; + + // if tab3 is given, use it for the last char + if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { + lcs = wp->w_p_lcs_chars.tab3; + } + p += utf_char2bytes(lcs, (char *)p); + n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); + } + p_extra = p_extra_free; + + // n_extra will be increased by FIX_FOX_BOGUSCOLS + // macro below, so need to adjust for that here + if (vcol_off > 0) { + n_extra -= vcol_off; + } + } + + { + int vc_saved = vcol_off; + + // Tab alignment should be identical regardless of + // 'conceallevel' value. So tab compensates of all + // previous concealed characters, and thus resets + // vcol_off and boguscols accumulated so far in the + // line. Note that the tab can be longer than + // 'tabstop' when there are concealed characters. + FIX_FOR_BOGUSCOLS; + + // Make sure, the highlighting for the tab char will be + // correctly set further below (effectively reverts the + // FIX_FOR_BOGSUCOLS macro). + if (n_extra == tab_len + vc_saved && wp->w_p_list + && wp->w_p_lcs_chars.tab1) { + tab_len += vc_saved; + } + } + + mb_utf8 = false; // don't draw as UTF-8 + if (wp->w_p_list) { + c = (n_extra == 0 && wp->w_p_lcs_chars.tab3) + ? wp->w_p_lcs_chars.tab3 + : wp->w_p_lcs_chars.tab1; + if (wp->w_p_lbr) { + c_extra = NUL; // using p_extra from above + } else { + c_extra = wp->w_p_lcs_chars.tab2; + } + c_final = wp->w_p_lcs_chars.tab3; + n_attr = tab_len + 1; + extra_attr = win_hl_attr(wp, HLF_0); + saved_attr2 = char_attr; // save current attr + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } + } else { + c_final = NUL; + c_extra = ' '; + c = ' '; + } + } else if (c == NUL + && (wp->w_p_list + || ((fromcol >= 0 || fromcol_prev >= 0) + && tocol > vcol + && VIsual_mode != Ctrl_V + && (wp->w_p_rl ? (col >= 0) : (col < grid->cols)) + && !(noinvcur + && lnum == wp->w_cursor.lnum + && (colnr_T)vcol == wp->w_virtcol))) + && lcs_eol_one > 0) { + // Display a '$' after the line or highlight an extra + // character if the line break is included. + // For a diff line the highlighting continues after the "$". + if (diff_hlf == (hlf_T)0 + && line_attr == 0 + && line_attr_lowprio == 0) { + // In virtualedit, visual selections may extend beyond end of line + if (area_highlighting && virtual_active() + && tocol != MAXCOL && vcol < tocol) { + n_extra = 0; + } else { + p_extra = at_end_str; + n_extra = 1; + c_extra = NUL; + c_final = NUL; + } + } + if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { + c = wp->w_p_lcs_chars.eol; + } else { + c = ' '; + } + lcs_eol_one = -1; + ptr--; // put it back at the NUL + extra_attr = win_hl_attr(wp, HLF_AT); + n_attr = 1; + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; // don't draw as UTF-8 + } + } else if (c != NUL) { + p_extra = transchar_buf(wp->w_buffer, c); + if (n_extra == 0) { + n_extra = byte2cells(c) - 1; + } + if ((dy_flags & DY_UHEX) && wp->w_p_rl) { + rl_mirror(p_extra); // reverse "<12>" + } + c_extra = NUL; + c_final = NUL; + if (wp->w_p_lbr) { + char_u *p; + + c = *p_extra; + p = xmalloc((size_t)n_extra + 1); + memset(p, ' ', (size_t)n_extra); + STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); // NOLINT(runtime/printf) + p[n_extra] = NUL; + xfree(p_extra_free); + p_extra_free = p_extra = p; + } else { + n_extra = byte2cells(c) - 1; + c = *p_extra++; + } + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_8); + saved_attr2 = char_attr; // save current attr + mb_utf8 = false; // don't draw as UTF-8 + } else if (VIsual_active + && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') + && virtual_active() + && tocol != MAXCOL + && vcol < tocol + && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) { + c = ' '; + ptr--; // put it back at the NUL + } + } + + if (wp->w_p_cole > 0 + && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) + && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) + && !(lnum_in_visual_area && vim_strchr((char *)wp->w_p_cocu, 'v') == NULL)) { + char_attr = conceal_attr; + if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0) + || has_match_conc > 1 || decor_conceal > 1) + && (syn_get_sub_char() != NUL + || (has_match_conc && match_conc) + || (decor_conceal && decor_state.conceal_char) + || wp->w_p_cole == 1) + && wp->w_p_cole != 3) { + // First time at this concealed item: display one + // character. + if (has_match_conc && match_conc) { + c = match_conc; + } else if (decor_conceal && decor_state.conceal_char) { + c = decor_state.conceal_char; + if (decor_state.conceal_attr) { + char_attr = decor_state.conceal_attr; + } + } else if (syn_get_sub_char() != NUL) { + c = syn_get_sub_char(); + } else if (wp->w_p_lcs_chars.conceal != NUL) { + c = wp->w_p_lcs_chars.conceal; + } else { + c = ' '; + } + + prev_syntax_id = syntax_seqnr; + + if (n_extra > 0) { + vcol_off += n_extra; + } + vcol += n_extra; + if (wp->w_p_wrap && n_extra > 0) { + if (wp->w_p_rl) { + col -= n_extra; + boguscols -= n_extra; + } else { + boguscols += n_extra; + col += n_extra; + } + } + n_extra = 0; + n_attr = 0; + } else if (n_skip == 0) { + is_concealing = true; + n_skip = 1; + } + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; // don't draw as UTF-8 + } + } else { + prev_syntax_id = 0; + is_concealing = false; + } + + if (n_skip > 0 && did_decrement_ptr) { + // not showing the '>', put pointer back to avoid getting stuck + ptr++; + } + } // end of printing from buffer content + + // In the cursor line and we may be concealing characters: correct + // the cursor column when we reach its position. + if (!did_wcol && draw_state == WL_LINE + && wp == curwin && lnum == wp->w_cursor.lnum + && conceal_cursor_line(wp) + && (int)wp->w_virtcol <= vcol + n_skip) { + if (wp->w_p_rl) { + wp->w_wcol = grid->cols - col + boguscols - 1; + } else { + wp->w_wcol = col - boguscols; + } + wp->w_wrow = row; + did_wcol = true; + wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; + } + + // Don't override visual selection highlighting. + if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) { + char_attr = hl_combine_attr(char_attr, extra_attr); + } + + // Handle the case where we are in column 0 but not on the first + // character of the line and the user wants us to show us a + // special character (via 'listchars' option "precedes:<char>". + if (lcs_prec_todo != NUL + && wp->w_p_list + && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) + && filler_todo <= 0 + && draw_state > WL_NR + && c != NUL) { + c = wp->w_p_lcs_chars.prec; + lcs_prec_todo = NUL; + if (utf_char2cells(mb_c) > 1) { + // Double-width character being overwritten by the "precedes" + // character, need to fill up half the character. + c_extra = MB_FILLER_CHAR; + c_final = NUL; + n_extra = 1; + n_attr = 2; + extra_attr = win_hl_attr(wp, HLF_AT); + } + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; // don't draw as UTF-8 + } + saved_attr3 = char_attr; // save current attr + char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr + n_attr3 = 1; + } + + // At end of the text line or just after the last character. + if (c == NUL && eol_hl_off == 0) { + // flag to indicate whether prevcol equals startcol of search_hl or + // one of the matches + bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, + (long)(ptr - line) - 1); + + // Invert at least one char, used for Visual and empty line or + // highlight match at end of line. If it's beyond the last + // char on the screen, just overwrite that one (tricky!) Not + // needed when a '$' was displayed for 'list'. + if (wp->w_p_lcs_chars.eol == lcs_eol_one + && ((area_attr != 0 && vcol == fromcol + && (VIsual_mode != Ctrl_V + || lnum == VIsual.lnum + || lnum == curwin->w_cursor.lnum)) + // highlight 'hlsearch' match at end of line + || prevcol_hl_flag)) { + int n = 0; + + if (wp->w_p_rl) { + if (col < 0) { + n = 1; + } + } else { + if (col >= grid->cols) { + n = -1; + } + } + if (n != 0) { + // At the window boundary, highlight the last character + // instead (better than nothing). + off += n; + col += n; + } else { + // Add a blank character to highlight. + schar_from_ascii(linebuf_char[off], ' '); + } + if (area_attr == 0 && !has_fold) { + // Use attributes from match with highest priority among + // 'search_hl' and the match list. + get_search_match_hl(wp, &screen_search_hl, (long)(ptr - line), &char_attr); + } + + int eol_attr = char_attr; + if (cul_attr) { + eol_attr = hl_combine_attr(cul_attr, eol_attr); + } + linebuf_attr[off] = eol_attr; + if (wp->w_p_rl) { + col--; + off--; + } else { + col++; + off++; + } + vcol++; + eol_hl_off = 1; + } + // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. + if (wp->w_p_wrap) { + v = wp->w_skipcol; + } else { + v = wp->w_leftcol; + } + + // check if line ends before left margin + if (vcol < v + col - win_col_off(wp)) { + vcol = v + col - win_col_off(wp); + } + // Get rid of the boguscols now, we want to draw until the right + // edge for 'cursorcolumn'. + col -= boguscols; + // boguscols = 0; // Disabled because value never read after this + + if (draw_color_col) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + bool has_virttext = false; + // Make sure alignment is the same regardless + // if listchars=eol:X is used or not. + int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0 + ? 1 : 0); + + if (has_decor) { + has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr, + col + eol_skip); + } + + if (((wp->w_p_cuc + && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off + && (int)wp->w_virtcol < + (long)grid->cols * (row - startrow + 1) + v + && lnum != wp->w_cursor.lnum) + || draw_color_col || line_attr_lowprio || line_attr + || diff_hlf != (hlf_T)0 || has_virttext)) { + int rightmost_vcol = 0; + int i; + + if (wp->w_p_cuc) { + rightmost_vcol = wp->w_virtcol; + } + + if (draw_color_col) { + // determine rightmost colorcolumn to possibly draw + for (i = 0; color_cols[i] >= 0; i++) { + if (rightmost_vcol < color_cols[i]) { + rightmost_vcol = color_cols[i]; + } + } + } + + int cuc_attr = win_hl_attr(wp, HLF_CUC); + int mc_attr = win_hl_attr(wp, HLF_MC); + + int diff_attr = 0; + if (diff_hlf == HLF_TXD) { + diff_hlf = HLF_CHD; + } + if (diff_hlf != 0) { + diff_attr = win_hl_attr(wp, (int)diff_hlf); + } + + int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr); + if (base_attr || line_attr || has_virttext) { + rightmost_vcol = INT_MAX; + } + + int col_stride = wp->w_p_rl ? -1 : 1; + + while (wp->w_p_rl ? col >= 0 : col < grid->cols) { + schar_from_ascii(linebuf_char[off], ' '); + col += col_stride; + if (draw_color_col) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + int col_attr = base_attr; + + if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { + col_attr = cuc_attr; + } else if (draw_color_col && VCOL_HLC == *color_cols) { + col_attr = mc_attr; + } + + col_attr = hl_combine_attr(col_attr, line_attr); + + linebuf_attr[off] = col_attr; + off += col_stride; + + if (VCOL_HLC >= rightmost_vcol) { + break; + } + + vcol += 1; + } + } + + // TODO(bfredl): integrate with the common beyond-the-end-loop + if (wp->w_buffer->terminal) { + // terminal buffers may need to highlight beyond the end of the + // logical line + int n = wp->w_p_rl ? -1 : 1; + while (col >= 0 && col < grid->cols) { + schar_from_ascii(linebuf_char[off], ' '); + linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol]; + off += n; + vcol += n; + col += n; + } + } + + draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); + grid_put_linebuf(grid, row, 0, col, grid->cols, wp->w_p_rl, wp, bg_attr, false); + row++; + + // Update w_cline_height and w_cline_folded if the cursor line was + // updated (saves a call to plines_win() later). + if (wp == curwin && lnum == curwin->w_cursor.lnum) { + curwin->w_cline_row = startrow; + curwin->w_cline_height = row - startrow; + curwin->w_cline_folded = foldinfo.fi_lines > 0; + curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); + conceal_cursor_used = conceal_cursor_line(curwin); + } + break; + } + + // Show "extends" character from 'listchars' if beyond the line end and + // 'list' is set. + if (wp->w_p_lcs_chars.ext != NUL + && draw_state == WL_LINE + && wp->w_p_list + && !wp->w_p_wrap + && filler_todo <= 0 + && (wp->w_p_rl ? col == 0 : col == grid->cols - 1) + && !has_fold + && (*ptr != NUL + || lcs_eol_one > 0 + || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { + c = wp->w_p_lcs_chars.ext; + char_attr = win_hl_attr(wp, HLF_AT); + mb_c = c; + if (utf_char2len(c) > 1) { + mb_utf8 = true; + u8cc[0] = 0; + c = 0xc0; + } else { + mb_utf8 = false; + } + } + + // advance to the next 'colorcolumn' + if (draw_color_col) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + // Highlight the cursor column if 'cursorcolumn' is set. But don't + // highlight the cursor position itself. + // Also highlight the 'colorcolumn' if it is different than + // 'cursorcolumn' + // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak' + // options are set + vcol_save_attr = -1; + if ((draw_state == WL_LINE + || draw_state == WL_BRI + || draw_state == WL_SBR) + && !lnum_in_visual_area + && search_attr == 0 + && area_attr == 0 + && filler_todo <= 0) { + if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol + && lnum != wp->w_cursor.lnum) { + vcol_save_attr = char_attr; + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); + } else if (draw_color_col && VCOL_HLC == *color_cols) { + vcol_save_attr = char_attr; + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); + } + } + + // Apply lowest-priority line attr now, so everything can override it. + if (draw_state == WL_LINE) { + char_attr = hl_combine_attr(line_attr_lowprio, char_attr); + } + + // Store character to be displayed. + // Skip characters that are left of the screen for 'nowrap'. + vcol_prev = vcol; + if (draw_state < WL_LINE || n_skip <= 0) { + // + // Store the character. + // + if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { + // A double-wide character is: put first half in left cell. + off--; + col--; + } + if (mb_utf8) { + schar_from_cc(linebuf_char[off], mb_c, u8cc); + } else { + schar_from_ascii(linebuf_char[off], (char)c); + } + if (multi_attr) { + linebuf_attr[off] = multi_attr; + multi_attr = 0; + } else { + linebuf_attr[off] = char_attr; + } + + if (utf_char2cells(mb_c) > 1) { + // Need to fill two screen columns. + off++; + col++; + // UTF-8: Put a 0 in the second screen char. + linebuf_char[off][0] = 0; + if (draw_state > WL_NR && filler_todo <= 0) { + vcol++; + } + // When "tocol" is halfway through a character, set it to the end of + // the character, otherwise highlighting won't stop. + if (tocol == vcol) { + tocol++; + } + if (wp->w_p_rl) { + // now it's time to backup one cell + off--; + col--; + } + } + if (wp->w_p_rl) { + off--; + col--; + } else { + off++; + col++; + } + } else if (wp->w_p_cole > 0 && is_concealing) { + n_skip--; + vcol_off++; + if (n_extra > 0) { + vcol_off += n_extra; + } + if (wp->w_p_wrap) { + // Special voodoo required if 'wrap' is on. + // + // Advance the column indicator to force the line + // drawing to wrap early. This will make the line + // take up the same screen space when parts are concealed, + // so that cursor line computations aren't messed up. + // + // To avoid the fictitious advance of 'col' causing + // trailing junk to be written out of the screen line + // we are building, 'boguscols' keeps track of the number + // of bad columns we have advanced. + if (n_extra > 0) { + vcol += n_extra; + if (wp->w_p_rl) { + col -= n_extra; + boguscols -= n_extra; + } else { + col += n_extra; + boguscols += n_extra; + } + n_extra = 0; + n_attr = 0; + } + + if (utf_char2cells(mb_c) > 1) { + // Need to fill two screen columns. + if (wp->w_p_rl) { + boguscols--; + col--; + } else { + boguscols++; + col++; + } + } + + if (wp->w_p_rl) { + boguscols--; + col--; + } else { + boguscols++; + col++; + } + } else { + if (n_extra > 0) { + vcol += n_extra; + n_extra = 0; + n_attr = 0; + } + } + } else { + n_skip--; + } + + // Only advance the "vcol" when after the 'number' or 'relativenumber' + // column. + if (draw_state > WL_NR + && filler_todo <= 0) { + vcol++; + } + + if (vcol_save_attr >= 0) { + char_attr = vcol_save_attr; + } + + // restore attributes after "predeces" in 'listchars' + if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { + char_attr = saved_attr3; + } + + // restore attributes after last 'listchars' or 'number' char + if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) { + char_attr = saved_attr2; + } + + // At end of screen line and there is more to come: Display the line + // so far. If there is no more to display it is caught above. + if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols)) + && foldinfo.fi_lines == 0 + && (draw_state != WL_LINE + || *ptr != NUL + || filler_todo > 0 + || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL + && p_extra != at_end_str) + || (n_extra != 0 + && (c_extra != NUL || *p_extra != NUL)))) { + bool wrap = wp->w_p_wrap // Wrapping enabled. + && filler_todo <= 0 // Not drawing diff filler lines. + && lcs_eol_one != -1 // Haven't printed the lcs_eol character. + && row != endrow - 1 // Not the last line being displayed. + && (grid->cols == Columns // Window spans the width of the screen, + || ui_has(kUIMultigrid)) // or has dedicated grid. + && !wp->w_p_rl; // Not right-to-left. + + int draw_col = col - boguscols; + if (filler_todo > 0) { + int index = filler_todo - (filler_lines - n_virt_lines); + if (index > 0) { + int i = (int)kv_size(virt_lines) - index; + assert(i >= 0); + int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset; + draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line, + kHlModeReplace, grid->cols, offset); + } + } else { + draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row); + } + + grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap); + if (wrap) { + ScreenGrid *current_grid = grid; + int current_row = row, dummy_col = 0; // dummy_col unused + grid_adjust(¤t_grid, ¤t_row, &dummy_col); + + // Force a redraw of the first column of the next line. + current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; + + // Remember that the line wraps, used for modeless copy. + current_grid->line_wraps[current_row] = true; + } + + boguscols = 0; + row++; + + // When not wrapping and finished diff lines, or when displayed + // '$' and highlighting until last column, break here. + if ((!wp->w_p_wrap && filler_todo <= 0) || lcs_eol_one == -1) { + break; + } + + // When the window is too narrow draw all "@" lines. + if (draw_state != WL_LINE && filler_todo <= 0) { + win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT); + row = endrow; + } + + // When line got too long for screen break here. + if (row == endrow) { + row++; + break; + } + + col = 0; + off = 0; + if (wp->w_p_rl) { + col = grid->cols - 1; // col is not used if breaking! + off += col; + } + + // reset the drawing state for the start of a wrapped line + draw_state = WL_START; + saved_n_extra = n_extra; + saved_p_extra = p_extra; + saved_c_extra = c_extra; + saved_c_final = c_final; + saved_char_attr = char_attr; + n_extra = 0; + lcs_prec_todo = wp->w_p_lcs_chars.prec; + if (filler_todo <= 0) { + need_showbreak = true; + } + filler_todo--; + // When the filler lines are actually below the last line of the + // file, don't draw the line itself, break here. + if (filler_todo == 0 && (wp->w_botfill || end_fill)) { + break; + } + } + } // for every character in the line + + // After an empty line check first word for capital. + if (*skipwhite((char *)line) == NUL) { + capcol_lnum = lnum + 1; + cap_col = 0; + } + + kv_destroy(virt_lines); + xfree(p_extra_free); + return row; +} diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h new file mode 100644 index 0000000000..e50969983e --- /dev/null +++ b/src/nvim/drawline.h @@ -0,0 +1,24 @@ +#ifndef NVIM_DRAWLINE_H +#define NVIM_DRAWLINE_H + +#include "nvim/decoration_provider.h" +#include "nvim/fold.h" +#include "nvim/screen.h" + +// Maximum columns for terminal highlight attributes +#define TERM_ATTRS_MAX 1024 + +typedef struct { + NS ns_id; + uint64_t mark_id; + int win_row; + int win_col; +} WinExtmark; +EXTERN kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE); + +EXTERN bool conceal_cursor_used INIT(= false); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawline.h.generated.h" +#endif +#endif // NVIM_DRAWLINE_H diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c new file mode 100644 index 0000000000..75fe0565c2 --- /dev/null +++ b/src/nvim/drawscreen.c @@ -0,0 +1,2373 @@ +// 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 + +// drawscreen.c: Code for updating all the windows on the screen. +// This is the top level, drawline.c is the middle and grid.c/screen.c the lower level. + +// update_screen() is the function that updates all windows and status lines. +// It is called from the main loop when must_redraw is non-zero. It may be +// called from other places when an immediate screen update is needed. +// +// The part of the buffer that is displayed in a window is set with: +// - w_topline (first buffer line in window) +// - w_topfill (filler lines above the first line) +// - w_leftcol (leftmost window cell in window), +// - w_skipcol (skipped window cells of first line) +// +// Commands that only move the cursor around in a window, do not need to take +// action to update the display. The main loop will check if w_topline is +// valid and update it (scroll the window) when needed. +// +// Commands that scroll a window change w_topline and must call +// check_cursor() to move the cursor into the visible part of the window, and +// call redraw_later(wp, VALID) to have the window displayed by update_screen() +// later. +// +// Commands that change text in the buffer must call changed_bytes() or +// changed_lines() to mark the area that changed and will require updating +// later. The main loop will call update_screen(), which will update each +// window that shows the changed buffer. This assumes text above the change +// can remain displayed as it is. Text after the change may need updating for +// scrolling, folding and syntax highlighting. +// +// Commands that change how a window is displayed (e.g., setting 'list') or +// invalidate the contents of a window in another way (e.g., change fold +// settings), must call redraw_later(wp, NOT_VALID) to have the whole window +// redisplayed by update_screen() later. +// +// Commands that change how a buffer is displayed (e.g., setting 'tabstop') +// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the +// buffer redisplayed by update_screen() later. +// +// Commands that change highlighting and possibly cause a scroll too must call +// redraw_later(wp, SOME_VALID) to update the whole window but still use +// scrolling to avoid redrawing everything. But the length of displayed lines +// must not change, use NOT_VALID then. +// +// Commands that move the window position must call redraw_later(wp, NOT_VALID). +// TODO(neovim): should minimize redrawing by scrolling when possible. +// +// Commands that change everything (e.g., resizing the screen) must call +// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR). +// +// Things that are handled indirectly: +// - When messages scroll the screen up, msg_scrolled will be set and +// update_screen() called to redraw. + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/diff.h" +#include "nvim/drawscreen.h" +#include "nvim/ex_getln.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/insexpand.h" +#include "nvim/match.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/plines.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" +#include "nvim/regexp.h" +#include "nvim/syntax.h" +#include "nvim/ui_compositor.h" +#include "nvim/undo.h" +#include "nvim/version.h" +#include "nvim/window.h" + +/// corner value flags for hsep_connected and vsep_connected +typedef enum { + WC_TOP_LEFT = 0, + WC_TOP_RIGHT, + WC_BOTTOM_LEFT, + WC_BOTTOM_RIGHT, +} WindowCorner; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawscreen.c.generated.h" +#endif + +static bool redraw_popupmenu = false; +static bool msg_grid_invalid = false; +static bool resizing = false; + +static char *provider_err = NULL; + +/// Check if the cursor line needs to be redrawn because of 'concealcursor'. +/// +/// When cursor is moved at the same time, both lines will be redrawn regardless. +void conceal_check_cursor_line(void) +{ + bool should_conceal = conceal_cursor_line(curwin); + if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) { + redrawWinline(curwin, curwin->w_cursor.lnum); + // Need to recompute cursor column, e.g., when starting Visual mode + // without concealing. + curs_columns(curwin, true); + } +} + +/// Resize the screen to Rows and Columns. +/// +/// Allocate default_grid.chars[] and other grid arrays. +/// +/// There may be some time between setting Rows and Columns and (re)allocating +/// default_grid arrays. This happens when starting up and when +/// (manually) changing the screen size. Always use default_grid.rows and +/// default_grid.Columns to access items in default_grid.chars[]. Use Rows +/// and Columns for positioning text etc. where the final size of the screen is +/// needed. +void screenalloc(void) +{ + // It's possible that we produce an out-of-memory message below, which + // will cause this function to be called again. To break the loop, just + // return here. + if (resizing) { + return; + } + resizing = true; + + int retry_count = 0; + +retry: + // Allocation of the screen buffers is done only when the size changes and + // when Rows and Columns have been set and we have started doing full + // screen stuff. + if ((default_grid.chars != NULL + && Rows == default_grid.rows + && Columns == default_grid.cols) + || Rows == 0 + || Columns == 0 + || (!full_screen && default_grid.chars == NULL)) { + resizing = false; + return; + } + + // Note that the window sizes are updated before reallocating the arrays, + // thus we must not redraw here! + RedrawingDisabled++; + + // win_new_screensize will recompute floats position, but tell the + // compositor to not redraw them yet + ui_comp_set_screen_valid(false); + if (msg_grid.chars) { + msg_grid_invalid = true; + } + + win_new_screensize(); // fit the windows in the new sized screen + + comp_col(); // recompute columns for shown command and ruler + + // We're changing the size of the screen. + // - Allocate new arrays for default_grid + // - Move lines from the old arrays into the new arrays, clear extra + // lines (unless the screen is going to be cleared). + // - Free the old arrays. + // + // If anything fails, make grid arrays NULL, so we don't do anything! + // Continuing with the old arrays may result in a crash, because the + // size is wrong. + + grid_alloc(&default_grid, Rows, Columns, true, true); + StlClickDefinition *new_tab_page_click_defs = + xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs)); + + stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); + xfree(tab_page_click_defs); + + tab_page_click_defs = new_tab_page_click_defs; + tab_page_click_defs_size = Columns; + + default_grid.comp_height = Rows; + default_grid.comp_width = Columns; + + default_grid.row_offset = 0; + default_grid.col_offset = 0; + default_grid.handle = DEFAULT_GRID_HANDLE; + + must_redraw = CLEAR; // need to clear the screen later + + RedrawingDisabled--; + + // Do not apply autocommands more than 3 times to avoid an endless loop + // in case applying autocommands always changes Rows or Columns. + if (starting == 0 && ++retry_count <= 3) { + apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); + // In rare cases, autocommands may have altered Rows or Columns, + // jump back to check if we need to allocate the screen again. + goto retry; + } + + resizing = false; +} + +void screenclear(void) +{ + check_for_delay(false); + screenalloc(); // allocate screen buffers if size changed + + int i; + + if (starting == NO_SCREEN || default_grid.chars == NULL) { + return; + } + + // blank out the default grid + for (i = 0; i < default_grid.rows; i++) { + grid_clear_line(&default_grid, default_grid.line_offset[i], + default_grid.cols, true); + default_grid.line_wraps[i] = false; + } + + ui_call_grid_clear(1); // clear the display + ui_comp_set_screen_valid(true); + + ns_hl_fast = -1; + + clear_cmdline = false; + mode_displayed = false; + + redraw_all_later(NOT_VALID); + redraw_cmdline = true; + redraw_tabline = true; + redraw_popupmenu = true; + pum_invalidate(); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + wp->w_redr_type = CLEAR; + } + } + if (must_redraw == CLEAR) { + must_redraw = NOT_VALID; // no need to clear again + } + compute_cmdrow(); + msg_row = cmdline_row; // put cursor on last line for messages + msg_col = 0; + msg_scrolled = 0; // can't scroll back + msg_didany = false; + msg_didout = false; + if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { + grid_invalidate(&msg_grid); + msg_grid_validate(); + msg_grid_invalid = false; + clear_cmdline = true; + } +} + +/// Set dimensions of the Nvim application "screen". +void screen_resize(int width, int height) +{ + // Avoid recursiveness, can happen when setting the window size causes + // another window-changed signal. + if (updating_screen || resizing_screen) { + return; + } + + if (width < 0 || height < 0) { // just checking... + return; + } + + if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { + // postpone the resizing + State = MODE_SETWSIZE; + return; + } + + // curwin->w_buffer can be NULL when we are closing a window and the + // buffer has already been closed and removing a scrollbar causes a resize + // event. Don't resize then, it will happen after entering another buffer. + if (curwin->w_buffer == NULL) { + return; + } + + resizing_screen = true; + + Rows = height; + Columns = width; + check_screensize(); + int max_p_ch = Rows - min_rows() + 1; + if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) { + p_ch = max_p_ch ? max_p_ch : 1; + } + height = Rows; + width = Columns; + p_lines = Rows; + p_columns = Columns; + ui_call_grid_resize(1, width, height); + + /// The window layout used to be adjusted here, but it now happens in + /// screenalloc() (also invoked from screenclear()). That is because the + /// recursize "resizing_screen" check above may skip this, but not screenalloc(). + + if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { + screenclear(); + } + + if (starting != NO_SCREEN) { + maketitle(); + + changed_line_abv_curs(); + invalidate_botline(); + + // We only redraw when it's needed: + // - While at the more prompt or executing an external command, don't + // redraw, but position the cursor. + // - While editing the command line, only redraw that. + // - in Ex mode, don't redraw anything. + // - Otherwise, redraw right now, and position the cursor. + // Always need to call update_screen() or screenalloc(), to make + // sure Rows/Columns and the size of the screen is correct! + if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM + || exmode_active) { + screenalloc(); + if (msg_grid.chars) { + msg_grid_validate(); + } + // TODO(bfredl): sometimes messes up the output. Implement clear+redraw + // also for the pager? (or: what if the pager was just a modal window?) + ui_comp_set_screen_valid(true); + repeat_message(); + } else { + if (curwin->w_p_scb) { + do_check_scrollbind(true); + } + if (State & MODE_CMDLINE) { + redraw_popupmenu = false; + update_screen(NOT_VALID); + redrawcmdline(); + if (pum_drawn()) { + cmdline_pum_display(false); + } + } else { + update_topline(curwin); + if (pum_drawn()) { + // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. + // For now make sure the nested update_screen(0) won't redraw the + // pum at the old position. Try to untangle this later. + redraw_popupmenu = false; + ins_compl_show_pum(); + } + update_screen(NOT_VALID); + if (redrawing()) { + setcursor(); + } + } + } + ui_flush(); + } + resizing_screen = false; +} + +/// Redraw the parts of the screen that is marked for redraw. +/// +/// Most code shouldn't call this directly, rather use redraw_later() and +/// and redraw_all_later() to mark parts of the screen as needing a redraw. +/// +/// @param type set to a NOT_VALID to force redraw of entire screen +int update_screen(int type) +{ + static bool did_intro = false; + bool is_stl_global = global_stl_height() > 0; + + // Don't do anything if the screen structures are (not yet) valid. + // A VimResized autocmd can invoke redrawing in the middle of a resize, + // which would bypass the checks in screen_resize for popupmenu etc. + if (!default_grid.chars || resizing) { + return FAIL; + } + + // May have postponed updating diffs. + if (need_diff_redraw) { + diff_redraw(true); + } + + if (must_redraw) { + if (type < must_redraw) { // use maximal type + type = must_redraw; + } + + // must_redraw is reset here, so that when we run into some weird + // reason to redraw while busy redrawing (e.g., asynchronous + // scrolling), or update_topline() in win_update() will cause a + // scroll, or a decoration provider requires a redraw, the screen + // will be redrawn later or in win_update(). + must_redraw = 0; + } + + // Need to update w_lines[]. + if (curwin->w_lines_valid == 0 && type < NOT_VALID) { + type = NOT_VALID; + } + + // Postpone the redrawing when it's not needed and when being called + // recursively. + if (!redrawing() || updating_screen) { + must_redraw = type; + if (type > INVERTED_ALL) { + curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now + } + return FAIL; + } + updating_screen = 1; + + display_tick++; // let syntax code know we're in a next round of + // display updating + + // Tricky: vim code can reset msg_scrolled behind our back, so need + // separate bookkeeping for now. + if (msg_did_scroll) { + msg_did_scroll = false; + msg_scrolled_at_flush = 0; + } + + if (type >= CLEAR || !default_grid.valid) { + ui_comp_set_screen_valid(false); + } + + // if the screen was scrolled up when displaying a message, scroll it down + if (msg_scrolled || msg_grid_invalid) { + clear_cmdline = true; + int valid = MAX(Rows - msg_scrollsize(), 0); + if (msg_grid.chars) { + // non-displayed part of msg_grid is considered invalid. + for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { + grid_clear_line(&msg_grid, msg_grid.line_offset[i], + msg_grid.cols, false); + } + } + if (msg_use_msgsep()) { + msg_grid.throttled = false; + // CLEAR is already handled + if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { + ui_comp_set_screen_valid(false); + for (int i = valid; i < Rows - p_ch; i++) { + grid_clear_line(&default_grid, default_grid.line_offset[i], + Columns, false); + } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (W_ENDROW(wp) > valid) { + wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); + } + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } + if (is_stl_global && Rows - p_ch - 1 > valid) { + curwin->w_redr_status = true; + } + } + msg_grid_set_pos(Rows - (int)p_ch, false); + msg_grid_invalid = false; + } else if (msg_scrolled > Rows - 5) { // clearing is faster + type = CLEAR; + } else if (type != CLEAR) { + check_for_delay(false); + grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (wp->w_winrow < msg_scrolled) { + if (W_ENDROW(wp) > msg_scrolled + && wp->w_redr_type < REDRAW_TOP + && wp->w_lines_valid > 0 + && wp->w_topline == wp->w_lines[0].wl_lnum) { + wp->w_upd_rows = msg_scrolled - wp->w_winrow; + wp->w_redr_type = REDRAW_TOP; + } else { + wp->w_redr_type = NOT_VALID; + if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) { + wp->w_redr_status = true; + } + } + } + } + if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { + curwin->w_redr_status = true; + } + redraw_cmdline = true; + redraw_tabline = true; + } + msg_scrolled = 0; + msg_scrolled_at_flush = 0; + need_wait_return = false; + } + + win_ui_flush(); + msg_ext_check_clear(); + + // reset cmdline_row now (may have been changed temporarily) + compute_cmdrow(); + + bool hl_changed = false; + // Check for changed highlighting + if (need_highlight_changed) { + highlight_changed(); + hl_changed = true; + } + + if (type == CLEAR) { // first clear screen + screenclear(); // will reset clear_cmdline + cmdline_screen_cleared(); // clear external cmdline state + type = NOT_VALID; + // must_redraw may be set indirectly, avoid another redraw later + must_redraw = 0; + } else if (!default_grid.valid) { + grid_invalidate(&default_grid); + default_grid.valid = true; + } + + // After disabling msgsep the grid might not have been deallocated yet, + // hence we also need to check msg_grid.chars + if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) { + grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0); + } + + ui_comp_set_screen_valid(true); + + DecorProviders providers; + decor_providers_start(&providers, type, &provider_err); + + // "start" callback could have changed highlights for global elements + if (win_check_ns_hl(NULL)) { + redraw_cmdline = true; + redraw_tabline = true; + } + + if (clear_cmdline) { // going to clear cmdline (done below) + check_for_delay(false); + } + + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + if (curwin->w_redr_type < NOT_VALID + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + ? number_width(curwin) : 0)) { + curwin->w_redr_type = NOT_VALID; + } + + // Only start redrawing if there is really something to do. + if (type == INVERTED) { + update_curswant(); + } + if (curwin->w_redr_type < type + && !((type == VALID + && curwin->w_lines[0].wl_valid + && curwin->w_topfill == curwin->w_old_topfill + && curwin->w_botfill == curwin->w_old_botfill + && curwin->w_topline == curwin->w_lines[0].wl_lnum) + || (type == INVERTED + && VIsual_active + && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum + && curwin->w_old_visual_mode == VIsual_mode + && (curwin->w_valid & VALID_VIRTCOL) + && curwin->w_old_curswant == curwin->w_curswant))) { + curwin->w_redr_type = type; + } + + // Redraw the tab pages line if needed. + if (redraw_tabline || type >= NOT_VALID) { + update_window_hl(curwin, type >= NOT_VALID); + FOR_ALL_TABS(tp) { + if (tp != curtab) { + update_window_hl(tp->tp_curwin, type >= NOT_VALID); + } + } + draw_tabline(); + } + + // Correct stored syntax highlighting info for changes in each displayed + // buffer. Each buffer must only be done once. + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + update_window_hl(wp, type >= NOT_VALID || hl_changed); + + buf_T *buf = wp->w_buffer; + if (buf->b_mod_set) { + if (buf->b_mod_tick_syn < display_tick + && syntax_present(wp)) { + syn_stack_apply_changes(buf); + buf->b_mod_tick_syn = display_tick; + } + + if (buf->b_mod_tick_decor < display_tick) { + decor_providers_invoke_buf(buf, &providers, &provider_err); + buf->b_mod_tick_decor = display_tick; + } + } + } + + // Go from top to bottom through the windows, redrawing the ones that need it. + bool did_one = false; + screen_search_hl.rm.regprog = NULL; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); + wp->w_redr_type = NOT_VALID; + } + + win_check_ns_hl(wp); + + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + win_redr_border(wp); + } + + if (wp->w_redr_type != 0) { + if (!did_one) { + did_one = true; + start_search_hl(); + } + win_update(wp, &providers); + } + + // redraw status line and window bar after the window to minimize cursor movement + if (wp->w_redr_status) { + win_redr_winbar(wp); + win_redr_status(wp); + } + } + + end_search_hl(); + + // May need to redraw the popup menu. + if (pum_drawn() && must_redraw_pum) { + win_check_ns_hl(curwin); + pum_redraw(); + } + + win_check_ns_hl(NULL); + + // Reset b_mod_set flags. Going through all windows is probably faster + // than going through all buffers (there could be many buffers). + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_buffer->b_mod_set = false; + } + + updating_screen = 0; + + // Clear or redraw the command line. Done last, because scrolling may + // mess up the command line. + if (clear_cmdline || redraw_cmdline || redraw_mode) { + showmode(); + } + + // May put up an introductory message when not editing a file + if (!did_intro) { + maybe_intro_message(); + } + did_intro = true; + + decor_providers_invoke_end(&providers, &provider_err); + kvi_destroy(providers); + + // either cmdline is cleared, not drawn or mode is last drawn + cmdline_was_last_drawn = false; + return OK; +} + +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + schar_T *chars = wp->w_float_config.border_chars; + int *attrs = wp->w_float_config.border_attr; + + int *adj = wp->w_border_adj; + int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; + + char* title = wp->w_float_config.title; + size_t n_title = wp->w_float_config.n_title; + stl_hlrec_t* title_hl = wp->w_float_config.title_hl; + + int m8[MAX_MCO + 1]; + int cc; + int len; + int t_attr = title_hl != NULL && title_hl->userhl + ? syn_id2attr(title_hl->userhl) + : 0; + t_attr = hl_combine_attr(attrs[1], t_attr); + + int title_pos = 2; + switch (wp->w_float_config.title_pos) { + case kTitleLeft: + title_pos = 2; + break; + case kTitleRight: + title_pos = icol - 2 - vim_strsize(title); + break; + case kTitleCenter: + title_pos = (icol - vim_strsize(title)) / 2 - 1; + break; + } + title_pos = title_pos < 2 ? 2 : title_pos; + + if (adj[0]) { + grid_puts_line_start(grid, 0); + if (adj[3]) { + grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + } + for (int i = 0; i < icol; i++) { + schar_T ch; + int attr; + // Draw the title if in the correct position. + if (i > title_pos && n_title > 0 && i < icol - 2) { + cc = utfc_ptr2char((char_u*) title, m8); + len = utfc_ptr2len(title); + n_title -= len; + title += len; + + while (title_hl != NULL && + (title_hl + 1)->start != NULL && + (title_hl + 1)->start < title) { + ++ title_hl; + t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl)); + } + + schar_from_cc(ch, cc, m8); + attr = t_attr; + } else { + memcpy(ch, chars[1], sizeof(schar_T)); + attr = attrs[1]; + } + grid_put_schar(grid, 0, i + adj[3], ch, attr); + } + if (adj[1]) { + grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); + } + grid_puts_line_flush(false); + } + + for (int i = 0; i < irow; i++) { + if (adj[3]) { + grid_puts_line_start(grid, i + adj[0]); + grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); + grid_puts_line_flush(false); + } + if (adj[1]) { + int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; + grid_puts_line_start(grid, i + adj[0]); + grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); + grid_puts_line_flush(false); + } + } + + if (adj[2]) { + grid_puts_line_start(grid, irow + adj[0]); + if (adj[3]) { + grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); + } + for (int i = 0; i < icol; i++) { + int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; + grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); + } + if (adj[1]) { + grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); + } + grid_puts_line_flush(false); + } +} + +/// Redraw the status line of window `wp`. +/// +/// If inversion is possible we use it. Else '=' characters are used. +static void win_redr_status(win_T *wp) +{ + int row; + int col; + char_u *p; + int len; + int fillchar; + int attr; + int width; + int this_ru_col; + bool is_stl_global = global_stl_height() > 0; + static bool busy = false; + + // May get here recursively when 'statusline' (indirectly) + // invokes ":redrawstatus". Simply ignore the call then. + if (busy + // Also ignore if wildmenu is showing. + || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { + return; + } + busy = true; + + wp->w_redr_status = false; + if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { + // no status line, either global statusline is enabled or the window is a last window + redraw_cmdline = true; + } else if (!redrawing()) { + // Don't redraw right now, do it later. Don't update status line when + // popup menu is visible and may be drawn over it + wp->w_redr_status = true; + } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { + // redraw custom status line + redraw_custom_statusline(wp); + } else { + fillchar = fillchar_status(&attr, wp); + width = is_stl_global ? Columns : wp->w_width; + + get_trans_bufname(wp->w_buffer); + p = NameBuff; + len = (int)STRLEN(p); + + if (bt_help(wp->w_buffer) + || wp->w_p_pvw + || bufIsChanged(wp->w_buffer) + || wp->w_buffer->b_p_ro) { + *(p + len++) = ' '; + } + if (bt_help(wp->w_buffer)) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); + len += (int)STRLEN(p + len); + } + if (wp->w_p_pvw) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); + len += (int)STRLEN(p + len); + } + if (bufIsChanged(wp->w_buffer)) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]"); + len += (int)STRLEN(p + len); + } + if (wp->w_buffer->b_p_ro) { + snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); + // len += (int)STRLEN(p + len); // dead assignment + } + + this_ru_col = ru_col - (Columns - width); + if (this_ru_col < (width + 1) / 2) { + this_ru_col = (width + 1) / 2; + } + if (this_ru_col <= 1) { + p = (char_u *)"<"; // No room for file name! + len = 1; + } else { + int clen = 0, i; + + // Count total number of display cells. + clen = (int)mb_string2cells((char *)p); + + // Find first character that will fit. + // Going from start to end is much faster for DBCS. + for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; + i += utfc_ptr2len((char *)p + i)) { + clen -= utf_ptr2cells((char *)p + i); + } + len = clen; + if (i > 0) { + p = p + i - 1; + *p = '<'; + len++; + } + } + + row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); + col = is_stl_global ? 0 : wp->w_wincol; + grid_puts(&default_grid, p, row, col, attr); + grid_fill(&default_grid, row, row + 1, len + col, + this_ru_col + col, fillchar, fillchar, attr); + + if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL) + && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) { + grid_puts(&default_grid, NameBuff, row, + (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr); + } + + win_redr_ruler(wp, true); + } + + // May need to draw the character below the vertical separator. + if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { + if (stl_connected(wp)) { + fillchar = fillchar_status(&attr, wp); + } else { + fillchar = fillchar_vsep(wp, &attr); + } + grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); + } + busy = false; +} + +/// Redraw the status line according to 'statusline' and take care of any +/// errors encountered. +static void redraw_custom_statusline(win_T *wp) +{ + static bool entered = false; + int saved_did_emsg = did_emsg; + + // When called recursively return. This can happen when the statusline + // contains an expression that triggers a redraw. + if (entered) { + return; + } + entered = true; + + did_emsg = false; + win_redr_custom(wp, false, false); + if (did_emsg) { + // When there is an error disable the statusline, otherwise the + // display is messed up with errors and a redraw triggers the problem + // again and again. + set_string_option_direct("statusline", -1, "", + OPT_FREE | (*wp->w_p_stl != NUL + ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); + } + did_emsg |= saved_did_emsg; + entered = false; +} + +static void win_redr_winbar(win_T *wp) +{ + static bool entered = false; + + // Return when called recursively. This can happen when the winbar contains an expression + // that triggers a redraw. + if (entered) { + return; + } + entered = true; + + if (wp->w_winbar_height == 0 || !redrawing()) { + // Do nothing. + } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { + int saved_did_emsg = did_emsg; + + did_emsg = false; + win_redr_custom(wp, true, false); + if (did_emsg) { + // When there is an error disable the winbar, otherwise the + // display is messed up with errors and a redraw triggers the problem + // again and again. + set_string_option_direct("winbar", -1, "", + OPT_FREE | (*wp->w_p_stl != NUL + ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); + } + did_emsg |= saved_did_emsg; + } + entered = false; +} + +/// Show current status info in ruler and various other places +/// +/// @param always if false, only show ruler if position has changed. +void showruler(bool always) +{ + if (!always && !redrawing()) { + return; + } + if ((*p_stl != NUL || *curwin->w_p_stl != NUL) + && (curwin->w_status_height || global_stl_height())) { + redraw_custom_statusline(curwin); + } else { + win_redr_ruler(curwin, always); + } + if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { + win_redr_winbar(curwin); + } + + if (need_maketitle + || (p_icon && (stl_syntax & STL_IN_ICON)) + || (p_title && (stl_syntax & STL_IN_TITLE))) { + maketitle(); + } + + // Redraw the tab pages line if needed. + if (redraw_tabline) { + draw_tabline(); + } +} + +static void redraw_win_signcol(win_T *wp) +{ + // If we can compute a change in the automatic sizing of the sign column + // under 'signcolumn=auto:X' and signs currently placed in the buffer, better + // figuring it out here so we can redraw the entire screen for it. + int scwidth = wp->w_scwidth; + wp->w_scwidth = win_signcol_count(wp); + if (wp->w_scwidth != scwidth) { + changed_line_abv_curs_win(wp); + } +} + +/// Check if horizontal separator of window "wp" at specified window corner is connected to the +/// horizontal separator of another window +/// Assumes global statusline is enabled +static bool hsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); + int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) + ? wp->w_winrow - 1 : W_ENDROW(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_ROW && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { + fr = fr->fr_next; + } + } + } + + return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); +} + +/// Check if vertical separator of window "wp" at specified window corner is connected to the +/// vertical separator of another window +static bool vsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); + int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) + ? wp->w_wincol - 1 : W_ENDCOL(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_COL && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { + fr = fr->fr_next; + } + } + } + + return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); +} + +/// Draw the vertical separator right of window "wp" +static void draw_vsep_win(win_T *wp) +{ + int hl; + int c; + + if (wp->w_vsep_width) { + // draw the vertical separator right of this window + c = fillchar_vsep(wp, &hl); + grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), + W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); + } +} + +/// Draw the horizontal separator below window "wp" +static void draw_hsep_win(win_T *wp) +{ + int hl; + int c; + + if (wp->w_hsep_height) { + // draw the horizontal separator below this window + c = fillchar_hsep(wp, &hl); + grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, + wp->w_wincol, W_ENDCOL(wp), c, c, hl); + } +} + +/// Get the separator connector for specified window corner of window "wp" +static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +{ + // It's impossible for windows to be connected neither vertically nor horizontally + // So if they're not vertically connected, assume they're horizontally connected + if (vsep_connected(wp, corner)) { + if (hsep_connected(wp, corner)) { + return wp->w_p_fcs_chars.verthoriz; + } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { + return wp->w_p_fcs_chars.vertright; + } else { + return wp->w_p_fcs_chars.vertleft; + } + } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { + return wp->w_p_fcs_chars.horizdown; + } else { + return wp->w_p_fcs_chars.horizup; + } +} + +/// Draw separator connecting characters on the corners of window "wp" +static void draw_sep_connectors_win(win_T *wp) +{ + // Don't draw separator connectors unless global statusline is enabled and the window has + // either a horizontal or vertical separator + if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { + return; + } + + int hl = win_hl_attr(wp, HLF_C); + + // Determine which edges of the screen the window is located on so we can avoid drawing separators + // on corners contained in those edges + bool win_at_top; + bool win_at_bottom = wp->w_hsep_height == 0; + bool win_at_left; + bool win_at_right = wp->w_vsep_width == 0; + frame_T *frp; + + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { + break; + } + } + win_at_top = frp->fr_parent == NULL; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + break; + } + } + win_at_left = frp->fr_parent == NULL; + + // Draw the appropriate separator connector in every corner where drawing them is necessary + if (!(win_at_top || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), + wp->w_winrow - 1, wp->w_wincol - 1, hl); + } + if (!(win_at_top || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), + wp->w_winrow - 1, W_ENDCOL(wp), hl); + } + if (!(win_at_bottom || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), + W_ENDROW(wp), wp->w_wincol - 1, hl); + } + if (!(win_at_bottom || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), + W_ENDROW(wp), W_ENDCOL(wp), hl); + } +} + +/// Update a single window. +/// +/// This may cause the windows below it also to be redrawn (when clearing the +/// screen or scrolling lines). +/// +/// How the window is redrawn depends on wp->w_redr_type. Each type also +/// implies the one below it. +/// NOT_VALID redraw the whole window +/// SOME_VALID redraw the whole window but do scroll when possible +/// REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID +/// INVERTED redraw the changed part of the Visual area +/// INVERTED_ALL redraw the whole Visual area +/// VALID 1. scroll up/down to adjust for a changed w_topline +/// 2. update lines at the top when scrolled down +/// 3. redraw changed text: +/// - if wp->w_buffer->b_mod_set set, update lines between +/// b_mod_top and b_mod_bot. +/// - if wp->w_redraw_top non-zero, redraw lines between +/// wp->w_redraw_top and wp->w_redr_bot. +/// - continue redrawing when syntax status is invalid. +/// 4. if scrolled up, update lines at the bottom. +/// This results in three areas that may need updating: +/// top: from first row to top_end (when scrolled down) +/// mid: from mid_start to mid_end (update inversion or changed text) +/// bot: from bot_start to last row (when scrolled up) +static void win_update(win_T *wp, DecorProviders *providers) +{ + bool called_decor_providers = false; +win_update_start: + ; + buf_T *buf = wp->w_buffer; + int type; + int top_end = 0; // Below last row of the top area that needs + // updating. 0 when no top area updating. + int mid_start = 999; // first row of the mid area that needs + // updating. 999 when no mid area updating. + int mid_end = 0; // Below last row of the mid area that needs + // updating. 0 when no mid area updating. + int bot_start = 999; // first row of the bot area that needs + // updating. 999 when no bot area updating + bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit + bool top_to_mod = false; // redraw above mod_top + + int row; // current window row to display + linenr_T lnum; // current buffer lnum to display + int idx; // current index in w_lines[] + int srow; // starting row of the current line + + bool eof = false; // if true, we hit the end of the file + bool didline = false; // if true, we finished the last line + int i; + long j; + static bool recursive = false; // being called recursively + const linenr_T old_botline = wp->w_botline; + // Remember what happened to the previous line. +#define DID_NONE 1 // didn't update a line +#define DID_LINE 2 // updated a normal line +#define DID_FOLD 3 // updated a folded line + int did_update = DID_NONE; + linenr_T syntax_last_parsed = 0; // last parsed text line + linenr_T mod_top = 0; + linenr_T mod_bot = 0; + int save_got_int; + + type = wp->w_redr_type; + + if (type >= NOT_VALID) { + wp->w_redr_status = true; + wp->w_lines_valid = 0; + } + + // Window is zero-height: Only need to draw the separator + if (wp->w_grid.rows == 0) { + // draw the horizontal separator below this window + draw_hsep_win(wp); + draw_sep_connectors_win(wp); + wp->w_redr_type = 0; + return; + } + + // Window is zero-width: Only need to draw the separator. + if (wp->w_grid.cols == 0) { + // draw the vertical separator right of this window + draw_vsep_win(wp); + draw_sep_connectors_win(wp); + wp->w_redr_type = 0; + return; + } + + redraw_win_signcol(wp); + + init_search_hl(wp, &screen_search_hl); + + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; + if (wp->w_nrwidth != i) { + type = NOT_VALID; + wp->w_nrwidth = i; + + if (buf->terminal) { + terminal_check_size(buf->terminal); + } + } else if (buf->b_mod_set + && buf->b_mod_xlines != 0 + && wp->w_redraw_top != 0) { + // When there are both inserted/deleted lines and specific lines to be + // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw + // everything (only happens when redrawing is off for while). + type = NOT_VALID; + } else { + // Set mod_top to the first line that needs displaying because of + // changes. Set mod_bot to the first line after the changes. + mod_top = wp->w_redraw_top; + if (wp->w_redraw_bot != 0) { + mod_bot = wp->w_redraw_bot + 1; + } else { + mod_bot = 0; + } + if (buf->b_mod_set) { + if (mod_top == 0 || mod_top > buf->b_mod_top) { + mod_top = buf->b_mod_top; + // Need to redraw lines above the change that may be included + // in a pattern match. + if (syntax_present(wp)) { + mod_top -= buf->b_s.b_syn_sync_linebreaks; + if (mod_top < 1) { + mod_top = 1; + } + } + } + if (mod_bot == 0 || mod_bot < buf->b_mod_bot) { + mod_bot = buf->b_mod_bot; + } + + // When 'hlsearch' is on and using a multi-line search pattern, a + // change in one line may make the Search highlighting in a + // previous line invalid. Simple solution: redraw all visible + // lines above the change. + // Same for a match pattern. + if (screen_search_hl.rm.regprog != NULL + && re_multiline(screen_search_hl.rm.regprog)) { + top_to_mod = true; + } else { + const matchitem_T *cur = wp->w_match_head; + while (cur != NULL) { + if (cur->match.regprog != NULL + && re_multiline(cur->match.regprog)) { + top_to_mod = true; + break; + } + cur = cur->next; + } + } + } + if (mod_top != 0 && hasAnyFolding(wp)) { + linenr_T lnumt, lnumb; + + // A change in a line can cause lines above it to become folded or + // unfolded. Find the top most buffer line that may be affected. + // If the line was previously folded and displayed, get the first + // line of that fold. If the line is folded now, get the first + // folded line. Use the minimum of these two. + + // Find last valid w_lines[] entry above mod_top. Set lnumt to + // the line below it. If there is no valid entry, use w_topline. + // Find the first valid w_lines[] entry below mod_bot. Set lnumb + // to this line. If there is no valid entry, use MAXLNUM. + lnumt = wp->w_topline; + lnumb = MAXLNUM; + for (i = 0; i < wp->w_lines_valid; i++) { + if (wp->w_lines[i].wl_valid) { + if (wp->w_lines[i].wl_lastlnum < mod_top) { + lnumt = wp->w_lines[i].wl_lastlnum + 1; + } + if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { + lnumb = wp->w_lines[i].wl_lnum; + // When there is a fold column it might need updating + // in the next line ("J" just above an open fold). + if (compute_foldcolumn(wp, 0) > 0) { + lnumb++; + } + } + } + } + + (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); + if (mod_top > lnumt) { + mod_top = lnumt; + } + + // Now do the same for the bottom line (one above mod_bot). + mod_bot--; + (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); + mod_bot++; + if (mod_bot < lnumb) { + mod_bot = lnumb; + } + } + + // When a change starts above w_topline and the end is below + // w_topline, start redrawing at w_topline. + // If the end of the change is above w_topline: do like no change was + // made, but redraw the first line to find changes in syntax. + if (mod_top != 0 && mod_top < wp->w_topline) { + if (mod_bot > wp->w_topline) { + mod_top = wp->w_topline; + } else if (syntax_present(wp)) { + top_end = 1; + } + } + + // When line numbers are displayed need to redraw all lines below + // inserted/deleted lines. + if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) { + mod_bot = MAXLNUM; + } + } + wp->w_redraw_top = 0; // reset for next time + wp->w_redraw_bot = 0; + + // When only displaying the lines at the top, set top_end. Used when + // window has scrolled down for msg_scrolled. + if (type == REDRAW_TOP) { + j = 0; + for (i = 0; i < wp->w_lines_valid; i++) { + j += wp->w_lines[i].wl_size; + if (j >= wp->w_upd_rows) { + top_end = (int)j; + break; + } + } + if (top_end == 0) { + // not found (cannot happen?): redraw everything + type = NOT_VALID; + } else { + // top area defined, the rest is VALID + type = VALID; + } + } + + // If there are no changes on the screen that require a complete redraw, + // handle three cases: + // 1: we are off the top of the screen by a few lines: scroll down + // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up + // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in + // w_lines[] that needs updating. + if ((type == VALID || type == SOME_VALID + || type == INVERTED || type == INVERTED_ALL) + && !wp->w_botfill && !wp->w_old_botfill) { + if (mod_top != 0 + && wp->w_topline == mod_top + && (!wp->w_lines[0].wl_valid + || wp->w_topline == wp->w_lines[0].wl_lnum)) { + // w_topline is the first changed line and window is not scrolled, + // the scrolling from changed lines will be done further down. + } else if (wp->w_lines[0].wl_valid + && (wp->w_topline < wp->w_lines[0].wl_lnum + || (wp->w_topline == wp->w_lines[0].wl_lnum + && wp->w_topfill > wp->w_old_topfill))) { + // New topline is above old topline: May scroll down. + if (hasAnyFolding(wp)) { + linenr_T ln; + + // count the number of lines we are off, counting a sequence + // of folded lines as one + j = 0; + for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { + j++; + if (j >= wp->w_grid.rows - 2) { + break; + } + (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL); + } + } else { + j = wp->w_lines[0].wl_lnum - wp->w_topline; + } + if (j < wp->w_grid.rows - 2) { // not too far off + i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); + // insert extra lines for previously invisible filler lines + if (wp->w_lines[0].wl_lnum != wp->w_topline) { + i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; + } + if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off + // Try to insert the correct number of lines. + // If not the last window, delete the lines at the bottom. + // win_ins_lines may fail when the terminal can't do it. + win_scroll_lines(wp, 0, i); + if (wp->w_lines_valid != 0) { + // Need to update rows that are new, stop at the + // first one that scrolled down. + top_end = i; + scrolled_down = true; + + // Move the entries that were scrolled, disable + // the entries for the lines to be redrawn. + if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { + wp->w_lines_valid = wp->w_grid.rows; + } + for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { + wp->w_lines[idx] = wp->w_lines[idx - j]; + } + while (idx >= 0) { + wp->w_lines[idx--].wl_valid = false; + } + } + } else { + mid_start = 0; // redraw all lines + } + } else { + mid_start = 0; // redraw all lines + } + } else { + // New topline is at or below old topline: May scroll up. + // When topline didn't change, find first entry in w_lines[] that + // needs updating. + + // try to find wp->w_topline in wp->w_lines[].wl_lnum + j = -1; + row = 0; + for (i = 0; i < wp->w_lines_valid; i++) { + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == wp->w_topline) { + j = i; + break; + } + row += wp->w_lines[i].wl_size; + } + if (j == -1) { + // if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all + // lines + mid_start = 0; + } else { + // Try to delete the correct number of lines. + // wp->w_topline is at wp->w_lines[i].wl_lnum. + + // If the topline didn't change, delete old filler lines, + // otherwise delete filler lines of the new topline... + if (wp->w_lines[0].wl_lnum == wp->w_topline) { + row += wp->w_old_topfill; + } else { + row += win_get_fill(wp, wp->w_topline); + } + // ... but don't delete new filler lines. + row -= wp->w_topfill; + if (row > 0) { + win_scroll_lines(wp, 0, -row); + bot_start = wp->w_grid.rows - row; + } + if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { + // Skip the lines (below the deleted lines) that are still + // valid and don't need redrawing. Copy their info + // upwards, to compensate for the deleted lines. Set + // bot_start to the first row that needs redrawing. + bot_start = 0; + idx = 0; + for (;;) { + wp->w_lines[idx] = wp->w_lines[j]; + // stop at line that didn't fit, unless it is still + // valid (no lines deleted) + if (row > 0 && bot_start + row + + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) { + wp->w_lines_valid = idx + 1; + break; + } + bot_start += wp->w_lines[idx++].wl_size; + + // stop at the last valid entry in w_lines[].wl_size + if (++j >= wp->w_lines_valid) { + wp->w_lines_valid = idx; + break; + } + } + + // Correct the first entry for filler lines at the top + // when it won't get updated below. + if (win_may_fill(wp) && bot_start > 0) { + wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true) + + wp->w_topfill); + } + } + } + } + + // When starting redraw in the first line, redraw all lines. + if (mid_start == 0) { + mid_end = wp->w_grid.rows; + } + } else { + // Not VALID or INVERTED: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + } + + if (type == SOME_VALID) { + // SOME_VALID: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + type = NOT_VALID; + } + + // check if we are updating or removing the inverted part + if ((VIsual_active && buf == curwin->w_buffer) + || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) { + linenr_T from, to; + + if (VIsual_active) { + if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) { + // If the type of Visual selection changed, redraw the whole + // selection. Also when the ownership of the X selection is + // gained or lost. + if (curwin->w_cursor.lnum < VIsual.lnum) { + from = curwin->w_cursor.lnum; + to = VIsual.lnum; + } else { + from = VIsual.lnum; + to = curwin->w_cursor.lnum; + } + // redraw more when the cursor moved as well + if (wp->w_old_cursor_lnum < from) { + from = wp->w_old_cursor_lnum; + } + if (wp->w_old_cursor_lnum > to) { + to = wp->w_old_cursor_lnum; + } + if (wp->w_old_visual_lnum < from) { + from = wp->w_old_visual_lnum; + } + if (wp->w_old_visual_lnum > to) { + to = wp->w_old_visual_lnum; + } + } else { + // Find the line numbers that need to be updated: The lines + // between the old cursor position and the current cursor + // position. Also check if the Visual position changed. + if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { + from = curwin->w_cursor.lnum; + to = wp->w_old_cursor_lnum; + } else { + from = wp->w_old_cursor_lnum; + to = curwin->w_cursor.lnum; + if (from == 0) { // Visual mode just started + from = to; + } + } + + if (VIsual.lnum != wp->w_old_visual_lnum + || VIsual.col != wp->w_old_visual_col) { + if (wp->w_old_visual_lnum < from + && wp->w_old_visual_lnum != 0) { + from = wp->w_old_visual_lnum; + } + if (wp->w_old_visual_lnum > to) { + to = wp->w_old_visual_lnum; + } + if (VIsual.lnum < from) { + from = VIsual.lnum; + } + if (VIsual.lnum > to) { + to = VIsual.lnum; + } + } + } + + // If in block mode and changed column or curwin->w_curswant: + // update all lines. + // First compute the actual start and end column. + if (VIsual_mode == Ctrl_V) { + colnr_T fromc, toc; + unsigned int save_ve_flags = curwin->w_ve_flags; + + if (curwin->w_p_lbr) { + curwin->w_ve_flags = VE_ALL; + } + + getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); + toc++; + curwin->w_ve_flags = save_ve_flags; + // Highlight to the end of the line, unless 'virtualedit' has + // "block". + if (curwin->w_curswant == MAXCOL) { + if (get_ve_flags() & VE_BLOCK) { + pos_T pos; + int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; + cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) { + colnr_T t; + + pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) { + toc = t; + } + } + toc++; + } else { + toc = MAXCOL; + } + } + + if (fromc != wp->w_old_cursor_fcol + || toc != wp->w_old_cursor_lcol) { + if (from > VIsual.lnum) { + from = VIsual.lnum; + } + if (to < VIsual.lnum) { + to = VIsual.lnum; + } + } + wp->w_old_cursor_fcol = fromc; + wp->w_old_cursor_lcol = toc; + } + } else { + // Use the line numbers of the old Visual area. + if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { + from = wp->w_old_cursor_lnum; + to = wp->w_old_visual_lnum; + } else { + from = wp->w_old_visual_lnum; + to = wp->w_old_cursor_lnum; + } + } + + // There is no need to update lines above the top of the window. + if (from < wp->w_topline) { + from = wp->w_topline; + } + + // If we know the value of w_botline, use it to restrict the update to + // the lines that are visible in the window. + if (wp->w_valid & VALID_BOTLINE) { + if (from >= wp->w_botline) { + from = wp->w_botline - 1; + } + if (to >= wp->w_botline) { + to = wp->w_botline - 1; + } + } + + // Find the minimal part to be updated. + // Watch out for scrolling that made entries in w_lines[] invalid. + // E.g., CTRL-U makes the first half of w_lines[] invalid and sets + // top_end; need to redraw from top_end to the "to" line. + // A middle mouse click with a Visual selection may change the text + // above the Visual area and reset wl_valid, do count these for + // mid_end (in srow). + if (mid_start > 0) { + lnum = wp->w_topline; + idx = 0; + srow = 0; + if (scrolled_down) { + mid_start = top_end; + } else { + mid_start = 0; + } + while (lnum < from && idx < wp->w_lines_valid) { // find start + if (wp->w_lines[idx].wl_valid) { + mid_start += wp->w_lines[idx].wl_size; + } else if (!scrolled_down) { + srow += wp->w_lines[idx].wl_size; + } + idx++; + if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) { + lnum = wp->w_lines[idx].wl_lnum; + } else { + lnum++; + } + } + srow += mid_start; + mid_end = wp->w_grid.rows; + for (; idx < wp->w_lines_valid; idx++) { // find end + if (wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum >= to + 1) { + // Only update until first row of this line + mid_end = srow; + break; + } + srow += wp->w_lines[idx].wl_size; + } + } + } + + if (VIsual_active && buf == curwin->w_buffer) { + wp->w_old_visual_mode = (char)VIsual_mode; + wp->w_old_cursor_lnum = curwin->w_cursor.lnum; + wp->w_old_visual_lnum = VIsual.lnum; + wp->w_old_visual_col = VIsual.col; + wp->w_old_curswant = curwin->w_curswant; + } else { + wp->w_old_visual_mode = 0; + wp->w_old_cursor_lnum = 0; + wp->w_old_visual_lnum = 0; + wp->w_old_visual_col = 0; + } + + // reset got_int, otherwise regexp won't work + save_got_int = got_int; + got_int = 0; + // Set the time limit to 'redrawtime'. + proftime_T syntax_tm = profile_setlimit(p_rdt); + syn_set_timeout(&syntax_tm); + + // Update all the window rows. + idx = 0; // first entry in w_lines[].wl_size + row = 0; + srow = 0; + lnum = wp->w_topline; // first line shown in window + + win_extmark_arr.size = 0; + + decor_redraw_reset(buf, &decor_state); + + DecorProviders line_providers; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + (void)win_signcol_count(wp); // check if provider changed signcol width + if (must_redraw != 0) { + must_redraw = 0; + if (!called_decor_providers) { + called_decor_providers = true; + goto win_update_start; + } + } + + bool cursorline_standout = win_cursorline_standout(wp); + + win_check_ns_hl(wp); + + for (;;) { + // stop updating when reached the end of the window (check for _past_ + // the end of the window is at the end of the loop) + if (row == wp->w_grid.rows) { + didline = true; + break; + } + + // stop updating when hit the end of the file + if (lnum > buf->b_ml.ml_line_count) { + eof = true; + break; + } + + // Remember the starting row of the line that is going to be dealt + // with. It is used further down when the line doesn't fit. + srow = row; + + // Update a line when it is in an area that needs updating, when it + // has changes or w_lines[idx] is invalid. + // "bot_start" may be halfway a wrapped line after using + // win_scroll_lines(), check if the current line includes it. + // When syntax folding is being used, the saved syntax states will + // already have been updated, we can't see where the syntax state is + // the same again, just update until the end of the window. + if (row < top_end + || (row >= mid_start && row < mid_end) + || top_to_mod + || idx >= wp->w_lines_valid + || (row + wp->w_lines[idx].wl_size > bot_start) + || (mod_top != 0 + && (lnum == mod_top + || (lnum >= mod_top + && (lnum < mod_bot + || did_update == DID_FOLD + || (did_update == DID_LINE + && syntax_present(wp) + && ((foldmethodIsSyntax(wp) + && hasAnyFolding(wp)) + || syntax_check_changed(lnum))) + // match in fixed position might need redraw + // if lines were inserted or deleted + || (wp->w_match_head != NULL + && buf->b_mod_xlines != 0))))) + || (cursorline_standout && lnum == wp->w_cursor.lnum) + || lnum == wp->w_last_cursorline) { + if (lnum == mod_top) { + top_to_mod = false; + } + + // When at start of changed lines: May scroll following lines + // up or down to minimize redrawing. + // Don't do this when the change continues until the end. + // Don't scroll when dollar_vcol >= 0, keep the "$". + // Don't scroll when redrawing the top, scrolled already above. + if (lnum == mod_top + && mod_bot != MAXLNUM + && !(dollar_vcol >= 0 && mod_bot == mod_top + 1) + && row >= top_end) { + int old_rows = 0; + int new_rows = 0; + int xtra_rows; + linenr_T l; + + // Count the old number of window rows, using w_lines[], which + // should still contain the sizes for the lines as they are + // currently displayed. + for (i = idx; i < wp->w_lines_valid; i++) { + // Only valid lines have a meaningful wl_lnum. Invalid + // lines are part of the changed area. + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == mod_bot) { + break; + } + old_rows += wp->w_lines[i].wl_size; + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { + // Must have found the last valid entry above mod_bot. + // Add following invalid entries. + i++; + while (i < wp->w_lines_valid + && !wp->w_lines[i].wl_valid) { + old_rows += wp->w_lines[i++].wl_size; + } + break; + } + } + + if (i >= wp->w_lines_valid) { + // We can't find a valid line below the changed lines, + // need to redraw until the end of the window. + // Inserting/deleting lines has no use. + bot_start = 0; + } else { + // Able to count old number of rows: Count new window + // rows, and may insert/delete lines + j = idx; + for (l = lnum; l < mod_bot; l++) { + if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { + new_rows++; + } else if (l == wp->w_topline) { + new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill; + } else { + new_rows += plines_win(wp, l, true); + } + j++; + if (new_rows > wp->w_grid.rows - row - 2) { + // it's getting too much, must redraw the rest + new_rows = 9999; + break; + } + } + xtra_rows = new_rows - old_rows; + if (xtra_rows < 0) { + // May scroll text up. If there is not enough + // remaining text or scrolling fails, must redraw the + // rest. If scrolling works, must redraw the text + // below the scrolled text. + if (row - xtra_rows >= wp->w_grid.rows - 2) { + mod_bot = MAXLNUM; + } else { + win_scroll_lines(wp, row, xtra_rows); + bot_start = wp->w_grid.rows + xtra_rows; + } + } else if (xtra_rows > 0) { + // May scroll text down. If there is not enough + // remaining text of scrolling fails, must redraw the + // rest. + if (row + xtra_rows >= wp->w_grid.rows - 2) { + mod_bot = MAXLNUM; + } else { + win_scroll_lines(wp, row + old_rows, xtra_rows); + if (top_end > row + old_rows) { + // Scrolled the part at the top that requires + // updating down. + top_end += xtra_rows; + } + } + } + + // When not updating the rest, may need to move w_lines[] + // entries. + if (mod_bot != MAXLNUM && i != j) { + if (j < i) { + int x = row + new_rows; + + // move entries in w_lines[] upwards + for (;;) { + // stop at last valid entry in w_lines[] + if (i >= wp->w_lines_valid) { + wp->w_lines_valid = (int)j; + break; + } + wp->w_lines[j] = wp->w_lines[i]; + // stop at a line that won't fit + if (x + (int)wp->w_lines[j].wl_size + > wp->w_grid.rows) { + wp->w_lines_valid = (int)j + 1; + break; + } + x += wp->w_lines[j++].wl_size; + i++; + } + if (bot_start > x) { + bot_start = x; + } + } else { // j > i + // move entries in w_lines[] downwards + j -= i; + wp->w_lines_valid += (linenr_T)j; + if (wp->w_lines_valid > wp->w_grid.rows) { + wp->w_lines_valid = wp->w_grid.rows; + } + for (i = wp->w_lines_valid; i - j >= idx; i--) { + wp->w_lines[i] = wp->w_lines[i - j]; + } + + // The w_lines[] entries for inserted lines are + // now invalid, but wl_size may be used above. + // Reset to zero. + while (i >= idx) { + wp->w_lines[i].wl_size = 0; + wp->w_lines[i--].wl_valid = false; + } + } + } + } + } + + // When lines are folded, display one line for all of them. + // Otherwise, display normally (can be several display lines when + // 'wrap' is on). + foldinfo_T foldinfo = fold_info(wp, lnum); + + if (foldinfo.fi_lines == 0 + && idx < wp->w_lines_valid + && wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum == lnum + && lnum > wp->w_topline + && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) + && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows + && win_get_fill(wp, lnum) == 0) { + // This line is not going to fit. Don't draw anything here, + // will draw "@ " lines below. + row = wp->w_grid.rows + 1; + } else { + prepare_search_hl(wp, &screen_search_hl, lnum); + // Let the syntax stuff know we skipped a few lines. + if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum + && syntax_present(wp)) { + syntax_end_parsing(syntax_last_parsed + 1); + } + + // Display one line + row = win_line(wp, lnum, srow, + foldinfo.fi_lines ? srow : wp->w_grid.rows, + mod_top == 0, false, foldinfo, &line_providers, &provider_err); + + if (foldinfo.fi_lines == 0) { + wp->w_lines[idx].wl_folded = false; + wp->w_lines[idx].wl_lastlnum = lnum; + did_update = DID_LINE; + syntax_last_parsed = lnum; + } else { + foldinfo.fi_lines--; + wp->w_lines[idx].wl_folded = true; + wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; + did_update = DID_FOLD; + } + } + + wp->w_lines[idx].wl_lnum = lnum; + wp->w_lines[idx].wl_valid = true; + + if (row > wp->w_grid.rows) { // past end of grid + // we may need the size of that too long line later on + if (dollar_vcol == -1) { + wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true); + } + idx++; + break; + } + if (dollar_vcol == -1) { + wp->w_lines[idx].wl_size = (uint16_t)(row - srow); + } + idx++; + lnum += foldinfo.fi_lines + 1; + } else { + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { + // 'relativenumber' set and cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. + foldinfo_T info = fold_info(wp, lnum); + (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true, + info, &line_providers, &provider_err); + } + + // This line does not need to be drawn, advance to the next one. + row += wp->w_lines[idx++].wl_size; + if (row > wp->w_grid.rows) { // past end of screen + break; + } + lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; + did_update = DID_NONE; + } + + if (lnum > buf->b_ml.ml_line_count) { + eof = true; + break; + } + } + // End of loop over all window lines. + + // Now that the window has been redrawn with the old and new cursor line, + // update w_last_cursorline. + wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; + + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; + + if (idx > wp->w_lines_valid) { + wp->w_lines_valid = idx; + } + + // Let the syntax stuff know we stop parsing here. + if (syntax_last_parsed != 0 && syntax_present(wp)) { + syntax_end_parsing(syntax_last_parsed + 1); + } + + // If we didn't hit the end of the file, and we didn't finish the last + // line we were working on, then the line didn't fit. + wp->w_empty_rows = 0; + wp->w_filler_rows = 0; + if (!eof && !didline) { + int at_attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, HLF_AT)); + if (lnum == wp->w_topline) { + // Single line that does not fit! + // Don't overwrite it, it can be edited. + wp->w_botline = lnum + 1; + } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) { + // Window ends in filler lines. + wp->w_botline = lnum; + wp->w_filler_rows = wp->w_grid.rows - srow; + } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" + int scr_row = wp->w_grid.rows - 1; + + // Last line isn't finished: Display "@@@" in the last screen line. + grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr); + + grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, + '@', ' ', at_attr); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" + int start_col = wp->w_grid.cols - 3; + + // Last line isn't finished: Display "@@@" at the end. + grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, + MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } else { + win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT); + wp->w_botline = lnum; + } + } else { + if (eof) { // we hit the end of the file + wp->w_botline = buf->b_ml.ml_line_count + 1; + j = win_get_fill(wp, wp->w_botline); + if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) { + // Display filler text below last line. win_line() will check + // for ml_line_count+1 and only draw filler lines + foldinfo_T info = FOLDINFO_INIT; + row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, + false, false, info, &line_providers, &provider_err); + } + } else if (dollar_vcol == -1) { + wp->w_botline = lnum; + } + + // make sure the rest of the screen is blank + // write the 'eob' character to rows that aren't part of the file. + win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows, + HLF_EOB); + } + + kvi_destroy(line_providers); + + if (wp->w_redr_type >= REDRAW_TOP) { + draw_vsep_win(wp); + draw_hsep_win(wp); + draw_sep_connectors_win(wp); + } + syn_set_timeout(NULL); + + // Reset the type of redrawing required, the window has been updated. + wp->w_redr_type = 0; + wp->w_old_topfill = wp->w_topfill; + wp->w_old_botfill = wp->w_botfill; + + // Send win_extmarks if needed + for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { + ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle, + kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id, + kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); + } + + if (dollar_vcol == -1) { + // There is a trick with w_botline. If we invalidate it on each + // change that might modify it, this will cause a lot of expensive + // calls to plines_win() in update_topline() each time. Therefore the + // value of w_botline is often approximated, and this value is used to + // compute the value of w_topline. If the value of w_botline was + // wrong, check that the value of w_topline is correct (cursor is on + // the visible part of the text). If it's not, we need to redraw + // again. Mostly this just means scrolling up a few lines, so it + // doesn't look too bad. Only do this for the current window (where + // changes are relevant). + wp->w_valid |= VALID_BOTLINE; + wp->w_viewport_invalid = true; + if (wp == curwin && wp->w_botline != old_botline && !recursive) { + recursive = true; + curwin->w_valid &= ~VALID_TOPLINE; + update_topline(curwin); // may invalidate w_botline again + if (must_redraw != 0) { + // Don't update for changes in buffer again. + i = curbuf->b_mod_set; + curbuf->b_mod_set = false; + win_update(curwin, providers); + must_redraw = 0; + curbuf->b_mod_set = i; + } + recursive = false; + } + } + + // restore got_int, unless CTRL-C was hit while redrawing + if (!got_int) { + got_int = save_got_int; + } +} + +/// Redraw a window later, with update_screen(type). +/// +/// Set must_redraw only if not already set to a higher value. +/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. +void redraw_later(win_T *wp, int type) + FUNC_ATTR_NONNULL_ALL +{ + if (!exiting && wp->w_redr_type < type) { + wp->w_redr_type = type; + if (type >= NOT_VALID) { + wp->w_lines_valid = 0; + } + if (must_redraw < type) { // must_redraw is the maximum of all windows + must_redraw = type; + } + } +} + +/// Mark all windows to be redrawn later. +void redraw_all_later(int type) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redraw_later(wp, type); + } + // This may be needed when switching tabs. + if (must_redraw < type) { + must_redraw = type; + } +} + +void screen_invalidate_highlights(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + redraw_later(wp, NOT_VALID); + wp->w_grid_alloc.valid = false; + } +} + +/// Mark all windows that are editing the current buffer to be updated later. +void redraw_curbuf_later(int type) +{ + redraw_buf_later(curbuf, type); +} + +void redraw_buf_later(buf_T *buf, int type) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + redraw_later(wp, type); + } + } +} + +void redraw_buf_line_later(buf_T *buf, linenr_T line) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && line >= wp->w_topline && line < wp->w_botline) { + redrawWinline(wp, line); + } + } +} + +void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && lastline >= wp->w_topline && firstline < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { + wp->w_redraw_top = firstline; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { + wp->w_redraw_bot = lastline; + } + redraw_later(wp, VALID); + } + } +} + +/// called when the status bars for the buffer 'buf' need to be updated +void redraw_buf_status_later(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && (wp->w_status_height + || (wp == curwin && global_stl_height()) + || wp->w_winbar_height)) { + wp->w_redr_status = true; + if (must_redraw < VALID) { + must_redraw = VALID; + } + } + } +} + +/// Mark all status lines and window bars for redraw; used after first :cd +void status_redraw_all(void) +{ + bool is_stl_global = global_stl_height() != 0; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) + || wp->w_winbar_height) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } + } +} + +/// Marks all status lines and window bars of the current buffer for redraw. +void status_redraw_curbuf(void) +{ + status_redraw_buf(curbuf); +} + +/// Marks all status lines and window bars of the given buffer for redraw. +void status_redraw_buf(buf_T *buf) +{ + bool is_stl_global = global_stl_height() != 0; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) + || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } + } +} + +/// Redraw all status lines that need to be redrawn. +void redraw_statuslines(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_status) { + win_redr_winbar(wp); + win_redr_status(wp); + } + } + if (redraw_tabline) { + draw_tabline(); + } +} + +/// Changed something in the current window, at buffer line "lnum", that +/// requires that line and possibly other lines to be redrawn. +/// Used when entering/leaving Insert mode with the cursor on a folded line. +/// Used to remove the "$" from a change command. +/// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot +/// may become invalid and the whole window will have to be redrawn. +void redrawWinline(win_T *wp, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL +{ + if (lnum >= wp->w_topline + && lnum < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { + wp->w_redraw_top = lnum; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { + wp->w_redraw_bot = lnum; + } + redraw_later(wp, VALID); + } +} diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h new file mode 100644 index 0000000000..3eac1caaa1 --- /dev/null +++ b/src/nvim/drawscreen.h @@ -0,0 +1,25 @@ +#ifndef NVIM_DRAWSCREEN_H +#define NVIM_DRAWSCREEN_H + +#include "nvim/drawline.h" + +/// flags for update_screen() +/// The higher the value, the higher the priority +enum { + VALID = 10, ///< buffer not changed, or changes marked with b_mod_* + INVERTED = 20, ///< redisplay inverted part that changed + INVERTED_ALL = 25, ///< redisplay whole inverted part + REDRAW_TOP = 30, ///< display first w_upd_rows screen lines + SOME_VALID = 35, ///< like NOT_VALID but may scroll + NOT_VALID = 40, ///< buffer needs complete redraw + CLEAR = 50, ///< screen messed up, clear it +}; + +/// While redrawing the screen this flag is set. It means the screen size +/// ('lines' and 'rows') must not be changed. +EXTERN bool updating_screen INIT(= 0); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawscreen.h.generated.h" +#endif +#endif // NVIM_DRAWSCREEN_H diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 969e0af9f5..3a47731715 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -16,6 +16,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/event/loop.h" @@ -25,6 +26,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/grid.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" @@ -46,9 +48,8 @@ #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/plines.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/quickfix.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/state.h" @@ -1747,7 +1748,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang replace_push(replaced); replaced = NUL; } - ++start_col; + start_col++; } } @@ -1913,7 +1914,7 @@ int get_literal(bool no_simplify) cc = cc * 10 + nc - '0'; } - ++i; + i++; } if (cc > 255 @@ -1948,7 +1949,7 @@ int get_literal(bool no_simplify) cc = '\n'; } - --no_mapping; + no_mapping--; if (nc) { vungetc(nc); // A character typed with i_CTRL-V_digit cannot have modifiers. @@ -2022,7 +2023,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) /// @param second_indent indent for second line if >= 0 void insertchar(int c, int flags, int second_indent) { - char_u *p; + char *p; int force_format = flags & INSCHAR_FORMAT; const int textwidth = comp_textwidth(force_format); @@ -2081,13 +2082,13 @@ void insertchar(int c, int flags, int second_indent) // Need to remove existing (middle) comment leader and insert end // comment leader. First, check what comment leader we can find. char_u *line = get_cursor_line_ptr(); - int i = get_leader_len((char *)line, (char **)&p, false, true); - if (i > 0 && vim_strchr((char *)p, COM_MIDDLE) != NULL) { // Just checking + int i = get_leader_len((char *)line, &p, false, true); + if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking // Skip middle-comment string while (*p && p[-1] != ':') { // find end of middle flags p++; } - int middle_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); + int middle_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); // Don't count trailing white space for middle_len while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) { middle_len--; @@ -2097,7 +2098,7 @@ void insertchar(int c, int flags, int second_indent) while (*p && p[-1] != ':') { // find end of end flags p++; } - int end_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); + int end_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); // Skip white space before the cursor i = curwin->w_cursor.col; @@ -3253,7 +3254,7 @@ int cursor_down(long n, int upd_topline) if (hasFolding(lnum, NULL, &last)) { lnum = last + 1; } else { - ++lnum; + lnum++; } if (lnum >= curbuf->b_ml.ml_line_count) { break; @@ -3433,7 +3434,7 @@ void replace_push(int c) memmove(p + 1, p, (size_t)replace_offset); } *p = (char_u)c; - ++replace_stack_nr; + replace_stack_nr++; } /* @@ -3468,7 +3469,7 @@ static void replace_join(int off) { for (ssize_t i = replace_stack_nr; --i >= 0;) { if (replace_stack[i] == NUL && off-- <= 0) { - --replace_stack_nr; + replace_stack_nr--; memmove(replace_stack + i, replace_stack + i + 1, (size_t)(replace_stack_nr - i)); return; @@ -3794,12 +3795,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) while (*look == '>') { look++; } - } - /* - * Is it a word: "=word"? - */ - else if (*look == '=' && look[1] != ',' && look[1] != NUL) { - ++look; + // Is it a word: "=word"? + } else if (*look == '=' && look[1] != ',' && look[1] != NUL) { + look++; if (*look == '~') { icase = true; look++; @@ -4253,7 +4251,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Otherwise remove the mode message. if (reg_recording != 0 || restart_edit != NUL) { showmode(); - } else if (p_smd) { + } else if (p_smd && (got_int || !skip_showmode())) { msg(""); } // Exit Insert mode @@ -5221,8 +5219,8 @@ static bool ins_tab(void) // Find first white before the cursor fpos = curwin->w_cursor; while (fpos.col > 0 && ascii_iswhite(ptr[-1])) { - --fpos.col; - --ptr; + fpos.col--; + ptr--; } // In Replace mode, don't change characters before the insert point. @@ -5254,8 +5252,8 @@ static bool ins_tab(void) } } } - ++fpos.col; - ++ptr; + fpos.col++; + ptr++; vcol += i; } @@ -5266,8 +5264,8 @@ static bool ins_tab(void) // Skip over the spaces we need. while (vcol < want_vcol && *ptr == ' ') { vcol += lbr_chartabsize(line, ptr, vcol); - ++ptr; - ++repl_off; + ptr++; + repl_off++; } if (vcol > want_vcol) { // Must have a char with 'showbreak' just before it. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7cb8bdee19..53f1074033 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1,9 +1,7 @@ // 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 -/* - * eval.c: Expression evaluation. - */ +// eval.c: Expression evaluation. #include <math.h> #include <stdlib.h> @@ -15,10 +13,12 @@ #endif #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -29,9 +29,10 @@ #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" @@ -43,8 +44,10 @@ #include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/path.h" +#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sign.h" @@ -71,19 +74,15 @@ static char * const namespace_char = "abglstvw"; /// Variable used for g: static ScopeDictDictItem globvars_var; -/* - * Old Vim variables such as "v:version" are also available without the "v:". - * Also in functions. We need a special hashtable for them. - */ +/// Old Vim variables such as "v:version" are also available without the "v:". +/// Also in functions. We need a special hashtable for them. static hashtab_T compat_hashtab; /// Used for checking if local variables or arguments used in a lambda. bool *eval_lavars_used = NULL; -/* - * Array to hold the hashtab with variables local to each sourced script. - * Each item holds a variable (nameless) that points to the dict_T. - */ +/// Array to hold the hashtab with variables local to each sourced script. +/// Each item holds a variable (nameless) that points to the dict_T. typedef struct { ScopeDictDictItem sv_var; dict_T sv_dict; @@ -98,9 +97,7 @@ static int echo_attr = 0; // attributes used for ":echo" // The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL }; -/* - * Info used by a ":for" loop. - */ +/// Info used by a ":for" loop. typedef struct { int fi_semicolon; // TRUE if ending in '; var]' int fi_varcount; // nr of variables in the list @@ -356,8 +353,6 @@ void eval_init(void) { vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; - struct vimvar *p; - init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE); init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE); vimvardict.dv_lock = VAR_FIXED; @@ -365,7 +360,7 @@ void eval_init(void) func_init(); for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { - p = &vimvars[i]; + struct vimvar *p = &vimvars[i]; assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN); STRCPY(p->vv_di.di_key, p->vv_name); if (p->vv_flags & VV_RO) { @@ -447,10 +442,8 @@ void eval_init(void) #if defined(EXITFREE) void eval_clear(void) { - struct vimvar *p; - for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { - p = &vimvars[i]; + struct vimvar *p = &vimvars[i]; if (p->vv_di.di_tv.v_type == VAR_STRING) { XFREE_CLEAR(p->vv_str); } else if (p->vv_di.di_tv.v_type == VAR_LIST) { @@ -473,13 +466,13 @@ void eval_clear(void) // autoloaded script names ga_clear_strings(&ga_loaded); - /* Script-local variables. First clear all the variables and in a second - * loop free the scriptvar_T, because a variable in one script might hold - * a reference to the whole scope of another script. */ - for (int i = 1; i <= ga_scripts.ga_len; ++i) { + // Script-local variables. First clear all the variables and in a second + // loop free the scriptvar_T, because a variable in one script might hold + // a reference to the whole scope of another script. + for (int i = 1; i <= ga_scripts.ga_len; i++) { vars_clear(&SCRIPT_VARS(i)); } - for (int i = 1; i <= ga_scripts.ga_len; ++i) { + for (int i = 1; i <= ga_scripts.ga_len; i++) { xfree(SCRIPT_SV(i)); } ga_clear(&ga_scripts); @@ -518,10 +511,6 @@ static char *redir_varname = NULL; /// @return OK if successfully completed the setup. FAIL otherwise. int var_redir_start(char *name, int append) { - int save_emsg; - int err; - typval_T tv; - // Catch a bad name early. if (!eval_isnamec1(*name)) { emsg(_(e_invarg)); @@ -553,10 +542,11 @@ int var_redir_start(char *name, int append) return FAIL; } - /* check if we can write to the variable: set it to or append an empty - * string */ - save_emsg = did_emsg; - did_emsg = FALSE; + // check if we can write to the variable: set it to or append an empty + // string + int save_emsg = did_emsg; + did_emsg = false; + typval_T tv; tv.v_type = VAR_STRING; tv.vval.v_string = ""; if (append) { @@ -565,7 +555,7 @@ int var_redir_start(char *name, int append) set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); - err = did_emsg; + int err = did_emsg; did_emsg |= save_emsg; if (err) { redir_endp = NULL; // don't store a value, only cleanup @@ -585,12 +575,11 @@ int var_redir_start(char *name, int append) /// :redir END void var_redir_str(char *value, int value_len) { - int len; - if (redir_lval == NULL) { return; } + int len; if (value_len == -1) { len = (int)STRLEN(value); // Append the entire string } else { @@ -606,12 +595,11 @@ void var_redir_str(char *value, int value_len) /// Frees the allocated memory. void var_redir_stop(void) { - typval_T tv; - if (redir_lval != NULL) { // If there was no error: assign the text to the variable. if (redir_endp != NULL) { ga_append(&redir_ga, NUL); // Append the trailing NUL. + typval_T tv; tv.v_type = VAR_STRING; tv.vval.v_string = redir_ga.ga_data; // Call get_lval() again, if it's inside a Dict or List it may @@ -935,7 +923,7 @@ varnumber_T eval_to_number(char *expr) varnumber_T retval; char *p = skipwhite(expr); - ++emsg_off; + emsg_off++; if (eval1(&p, &rettv, true) == FAIL) { retval = -1; @@ -943,7 +931,7 @@ varnumber_T eval_to_number(char *expr) retval = tv_get_number_chk(&rettv, NULL); tv_clear(&rettv); } - --emsg_off; + emsg_off--; return retval; } @@ -1000,11 +988,9 @@ void prepare_vimvar(int idx, typval_T *save_tv) /// When no longer defined, remove the variable from the v: hashtable. void restore_vimvar(int idx, typval_T *save_tv) { - hashitem_T *hi; - vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); + hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); if (HASHITEM_EMPTY(hi)) { internal_error("restore_vimvar()"); } else { @@ -1040,7 +1026,7 @@ list_T *eval_spell_expr(char *badword, char *expr) vimvars[VV_VAL].vv_type = VAR_STRING; vimvars[VV_VAL].vv_str = badword; if (p_verbose == 0) { - ++emsg_off; + emsg_off++; } if (eval1(&p, &rettv, true) == OK) { @@ -1052,7 +1038,7 @@ list_T *eval_spell_expr(char *badword, char *expr) } if (p_verbose == 0) { - --emsg_off; + emsg_off--; } restore_vimvar(VV_VAL, &save_val); @@ -1134,12 +1120,11 @@ varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv) FUNC_ATTR_NONNULL_ALL { typval_T rettv; - varnumber_T retval; if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) { return -1; } - retval = tv_get_number_chk(&rettv, NULL); + varnumber_T retval = tv_get_number_chk(&rettv, NULL); tv_clear(&rettv); return retval; } @@ -1191,42 +1176,6 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv) return rettv.vval.v_list; } -/// Prepare profiling for entering a child or something else that is not -/// counted for the script/function itself. -/// Should always be called in pair with prof_child_exit(). -/// -/// @param tm place to store waittime -void prof_child_enter(proftime_T *tm) -{ - funccall_T *fc = get_current_funccal(); - - if (fc != NULL && fc->func->uf_profiling) { - fc->prof_child = profile_start(); - } - - script_prof_save(tm); -} - -/// Take care of time spent in a child. -/// Should always be called after prof_child_enter(). -/// -/// @param tm where waittime was stored -void prof_child_exit(proftime_T *tm) -{ - funccall_T *fc = get_current_funccal(); - - if (fc != NULL && fc->func->uf_profiling) { - fc->prof_child = profile_end(fc->prof_child); - // don't count waiting time - fc->prof_child = profile_sub_wait(*tm, fc->prof_child); - fc->func->uf_tm_children = - profile_add(fc->func->uf_tm_children, fc->prof_child); - fc->func->uf_tml_children = - profile_add(fc->func->uf_tml_children, fc->prof_child); - } - script_prof_restore(tm); -} - /// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding /// it in "*cp". Doesn't give error messages. int eval_foldexpr(char *arg, int *cp) @@ -1235,11 +1184,11 @@ int eval_foldexpr(char *arg, int *cp) varnumber_T retval; int use_sandbox = was_set_insecurely(curwin, "foldexpr", OPT_LOCAL); - ++emsg_off; + emsg_off++; if (use_sandbox) { - ++sandbox; + sandbox++; } - ++textlock; + textlock++; *cp = NUL; if (eval0(arg, &tv, NULL, true) == FAIL) { retval = 0; @@ -1260,11 +1209,11 @@ int eval_foldexpr(char *arg, int *cp) } tv_clear(&tv); } - --emsg_off; + emsg_off--; if (use_sandbox) { - --sandbox; + sandbox--; } - --textlock; + textlock--; return (int)retval; } @@ -1298,16 +1247,11 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const const bool skip, const int flags, const int fne_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { - dictitem_T *v; - typval_T var1; - typval_T var2; - int empty1 = FALSE; - listitem_T *ni; - hashtab_T *ht = NULL; + bool empty1 = false; int quiet = flags & GLV_QUIET; // Clear everything in "lp". - memset(lp, 0, sizeof(lval_T)); + CLEAR_POINTER(lp); if (skip) { // When skipping just find the end of the name. @@ -1355,11 +1299,13 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return p; } + hashtab_T *ht = NULL; + // Only pass &ht when we would write to the variable, it prevents autoload // as well. - v = find_var(lp->ll_name, lp->ll_name_len, - (flags & GLV_READ_ONLY) ? NULL : &ht, - flags & GLV_NO_AUTOLOAD); + dictitem_T *v = find_var(lp->ll_name, lp->ll_name_len, + (flags & GLV_READ_ONLY) ? NULL : &ht, + flags & GLV_NO_AUTOLOAD); if (v == NULL && !quiet) { semsg(_("E121: Undefined variable: %.*s"), (int)lp->ll_name_len, lp->ll_name); @@ -1370,7 +1316,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const // Loop until no more [idx] or .key is following. lp->ll_tv = &v->di_tv; + typval_T var1; var1.v_type = VAR_UNKNOWN; + typval_T var2; var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) @@ -1612,7 +1560,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string. tv_clear(&var2); if (lp->ll_n2 < 0) { - ni = tv_list_find(lp->ll_list, (int)lp->ll_n2); + listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2); if (ni == NULL) { if (!quiet) { semsg(_(e_listidx), (int64_t)lp->ll_n2); @@ -1765,9 +1713,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool ll_n1++; } - /* - * Assign the List values to the list items. - */ + // Assign the List values to the list items. for (ri = tv_list_first(rettv->vval.v_list); ri != NULL;) { if (op != NULL && *op != '=') { eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op); @@ -1888,7 +1834,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } if (skip) { - ++emsg_skip; + emsg_skip++; } if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) { *errp = false; @@ -1930,7 +1876,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } } if (skip) { - --emsg_skip; + emsg_skip--; } return fi; @@ -2009,7 +1955,7 @@ void free_for_info(void *fi_void) void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) FUNC_ATTR_NONNULL_ALL { - int got_eq = FALSE; + bool got_eq = false; int c; char *p; @@ -2035,7 +1981,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) if (c == '&') { c = (uint8_t)xp->xp_pattern[1]; if (c == '&') { - ++xp->xp_pattern; + xp->xp_pattern++; xp->xp_context = cmdidx != CMD_let || got_eq ? EXPAND_EXPRESSION : EXPAND_NOTHING; } else if (c != ' ') { @@ -2048,7 +1994,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) // environment variable xp->xp_context = EXPAND_ENV_VARS; } else if (c == '=') { - got_eq = TRUE; + got_eq = true; xp->xp_context = EXPAND_EXPRESSION; } else if (c == '#' && xp->xp_context == EXPAND_EXPRESSION) { @@ -2073,7 +2019,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) xp->xp_context = EXPAND_NOTHING; } else if (c == '|') { if (xp->xp_pattern[1] == '|') { - ++xp->xp_pattern; + xp->xp_pattern++; xp->xp_context = EXPAND_EXPRESSION; } else { xp->xp_context = EXPAND_COMMANDS; @@ -2123,11 +2069,9 @@ void del_menutrans_vars(void) hash_unlock(&globvarht); } -/* - * Local string buffer for the next two functions to store a variable name - * with its prefix. Allocated in cat_prefix_varname(), freed later in - * get_user_var_name(). - */ +/// Local string buffer for the next two functions to store a variable name +/// with its prefix. Allocated in cat_prefix_varname(), freed later in +/// get_user_var_name(). static char *varnamebuf = NULL; static size_t varnamebuflen = 0; @@ -2171,10 +2115,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (gdone++ == 0) { hi = globvarht.ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } if (STRNCMP("g:", xp->xp_pattern, 2) == 0) { return cat_prefix_varname('g', (char *)hi->hi_key); @@ -2188,10 +2132,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (bdone++ == 0) { hi = ht->ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } return cat_prefix_varname('b', (char *)hi->hi_key); } @@ -2202,10 +2146,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (wdone++ == 0) { hi = ht->ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } return cat_prefix_varname('w', (char *)hi->hi_key); } @@ -2216,10 +2160,10 @@ char *get_user_var_name(expand_T *xp, int idx) if (tdone++ == 0) { hi = ht->ht_array; } else { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } return cat_prefix_varname('t', (char *)hi->hi_key); } @@ -2291,7 +2235,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ funcexe.evaluate = evaluate; funcexe.partial = partial; funcexe.basetv = basetv; - int ret = get_func_tv((char_u *)s, len, rettv, (char_u **)arg, &funcexe); + int ret = get_func_tv((char_u *)s, len, rettv, arg, &funcexe); xfree(s); @@ -2317,11 +2261,9 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ // TODO(ZyX-I): move to eval/expressions -/* - * The "evaluate" argument: When FALSE, the argument is only parsed but not - * executed. The function may return OK, but the rettv will be of type - * VAR_UNKNOWN. The function still returns FAIL for a syntax error. - */ +/// The "evaluate" argument: When FALSE, the argument is only parsed but not +/// executed. The function may return OK, but the rettv will be of type +/// VAR_UNKNOWN. The function still returns FAIL for a syntax error. /// Handle zero level expression. /// This calls eval1() and handles error message and nextcmd. @@ -2372,18 +2314,16 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) /// @return OK or FAIL. int eval1(char **arg, typval_T *rettv, int evaluate) { - int result; + bool result; typval_T var2; - /* - * Get the first variable. - */ + // Get the first variable. if (eval2(arg, rettv, evaluate) == FAIL) { return FAIL; } if ((*arg)[0] == '?') { - result = FALSE; + result = false; if (evaluate) { bool error = false; @@ -2396,17 +2336,13 @@ int eval1(char **arg, typval_T *rettv, int evaluate) } } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 1); if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive! return FAIL; } - /* - * Check for the ":". - */ + // Check for the ":". if ((*arg)[0] != ':') { emsg(_("E109: Missing ':' after '?'")); if (evaluate && result) { @@ -2415,9 +2351,7 @@ int eval1(char **arg, typval_T *rettv, int evaluate) return FAIL; } - /* - * Get the third variable. - */ + // Get the third variable. *arg = skipwhite(*arg + 1); if (eval1(arg, &var2, evaluate && !result) == FAIL) { // Recursive! if (evaluate && result) { @@ -2445,22 +2379,16 @@ int eval1(char **arg, typval_T *rettv, int evaluate) static int eval2(char **arg, typval_T *rettv, int evaluate) { typval_T var2; - long result; - int first; bool error = false; - /* - * Get the first variable. - */ + // Get the first variable. if (eval3(arg, rettv, evaluate) == FAIL) { return FAIL; } - /* - * Repeat until there is no following "||". - */ - first = TRUE; - result = FALSE; + // Repeat until there is no following "||". + bool first = true; + bool result = false; while ((*arg)[0] == '|' && (*arg)[1] == '|') { if (evaluate && first) { if (tv_get_number_chk(rettv, &error) != 0) { @@ -2473,17 +2401,13 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) first = false; } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 2); if (eval3(arg, &var2, evaluate && !result) == FAIL) { return FAIL; } - /* - * Compute the result. - */ + // Compute the result. if (evaluate && !result) { if (tv_get_number_chk(&var2, &error) != 0) { result = true; @@ -2514,22 +2438,16 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) static int eval3(char **arg, typval_T *rettv, int evaluate) { typval_T var2; - long result; - int first; bool error = false; - /* - * Get the first variable. - */ + // Get the first variable. if (eval4(arg, rettv, evaluate) == FAIL) { return FAIL; } - /* - * Repeat until there is no following "&&". - */ - first = TRUE; - result = TRUE; + // Repeat until there is no following "&&". + bool first = true; + bool result = true; while ((*arg)[0] == '&' && (*arg)[1] == '&') { if (evaluate && first) { if (tv_get_number_chk(rettv, &error) == 0) { @@ -2542,17 +2460,13 @@ static int eval3(char **arg, typval_T *rettv, int evaluate) first = false; } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 2); if (eval4(arg, &var2, evaluate && result) == FAIL) { return FAIL; } - /* - * Compute the result. - */ + // Compute the result. if (evaluate && result) { if (tv_get_number_chk(&var2, &error) == 0) { result = false; @@ -2597,9 +2511,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) int len = 2; bool ic; - /* - * Get the first variable. - */ + // Get the first variable. if (eval5(arg, rettv, evaluate) == FAIL) { return FAIL; } @@ -2648,9 +2560,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) break; } - /* - * If there is a comparative operator, use it. - */ + // If there is a comparative operator, use it. if (type != EXPR_UNKNOWN) { // extra question mark appended: ignore case if (p[len] == '?') { @@ -2701,16 +2611,12 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) float_T f1 = 0, f2 = 0; char *p; - /* - * Get the first variable. - */ - if (eval6(arg, rettv, evaluate, FALSE) == FAIL) { + // Get the first variable. + if (eval6(arg, rettv, evaluate, false) == FAIL) { return FAIL; } - /* - * Repeat computing, until no '+', '-' or '.' is following. - */ + // Repeat computing, until no '+', '-' or '.' is following. for (;;) { op = (char_u)(**arg); if (op != '+' && op != '-' && op != '.') { @@ -2732,9 +2638,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) } } - /* - * Get the second variable. - */ + // Get the second variable. if (op == '.' && *(*arg + 1) == '.') { // ..string concatenation (*arg)++; } @@ -2745,9 +2649,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) } if (evaluate) { - /* - * Compute the result. - */ + // Compute the result. if (op == '.') { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -2876,16 +2778,12 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) float_T f1 = 0, f2 = 0; bool error = false; - /* - * Get the first variable. - */ + // Get the first variable. if (eval7(arg, rettv, evaluate, want_string) == FAIL) { return FAIL; } - /* - * Repeat computing, until no '*', '/' or '%' is following. - */ + // Repeat computing, until no '*', '/' or '%' is following. for (;;) { op = (char_u)(**arg); if (op != '*' && op != '/' && op != '%') { @@ -2908,9 +2806,7 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) n1 = 0; } - /* - * Get the second variable. - */ + // Get the second variable. *arg = skipwhite(*arg + 1); if (eval7(arg, &var2, evaluate, false) == FAIL) { return FAIL; @@ -2935,10 +2831,8 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) } } - /* - * Compute the result. - * When either side is a float the result is a float. - */ + // Compute the result. + // When either side is a float the result is a float. if (use_float) { if (op == '*') { f1 = f1 * f2; @@ -3051,9 +2945,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) get_float = true; p = skipdigits(p + 2); if (*p == 'e' || *p == 'E') { - ++p; + p++; if (*p == '-' || *p == '+') { - ++p; + p++; } if (!ascii_isdigit(*p)) { get_float = false; @@ -3147,7 +3041,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Lambda: {arg, arg -> expr} // Dictionary: {'key': val, 'key': val} case '{': - ret = get_lambda_tv((char_u **)arg, rettv, evaluate); + ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { ret = dict_get_tv(arg, rettv, evaluate, false); } @@ -3164,15 +3058,12 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Register contents: @r. case '@': - ++*arg; + (*arg)++; int regname = mb_cptr2char_adv((const char_u**) arg); if (evaluate) { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, kGRegExprSrc); } - // if (**arg != NUL) { - // ++*arg; - // } break; // nested expression: (expression). @@ -3180,7 +3071,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) *arg = skipwhite(*arg + 1); ret = eval1(arg, rettv, evaluate); // recursive! if (**arg == ')') { - ++*arg; + (*arg)++; } else if (ret == OK) { emsg(_("E110: Missing ')'")); tv_clear(rettv); @@ -3326,7 +3217,7 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e funcexe.selfdict = selfdict; funcexe.basetv = basetv; const int ret = get_func_tv((char_u *)funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, - (char_u **)arg, &funcexe); + arg, &funcexe); // Clear the funcref afterwards, so that deleting it while // evaluating the arguments is possible (see test55). @@ -3354,7 +3245,7 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu typval_T base = *rettv; rettv->v_type = VAR_UNKNOWN; - int ret = get_lambda_tv((char_u **)arg, rettv, evaluate); + int ret = get_lambda_tv(arg, rettv, evaluate); if (ret != OK) { return FAIL; } else if (**arg != '(') { @@ -3504,9 +3395,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) typval_T var1 = TV_INITIAL_VALUE; typval_T var2 = TV_INITIAL_VALUE; if (**arg == '.') { - /* - * dict.name - */ + // dict.name key = *arg + 1; for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) {} if (len == 0) { @@ -3514,11 +3403,9 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) } *arg = skipwhite(key + len); } else { - /* - * something[idx] - * - * Get the (first) variable from inside the []. - */ + // something[idx] + // + // Get the (first) variable from inside the []. *arg = skipwhite(*arg + 1); if (**arg == ':') { empty1 = true; @@ -3530,9 +3417,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) return FAIL; } - /* - * Get the second variable from inside the [:]. - */ + // Get the second variable from inside the [:]. if (**arg == ':') { range = true; *arg = skipwhite(*arg + 1); @@ -3773,11 +3658,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { - long numval; - char *stringval; - getoption_T opt_type; bool working = (**arg == '+'); // has("+option") - int ret = OK; int opt_flags; // Isolate the option name and find its value. @@ -3794,10 +3675,14 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return OK; } + long numval; + char *stringval; + int ret = OK; + char c = *option_end; *option_end = NUL; - opt_type = get_option_value(*arg, &numval, - rettv == NULL ? NULL : &stringval, opt_flags); + getoption_T opt_type = get_option_value(*arg, &numval, + rettv == NULL ? NULL : &stringval, opt_flags); if (opt_type == gov_unknown) { if (rettv != NULL) { @@ -3838,9 +3723,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) char *p; unsigned int extra = 0; - /* - * Find the end of the string, skipping backslashed characters. - */ + // Find the end of the string, skipping backslashed characters. for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { p++; @@ -3864,10 +3747,8 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) return OK; } - /* - * Copy the string into allocated memory, handling backslashed - * characters. - */ + // Copy the string into allocated memory, handling backslashed + // characters. const int len = (int)(p - *arg + extra); char *name = xmalloc((size_t)len); rettv->v_type = VAR_STRING; @@ -3906,7 +3787,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) } nr = 0; while (--n >= 0 && ascii_isxdigit(p[1])) { - ++p; + p++; nr = (nr << 4) + hex2nr(*p); } p++; @@ -3936,7 +3817,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) *name = (char)((*name << 3) + *p++ - '0'); } } - ++name; + name++; break; // Special key, e.g.: "\<C-W>" @@ -3958,11 +3839,11 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) FALLTHROUGH; default: - mb_copy_char((const char_u **)&p, (char_u **)&name); + mb_copy_char((const char **)&p, &name); break; } } else { - mb_copy_char((const char_u **)&p, (char_u **)&name); + mb_copy_char((const char **)&p, &name); } } *name = NUL; @@ -3980,19 +3861,16 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) { char *p; - char *str; int reduce = 0; - /* - * Find the end of the string, skipping ''. - */ + // Find the end of the string, skipping ''. for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\'') { if (p[1] != '\'') { break; } - ++reduce; - ++p; + reduce++; + p++; } } @@ -4007,10 +3885,8 @@ static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) return OK; } - /* - * Copy the string into allocated memory, handling '' to ' reduction. - */ - str = xmalloc((size_t)((p - *arg) - reduce)); + // Copy the string into allocated memory, handling '' to ' reduction. + char *str = xmalloc((size_t)((p - *arg) - reduce)); rettv->v_type = VAR_STRING; rettv->vval.v_string = str; @@ -4019,9 +3895,9 @@ static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate) if (p[1] != '\'') { break; } - ++p; + p++; } - mb_copy_char((const char_u **)&p, (char_u **)&str); + mb_copy_char((const char **)&p, &str); } *str = NUL; *arg = p + 1; @@ -4120,16 +3996,14 @@ failret: /// @param ic ignore case bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) { - char_u *s1, *s2; - dict_T *d1, *d2; - int a1, a2; - // empty and NULL function name considered the same - s1 = (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial)); + char_u *s1 = + (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial)); if (s1 != NULL && *s1 == NUL) { s1 = NULL; } - s2 = (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial)); + char_u *s2 = + (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial)); if (s2 != NULL && *s2 == NUL) { s2 = NULL; } @@ -4142,8 +4016,8 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) } // empty dict and NULL dict is different - d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict; - d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict; + dict_T *d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict; + dict_T *d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict; if (d1 == NULL || d2 == NULL) { if (d1 != d2) { return false; @@ -4153,8 +4027,8 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) } // empty list and no list considered the same - a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc; - a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc; + int a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc; + int a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc; if (a1 != a2) { return false; } @@ -4183,25 +4057,23 @@ int get_copyID(void) return current_copyID; } -/* - * Garbage collection for lists and dictionaries. - * - * We use reference counts to be able to free most items right away when they - * are no longer used. But for composite items it's possible that it becomes - * unused while the reference count is > 0: When there is a recursive - * reference. Example: - * :let l = [1, 2, 3] - * :let d = {9: l} - * :let l[1] = d - * - * Since this is quite unusual we handle this with garbage collection: every - * once in a while find out which lists and dicts are not referenced from any - * variable. - * - * Here is a good reference text about garbage collection (refers to Python - * but it applies to all reference-counting mechanisms): - * http://python.ca/nas/python/gc/ - */ +/// Garbage collection for lists and dictionaries. +/// +/// We use reference counts to be able to free most items right away when they +/// are no longer used. But for composite items it's possible that it becomes +/// unused while the reference count is > 0: When there is a recursive +/// reference. Example: +/// :let l = [1, 2, 3] +/// :let d = {9: l} +/// :let l[1] = d +/// +/// Since this is quite unusual we handle this with garbage collection: every +/// once in a while find out which lists and dicts are not referenced from any +/// variable. +/// +/// Here is a good reference text about garbage collection (refers to Python +/// but it applies to all reference-counting mechanisms): +/// http://python.ca/nas/python/gc/ /// Do garbage collection for lists and dicts. /// @@ -4220,6 +4092,23 @@ bool garbage_collect(bool testing) garbage_collect_at_exit = false; } + // The execution stack can grow big, limit the size. + if (exestack.ga_maxlen - exestack.ga_len > 500) { + // Keep 150% of the current size, with a minimum of the growth size. + int n = exestack.ga_len / 2; + if (n < exestack.ga_growsize) { + n = exestack.ga_growsize; + } + + // Don't make it bigger though. + if (exestack.ga_len + n < exestack.ga_maxlen) { + size_t new_len = (size_t)exestack.ga_itemsize * (size_t)(exestack.ga_len + n); + char *pp = xrealloc(exestack.ga_data, new_len); + exestack.ga_maxlen = exestack.ga_len + n; + exestack.ga_data = pp; + } + } + // We advance by two (COPYID_INC) because we add one for items referenced // through previous_funccal. const int copyID = get_copyID(); @@ -4233,7 +4122,7 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_previous_funccal)(copyID); // script-local variables - for (int i = 1; i <= ga_scripts.ga_len; ++i) { + for (int i = 1; i <= ga_scripts.ga_len; i++) { ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL); } @@ -4692,21 +4581,16 @@ static int get_literal_key(char **arg, typval_T *tv) /// @return OK or FAIL. Returns NOTDONE for {expr}. static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) { - dict_T *d = NULL; - typval_T tvkey; typval_T tv; char *key = NULL; - dictitem_T *item; char *start = skipwhite(*arg + 1); char buf[NUMBUFLEN]; - /* - * First check if it's not a curly-braces thing: {expr}. - * Must do this without evaluating, otherwise a function may be called - * twice. Unfortunately this means we need to call eval1() twice for the - * first item. - * But {} is an empty Dictionary. - */ + // First check if it's not a curly-braces thing: {expr}. + // Must do this without evaluating, otherwise a function may be called + // twice. Unfortunately this means we need to call eval1() twice for the + // first item. + // But {} is an empty Dictionary. if (*start != '}') { if (eval1(&start, &tv, false) == FAIL) { // recursive! return FAIL; @@ -4716,9 +4600,11 @@ static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) } } + dict_T *d = NULL; if (evaluate) { d = tv_dict_alloc(); } + typval_T tvkey; tvkey.v_type = VAR_UNKNOWN; tv.v_type = VAR_UNKNOWN; @@ -4751,7 +4637,7 @@ static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) goto failret; } if (evaluate) { - item = tv_dict_find(d, (const char *)key, -1); + dictitem_T *item = tv_dict_find(d, (const char *)key, -1); if (item != NULL) { semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key); tv_clear(&tvkey); @@ -4806,8 +4692,6 @@ failret: size_t string2float(const char *const text, float_T *const ret_value) FUNC_ATTR_NONNULL_ALL { - char *s = NULL; - // MS-Windows does not deal with "inf" and "nan" properly if (STRNICMP(text, "inf", 3) == 0) { *ret_value = (float_T)INFINITY; @@ -4821,6 +4705,7 @@ size_t string2float(const char *const text, float_T *const ret_value) *ret_value = (float_T)NAN; return 3; } + char *s = NULL; *ret_value = strtod(text, &s); return (size_t)(s - text); } @@ -4834,23 +4719,18 @@ size_t string2float(const char *const text, float_T *const ret_value) /// @return FAIL if the name is invalid. static int get_env_tv(char **arg, typval_T *rettv, int evaluate) { - char *name; - char *string = NULL; - int len; - int cc; - - ++*arg; - name = *arg; - len = get_env_len((const char **)arg); + (*arg)++; + char *name = *arg; + int len = get_env_len((const char **)arg); if (evaluate) { if (len == 0) { return FAIL; // Invalid empty name. } - cc = (char_u)name[len]; + int cc = (int)name[len]; name[len] = NUL; // First try vim_getenv(), fast for normal environment vars. - string = vim_getenv(name); + char *string = vim_getenv(name); if (string == NULL || *string == NUL) { xfree(string); @@ -4868,18 +4748,6 @@ static int get_env_tv(char **arg, typval_T *rettv, int evaluate) return OK; } -/// Get the argument list for a given window -void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) -{ - tv_list_alloc_ret(rettv, argcount); - if (arglist != NULL) { - for (int idx = 0; idx < argcount; idx++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)alist_name(&arglist[idx]), -1); - } - } -} - /// Add an assert error to v:errors. void assert_error(garray_T *gap) { @@ -4909,17 +4777,10 @@ win_T *find_win_by_nr_or_id(typval_T *vp) /// Implementation of map() and filter(). void filter_map(typval_T *argvars, typval_T *rettv, int map) { - typval_T *expr; list_T *l = NULL; - dictitem_T *di; - hashtab_T *ht; - hashitem_T *hi; dict_T *d = NULL; - typval_T save_val; - typval_T save_key; blob_T *b = NULL; int rem = false; - int todo; char *ermsg = map ? "map()" : "filter()"; const char *const arg_errmsg = (map ? N_("map() argument") @@ -4950,18 +4811,20 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) return; } - expr = &argvars[1]; + typval_T *expr = &argvars[1]; // On type errors, the preceding call has already displayed an error // message. Avoid a misleading error message for an empty string that // was not passed as argument. if (expr->v_type != VAR_UNKNOWN) { + typval_T save_val; prepare_vimvar(VV_VAL, &save_val); // We reset "did_emsg" to be able to detect whether an error // occurred during evaluation of the expression. save_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; + typval_T save_key; prepare_vimvar(VV_KEY, &save_key); if (argvars[0].v_type == VAR_DICT) { vimvars[VV_KEY].vv_type = VAR_STRING; @@ -4970,14 +4833,14 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) if (map && d->dv_lock == VAR_UNLOCKED) { d->dv_lock = VAR_LOCKED; } - ht = &d->dv_hashtab; + hashtab_T *ht = &d->dv_hashtab; hash_lock(ht); - todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + int todo = (int)ht->ht_used; + for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; - di = TV_DICT_HI2DI(hi); + dictitem_T *di = TV_DICT_HI2DI(hi); if (map && (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { @@ -5126,7 +4989,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { name = s; - trans_name = (char *)trans_function_name((char_u **)&name, false, + trans_name = (char *)trans_function_name(&name, false, TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); if (*name != NUL) { @@ -5330,29 +5193,6 @@ linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) return (linenr_T)tv_get_number_chk(tv, NULL); } -void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) -{ - if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - if (is_qf || wp != NULL) { - (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list); - } - } else { - tv_dict_alloc_ret(rettv); - if (is_qf || wp != NULL) { - if (what_arg->v_type == VAR_DICT) { - dict_T *d = what_arg->vval.v_dict; - - if (d != NULL) { - qf_get_properties(wp, d, rettv->vval.v_dict); - } - } else { - emsg(_(e_dictreq)); - } - } - } -} - /// @return information (variables, options, etc.) about a tab page /// as a dictionary. dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) @@ -5689,11 +5529,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T FUNC_ATTR_NONNULL_ARG(4, 5) { linenr_T lnum = lnum_arg + (append ? 1 : 0); - const char *line = NULL; - list_T *l = NULL; - listitem_T *li = NULL; long added = 0; - linenr_T append_lnum; buf_T *curbuf_save = NULL; win_T *curwin_save = NULL; const bool is_curbuf = buf == curbuf; @@ -5715,6 +5551,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T find_win_for_curbuf(); } + linenr_T append_lnum; if (append) { // appendbufline() uses the line number below which we insert append_lnum = lnum - 1; @@ -5724,6 +5561,9 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T append_lnum = curbuf->b_ml.ml_line_count; } + list_T *l = NULL; + listitem_T *li = NULL; + const char *line = NULL; if (lines->v_type == VAR_LIST) { l = lines->vval.v_list; li = tv_list_first(l); @@ -5807,7 +5647,6 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { - const void *iter = NULL; list_T *const list = tv_list_alloc(kListLenShouldKnow); rettv->v_type = VAR_LIST; rettv->vval.v_list = list; @@ -5816,6 +5655,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) if (dirs == NULL) { return; } + const void *iter = NULL; do { size_t dir_len; const char *dir; @@ -5925,9 +5765,9 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist #ifdef USE_CRNL // translate <CR><NL> into <NL> char *d = res; - for (char *s = res; *s; ++s) { + for (char *s = res; *s; s++) { if (s[0] == CAR && s[1] == NL) { - ++s; + s++; } *d++ = *s; @@ -6499,12 +6339,9 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret // Argument can be [lnum, col, coladd]. if (tv->v_type == VAR_LIST) { - list_T *l; - int len; bool error = false; - listitem_T *li; - l = tv->vval.v_list; + list_T *l = tv->vval.v_list; if (l == NULL) { return NULL; } @@ -6521,6 +6358,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (error) { return NULL; } + int len; if (charcol) { len = mb_charlen(ml_get(pos.lnum)); } else { @@ -6528,7 +6366,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret } // We accept "$" for the column number: last column. - li = tv_list_find(l, 1L); + listitem_T *li = tv_list_find(l, 1L); if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING && TV_LIST_ITEM_TV(li)->vval.v_string != NULL && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { @@ -6628,8 +6466,6 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol) { list_T *l; - int i = 0; - long n; // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only // there when "fnump" isn't NULL; "coladd" and "curswant" are optional. @@ -6640,6 +6476,8 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c return FAIL; } + int i = 0; + long n; if (fnump != NULL) { n = tv_list_find_nr(l, i++, NULL); // fnum if (n < 0) { @@ -6692,15 +6530,13 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c /// @return 0 for error. int get_env_len(const char **arg) { - int len; - const char *p; for (p = *arg; vim_isIDc(*p); p++) {} if (p == *arg) { // No name found. return 0; } - len = (int)(p - *arg); + int len = (int)(p - *arg); *arg = p; return len; } @@ -6748,8 +6584,6 @@ int get_id_len(const char **const arg) /// 0 if something else is wrong. int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbose) { - int len; - *alias = NULL; // default to no alias if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA @@ -6758,7 +6592,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo *arg += 3; return get_id_len(arg) + 3; } - len = eval_fname_script(*arg); + int len = eval_fname_script(*arg); if (len > 0) { // literal "<SID>", "s:" or "<SNR>" *arg += len; @@ -6776,10 +6610,8 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo return len; } - /* - * Include any <SID> etc in the expanded string: - * Thus the -len here. - */ + // Include any <SID> etc in the expanded string: + // Thus the -len here. char *temp_string = make_expanded_name(*arg - len, expr_start, expr_end, (char *)p); if (temp_string == NULL) { return -1; @@ -6811,10 +6643,6 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo const char *find_name_end(const char *arg, const char **expr_start, const char **expr_end, int flags) { - int mb_nest = 0; - int br_nest = 0; - int len; - if (expr_start != NULL) { *expr_start = NULL; *expr_end = NULL; @@ -6825,6 +6653,10 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * return arg; } + int mb_nest = 0; + int br_nest = 0; + int len; + const char *p; for (p = arg; *p != NUL && (eval_isnamec(*p) @@ -6842,7 +6674,7 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * // skip over "str\"ing" to avoid counting [ and ] inside it. for (p = p + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { - ++p; + p++; } } if (*p == NUL) { @@ -6860,9 +6692,9 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * if (mb_nest == 0) { if (*p == '[') { - ++br_nest; + br_nest++; } else if (*p == ']') { - --br_nest; + br_nest--; } } @@ -6898,20 +6730,19 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * static char *make_expanded_name(const char *in_start, char *expr_start, char *expr_end, char *in_end) { - char c1; - char *retval = NULL; - char *temp_result; - char *nextcmd = NULL; - if (expr_end == NULL || in_end == NULL) { return NULL; } + + char *retval = NULL; + char *nextcmd = NULL; + *expr_start = NUL; *expr_end = NUL; - c1 = *in_end; + char c1 = *in_end; *in_end = NUL; - temp_result = eval_to_string(expr_start + 1, &nextcmd, false); + char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false); if (temp_result != NULL && nextcmd == NULL) { retval = xmalloc(STRLEN(temp_result) + (size_t)(expr_start - in_start) + (size_t)(in_end - expr_end) + 1); @@ -7354,7 +7185,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int if (rettv->v_type == VAR_DICT) { selfdict = rettv->vval.v_dict; if (selfdict != NULL) { - ++selfdict->dv_refcount; + selfdict->dv_refcount++; } } else { selfdict = NULL; @@ -7431,8 +7262,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va const size_t varname_len, int no_autoload) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - hashitem_T *hi; - if (varname_len == 0) { // Must be something like "s:", otherwise "ht" would be NULL. switch (htname) { @@ -7456,7 +7285,7 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va return NULL; } - hi = hash_find_len(ht, varname, varname_len); + hashitem_T *hi = hash_find_len(ht, varname, varname_len); if (HASHITEM_EMPTY(hi)) { // For global variables we may try auto-loading the script. If it // worked find the variable again. Don't auto-load a script if it was @@ -7491,7 +7320,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char **varname, dict_T **d) { - hashitem_T *hi; funccall_T *funccal = get_funccal(); *d = NULL; @@ -7507,7 +7335,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char *varname = name; // "version" is "v:version" in all scopes - hi = hash_find_len(&compat_hashtab, name, name_len); + hashitem_T *hi = hash_find_len(&compat_hashtab, name, name_len); if (!HASHITEM_EMPTY(hi)) { return &compat_hashtab; } @@ -7596,28 +7424,26 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var /// sourcing this script and when executing functions defined in the script. void new_script_vars(scid_T id) { - hashtab_T *ht; scriptvar_T *sv; ga_grow(&ga_scripts, id - ga_scripts.ga_len); - { - /* Re-allocating ga_data means that an ht_array pointing to - * ht_smallarray becomes invalid. We can recognize this: ht_mask is - * at its init value. Also reset "v_dict", it's always the same. */ - for (int i = 1; i <= ga_scripts.ga_len; ++i) { - ht = &SCRIPT_VARS(i); - if (ht->ht_mask == HT_INIT_SIZE - 1) { - ht->ht_array = ht->ht_smallarray; - } - sv = SCRIPT_SV(i); - sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict; - } - while (ga_scripts.ga_len < id) { - sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T)); - init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE); - ++ga_scripts.ga_len; + // Re-allocating ga_data means that an ht_array pointing to + // ht_smallarray becomes invalid. We can recognize this: ht_mask is + // at its init value. Also reset "v_dict", it's always the same. + for (int i = 1; i <= ga_scripts.ga_len; i++) { + hashtab_T *ht = &SCRIPT_VARS(i); + if (ht->ht_mask == HT_INIT_SIZE - 1) { + ht->ht_array = ht->ht_smallarray; } + sv = SCRIPT_SV(i); + sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict; + } + + while (ga_scripts.ga_len < id) { + sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T)); + init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE); + ga_scripts.ga_len++; } } @@ -7641,8 +7467,8 @@ void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, ScopeType scope) /// Unreference a dictionary initialized by init_var_dict(). void unref_var_dict(dict_T *dict) { - /* Now the dict needs to be freed if no one else is using it, go back to - * normal reference counting. */ + // Now the dict needs to be freed if no one else is using it, go back to + // normal reference counting. dict->dv_refcount -= DO_NOT_FREE_CNT - 1; tv_dict_unref(dict); } @@ -7674,7 +7500,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c emsg(_("E698: variable nested too deep for making a copy")); return FAIL; } - ++recurse; + recurse++; switch (from->v_type) { case VAR_NUMBER: @@ -7727,7 +7553,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c } else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) { // use the copy made earlier to->vval.v_dict = from->vval.v_dict->dv_copydict; - ++to->vval.v_dict->dv_refcount; + to->vval.v_dict->dv_refcount++; } else { to->vval.v_dict = tv_dict_copy(conv, from->vval.v_dict, deep, copyID); } @@ -7739,7 +7565,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c internal_error("var_item_copy(UNKNOWN)"); ret = FAIL; } - --recurse; + recurse--; return ret; } @@ -7756,7 +7582,7 @@ void ex_echo(exarg_T *eap) const int called_emsg_before = called_emsg; if (eap->skip) { - ++emsg_skip; + emsg_skip++; } while (*arg != NUL && *arg != '|' && *arg != '\n' && !got_int) { // If eval1() causes an error message the text from the command may @@ -7836,12 +7662,11 @@ void ex_execute(exarg_T *eap) typval_T rettv; int ret = OK; garray_T ga; - int save_did_emsg; ga_init(&ga, 1, 80); if (eap->skip) { - ++emsg_skip; + emsg_skip++; } while (*arg != NUL && *arg != '|' && *arg != '\n') { ret = eval1_emsg(&arg, &rettv, !eap->skip); @@ -7885,7 +7710,7 @@ void ex_execute(exarg_T *eap) ui_flush(); } else if (eap->cmdidx == CMD_echoerr) { // We don't want to abort following commands, restore did_emsg. - save_did_emsg = did_emsg; + int save_did_emsg = did_emsg; msg_ext_set_kind("echoerr"); emsg(ga.ga_data); if (!force_abort) { @@ -7899,7 +7724,7 @@ void ex_execute(exarg_T *eap) ga_clear(&ga); if (eap->skip) { - --emsg_skip; + emsg_skip--; } eap->nextcmd = (char *)check_nextcmd((char_u *)arg); @@ -7915,7 +7740,7 @@ const char *find_option_end(const char **const arg, int *const opt_flags) { const char *p = *arg; - ++p; + p++; if (*p == 'g' && p[1] == ':') { *opt_flags = OPT_GLOBAL; p += 2; @@ -7941,174 +7766,6 @@ const char *find_option_end(const char **const arg, int *const opt_flags) return p; } -/// Start profiling function "fp". -void func_do_profile(ufunc_T *fp) -{ - int len = fp->uf_lines.ga_len; - - if (!fp->uf_prof_initialized) { - if (len == 0) { - len = 1; // avoid getting error for allocating zero bytes - } - fp->uf_tm_count = 0; - fp->uf_tm_self = profile_zero(); - fp->uf_tm_total = profile_zero(); - - if (fp->uf_tml_count == NULL) { - fp->uf_tml_count = xcalloc((size_t)len, sizeof(int)); - } - - if (fp->uf_tml_total == NULL) { - fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T)); - } - - if (fp->uf_tml_self == NULL) { - fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T)); - } - - fp->uf_tml_idx = -1; - fp->uf_prof_initialized = true; - } - - fp->uf_profiling = TRUE; -} - -/// Dump the profiling results for all functions in file "fd". -void func_dump_profile(FILE *fd) -{ - hashitem_T *hi; - int todo; - ufunc_T *fp; - ufunc_T **sorttab; - int st_len = 0; - - todo = (int)func_hashtab.ht_used; - if (todo == 0) { - return; // nothing to dump - } - - sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo); - - for (hi = func_hashtab.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - fp = HI2UF(hi); - if (fp->uf_prof_initialized) { - sorttab[st_len++] = fp; - - if (fp->uf_name[0] == K_SPECIAL) { - fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3); - } else { - fprintf(fd, "FUNCTION %s()\n", fp->uf_name); - } - if (fp->uf_script_ctx.sc_sid != 0) { - bool should_free; - const LastSet last_set = (LastSet){ - .script_ctx = fp->uf_script_ctx, - .channel_id = 0, - }; - char *p = (char *)get_scriptname(last_set, &should_free); - fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", - p, fp->uf_script_ctx.sc_lnum); - if (should_free) { - xfree(p); - } - } - if (fp->uf_tm_count == 1) { - fprintf(fd, "Called 1 time\n"); - } else { - fprintf(fd, "Called %d times\n", fp->uf_tm_count); - } - fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total)); - fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self)); - fprintf(fd, "\n"); - fprintf(fd, "count total (s) self (s)\n"); - - for (int i = 0; i < fp->uf_lines.ga_len; ++i) { - if (FUNCLINE(fp, i) == NULL) { - continue; - } - prof_func_line(fd, fp->uf_tml_count[i], - &fp->uf_tml_total[i], &fp->uf_tml_self[i], TRUE); - fprintf(fd, "%s\n", FUNCLINE(fp, i)); - } - fprintf(fd, "\n"); - } - } - } - - if (st_len > 0) { - qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), - prof_total_cmp); - prof_sort_list(fd, sorttab, st_len, "TOTAL", FALSE); - qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), - prof_self_cmp); - prof_sort_list(fd, sorttab, st_len, "SELF", TRUE); - } - - xfree(sorttab); -} - -/// @param prefer_self when equal print only self time -static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, int prefer_self) -{ - int i; - ufunc_T *fp; - - fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title); - fprintf(fd, "count total (s) self (s) function\n"); - for (i = 0; i < 20 && i < st_len; ++i) { - fp = sorttab[i]; - prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self, - prefer_self); - if (fp->uf_name[0] == K_SPECIAL) { - fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); - } else { - fprintf(fd, " %s()\n", fp->uf_name); - } - } - fprintf(fd, "\n"); -} - -/// Print the count and times for one function or function line. -/// -/// @param prefer_self when equal print only self time -static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self, - int prefer_self) -{ - if (count > 0) { - fprintf(fd, "%5d ", count); - if (prefer_self && profile_equal(*total, *self)) { - fprintf(fd, " "); - } else { - fprintf(fd, "%s ", profile_msg(*total)); - } - if (!prefer_self && profile_equal(*total, *self)) { - fprintf(fd, " "); - } else { - fprintf(fd, "%s ", profile_msg(*self)); - } - } else { - fprintf(fd, " "); - } -} - -/// Compare function for total time sorting. -static int prof_total_cmp(const void *s1, const void *s2) -{ - ufunc_T *p1 = *(ufunc_T **)s1; - ufunc_T *p2 = *(ufunc_T **)s2; - return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); -} - -/// Compare function for self time sorting. -static int prof_self_cmp(const void *s1, const void *s2) -{ - ufunc_T *p1 = *(ufunc_T **)s1; - ufunc_T *p2 = *(ufunc_T **)s2; - return profile_cmp(p1->uf_tm_self, p2->uf_tm_self); -} - /// Return the autoload script name for a function or variable name /// Caller must make sure that "name" contains AUTOLOAD_CHAR. /// @@ -8183,61 +7840,6 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r return ret; } -/// Called when starting to read a function line. -/// "sourcing_lnum" must be correct! -/// When skipping lines it may not actually be executed, but we won't find out -/// until later and we need to store the time now. -void func_line_start(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - - if (fp->uf_profiling && sourcing_lnum >= 1 - && sourcing_lnum <= fp->uf_lines.ga_len) { - fp->uf_tml_idx = sourcing_lnum - 1; - // Skip continuation lines. - while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { - fp->uf_tml_idx--; - } - fp->uf_tml_execed = false; - fp->uf_tml_start = profile_start(); - fp->uf_tml_children = profile_zero(); - fp->uf_tml_wait = profile_get_wait(); - } -} - -/// Called when actually executing a function line. -void func_line_exec(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - - if (fp->uf_profiling && fp->uf_tml_idx >= 0) { - fp->uf_tml_execed = TRUE; - } -} - -/// Called when done with a function line. -void func_line_end(void *cookie) -{ - funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; - - if (fp->uf_profiling && fp->uf_tml_idx >= 0) { - if (fp->uf_tml_execed) { - ++fp->uf_tml_count[fp->uf_tml_idx]; - fp->uf_tml_start = profile_end(fp->uf_tml_start); - fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start); - fp->uf_tml_total[fp->uf_tml_idx] = - profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start); - fp->uf_tml_self[fp->uf_tml_idx] = - profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start, - fp->uf_tml_children); - } - fp->uf_tml_idx = -1; - } -} - static var_flavour_T var_flavour(char *varname) FUNC_ATTR_PURE { @@ -8325,11 +7927,9 @@ int store_session_globals(FILE *fd) } if ((fprintf(fd, "let %s = %c%s%c", this_var->di_key, - ((this_var->di_tv.v_type == VAR_STRING) ? '"' - : ' '), + ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' '), p, - ((this_var->di_tv.v_type == VAR_STRING) ? '"' - : ' ')) < 0) + ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' ')) < 0) || put_eol(fd) == FAIL) { xfree(p); return FAIL; @@ -8414,10 +8014,8 @@ int modify_fname(char *src, bool tilde_file, size_t *usedlen, char **fnamep, cha size_t *fnamelen) { int valid = 0; - char *tail; char *s, *p, *pbuf; char dirname[MAXPATHL]; - int c; bool has_fullname = false; bool has_homerelative = false; @@ -8479,6 +8077,8 @@ repeat: } } + int c; + // ":." - path relative to the current directory // ":~" - path relative to the home directory // ":8" - shortname path - postponed till after @@ -8544,7 +8144,7 @@ repeat: } } - tail = path_tail(*fnamep); + char *tail = path_tail(*fnamep); *fnamelen = STRLEN(*fnamep); // ":h" - head, remove "/file_name", can be repeated @@ -8585,10 +8185,9 @@ repeat: // ":r" - root, without extension, can be repeated while (src[*usedlen] == ':' && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) { - /* find a '.' in the tail: - * - for second :e: before the current fname - * - otherwise: The last '.' - */ + // find a '.' in the tail: + // - for second :e: before the current fname + // - otherwise: The last '.' const bool is_second_e = *fnamep > tail; if (src[*usedlen + 1] == 'e' && is_second_e) { s = (*fnamep) - 2; @@ -8639,18 +8238,16 @@ repeat: if (src[*usedlen] == ':' && (src[*usedlen + 1] == 's' || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) { - int sep; - char *flags; - int didit = false; + bool didit = false; - flags = ""; + char *flags = ""; s = src + *usedlen + 2; if (src[*usedlen + 1] == 'g') { flags = "g"; s++; } - sep = (char_u)(*s++); + int sep = (char_u)(*s++); if (sep) { // find end of pattern p = vim_strchr(s, sep); @@ -8668,7 +8265,7 @@ repeat: *fnamelen = STRLEN(s); xfree(*bufp); *bufp = s; - didit = TRUE; + didit = true; xfree(sub); xfree(str); } @@ -8709,26 +8306,22 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags { int sublen; regmatch_T regmatch; - int do_all; - char *tail; - char *end; garray_T ga; - char *save_cpo; char *zero_width = NULL; // Make 'cpoptions' empty, so that the 'l' flag doesn't work here - save_cpo = p_cpo; + char *save_cpo = p_cpo; p_cpo = (char *)empty_option; ga_init(&ga, 1, 200); - do_all = (flags[0] == 'g'); + int do_all = (flags[0] == 'g'); regmatch.rm_ic = p_ic; regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { - tail = str; - end = str + STRLEN(str); + char *tail = str; + char *end = str + STRLEN(str); while (vim_regexec_nl(®match, (char_u *)str, (colnr_T)(tail - str))) { // Skip empty match except for first match. if (regmatch.startp[0] == regmatch.endp[0]) { @@ -8869,8 +8462,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo struct caller_scope saved_provider_caller_scope = provider_caller_scope; provider_caller_scope = (struct caller_scope) { .script_ctx = current_sctx, - .sourcing_name = sourcing_name, - .sourcing_lnum = sourcing_lnum, + .es_entry = ((estack_T *)exestack.ga_data)[exestack.ga_len - 1], .autocmd_fname = autocmd_fname, .autocmd_match = autocmd_match, .autocmd_bufnr = autocmd_bufnr, @@ -8969,8 +8561,8 @@ bool eval_has_provider(const char *feat) /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. void eval_fmt_source_name_line(char *buf, size_t bufsize) { - if (sourcing_name) { - snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum); + if (SOURCING_NAME) { + snprintf(buf, bufsize, "%s:%" PRIdLINENR, SOURCING_NAME, SOURCING_LNUM); } else { snprintf(buf, bufsize, "?"); } @@ -9012,8 +8604,6 @@ void invoke_prompt_callback(void) { typval_T rettv; typval_T argv[2]; - char *text; - char *prompt; linenr_T lnum = curbuf->b_ml.ml_line_count; // Add a new line for the prompt before invoking the callback, so that @@ -9025,8 +8615,8 @@ void invoke_prompt_callback(void) if (curbuf->b_prompt_callback.type == kCallbackNone) { return; } - text = (char *)ml_get(lnum); - prompt = (char *)prompt_text(); + char *text = (char *)ml_get(lnum); + char *prompt = (char *)prompt_text(); if (STRLEN(text) >= STRLEN(prompt)) { text += STRLEN(prompt); } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 7dbd18737a..b0cb5fd8c1 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -11,15 +11,6 @@ #define COPYID_INC 2 #define COPYID_MASK (~0x1) -// All user-defined functions are found in this hashtable. -extern hashtab_T func_hashtab; - -// From user function to hashitem and back. -EXTERN ufunc_T dumuf; -#define UF2HIKEY(fp) ((fp)->uf_name) -#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) -#define HI2UF(hi) HIKEY2UF((hi)->hi_key) - /* * Structure returned by get_lval() and used by set_var_lval(). * For a plain name: diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 6d8776d08b..e4e9b34ec6 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -72,6 +72,7 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, + charclass={args=1, base=1}, charcol={args=1, base=1}, charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, @@ -327,6 +328,7 @@ return { serverstop={args=1}, setbufline={args=3, base=3}, setbufvar={args=3, base=3}, + setcellwidths={args=1, base=1}, setcharpos={args=2, base=2}, setcharsearch={args=1, base=1}, setcmdpos={args=1, base=1}, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 090939666d..bb514fba47 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -30,12 +30,12 @@ #include "nvim/vim.h" // For _() const char *const encode_bool_var_names[] = { - [kBoolVarTrue] = "true", - [kBoolVarFalse] = "false", + [kBoolVarTrue] = "v:true", + [kBoolVarFalse] = "v:false", }; const char *const encode_special_var_names[] = { - [kSpecialVarNull] = "null", + [kSpecialVarNull] = "v:null", }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f24285063e..3b9dc92137 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7,12 +7,14 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -27,10 +29,12 @@ #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/if_cscope.h" @@ -45,6 +49,7 @@ #include "nvim/match.h" #include "nvim/math.h" #include "nvim/memline.h" +#include "nvim/menu.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" @@ -52,18 +57,20 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/dl.h" -#include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/plines.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sha256.h" #include "nvim/sign.h" #include "nvim/spell.h" +#include "nvim/spellsuggest.h" #include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/tag.h" @@ -117,13 +124,12 @@ static va_list dummy_ap; char *get_function_name(expand_T *xp, int idx) { static int intidx = -1; - char_u *name; if (idx == 0) { intidx = -1; } if (intidx < 0) { - name = (char_u *)get_user_func_name(xp, idx); + char_u *name = (char_u *)get_user_func_name(xp, idx); if (name != NULL) { if (*name != NUL && *name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) { @@ -154,13 +160,12 @@ char *get_function_name(expand_T *xp, int idx) char *get_expr_name(expand_T *xp, int idx) { static int intidx = -1; - char_u *name; if (idx == 0) { intidx = -1; } if (intidx < 0) { - name = (char_u *)get_function_name(xp, idx); + char_u *name = (char_u *)get_function_name(xp, idx); if (name != NULL) { return (char *)name; } @@ -294,10 +299,9 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_FLOAT) { float_op_wrapper(argvars, rettv, (FunPtr)&fabs); } else { - varnumber_T n; bool error = false; - n = tv_get_number_chk(&argvars[0], &error); + varnumber_T n = tv_get_number_chk(&argvars[0], &error); if (error) { rettv->vval.v_number = -1; } else if (n > 0) { @@ -371,77 +375,6 @@ static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - // use the current window - rettv->vval.v_number = ARGCOUNT; - } else if (argvars[0].v_type == VAR_NUMBER - && tv_get_number(&argvars[0]) == -1) { - // use the global argument list - rettv->vval.v_number = GARGCOUNT; - } else { - // use the argument list of the specified window - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) { - rettv->vval.v_number = WARGCOUNT(wp); - } else { - rettv->vval.v_number = -1; - } - } -} - -/// "argidx()" function -static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curwin->w_arg_idx; -} - -/// "arglistid" function -static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - win_T *wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp != NULL) { - rettv->vval.v_number = wp->w_alist->id; - } -} - -/// "argv(nr)" function -static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - aentry_T *arglist = NULL; - int argcount = -1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type == VAR_UNKNOWN) { - arglist = ARGLIST; - argcount = ARGCOUNT; - } else if (argvars[1].v_type == VAR_NUMBER - && tv_get_number(&argvars[1]) == -1) { - arglist = GARGLIST; - argcount = GARGCOUNT; - } else { - win_T *wp = find_win_by_nr_or_id(&argvars[1]); - if (wp != NULL) { - // Use the argument list of the specified window - arglist = WARGLIST(wp); - argcount = WARGCOUNT(wp); - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - int idx = (int)tv_get_number_chk(&argvars[0], NULL); - if (arglist != NULL && idx >= 0 && idx < argcount) { - rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); - } else if (idx == -1) { - get_arglist_as_rettv(arglist, argcount, rettv); - } - } else { - get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); - } -} - /// "atan2()" function static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -479,8 +412,8 @@ static buf_T *find_buffer(typval_T *avar) } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { buf = buflist_findname_exp(avar->vval.v_string); if (buf == NULL) { - /* No full path name match, try a match with a URL or a "nofile" - * buffer, these don't use the full path. */ + // No full path name match, try a match with a URL or a "nofile" + // buffer, these don't use the full path. FOR_ALL_BUFFERS(bp) { if (bp->b_fname != NULL && (path_with_url(bp->b_fname) || bt_nofilename(bp)) @@ -632,17 +565,15 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Get buffer by number or pattern. buf_T *tv_get_buf(typval_T *tv, int curtab_only) { - char_u *name = (char_u *)tv->vval.v_string; - int save_magic; - char *save_cpo; - buf_T *buf; - if (tv->v_type == VAR_NUMBER) { return buflist_findnr((int)tv->vval.v_number); } if (tv->v_type != VAR_STRING) { return NULL; } + + char_u *name = (char_u *)tv->vval.v_string; + if (name == NULL || *name == NUL) { return curbuf; } @@ -651,13 +582,13 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) } // Ignore 'magic' and 'cpoptions' here to make scripts portable - save_magic = p_magic; - p_magic = TRUE; - save_cpo = p_cpo; + int save_magic = p_magic; + p_magic = true; + char *save_cpo = p_cpo; p_cpo = ""; - buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), - true, false, curtab_only)); + buf_T *buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), + true, false, curtab_only)); p_magic = save_magic; p_cpo = save_cpo; @@ -686,10 +617,8 @@ buf_T *tv_get_buf_from_arg(typval_T *const tv) FUNC_ATTR_NONNULL_ALL /// valid. buf_T *get_buf_arg(typval_T *arg) { - buf_T *buf; - emsg_off++; - buf = tv_get_buf(arg, false); + buf_T *buf = tv_get_buf(arg, false); emsg_off--; if (buf == NULL) { semsg(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); @@ -735,13 +664,13 @@ static void byteidx(typval_T *argvars, typval_T *rettv, int comp) /// "byteidx()" function static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - byteidx(argvars, rettv, FALSE); + byteidx(argvars, rettv, false); } /// "byteidxcomp()" function static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - byteidx(argvars, rettv, TRUE); + byteidx(argvars, rettv, true); } /// "call(func, arglist [, dict])" function @@ -758,7 +687,6 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool owned = false; char_u *func; partial_T *partial = NULL; - dict_T *selfdict = NULL; if (argvars[0].v_type == VAR_FUNC) { func = (char_u *)argvars[0].vval.v_string; } else if (argvars[0].v_type == VAR_PARTIAL) { @@ -776,6 +704,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // type error or empty name } + dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT) { emsg(_(e_dictreq)); @@ -898,10 +827,9 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) { colnr_T col = 0; - pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], false, &fnum, charcol); + pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol); if (fp != NULL && fnum == curbuf->b_fnum) { if (fp->col == MAXCOL) { // '> can be MAXCOL, get the length of the line then @@ -984,9 +912,6 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "chdir(dir)" function static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char_u *cwd; - CdScope scope = kCdScopeGlobal; - rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -997,7 +922,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Return the current directory - cwd = xmalloc(MAXPATHL); + char_u *cwd = xmalloc(MAXPATHL); if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(cwd); @@ -1006,6 +931,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } xfree(cwd); + CdScope scope = kCdScopeGlobal; if (curwin->w_localdir != NULL) { scope = kCdScopeWindow; } else if (curtab->tp_localdir != NULL) { @@ -1021,11 +947,8 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "cindent(lnum)" function static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T pos; - linenr_T lnum; - - pos = curwin->w_cursor; - lnum = tv_get_lnum(argvars); + pos_T pos = curwin->w_cursor; + linenr_T lnum = tv_get_lnum(argvars); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = lnum; rettv->vval.v_number = get_c_indent(); @@ -1060,14 +983,12 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; char buf2[NUMBUFLEN]; - const char *message; const char *buttons = NULL; int def = 1; int type = VIM_GENERIC; - const char *typestr; bool error = false; - message = tv_get_string_chk(&argvars[0]); + const char *message = tv_get_string_chk(&argvars[0]); if (message == NULL) { error = true; } @@ -1079,7 +1000,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type != VAR_UNKNOWN) { def = (int)tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { - typestr = tv_get_string_buf_chk(&argvars[3], buf2); + const char *typestr = tv_get_string_buf_chk(&argvars[3], buf2); if (typestr == NULL) { error = true; } else { @@ -1152,15 +1073,13 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else if (argvars[0].v_type == VAR_LIST) { - listitem_T *li; - list_T *l; - long idx; + list_T *l = argvars[0].vval.v_list; - if ((l = argvars[0].vval.v_list) != NULL) { - li = tv_list_first(l); + if (l != NULL) { + listitem_T *li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[3].v_type != VAR_UNKNOWN) { - idx = tv_get_number_chk(&argvars[3], &error); + long idx = tv_get_number_chk(&argvars[3], &error); if (!error) { li = tv_list_find(l, (int)idx); if (li == NULL) { @@ -1180,19 +1099,17 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else if (argvars[0].v_type == VAR_DICT) { - int todo; - dict_T *d; - hashitem_T *hi; + dict_T *d = argvars[0].vval.v_dict; - if ((d = argvars[0].vval.v_dict) != NULL) { + if (d != NULL) { if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[3].v_type != VAR_UNKNOWN) { emsg(_(e_invarg)); } } - todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + int todo = error ? 0 : (int)d->dv_hashtab.ht_used; + for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { @@ -1414,10 +1331,8 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "debugbreak()" function static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int pid; - rettv->vval.v_number = FAIL; - pid = (int)tv_get_number(&argvars[0]); + int pid = (int)tv_get_number(&argvars[0]); if (pid == 0) { emsg(_(e_invarg)); } else { @@ -1564,10 +1479,6 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "deletebufline()" function static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - linenr_T last; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - buf_T *const buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { rettv->vval.v_number = 1; // FAIL @@ -1576,6 +1487,7 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const bool is_curbuf = buf == curbuf; const bool save_VIsual_active = VIsual_active; + linenr_T last; const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); if (argvars[2].v_type != VAR_UNKNOWN) { last = tv_get_lnum_buf(&argvars[2], buf); @@ -1589,6 +1501,8 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + buf_T *curbuf_save = NULL; + win_T *curwin_save = NULL; if (!is_curbuf) { VIsual_active = false; curbuf_save = curbuf; @@ -1660,8 +1574,6 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int change_start = 0; static int change_end = 0; static hlf_T hlID = (hlf_T)0; - int filler_lines; - int col; if (lnum < 0) { // ignore type error in {lnum} arg lnum = 0; @@ -1670,7 +1582,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) || changedtick != buf_get_changedtick(curbuf) || fnum != curbuf->b_fnum) { // New line, buffer, change: need to get the values. - filler_lines = diff_check(curwin, lnum); + int filler_lines = diff_check(curwin, lnum); if (filler_lines < 0) { if (filler_lines == -1) { change_start = MAXCOL; @@ -1692,7 +1604,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. + int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. if (col >= change_start && col <= change_end) { hlID = HLF_TXD; // Changed text. } else { @@ -2042,12 +1954,9 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "expand()" function static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - size_t len; char *errormsg; int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; - expand_T xpc; bool error = false; - char_u *result; #ifdef BACKSLASH_IN_FILENAME char_u *p_csl_save = p_csl; @@ -2066,7 +1975,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *s = tv_get_string(&argvars[0]); if (*s == '%' || *s == '#' || *s == '<') { emsg_off++; - result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); + size_t len; + char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); @@ -2085,6 +1995,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) options |= WILD_KEEP_ALL; } if (!error) { + expand_T xpc; ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; if (p_wic) { @@ -2151,8 +2062,6 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "flatten(list[, {maxdepth}])" function static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *list; - long maxdepth; bool error = false; if (argvars[0].v_type != VAR_LIST) { @@ -2160,6 +2069,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + long maxdepth; if (argvars[1].v_type == VAR_UNKNOWN) { maxdepth = 999999; } else { @@ -2173,7 +2083,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list = argvars[0].vval.v_list; + list_T *list = argvars[0].vval.v_list; if (list != NULL && !var_check_lock(tv_list_locked(list), N_("flatten() argument"), @@ -2190,7 +2100,6 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const arg_errmsg = N_("extend() argument"); if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - long before; bool error = false; list_T *const l1 = argvars[0].vval.v_list; @@ -2198,7 +2107,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); + long before = (long)tv_get_number_chk(&argvars[2], &error); if (error) { return; // Type error; errmsg already given. } @@ -2360,7 +2269,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) /// "filter()" function static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - filter_map(argvars, rettv, FALSE); + filter_map(argvars, rettv, false); } /// "finddir({fname}[, {path}[, {count}]])" function @@ -2440,130 +2349,6 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(fbuf); } -/// "foldclosed()" function -static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - linenr_T first; - linenr_T last; - if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { - if (end) { - rettv->vval.v_number = (varnumber_T)last; - } else { - rettv->vval.v_number = (varnumber_T)first; - } - return; - } - } - rettv->vval.v_number = -1; -} - -/// "foldclosed()" function -static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, FALSE); -} - -/// "foldclosedend()" function -static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, TRUE); -} - -/// "foldlevel()" function -static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = foldLevel(lnum); - } -} - -/// "foldtext()" function -static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T foldstart; - linenr_T foldend; - char_u *dashes; - linenr_T lnum; - char_u *s; - char_u *r; - int len; - char *txt; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); - foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); - dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES); - if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { - // Find first non-empty line in the fold. - for (lnum = foldstart; lnum < foldend; lnum++) { - if (!linewhite(lnum)) { - break; - } - } - - // Find interesting text in this line. - s = (char_u *)skipwhite((char *)ml_get(lnum)); - // skip C comment-start - if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { - s = (char_u *)skipwhite((char *)s + 2); - if (*skipwhite((char *)s) == NUL && lnum + 1 < foldend) { - s = (char_u *)skipwhite((char *)ml_get(lnum + 1)); - if (*s == '*') { - s = (char_u *)skipwhite((char *)s + 1); - } - } - } - unsigned long count = (unsigned long)foldend - (unsigned long)foldstart + 1; - txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); - r = xmalloc(STRLEN(txt) - + STRLEN(dashes) // for %s - + 20 // for %3ld - + STRLEN(s)); // concatenated - sprintf((char *)r, txt, dashes, count); - len = (int)STRLEN(r); - STRCAT(r, s); - // remove 'foldmarker' and 'commentstring' - foldtext_cleanup(r + len); - rettv->vval.v_string = (char *)r; - } -} - -/// "foldtextresult(lnum)" function -static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *text; - char_u buf[FOLD_TEXT_LEN]; - static bool entered = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (entered) { - return; // reject recursive use - } - entered = true; - linenr_T lnum = tv_get_lnum(argvars); - // Treat illegal types and illegal string values for {lnum} the same. - if (lnum < 0) { - lnum = 0; - } - - foldinfo_T info = fold_info(curwin, lnum); - if (info.fi_lines > 0) { - text = get_foldtext(curwin, lnum, lnum + (linenr_T)info.fi_lines - 1, info, buf); - if (text == buf) { - text = vim_strsave(text); - } - rettv->vval.v_string = (char *)text; - } - - entered = false; -} - /// "foreground()" function static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) {} @@ -2593,10 +2378,6 @@ static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "get()" function static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; typval_T *tv = NULL; bool what_is_dict = false; @@ -2617,17 +2398,19 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL) { + list_T *l = argvars[0].vval.v_list; + if (l != NULL) { bool error = false; - li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error)); + listitem_T *li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { tv = TV_LIST_ITEM_TV(li); } } } else if (argvars[0].v_type == VAR_DICT) { - if ((d = argvars[0].vval.v_dict) != NULL) { - di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); + dict_T *d = argvars[0].vval.v_dict; + if (d != NULL) { + dictitem_T *di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); if (di != NULL) { tv = &di->di_tv; } @@ -2639,7 +2422,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_PARTIAL) { pt = argvars[0].vval.v_partial; } else { - memset(&fref_pt, 0, sizeof(fref_pt)); + CLEAR_FIELD(fref_pt); fref_pt.pt_name = (char_u *)argvars[0].vval.v_string; pt = &fref_pt; } @@ -2851,147 +2634,6 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "getchar()" and "getcharstr()" functions -static void getchar_common(typval_T *argvars, typval_T *rettv) - FUNC_ATTR_NONNULL_ALL -{ - varnumber_T n; - bool error = false; - - no_mapping++; - allow_keys++; - for (;;) { - // Position the cursor. Needed after a message that ends in a space, - // or if event processing caused a redraw. - ui_cursor_goto(msg_row, msg_col); - - if (argvars[0].v_type == VAR_UNKNOWN) { - // getchar(): blocking wait. - // TODO(bfredl): deduplicate shared logic with state_enter ? - if (!char_avail()) { - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); - if (!multiqueue_empty(main_loop.events)) { - state_handle_k_event(); - continue; - } - } - n = safe_vgetc(); - } else if (tv_get_number_chk(&argvars[0], &error) == 1) { - // getchar(1): only check if char avail - n = vpeekc_any(); - } else if (error || vpeekc_any() == NUL) { - // illegal argument or getchar(0) and no char avail: return zero - n = 0; - } else { - // getchar(0) and char avail() != NUL: get a character. - // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE. - n = safe_vgetc(); - } - - if (n == K_IGNORE - || n == K_MOUSEMOVE - || n == K_VER_SCROLLBAR - || n == K_HOR_SCROLLBAR) { - continue; - } - break; - } - no_mapping--; - allow_keys--; - - if (!ui_has_messages()) { - // redraw the screen after getchar() - update_screen(CLEAR); - } - - set_vim_var_nr(VV_MOUSE_WIN, 0); - set_vim_var_nr(VV_MOUSE_WINID, 0); - set_vim_var_nr(VV_MOUSE_LNUM, 0); - set_vim_var_nr(VV_MOUSE_COL, 0); - - rettv->vval.v_number = n; - if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { - char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 - int i = 0; - - // Turn a special key into three bytes, plus modifier. - if (mod_mask != 0) { - temp[i++] = K_SPECIAL; - temp[i++] = KS_MODIFIER; - temp[i++] = (char_u)mod_mask; - } - if (IS_SPECIAL(n)) { - temp[i++] = K_SPECIAL; - temp[i++] = (char_u)K_SECOND(n); - temp[i++] = K_THIRD(n); - } else { - i += utf_char2bytes((int)n, (char *)temp + i); - } - assert(i < 10); - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)vim_strsave(temp); - - if (is_mouse_key((int)n)) { - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - linenr_T lnum; - win_T *wp; - int winnr = 1; - - if (row >= 0 && col >= 0) { - // Find the window at the mouse coordinates and compute the - // text position. - win_T *const win = mouse_find_win(&grid, &row, &col); - if (win == NULL) { - return; - } - (void)mouse_comp_pos(win, &row, &col, &lnum); - for (wp = firstwin; wp != win; wp = wp->w_next) { - ++winnr; - } - set_vim_var_nr(VV_MOUSE_WIN, winnr); - set_vim_var_nr(VV_MOUSE_WINID, wp->handle); - set_vim_var_nr(VV_MOUSE_LNUM, lnum); - set_vim_var_nr(VV_MOUSE_COL, col + 1); - } - } - } -} - -/// "getchar()" function -static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getchar_common(argvars, rettv); -} - -/// "getcharstr()" function -static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getchar_common(argvars, rettv); - - if (rettv->v_type == VAR_NUMBER) { - char temp[7]; // mbyte-char: 6, NUL: 1 - const varnumber_T n = rettv->vval.v_number; - int i = 0; - - if (n != 0) { - i += utf_char2bytes((int)n, (char *)temp); - } - assert(i < 7); - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrdup(temp); - } -} - -/// "getcharmod()" function -static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = mod_mask; -} - static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol) { pos_T *fp = NULL; @@ -3203,15 +2845,15 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Numbers of the scope objects (window, tab) we want the working directory // of. A `-1` means to skip this scope, a `0` means the current object. int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeWindow] = 0, // Number of window to look at. [kCdScopeTabpage] = 0, // Number of tab to look at. }; char *cwd = NULL; // Current working directory to print char *from = NULL; // The original string to copy - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -3455,13 +3097,6 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buffer_lines(curbuf, lnum, end, retlist, rettv); } -/// "getloclist()" function -static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - get_qf_loc_list(false, wp, &argvars[1], rettv); -} - /// "getmarklist()" function static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -3483,8 +3118,6 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "getmousepos()" function static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *d; - win_T *wp; int row = mouse_row; int col = mouse_col; int grid = mouse_grid; @@ -3495,12 +3128,12 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) varnumber_T column = 0; tv_dict_alloc_ret(rettv); - d = rettv->vval.v_dict; + dict_T *d = rettv->vval.v_dict; tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1); tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1); - wp = mouse_find_win(&grid, &row, &col); + win_T *wp = mouse_find_win(&grid, &row, &col); if (wp != NULL) { int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; // The height is adjusted by 1 when there is a bottom border. This is not @@ -3546,12 +3179,6 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) getpos_both(argvars, rettv, false, false); } -/// "getqflist()" functions -static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_qf_loc_list(true, NULL, &argvars[0], rettv); -} - /// Common between getreg(), getreginfo() and getregtype(): get the register /// name from the first argument. /// Returns zero on error. @@ -3788,7 +3415,6 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Move the window wp into a new split of targetwin in a given direction static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) { - int dir; int height = wp->w_height; win_T *oldwin = curwin; @@ -3802,6 +3428,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags } // Remove the old window and frame from the tree of frames + int dir; (void)winframe_remove(wp, &dir, NULL); win_remove(wp, NULL); last_status(false); // may need to remove last status line @@ -3826,12 +3453,8 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags /// "win_splitmove()" function static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp; - win_T *targetwin; - int flags = 0, size = 0; - - wp = find_win_by_nr_or_id(&argvars[0]); - targetwin = find_win_by_nr_or_id(&argvars[1]); + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + win_T *targetwin = find_win_by_nr_or_id(&argvars[1]); if (wp == NULL || targetwin == NULL || wp == targetwin || !win_valid(wp) || !win_valid(targetwin) @@ -3841,6 +3464,8 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + int flags = 0, size = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { dict_T *d; dictitem_T *di; @@ -3890,8 +3515,8 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) expand_T xpc; bool error = false; - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. rettv->v_type = VAR_STRING; if (argvars[1].v_type != VAR_UNKNOWN) { if (tv_get_number_chk(&argvars[1], &error)) { @@ -3964,7 +3589,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (file != NULL && !error) { garray_T ga; ga_init(&ga, (int)sizeof(char_u *), 10); - globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); + globpath((char *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); @@ -4306,87 +3931,6 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "histadd()" function -static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType histype; - - rettv->vval.v_number = false; - if (check_secure()) { - return; - } - const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error - histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; - if (histype != HIST_INVALID) { - char buf[NUMBUFLEN]; - str = tv_get_string_buf(&argvars[1], buf); - if (*str != NUL) { - init_history(); - add_to_history(histype, (char_u *)str, false, NUL); - rettv->vval.v_number = true; - return; - } - } -} - -/// "histdel()" function -static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n; - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - n = 0; - } else if (argvars[1].v_type == VAR_UNKNOWN) { - // only one argument: clear entire history - n = clr_history(get_histtype(str, strlen(str), false)); - } else if (argvars[1].v_type == VAR_NUMBER) { - // index given: remove that entry - n = del_history_idx(get_histtype(str, strlen(str), false), - (int)tv_get_number(&argvars[1])); - } else { - // string given: remove all matching entries - char buf[NUMBUFLEN]; - n = del_history_entry(get_histtype(str, strlen(str), false), - (char_u *)tv_get_string_buf(&argvars[1], buf)); - } - rettv->vval.v_number = n; -} - -/// "histget()" function -static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType type; - int idx; - - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - rettv->vval.v_string = NULL; - } else { - type = get_histtype(str, strlen(str), false); - if (argvars[1].v_type == VAR_UNKNOWN) { - idx = get_history_idx(type); - } else { - idx = (int)tv_get_number_chk(&argvars[1], NULL); - } - // -1 on type error - rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx)); - } - rettv->v_type = VAR_STRING; -} - -/// "histnr()" function -static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const history = tv_get_string_chk(&argvars[0]); - HistoryType i = history == NULL - ? HIST_INVALID - : get_histtype(history, strlen(history), false); - if (i != HIST_INVALID) { - i = get_history_idx(i); - } - rettv->vval.v_number = i; -} - /// "highlightID(name)" function static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -4526,21 +4070,18 @@ static bool inputsecret_flag = false; /// Also handles inputsecret() when inputsecret is set. static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - get_user_input(argvars, rettv, FALSE, inputsecret_flag); + get_user_input(argvars, rettv, false, inputsecret_flag); } /// "inputdialog()" function static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - get_user_input(argvars, rettv, TRUE, inputsecret_flag); + get_user_input(argvars, rettv, true, inputsecret_flag); } /// "inputlist()" function static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int selected; - int mouse_used; - if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listarg), "inputlist()"); return; @@ -4558,7 +4099,8 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); // Ask for choice. - selected = prompt_for_number(&mouse_used); + int mouse_used; + int selected = prompt_for_number(&mouse_used); if (mouse_used) { selected -= lines_left; } @@ -4695,7 +4237,6 @@ static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) { lval_T lv; - dictitem_T *di; rettv->vval.v_number = -1; const char_u *const end = (char_u *)get_lval((char *)tv_get_string(&argvars[0]), @@ -4708,7 +4249,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) semsg(_(e_trailing_arg), end); } else { if (lv.ll_tv == NULL) { - di = find_var(lv.ll_name, lv.ll_name_len, NULL, true); + dictitem_T *di = find_var(lv.ll_name, lv.ll_name_len, NULL, true); if (di != NULL) { // Consider a variable locked when: // 1. the variable itself is locked @@ -5016,7 +4557,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. - if (!os_isdir_executable((const char *)cwd)) { + if (!os_isdir((const char_u *)cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -5410,7 +4951,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "map()" function static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - filter_map(argvars, rettv, TRUE); + filter_map(argvars, rettv, true); } static void find_some_match(typval_T *const argvars, typval_T *const rettv, @@ -5420,18 +4961,16 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, long len = 0; char_u *expr = NULL; regmatch_T regmatch; - char *save_cpo; long start = 0; long nth = 1; colnr_T startcol = 0; bool match = false; list_T *l = NULL; - listitem_T *li = NULL; long idx = 0; char_u *tofree = NULL; // Make 'cpoptions' empty, the 'l' flag should not be used here. - save_cpo = p_cpo; + char *save_cpo = p_cpo; p_cpo = ""; rettv->vval.v_number = -1; @@ -5458,6 +4997,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, break; } + listitem_T *li = NULL; if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL) { goto theend; @@ -5566,10 +5106,8 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz((const char *)regmatch.startp[0], rd); - TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( - regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( - regmatch.endp[0] - expr); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); if (l != NULL) { TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; } @@ -5710,13 +5248,13 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool /// "max()" function static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - max_min(argvars, rettv, TRUE); + max_min(argvars, rettv, true); } /// "min()" function static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - max_min(argvars, rettv, FALSE); + max_min(argvars, rettv, false); } /// "mkdir()" function @@ -6047,14 +5585,13 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; { - int len; int saved_did_emsg = did_emsg; // Get the required length, allocate the buffer and do it for real. did_emsg = false; char buf[NUMBUFLEN]; const char *fmt = tv_get_string_buf(&argvars[0], buf); - len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); + int len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); if (!did_emsg) { char *s = xmalloc((size_t)len + 1); rettv->vval.v_string = s; @@ -6067,13 +5604,12 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "prompt_setcallback({buffer}, {callback})" function static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; Callback prompt_callback = { .type = kCallbackNone }; if (check_secure()) { return; } - buf = tv_get_buf(&argvars[0], false); + buf_T *buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { return; } @@ -6091,13 +5627,12 @@ static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr /// "prompt_setinterrupt({buffer}, {callback})" function static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; Callback interrupt_callback = { .type = kCallbackNone }; if (check_secure()) { return; } - buf = tv_get_buf(&argvars[0], false); + buf_T *buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { return; } @@ -6342,13 +5877,11 @@ static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "range()" function static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - varnumber_T start; varnumber_T end; varnumber_T stride = 1; - varnumber_T i; bool error = false; - start = tv_get_number_chk(&argvars[0], &error); + varnumber_T start = tv_get_number_chk(&argvars[0], &error); if (argvars[1].v_type == VAR_UNKNOWN) { end = start - 1; start = 0; @@ -6368,7 +5901,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_("E727: Start past end")); } else { tv_list_alloc_ret(rettv, (end - start) / stride); - for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { + for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, i); } } @@ -6379,8 +5912,6 @@ static varnumber_T readdir_checkitem(void *context, const char *name) FUNC_ATTR_NONNULL_ALL { typval_T *expr = (typval_T *)context; - typval_T save_val; - typval_T rettv; typval_T argv[2]; varnumber_T retval = 0; bool error = false; @@ -6389,11 +5920,13 @@ static varnumber_T readdir_checkitem(void *context, const char *name) return 1; } + typval_T save_val; prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); argv[0].v_type = VAR_STRING; argv[0].vval.v_string = (char *)name; + typval_T rettv; if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { goto theend; } @@ -6435,12 +5968,11 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool binary = false; bool blob = false; FILE *fd; - char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 + char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); - int readlen; // size of last fread() - char_u *prev = NULL; // previously read bytes, if any - long prevlen = 0; // length of data in prev - long prevsize = 0; // size of prev buffer + char_u *prev = NULL; // previously read bytes, if any + long prevlen = 0; // length of data in prev + long prevsize = 0; // size of prev buffer long maxline = MAXLNUM; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -6482,7 +6014,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); while (maxline < 0 || tv_list_len(l) < maxline) { - readlen = (int)fread(buf, 1, (size_t)io_size, fd); + int readlen = (int)fread(buf, 1, (size_t)io_size, fd); // This for loop processes what was read, but is also entered at end // of file so that either: @@ -6514,9 +6046,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) assert(len < INT_MAX); s = vim_strnsave(start, len); } else { - /* Change "prev" buffer to be the right size. This way - * the bytes are only copied once, and very long lines are - * allocated only once. */ + // Change "prev" buffer to be the right size. This way + // the bytes are only copied once, and very long lines are + // allocated only once. s = xrealloc(prev, (size_t)prevlen + len + 1); memcpy(s + prevlen, start, len); s[(size_t)prevlen + len] = NUL; @@ -6590,10 +6122,10 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (start < p) { // There's part of a line in buf, store it in "prev". if (p - start + prevlen >= prevsize) { - /* A common use case is ordinary text files and "prev" gets a - * fragment of a line, so the first allocation is made - * small, to avoid repeatedly 'allocing' large and - * 'reallocing' small. */ + // A common use case is ordinary text files and "prev" gets a + // fragment of a line, so the first allocation is made + // small, to avoid repeatedly 'allocing' large and + // 'reallocing' small. if (prevsize == 0) { prevsize = (long)(p - start); } else { @@ -7142,7 +6674,6 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int get_search_arg(typval_T *varp, int *flagsp) { int dir = FORWARD; - int mask; if (varp->v_type != VAR_UNKNOWN) { char nbuf[NUMBUFLEN]; @@ -7150,6 +6681,7 @@ static int get_search_arg(typval_T *varp, int *flagsp) if (flags == NULL) { return 0; // Type error; errmsg already given. } + int mask; while (*flags != NUL) { switch (*flags) { case 'b': @@ -7199,26 +6731,19 @@ static int get_search_arg(typval_T *varp, int *flagsp) /// Shared by search() and searchpos() functions. static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { - int flags; - pos_T pos; - pos_T save_cursor; bool save_p_ws = p_ws; - int dir; int retval = 0; // default: FAIL long lnum_stop = 0; - proftime_T tm; long time_limit = 0; int options = SEARCH_KEEP; - int subpatnum; - searchit_arg_T sia; bool use_skip = false; const char *const pat = tv_get_string(&argvars[0]); - dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. + int dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. if (dir == 0) { goto theend; } - flags = *flagsp; + int flags = *flagsp; if (flags & SP_START) { options |= SEARCH_START; } @@ -7245,25 +6770,27 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } // Set the time limit, if there is one. - tm = profile_setlimit(time_limit); - - /* - * This function does not accept SP_REPEAT and SP_RETCOUNT flags. - * Check to make sure only those flags are set. - * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both - * flags cannot be set. Check for that condition also. - */ + proftime_T tm = profile_setlimit(time_limit); + + // This function does not accept SP_REPEAT and SP_RETCOUNT flags. + // Check to make sure only those flags are set. + // Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both + // flags cannot be set. Check for that condition also. if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { semsg(_(e_invarg2), tv_get_string(&argvars[1])); goto theend; } - pos = save_cursor = curwin->w_cursor; + pos_T save_cursor; + pos_T pos = save_cursor = curwin->w_cursor; pos_T firstpos = { 0 }; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = (linenr_T)lnum_stop; - sia.sa_tm = &tm; + searchit_arg_T sia = { + .sa_stop_lnum = (linenr_T)lnum_stop, + .sa_tm = &tm, + }; + + int subpatnum; // Repeat until {skip} returns false. for (;;) { @@ -7398,8 +6925,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) } sctx_T save_current_sctx; - char *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; - linenr_T save_sourcing_lnum; + char *save_autocmd_fname, *save_autocmd_match; int save_autocmd_bufnr; funccal_entry_T funccal_entry; @@ -7407,16 +6933,14 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) // If this is called from a provider function, restore the scope // information of the caller. save_current_sctx = current_sctx; - save_sourcing_name = sourcing_name; - save_sourcing_lnum = sourcing_lnum; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; save_autocmd_bufnr = autocmd_bufnr; save_funccal(&funccal_entry); current_sctx = provider_caller_scope.script_ctx; - sourcing_name = provider_caller_scope.sourcing_name; - sourcing_lnum = provider_caller_scope.sourcing_lnum; + ga_grow(&exestack, 1); + ((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; @@ -7433,8 +6957,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (l_provider_call_nesting) { current_sctx = save_current_sctx; - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + exestack.ga_len--; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; autocmd_bufnr = save_autocmd_bufnr; @@ -7568,14 +7091,13 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screenattr()" function static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int c; - - ScreenGrid *grid; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); + int c; if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { @@ -7587,14 +7109,13 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screenchar()" function static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int c; - - ScreenGrid *grid; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); + int c; if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { @@ -7606,10 +7127,10 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screenchars()" function static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - ScreenGrid *grid; int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { @@ -7640,10 +7161,6 @@ static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screenpos({winid}, {lnum}, {col})" function static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T pos; - int row = 0; - int scol = 0, ccol = 0, ecol = 0; - tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; @@ -7652,9 +7169,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - pos.lnum = (linenr_T)tv_get_number(&argvars[1]); - pos.col = (colnr_T)tv_get_number(&argvars[2]) - 1; - pos.coladd = 0; + pos_T pos = { + .lnum = (linenr_T)tv_get_number(&argvars[1]), + .col = (colnr_T)tv_get_number(&argvars[2]) - 1, + .coladd = 0 + }; + int row = 0; + int scol = 0, ccol = 0, ecol = 0; textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); tv_dict_add_nr(dict, S_LEN("row"), row); @@ -7722,7 +7243,6 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) { bool save_p_ws = p_ws; - int dir; int flags = 0; int retval = 0; // default: FAIL long lnum_stop = 0; @@ -7740,7 +7260,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } // Handle the optional fourth argument: flags. - dir = get_search_arg(&argvars[3], &flags); // may set p_ws. + int dir = get_search_arg(&argvars[3], &flags); // may set p_ws. if (dir == 0) { goto theend; } @@ -7834,33 +7354,24 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir long time_limit) FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - char *save_cpo; - char_u *pat, *pat2 = NULL, *pat3 = NULL; long retval = 0; - pos_T pos; - pos_T firstpos; - pos_T foundpos; - pos_T save_cursor; - pos_T save_pos; - int n; int nest = 1; bool use_skip = false; int options = SEARCH_KEEP; - proftime_T tm; // Make 'cpoptions' empty, the 'l' flag should not be used here. - save_cpo = p_cpo; + char *save_cpo = p_cpo; p_cpo = (char *)empty_option; // Set the time limit, if there is one. - tm = profile_setlimit(time_limit); + proftime_T tm = profile_setlimit(time_limit); // Make two search patterns: start/end (pat2, for in nested pairs) and // start/middle/end (pat3, for the top pair). const size_t pat2_len = strlen(spat) + strlen(epat) + 17; - pat2 = xmalloc(pat2_len); + char_u *pat2 = xmalloc(pat2_len); const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25; - pat3 = xmalloc(pat3_len); + char_u *pat3 = xmalloc(pat3_len); snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); if (*mpat == NUL) { STRCPY(pat3, pat2); @@ -7876,19 +7387,21 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir use_skip = eval_expr_valid_arg(skip); } - save_cursor = curwin->w_cursor; - pos = curwin->w_cursor; + pos_T save_cursor = curwin->w_cursor; + pos_T pos = curwin->w_cursor; + pos_T firstpos; clearpos(&firstpos); + pos_T foundpos; clearpos(&foundpos); - pat = pat3; + char_u *pat = pat3; for (;;) { - searchit_arg_T sia; - memset(&sia, 0, sizeof(sia)); - sia.sa_stop_lnum = lnum_stop; - sia.sa_tm = &tm; + searchit_arg_T sia = { + .sa_stop_lnum = lnum_stop, + .sa_tm = &tm, + }; - n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, &sia); + int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL break; @@ -7914,7 +7427,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir // If the skip pattern matches, ignore this match. if (use_skip) { - save_pos = curwin->w_cursor; + pos_T save_pos = curwin->w_cursor; curwin->w_cursor = pos; bool err = false; const bool r = eval_expr_to_bool(skip, &err); @@ -8103,13 +7616,13 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Otherwise use the column number as a byte offset. static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) { - pos_T pos; - int fnum; colnr_T curswant = -1; rettv->vval.v_number = -1; const char *const name = tv_get_string_chk(argvars); if (name != NULL) { + pos_T pos; + int fnum; if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) { if (pos.col != MAXCOL && --pos.col < 0) { pos.col = 0; @@ -8143,15 +7656,13 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *d; - dictitem_T *di; - if (argvars[0].v_type != VAR_DICT) { emsg(_(e_dictreq)); return; } - if ((d = argvars[0].vval.v_dict) != NULL) { + dict_T *d = argvars[0].vval.v_dict; + if (d != NULL) { char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); if (csearch != NULL) { int pcc[MAX_MCO]; @@ -8159,7 +7670,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_last_csearch(c, csearch, utfc_ptr2len((char *)csearch)); } - di = tv_dict_find(d, S_LEN("forward")); + dictitem_T *di = tv_dict_find(d, S_LEN("forward")); if (di != NULL) { set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); } @@ -8240,117 +7751,17 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); } -/// Create quickfix/location list from VimL values -/// -/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid -/// args argument in which case errors out, including VAR_UNKNOWN parameters. -/// -/// @param[in,out] wp Window to create location list for. May be NULL in -/// which case quickfix list will be created. -/// @param[in] args [list, action, what] -/// @param[in] args[0] Quickfix list contents. -/// @param[in] args[1] Optional. Action to perform: -/// append to an existing list, replace its content, -/// or create a new one. -/// @param[in] args[2] Optional. Quickfix list properties or title. -/// Defaults to caller function name. -/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. -static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) - FUNC_ATTR_NONNULL_ARG(2, 3) -{ - static char *e_invact = N_("E927: Invalid action: '%s'"); - const char *title = NULL; - char action = ' '; - static int recursive = 0; - rettv->vval.v_number = -1; - dict_T *what = NULL; - - typval_T *list_arg = &args[0]; - if (list_arg->v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } else if (recursive != 0) { - emsg(_(e_au_recursive)); - return; - } - - typval_T *action_arg = &args[1]; - if (action_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (action_arg->v_type != VAR_STRING) { - emsg(_(e_stringreq)); - return; - } - const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') - && act[1] == NUL) { - action = *act; - } else { - semsg(_(e_invact), act); - return; - } - - typval_T *const what_arg = &args[2]; - if (what_arg->v_type == VAR_UNKNOWN) { - // Option argument was not given. - goto skip_args; - } else if (what_arg->v_type == VAR_STRING) { - title = tv_get_string_chk(what_arg); - if (!title) { - // Type error. Error already printed by tv_get_string_chk(). - return; - } - } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) { - what = what_arg->vval.v_dict; - } else { - emsg(_(e_dictreq)); - return; - } - -skip_args: - if (!title) { - title = (wp ? ":setloclist()" : ":setqflist()"); - } - - recursive++; - list_T *const l = list_arg->vval.v_list; - if (set_errorlist(wp, l, action, (char *)title, what) == OK) { - rettv->vval.v_number = 0; - } - recursive--; -} - -/// "setloclist()" function -static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win; - - rettv->vval.v_number = -1; - - win = find_win_by_nr_or_id(&argvars[0]); - if (win != NULL) { - set_qf_ll_list(win, &argvars[1], rettv); - } -} - /// "setpos()" function static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { set_position(argvars, rettv, false); } -/// "setqflist()" function -static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - set_qf_ll_list(NULL, argvars, rettv); -} - /// Translate a register type string to the yank type and block length -static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *const block_len) +static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len) FUNC_ATTR_NONNULL_ALL { - char *stropt = (char *)(*pp); + char *stropt = *pp; switch (*stropt) { case 'v': case 'c': // character-wise selection @@ -8372,7 +7783,7 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c default: return FAIL; } - *pp = (char_u *)stropt; + *pp = stropt; return OK; } @@ -8380,11 +7791,9 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool append = false; - MotionType yank_type; - long block_len; - block_len = -1; - yank_type = kMTUnknown; + long block_len = -1; + MotionType yank_type = kMTUnknown; rettv->vval.v_number = 1; // FAIL is default. @@ -8404,7 +7813,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (tv_dict_len(d) == 0) { // Empty dict, clear the register (like setreg(0, [])) - char_u *lstval[2] = { NULL, NULL }; + char *lstval[2] = { NULL, NULL }; write_reg_contents_lst(regname, lstval, false, kMTUnknown, -1); return; } @@ -8416,7 +7825,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *stropt = tv_dict_get_string(d, "regtype", false); if (stropt != NULL) { - const int ret = get_yank_type((char_u **)&stropt, &yank_type, &block_len); + const int ret = get_yank_type((char **)&stropt, &yank_type, &block_len); if (ret == FAIL || *(++stropt) != NUL) { semsg(_(e_invargval), "value"); @@ -8459,7 +7868,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_unnamed = true; break; default: - get_yank_type((char_u **)&stropt, &yank_type, &block_len); + get_yank_type((char **)&stropt, &yank_type, &block_len); } } } @@ -8493,7 +7902,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); *curval++ = NULL; - write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, (colnr_T)block_len); + write_reg_contents_lst(regname, lstval, append, yank_type, (colnr_T)block_len); free_lstval: while (curallocval > allocval) { @@ -8523,14 +7932,12 @@ free_lstval: static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static char *e_invact2 = N_("E962: Invalid action: '%s'"); - win_T *wp; - dict_T *d; char action = 'r'; rettv->vval.v_number = -1; // first argument: window number or id - wp = find_win_by_nr_or_id(&argvars[0]); + win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL) { return; } @@ -8540,7 +7947,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_dictreq)); return; } - d = argvars[1].vval.v_dict; + dict_T *d = argvars[1].vval.v_dict; if (d == NULL) { return; } @@ -8600,9 +8007,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; if (argvars[0].v_type != VAR_UNKNOWN) { - long col; - - col = (long)tv_get_number_chk(argvars, NULL); + long col = (long)tv_get_number_chk(argvars, NULL); if (col < 0) { return; // type error; errmsg already given } @@ -8681,10 +8086,9 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - bool rpc = false; CallbackReader on_stdin = CALLBACK_READER_INIT; dict_T *opts = argvars[0].vval.v_dict; - rpc = tv_dict_get_number(opts, "rpc") != 0; + bool rpc = tv_dict_get_number(opts, "rpc") != 0; if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { return; @@ -8732,9 +8136,6 @@ static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "spellbadword()" function static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *word = ""; - hlf_T attr = HLF_COUNT; - size_t len = 0; const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { @@ -8748,6 +8149,9 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + const char *word = ""; + hlf_T attr = HLF_COUNT; + size_t len = 0; if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); @@ -8779,22 +8183,16 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len); tv_list_append_string(rettv->vval.v_list, - (attr == HLF_SPB ? "bad" - : attr == HLF_SPR ? "rare" - : attr == HLF_SPL ? "local" - : attr == - HLF_SPC ? "caps" - : - NULL), -1); + (attr == HLF_SPB ? "bad" : + attr == HLF_SPR ? "rare" : + attr == HLF_SPL ? "local" : + attr == HLF_SPC ? "caps" : NULL), -1); } /// "spellsuggest()" function static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - bool typeerr = false; - int maxcount; garray_T ga = GA_EMPTY_INIT_VALUE; - bool need_capital = false; const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { @@ -8808,8 +8206,11 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + int maxcount; + bool need_capital = false; const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { + bool typeerr = false; maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { goto f_spellsuggest_return; @@ -8838,14 +8239,12 @@ f_spellsuggest_return: static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char *save_cpo; - int match; colnr_T col = 0; bool keepempty = false; bool typeerr = false; // Make 'cpoptions' empty, the 'l' flag should not be used here. - save_cpo = p_cpo; + char *save_cpo = p_cpo; p_cpo = ""; const char *str = tv_get_string(&argvars[0]); @@ -8878,6 +8277,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) }; if (regmatch.regprog != NULL) { while (*str != NUL || keepempty) { + bool match; if (*str == NUL) { match = false; // Empty item at the end. } else { @@ -8978,7 +8378,6 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int base = 10; - varnumber_T n; int what = 0; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -9008,6 +8407,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) what |= STR2NR_HEX | STR2NR_FORCE; break; } + varnumber_T n; vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false); // Text after the number is silently ignored. if (isneg) { @@ -9583,7 +8983,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - memset(str, NUL, sizeof(str)); + CLEAR_FIELD(str); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { @@ -9694,11 +9094,9 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Common code for tabpagewinnr() and winnr(). static int get_winnr(tabpage_T *tp, typval_T *argvar) { - win_T *twin; int nr = 1; - win_T *wp; - twin = (tp == curtab) ? curwin : tp->tp_curwin; + win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin; if (argvar->v_type != VAR_UNKNOWN) { bool invalid_arg = false; const char *const arg = tv_get_string_chk(argvar); @@ -9713,20 +9111,20 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) } } else { // Extract the window count (if specified). e.g. winnr('3j') - char_u *endp; - long count = strtol((char *)arg, (char **)&endp, 10); + char *endp; + long count = strtol((char *)arg, &endp, 10); if (count <= 0) { // if count is not specified, default to 1 count = 1; } if (endp != NULL && *endp != '\0') { - if (strequal((char *)endp, "j")) { + if (strequal(endp, "j")) { twin = win_vert_neighbor(tp, twin, false, count); - } else if (strequal((char *)endp, "k")) { + } else if (strequal(endp, "k")) { twin = win_vert_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "h")) { + } else if (strequal(endp, "h")) { twin = win_horz_neighbor(tp, twin, true, count); - } else if (strequal((char *)endp, "l")) { + } else if (strequal(endp, "l")) { twin = win_horz_neighbor(tp, twin, false, count); } else { invalid_arg = true; @@ -9743,14 +9141,14 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) } if (nr > 0) { - for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin; wp != twin; wp = wp->w_next) { if (wp == NULL) { // didn't find it in this tabpage nr = 0; break; } - ++nr; + nr++; } } return nr; @@ -9772,13 +9170,11 @@ static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "tagfiles()" function static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char *fname; - tagname_T tn; - tv_list_alloc_ret(rettv, kListLenUnknown); - fname = xmalloc(MAXPATHL); + char *fname = xmalloc(MAXPATHL); bool first = true; + tagname_T tn; while (get_tagfname(&tn, first, (char_u *)fname) == OK) { tv_list_append_string(rettv->vval.v_list, fname, -1); first = false; @@ -9857,7 +9253,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. - if (!os_isdir_executable(cwd)) { + if (!os_isdir((const char_u *)cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -9978,7 +9374,6 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int repeat = 1; - dict_T *dict; rettv->vval.v_number = -1; if (check_secure()) { @@ -9986,8 +9381,8 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT - || (dict = argvars[2].vval.v_dict) == NULL) { + dict_T *dict = argvars[2].vval.v_dict; + if (argvars[2].v_type != VAR_DICT || dict == NULL) { semsg(_(e_invarg2), tv_get_string(&argvars[2])); return; } @@ -10128,10 +9523,8 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) char buf2[NUMBUFLEN]; const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); const char_u *mask = NULL; - const char_u *tail; const char_u *prev; const char_u *p; - int c1; int dir = 0; rettv->v_type = VAR_STRING; @@ -10156,6 +9549,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + int c1; if (dir == 0 || dir == 1) { // Trim leading characters while (*head != NUL) { @@ -10178,7 +9572,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tail = head + STRLEN(head); + const char_u *tail = head + STRLEN(head); if (dir == 0 || dir == 2) { // Trim trailing characters for (; tail > head; tail = prev) { @@ -10277,10 +9671,9 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { colnr_T vcol = 0; - pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], false, &fnum, false); + pos_T *fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { // Limit the column to a valid value, getvvcol() doesn't check. @@ -10293,7 +9686,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } getvvcol(curwin, fp, NULL, NULL, &vcol); - ++vcol; + vcol++; } rettv->vval.v_number = vcol; @@ -10384,17 +9777,14 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_move_separator()" function static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp; - int offset; - rettv->vval.v_number = false; - wp = find_win_by_nr_or_id(&argvars[0]); + win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL || wp->w_floating) { return; } - offset = (int)tv_get_number(&argvars[1]); + int offset = (int)tv_get_number(&argvars[1]); win_drag_vsep_line(wp, offset); rettv->vval.v_number = true; } @@ -10475,18 +9865,15 @@ static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "winnr()" function static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int nr = 1; - - nr = get_winnr(curtab, &argvars[0]); - rettv->vval.v_number = nr; + rettv->vval.v_number = get_winnr(curtab, &argvars[0]); } /// "winrestcmd()" function static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - garray_T ga; char_u buf[50]; + garray_T ga; ga_init(&ga, (int)sizeof(char), 70); // Do this twice to handle some window layouts properly. @@ -10511,10 +9898,9 @@ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "winrestview()" function static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *dict; + dict_T *dict = argvars[0].vval.v_dict; - if (argvars[0].v_type != VAR_DICT - || (dict = argvars[0].vval.v_dict) == NULL) { + if (argvars[0].v_type != VAR_DICT || dict == NULL) { emsg(_(e_invarg)); } else { dictitem_T *di; @@ -10562,10 +9948,8 @@ static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "winsaveview()" function static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *dict; - tv_dict_alloc_ret(rettv); - dict = rettv->vval.v_dict; + dict_T *dict = rettv->vval.v_dict; tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ff1808ed91..8822bb0491 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -233,7 +233,7 @@ void tv_list_init_static10(staticList10_T *const sl) #define SL_SIZE ARRAY_SIZE(sl->sl_items) list_T *const l = &sl->sl_list; - memset(sl, 0, sizeof(staticList10_T)); + CLEAR_POINTER(sl); l->lv_first = &sl->sl_items[0]; l->lv_last = &sl->sl_items[SL_SIZE - 1]; l->lv_refcount = DO_NOT_FREE_CNT; @@ -261,7 +261,7 @@ void tv_list_init_static10(staticList10_T *const sl) void tv_list_init_static(list_T *const l) FUNC_ATTR_NONNULL_ALL { - memset(l, 0, sizeof(*l)); + CLEAR_POINTER(l); l->lv_refcount = DO_NOT_FREE_CNT; list_log(l, NULL, NULL, "sinit"); } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c02351947b..c4bc9f603b 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -13,10 +13,9 @@ #include "nvim/hashtab.h" #include "nvim/lib/queue.h" #include "nvim/macros.h" -#include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/message.h" #include "nvim/pos.h" // for linenr_T -#include "nvim/profile.h" // for proftime_T #include "nvim/types.h" #ifdef LOG_LIST_ACTIONS # include "nvim/memory.h" @@ -356,6 +355,8 @@ struct ufunc { ///< used for s: variables int uf_refcount; ///< reference count, see func_name_refcount() funccall_T *uf_scoped; ///< l: local variables for closure + char_u *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with + ///< "<SNR>" as a string, otherwise NULL char_u uf_name[]; ///< Name of function (actual size equals name); ///< can start with <SNR>123_ ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 2f4799db57..c46cb6ba5d 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -12,8 +12,8 @@ #include "nvim/eval/funcs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" @@ -21,7 +21,9 @@ #include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" +#include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -44,7 +46,7 @@ # include "eval/userfunc.c.generated.h" #endif -hashtab_T func_hashtab; +static hashtab_T func_hashtab; // Used by get_func_tv() static garray_T funcargs = GA_EMPTY_INIT_VALUE; @@ -66,13 +68,19 @@ void func_init(void) hash_init(&func_hashtab); } +/// Return the function hash table +hashtab_T *func_tbl_get(void) +{ + return &func_hashtab; +} + /// Get function arguments. -static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs, +static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) { bool mustend = false; - char_u *arg = *argp; - char_u *p = arg; + char *arg = *argp; + char *p = arg; char_u c; int i; @@ -89,7 +97,7 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i // Isolate the arguments: "arg1, arg2, ...)" bool any_default = false; - while (*p != endchar) { + while (*p != (char)endchar) { if (p[0] == '.' && p[1] == '.' && p[2] == '.') { if (varargs != NULL) { *varargs = true; @@ -111,44 +119,43 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i } if (newargs != NULL) { ga_grow(newargs, 1); - c = *p; + c = (char_u)(*p); *p = NUL; - arg = vim_strsave(arg); + arg = xstrdup(arg); // Check for duplicate argument name. for (i = 0; i < newargs->ga_len; i++) { - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + if (STRCMP(((char **)(newargs->ga_data))[i], arg) == 0) { semsg(_("E853: Duplicate argument name: %s"), arg); xfree(arg); goto err_ret; } } - ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + ((char **)(newargs->ga_data))[newargs->ga_len] = arg; newargs->ga_len++; - *p = c; + *p = (char)c; } - if (*skipwhite((char *)p) == '=' && default_args != NULL) { + if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; any_default = true; - p = (char_u *)skipwhite((char *)p) + 1; - p = (char_u *)skipwhite((char *)p); - char_u *expr = p; - if (eval1((char **)&p, &rettv, false) != FAIL) { + p = skipwhite(p) + 1; + p = skipwhite(p); + char_u *expr = (char_u *)p; + if (eval1(&p, &rettv, false) != FAIL) { ga_grow(default_args, 1); // trim trailing whitespace - while (p > expr && ascii_iswhite(p[-1])) { + while (p > (char *)expr && ascii_iswhite(p[-1])) { p--; } - c = *p; + c = (char_u)(*p); *p = NUL; expr = vim_strsave(expr); - ((char_u **)(default_args->ga_data)) - [default_args->ga_len] = expr; + ((char **)(default_args->ga_data))[default_args->ga_len] = (char *)expr; default_args->ga_len++; - *p = c; + *p = (char)c; } else { mustend = true; } @@ -162,15 +169,15 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i mustend = true; } } - p = (char_u *)skipwhite((char *)p); - if (mustend && *p != endchar) { + p = skipwhite(p); + if (mustend && *p != (char)endchar) { if (!skip) { semsg(_(e_invarg2), *argp); } break; } } - if (*p != endchar) { + if (*p != (char)endchar) { goto err_ret; } p++; // skip "endchar" @@ -213,10 +220,21 @@ char_u *get_lambda_name(void) return name; } +static void set_ufunc_name(ufunc_T *fp, char_u *name) +{ + STRCPY(fp->uf_name, name); + + if (name[0] == K_SPECIAL) { + fp->uf_name_exp = xmalloc(STRLEN(name) + 3); + STRCPY(fp->uf_name_exp, "<SNR>"); + STRCAT(fp->uf_name_exp, fp->uf_name + 3); + } +} + /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) { garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; @@ -224,13 +242,13 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) partial_T *pt = NULL; int varargs; int ret; - char_u *start = (char_u *)skipwhite((char *)(*arg) + 1); + char_u *start = (char_u *)skipwhite(*arg + 1); char_u *s, *e; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, NULL, true); + ret = get_function_args((char **)&start, '-', NULL, NULL, NULL, true); if (ret == FAIL || *start != '>') { return NOTDONE; } @@ -241,7 +259,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) } else { pnewargs = NULL; } - *arg = (char_u *)skipwhite((char *)(*arg) + 1); + *arg = skipwhite(*arg + 1); ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, false); if (ret == FAIL || **arg != '>') { goto errret; @@ -253,14 +271,14 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) } // Get the start and the end of the expression. - *arg = (char_u *)skipwhite((char *)(*arg) + 1); - s = *arg; - ret = skip_expr((char **)arg); + *arg = skipwhite((*arg) + 1); + s = (char_u *)(*arg); + ret = skip_expr(arg); if (ret == FAIL) { goto errret; } - e = *arg; - *arg = (char_u *)skipwhite((char *)(*arg)); + e = (char_u *)(*arg); + *arg = skipwhite(*arg); if (**arg != '}') { goto errret; } @@ -282,7 +300,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) // Add "return " before the expression. size_t len = (size_t)(7 + e - s + 1); p = (char_u *)xmalloc(len); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; STRCPY(p, "return "); STRLCPY(p + 7, s, e - s + 1); if (strstr((char *)p + 7, "a:") == NULL) { @@ -291,7 +309,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) } fp->uf_refcount = 1; - STRCPY(fp->uf_name, name); + set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1); @@ -313,7 +331,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len; + fp->uf_script_ctx.sc_lnum += SOURCING_LNUM - newlines.ga_len; pt->pt_func = fp; pt->pt_refcount = 1; @@ -411,34 +429,32 @@ void emsg_funcname(char *ermsg, const char_u *name) /// @param funcexe various values /// /// @return OK or FAIL. -int get_func_tv(const char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe) +int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) { - char_u *argp; + char *argp; int ret = OK; typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments int argcount = 0; // number of arguments found - /* - * Get the arguments. - */ + // Get the arguments. argp = *arg; while (argcount < MAX_FUNC_ARGS - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) { - argp = (char_u *)skipwhite((char *)argp + 1); // skip the '(' or ',' + argp = skipwhite(argp + 1); // skip the '(' or ',' if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1((char **)&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { + if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { ret = FAIL; break; } - ++argcount; + argcount++; if (*argp != ',') { break; } } if (*argp == ')') { - ++argp; + argp++; } else { ret = FAIL; } @@ -472,7 +488,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char_u **arg, func tv_clear(&argvars[argcount]); } - *arg = (char_u *)skipwhite((char *)argp); + *arg = skipwhite(argp); return ret; } @@ -740,6 +756,7 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); + XFREE_CLEAR(fp->uf_name_exp); if (fp->uf_cb_free != NULL) { fp->uf_cb_free(fp->uf_cb_state); @@ -803,8 +820,6 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett linenr_T firstline, linenr_T lastline, dict_T *selfdict) FUNC_ATTR_NONNULL_ARG(1, 3, 4) { - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; bool using_sandbox = false; funccall_T *fc; int save_did_emsg; @@ -814,7 +829,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett int ai; bool islambda = false; char_u numbuf[NUMBUFLEN]; - char_u *name; + char *name; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; proftime_T wait_start; @@ -830,7 +845,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett rettv->vval.v_number = -1; return; } - ++depth; + depth++; // Save search patterns and redo buffer. save_search_patterns(); if (!ins_compl_active()) { @@ -870,7 +885,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // some compiler that checks the destination size. v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; #ifndef __clang_analyzer__ - name = v->di_key; + name = (char *)v->di_key; STRCPY(name, "self"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -896,7 +911,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // destination size. v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; #ifndef __clang_analyzer__ - name = v->di_key; + name = (char *)v->di_key; STRCPY(name, "000"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -935,13 +950,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // evaluate named argument default expression isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount; if (isdefault) { - char_u *default_expr = NULL; + char *default_expr = NULL; def_rettv.v_type = VAR_NUMBER; def_rettv.vval.v_number = -1; - default_expr = ((char_u **)(fp->uf_def_args.ga_data)) + default_expr = ((char **)(fp->uf_def_args.ga_data)) [ai + fp->uf_def_args.ga_len]; - if (eval1((char **)&default_expr, &def_rettv, true) == FAIL) { + if (eval1(&default_expr, &def_rettv, true) == FAIL) { default_arg_err = true; break; } @@ -953,7 +968,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett } // "..." argument a:1, a:2, etc. snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); - name = numbuf; + name = (char *)numbuf; } if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; @@ -994,73 +1009,49 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Don't redraw while executing the function. RedrawingDisabled++; - save_sourcing_name = (char_u *)sourcing_name; - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 1; if (fp->uf_flags & FC_SANDBOX) { using_sandbox = true; sandbox++; } - // need space for new sourcing_name: - // * save_sourcing_name - // * "["number"].." or "function " - // * "<SNR>" + fp->uf_name - 3 - // * terminating NUL - size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) - + STRLEN(fp->uf_name) + 27; - sourcing_name = xmalloc(len); - { - if (save_sourcing_name != NULL - && STRNCMP(save_sourcing_name, "function ", 9) == 0) { - vim_snprintf(sourcing_name, - len, - "%s[%" PRId64 "]..", - save_sourcing_name, - (int64_t)save_sourcing_lnum); - } else { - STRCPY(sourcing_name, "function "); - } - cat_func_name((char_u *)sourcing_name + STRLEN(sourcing_name), fp); - - if (p_verbose >= 12) { - ++no_wait_return; - verbose_enter_scroll(); + estack_push_ufunc(fp, 1); + if (p_verbose >= 12) { + no_wait_return++; + verbose_enter_scroll(); - smsg(_("calling %s"), sourcing_name); - if (p_verbose >= 14) { - msg_puts("("); - for (int i = 0; i < argcount; i++) { - if (i > 0) { - msg_puts(", "); - } - if (argvars[i].v_type == VAR_NUMBER) { - msg_outnum((long)argvars[i].vval.v_number); - } else { - // Do not want errors such as E724 here. - emsg_off++; - char *tofree = encode_tv2string(&argvars[i], NULL); - emsg_off--; - if (tofree != NULL) { - char *s = tofree; - char buf[MSG_BUF_LEN]; - if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf)); - s = buf; - } - msg_puts(s); - xfree(tofree); + smsg(_("calling %s"), SOURCING_NAME); + if (p_verbose >= 14) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { + if (i > 0) { + msg_puts(", "); + } + if (argvars[i].v_type == VAR_NUMBER) { + msg_outnum((long)argvars[i].vval.v_number); + } else { + // Do not want errors such as E724 here. + emsg_off++; + char *tofree = encode_tv2string(&argvars[i], NULL); + emsg_off--; + if (tofree != NULL) { + char *s = tofree; + char buf[MSG_BUF_LEN]; + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf)); + s = buf; } + msg_puts(s); + xfree(tofree); } } - msg_puts(")"); } - msg_puts("\n"); // don't overwrite this either - - verbose_leave_scroll(); - --no_wait_return; + msg_puts(")"); } + msg_puts("\n"); // don't overwrite this either + + verbose_leave_scroll(); + no_wait_return--; } const bool do_profiling_yes = do_profiling == PROF_YES; @@ -1097,12 +1088,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett if (default_arg_err && (fp->uf_flags & FC_ABORT)) { did_emsg = true; } else if (islambda) { - char_u *p = *(char_u **)fp->uf_lines.ga_data + 7; + char *p = *(char **)fp->uf_lines.ga_data + 7; // A Lambda always has the command "return {expr}". It is much faster // to evaluate {expr} directly. ex_nesting_level++; - (void)eval1((char **)&p, rettv, true); + (void)eval1(&p, rettv, true); ex_nesting_level--; } else { // call do_cmdline() to execute the lines @@ -1110,7 +1101,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); } - --RedrawingDisabled; + RedrawingDisabled--; // when the function was aborted because of an error, return -1 if ((did_emsg @@ -1140,14 +1131,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // when being verbose, mention the return value if (p_verbose >= 12) { - ++no_wait_return; + no_wait_return++; verbose_enter_scroll(); if (aborting()) { - smsg(_("%s aborted"), sourcing_name); + smsg(_("%s aborted"), SOURCING_NAME); } else if (fc->rettv->v_type == VAR_NUMBER) { smsg(_("%s returning #%" PRId64 ""), - sourcing_name, (int64_t)fc->rettv->vval.v_number); + SOURCING_NAME, (int64_t)fc->rettv->vval.v_number); } else { char buf[MSG_BUF_LEN]; @@ -1163,19 +1154,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); s = buf; } - smsg(_("%s returning %s"), sourcing_name, s); + smsg(_("%s returning %s"), SOURCING_NAME, s); xfree(tofree); } } msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); - --no_wait_return; + no_wait_return--; } - xfree(sourcing_name); - sourcing_name = (char *)save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + estack_pop(); current_sctx = save_current_sctx; if (do_profiling_yes) { script_prof_restore(&wait_start); @@ -1184,15 +1173,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett sandbox--; } - if (p_verbose >= 12 && sourcing_name != NULL) { - ++no_wait_return; + if (p_verbose >= 12 && SOURCING_NAME != NULL) { + no_wait_return++; verbose_enter_scroll(); - smsg(_("continuing in %s"), sourcing_name); + smsg(_("continuing in %s"), SOURCING_NAME); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); - --no_wait_return; + no_wait_return--; } did_emsg |= save_did_emsg; @@ -1633,9 +1622,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) msg_puts(" "); } msg_puts(force ? "function! " : "function "); - if (fp->uf_name[0] == K_SPECIAL) { - msg_puts_attr("<SNR>", HL_ATTR(HLF_8)); - msg_puts((const char *)fp->uf_name + 3); + if (fp->uf_name_exp != NULL) { + msg_puts((const char *)fp->uf_name_exp); } else { msg_puts((const char *)fp->uf_name); } @@ -1691,7 +1679,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) /// @param partial return: partial of a FuncRef /// /// @return the function name in allocated memory, or NULL for failure. -char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) +char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) FUNC_ATTR_NONNULL_ARG(1) { char_u *name = NULL; @@ -1702,13 +1690,13 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, lval_T lv; if (fdp != NULL) { - memset(fdp, 0, sizeof(funcdict_T)); + CLEAR_POINTER(fdp); } - start = *pp; + start = (char_u *)(*pp); // Check for hard coded <SNR>: already translated function ID (from a user // command). - if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA + if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; @@ -1742,7 +1730,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, semsg(_(e_invarg2), start); } } else { - *pp = (char_u *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR); + *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR); } goto theend; } @@ -1756,7 +1744,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { name = vim_strsave((char_u *)lv.ll_tv->vval.v_string); - *pp = (char_u *)end; + *pp = (char *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { @@ -1767,10 +1755,10 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } name = xmallocz((size_t)len); memcpy(name, end + 1, (size_t)len); - *pp = (char_u *)end + 1 + len; + *pp = (char *)end + 1 + len; } else { name = vim_strsave((char_u *)partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; + *pp = (char *)end; } if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; @@ -1781,7 +1769,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, || fdp->fd_newkey == NULL)) { emsg(_(e_funcref)); } else { - *pp = (char_u *)end; + *pp = (char *)end; } name = NULL; } @@ -1790,7 +1778,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, if (lv.ll_name == NULL) { // Error found, but continue after the function name. - *pp = (char_u *)end; + *pp = (char *)end; goto theend; } @@ -1803,16 +1791,16 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, name = NULL; } } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - *pp); + len = (int)(end - (char_u *)(*pp)); name = deref_func_name((const char *)(*pp), &len, partial, flags & TFN_NO_AUTOLOAD); - if (name == *pp) { + if (name == (char_u *)(*pp)) { name = NULL; } } if (name != NULL) { name = vim_strsave(name); - *pp = (char_u *)end; + *pp = (char *)end; if (STRNCMP(name, "<SNR>", 5) == 0) { // Change "<SNR>" to the byte sequence. name[0] = K_SPECIAL; @@ -1890,7 +1878,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, } memmove(name + lead, lv.ll_name, (size_t)len); name[lead + len] = NUL; - *pp = (char_u *)end; + *pp = (char *)end; theend: clear_lval(&lv); @@ -1941,7 +1929,7 @@ void ex_function(exarg_T *eap) todo = (int)func_hashtab.ht_used; for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; fp = HI2UF(hi); if (message_filtered(fp->uf_name)) { continue; @@ -1974,7 +1962,7 @@ void ex_function(exarg_T *eap) todo = (int)func_hashtab.ht_used; for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; fp = HI2UF(hi); if (!isdigit(*fp->uf_name) && vim_regexec(®match, (char *)fp->uf_name, 0)) { @@ -1986,7 +1974,7 @@ void ex_function(exarg_T *eap) } } if (*p == '/') { - ++p; + p++; } eap->nextcmd = (char *)check_nextcmd(p); return; @@ -2007,7 +1995,7 @@ void ex_function(exarg_T *eap) // s:func script-local function name // g:func global function name, same as "func" p = (char_u *)eap->arg; - name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); + name = trans_function_name((char **)&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); paren = (vim_strchr((char *)p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { /* @@ -2064,7 +2052,7 @@ void ex_function(exarg_T *eap) msg_putchar(' '); } } - msg_prt_line(FUNCLINE(fp, j), false); + msg_prt_line((char_u *)FUNCLINE(fp, j), false); ui_flush(); // show a line at a time os_breakcheck(); } @@ -2108,9 +2096,8 @@ void ex_function(exarg_T *eap) } if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { int j = (*arg == K_SPECIAL) ? 3 : 0; - while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) - : eval_isnamec(arg[j]))) { - ++j; + while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) : eval_isnamec(arg[j]))) { + j++; } if (arg[j] != NUL) { emsg_funcname((char *)e_invarg2, arg); @@ -2122,7 +2109,7 @@ void ex_function(exarg_T *eap) } } - if (get_function_args(&p, ')', &newargs, &varargs, + if (get_function_args((char **)&p, ')', &newargs, &varargs, &default_args, eap->skip) == FAIL) { goto errret_2; } @@ -2192,7 +2179,7 @@ void ex_function(exarg_T *eap) } // Save the starting line number. - sourcing_lnum_top = sourcing_lnum; + sourcing_lnum_top = SOURCING_LNUM; indent = 2; nesting = 0; @@ -2234,10 +2221,10 @@ void ex_function(exarg_T *eap) ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); } - // Detect line continuation: sourcing_lnum increased more than one. + // Detect line continuation: SOURCING_LNUM increased more than one. sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); - if (sourcing_lnum < sourcing_lnum_off) { - sourcing_lnum_off -= sourcing_lnum; + if (SOURCING_LNUM < sourcing_lnum_off) { + sourcing_lnum_off -= SOURCING_LNUM; } else { sourcing_lnum_off = 0; } @@ -2315,7 +2302,7 @@ void ex_function(exarg_T *eap) p = (char_u *)skipwhite((char *)p + 1); } p += eval_fname_script((const char *)p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); + xfree(trans_function_name((char **)&p, true, 0, NULL, NULL)); if (*skipwhite((char *)p) == '(') { nesting++; indent += 2; @@ -2400,12 +2387,12 @@ void ex_function(exarg_T *eap) // allocates 250 bytes per line, this saves 80% on average. The cost // is an extra alloc/free. p = vim_strsave(theline); - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; // Add NULL lines for continuation lines, so that the line count is // equal to the index in the growarray. while (sourcing_lnum_off-- > 0) { - ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; + ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL; } // Check for end of eap->arg. @@ -2454,9 +2441,12 @@ void ex_function(exarg_T *eap) fp = NULL; overwrite = true; } else { - // redefine existing function + char_u *exp_name = fp->uf_name_exp; + // redefine existing function, keep the expanded name XFREE_CLEAR(name); + fp->uf_name_exp = NULL; func_clear_items(fp); + fp->uf_name_exp = exp_name; fp->uf_profiling = false; fp->uf_prof_initialized = false; } @@ -2495,13 +2485,12 @@ void ex_function(exarg_T *eap) // Check that the autoload name matches the script name. int j = FAIL; - if (sourcing_name != NULL) { + if (SOURCING_NAME != NULL) { scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); p = (char_u *)vim_strchr((char *)scriptname, '/'); plen = (int)STRLEN(p); - slen = (int)STRLEN(sourcing_name); - if (slen > plen && FNAMECMP(p, - sourcing_name + slen - plen) == 0) { + slen = (int)STRLEN(SOURCING_NAME); + if (slen > plen && FNAMECMP(p, SOURCING_NAME + slen - plen) == 0) { j = OK; } xfree(scriptname); @@ -2536,7 +2525,7 @@ void ex_function(exarg_T *eap) } // insert the new function in the function list - STRCPY(fp->uf_name, name); + set_ufunc_name(fp, name); if (overwrite) { hi = hash_find(&func_hashtab, (char *)name); hi->hi_key = UF2HIKEY(fp); @@ -2628,8 +2617,7 @@ bool function_exists(const char *const name, bool no_deref) if (no_deref) { flag |= TFN_NO_DEREF; } - char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL, - NULL); + char *const p = (char *)trans_function_name((char **)&nm, false, flag, NULL, NULL); nm = (char_u *)skipwhite((char *)nm); // Only accept "funcname", "funcname ", "funcname (..." and @@ -2656,10 +2644,10 @@ char *get_user_func_name(expand_T *xp, int idx) assert(hi); if (done < func_hashtab.ht_used) { if (done++ > 0) { - ++hi; + hi++; } while (HASHITEM_EMPTY(hi)) { - ++hi; + hi++; } fp = HI2UF(hi); @@ -2693,7 +2681,7 @@ void ex_delfunction(exarg_T *eap) funcdict_T fudi; p = (char_u *)eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + name = trans_function_name((char **)&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) { @@ -2867,7 +2855,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - ++emsg_skip; + emsg_skip++; } eap->nextcmd = NULL; @@ -2899,7 +2887,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -2932,7 +2920,7 @@ void ex_call(exarg_T *eap) return; } - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + tofree = trans_function_name((char **)&arg, false, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. semsg(_(e_dictkey), fudi.fd_newkey); @@ -2987,7 +2975,7 @@ void ex_call(exarg_T *eap) funcexe.evaluate = true; funcexe.partial = partial; funcexe.selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { + if (get_func_tv(name, -1, &rettv, (char **)&arg, &funcexe) == FAIL) { failed = true; break; } @@ -3145,8 +3133,7 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) // If breakpoints have been added/deleted need to check for it. if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, - sourcing_lnum); + fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -3160,14 +3147,14 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) } else { // Skip NULL lines (continuation lines). while (fcp->linenr < gap->ga_len - && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) { + && ((char **)(gap->ga_data))[fcp->linenr] == NULL) { fcp->linenr++; } if (fcp->linenr >= gap->ga_len) { retval = NULL; } else { - retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); - sourcing_lnum = fcp->linenr; + retval = (char_u *)xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); + SOURCING_LNUM = fcp->linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); } @@ -3175,11 +3162,10 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) } // Did we encounter a breakpoint? - if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { - dbg_breakpoint(fp->uf_name, sourcing_lnum); + if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) { + dbg_breakpoint(fp->uf_name, SOURCING_LNUM); // Find next breakpoint. - fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, - sourcing_lnum); + fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index ed86aaad4a..4b7007aae9 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -4,6 +4,11 @@ #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +// From user function to hashitem and back. +#define UF2HIKEY(fp) ((fp)->uf_name) +#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) +#define HI2UF(hi) HIKEY2UF((hi)->hi_key) + ///< Structure used by trans_function_name() typedef struct { dict_T *fd_dict; ///< Dictionary used. @@ -59,8 +64,8 @@ typedef struct { .basetv = NULL, \ } -#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] -#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] +#define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char **)(fp->uf_lines.ga_data))[j] #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/userfunc.h.generated.h" diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index ea1c3a8c4e..51123e1a85 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -7,6 +7,7 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" @@ -15,8 +16,10 @@ #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/window.h" diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 9d1ac185d4..bc8e823797 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -15,15 +15,18 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" @@ -36,6 +39,7 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/help.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" @@ -59,9 +63,9 @@ #include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/strings.h" @@ -300,7 +304,7 @@ void ex_align(exarg_T *eap) new_indent--; break; } - --new_indent; + new_indent--; } } } @@ -1091,12 +1095,12 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) if (line1 == n) { line1 = curwin->w_cursor.lnum; } - ++line1; + line1++; if (curwin->w_cursor.lnum < line1) { - ++line1; + line1++; } if (curwin->w_cursor.lnum < line2) { - ++line2; + line2++; } ++curwin->w_cursor.lnum; } @@ -1198,7 +1202,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out break; } } - ++p; + p++; } } while (trailarg != NULL); @@ -1442,7 +1446,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b } beginline(BL_WHITE | BL_FIX); // cursor on first non-blank - --no_wait_return; + no_wait_return--; if (linecount > p_report) { if (do_in) { @@ -1460,8 +1464,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b error: // put cursor back in same position for ":w !cmd" curwin->w_cursor = cursor_save; - --no_wait_return; - wait_return(FALSE); + no_wait_return--; + wait_return(false); } filterend: @@ -2111,7 +2115,7 @@ void do_wqall(exarg_T *eap) * 4. if overwriting is allowed (even after a dialog) */ if (not_writing()) { - ++error; + error++; break; } if (buf->b_ffname == NULL) { @@ -2246,7 +2250,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln } } if (other) { - --no_wait_return; + no_wait_return--; } if (setpm) { setpcmark(); @@ -2959,7 +2963,7 @@ void ex_append(exarg_T *eap) } if (eap->cmdidx != CMD_append) { - --lnum; + lnum--; } // when the buffer is empty need to delete the dummy line @@ -3015,7 +3019,7 @@ void ex_append(exarg_T *eap) vcol = 0; for (p = theline; indent > vcol; ++p) { if (*p == ' ') { - ++vcol; + vcol++; } else if (*p == TAB) { vcol += 8 - vcol % 8; } else { @@ -3044,7 +3048,7 @@ void ex_append(exarg_T *eap) } xfree(theline); - ++lnum; + lnum++; if (empty) { ml_delete(2L, false); @@ -3136,10 +3140,10 @@ void ex_z(exarg_T *eap) kind = x; if (*kind == '-' || *kind == '+' || *kind == '=' || *kind == '^' || *kind == '.') { - ++x; + x++; } while (*x == '-' || *x == '+') { - ++x; + x++; } if (*x != 0) { @@ -3196,7 +3200,7 @@ void ex_z(exarg_T *eap) if (*kind == '+') { start += (linenr_T)bigness * (linenr_T)(x - kind - 1) + 1; } else if (eap->addr_count == 0) { - ++start; + start++; } end = start + (linenr_T)bigness - 1; curs = end; @@ -3343,6 +3347,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) { save_re_pat(RE_SUBST, (char_u *)pat, p_magic); } + // put pattern in history add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL); } @@ -3541,7 +3546,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T which_pat = RE_LAST; // use last used regexp delimiter = (char_u)(*cmd++); // remember delimiter character pat = cmd; // remember start of search pat - cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, (char_u **)&eap->arg); + cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, &eap->arg); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; @@ -4296,7 +4301,7 @@ skip: * has been appended to new_start, we don't need * it in the buffer. */ - ++lnum; + lnum++; if (u_savedel(lnum, nmatch_tl) != OK) { break; } @@ -4626,7 +4631,7 @@ void ex_global(exarg_T *eap) delim = *cmd; // get the delimiter cmd++; // skip delimiter if there is one pat = cmd; // remember start of pattern - cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, (char_u **)&eap->arg); + cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, &eap->arg); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } @@ -4775,1124 +4780,6 @@ bool prepare_tagpreview(bool undo_sync) return false; } -/// ":help": open a read-only window on a help file -void ex_help(exarg_T *eap) -{ - char *arg; - char *tag; - FILE *helpfd; // file descriptor of help file - int n; - int i; - win_T *wp; - int num_matches; - char **matches; - char *p; - int empty_fnum = 0; - int alt_fnum = 0; - buf_T *buf; - int len; - char *lang; - const bool old_KeyTyped = KeyTyped; - - if (eap != NULL) { - /* - * A ":help" command ends at the first LF, or at a '|' that is - * followed by some text. Set nextcmd to the following command. - */ - for (arg = eap->arg; *arg; arg++) { - if (*arg == '\n' || *arg == '\r' - || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { - *arg++ = NUL; - eap->nextcmd = arg; - break; - } - } - arg = eap->arg; - - if (eap->forceit && *arg == NUL && !curbuf->b_help) { - emsg(_("E478: Don't panic!")); - return; - } - - if (eap->skip) { // not executing commands - return; - } - } else { - arg = ""; - } - - // remove trailing blanks - p = arg + STRLEN(arg) - 1; - while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') { - *p-- = NUL; - } - - // Check for a specified language - lang = check_help_lang(arg); - - // When no argument given go to the index. - if (*arg == NUL) { - arg = "help.txt"; - } - - /* - * Check if there is a match for the argument. - */ - n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit); - - i = 0; - if (n != FAIL && lang != NULL) { - // Find first item with the requested language. - for (i = 0; i < num_matches; ++i) { - len = (int)STRLEN(matches[i]); - if (len > 3 && matches[i][len - 3] == '@' - && STRICMP(matches[i] + len - 2, lang) == 0) { - break; - } - } - } - if (i >= num_matches || n == FAIL) { - if (lang != NULL) { - semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg); - } else { - semsg(_("E149: Sorry, no help for %s"), arg); - } - if (n != FAIL) { - FreeWild(num_matches, matches); - } - return; - } - - // The first match (in the requested language) is the best match. - tag = xstrdup(matches[i]); - FreeWild(num_matches, matches); - - /* - * Re-use an existing help window or open a new one. - * Always open a new one for ":tab help". - */ - if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { - if (cmdmod.cmod_tab != 0) { - wp = NULL; - } else { - wp = NULL; - FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { - if (bt_help(wp2->w_buffer)) { - wp = wp2; - break; - } - } - } - if (wp != NULL && wp->w_buffer->b_nwindows > 0) { - win_enter(wp, true); - } else { - // There is no help window yet. - // Try to open the file specified by the "helpfile" option. - if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) { - smsg(_("Sorry, help file \"%s\" not found"), p_hf); - goto erret; - } - fclose(helpfd); - - // Split off help window; put it at far top if no position - // specified, the current window is vertically split and - // narrow. - n = WSP_HELP; - if (cmdmod.cmod_split == 0 && curwin->w_width != Columns - && curwin->w_width < 80) { - n |= WSP_TOP; - } - if (win_split(0, n) == FAIL) { - goto erret; - } - - if (curwin->w_height < p_hh) { - win_setheight((int)p_hh); - } - - /* - * Open help file (do_ecmd() will set b_help flag, readfile() will - * set b_p_ro flag). - * Set the alternate file to the previously edited file. - */ - alt_fnum = curbuf->b_fnum; - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, - ECMD_HIDE + ECMD_SET_HELP, - NULL); // buffer is still open, don't store info - - if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { - curwin->w_alt_fnum = alt_fnum; - } - empty_fnum = curbuf->b_fnum; - } - } - - restart_edit = 0; // don't want insert mode in help file - - // Restore KeyTyped, setting 'filetype=help' may reset it. - // It is needed for do_tag top open folds under the cursor. - KeyTyped = old_KeyTyped; - - do_tag((char_u *)tag, DT_HELP, 1, false, true); - - // Delete the empty buffer if we're not using it. Careful: autocommands - // may have jumped to another window, check that the buffer is not in a - // window. - if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { - buf = buflist_findnr(empty_fnum); - if (buf != NULL && buf->b_nwindows == 0) { - wipe_buffer(buf, true); - } - } - - // keep the previous alternate file - if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum - && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { - curwin->w_alt_fnum = alt_fnum; - } - -erret: - xfree(tag); -} - -/// In an argument search for a language specifiers in the form "@xx". -/// Changes the "@" to NUL if found, and returns a pointer to "xx". -/// -/// @return NULL if not found. -char *check_help_lang(char *arg) -{ - int len = (int)STRLEN(arg); - - if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) - && ASCII_ISALPHA(arg[len - 1])) { - arg[len - 3] = NUL; // remove the '@' - return arg + len - 2; - } - return NULL; -} - -/// Return a heuristic indicating how well the given string matches. The -/// smaller the number, the better the match. This is the order of priorities, -/// from best match to worst match: -/// - Match with least alphanumeric characters is better. -/// - Match with least total characters is better. -/// - Match towards the start is better. -/// - Match starting with "+" is worse (feature instead of command) -/// Assumption is made that the matched_string passed has already been found to -/// match some string for which help is requested. webb. -/// -/// @param offset offset for match -/// @param wrong_case no matching case -/// -/// @return a heuristic indicating how well the given string matches. -int help_heuristic(char *matched_string, int offset, int wrong_case) - FUNC_ATTR_PURE -{ - int num_letters; - char *p; - - num_letters = 0; - for (p = matched_string; *p; p++) { - if (ASCII_ISALNUM(*p)) { - num_letters++; - } - } - - /* - * Multiply the number of letters by 100 to give it a much bigger - * weighting than the number of characters. - * If there only is a match while ignoring case, add 5000. - * If the match starts in the middle of a word, add 10000 to put it - * somewhere in the last half. - * If the match is more than 2 chars from the start, multiply by 200 to - * put it after matches at the start. - */ - if (offset > 0 - && ASCII_ISALNUM(matched_string[offset]) - && ASCII_ISALNUM(matched_string[offset - 1])) { - offset += 10000; - } else if (offset > 2) { - offset *= 200; - } - if (wrong_case) { - offset += 5000; - } - // Features are less interesting than the subjects themselves, but "+" - // alone is not a feature. - if (matched_string[0] == '+' && matched_string[1] != NUL) { - offset += 100; - } - return 100 * num_letters + (int)STRLEN(matched_string) + offset; -} - -/// Compare functions for qsort() below, that checks the help heuristics number -/// that has been put after the tagname by find_tags(). -static int help_compare(const void *s1, const void *s2) -{ - char *p1; - char *p2; - - p1 = *(char **)s1 + strlen(*(char **)s1) + 1; - p2 = *(char **)s2 + strlen(*(char **)s2) + 1; - - // Compare by help heuristic number first. - int cmp = strcmp(p1, p2); - if (cmp != 0) { - return cmp; - } - - // Compare by strings as tie-breaker when same heuristic number. - return strcmp(*(char **)s1, *(char **)s2); -} - -/// Find all help tags matching "arg", sort them and return in matches[], with -/// the number of matches in num_matches. -/// The matches will be sorted with a "best" match algorithm. -/// When "keep_lang" is true try keeping the language of the current buffer. -int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang) -{ - int i; - - // Specific tags that either have a specific replacement or won't go - // through the generic rules. - static char *(except_tbl[][2]) = { - { "*", "star" }, - { "g*", "gstar" }, - { "[*", "[star" }, - { "]*", "]star" }, - { ":*", ":star" }, - { "/*", "/star" }, // NOLINT - { "/\\*", "/\\\\star" }, - { "\"*", "quotestar" }, - { "**", "starstar" }, - { "cpo-*", "cpo-star" }, - { "/\\(\\)", "/\\\\(\\\\)" }, - { "/\\%(\\)", "/\\\\%(\\\\)" }, - { "?", "?" }, - { "??", "??" }, - { ":?", ":?" }, - { "?<CR>", "?<CR>" }, - { "g?", "g?" }, - { "g?g?", "g?g?" }, - { "g??", "g??" }, - { "-?", "-?" }, - { "q?", "q?" }, - { "v_g?", "v_g?" }, - { "/\\?", "/\\\\?" }, - { "/\\z(\\)", "/\\\\z(\\\\)" }, - { "\\=", "\\\\=" }, - { ":s\\=", ":s\\\\=" }, - { "[count]", "\\[count]" }, - { "[quotex]", "\\[quotex]" }, - { "[range]", "\\[range]" }, - { ":[range]", ":\\[range]" }, - { "[pattern]", "\\[pattern]" }, - { "\\|", "\\\\bar" }, - { "\\%$", "/\\\\%\\$" }, - { "s/\\~", "s/\\\\\\~" }, - { "s/\\U", "s/\\\\U" }, - { "s/\\L", "s/\\\\L" }, - { "s/\\1", "s/\\\\1" }, - { "s/\\2", "s/\\\\2" }, - { "s/\\3", "s/\\\\3" }, - { "s/\\9", "s/\\\\9" }, - { NULL, NULL } - }; - - static const char *(expr_table[]) = { - "!=?", "!~?", "<=?", "<?", "==?", "=~?", - ">=?", ">?", "is?", "isnot?" - }; - char *d = (char *)IObuff; // assume IObuff is long enough! - d[0] = NUL; - - if (STRNICMP(arg, "expr-", 5) == 0) { - // When the string starting with "expr-" and containing '?' and matches - // the table, it is taken literally (but ~ is escaped). Otherwise '?' - // is recognized as a wildcard. - for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) { - if (STRCMP(arg + 5, expr_table[i]) == 0) { - for (int si = 0, di = 0;; si++) { - if (arg[si] == '~') { - d[di++] = '\\'; - } - d[di++] = arg[si]; - if (arg[si] == NUL) { - break; - } - } - break; - } - } - } else { - // Recognize a few exceptions to the rule. Some strings that contain - // '*'are changed to "star", otherwise '*' is recognized as a wildcard. - for (i = 0; except_tbl[i][0] != NULL; i++) { - if (STRCMP(arg, except_tbl[i][0]) == 0) { - STRCPY(d, except_tbl[i][1]); - break; - } - } - } - - if (d[0] == NUL) { // no match in table - // Replace "\S" with "/\\S", etc. Otherwise every tag is matched. - // Also replace "\%^" and "\%(", they match every tag too. - // Also "\zs", "\z1", etc. - // Also "\@<", "\@=", "\@<=", etc. - // And also "\_$" and "\_^". - if (arg[0] == '\\' - && ((arg[1] != NUL && arg[2] == NUL) - || (vim_strchr("%_z@", arg[1]) != NULL - && arg[2] != NUL))) { - vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1); - // Check for "/\\_$", should be "/\\_\$" - if (d[3] == '_' && d[4] == '$') { - STRCPY(d + 4, "\\$"); - } - } else { - // Replace: - // "[:...:]" with "\[:...:]" - // "[++...]" with "\[++...]" - // "\{" with "\\{" -- matching "} \}" - if ((arg[0] == '[' && (arg[1] == ':' - || (arg[1] == '+' && arg[2] == '+'))) - || (arg[0] == '\\' && arg[1] == '{')) { - *d++ = '\\'; - } - - // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. - if (*arg == '(' && arg[1] == '\'') { - arg++; - } - for (const char *s = arg; *s; s++) { - // Replace "|" with "bar" and '"' with "quote" to match the name of - // the tags for these commands. - // Replace "*" with ".*" and "?" with "." to match command line - // completion. - // Insert a backslash before '~', '$' and '.' to avoid their - // special meaning. - if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!? - break; - } - switch (*s) { - case '|': - STRCPY(d, "bar"); - d += 3; - continue; - case '"': - STRCPY(d, "quote"); - d += 5; - continue; - case '*': - *d++ = '.'; - break; - case '?': - *d++ = '.'; - continue; - case '$': - case '.': - case '~': - *d++ = '\\'; - break; - } - - /* - * Replace "^x" by "CTRL-X". Don't do this for "^_" to make - * ":help i_^_CTRL-D" work. - * Insert '-' before and after "CTRL-X" when applicable. - */ - if (*s < ' ' - || (*s == '^' && s[1] - && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) { - if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') { - *d++ = '_'; // prepend a '_' to make x_CTRL-x - } - STRCPY(d, "CTRL-"); - d += 5; - if (*s < ' ') { - *d++ = (char)(*s + '@'); - if (d[-1] == '\\') { - *d++ = '\\'; // double a backslash - } - } else { - *d++ = *++s; - } - if (s[1] != NUL && s[1] != '_') { - *d++ = '_'; // append a '_' - } - continue; - } else if (*s == '^') { // "^" or "CTRL-^" or "^_" - *d++ = '\\'; - } - /* - * Insert a backslash before a backslash after a slash, for search - * pattern tags: "/\|" --> "/\\|". - */ - else if (s[0] == '\\' && s[1] != '\\' - && *arg == '/' && s == arg + 1) { - *d++ = '\\'; - } - - // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in - // "CTRL-\_CTRL-N" - if (STRNICMP(s, "CTRL-\\_", 7) == 0) { - STRCPY(d, "CTRL-\\\\"); - d += 7; - s += 6; - } - - *d++ = *s; - - // If tag contains "({" or "([", tag terminates at the "(". - // This is for help on functions, e.g.: abs({expr}). - if (*s == '(' && (s[1] == '{' || s[1] == '[')) { - break; - } - - // If tag starts with ', toss everything after a second '. Fixes - // CTRL-] on 'option'. (would include the trailing '.'). - if (*s == '\'' && s > arg && *arg == '\'') { - break; - } - // Also '{' and '}'. Fixes CTRL-] on '{address}'. - if (*s == '}' && s > arg && *arg == '{') { - break; - } - } - *d = NUL; - - if (*IObuff == '`') { - if ((char_u *)d > IObuff + 2 && d[-1] == '`') { - // remove the backticks from `command` - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); - d[-2] = NUL; - } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { - // remove the backticks and comma from `command`, - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); - d[-3] = NUL; - } else if ((char_u *)d > IObuff + 4 && d[-3] == '`' - && d[-2] == '\\' && d[-1] == '.') { - // remove the backticks and dot from `command`\. - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); - d[-4] = NUL; - } - } - } - } - - *matches = NULL; - *num_matches = 0; - int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; - if (keep_lang) { - flags |= TAG_KEEP_LANG; - } - if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK - && *num_matches > 0) { - // Sort the matches found on the heuristic number that is after the - // tag name. - qsort((void *)(*matches), (size_t)(*num_matches), - sizeof(char_u *), help_compare); - // Delete more than TAG_MANY to reduce the size of the listing. - while (*num_matches > TAG_MANY) { - xfree((*matches)[--*num_matches]); - } - } - return OK; -} - -/// Called when starting to edit a buffer for a help file. -static void prepare_help_buffer(void) -{ - curbuf->b_help = true; - set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0); - - // Always set these options after jumping to a help tag, because the - // user may have an autocommand that gets in the way. - // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and - // latin1 word characters (for translated help files). - // Only set it when needed, buf_init_chartab() is some work. - char *p = "!-~,^*,^|,^\",192-255"; - if (STRCMP(curbuf->b_p_isk, p) != 0) { - set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); - check_buf_options(curbuf); - (void)buf_init_chartab(curbuf, FALSE); - } - - // Don't use the global foldmethod. - set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0); - - curbuf->b_p_ts = 8; // 'tabstop' is 8. - curwin->w_p_list = FALSE; // No list mode. - - curbuf->b_p_ma = FALSE; // Not modifiable. - curbuf->b_p_bin = FALSE; // Reset 'bin' before reading file. - curwin->w_p_nu = 0; // No line numbers. - curwin->w_p_rnu = 0; // No relative line numbers. - RESET_BINDING(curwin); // No scroll or cursor binding. - curwin->w_p_arab = FALSE; // No arabic mode. - curwin->w_p_rl = FALSE; // Help window is left-to-right. - curwin->w_p_fen = FALSE; // No folding in the help window. - curwin->w_p_diff = FALSE; // No 'diff'. - curwin->w_p_spell = FALSE; // No spell checking. - - set_buflisted(FALSE); -} - -/// After reading a help file: May cleanup a help buffer when syntax -/// highlighting is not used. -void fix_help_buffer(void) -{ - linenr_T lnum; - char *line; - bool in_example = false; - - // Set filetype to "help". - if (STRCMP(curbuf->b_p_ft, "help") != 0) { - curbuf->b_ro_locked++; - set_option_value("ft", 0L, "help", OPT_LOCAL); - curbuf->b_ro_locked--; - } - - if (!syntax_present(curwin)) { - for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { - line = (char *)ml_get_buf(curbuf, lnum, false); - const size_t len = STRLEN(line); - if (in_example && len > 0 && !ascii_iswhite(line[0])) { - // End of example: non-white or '<' in first column. - if (line[0] == '<') { - // blank-out a '<' in the first column - line = (char *)ml_get_buf(curbuf, lnum, true); - line[0] = ' '; - } - in_example = false; - } - if (!in_example && len > 0) { - if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { - // blank-out a '>' in the last column (start of example) - line = (char *)ml_get_buf(curbuf, lnum, true); - line[len - 1] = ' '; - in_example = true; - } else if (line[len - 1] == '~') { - // blank-out a '~' at the end of line (header marker) - line = (char *)ml_get_buf(curbuf, lnum, true); - line[len - 1] = ' '; - } - } - } - } - - /* - * In the "help.txt" and "help.abx" file, add the locally added help - * files. This uses the very first line in the help file. - */ - char *const fname = path_tail(curbuf->b_fname); - if (FNAMECMP(fname, "help.txt") == 0 - || (FNAMENCMP(fname, "help.", 5) == 0 - && ASCII_ISALPHA(fname[5]) - && ASCII_ISALPHA(fname[6]) - && TOLOWER_ASC(fname[7]) == 'x' - && fname[8] == NUL)) { - for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) { - line = (char *)ml_get_buf(curbuf, lnum, false); - if (strstr(line, "*local-additions*") == NULL) { - continue; - } - - // Go through all directories in 'runtimepath', skipping - // $VIMRUNTIME. - char *p = (char *)p_rtp; - while (*p != NUL) { - copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); - char *const rt = vim_getenv("VIMRUNTIME"); - if (rt != NULL - && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { - int fcount; - char **fnames; - char *s; - vimconv_T vc; - char *cp; - - // Find all "doc/ *.txt" files in this directory. - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, "doc/*.??[tx]", - sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - continue; - } - - // Note: We cannot just do `&NameBuff` because it is a statically sized array - // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; - if (gen_expand_wildcards(1, buff_list, &fcount, - &fnames, EW_FILE|EW_SILENT) == OK - && fcount > 0) { - // If foo.abx is found use it instead of foo.txt in - // the same directory. - for (int i1 = 0; i1 < fcount; i1++) { - for (int i2 = 0; i2 < fcount; i2++) { - if (i1 == i2) { - continue; - } - if (fnames[i1] == NULL || fnames[i2] == NULL) { - continue; - } - const char *const f1 = fnames[i1]; - const char *const f2 = fnames[i2]; - const char *const t1 = path_tail(f1); - const char *const t2 = path_tail(f2); - const char *const e1 = (char *)STRRCHR(t1, '.'); - const char *const e2 = (char *)STRRCHR(t2, '.'); - if (e1 == NULL || e2 == NULL) { - continue; - } - if (FNAMECMP(e1, ".txt") != 0 - && FNAMECMP(e1, fname + 4) != 0) { - // Not .txt and not .abx, remove it. - XFREE_CLEAR(fnames[i1]); - continue; - } - if (e1 - f1 != e2 - f2 - || FNAMENCMP(f1, f2, e1 - f1) != 0) { - continue; - } - if (FNAMECMP(e1, ".txt") == 0 - && FNAMECMP(e2, fname + 4) == 0) { - // use .abx instead of .txt - XFREE_CLEAR(fnames[i1]); - } - } - } - for (int fi = 0; fi < fcount; fi++) { - if (fnames[fi] == NULL) { - continue; - } - - FILE *const fd = os_fopen(fnames[fi], "r"); - if (fd == NULL) { - continue; - } - vim_fgets(IObuff, IOSIZE, fd); - if (IObuff[0] == '*' - && (s = vim_strchr((char *)IObuff + 1, '*')) - != NULL) { - TriState this_utf = kNone; - // Change tag definition to a - // reference and remove <CR>/<NL>. - IObuff[0] = '|'; - *s = '|'; - while (*s != NUL) { - if (*s == '\r' || *s == '\n') { - *s = NUL; - } - // The text is utf-8 when a byte - // above 127 is found and no - // illegal byte sequence is found. - if ((char_u)(*s) >= 0x80 && this_utf != kFalse) { - this_utf = kTrue; - const int l = utf_ptr2len(s); - if (l == 1) { - this_utf = kFalse; - } - s += l - 1; - } - ++s; - } - // The help file is latin1 or utf-8; - // conversion to the current - // 'encoding' may be required. - vc.vc_type = CONV_NONE; - convert_setup(&vc, - (char_u *)(this_utf == kTrue ? "utf-8" : "latin1"), - p_enc); - if (vc.vc_type == CONV_NONE) { - // No conversion needed. - cp = (char *)IObuff; - } else { - // Do the conversion. If it fails - // use the unconverted text. - cp = (char *)string_convert(&vc, IObuff, NULL); - if (cp == NULL) { - cp = (char *)IObuff; - } - } - convert_setup(&vc, NULL, NULL); - - ml_append(lnum, cp, (colnr_T)0, false); - if ((char_u *)cp != IObuff) { - xfree(cp); - } - lnum++; - } - fclose(fd); - } - FreeWild(fcount, fnames); - } - } - xfree(rt); - } - break; - } - } -} - -/// ":exusage" -void ex_exusage(exarg_T *eap) -{ - do_cmdline_cmd("help ex-cmd-index"); -} - -/// ":viusage" -void ex_viusage(exarg_T *eap) -{ - do_cmdline_cmd("help normal-index"); -} - -/// Generate tags in one help directory -/// -/// @param dir Path to the doc directory -/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.) -/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for -/// French) -/// @param add_help_tags Whether to add the "help-tags" tag -/// @param ignore_writeerr ignore write error -static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags, - bool ignore_writeerr) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - int filecount; - char **files; - char *p1, *p2; - char *s; - TriState utf8 = kNone; - bool mix = false; // detected mixed encodings - - // Find all *.txt files. - size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); - if (dirlen >= MAXPATHL - || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT - || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - return; - } - - // Note: We cannot just do `&NameBuff` because it is a statically sized array - // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; - const int res = gen_expand_wildcards(1, buff_list, &filecount, &files, - EW_FILE|EW_SILENT); - if (res == FAIL || filecount == 0) { - if (!got_int) { - semsg(_("E151: No match: %s"), NameBuff); - } - if (res != FAIL) { - FreeWild(filecount, files); - } - return; - } - - // - // Open the tags file for writing. - // Do this before scanning through all the files. - // - memcpy(NameBuff, dir, dirlen + 1); - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - return; - } - - FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); - if (fd_tags == NULL) { - if (!ignore_writeerr) { - semsg(_("E152: Cannot open %s for writing"), NameBuff); - } - FreeWild(filecount, files); - return; - } - - // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" - // add the "help-tags" tag. - ga_init(&ga, (int)sizeof(char_u *), 100); - if (add_help_tags - || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) { - size_t s_len = 18 + STRLEN(tagfname); - s = xmalloc(s_len); - snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname); - GA_APPEND(char *, &ga, s); - } - - // Go over all the files and extract the tags. - for (int fi = 0; fi < filecount && !got_int; fi++) { - FILE *const fd = os_fopen(files[fi], "r"); - if (fd == NULL) { - semsg(_("E153: Unable to open %s for reading"), files[fi]); - continue; - } - const char *const fname = files[fi] + dirlen + 1; - - bool firstline = true; - while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { - if (firstline) { - // Detect utf-8 file by a non-ASCII char in the first line. - TriState this_utf8 = kNone; - for (s = (char *)IObuff; *s != NUL; s++) { - if ((char_u)(*s) >= 0x80) { - this_utf8 = kTrue; - const int l = utf_ptr2len(s); - if (l == 1) { - // Illegal UTF-8 byte sequence. - this_utf8 = kFalse; - break; - } - s += l - 1; - } - } - if (this_utf8 == kNone) { // only ASCII characters found - this_utf8 = kFalse; - } - if (utf8 == kNone) { // first file - utf8 = this_utf8; - } else if (utf8 != this_utf8) { - semsg(_("E670: Mix of help file encodings within a language: %s"), - files[fi]); - mix = !got_int; - got_int = TRUE; - } - firstline = false; - } - p1 = vim_strchr((char *)IObuff, '*'); // find first '*' - while (p1 != NULL) { - p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'. - if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". - for (s = p1 + 1; s < p2; s++) { - if (*s == ' ' || *s == '\t' || *s == '|') { - break; - } - } - - // Only accept a *tag* when it consists of valid - // characters, there is white space before it and is - // followed by a white character or end-of-line. - if (s == p2 - && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') - && (vim_strchr(" \t\n\r", s[1]) != NULL - || s[1] == '\0')) { - *p2 = '\0'; - p1++; - size_t s_len= (size_t)(p2 - p1) + STRLEN(fname) + 2; - s = xmalloc(s_len); - GA_APPEND(char *, &ga, s); - snprintf(s, s_len, "%s\t%s", p1, fname); - - // find next '*' - p2 = vim_strchr(p2 + 1, '*'); - } - } - p1 = p2; - } - line_breakcheck(); - } - - fclose(fd); - } - - FreeWild(filecount, files); - - if (!got_int && ga.ga_data != NULL) { - // Sort the tags. - sort_strings(ga.ga_data, ga.ga_len); - - // Check for duplicates. - for (int i = 1; i < ga.ga_len; i++) { - p1 = ((char **)ga.ga_data)[i - 1]; - p2 = ((char **)ga.ga_data)[i]; - while (*p1 == *p2) { - if (*p2 == '\t') { - *p2 = NUL; - vim_snprintf((char *)NameBuff, MAXPATHL, - _("E154: Duplicate tag \"%s\" in file %s/%s"), - ((char_u **)ga.ga_data)[i], dir, p2 + 1); - emsg((char *)NameBuff); - *p2 = '\t'; - break; - } - ++p1; - ++p2; - } - } - - if (utf8 == kTrue) { - fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); - } - - // Write the tags into the file. - for (int i = 0; i < ga.ga_len; i++) { - s = ((char **)ga.ga_data)[i]; - if (STRNCMP(s, "help-tags\t", 10) == 0) { - // help-tags entry was added in formatted form - fputs(s, fd_tags); - } else { - fprintf(fd_tags, "%s\t/" "*", s); - for (p1 = s; *p1 != '\t'; p1++) { - // insert backslash before '\\' and '/' - if (*p1 == '\\' || *p1 == '/') { - putc('\\', fd_tags); - } - putc(*p1, fd_tags); - } - fprintf(fd_tags, "*\n"); - } - } - } - if (mix) { - got_int = false; // continue with other languages - } - - GA_DEEP_CLEAR_PTR(&ga); - fclose(fd_tags); // there is no check for an error... -} - -/// Generate tags in one help directory, taking care of translations. -static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) - FUNC_ATTR_NONNULL_ALL -{ - int len; - garray_T ga; - char lang[2]; - char ext[5]; - char fname[8]; - int filecount; - char **files; - - // Get a list of all files in the help directory and in subdirectories. - STRLCPY(NameBuff, dirname, sizeof(NameBuff)); - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { - emsg(_(e_fnametoolong)); - return; - } - - // Note: We cannot just do `&NameBuff` because it is a statically sized array - // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; - if (gen_expand_wildcards(1, buff_list, &filecount, &files, - EW_FILE|EW_SILENT) == FAIL - || filecount == 0) { - semsg(_("E151: No match: %s"), NameBuff); - return; - } - - // Go over all files in the directory to find out what languages are - // present. - int j; - ga_init(&ga, 1, 10); - for (int i = 0; i < filecount; i++) { - len = (int)STRLEN(files[i]); - if (len <= 4) { - continue; - } - - if (STRICMP(files[i] + len - 4, ".txt") == 0) { - // ".txt" -> language "en" - lang[0] = 'e'; - lang[1] = 'n'; - } else if (files[i][len - 4] == '.' - && ASCII_ISALPHA(files[i][len - 3]) - && ASCII_ISALPHA(files[i][len - 2]) - && TOLOWER_ASC(files[i][len - 1]) == 'x') { - // ".abx" -> language "ab" - lang[0] = (char)TOLOWER_ASC(files[i][len - 3]); - lang[1] = (char)TOLOWER_ASC(files[i][len - 2]); - } else { - continue; - } - - // Did we find this language already? - for (j = 0; j < ga.ga_len; j += 2) { - if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) { - break; - } - } - if (j == ga.ga_len) { - // New language, add it. - ga_grow(&ga, 2); - ((char *)ga.ga_data)[ga.ga_len++] = lang[0]; - ((char *)ga.ga_data)[ga.ga_len++] = lang[1]; - } - } - - /* - * Loop over the found languages to generate a tags file for each one. - */ - for (j = 0; j < ga.ga_len; j += 2) { - STRCPY(fname, "tags-xx"); - fname[5] = ((char *)ga.ga_data)[j]; - fname[6] = ((char *)ga.ga_data)[j + 1]; - if (fname[5] == 'e' && fname[6] == 'n') { - // English is an exception: use ".txt" and "tags". - fname[4] = NUL; - STRCPY(ext, ".txt"); - } else { - // Language "ab" uses ".abx" and "tags-ab". - STRCPY(ext, ".xxx"); - ext[1] = fname[5]; - ext[2] = fname[6]; - } - helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr); - } - - ga_clear(&ga); - FreeWild(filecount, files); -} - -static void helptags_cb(char *fname, void *cookie) - FUNC_ATTR_NONNULL_ALL -{ - do_helptags(fname, *(bool *)cookie, true); -} - -/// ":helptags" -void ex_helptags(exarg_T *eap) -{ - expand_T xpc; - char *dirname; - bool add_help_tags = false; - - // Check for ":helptags ++t {dir}". - if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { - add_help_tags = true; - eap->arg = skipwhite(eap->arg + 3); - } - - if (STRCMP(eap->arg, "ALL") == 0) { - do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags); - } else { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_DIRECTORIES; - dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL, - WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); - if (dirname == NULL || !os_isdir((char_u *)dirname)) { - semsg(_("E150: Not a directory: %s"), eap->arg); - } else { - do_helptags(dirname, add_help_tags, false); - } - xfree(dirname); - } -} - -/// ":helpclose": Close one help window -void ex_helpclose(exarg_T *eap) -{ - FOR_ALL_WINDOWS_IN_TAB(win, curtab) { - if (bt_help(win->w_buffer)) { - win_close(win, false, eap->forceit); - return; - } - } -} - /// Shows the effects of the :substitute command being typed ('inccommand'). /// If inccommand=split, shows a preview window and later restores the layout. /// diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index a55e74a789..3aaba9ce42 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -20,9 +20,9 @@ #define ECMD_NOWINENTER 0x40 // do not trigger BufWinEnter // for lnum argument in do_ecmd() -#define ECMD_LASTL (linenr_T)0 // use last position in loaded file -#define ECMD_LAST ((linenr_T) - 1) // use last position in all files -#define ECMD_ONE (linenr_T)1 // use first line +#define ECMD_LASTL (linenr_T)0 // use last position in loaded file +#define ECMD_LAST ((linenr_T)(-1)) // use last position in all files +#define ECMD_ONE (linenr_T)1 // use first line /// Previous :substitute replacement string definition typedef struct { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index a5ba5e0b30..4bed1e94b9 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -107,6 +107,12 @@ module.cmds = { func='ex_listdo', }, { + command='argdedupe', + flags=TRLBAR, + addr_type='ADDR_NONE', + func='ex_argdedupe', + }, + { command='argedit', flags=bit.bor(BANG, NEEDARG, RANGE, ZEROR, FILES, CMDARG, ARGOPT, TRLBAR), addr_type='ADDR_ARGUMENTS', @@ -3175,7 +3181,7 @@ module.cmds = { }, { command='wincmd', - flags=bit.bor(NEEDARG, WORD1, RANGE, CMDWIN, LOCK_OK), + flags=bit.bor(NEEDARG, WORD1, RANGE, COUNT, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_wincmd', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 730a2f1b69..54315a6417 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -11,30 +11,27 @@ #include <stdbool.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/globals.h" #include "nvim/vim.h" #ifdef HAVE_LOCALE_H # include <locale.h> #endif -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" -#include "nvim/debugger.h" -#include "nvim/eval/userfunc.h" +#include "nvim/eval.h" #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" -#include "nvim/garray.h" -#include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/mbyte.h" -#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" @@ -42,101 +39,19 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" -#include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/profile.h" #include "nvim/quickfix.h" -#include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/undo.h" -#include "nvim/version.h" #include "nvim/window.h" -/// Growarray to store info about already sourced scripts. -static garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL }; -#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) - -// Struct used in sn_prl_ga for every line of a script. -typedef struct sn_prl_S { - int snp_count; ///< nr of times line was executed - proftime_T sn_prl_total; ///< time spent in a line + children - proftime_T sn_prl_self; ///< time spent in a line itself -} sn_prl_T; - -/// Structure used to store info for each sourced file. -/// It is shared between do_source() and getsourceline(). -/// This is required, because it needs to be handed to do_cmdline() and -/// sourcing can be done recursively. -struct source_cookie { - FILE *fp; ///< opened file for sourcing - char *nextline; ///< if not NULL: line that was read ahead - linenr_T sourcing_lnum; ///< line number of the source file - int finished; ///< ":finish" used -#if defined(USE_CRNL) - int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS - bool error; ///< true if LF found after CR-LF -#endif - linenr_T breakpoint; ///< next line with breakpoint or zero - char *fname; ///< name of sourced file - int dbg_tick; ///< debug_tick when breakpoint was set - int level; ///< top nesting level of sourced file - vimconv_T conv; ///< type of conversion -}; - -#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.c.generated.h" #endif -static char *profile_fname = NULL; - -/// ":profile cmd args" -void ex_profile(exarg_T *eap) -{ - static proftime_T pause_time; - - char *e; - int len; - - e = (char *)skiptowhite((char_u *)eap->arg); - len = (int)(e - eap->arg); - e = skipwhite(e); - - if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { - xfree(profile_fname); - profile_fname = (char *)expand_env_save_opt((char_u *)e, true); - do_profiling = PROF_YES; - profile_set_wait(profile_zero()); - set_vim_var_nr(VV_PROFILING, 1L); - } else if (do_profiling == PROF_NONE) { - emsg(_("E750: First use \":profile start {fname}\"")); - } else if (STRCMP(eap->arg, "stop") == 0) { - profile_dump(); - do_profiling = PROF_NONE; - set_vim_var_nr(VV_PROFILING, 0L); - profile_reset(); - } else if (STRCMP(eap->arg, "pause") == 0) { - if (do_profiling == PROF_YES) { - pause_time = profile_start(); - } - do_profiling = PROF_PAUSED; - } else if (STRCMP(eap->arg, "continue") == 0) { - if (do_profiling == PROF_PAUSED) { - pause_time = profile_end(pause_time); - profile_set_wait(profile_add(profile_get_wait(), pause_time)); - } - do_profiling = PROF_YES; - } else if (STRCMP(eap->arg, "dump") == 0) { - profile_dump(); - } else { - // The rest is similar to ":breakadd". - ex_breakadd(eap); - } -} - void ex_ruby(exarg_T *eap) { script_host_execute("ruby", eap); @@ -182,273 +97,6 @@ void ex_perldo(exarg_T *eap) script_host_do_range("perl", eap); } -// Command line expansion for :profile. -static enum { - PEXP_SUBCMD, ///< expand :profile sub-commands - PEXP_FUNC, ///< expand :profile func {funcname} -} pexpand_what; - -static char *pexpand_cmds[] = { - "continue", - "dump", - "file", - "func", - "pause", - "start", - "stop", - NULL -}; - -/// Function given to ExpandGeneric() to obtain the profile command -/// specific expansion. -char *get_profile_name(expand_T *xp, int idx) - FUNC_ATTR_PURE -{ - switch (pexpand_what) { - case PEXP_SUBCMD: - return pexpand_cmds[idx]; - // case PEXP_FUNC: TODO - default: - return NULL; - } -} - -/// Handle command line completion for :profile command. -void set_context_in_profile_cmd(expand_T *xp, const char *arg) -{ - // Default: expand subcommands. - xp->xp_context = EXPAND_PROFILE; - pexpand_what = PEXP_SUBCMD; - xp->xp_pattern = (char *)arg; - - char_u *const end_subcmd = skiptowhite((const char_u *)arg); - if (*end_subcmd == NUL) { - return; - } - - if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) { - xp->xp_context = EXPAND_FILES; - xp->xp_pattern = skipwhite((char *)end_subcmd); - return; - } - - // TODO(tarruda): expand function names after "func" - xp->xp_context = EXPAND_NOTHING; -} - -/// Dump the profiling info. -void profile_dump(void) -{ - FILE *fd; - - if (profile_fname != NULL) { - fd = os_fopen(profile_fname, "w"); - if (fd == NULL) { - semsg(_(e_notopen), profile_fname); - } else { - script_dump_profile(fd); - func_dump_profile(fd); - fclose(fd); - } - } -} - -/// Reset all profiling information. -static void profile_reset(void) -{ - // Reset sourced files. - for (int id = 1; id <= script_items.ga_len; id++) { - scriptitem_T *si = &SCRIPT_ITEM(id); - if (si->sn_prof_on) { - si->sn_prof_on = false; - si->sn_pr_force = false; - si->sn_pr_child = profile_zero(); - si->sn_pr_nest = 0; - si->sn_pr_count = 0; - si->sn_pr_total = profile_zero(); - si->sn_pr_self = profile_zero(); - si->sn_pr_start = profile_zero(); - si->sn_pr_children = profile_zero(); - ga_clear(&si->sn_prl_ga); - si->sn_prl_start = profile_zero(); - si->sn_prl_children = profile_zero(); - si->sn_prl_wait = profile_zero(); - si->sn_prl_idx = -1; - si->sn_prl_execed = 0; - } - } - - // Reset functions. - size_t n = func_hashtab.ht_used; - hashitem_T *hi = func_hashtab.ht_array; - - for (; n > (size_t)0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - n--; - ufunc_T *uf = HI2UF(hi); - if (uf->uf_prof_initialized) { - uf->uf_profiling = 0; - uf->uf_tm_count = 0; - uf->uf_tm_total = profile_zero(); - uf->uf_tm_self = profile_zero(); - uf->uf_tm_children = profile_zero(); - - for (int i = 0; i < uf->uf_lines.ga_len; i++) { - uf->uf_tml_count[i] = 0; - uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0; - } - - uf->uf_tml_start = profile_zero(); - uf->uf_tml_children = profile_zero(); - uf->uf_tml_wait = profile_zero(); - uf->uf_tml_idx = -1; - uf->uf_tml_execed = 0; - } - } - } - - XFREE_CLEAR(profile_fname); -} - -/// Start profiling a script. -static void profile_init(scriptitem_T *si) -{ - si->sn_pr_count = 0; - si->sn_pr_total = profile_zero(); - si->sn_pr_self = profile_zero(); - - ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); - si->sn_prl_idx = -1; - si->sn_prof_on = true; - si->sn_pr_nest = 0; -} - -/// Save time when starting to invoke another script or function. -/// -/// @param tm place to store wait time -void script_prof_save(proftime_T *tm) -{ - scriptitem_T *si; - - if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && si->sn_pr_nest++ == 0) { - si->sn_pr_child = profile_start(); - } - } - *tm = profile_get_wait(); -} - -/// Count time spent in children after invoking another script or function. -void script_prof_restore(proftime_T *tm) -{ - scriptitem_T *si; - - if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && --si->sn_pr_nest == 0) { - si->sn_pr_child = profile_end(si->sn_pr_child); - // don't count wait time - si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); - si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); - si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); - } - } -} - -static proftime_T inchar_time; - -/// Called when starting to wait for the user to type a character. -void prof_inchar_enter(void) -{ - inchar_time = profile_start(); -} - -/// Called when finished waiting for the user to type a character. -void prof_inchar_exit(void) -{ - inchar_time = profile_end(inchar_time); - profile_set_wait(profile_add(profile_get_wait(), inchar_time)); -} - -/// Dump the profiling results for all scripts in file "fd". -static void script_dump_profile(FILE *fd) -{ - scriptitem_T *si; - FILE *sfd; - sn_prl_T *pp; - - for (int id = 1; id <= script_items.ga_len; id++) { - si = &SCRIPT_ITEM(id); - if (si->sn_prof_on) { - fprintf(fd, "SCRIPT %s\n", si->sn_name); - if (si->sn_pr_count == 1) { - fprintf(fd, "Sourced 1 time\n"); - } else { - fprintf(fd, "Sourced %d times\n", si->sn_pr_count); - } - fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total)); - fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self)); - fprintf(fd, "\n"); - fprintf(fd, "count total (s) self (s)\n"); - - sfd = os_fopen((char *)si->sn_name, "r"); - if (sfd == NULL) { - fprintf(fd, "Cannot open file!\n"); - } else { - // Keep going till the end of file, so that trailing - // continuation lines are listed. - for (int i = 0;; i++) { - if (vim_fgets(IObuff, IOSIZE, sfd)) { - break; - } - // When a line has been truncated, append NL, taking care - // of multi-byte characters . - if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) { - int n = IOSIZE - 2; - - // Move to the first byte of this char. - // utf_head_off() doesn't work, because it checks - // for a truncated character. - while (n > 0 && (IObuff[n] & 0xc0) == 0x80) { - n--; - } - - IObuff[n] = NL; - IObuff[n + 1] = NUL; - } - if (i < si->sn_prl_ga.ga_len - && (pp = &PRL_ITEM(si, i))->snp_count > 0) { - fprintf(fd, "%5d ", pp->snp_count); - if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) { - fprintf(fd, " "); - } else { - fprintf(fd, "%s ", profile_msg(pp->sn_prl_total)); - } - fprintf(fd, "%s ", profile_msg(pp->sn_prl_self)); - } else { - fprintf(fd, " "); - } - fprintf(fd, "%s", IObuff); - } - fclose(sfd); - } - fprintf(fd, "\n"); - } - } -} - -/// @return true when a function defined in the current script should be -/// profiled. -bool prof_def_func(void) - FUNC_ATTR_PURE -{ - if (current_sctx.sc_sid > 0) { - return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force; - } - return false; -} - /// If 'autowrite' option set, try to write the file. /// Careful: autocommands may make "buf" invalid! /// @@ -796,483 +444,6 @@ int buf_write_all(buf_T *buf, int forceit) return retval; } -/// Code to handle the argument list. - -#define AL_SET 1 -#define AL_ADD 2 -#define AL_DEL 3 - -/// Isolate one argument, taking backticks. -/// Changes the argument in-place, puts a NUL after it. Backticks remain. -/// -/// @return a pointer to the start of the next argument. -static char *do_one_arg(char *str) -{ - char *p; - bool inbacktick; - - inbacktick = false; - for (p = str; *str; str++) { - // When the backslash is used for escaping the special meaning of a - // character we need to keep it until wildcard expansion. - if (rem_backslash((char_u *)str)) { - *p++ = *str++; - *p++ = *str; - } else { - // An item ends at a space not in backticks - if (!inbacktick && ascii_isspace(*str)) { - break; - } - if (*str == '`') { - inbacktick ^= true; - } - *p++ = *str; - } - } - str = skipwhite(str); - *p = NUL; - - return str; -} - -/// Separate the arguments in "str" and return a list of pointers in the -/// growarray "gap". -static void get_arglist(garray_T *gap, char *str, int escaped) -{ - ga_init(gap, (int)sizeof(char_u *), 20); - while (*str != NUL) { - GA_APPEND(char *, gap, str); - - // If str is escaped, don't handle backslashes or spaces - if (!escaped) { - return; - } - - // Isolate one argument, change it in-place, put a NUL after it. - str = do_one_arg(str); - } -} - -/// Parse a list of arguments (file names), expand them and return in -/// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. -/// -/// @return FAIL or OK. -int get_arglist_exp(char_u *str, int *fcountp, char ***fnamesp, bool wig) -{ - garray_T ga; - int i; - - get_arglist(&ga, (char *)str, true); - - if (wig) { - i = expand_wildcards(ga.ga_len, ga.ga_data, - fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); - } else { - i = gen_expand_wildcards(ga.ga_len, ga.ga_data, - fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); - } - - ga_clear(&ga); - return i; -} - -/// @param str -/// @param what -/// AL_SET: Redefine the argument list to 'str'. -/// AL_ADD: add files in 'str' to the argument list after "after". -/// AL_DEL: remove files in 'str' from the argument list. -/// @param after -/// 0 means before first one -/// @param will_edit will edit added argument -/// -/// @return FAIL for failure, OK otherwise. -static int do_arglist(char *str, int what, int after, bool will_edit) - FUNC_ATTR_NONNULL_ALL -{ - garray_T new_ga; - int exp_count; - char **exp_files; - char *p; - int match; - int arg_escaped = true; - - // Set default argument for ":argadd" command. - if (what == AL_ADD && *str == NUL) { - if (curbuf->b_ffname == NULL) { - return FAIL; - } - str = curbuf->b_fname; - arg_escaped = false; - } - - // Collect all file name arguments in "new_ga". - get_arglist(&new_ga, str, arg_escaped); - - if (what == AL_DEL) { - regmatch_T regmatch; - bool didone; - - // Delete the items: use each item as a regexp and find a match in the - // argument list. - regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set - for (int i = 0; i < new_ga.ga_len && !got_int; i++) { - p = ((char **)new_ga.ga_data)[i]; - p = file_pat_to_reg_pat(p, NULL, NULL, false); - if (p == NULL) { - break; - } - regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - xfree(p); - break; - } - - didone = false; - for (match = 0; match < ARGCOUNT; match++) { - if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { - didone = true; - xfree(ARGLIST[match].ae_fname); - memmove(ARGLIST + match, ARGLIST + match + 1, - (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); - ALIST(curwin)->al_ga.ga_len--; - if (curwin->w_arg_idx > match) { - curwin->w_arg_idx--; - } - match--; - } - } - - vim_regfree(regmatch.regprog); - xfree(p); - if (!didone) { - semsg(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]); - } - } - ga_clear(&new_ga); - } else { - int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data, - &exp_count, &exp_files, - EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); - ga_clear(&new_ga); - if (i == FAIL || exp_count == 0) { - emsg(_(e_nomatch)); - return FAIL; - } - - if (what == AL_ADD) { - alist_add_list(exp_count, exp_files, after, will_edit); - xfree(exp_files); - } else { - assert(what == AL_SET); - alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); - } - } - - alist_check_arg_idx(); - - return OK; -} - -/// Check the validity of the arg_idx for each other window. -static void alist_check_arg_idx(void) -{ - FOR_ALL_TAB_WINDOWS(tp, win) { - if (win->w_alist == curwin->w_alist) { - check_arg_idx(win); - } - } -} - -/// @return true if window "win" is editing the file at the current argument -/// index. -static bool editing_arg_idx(win_T *win) -{ - return !(win->w_arg_idx >= WARGCOUNT(win) - || (win->w_buffer->b_fnum - != WARGLIST(win)[win->w_arg_idx].ae_fnum - && (win->w_buffer->b_ffname == NULL - || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]), - win->w_buffer->b_ffname, true, - true) & kEqualFiles)))); -} - -/// Check if window "win" is editing the w_arg_idx file in its argument list. -void check_arg_idx(win_T *win) -{ - if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) { - // We are not editing the current entry in the argument list. - // Set "arg_had_last" if we are editing the last one. - win->w_arg_idx_invalid = true; - if (win->w_arg_idx != WARGCOUNT(win) - 1 - && arg_had_last == false - && ALIST(win) == &global_alist - && GARGCOUNT > 0 - && win->w_arg_idx < GARGCOUNT - && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum - || (win->w_buffer->b_ffname != NULL - && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), - win->w_buffer->b_ffname, true, true) - & kEqualFiles)))) { - arg_had_last = true; - } - } else { - // We are editing the current entry in the argument list. - // Set "arg_had_last" if it's also the last one - win->w_arg_idx_invalid = false; - if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { - arg_had_last = true; - } - } -} - -/// ":args", ":argslocal" and ":argsglobal". -void ex_args(exarg_T *eap) -{ - if (eap->cmdidx != CMD_args) { - alist_unlink(ALIST(curwin)); - if (eap->cmdidx == CMD_argglobal) { - ALIST(curwin) = &global_alist; - } else { // eap->cmdidx == CMD_arglocal - alist_new(); - } - } - - if (*eap->arg != NUL) { - // ":args file ..": define new argument list, handle like ":next" - // Also for ":argslocal file .." and ":argsglobal file ..". - ex_next(eap); - } else if (eap->cmdidx == CMD_args) { - // ":args": list arguments. - if (ARGCOUNT > 0) { - char **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT); - // Overwrite the command, for a short list there is no scrolling - // required and no wait_return(). - gotocmdline(true); - for (int i = 0; i < ARGCOUNT; i++) { - items[i] = alist_name(&ARGLIST[i]); - } - list_in_columns((char_u **)items, ARGCOUNT, curwin->w_arg_idx); - xfree(items); - } - } else if (eap->cmdidx == CMD_arglocal) { - garray_T *gap = &curwin->w_alist->al_ga; - - // ":argslocal": make a local copy of the global argument list. - ga_grow(gap, GARGCOUNT); - for (int i = 0; i < GARGCOUNT; i++) { - if (GARGLIST[i].ae_fname != NULL) { - AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = - vim_strsave(GARGLIST[i].ae_fname); - AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = - GARGLIST[i].ae_fnum; - gap->ga_len++; - } - } - } -} - -/// ":previous", ":sprevious", ":Next" and ":sNext". -void ex_previous(exarg_T *eap) -{ - // If past the last one already, go to the last one. - if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) { - do_argfile(eap, ARGCOUNT - 1); - } else { - do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); - } -} - -/// ":rewind", ":first", ":sfirst" and ":srewind". -void ex_rewind(exarg_T *eap) -{ - do_argfile(eap, 0); -} - -/// ":last" and ":slast". -void ex_last(exarg_T *eap) -{ - do_argfile(eap, ARGCOUNT - 1); -} - -/// ":argument" and ":sargument". -void ex_argument(exarg_T *eap) -{ - int i; - - if (eap->addr_count > 0) { - i = (int)eap->line2 - 1; - } else { - i = curwin->w_arg_idx; - } - do_argfile(eap, i); -} - -/// Edit file "argn" of the argument lists. -void do_argfile(exarg_T *eap, int argn) -{ - int other; - char *p; - int old_arg_idx = curwin->w_arg_idx; - - if (argn < 0 || argn >= ARGCOUNT) { - if (ARGCOUNT <= 1) { - emsg(_("E163: There is only one file to edit")); - } else if (argn < 0) { - emsg(_("E164: Cannot go before first file")); - } else { - emsg(_("E165: Cannot go beyond last file")); - } - } else { - setpcmark(); - - // split window or create new tab page first - if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { - if (win_split(0, 0) == FAIL) { - return; - } - RESET_BINDING(curwin); - } else { - // if 'hidden' set, only check for changed file when re-editing - // the same buffer - other = true; - if (buf_hide(curbuf)) { - p = fix_fname(alist_name(&ARGLIST[argn])); - other = otherfile(p); - xfree(p); - } - if ((!buf_hide(curbuf) || !other) - && check_changed(curbuf, CCGD_AW - | (other ? 0 : CCGD_MULTWIN) - | (eap->forceit ? CCGD_FORCEIT : 0) - | CCGD_EXCMD)) { - return; - } - } - - curwin->w_arg_idx = argn; - if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { - arg_had_last = true; - } - - // Edit the file; always use the last known line number. - // When it fails (e.g. Abort for already edited file) restore the - // argument index. - if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, - eap, ECMD_LAST, - (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) - + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { - curwin->w_arg_idx = old_arg_idx; - } else if (eap->cmdidx != CMD_argdo) { - // like Vi: set the mark where the cursor is in the file. - setmark('\''); - } - } -} - -/// ":next", and commands that behave like it. -void ex_next(exarg_T *eap) -{ - int i; - - // check for changed buffer now, if this fails the argument list is not - // redefined. - if (buf_hide(curbuf) - || eap->cmdidx == CMD_snext - || !check_changed(curbuf, CCGD_AW - | (eap->forceit ? CCGD_FORCEIT : 0) - | CCGD_EXCMD)) { - if (*eap->arg != NUL) { // redefine file list - if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { - return; - } - i = 0; - } else { - i = curwin->w_arg_idx + (int)eap->line2; - } - do_argfile(eap, i); - } -} - -/// ":argedit" -void ex_argedit(exarg_T *eap) -{ - int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; - // Whether curbuf will be reused, curbuf->b_ffname will be set. - bool curbuf_is_reusable = curbuf_reusable(); - - if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { - return; - } - maketitle(); - - if (curwin->w_arg_idx == 0 - && (curbuf->b_ml.ml_flags & ML_EMPTY) - && (curbuf->b_ffname == NULL || curbuf_is_reusable)) { - i = 0; - } - // Edit the argument. - if (i < ARGCOUNT) { - do_argfile(eap, i); - } -} - -/// ":argadd" -void ex_argadd(exarg_T *eap) -{ - do_arglist(eap->arg, AL_ADD, - eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, - false); - maketitle(); -} - -/// ":argdelete" -void ex_argdelete(exarg_T *eap) -{ - if (eap->addr_count > 0 || *eap->arg == NUL) { - // ":argdel" works like ":.argdel" - if (eap->addr_count == 0) { - if (curwin->w_arg_idx >= ARGCOUNT) { - emsg(_("E610: No argument to delete")); - return; - } - eap->line1 = eap->line2 = curwin->w_arg_idx + 1; - } else if (eap->line2 > ARGCOUNT) { - // ":1,4argdel": Delete all arguments in the range. - eap->line2 = ARGCOUNT; - } - linenr_T n = eap->line2 - eap->line1 + 1; - if (*eap->arg != NUL) { - // Can't have both a range and an argument. - emsg(_(e_invarg)); - } else if (n <= 0) { - // Don't give an error for ":%argdel" if the list is empty. - if (eap->line1 != 1 || eap->line2 != 0) { - emsg(_(e_invrange)); - } - } else { - for (linenr_T i = eap->line1; i <= eap->line2; i++) { - xfree(ARGLIST[i - 1].ae_fname); - } - memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, - (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T)); - ALIST(curwin)->al_ga.ga_len -= (int)n; - if (curwin->w_arg_idx >= eap->line2) { - curwin->w_arg_idx -= (int)n; - } else if (curwin->w_arg_idx > eap->line1) { - curwin->w_arg_idx = (int)eap->line1; - } - if (ARGCOUNT == 0) { - curwin->w_arg_idx = 0; - } else if (curwin->w_arg_idx >= ARGCOUNT) { - curwin->w_arg_idx = ARGCOUNT - 1; - } - } - } else { - do_arglist(eap->arg, AL_DEL, 0, false); - } - maketitle(); -} - /// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo" void ex_listdo(exarg_T *eap) { @@ -1524,51 +695,6 @@ void ex_listdo(exarg_T *eap) } } -/// Add files[count] to the arglist of the current window after arg "after". -/// The file names in files[count] must have been allocated and are taken over. -/// Files[] itself is not taken over. -/// -/// @param after: where to add: 0 = before first one -/// @param will_edit will edit adding argument -static void alist_add_list(int count, char **files, int after, bool will_edit) - FUNC_ATTR_NONNULL_ALL -{ - int old_argcount = ARGCOUNT; - ga_grow(&ALIST(curwin)->al_ga, count); - { - if (after < 0) { - after = 0; - } - if (after > ARGCOUNT) { - after = ARGCOUNT; - } - if (after < ARGCOUNT) { - memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), - (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); - } - for (int i = 0; i < count; i++) { - const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); - ARGLIST[after + i].ae_fname = (char_u *)files[i]; - ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); - } - ALIST(curwin)->al_ga.ga_len += count; - if (old_argcount > 0 && curwin->w_arg_idx >= after) { - curwin->w_arg_idx += count; - } - return; - } -} - -// Function given to ExpandGeneric() to obtain the possible arguments of the -// argedit and argdelete commands. -char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx >= ARGCOUNT) { - return NULL; - } - return alist_name(&ARGLIST[idx]); -} - /// ":compiler[!] {name}" void ex_compiler(exarg_T *eap) { @@ -1632,987 +758,6 @@ void ex_compiler(exarg_T *eap) } } -/// ":options" -void ex_options(exarg_T *eap) -{ - char buf[500]; - bool multi_mods = 0; - - buf[0] = NUL; - (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); - - os_setenv("OPTWIN_CMD", buf, 1); - cmd_source(SYS_OPTWIN_FILE, NULL); -} - -/// ":source [{fname}]" -void ex_source(exarg_T *eap) -{ - cmd_source(eap->arg, eap); -} - -static void cmd_source(char *fname, exarg_T *eap) -{ - if (eap != NULL && *fname == NUL) { - cmd_source_buffer(eap); - } else if (eap != NULL && eap->forceit) { - // ":source!": read Normal mode commands - // Need to execute the commands directly. This is required at least - // for: - // - ":g" command busy - // - after ":argdo", ":windo" or ":bufdo" - // - another command follows - // - inside a loop - openscript((char_u *)fname, global_busy || listcmd_busy || eap->nextcmd != NULL - || eap->cstack->cs_idx >= 0); - - // ":source" read ex commands - } else if (do_source(fname, false, DOSO_NONE) == FAIL) { - semsg(_(e_notopen), fname); - } -} - -/// Concatenate VimL line if it starts with a line continuation into a growarray -/// (excluding the continuation chars and leading whitespace) -/// -/// @note Growsize of the growarray may be changed to speed up concatenations! -/// -/// @param ga the growarray to append to -/// @param init_growsize the starting growsize value of the growarray -/// @param p pointer to the beginning of the line to consider -/// @param len the length of this line -/// -/// @return true if this line did begin with a continuation (the next line -/// should also be considered, if it exists); false otherwise -static bool concat_continued_line(garray_T *const ga, const int init_growsize, - const char_u *const p, size_t len) - FUNC_ATTR_NONNULL_ALL -{ - const char *const line = (char *)skipwhite_len(p, len); - len -= (size_t)((char_u *)line - p); - // Skip lines starting with '\" ', concat lines starting with '\' - if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) { - return true; - } else if (len == 0 || line[0] != '\\') { - return false; - } - if (ga->ga_len > init_growsize) { - ga_set_growsize(ga, MIN(ga->ga_len, 8000)); - } - ga_concat_len(ga, line + 1, len - 1); - return true; -} - -typedef struct { - linenr_T curr_lnum; - const linenr_T final_lnum; -} GetBufferLineCookie; - -/// ":source" and associated commands. -/// -/// @return address holding the next breakpoint line for a source cookie -linenr_T *source_breakpoint(void *cookie) -{ - return &((struct source_cookie *)cookie)->breakpoint; -} - -/// @return the address holding the debug tick for a source cookie. -int *source_dbg_tick(void *cookie) -{ - return &((struct source_cookie *)cookie)->dbg_tick; -} - -/// @return the nesting level for a source cookie. -int source_level(void *cookie) - FUNC_ATTR_PURE -{ - return ((struct source_cookie *)cookie)->level; -} - -/// Special function to open a file without handle inheritance. -/// If possible the handle is closed on exec(). -static FILE *fopen_noinh_readbin(char *filename) -{ -#ifdef WIN32 - int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0); -#else - int fd_tmp = os_open(filename, O_RDONLY, 0); -#endif - - if (fd_tmp < 0) { - return NULL; - } - - (void)os_set_cloexec(fd_tmp); - - return fdopen(fd_tmp, READBIN); -} - -typedef struct { - char *buf; - size_t offset; -} GetStrLineCookie; - -/// Get one full line from a sourced string (in-memory, no file). -/// Called by do_cmdline() when it's called from do_source_str(). -/// -/// @return pointer to allocated line, or NULL for end-of-file or -/// some error. -static char *get_str_line(int c, void *cookie, int indent, bool do_concat) -{ - GetStrLineCookie *p = cookie; - if (STRLEN(p->buf) <= p->offset) { - return NULL; - } - const char *line = p->buf + p->offset; - const char *eol = (char *)skip_to_newline((char_u *)line); - garray_T ga; - ga_init(&ga, sizeof(char_u), 400); - ga_concat_len(&ga, line, (size_t)(eol - line)); - if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { - while (eol[0] != NUL) { - line = eol + 1; - const char_u *const next_eol = skip_to_newline((char_u *)line); - if (!concat_continued_line(&ga, 400, (char_u *)line, (size_t)(next_eol - (char_u *)line))) { - break; - } - eol = (char *)next_eol; - } - } - ga_append(&ga, NUL); - p->offset = (size_t)(eol - p->buf) + 1; - return ga.ga_data; -} - -/// Create a new script item and allocate script-local vars. @see new_script_vars -/// -/// @param name File name of the script. NULL for anonymous :source. -/// @param[out] sid_out SID of the new item. -/// -/// @return pointer to the created script item. -scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) -{ - static scid_T last_current_SID = 0; - const scid_T sid = ++last_current_SID; - if (sid_out != NULL) { - *sid_out = sid; - } - ga_grow(&script_items, sid - script_items.ga_len); - while (script_items.ga_len < sid) { - script_items.ga_len++; - SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; - SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false; - } - SCRIPT_ITEM(sid).sn_name = (char_u *)name; - new_script_vars(sid); // Allocate the local script variables to use for this script. - return &SCRIPT_ITEM(sid); -} - -static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name) -{ - char *save_sourcing_name = sourcing_name; - linenr_T save_sourcing_lnum = sourcing_lnum; - char sourcing_name_buf[256]; - if (save_sourcing_name == NULL) { - sourcing_name = (char *)traceback_name; - } else { - snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf), - "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name, - save_sourcing_lnum); - sourcing_name = sourcing_name_buf; // -V507 reassigned below, before return. - } - sourcing_lnum = 0; - - const sctx_T save_current_sctx = current_sctx; - if (current_sctx.sc_sid != SID_LUA) { - current_sctx.sc_sid = SID_STR; - } - current_sctx.sc_seq = 0; - current_sctx.sc_lnum = save_sourcing_lnum; - funccal_entry_T entry; - save_funccal(&entry); - int retval = do_cmdline(NULL, fgetline, cookie, - DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); - sourcing_lnum = save_sourcing_lnum; - sourcing_name = save_sourcing_name; - current_sctx = save_current_sctx; - restore_funccal(); - return retval; -} - -static void cmd_source_buffer(const exarg_T *const eap) - FUNC_ATTR_NONNULL_ALL -{ - if (curbuf == NULL) { - return; - } - garray_T ga; - ga_init(&ga, sizeof(char_u), 400); - const linenr_T final_lnum = eap->line2; - // Copy the contents to be executed. - for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { - // Adjust growsize to current length to speed up concatenating many lines. - if (ga.ga_len > 400) { - ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); - } - ga_concat(&ga, (char *)ml_get(curr_lnum)); - ga_append(&ga, NL); - } - ((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL; - const GetStrLineCookie cookie = { - .buf = ga.ga_data, - .offset = 0, - }; - if (curbuf->b_fname - && path_with_extension((const char *)curbuf->b_fname, "lua")) { - nlua_source_using_linegetter(get_str_line, (void *)&cookie, - ":source (no file)"); - } else { - source_using_linegetter((void *)&cookie, get_str_line, - ":source (no file)"); - } - ga_clear(&ga); -} - -/// Executes lines in `src` as Ex commands. -/// -/// @see do_source() -int do_source_str(const char *cmd, const char *traceback_name) -{ - GetStrLineCookie cookie = { - .buf = (char *)cmd, - .offset = 0, - }; - return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); -} - -/// When fname is a 'lua' file nlua_exec_file() is invoked to source it. -/// Otherwise reads the file `fname` and executes its lines as Ex commands. -/// -/// This function may be called recursively! -/// -/// @see do_source_str -/// -/// @param fname -/// @param check_other check for .vimrc and _vimrc -/// @param is_vimrc DOSO_ value -/// -/// @return FAIL if file could not be opened, OK otherwise -int do_source(char *fname, int check_other, int is_vimrc) -{ - struct source_cookie cookie; - char *save_sourcing_name; - linenr_T save_sourcing_lnum; - char *p; - char *fname_exp; - uint8_t *firstline = NULL; - int retval = FAIL; - int save_debug_break_level = debug_break_level; - scriptitem_T *si = NULL; - proftime_T wait_start; - bool trigger_source_post = false; - - p = expand_env_save(fname); - if (p == NULL) { - return retval; - } - fname_exp = fix_fname(p); - xfree(p); - if (fname_exp == NULL) { - return retval; - } - if (os_isdir((char_u *)fname_exp)) { - smsg(_("Cannot source a directory: \"%s\""), fname); - goto theend; - } - - // Apply SourceCmd autocommands, they should get the file and source it. - if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) - && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, - false, curbuf)) { - retval = aborting() ? FAIL : OK; - if (retval == OK) { - // Apply SourcePost autocommands. - apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); - } - goto theend; - } - - // Apply SourcePre autocommands, they may get the file. - apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); - - cookie.fp = fopen_noinh_readbin(fname_exp); - if (cookie.fp == NULL && check_other) { - // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, - // and ".exrc" by "_exrc" or vice versa. - p = path_tail(fname_exp); - if ((*p == '.' || *p == '_') - && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) { - *p = (*p == '_') ? '.' : '_'; - cookie.fp = fopen_noinh_readbin(fname_exp); - } - } - - if (cookie.fp == NULL) { - if (p_verbose > 1) { - verbose_enter(); - if (sourcing_name == NULL) { - smsg(_("could not source \"%s\""), fname); - } else { - smsg(_("line %" PRId64 ": could not source \"%s\""), - (int64_t)sourcing_lnum, fname); - } - verbose_leave(); - } - goto theend; - } - - // The file exists. - // - In verbose mode, give a message. - // - For a vimrc file, may want to call vimrc_found(). - if (p_verbose > 1) { - verbose_enter(); - if (sourcing_name == NULL) { - smsg(_("sourcing \"%s\""), fname); - } else { - smsg(_("line %" PRId64 ": sourcing \"%s\""), - (int64_t)sourcing_lnum, fname); - } - verbose_leave(); - } - if (is_vimrc == DOSO_VIMRC) { - vimrc_found(fname_exp, "MYVIMRC"); - } - -#ifdef USE_CRNL - // If no automatic file format: Set default to CR-NL. - if (*p_ffs == NUL) { - cookie.fileformat = EOL_DOS; - } else { - cookie.fileformat = EOL_UNKNOWN; - } - cookie.error = false; -#endif - - cookie.nextline = NULL; - cookie.sourcing_lnum = 0; - cookie.finished = false; - - // Check if this script has a breakpoint. - cookie.breakpoint = dbg_find_breakpoint(true, (char_u *)fname_exp, (linenr_T)0); - cookie.fname = fname_exp; - cookie.dbg_tick = debug_tick; - - cookie.level = ex_nesting_level; - - // Keep the sourcing name/lnum, for recursive calls. - save_sourcing_name = sourcing_name; - sourcing_name = fname_exp; - save_sourcing_lnum = sourcing_lnum; - sourcing_lnum = 0; - - // start measuring script load time if --startuptime was passed and - // time_fd was successfully opened afterwards. - proftime_T rel_time; - proftime_T start_time; - FILE * const l_time_fd = time_fd; - if (l_time_fd != NULL) { - time_push(&rel_time, &start_time); - } - - const int l_do_profiling = do_profiling; - if (l_do_profiling == PROF_YES) { - prof_child_enter(&wait_start); // entering a child now - } - - // Don't use local function variables, if called from a function. - // Also starts profiling timer for nested script. - funccal_entry_T funccalp_entry; - save_funccal(&funccalp_entry); - - const sctx_T save_current_sctx = current_sctx; - si = get_current_script_id((char_u *)fname_exp, ¤t_sctx); - - if (l_do_profiling == PROF_YES) { - bool forceit = false; - - // Check if we do profiling for this script. - if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) { - profile_init(si); - si->sn_pr_force = forceit; - } - if (si->sn_prof_on) { - si->sn_pr_count++; - si->sn_pr_start = profile_start(); - si->sn_pr_children = profile_zero(); - } - } - - cookie.conv.vc_type = CONV_NONE; // no conversion - - // Read the first line so we can check for a UTF-8 BOM. - firstline = (uint8_t *)getsourceline(0, (void *)&cookie, 0, true); - if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef - && firstline[1] == 0xbb && firstline[2] == 0xbf) { - // Found BOM; setup conversion, skip over BOM and recode the line. - convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc); - p = (char *)string_convert(&cookie.conv, (char_u *)firstline + 3, NULL); - if (p == NULL) { - p = xstrdup((char *)firstline + 3); - } - xfree(firstline); - firstline = (uint8_t *)p; - } - - if (path_with_extension((const char *)fname_exp, "lua")) { - const sctx_T current_sctx_backup = current_sctx; - const linenr_T sourcing_lnum_backup = sourcing_lnum; - current_sctx.sc_sid = SID_LUA; - current_sctx.sc_lnum = 0; - sourcing_lnum = 0; - // Source the file as lua - nlua_exec_file((const char *)fname_exp); - current_sctx = current_sctx_backup; - sourcing_lnum = sourcing_lnum_backup; - } else { - // Call do_cmdline, which will call getsourceline() to get the lines. - do_cmdline((char *)firstline, getsourceline, (void *)&cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); - } - retval = OK; - - if (l_do_profiling == PROF_YES) { - // Get "si" again, "script_items" may have been reallocated. - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on) { - si->sn_pr_start = profile_end(si->sn_pr_start); - si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start); - si->sn_pr_total = profile_add(si->sn_pr_total, si->sn_pr_start); - si->sn_pr_self = profile_self(si->sn_pr_self, si->sn_pr_start, - si->sn_pr_children); - } - } - - if (got_int) { - emsg(_(e_interr)); - } - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - if (p_verbose > 1) { - verbose_enter(); - smsg(_("finished sourcing %s"), fname); - if (sourcing_name != NULL) { - smsg(_("continuing in %s"), sourcing_name); - } - verbose_leave(); - } - - if (l_time_fd != NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); - time_msg((char *)IObuff, &start_time); - time_pop(rel_time); - } - - if (!got_int) { - trigger_source_post = true; - } - - // After a "finish" in debug mode, need to break at first command of next - // sourced file. - if (save_debug_break_level > ex_nesting_level - && debug_break_level == ex_nesting_level) { - debug_break_level++; - } - - current_sctx = save_current_sctx; - restore_funccal(); - if (l_do_profiling == PROF_YES) { - prof_child_exit(&wait_start); // leaving a child now - } - fclose(cookie.fp); - xfree(cookie.nextline); - xfree(firstline); - convert_setup(&cookie.conv, NULL, NULL); - - if (trigger_source_post) { - apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); - } - -theend: - xfree(fname_exp); - return retval; -} - -/// Check if fname was sourced before to finds its SID. -/// If it's new, generate a new SID. -/// -/// @param[in] fname file path of script -/// @param[out] ret_sctx sctx of this script -scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx) -{ - static int last_current_SID_seq = 0; - - sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, - .sc_lnum = 0, - .sc_sid = 0 }; - scriptitem_T *si = NULL; - - assert(script_items.ga_len >= 0); - for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) { - // We used to check inode here, but that doesn't work: - // - If a script is edited and written, it may get a different - // inode number, even though to the user it is the same script. - // - If a script is deleted and another script is written, with a - // different name, the inode may be re-used. - si = &SCRIPT_ITEM(script_sctx.sc_sid); - if (si->sn_name != NULL && FNAMECMP(si->sn_name, fname) == 0) { - // Found it! - break; - } - } - if (script_sctx.sc_sid == 0) { - si = new_script_item((char *)vim_strsave(fname), &script_sctx.sc_sid); - } - if (ret_sctx != NULL) { - *ret_sctx = script_sctx; - } - - return si; -} - -/// ":scriptnames" -void ex_scriptnames(exarg_T *eap) -{ - if (eap->addr_count > 0) { - // :script {scriptId}: edit the script - if (eap->line2 < 1 || eap->line2 > script_items.ga_len) { - emsg(_(e_invarg)); - } else { - eap->arg = (char *)SCRIPT_ITEM(eap->line2).sn_name; - do_exedit(eap, NULL); - } - return; - } - - for (int i = 1; i <= script_items.ga_len && !got_int; i++) { - if (SCRIPT_ITEM(i).sn_name != NULL) { - home_replace(NULL, (char *)SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true); - vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); - if (!message_filtered(IObuff)) { - msg_putchar('\n'); - msg_outtrans((char *)IObuff); - line_breakcheck(); - } - } - } -} - -#if defined(BACKSLASH_IN_FILENAME) -/// Fix slashes in the list of script names for 'shellslash'. -void scriptnames_slash_adjust(void) -{ - for (int i = 1; i <= script_items.ga_len; i++) { - if (SCRIPT_ITEM(i).sn_name != NULL) { - slash_adjust(SCRIPT_ITEM(i).sn_name); - } - } -} - -#endif - -/// Get a pointer to a script name. Used for ":verbose set". -/// Message appended to "Last set from " -char_u *get_scriptname(LastSet last_set, bool *should_free) -{ - *should_free = false; - - switch (last_set.script_ctx.sc_sid) { - case SID_MODELINE: - return (char_u *)_("modeline"); - case SID_CMDARG: - return (char_u *)_("--cmd argument"); - case SID_CARG: - return (char_u *)_("-c argument"); - case SID_ENV: - return (char_u *)_("environment variable"); - case SID_ERROR: - return (char_u *)_("error handler"); - case SID_WINLAYOUT: - return (char_u *)_("changed window size"); - case SID_LUA: - return (char_u *)_("Lua"); - case SID_API_CLIENT: - snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); - return IObuff; - case SID_STR: - return (char_u *)_("anonymous :source"); - default: { - char *const sname = (char *)SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name; - if (sname == NULL) { - snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"), - last_set.script_ctx.sc_sid); - return IObuff; - } - - *should_free = true; - return (char_u *)home_replace_save(NULL, sname); - } - } -} - -#if defined(EXITFREE) -void free_scriptnames(void) -{ - profile_reset(); - -# define FREE_SCRIPTNAME(item) xfree((item)->sn_name) - GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME); -} -#endif - -linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie) - FUNC_ATTR_PURE -{ - return fgetline == getsourceline - ? ((struct source_cookie *)cookie)->sourcing_lnum - : sourcing_lnum; -} - -/// Get one full line from a sourced file. -/// Called by do_cmdline() when it's called from do_source(). -/// -/// @return pointer to the line in allocated memory, or NULL for end-of-file or -/// some error. -char *getsourceline(int c, void *cookie, int indent, bool do_concat) -{ - struct source_cookie *sp = (struct source_cookie *)cookie; - char *line; - char *p; - - // If breakpoints have been added/deleted need to check for it. - if (sp->dbg_tick < debug_tick) { - sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, sourcing_lnum); - sp->dbg_tick = debug_tick; - } - if (do_profiling == PROF_YES) { - script_line_end(); - } - // Set the current sourcing line number. - sourcing_lnum = sp->sourcing_lnum + 1; - // Get current line. If there is a read-ahead line, use it, otherwise get - // one now. - if (sp->finished) { - line = NULL; - } else if (sp->nextline == NULL) { - line = get_one_sourceline(sp); - } else { - line = sp->nextline; - sp->nextline = NULL; - sp->sourcing_lnum++; - } - if (line != NULL && do_profiling == PROF_YES) { - script_line_start(); - } - - // Only concatenate lines starting with a \ when 'cpoptions' doesn't - // contain the 'C' flag. - if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { - // compensate for the one line read-ahead - sp->sourcing_lnum--; - - // Get the next line and concatenate it when it starts with a - // backslash. We always need to read the next line, keep it in - // sp->nextline. - // Also check for a comment in between continuation lines: "\ . - sp->nextline = get_one_sourceline(sp); - if (sp->nextline != NULL - && (*(p = skipwhite(sp->nextline)) == '\\' - || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) { - garray_T ga; - - ga_init(&ga, (int)sizeof(char_u), 400); - ga_concat(&ga, line); - while (sp->nextline != NULL - && concat_continued_line(&ga, 400, (char_u *)sp->nextline, - STRLEN(sp->nextline))) { - xfree(sp->nextline); - sp->nextline = get_one_sourceline(sp); - } - ga_append(&ga, NUL); - xfree(line); - line = ga.ga_data; - } - } - - if (line != NULL && sp->conv.vc_type != CONV_NONE) { - char *s; - - // Convert the encoding of the script line. - s = (char *)string_convert(&sp->conv, (char_u *)line, NULL); - if (s != NULL) { - xfree(line); - line = s; - } - } - - // Did we encounter a breakpoint? - if (sp->breakpoint != 0 && sp->breakpoint <= sourcing_lnum) { - dbg_breakpoint((char_u *)sp->fname, sourcing_lnum); - // Find next breakpoint. - sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, sourcing_lnum); - sp->dbg_tick = debug_tick; - } - - return line; -} - -static char *get_one_sourceline(struct source_cookie *sp) -{ - garray_T ga; - int len; - int c; - char *buf; -#ifdef USE_CRNL - int has_cr; // CR-LF found -#endif - bool have_read = false; - - // use a growarray to store the sourced line - ga_init(&ga, 1, 250); - - // Loop until there is a finished line (or end-of-file). - sp->sourcing_lnum++; - for (;;) { - // make room to read at least 120 (more) characters - ga_grow(&ga, 120); - buf = ga.ga_data; - -retry: - errno = 0; - if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, - sp->fp) == NULL) { - if (errno == EINTR) { - goto retry; - } - - break; - } - len = ga.ga_len + (int)STRLEN(buf + ga.ga_len); -#ifdef USE_CRNL - // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the - // CTRL-Z by its own, or after a NL. - if ((len == 1 || (len >= 2 && buf[len - 2] == '\n')) - && sp->fileformat == EOL_DOS - && buf[len - 1] == Ctrl_Z) { - buf[len - 1] = NUL; - break; - } -#endif - - have_read = true; - ga.ga_len = len; - - // If the line was longer than the buffer, read more. - if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') { - continue; - } - - if (len >= 1 && buf[len - 1] == '\n') { // remove trailing NL -#ifdef USE_CRNL - has_cr = (len >= 2 && buf[len - 2] == '\r'); - if (sp->fileformat == EOL_UNKNOWN) { - if (has_cr) { - sp->fileformat = EOL_DOS; - } else { - sp->fileformat = EOL_UNIX; - } - } - - if (sp->fileformat == EOL_DOS) { - if (has_cr) { // replace trailing CR - buf[len - 2] = '\n'; - len--; - ga.ga_len--; - } else { // lines like ":map xx yy^M" will have failed - if (!sp->error) { - msg_source(HL_ATTR(HLF_W)); - emsg(_("W15: Warning: Wrong line separator, ^M may be missing")); - } - sp->error = true; - sp->fileformat = EOL_UNIX; - } - } -#endif - // The '\n' is escaped if there is an odd number of ^V's just - // before it, first set "c" just before the 'V's and then check - // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo - for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {} - if ((len & 1) != (c & 1)) { // escaped NL, read more - sp->sourcing_lnum++; - continue; - } - - buf[len - 1] = NUL; // remove the NL - } - - // Check for ^C here now and then, so recursive :so can be broken. - line_breakcheck(); - break; - } - - if (have_read) { - return ga.ga_data; - } - - xfree(ga.ga_data); - return NULL; -} - -/// Called when starting to read a script line. -/// "sourcing_lnum" must be correct! -/// When skipping lines it may not actually be executed, but we won't find out -/// until later and we need to store the time now. -void script_line_start(void) -{ - scriptitem_T *si; - sn_prl_T *pp; - - if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { - return; - } - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && sourcing_lnum >= 1) { - // Grow the array before starting the timer, so that the time spent - // here isn't counted. - (void)ga_grow(&si->sn_prl_ga, sourcing_lnum - si->sn_prl_ga.ga_len); - si->sn_prl_idx = sourcing_lnum - 1; - while (si->sn_prl_ga.ga_len <= si->sn_prl_idx - && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { - // Zero counters for a line that was not used before. - pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len); - pp->snp_count = 0; - pp->sn_prl_total = profile_zero(); - pp->sn_prl_self = profile_zero(); - si->sn_prl_ga.ga_len++; - } - si->sn_prl_execed = false; - si->sn_prl_start = profile_start(); - si->sn_prl_children = profile_zero(); - si->sn_prl_wait = profile_get_wait(); - } -} - -/// Called when actually executing a function line. -void script_line_exec(void) -{ - scriptitem_T *si; - - if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { - return; - } - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && si->sn_prl_idx >= 0) { - si->sn_prl_execed = true; - } -} - -/// Called when done with a function line. -void script_line_end(void) -{ - scriptitem_T *si; - sn_prl_T *pp; - - if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { - return; - } - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && si->sn_prl_idx >= 0 - && si->sn_prl_idx < si->sn_prl_ga.ga_len) { - if (si->sn_prl_execed) { - pp = &PRL_ITEM(si, si->sn_prl_idx); - pp->snp_count++; - si->sn_prl_start = profile_end(si->sn_prl_start); - si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start); - pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start); - pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start, - si->sn_prl_children); - } - si->sn_prl_idx = -1; - } -} - -/// ":scriptencoding": Set encoding conversion for a sourced script. -/// Without the multi-byte feature it's simply ignored. -void ex_scriptencoding(exarg_T *eap) -{ - struct source_cookie *sp; - char *name; - - if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { - emsg(_("E167: :scriptencoding used outside of a sourced file")); - return; - } - - if (*eap->arg != NUL) { - name = (char *)enc_canonize((char_u *)eap->arg); - } else { - name = eap->arg; - } - - // Setup for conversion from the specified encoding to 'encoding'. - sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie); - convert_setup(&sp->conv, (char_u *)name, p_enc); - - if (name != eap->arg) { - xfree(name); - } -} - -/// ":finish": Mark a sourced file as finished. -void ex_finish(exarg_T *eap) -{ - if (getline_equal(eap->getline, eap->cookie, getsourceline)) { - do_finish(eap, false); - } else { - emsg(_("E168: :finish used outside of a sourced file")); - } -} - -/// Mark a sourced file as finished. Possibly makes the ":finish" pending. -/// Also called for a pending finish at the ":endtry" or after returning from -/// an extra do_cmdline(). "reanimate" is used in the latter case. -void do_finish(exarg_T *eap, int reanimate) -{ - int idx; - - if (reanimate) { - ((struct source_cookie *)getline_cookie(eap->getline, - eap->cookie))->finished = false; - } - - // Cleanup (and deactivate) conditionals, but stop when a try conditional - // not in its finally clause (which then is to be executed next) is found. - // In this case, make the ":finish" pending for execution at the ":endtry". - // Otherwise, finish normally. - idx = cleanup_conditionals(eap->cstack, 0, true); - if (idx >= 0) { - eap->cstack->cs_pending[idx] = CSTP_FINISH; - report_make_pending(CSTP_FINISH, NULL); - } else { - ((struct source_cookie *)getline_cookie(eap->getline, - eap->cookie))->finished = true; - } -} - -/// @return true when a sourced file had the ":finish" command: Don't give error -/// message for missing ":endif". -/// false when not sourcing a file. -bool source_finished(LineGetter fgetline, void *cookie) -{ - return getline_equal(fgetline, cookie, getsourceline) - && ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished; -} - /// ":checktime [buffer]" void ex_checktime(exarg_T *eap) { @@ -3007,7 +1152,7 @@ void ex_drop(exarg_T *eap) // and mostly only one file is dropped. // This also ignores wildcards, since it is very unlikely the user is // editing a file name with a wildcard character. - do_arglist(eap->arg, AL_SET, 0, false); + set_arglist(eap->arg); // Expanding wildcards may result in an empty argument list. E.g. when // editing "foo.pyc" and ".pyc" is in 'wildignore'. Assume that we diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index c463bfa5ab..e454a30028 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -3,8 +3,7 @@ #include <stdbool.h> -#include "nvim/ex_docmd.h" -#include "nvim/runtime.h" +#include "nvim/ex_cmds_defs.h" // // flags for check_changed() @@ -15,27 +14,6 @@ #define CCGD_ALLBUF 8 // may write all buffers #define CCGD_EXCMD 16 // may suggest using ! -typedef struct scriptitem_S { - char_u *sn_name; - bool sn_prof_on; ///< true when script is/was profiled - bool sn_pr_force; ///< forceit: profile functions in this script - proftime_T sn_pr_child; ///< time set when going into first child - int sn_pr_nest; ///< nesting for sn_pr_child - // profiling the script as a whole - int sn_pr_count; ///< nr of times sourced - proftime_T sn_pr_total; ///< time spent in script + children - proftime_T sn_pr_self; ///< time spent in script itself - proftime_T sn_pr_start; ///< time at script start - proftime_T sn_pr_children; ///< time in children after script start - // profiling the script per line - garray_T sn_prl_ga; ///< things stored for every line - proftime_T sn_prl_start; ///< start time for current line - proftime_T sn_prl_children; ///< time spent in children for this line - proftime_T sn_prl_wait; ///< wait start time for current line - linenr_T sn_prl_idx; ///< index of line being timed; -1 if none - int sn_prl_execed; ///< line being timed was executed -} scriptitem_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.h.generated.h" #endif diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4ac9847e53..afa8a276c8 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,14 +9,17 @@ #include <stdlib.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/debugger.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" @@ -38,6 +41,7 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/hardcopy.h" +#include "nvim/help.h" #include "nvim/highlight_group.h" #include "nvim/if_cscope.h" #include "nvim/input.h" @@ -62,10 +66,10 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/shada.h" #include "nvim/sign.h" @@ -100,16 +104,14 @@ typedef struct { #define FREE_WCMD(wcmd) xfree((wcmd)->line) -/* - * Structure used to store info for line position in a while or for loop. - * This is required, because do_one_cmd() may invoke ex_function(), which - * reads more lines that may come from the while/for loop. - */ +/// Structure used to store info for line position in a while or for loop. +/// This is required, because do_one_cmd() may invoke ex_function(), which +/// reads more lines that may come from the while/for loop. struct loop_cookie { garray_T *lines_gap; // growarray with line info int current_line; // last read line from growarray - int repeating; // TRUE when looping a second time - // When "repeating" is FALSE use "getline" and "cookie" to get lines + int repeating; // true when looping a second time + // When "repeating" is false use "getline" and "cookie" to get lines char *(*getline)(int, void *, int, bool); void *cookie; }; @@ -136,9 +138,7 @@ struct dbg_stuff { # define ex_language ex_ni #endif -/* - * Declare cmdnames[]. - */ +// Declare cmdnames[]. #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds_defs.generated.h" #endif @@ -148,7 +148,7 @@ static char dollar_command[2] = { '$', 0 }; static void save_dbg_stuff(struct dbg_stuff *dsp) { dsp->trylevel = trylevel; trylevel = 0; - dsp->force_abort = force_abort; force_abort = FALSE; + dsp->force_abort = force_abort; force_abort = false; dsp->caught_stack = caught_stack; caught_stack = NULL; dsp->vv_exception = v_exception(NULL); dsp->vv_throwpoint = v_throwpoint(NULL); @@ -179,11 +179,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) /// Repeatedly get commands for Ex mode, until the ":vi" command is given. void do_exmode(void) { - int save_msg_scroll; - int prev_msg_row; - linenr_T prev_line; - varnumber_T changedtick; - exmode_active = true; State = MODE_NORMAL; may_trigger_modechanged(); @@ -194,7 +189,7 @@ void do_exmode(void) return; } - save_msg_scroll = msg_scroll; + int save_msg_scroll = msg_scroll; RedrawingDisabled++; // don't redisplay the window no_wait_return++; // don't wait for return @@ -209,9 +204,9 @@ void do_exmode(void) need_wait_return = false; ex_pressedreturn = false; ex_no_reprint = false; - changedtick = buf_get_changedtick(curbuf); - prev_msg_row = msg_row; - prev_line = curwin->w_cursor.lnum; + varnumber_T changedtick = buf_get_changedtick(curbuf); + int prev_msg_row = msg_row; + linenr_T prev_line = curwin->w_cursor.lnum; cmdline_row = msg_row; do_cmdline(NULL, getexline, NULL, 0); lines_left = Rows - 1; @@ -232,7 +227,7 @@ void do_exmode(void) } } msg_col = 0; - print_line_no_prefix(curwin->w_cursor.lnum, FALSE, FALSE); + print_line_no_prefix(curwin->w_cursor.lnum, false, false); msg_clr_eos(); } } else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF @@ -301,26 +296,26 @@ int do_cmdline_cmd(const char *cmd) /// @return FAIL if cmdline could not be executed, OK otherwise int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) { - char *next_cmdline; // next cmd to execute - char *cmdline_copy = NULL; // copy of cmd line + char *next_cmdline; // next cmd to execute + char *cmdline_copy = NULL; // copy of cmd line bool used_getline = false; // used "fgetline" to obtain command static int recursive = 0; // recursive depth bool msg_didout_before_start = false; int count = 0; // line number count - int did_inc = FALSE; // incremented RedrawingDisabled + bool did_inc = false; // incremented RedrawingDisabled int retval = OK; cstack_T cstack = { // conditional stack .cs_idx = -1, }; garray_T lines_ga; // keep lines for ":while"/":for" int current_line = 0; // active line in lines_ga - char *fname = NULL; // function or script name + char *fname = NULL; // function or script name linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie - int *dbg_tick = NULL; // ptr to dbg_tick field in cookie + int *dbg_tick = NULL; // ptr to dbg_tick field in cookie struct dbg_stuff debug_saved; // saved things for debug mode int initial_trylevel; - struct msglist **saved_msg_list = NULL; - struct msglist *private_msg_list; + msglist_T **saved_msg_list = NULL; + msglist_T *private_msg_list; // "fgetline" and "cookie" passed to do_one_cmd() char *(*cmd_getline)(int, void *, int, bool); @@ -361,7 +356,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Inside a function use a higher nesting level. getline_is_func = getline_equal(fgetline, cookie, get_func_line); if (getline_is_func && ex_nesting_level == func_level(real_cookie)) { - ++ex_nesting_level; + ex_nesting_level++; } // Get the function or script name and the address where the next breakpoint @@ -371,14 +366,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) breakpoint = func_breakpoint(real_cookie); dbg_tick = func_dbg_tick(real_cookie); } else if (getline_equal(fgetline, cookie, getsourceline)) { - fname = sourcing_name; + fname = SOURCING_NAME; breakpoint = source_breakpoint(real_cookie); dbg_tick = source_dbg_tick(real_cookie); } - /* - * Initialize "force_abort" and "suppress_errthrow" at the top level. - */ + // Initialize "force_abort" and "suppress_errthrow" at the top level. if (!recursive) { force_abort = false; suppress_errthrow = false; @@ -390,13 +383,13 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (flags & DOCMD_EXCRESET) { save_dbg_stuff(&debug_saved); } else { - memset(&debug_saved, 0, sizeof(debug_saved)); + CLEAR_FIELD(debug_saved); } initial_trylevel = trylevel; current_exception = NULL; - // "did_emsg" will be set to TRUE when emsg() is used, in which case we + // "did_emsg" will be set to true when emsg() is used, in which case we // cancel the whole command line, and any if/endif or loop. // If force_abort is set, we cancel everything. did_emsg = false; @@ -408,12 +401,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) KeyTyped = false; } - /* - * Continue executing command lines: - * - when inside an ":if", ":while" or ":for" - * - for multiple commands on one line, separated with '|' - * - when repeating until there are no more lines (for ":source") - */ + // Continue executing command lines: + // - when inside an ":if", ":while" or ":for" + // - for multiple commands on one line, separated with '|' + // - when repeating until there are no more lines (for ":source") next_cmdline = cmdline; do { getline_is_func = getline_equal(fgetline, cookie, get_func_line); @@ -427,11 +418,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) did_emsg = false; } - /* - * 1. If repeating a line in a loop, get a line from lines_ga. - * 2. If no line given: Get an allocated line with fgetline(). - * 3. If a line is given: Make a copy, so we can mess with it. - */ + // 1. If repeating a line in a loop, get a line from lines_ga. + // 2. If no line given: Get an allocated line with fgetline(). + // 3. If a line is given: Make a copy, so we can mess with it. // 1. If repeating, get a previous line from lines_ga. if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) { @@ -464,20 +453,19 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (breakpoint != NULL && dbg_tick != NULL && *dbg_tick != debug_tick) { *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, sourcing_lnum); + (char_u *)fname, SOURCING_LNUM); *dbg_tick = debug_tick; } next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line; - sourcing_lnum = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; + SOURCING_LNUM = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; // Did we encounter a breakpoint? - if (breakpoint != NULL && *breakpoint != 0 - && *breakpoint <= sourcing_lnum) { - dbg_breakpoint((char_u *)fname, sourcing_lnum); + if (breakpoint != NULL && *breakpoint != 0 && *breakpoint <= SOURCING_LNUM) { + dbg_breakpoint((char_u *)fname, SOURCING_LNUM); // Find next breakpoint. *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, sourcing_lnum); + (char_u *)fname, SOURCING_LNUM); *dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -509,10 +497,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // 2. If no line given, get an allocated line with fgetline(). if (next_cmdline == NULL) { - /* - * Need to set msg_didout for the first line after an ":if", - * otherwise the ":if" will be overwritten. - */ + // Need to set msg_didout for the first line after an ":if", + // otherwise the ":if" will be overwritten. if (count == 1 && getline_equal(fgetline, cookie, getexline)) { msg_didout = true; } @@ -532,9 +518,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } used_getline = true; - /* - * Keep the first typed line. Clear it when more lines are typed. - */ + // Keep the first typed line. Clear it when more lines are typed. if (flags & DOCMD_KEEPLINE) { xfree(repeat_cmdline); if (count == 0) { @@ -549,13 +533,11 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } cmdline_copy = next_cmdline; - /* - * Save the current line when inside a ":while" or ":for", and when - * the command looks like a ":while" or ":for", because we may need it - * later. When there is a '|' and another command, it is stored - * separately, because we need to be able to jump back to it from an - * :endwhile/:endfor. - */ + // Save the current line when inside a ":while" or ":for", and when + // the command looks like a ":while" or ":for", because we may need it + // later. When there is a '|' and another command, it is stored + // separately, because we need to be able to jump back to it from an + // :endwhile/:endfor. if (current_line == lines_ga.ga_len && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) { store_loop_line(&lines_ga, next_cmdline); @@ -563,32 +545,28 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) did_endif = false; if (count++ == 0) { - /* - * All output from the commands is put below each other, without - * waiting for a return. Don't do this when executing commands - * from a script or when being called recursive (e.g. for ":e - * +command file"). - */ + // All output from the commands is put below each other, without + // waiting for a return. Don't do this when executing commands + // from a script or when being called recursive (e.g. for ":e + // +command file"). if (!(flags & DOCMD_NOWAIT) && !recursive) { msg_didout_before_start = msg_didout; msg_didany = false; // no output yet msg_start(); - msg_scroll = TRUE; // put messages below each other - ++no_wait_return; // don't wait for return until finished - ++RedrawingDisabled; - did_inc = TRUE; + msg_scroll = true; // put messages below each other + no_wait_return++; // don't wait for return until finished + RedrawingDisabled++; + did_inc = true; } } - if ((p_verbose >= 15 && sourcing_name != NULL) || p_verbose >= 16) { - msg_verbose_cmd(sourcing_lnum, cmdline_copy); + if ((p_verbose >= 15 && SOURCING_NAME != NULL) || p_verbose >= 16) { + msg_verbose_cmd(SOURCING_LNUM, cmdline_copy); } - /* - * 2. Execute one '|' separated command. - * do_one_cmd() will return NULL if there is no trailing '|'. - * "cmdline_copy" can change, e.g. for '%' and '#' expansion. - */ + // 2. Execute one '|' separated command. + // do_one_cmd() will return NULL if there is no trailing '|'. + // "cmdline_copy" can change, e.g. for '%' and '#' expansion. recursive++; next_cmdline = do_one_cmd(&cmdline_copy, flags, &cstack, cmd_getline, cmd_cookie); recursive--; @@ -601,10 +579,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (next_cmdline == NULL) { XFREE_CLEAR(cmdline_copy); - // + // If the command was typed, remember it for the ':' register. // Do this AFTER executing the command to make :@: work. - // if (getline_equal(fgetline, cookie, getexline) && new_last_cmdline != NULL) { xfree(last_cmdline); @@ -622,18 +599,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (did_emsg && !force_abort && getline_equal(fgetline, cookie, get_func_line) && !func_has_abort(real_cookie)) { - did_emsg = FALSE; + did_emsg = false; } if (cstack.cs_looplevel > 0) { - ++current_line; - - /* - * An ":endwhile", ":endfor" and ":continue" is handled here. - * If we were executing commands, jump back to the ":while" or - * ":for". - * If we were not executing commands, decrement cs_looplevel. - */ + current_line++; + + // An ":endwhile", ":endfor" and ":continue" is handled here. + // If we were executing commands, jump back to the ":while" or + // ":for". + // If we were not executing commands, decrement cs_looplevel. if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) { cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP); @@ -667,34 +642,27 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); } } - } - /* - * For a ":while" or ":for" we need to remember the line number. - */ - else if (cstack.cs_lflags & CSL_HAD_LOOP) { + } else if (cstack.cs_lflags & CSL_HAD_LOOP) { + // For a ":while" or ":for" we need to remember the line number. cstack.cs_lflags &= ~CSL_HAD_LOOP; cstack.cs_line[cstack.cs_idx] = current_line - 1; } } - /* - * When not inside any ":while" loop, clear remembered lines. - */ + // When not inside any ":while" loop, clear remembered lines. if (cstack.cs_looplevel == 0) { if (!GA_EMPTY(&lines_ga)) { - sourcing_lnum = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; + SOURCING_LNUM = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); } current_line = 0; } - /* - * A ":finally" makes did_emsg, got_int and current_exception pending for - * being restored at the ":endtry". Reset them here and set the - * ACTIVE and FINALLY flags, so that the finally clause gets executed. - * This includes the case where a missing ":endif", ":endwhile" or - * ":endfor" was detected by the ":finally" itself. - */ + // A ":finally" makes did_emsg, got_int and current_exception pending for + // being restored at the ":endtry". Reset them here and set the + // ACTIVE and FINALLY flags, so that the finally clause gets executed. + // This includes the case where a missing ":endif", ":endwhile" or + // ":endfor" was detected by the ":finally" itself. if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; report_make_pending((cstack.cs_pending[cstack.cs_idx] @@ -720,38 +688,34 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Convert an interrupt to an exception if appropriate. (void)do_intthrow(&cstack); - } - /* - * Continue executing command lines when: - * - no CTRL-C typed, no aborting error, no exception thrown or try - * conditionals need to be checked for executing finally clauses or - * catching an interrupt exception - * - didn't get an error message or lines are not typed - * - there is a command after '|', inside a :if, :while, :for or :try, or - * looping for ":source" command or function call. - */ - while (!((got_int || (did_emsg && force_abort) || current_exception) - && cstack.cs_trylevel == 0) - && !(did_emsg - // Keep going when inside try/catch, so that the error can be - // deal with, except when it is a syntax error, it may cause - // the :endtry to be missed. - && (cstack.cs_trylevel == 0 || did_emsg_syntax) - && used_getline - && getline_equal(fgetline, cookie, getexline)) - && (next_cmdline != NULL - || cstack.cs_idx >= 0 - || (flags & DOCMD_REPEAT))); + + // Continue executing command lines when: + // - no CTRL-C typed, no aborting error, no exception thrown or try + // conditionals need to be checked for executing finally clauses or + // catching an interrupt exception + // - didn't get an error message or lines are not typed + // - there is a command after '|', inside a :if, :while, :for or :try, or + // looping for ":source" command or function call. + } while (!((got_int || (did_emsg && force_abort) || current_exception) + && cstack.cs_trylevel == 0) + && !(did_emsg + // Keep going when inside try/catch, so that the error can be + // deal with, except when it is a syntax error, it may cause + // the :endtry to be missed. + && (cstack.cs_trylevel == 0 || did_emsg_syntax) + && used_getline + && getline_equal(fgetline, cookie, getexline)) + && (next_cmdline != NULL + || cstack.cs_idx >= 0 + || (flags & DOCMD_REPEAT))); xfree(cmdline_copy); did_emsg_syntax = false; GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); if (cstack.cs_idx >= 0) { - /* - * If a sourced file or executed function ran to its end, report the - * unclosed conditional. - */ + // If a sourced file or executed function ran to its end, report the + // unclosed conditional. if (!got_int && !current_exception && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) @@ -768,18 +732,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } } - /* - * Reset "trylevel" in case of a ":finish" or ":return" or a missing - * ":endtry" in a sourced file or executed function. If the try - * conditional is in its finally clause, ignore anything pending. - * If it is in a catch clause, finish the caught exception. - * Also cleanup any "cs_forinfo" structures. - */ + // Reset "trylevel" in case of a ":finish" or ":return" or a missing + // ":endtry" in a sourced file or executed function. If the try + // conditional is in its finally clause, ignore anything pending. + // If it is in a catch clause, finish the caught exception. + // Also cleanup any "cs_forinfo" structures. do { - int idx = cleanup_conditionals(&cstack, 0, TRUE); + int idx = cleanup_conditionals(&cstack, 0, true); if (idx >= 0) { - --idx; // remove try block not in its finally clause + idx--; // remove try block not in its finally clause } rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); @@ -799,17 +761,13 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // commands are executed. if (current_exception) { char *p = NULL; - char *saved_sourcing_name; - linenr_T saved_sourcing_lnum; - struct msglist *messages = NULL; - struct msglist *next; - - /* - * If the uncaught exception is a user exception, report it as an - * error. If it is an error exception, display the saved error - * message now. For an interrupt exception, do nothing; the - * interrupt message is given elsewhere. - */ + msglist_T *messages = NULL; + msglist_T *next; + + // If the uncaught exception is a user exception, report it as an + // error. If it is an error exception, display the saved error + // message now. For an interrupt exception, do nothing; the + // interrupt message is given elsewhere. switch (current_exception->type) { case ET_USER: vim_snprintf((char *)IObuff, IOSIZE, @@ -825,10 +783,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) break; } - saved_sourcing_name = sourcing_name; - saved_sourcing_lnum = sourcing_lnum; - sourcing_name = current_exception->throw_name; - sourcing_lnum = current_exception->throw_lnum; + estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); current_exception->throw_name = NULL; discard_current_exception(); // uses IObuff if 'verbose' @@ -848,9 +803,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) emsg(p); xfree(p); } - xfree(sourcing_name); - sourcing_name = saved_sourcing_name; - sourcing_lnum = saved_sourcing_lnum; + xfree(SOURCING_NAME); + estack_pop(); } else if (got_int || (did_emsg && force_abort)) { // On an interrupt or an aborting error not converted to an exception, // disable the conversion of errors to exceptions. (Interrupts are not @@ -880,25 +834,21 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } else { // When leaving a function, reduce nesting level. if (getline_equal(fgetline, cookie, get_func_line)) { - --ex_nesting_level; + ex_nesting_level--; } - /* - * Go to debug mode when returning from a function in which we are - * single-stepping. - */ + // Go to debug mode when returning from a function in which we are + // single-stepping. if ((getline_equal(fgetline, cookie, getsourceline) || getline_equal(fgetline, cookie, get_func_line)) && ex_nesting_level + 1 <= debug_break_level) { do_debug(getline_equal(fgetline, cookie, getsourceline) - ? (char_u *)_("End of sourced file") - : (char_u *)_("End of function")); + ? (char_u *)_("End of sourced file") + : (char_u *)_("End of function")); } } - /* - * Restore the exception environment (done after returning from the - * debugger). - */ + // Restore the exception environment (done after returning from the + // debugger). if (flags & DOCMD_EXCRESET) { restore_dbg_stuff(&debug_saved); } @@ -914,32 +864,26 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) } } - /* - * If there was too much output to fit on the command line, ask the user to - * hit return before redrawing the screen. With the ":global" command we do - * this only once after the command is finished. - */ + // If there was too much output to fit on the command line, ask the user to + // hit return before redrawing the screen. With the ":global" command we do + // this only once after the command is finished. if (did_inc) { - --RedrawingDisabled; - --no_wait_return; - msg_scroll = FALSE; - - /* - * When just finished an ":if"-":else" which was typed, no need to - * wait for hit-return. Also for an error situation. - */ + RedrawingDisabled--; + no_wait_return--; + msg_scroll = false; + + // When just finished an ":if"-":else" which was typed, no need to + // wait for hit-return. Also for an error situation. if (retval == FAIL || (did_endif && KeyTyped && !did_emsg)) { need_wait_return = false; msg_didany = false; // don't wait when restarting edit } else if (need_wait_return) { - /* - * The msg_start() above clears msg_didout. The wait_return we do - * here should not overwrite the command that may be shown before - * doing that. - */ + // The msg_start() above clears msg_didout. The wait_return we do + // here should not overwrite the command that may be shown before + // doing that. msg_didout |= msg_didout_before_start; - wait_return(FALSE); + wait_return(false); } } @@ -954,13 +898,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) { struct loop_cookie *cp = (struct loop_cookie *)cookie; - wcmd_T *wp; - char *line; if (cp->current_line + 1 >= cp->lines_gap->ga_len) { if (cp->repeating) { return NULL; // trying to read past ":endwhile"/":endfor" } + char *line; // First time inside the ":while"/":for": get line normally. if (cp->getline == NULL) { line = (char *)getcmdline(c, 0L, indent, do_concat); @@ -969,7 +912,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) } if (line != NULL) { store_loop_line(cp->lines_gap, line); - ++cp->current_line; + cp->current_line++; } return line; @@ -977,8 +920,8 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) KeyTyped = false; cp->current_line++; - wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; - sourcing_lnum = wp->lnum; + wcmd_T *wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; + SOURCING_LNUM = wp->lnum; return xstrdup(wp->line); } @@ -987,23 +930,20 @@ static void store_loop_line(garray_T *gap, char *line) { wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap); p->line = xstrdup(line); - p->lnum = sourcing_lnum; + p->lnum = SOURCING_LNUM; } -/// If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals -/// "func". * Otherwise return TRUE when "fgetline" equals "func". +/// If "fgetline" is get_loop_line(), return true if the getline it uses equals +/// "func". * Otherwise return true when "fgetline" equals "func". /// /// @param cookie argument for fgetline() -int getline_equal(LineGetter fgetline, void *cookie, LineGetter func) +bool getline_equal(LineGetter fgetline, void *cookie, LineGetter func) { - LineGetter gp; - struct loop_cookie *cp; - // When "fgetline" is "get_loop_line()" use the "cookie" to find the // function that's originally used to obtain the lines. This may be // nested several levels. - gp = fgetline; - cp = (struct loop_cookie *)cookie; + LineGetter gp = fgetline; + struct loop_cookie *cp = (struct loop_cookie *)cookie; while (gp == get_loop_line) { gp = cp->getline; cp = cp->cookie; @@ -1017,14 +957,11 @@ int getline_equal(LineGetter fgetline, void *cookie, LineGetter func) /// @param cookie argument for fgetline() void *getline_cookie(LineGetter fgetline, void *cookie) { - LineGetter gp; - struct loop_cookie *cp; - // When "fgetline" is "get_loop_line()" use the "cookie" to find the // cookie that's originally used to obtain the lines. This may be nested // several levels. - gp = fgetline; - cp = (struct loop_cookie *)cookie; + LineGetter gp = fgetline; + struct loop_cookie *cp = (struct loop_cookie *)cookie; while (gp == get_loop_line) { gp = cp->getline; cp = cp->cookie; @@ -1038,11 +975,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie) /// @return the buffer number. static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, long offset) { - buf_T *buf; buf_T *nextbuf; long count = offset; - buf = firstbuf; + buf_T *buf = firstbuf; while (buf->b_next != NULL && buf->b_fnum < lnum) { buf = buf->b_next; } @@ -1085,7 +1021,7 @@ static int current_win_nr(const win_T *win) int nr = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - ++nr; + nr++; if (wp == win) { break; } @@ -1098,7 +1034,7 @@ static int current_tab_nr(tabpage_T *tab) int nr = 0; FOR_ALL_TABS(tp) { - ++nr; + nr++; if (tp == tab) { break; } @@ -1385,12 +1321,11 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate) // Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a // count, it's a buffer name. char *p; - long n; if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg) && (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL || ascii_iswhite(*p))) { - n = getdigits_long(&eap->arg, false, -1); + long n = getdigits_long(&eap->arg, false, -1); eap->arg = skipwhite(eap->arg); if (n <= 0 && (eap->argt & EX_ZEROR) == 0) { if (errormsg != NULL) { @@ -1423,21 +1358,20 @@ bool is_cmd_ni(cmdidx_T cmdidx) /// @return Success or failure bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg) { - char *cmd; - char *p; char *after_modifier = NULL; // Initialize cmdinfo - memset(cmdinfo, 0, sizeof(*cmdinfo)); + CLEAR_POINTER(cmdinfo); // Initialize eap - memset(eap, 0, sizeof(*eap)); - eap->line1 = 1; - eap->line2 = 1; - eap->cmd = cmdline; - eap->cmdlinep = &cmdline; - eap->getline = NULL; - eap->cookie = NULL; + *eap = (exarg_T){ + .line1 = 1, + .line2 = 1, + .cmd = cmdline, + .cmdlinep = &cmdline, + .getline = NULL, + .cookie = NULL, + }; const bool save_ex_pressedreturn = ex_pressedreturn; // Parse command modifiers @@ -1449,13 +1383,13 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er after_modifier = eap->cmd; // Save location after command modifiers - cmd = eap->cmd; + char *cmd = eap->cmd; // Skip ranges to find command name since we need the command to know what kind of range it uses eap->cmd = skip_range(eap->cmd, NULL); if (*eap->cmd == '*') { eap->cmd = skipwhite(eap->cmd + 1); } - p = find_ex_command(eap, NULL); + char *p = find_ex_command(eap, NULL); if (p == NULL) { *errormsg = _(e_ambiguous_use_of_user_defined_command); goto err; @@ -1719,7 +1653,7 @@ end: static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie) { - // Count this line for profiling if skip is TRUE. + // Count this line for profiling if skip is true. if (do_profiling == PROF_YES && (!eap->skip || cstack->cs_idx == 0 || (cstack->cs_idx > 0 @@ -1855,7 +1789,7 @@ static bool skip_cmd(const exarg_T *eap) /// Execute one Ex command. /// -/// If 'sourcing' is TRUE, the command will be included in the error message. +/// If 'sourcing' is true, the command will be included in the error message. /// /// 1. skip comment lines and leading space /// 2. handle command modifiers @@ -1877,10 +1811,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter const int save_reg_executing = reg_executing; const bool save_pending_end_reg_executing = pending_end_reg_executing; - exarg_T ea; - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; + exarg_T ea = { + .line1 = 1, + .line2 = 1, + }; ex_nesting_level++; // When the last file has not been edited :q has to be typed twice. @@ -1890,7 +1824,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // avoid that an autocommand, e.g. QuitPre, does this && !getline_equal(fgetline, cookie, getnextac)) { - --quitmore; + quitmore--; } // Reset browse, confirm, etc.. They are restored when returning, for @@ -1941,7 +1875,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // used, throw an interrupt exception and skip the next command. dbg_check_breakpoint(&ea); if (!ea.skip && got_int) { - ea.skip = TRUE; + ea.skip = true; (void)do_intthrow(cstack); } @@ -2015,7 +1949,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter && has_event(EVENT_CMDUNDEFINED)) { p = ea.cmd; while (ASCII_ISALNUM(*p)) { - ++p; + p++; } p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd)); int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); @@ -2194,16 +2128,16 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.arg = skipwhite(ea.arg + 1); ea.append = true; } else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { // :w !filter - ++ea.arg; - ea.usefilter = TRUE; + ea.arg++; + ea.usefilter = true; } } else if (ea.cmdidx == CMD_read) { if (ea.forceit) { - ea.usefilter = TRUE; // :r! filter if ea.forceit - ea.forceit = FALSE; + ea.usefilter = true; // :r! filter if ea.forceit + ea.forceit = false; } else if (*ea.arg == '!') { // :r !filter - ++ea.arg; - ea.usefilter = TRUE; + ea.arg++; + ea.usefilter = true; } } else if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { ea.amount = 1; @@ -2294,10 +2228,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter do_throw(cstack); } else if (check_cstack) { if (source_finished(fgetline, cookie)) { - do_finish(&ea, TRUE); + do_finish(&ea, true); } else if (getline_equal(fgetline, cookie, get_func_line) && current_func_returned()) { - do_return(&ea, TRUE, FALSE, NULL); + do_return(&ea, true, false, NULL); } } need_rethrow = check_cstack = false; @@ -2332,7 +2266,7 @@ doend: ea.nextcmd = NULL; } - --ex_nesting_level; + ex_nesting_level--; return ea.nextcmd; } @@ -2368,8 +2302,6 @@ char *ex_errmsg(const char *const msg, const char *const arg) /// @return FAIL when the command is not to be executed. int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool skip_only) { - char *p; - CLEAR_POINTER(cmod); // Repeat until no more command modifiers are found. @@ -2401,7 +2333,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool return FAIL; } - p = skip_range(eap->cmd, NULL); + char *p = skip_range(eap->cmd, NULL); switch (*p) { // When adding an entry, also modify cmd_exists(). case 'a': @@ -2840,12 +2772,12 @@ theend: } /// Check for an Ex command with optional tail. -/// If there is a match advance "pp" to the argument and return TRUE. +/// If there is a match advance "pp" to the argument and return true. /// /// @param pp start of command /// @param cmd name of command /// @param len required length -int checkforcmd(char **pp, char *cmd, int len) +bool checkforcmd(char **pp, char *cmd, int len) { int i; @@ -2858,7 +2790,7 @@ int checkforcmd(char **pp, char *cmd, int len) *pp = skipwhite(*pp + i); return true; } - return FALSE; + return false; } /// Append "cmd" to the error message in IObuff. @@ -2886,7 +2818,7 @@ static void append_command(char *cmd) } else if ((char_u *)d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) { break; } else { - mb_copy_char((const char_u **)&s, (char_u **)&d); + mb_copy_char((const char **)&s, &d); } } *d = NUL; @@ -2895,29 +2827,23 @@ static void append_command(char *cmd) /// Find an Ex command by its name, either built-in or user. /// Start of the name can be found at eap->cmd. /// Sets eap->cmdidx and returns a pointer to char after the command name. -/// "full" is set to TRUE if the whole command name matched. +/// "full" is set to true if the whole command name matched. /// /// @return NULL for an ambiguous user command. char *find_ex_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { - int len; - char *p; - int i; - - /* - * Isolate the command and search for it in the command table. - * Exceptions: - * - the 'k' command can directly be followed by any character. - * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - * but :sre[wind] is another command, as are :scr[iptnames], - * :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. - * - the "d" command can directly be followed by 'l' or 'p' flag. - */ - p = eap->cmd; + // Isolate the command and search for it in the command table. + // Exceptions: + // - the 'k' command can directly be followed by any character. + // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' + // but :sre[wind] is another command, as are :scr[iptnames], + // :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. + // - the "d" command can directly be followed by 'l' or 'p' flag. + char *p = eap->cmd; if (*p == 'k') { eap->cmdidx = CMD_k; - ++p; + p++; } else if (p[0] == 's' && ((p[1] == 'c' && (p[2] == NUL @@ -2929,15 +2855,15 @@ char *find_ex_command(exarg_T *eap, int *full) || p[1] == 'I' || (p[1] == 'r' && p[2] != 'e'))) { eap->cmdidx = CMD_substitute; - ++p; + p++; } else { while (ASCII_ISALPHA(*p)) { - ++p; + p++; } // for python 3.x support ":py3", ":python3", ":py3file", etc. if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') { while (ASCII_ISALNUM(*p)) { - ++p; + p++; } } @@ -2945,17 +2871,18 @@ char *find_ex_command(exarg_T *eap, int *full) if (p == eap->cmd && vim_strchr("@!=><&~#", *p) != NULL) { p++; } - len = (int)(p - eap->cmd); + int len = (int)(p - eap->cmd); if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) { // Check for ":dl", ":dell", etc. to ":deletel": that's // :delete with the 'l' flag. Same for 'p'. + int i; for (i = 0; i < len; i++) { if (eap->cmd[i] != ("delete")[i]) { break; } } if (i == len - 1) { - --len; + len--; if (p[-1] == 'l') { eap->flags |= EXFLAG_LIST; } else { @@ -2992,7 +2919,7 @@ char *find_ex_command(exarg_T *eap, int *full) (size_t)len) == 0) { if (full != NULL && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) { - *full = TRUE; + *full = true; } break; } @@ -3003,7 +2930,7 @@ char *find_ex_command(exarg_T *eap, int *full) && *eap->cmd >= 'A' && *eap->cmd <= 'Z') { // User defined commands may contain digits. while (ASCII_ISALNUM(*p)) { - ++p; + p++; } p = find_ucmd(eap, p, full, NULL, NULL); } @@ -3075,9 +3002,6 @@ int modifier_len(char *cmd) /// 3 if there is an ambiguous match. int cmd_exists(const char *const name) { - exarg_T ea; - char *p; - // Check command modifiers. for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { int j; @@ -3093,11 +3017,12 @@ int cmd_exists(const char *const name) // Check built-in commands and user defined commands. // For ":2match" and ":3match" we need to skip the number. + exarg_T ea; ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name); ea.cmdidx = (cmdidx_T)0; ea.flags = 0; int full = false; - p = find_ex_command(&ea, &full); + char *p = find_ex_command(&ea, &full); if (p == NULL) { return 3; } @@ -3113,7 +3038,6 @@ int cmd_exists(const char *const name) /// "fullcommand" function void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - exarg_T ea; char *name = argvars[0].vval.v_string; rettv->v_type = VAR_STRING; @@ -3127,6 +3051,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } name = skip_range(name, NULL); + exarg_T ea; ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; ea.cmdidx = (cmdidx_T)0; ea.flags = 0; @@ -3175,14 +3100,10 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) return NULL; } - /* - * 3. parse a range specifier of the form: addr [,addr] [;addr] .. - */ + // 3. parse a range specifier of the form: addr [,addr] [;addr] .. cmd = (const char *)skip_range(cmd, &xp->xp_context); - /* - * 4. parse command - */ + // 4. parse command xp->xp_pattern = (char *)cmd; if (*cmd == NUL) { return NULL; @@ -3195,13 +3116,11 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) if (*cmd == '|' || *cmd == '\n') { return cmd + 1; // There's another command } - /* - * Isolate the command and search for it in the command table. - * Exceptions: - * - the 'k' command can directly be followed by any character, but - * do accept "keepmarks", "keepalt" and "keepjumps". - * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - */ + // Isolate the command and search for it in the command table. + // Exceptions: + // - the 'k' command can directly be followed by any character, but + // do accept "keepmarks", "keepalt" and "keepjumps". + // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' const char *p; if (*cmd == 'k' && cmd[1] != 'e') { ea.cmdidx = CMD_k; @@ -3281,9 +3200,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) p++; } - /* - * 5. parse arguments - */ + // 5. parse arguments if (!IS_USER_CMDIDX(ea.cmdidx)) { ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; } @@ -3341,10 +3258,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) arg = (const char *)skipwhite(arg); } - /* - * Check for '|' to separate commands and '"' to start comments. - * Don't do this for ":read !cmd" and ":write !cmd". - */ + // Check for '|' to separate commands and '"' to start comments. + // Don't do this for ":read !cmd" and ":write !cmd". if ((ea.argt & EX_TRLBAR) && !usefilter) { p = arg; // ":redir @" is not the start of a comment @@ -3392,18 +3307,15 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) } if (ea.argt & EX_XFILE) { - int c; int in_quote = false; const char *bow = NULL; // Beginning of word. - /* - * Allow spaces within back-quotes to count as part of the argument - * being expanded. - */ + // Allow spaces within back-quotes to count as part of the argument + // being expanded. xp->xp_pattern = skipwhite(arg); p = (const char *)xp->xp_pattern; while (*p != NUL) { - c = utf_ptr2char(p); + int c = utf_ptr2char(p); if (c == '\\' && p[1] != NUL) { p++; } else if (c == '`') { @@ -3412,13 +3324,9 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) bow = p + 1; } in_quote = !in_quote; - } - /* An argument can contain just about everything, except - * characters that end the command and white space. */ - else if (c == '|' - || c == '\n' - || c == '"' - || ascii_iswhite(c)) { + // An argument can contain just about everything, except + // characters that end the command and white space. + } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) { len = 0; // avoid getting stuck when space is in 'isfname' while (*p != NUL) { c = utf_ptr2char(p); @@ -3438,10 +3346,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) MB_PTR_ADV(p); } - /* - * If we are still inside the quotes, and we passed a space, just - * expand from there. - */ + // If we are still inside the quotes, and we passed a space, just + // expand from there. if (bow != NULL && in_quote) { xp->xp_pattern = (char *)bow; } @@ -3450,7 +3356,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) // For a shell command more chars need to be escaped. if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) { #ifndef BACKSLASH_IN_FILENAME - xp->xp_shell = TRUE; + xp->xp_shell = true; #endif // When still after the command name expand executables. if (xp->xp_pattern == skipwhite(arg)) { @@ -3483,14 +3389,12 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) if (*p == NUL && p > (const char *)xp->xp_pattern + 1 && match_user((char_u *)xp->xp_pattern + 1) >= 1) { xp->xp_context = EXPAND_USER; - ++xp->xp_pattern; + xp->xp_pattern++; } } } - /* - * 6. switch on command name - */ + // 6. switch on command name switch (ea.cmdidx) { case CMD_find: case CMD_sfind: @@ -3514,8 +3418,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) xp->xp_pattern = (char *)arg; break; - /* Command modifiers: return the argument. - * Also for commands with an argument that is a command. */ + // Command modifiers: return the argument. + // Also for commands with an argument that is a command. case CMD_aboveleft: case CMD_argdo: case CMD_belowright: @@ -3573,9 +3477,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) } return (const char *)find_nextcmd((char_u *)arg); - /* - * All completion for the +cmdline_compl feature goes here. - */ + // All completion for the +cmdline_compl feature goes here. case CMD_command: return set_context_in_user_cmd(xp, arg); @@ -3785,7 +3687,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) } else if (context == EXPAND_COMMANDS) { return arg; } else if (context == EXPAND_MAPPINGS) { - return (const char *)set_context_in_map_cmd(xp, (char_u *)"map", (char_u *)arg, forceit, + return (const char *)set_context_in_map_cmd(xp, "map", (char_u *)arg, forceit, false, false, CMD_map); } @@ -3823,7 +3725,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_snoremap: case CMD_xmap: case CMD_xnoremap: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, false, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, false, ea.cmdidx); case CMD_unmap: case CMD_nunmap: @@ -3834,7 +3736,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_lunmap: case CMD_sunmap: case CMD_xunmap: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, false, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, true, ea.cmdidx); case CMD_mapclear: case CMD_nmapclear: @@ -3855,12 +3757,12 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_cnoreabbrev: case CMD_iabbrev: case CMD_inoreabbrev: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, true, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, false, ea.cmdidx); case CMD_unabbreviate: case CMD_cunabbrev: case CMD_iunabbrev: - return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, true, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, true, ea.cmdidx); case CMD_menu: case CMD_noremenu: @@ -3992,8 +3894,6 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) /// @return the "cmd" pointer advanced to beyond the range. char *skip_range(const char *cmd, int *ctx) { - unsigned delim; - while (vim_strchr(" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) { if (*cmd == '\\') { if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') { @@ -4006,10 +3906,10 @@ char *skip_range(const char *cmd, int *ctx) *ctx = EXPAND_NOTHING; } } else if (*cmd == '/' || *cmd == '?') { - delim = (unsigned)(*cmd++); + unsigned delim = (unsigned)(*cmd++); while (*cmd != NUL && *cmd != (char)delim) { if (*cmd++ == '\\' && *cmd != NUL) { - ++cmd; + cmd++; } } if (*cmd == NUL && ctx != NULL) { @@ -4017,7 +3917,7 @@ char *skip_range(const char *cmd, int *ctx) } } if (*cmd != NUL) { - ++cmd; + cmd++; } } @@ -4055,17 +3955,15 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int int c; int i; linenr_T n; - char *cmd; pos_T pos; - linenr_T lnum; buf_T *buf; - cmd = skipwhite(*ptr); - lnum = MAXLNUM; + char *cmd = skipwhite(*ptr); + linenr_T lnum = MAXLNUM; do { switch (*cmd) { case '.': // '.' - Cursor position - ++cmd; + cmd++; switch (addr_type) { case ADDR_LINES: case ADDR_OTHER: @@ -4101,7 +3999,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int break; case '$': // '$' - last line - ++cmd; + cmd++; switch (addr_type) { case ADDR_LINES: case ADDR_OTHER: @@ -4162,7 +4060,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } if (skip) { - ++cmd; + cmd++; } else { // Only accept a mark in another file when it is // used by itself: ":'M". @@ -4194,7 +4092,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int if (skip) { // skip "/pat/" cmd = (char *)skip_regexp((char_u *)cmd, c, p_magic, NULL); if (*cmd == c) { - ++cmd; + cmd++; } } else { int flags; @@ -4234,7 +4132,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int break; case '\\': // "\?", "\/" or "\&", repeat search - ++cmd; + cmd++; if (addr_type != ADDR_LINES) { addr_error(addr_type); cmd = NULL; @@ -4265,7 +4163,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } } - ++cmd; + cmd++; break; default: @@ -4560,45 +4458,35 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) /// @return FAIL for failure, OK otherwise. int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) { - int has_wildcards; // need to expand wildcards - char *repl; - size_t srclen; - char *p; - int escaped; - // Skip a regexp pattern for ":vimgrep[add] pat file..." - p = skip_grep_pat(eap); - - /* - * Decide to expand wildcards *before* replacing '%', '#', etc. If - * the file name contains a wildcard it should not cause expanding. - * (it will be expanded anyway if there is a wildcard before replacing). - */ - has_wildcards = path_has_wildcard((char_u *)p); + char *p = skip_grep_pat(eap); + + // Decide to expand wildcards *before* replacing '%', '#', etc. If + // the file name contains a wildcard it should not cause expanding. + // (it will be expanded anyway if there is a wildcard before replacing). + int has_wildcards = path_has_wildcard((char_u *)p); while (*p != NUL) { // Skip over `=expr`, wildcards in it are not expanded. if (p[0] == '`' && p[1] == '=') { p += 2; (void)skip_expr(&p); if (*p == '`') { - ++p; + p++; } continue; } - /* - * Quick check if this cannot be the start of a special string. - * Also removes backslash before '%', '#' and '<'. - */ + // Quick check if this cannot be the start of a special string. + // Also removes backslash before '%', '#' and '<'. if (vim_strchr("%#<", *p) == NULL) { p++; continue; } - /* - * Try to find a match at this position. - */ - repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum), - errormsgp, &escaped); + // Try to find a match at this position. + size_t srclen; + int escaped; + char *repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum), + errormsgp, &escaped); if (*errormsgp != NULL) { // error detected return FAIL; } @@ -4668,20 +4556,16 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) xfree(repl); } - /* - * One file argument: Expand wildcards. - * Don't do this with ":r !command" or ":w !command". - */ + // One file argument: Expand wildcards. + // Don't do this with ":r !command" or ":w !command". if ((eap->argt & EX_NOSPC) && !eap->usefilter) { // Replace environment variables. if (has_wildcards) { - /* - * May expand environment variables. This - * can be done much faster with expand_env() than with - * something else (e.g., calling a shell). - * After expanding environment variables, check again - * if there are still wildcards present. - */ + // May expand environment variables. This + // can be done much faster with expand_env() than with + // something else (e.g., calling a shell). + // After expanding environment variables, check again + // if there are still wildcards present. if (vim_strchr(eap->arg, '$') != NULL || vim_strchr(eap->arg, '~') != NULL) { expand_env_esc((char_u *)eap->arg, NameBuff, MAXPATHL, true, true, NULL); @@ -4695,15 +4579,16 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) } } - /* - * Halve the number of backslashes (this is Vi compatible). - * For Unix, when wildcards are expanded, this is - * done by ExpandOne() below. - */ + // Halve the number of backslashes (this is Vi compatible). + // For Unix, when wildcards are expanded, this is + // done by ExpandOne() below. #ifdef UNIX - if (!has_wildcards) -#endif + if (!has_wildcards) { + backslash_halve((char_u *)eap->arg); + } +#else backslash_halve((char_u *)eap->arg); +#endif if (has_wildcards) { expand_T xpc; @@ -4733,11 +4618,9 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) /// @return a pointer to the character after the replaced string. static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, char **cmdlinep) { - /* - * The new command line is build in new_cmdline[]. - * First allocate it. - * Careful: a "+cmd" argument may have been NUL terminated. - */ + // The new command line is build in new_cmdline[]. + // First allocate it. + // Careful: a "+cmd" argument may have been NUL terminated. size_t len = STRLEN(repl); size_t i = (size_t)(src - *cmdlinep) + STRLEN(src + srclen) + len + 3; if (eap->nextcmd != NULL) { @@ -4746,12 +4629,10 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch char *new_cmdline = xmalloc(i); size_t offset = (size_t)(src - *cmdlinep); - /* - * Copy the stuff before the expanded part. - * Copy the expanded stuff. - * Copy what came after the expanded part. - * Copy the next commands, if there are any. - */ + // Copy the stuff before the expanded part. + // Copy the expanded stuff. + // Copy what came after the expanded part. + // Copy the next commands, if there are any. i = offset; // length of part before match memmove(new_cmdline, *cmdlinep, i); @@ -4847,12 +4728,12 @@ static char *getargcmd(char **argp) char *command = NULL; if (*arg == '+') { // +[command] - ++arg; + arg++; if (ascii_isspace(*arg) || *arg == '\0') { command = (char *)dollar_command; } else { command = arg; - arg = skip_cmd_arg(command, TRUE); + arg = skip_cmd_arg(command, true); if (*arg != NUL) { *arg++ = NUL; // terminate command with NUL } @@ -4866,7 +4747,7 @@ static char *getargcmd(char **argp) /// Find end of "+command" argument. Skip over "\ " and "\\". /// -/// @param rembs TRUE to halve the number of backslashes +/// @param rembs true to halve the number of backslashes static char *skip_cmd_arg(char *p, int rembs) { while (*p && !ascii_isspace(*p)) { @@ -4874,7 +4755,7 @@ static char *skip_cmd_arg(char *p, int rembs) if (rembs) { STRMOVE(p, p + 1); } else { - ++p; + p++; } } MB_PTR_ADV(p); @@ -4905,7 +4786,6 @@ static int getargopt(exarg_T *eap) char *arg = eap->arg + 2; int *pp = NULL; int bad_char_idx; - char *p; // ":edit ++[no]bin[ary] file" if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) { @@ -4964,7 +4844,7 @@ static int getargopt(exarg_T *eap) eap->force_ff = (char_u)eap->cmd[eap->force_ff]; } else if (pp == &eap->force_enc) { // Make 'fileencoding' lower case. - for (p = eap->cmd + eap->force_enc; *p != NUL; p++) { + for (char *p = eap->cmd + eap->force_enc; *p != NUL; p++) { *p = (char)TOLOWER_ASC(*p); } } else { @@ -4989,7 +4869,6 @@ static int get_tabpage_arg(exarg_T *eap) if (eap->arg && *eap->arg != NUL) { char *p = eap->arg; - char *p_save; int relative = 0; // argument +N/-N means: go to N places to the // right/left relative to the current position. @@ -5001,7 +4880,7 @@ static int get_tabpage_arg(exarg_T *eap) p++; } - p_save = p; + char *p_save = p; tab_number = (int)getdigits(&p, false, tab_number); if (relative == 0) { @@ -5223,9 +5102,9 @@ char_u *check_nextcmd(char_u *p) /// - and forceit not used /// - and not repeated twice on a row /// -/// @param message when FALSE check only, no messages +/// @param message when false check only, no messages /// -/// @return FAIL and give error message if 'message' TRUE, return OK otherwise +/// @return FAIL and give error message if 'message' true, return OK otherwise static int check_more(int message, bool forceit) { int n = ARGCOUNT - curwin->w_arg_idx - 1; @@ -5266,10 +5145,9 @@ static void ex_colorscheme(exarg_T *eap) { if (*eap->arg == NUL) { char *expr = xstrdup("g:colors_name"); - char *p = NULL; emsg_off++; - p = eval_to_string(expr, NULL, false); + char *p = eval_to_string(expr, NULL, false); emsg_off--; xfree(expr); @@ -5473,16 +5351,15 @@ static void ex_pclose(exarg_T *eap) /// @param tp NULL or the tab page "win" is in void ex_win_close(int forceit, win_T *win, tabpage_T *tp) { - int need_hide; - buf_T *buf = win->w_buffer; - // Never close the autocommand window. if (win == aucmd_win) { emsg(_(e_autocmd_close)); return; } - need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); + buf_T *buf = win->w_buffer; + + bool need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); if (need_hide && !buf_hide(buf) && !forceit) { if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) { bufref_T bufref; @@ -5510,8 +5387,6 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp) /// ":tabclose N": close tab page N. static void ex_tabclose(exarg_T *eap) { - tabpage_T *tp; - if (cmdwin_type != 0) { cmdwin_result = K_IGNORE; } else if (first_tabpage->tp_next == NULL) { @@ -5519,7 +5394,7 @@ static void ex_tabclose(exarg_T *eap) } else { int tab_number = get_tabpage_arg(eap); if (eap->errmsg == NULL) { - tp = find_tabpage(tab_number); + tabpage_T *tp = find_tabpage(tab_number); if (tp == NULL) { beep_flush(); return; @@ -5591,7 +5466,6 @@ void tabpage_close(int forceit) void tabpage_close_other(tabpage_T *tp, int forceit) { int done = 0; - win_T *wp; int h = tabline_height(); char prev_idx[NUMBUFLEN]; @@ -5599,7 +5473,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) // one. OK, so I'm paranoid... while (++done < 1000) { snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp)); - wp = tp->tp_lastwin; + win_T *wp = tp->tp_lastwin; ex_win_close(forceit, wp, tp); // Autocommands may delete the tab page under our fingers and we may @@ -5619,10 +5493,9 @@ void tabpage_close_other(tabpage_T *tp, int forceit) static void ex_only(exarg_T *eap) { win_T *wp; - linenr_T wnr; if (eap->addr_count > 0) { - wnr = eap->line2; + linenr_T wnr = eap->line2; for (wp = firstwin; --wnr > 0;) { if (wp->w_next == NULL) { break; @@ -5636,17 +5509,7 @@ static void ex_only(exarg_T *eap) if (wp != curwin) { win_goto(wp); } - close_others(TRUE, eap->forceit); -} - -/// ":all" and ":sall". -/// Also used for ":tab drop file ..." after setting the argument list. -void ex_all(exarg_T *eap) -{ - if (eap->addr_count == 0) { - eap->line2 = 9999; - } - do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); + close_others(true, eap->forceit); } static void ex_hide(exarg_T *eap) @@ -5761,162 +5624,6 @@ static void ex_goto(exarg_T *eap) goto_byte(eap->line2); } -/// Clear an argument list: free all file names and reset it to zero entries. -void alist_clear(alist_T *al) -{ -#define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname) - GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); -} - -/// Init an argument list. -void alist_init(alist_T *al) -{ - ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); -} - -/// Remove a reference from an argument list. -/// Ignored when the argument list is the global one. -/// If the argument list is no longer used by any window, free it. -void alist_unlink(alist_T *al) -{ - if (al != &global_alist && --al->al_refcount <= 0) { - alist_clear(al); - xfree(al); - } -} - -/// Create a new argument list and use it for the current window. -void alist_new(void) -{ - curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); - curwin->w_alist->al_refcount = 1; - curwin->w_alist->id = ++max_alist_id; - alist_init(curwin->w_alist); -} - -#if !defined(UNIX) - -/// Expand the file names in the global argument list. -/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer -/// numbers to be re-used. -void alist_expand(int *fnum_list, int fnum_len) -{ - char **old_arg_files; - int old_arg_count; - char **new_arg_files; - int new_arg_file_count; - char *save_p_su = p_su; - int i; - - /* Don't use 'suffixes' here. This should work like the shell did the - * expansion. Also, the vimrc file isn't read yet, thus the user - * can't set the options. */ - p_su = empty_option; - old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); - for (i = 0; i < GARGCOUNT; ++i) { - old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); - } - old_arg_count = GARGCOUNT; - if (expand_wildcards(old_arg_count, old_arg_files, - &new_arg_file_count, &new_arg_files, - EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK - && new_arg_file_count > 0) { - alist_set(&global_alist, new_arg_file_count, new_arg_files, - TRUE, fnum_list, fnum_len); - FreeWild(old_arg_count, old_arg_files); - } - p_su = save_p_su; -} -#endif - -/// Set the argument list for the current window. -/// Takes over the allocated files[] and the allocated fnames in it. -void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len) -{ - int i; - static int recursive = 0; - - if (recursive) { - emsg(_(e_au_recursive)); - return; - } - recursive++; - - alist_clear(al); - ga_grow(&al->al_ga, count); - { - for (i = 0; i < count; ++i) { - if (got_int) { - /* When adding many buffers this can take a long time. Allow - * interrupting here. */ - while (i < count) { - xfree(files[i++]); - } - break; - } - - /* May set buffer name of a buffer previously used for the - * argument list, so that it's re-used by alist_add. */ - if (fnum_list != NULL && i < fnum_len) { - buf_set_name(fnum_list[i], files[i]); - } - - alist_add(al, files[i], use_curbuf ? 2 : 1); - os_breakcheck(); - } - xfree(files); - } - - if (al == &global_alist) { - arg_had_last = false; - } - recursive--; -} - -/// Add file "fname" to argument list "al". -/// "fname" must have been allocated and "al" must have been checked for room. -/// -/// @param set_fnum 1: set buffer number; 2: re-use curbuf -void alist_add(alist_T *al, char *fname, int set_fnum) -{ - if (fname == NULL) { // don't add NULL file names - return; - } -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(fname); -#endif - AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname; - if (set_fnum > 0) { - AARGLIST(al)[al->al_ga.ga_len].ae_fnum = - buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); - } - ++al->al_ga.ga_len; -} - -#if defined(BACKSLASH_IN_FILENAME) - -/// Adjust slashes in file names. Called after 'shellslash' was set. -void alist_slash_adjust(void) -{ - for (int i = 0; i < GARGCOUNT; ++i) { - if (GARGLIST[i].ae_fname != NULL) { - slash_adjust(GARGLIST[i].ae_fname); - } - } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_alist != &global_alist) { - for (int i = 0; i < WARGCOUNT(wp); ++i) { - if (WARGLIST(wp)[i].ae_fname != NULL) { - slash_adjust(WARGLIST(wp)[i].ae_fname); - } - } - } - } -} - -#endif - /// ":preserve". static void ex_preserve(exarg_T *eap) { @@ -5985,9 +5692,7 @@ void ex_splitview(exarg_T *eap) eap->arg = fname; } - /* - * Either open new tab page or split the window. - */ + // Either open new tab page or split the window. if (use_tab) { if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab : eap->addr_count == 0 ? 0 : (int)eap->line2 + 1, (char_u *)eap->arg) != FAIL) { @@ -6021,12 +5726,11 @@ theend: /// Open a new tab page. void tabpage_new(void) { - exarg_T ea; - - memset(&ea, 0, sizeof(ea)); - ea.cmdidx = CMD_tabnew; - ea.cmd = "tabn"; - ea.arg = ""; + exarg_T ea = { + .cmdidx = CMD_tabnew, + .cmd = "tabn", + .arg = "", + }; ex_splitview(&ea); } @@ -6092,7 +5796,7 @@ static void ex_tabs(exarg_T *eap) int tabcount = 1; msg_start(); - msg_scroll = TRUE; + msg_scroll = true; win_T *lastused_win = valid_tabpage(lastused_tabpage) ? lastused_tabpage->tp_curwin @@ -6147,15 +5851,14 @@ static void ex_mode(exarg_T *eap) /// set, increment or decrement current window height static void ex_resize(exarg_T *eap) { - int n; win_T *wp = curwin; if (eap->addr_count > 0) { - n = (int)eap->line2; + int n = (int)eap->line2; for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) {} } - n = (int)atol(eap->arg); + int n = (int)atol(eap->arg); if (cmdmod.cmod_split & WSP_VERT) { if (*eap->arg == '-' || *eap->arg == '+') { n += wp->w_width; @@ -6176,15 +5879,12 @@ static void ex_resize(exarg_T *eap) /// ":find [+command] <file>" command. static void ex_find(exarg_T *eap) { - char *fname; - linenr_T count; - - fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), - FNAME_MESS, true, (char_u *)curbuf->b_ffname); + char *fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), + FNAME_MESS, true, (char_u *)curbuf->b_ffname); if (eap->addr_count > 0) { // Repeat finding the file "count" times. This matters when it // appears several times in the path. - count = eap->line2; + linenr_T count = eap->line2; while (fname != NULL && --count > 0) { xfree(fname); fname = (char *)find_file_in_path(NULL, 0, FNAME_MESS, false, (char_u *)curbuf->b_ffname); @@ -6210,11 +5910,8 @@ static void ex_edit(exarg_T *eap) void do_exedit(exarg_T *eap, win_T *old_curwin) { int n; - int need_hide; - /* - * ":vi" command ends Ex mode. - */ + // ":vi" command ends Ex mode. if (exmode_active && (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)) { exmode_active = false; @@ -6286,12 +5983,12 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) old_curwin == NULL ? curwin : NULL) == FAIL) { // Editing the file failed. If the window was split, close it. if (old_curwin != NULL) { - need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); + bool need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); if (!need_hide || buf_hide(curbuf)) { cleanup_T cs; // Reset the error/interrupt/exception state here so that - // aborting() returns FALSE when closing a window. + // aborting() returns false when closing a window. enter_cleanup(&cs); win_close(curwin, !need_hide && !buf_hide(curbuf), false); @@ -6320,10 +6017,8 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) } } - /* - * if ":split file" worked, set alternate file name in old window to new - * file - */ + // if ":split file" worked, set alternate file name in old window to new + // file if (old_curwin != NULL && *eap->arg != NUL && curwin != old_curwin @@ -6369,9 +6064,7 @@ static void ex_syncbind(exarg_T *eap) setpcmark(); - /* - * determine max topline - */ + // determine max topline if (curwin->w_p_scb) { topline = curwin->w_topline; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -6389,23 +6082,21 @@ static void ex_syncbind(exarg_T *eap) topline = 1; } - /* - * Set all scrollbind windows to the same topline. - */ + // Set all scrollbind windows to the same topline. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { curwin = wp; if (curwin->w_p_scb) { curbuf = curwin->w_buffer; y = topline - curwin->w_topline; if (y > 0) { - scrollup(y, TRUE); + scrollup(y, true); } else { - scrolldown(-y, TRUE); + scrolldown(-y, true); } curwin->w_scbind_pos = topline; redraw_later(curwin, VALID); cursor_correct(); - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } } curwin = save_curwin; @@ -6425,9 +6116,7 @@ static void ex_syncbind(exarg_T *eap) static void ex_read(exarg_T *eap) { - int i; int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); - linenr_T lnum; if (eap->usefilter) { // :r!cmd do_bang(1, eap, false, false, true); @@ -6435,6 +6124,7 @@ static void ex_read(exarg_T *eap) if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) { return; } + int i; if (*eap->arg == NUL) { if (check_fname() == FAIL) { // check for no file name @@ -6457,6 +6147,7 @@ static void ex_read(exarg_T *eap) if (empty && exmode_active) { // Delete the empty line that remains. Historically ex does // this but vi doesn't. + linenr_T lnum; if (eap->line2 == 0) { lnum = curbuf->b_ml.ml_line_count; } else { @@ -6685,17 +6376,14 @@ static void ex_equal(exarg_T *eap) static void ex_sleep(exarg_T *eap) { - int n; - long len; - if (cursor_valid()) { - n = curwin->w_winrow + curwin->w_wrow - msg_scrolled; + int n = curwin->w_winrow + curwin->w_wrow - msg_scrolled; if (n >= 0) { ui_cursor_goto(n, curwin->w_wincol + curwin->w_wcol); } } - len = eap->line2; + long len = eap->line2; switch (*eap->arg) { case 'm': break; @@ -6815,7 +6503,7 @@ static void ex_operators(exarg_T *eap) } else { oa.op_type = OP_LSHIFT; } - op_shift(&oa, FALSE, eap->amount); + op_shift(&oa, false, eap->amount); break; } virtual_op = kNone; @@ -6828,7 +6516,7 @@ static void ex_put(exarg_T *eap) // ":0put" works like ":1put!". if (eap->line2 == 0) { eap->line2 = 1; - eap->forceit = TRUE; + eap->forceit = true; } curwin->w_cursor.lnum = eap->line2; check_cursor_col(); @@ -6846,9 +6534,7 @@ static void ex_copymove(exarg_T *eap) } get_flags(eap); - /* - * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' - */ + // move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) { emsg(_(e_invrange)); return; @@ -6910,7 +6596,7 @@ static void ex_join(exarg_T *eap) beep_flush(); return; } - ++eap->line2; + eap->line2++; } do_join((size_t)((ssize_t)eap->line2 - eap->line1 + 1), !eap->forceit, true, true, true); beginline(BL_WHITE | BL_FIX); @@ -6939,11 +6625,9 @@ static void ex_at(exarg_T *eap) exec_from_reg = true; - /* - * Execute from the typeahead buffer. - * Continue until the stuff buffer is empty and all added characters - * have been consumed. - */ + // Execute from the typeahead buffer. + // Continue until the stuff buffer is empty and all added characters + // have been consumed. while (!stuff_empty() || typebuf.tb_len > prev_len) { (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); } @@ -7032,15 +6716,15 @@ static void ex_later(exarg_T *eap) count = getdigits_long(&p, false, 0); switch (*p) { case 's': - ++p; sec = true; break; + p++; sec = true; break; case 'm': - ++p; sec = true; count *= 60; break; + p++; sec = true; count *= 60; break; case 'h': - ++p; sec = true; count *= 60 * 60; break; + p++; sec = true; count *= 60 * 60; break; case 'd': - ++p; sec = true; count *= 24 * 60 * 60; break; + p++; sec = true; count *= 24 * 60 * 60; break; case 'f': - ++p; file = true; break; + p++; file = true; break; } } @@ -7055,17 +6739,16 @@ static void ex_later(exarg_T *eap) /// ":redir": start/stop redirection. static void ex_redir(exarg_T *eap) { - char *mode; - char *fname; char *arg = eap->arg; if (STRICMP(eap->arg, "END") == 0) { close_redir(); } else { if (*arg == '>') { - ++arg; + arg++; + char *mode; if (*arg == '>') { - ++arg; + arg++; mode = "a"; } else { mode = "w"; @@ -7075,7 +6758,7 @@ static void ex_redir(exarg_T *eap) close_redir(); // Expand environment variables and "~/". - fname = expand_env_save(arg); + char *fname = expand_env_save(arg); if (fname == NULL) { return; } @@ -7085,7 +6768,7 @@ static void ex_redir(exarg_T *eap) } else if (*arg == '@') { // redirect to a register a-z (resp. A-Z for appending) close_redir(); - ++arg; + arg++; if (valid_yank_reg(*arg, true) && *arg != '_') { redir_reg = (char_u)(*arg++); if (*arg == '>' && arg[1] == '>') { // append @@ -7114,10 +6797,10 @@ static void ex_redir(exarg_T *eap) arg += 2; if (*arg == '>') { - ++arg; - append = TRUE; + arg++; + append = true; } else { - append = FALSE; + append = false; } if (var_redir_start(skipwhite(arg), append) == OK) { @@ -7146,14 +6829,15 @@ static void ex_redraw(exarg_T *eap) int p = p_lz; RedrawingDisabled = 0; - p_lz = FALSE; + p_lz = false; validate_cursor(); update_topline(curwin); if (eap->forceit) { redraw_all_later(NOT_VALID); + redraw_cmdline = true; } update_screen(eap->forceit ? NOT_VALID - : VIsual_active ? INVERTED : 0); + : VIsual_active ? INVERTED : 0); if (need_maketitle) { maketitle(); } @@ -7180,7 +6864,7 @@ static void ex_redrawstatus(exarg_T *eap) int p = p_lz; RedrawingDisabled = 0; - p_lz = FALSE; + p_lz = false; if (eap->forceit) { status_redraw_all(); } else { @@ -7245,8 +6929,6 @@ int vim_mkdir_emsg(const char *const name, const int prot) /// @return file descriptor, or NULL on failure. FILE *open_exfile(char_u *fname, int forceit, char *mode) { - FILE *fd; - #ifdef UNIX // with Unix it is possible to open a directory if (os_isdir(fname)) { @@ -7259,6 +6941,7 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) return NULL; } + FILE *fd; if ((fd = os_fopen((char *)fname, mode)) == NULL) { semsg(_("E190: Cannot open \"%s\" for writing"), fname); } @@ -7269,14 +6952,12 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) /// ":mark" and ":k". static void ex_mark(exarg_T *eap) { - pos_T pos; - if (*eap->arg == NUL) { // No argument? emsg(_(e_argreq)); } else if (eap->arg[1] != NUL) { // more than one character? semsg(_(e_trailing_arg), eap->arg); } else { - pos = curwin->w_cursor; // save curwin->w_cursor + pos_T pos = curwin->w_cursor; // save curwin->w_cursor curwin->w_cursor.lnum = eap->line2; beginline(BL_WHITE | BL_FIX); if (setmark(*eap->arg) == FAIL) { // set mark @@ -7357,10 +7038,7 @@ static void ex_normal(exarg_T *eap) emsg("Can't re-enter normal mode from terminal mode"); return; } - save_state_T save_state; char *arg = NULL; - int l; - char *p; if (ex_normal_lock > 0) { emsg(_(e_secure)); @@ -7378,6 +7056,8 @@ static void ex_normal(exarg_T *eap) int len = 0; // Count the number of characters to be escaped. + int l; + char *p; for (p = eap->arg; *p != NUL; p++) { for (l = utfc_ptr2len(p) - 1; l > 0; l--) { if (*++p == (char)K_SPECIAL) { // trailbyte K_SPECIAL @@ -7403,6 +7083,7 @@ static void ex_normal(exarg_T *eap) } ex_normal_busy++; + save_state_T save_state; if (save_current_state(&save_state)) { // Repeat the :normal command for each line in the range. When no // range given, execute it just once, without positioning the cursor @@ -7521,8 +7202,6 @@ static void ex_psearch(exarg_T *eap) static void ex_findpat(exarg_T *eap) { bool whole = true; - long n; - char *p; int action; switch (cmdnames[eap->cmdidx].cmd_name[2]) { @@ -7544,7 +7223,7 @@ static void ex_findpat(exarg_T *eap) break; } - n = 1; + long n = 1; if (ascii_isdigit(*eap->arg)) { // get count n = getdigits_long(&eap->arg, false, 0); eap->arg = skipwhite(eap->arg); @@ -7552,7 +7231,7 @@ static void ex_findpat(exarg_T *eap) if (*eap->arg == '/') { // Match regexp, not just whole words whole = false; eap->arg++; - p = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL); + char *p = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -7686,7 +7365,6 @@ enum { ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) FUNC_ATTR_NONNULL_ALL { - size_t len; static char *(spec_str[]) = { [SPEC_PERC] = "%", [SPEC_HASH] = "#", @@ -7705,8 +7383,8 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) // [SPEC_CLIENT] = "<client>", }; - for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) { - len = STRLEN(spec_str[i]); + for (size_t i = 0; i < ARRAY_SIZE(spec_str); i++) { + size_t len = STRLEN(spec_str[i]); if (STRNCMP(src, spec_str[i], len) == 0) { *usedlen = len; assert(i <= SSIZE_MAX); @@ -7725,6 +7403,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// '<cexpr>' to C-expression under the cursor /// '<cfile>' to path name under the cursor /// '<sfile>' to sourced file name +/// '<stack>' to call stack /// '<slnum>' to sourced file line number /// '<afile>' to file name for autocommand /// '<abuf>' to buffer number for autocommand @@ -7746,12 +7425,9 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, int *escaped) { - int i; - char *s; char *result; char *resultbuf = NULL; size_t resultlen; - buf_T *buf; int valid = VALID_HEAD | VALID_PATH; // Assume valid result. bool tilde_file = false; bool skip_mod = false; @@ -7759,40 +7435,34 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum *errormsg = NULL; if (escaped != NULL) { - *escaped = FALSE; + *escaped = false; } - /* - * Check if there is something to do. - */ + // Check if there is something to do. ssize_t spec_idx = find_cmdline_var(src, usedlen); if (spec_idx < 0) { // no match *usedlen = 1; return NULL; } - /* - * Skip when preceded with a backslash "\%" and "\#". - * Note: In "\\%" the % is also not recognized! - */ + // Skip when preceded with a backslash "\%" and "\#". + // Note: In "\\%" the % is also not recognized! if (src > srcstart && src[-1] == '\\') { *usedlen = 0; STRMOVE(src - 1, src); // remove backslash return NULL; } - /* - * word or WORD under cursor - */ + // word or WORD under cursor if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD || spec_idx == SPEC_CEXPR) { - resultlen = find_ident_under_cursor((char_u **)&result, + resultlen = find_ident_under_cursor(&result, spec_idx == SPEC_CWORD - ? (FIND_IDENT | FIND_STRING) - : (spec_idx == SPEC_CEXPR - ? (FIND_IDENT | FIND_STRING | FIND_EVAL) - : FIND_STRING)); + ? (FIND_IDENT | FIND_STRING) + : (spec_idx == SPEC_CEXPR + ? (FIND_IDENT | FIND_STRING | FIND_EVAL) + : FIND_STRING)); if (resultlen == 0) { *errormsg = ""; return NULL; @@ -7822,16 +7492,16 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum resultbuf = result; *usedlen = 2; if (escaped != NULL) { - *escaped = TRUE; + *escaped = true; } skip_mod = true; break; } - s = (char *)src + 1; + char *s = (char *)src + 1; if (*s == '<') { // "#<99" uses v:oldfiles. s++; } - i = getdigits_int(&s, false, 0); + int i = getdigits_int(&s, false, 0); if ((char_u *)s == src + 2 && src[1] == '-') { // just a minus sign, don't skip over it s--; @@ -7853,7 +7523,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum if (i == 0 && src[1] == '<' && *usedlen > 1) { *usedlen = 1; } - buf = buflist_findnr(i); + buf_T *buf = buflist_findnr(i); if (buf == NULL) { *errormsg = _("E194: No alternate file name to substitute for '#'"); return NULL; @@ -7918,29 +7588,33 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum break; case SPEC_SFILE: // file name for ":so" command - result = sourcing_name; + case SPEC_STACK: // call stack + result = estack_sfile(spec_idx == SPEC_SFILE ? ESTACK_SFILE : ESTACK_STACK); if (result == NULL) { - *errormsg = _("E498: no :source file name to substitute for \"<sfile>\""); + *errormsg = spec_idx == SPEC_SFILE + ? _("E498: no :source file name to substitute for \"<sfile>\"") + : _("E489: no call stack to substitute for \"<stack>\""); return NULL; } + resultbuf = result; // remember allocated string break; case SPEC_SLNUM: // line in file for ":so" command - if (sourcing_name == NULL || sourcing_lnum == 0) { + if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) { *errormsg = _("E842: no line number to use for \"<slnum>\""); return NULL; } - snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, sourcing_lnum); + snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM); result = strbuf; break; case SPEC_SFLNUM: // line in script file - if (current_sctx.sc_lnum + sourcing_lnum == 0) { + if (current_sctx.sc_lnum + SOURCING_LNUM == 0) { *errormsg = _("E961: no line number to use for \"<sflnum>\""); return NULL; } snprintf((char *)strbuf, sizeof(strbuf), "%" PRIdLINENR, - current_sctx.sc_lnum + sourcing_lnum); + current_sctx.sc_lnum + SOURCING_LNUM); result = strbuf; break; @@ -7966,6 +7640,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum // Remove the file name extension. if (src[*usedlen] == '<') { (*usedlen)++; + char *s; if ((s = (char *)STRRCHR(result, '.')) != NULL && s >= path_tail(result)) { resultlen = (size_t)(s - result); @@ -7995,88 +7670,21 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum return (char_u *)result; } -/// Concatenate all files in the argument list, separated by spaces, and return -/// it in one allocated string. -/// Spaces and backslashes in the file names are escaped with a backslash. -static char *arg_all(void) -{ - int len; - int idx; - char *retval = NULL; - char *p; - - /* - * Do this loop two times: - * first time: compute the total length - * second time: concatenate the names - */ - for (;;) { - len = 0; - for (idx = 0; idx < ARGCOUNT; idx++) { - p = alist_name(&ARGLIST[idx]); - if (p == NULL) { - continue; - } - if (len > 0) { - // insert a space in between names - if (retval != NULL) { - retval[len] = ' '; - } - ++len; - } - for (; *p != NUL; p++) { - if (*p == ' ' -#ifndef BACKSLASH_IN_FILENAME - || *p == '\\' -#endif - || *p == '`') { - // insert a backslash - if (retval != NULL) { - retval[len] = '\\'; - } - len++; - } - if (retval != NULL) { - retval[len] = *p; - } - len++; - } - } - - // second time: break here - if (retval != NULL) { - retval[len] = NUL; - break; - } - - // allocate memory - retval = xmalloc((size_t)len + 1); - } - - return retval; -} - /// Expand the <sfile> string in "arg". /// /// @return an allocated string, or NULL for any error. char *expand_sfile(char *arg) { - char *errormsg; - size_t len; - char *result; - char *newres; - char *repl; - size_t srclen; - char *p; - - result = xstrdup(arg); + char *result = xstrdup(arg); - for (p = result; *p;) { + for (char *p = result; *p;) { if (STRNCMP(p, "<sfile>", 7) != 0) { - ++p; + p++; } else { // replace "<sfile>" with the sourced file name, and do ":" stuff - repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL); + size_t srclen; + char *errormsg; + char *repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL); if (errormsg != NULL) { if (*errormsg) { emsg(errormsg); @@ -8088,8 +7696,8 @@ char *expand_sfile(char *arg) p += srclen; continue; } - len = STRLEN(result) - srclen + STRLEN(repl) + 1; - newres = xmalloc(len); + size_t len = STRLEN(result) - srclen + STRLEN(repl) + 1; + char *newres = xmalloc(len); memmove(newres, result, (size_t)(p - result)); STRCPY(newres + (p - result), repl); len = STRLEN(newres); @@ -8107,9 +7715,7 @@ char *expand_sfile(char *arg) /// ":rshada" and ":wshada". static void ex_shada(exarg_T *eap) { - char *save_shada; - - save_shada = (char *)p_shada; + char *save_shada = (char *)p_shada; if (*p_shada == NUL) { p_shada = (char_u *)"'100"; } @@ -8193,10 +7799,6 @@ static TriState filetype_indent = kNone; /// indent off: load indoff.vim static void ex_filetype(exarg_T *eap) { - char *arg = eap->arg; - bool plugin = false; - bool indent = false; - if (*eap->arg == NUL) { // Print current status. smsg("filetype detection:%s plugin:%s indent:%s", @@ -8206,6 +7808,10 @@ static void ex_filetype(exarg_T *eap) return; } + char *arg = eap->arg; + bool plugin = false; + bool indent = false; + // Accept "plugin" and "indent" in any order. for (;;) { if (STRNCMP(arg, "plugin", 6) == 0) { @@ -8342,7 +7948,7 @@ static void ex_foldopen(exarg_T *eap) static void ex_folddo(exarg_T *eap) { // First set the marks for all lines closed/open. - for (linenr_T lnum = eap->line1; lnum <= eap->line2; ++lnum) { + for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) { if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) { ml_setmarked(lnum); } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f67c8a6720..69d509abb7 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -16,12 +16,12 @@ #include "nvim/debugger.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -151,8 +151,8 @@ int aborted_in_try(void) bool cause_errthrow(const char *mesg, bool severe, bool *ignore) FUNC_ATTR_NONNULL_ALL { - struct msglist *elem; - struct msglist **plist; + msglist_T *elem; + msglist_T **plist; /* * Do nothing when displaying the interrupt message or reporting an @@ -254,7 +254,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) plist = &(*plist)->next; } - elem = xmalloc(sizeof(struct msglist)); + elem = xmalloc(sizeof(msglist_T)); elem->msg = xstrdup(mesg); elem->next = NULL; elem->throw_msg = NULL; @@ -275,20 +275,26 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) (*msg_list)->throw_msg = tmsg; } } + + // Get the source name and lnum now, it may change before + // reaching do_errthrow(). + elem->sfile = estack_sfile(ESTACK_NONE); + elem->slnum = SOURCING_LNUM; } return true; } } /// Free a "msg_list" and the messages it contains. -static void free_msglist(struct msglist *l) +static void free_msglist(msglist_T *l) { - struct msglist *messages, *next; + msglist_T *messages, *next; messages = l; while (messages != NULL) { next = messages->next; xfree(messages->msg); + xfree(messages->sfile); xfree(messages); messages = next; } @@ -389,7 +395,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int * if (type == ET_ERROR) { *should_free = true; - mesg = ((struct msglist *)value)->throw_msg; + mesg = ((msglist_T *)value)->throw_msg; if (cmdname != NULL && *cmdname != NUL) { size_t cmdlen = STRLEN(cmdname); ret = xstrnsave("Vim(", 4 + cmdlen + 2 + STRLEN(mesg)); @@ -469,7 +475,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) if (type == ET_ERROR) { // Store the original message and prefix the exception value with // "Vim:" or, if a command name is given, "Vim(cmdname):". - excp->messages = (struct msglist *)value; + excp->messages = (msglist_T *)value; } excp->value = get_exception_string(value, type, cmdname, &should_free); @@ -478,8 +484,18 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) } excp->type = type; - excp->throw_name = xstrdup(sourcing_name == NULL ? "" : sourcing_name); - excp->throw_lnum = sourcing_lnum; + if (type == ET_ERROR && ((msglist_T *)value)->sfile != NULL) { + msglist_T *entry = (msglist_T *)value; + excp->throw_name = entry->sfile; + entry->sfile = NULL; + excp->throw_lnum = entry->slnum; + } else { + excp->throw_name = estack_sfile(ESTACK_NONE); + if (excp->throw_name == NULL) { + excp->throw_name = xstrdup(""); + } + excp->throw_lnum = SOURCING_LNUM; + } if (p_verbose >= 13 || debug_break_level > 0) { int save_msg_silent = msg_silent; @@ -489,7 +505,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) } else { verbose_enter(); } - ++no_wait_return; + no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { msg_scroll = TRUE; // always scroll up, don't overwrite } @@ -499,7 +515,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; } - --no_wait_return; + no_wait_return--; if (debug_break_level > 0) { msg_silent = save_msg_silent; } else { @@ -542,7 +558,7 @@ static void discard_exception(except_T *excp, bool was_finished) } else { verbose_enter(); } - ++no_wait_return; + no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { msg_scroll = TRUE; // always scroll up, don't overwrite } @@ -610,7 +626,7 @@ static void catch_exception(except_T *excp) } else { verbose_enter(); } - ++no_wait_return; + no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { msg_scroll = TRUE; // always scroll up, don't overwrite } @@ -620,7 +636,7 @@ static void catch_exception(except_T *excp) if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; } - --no_wait_return; + no_wait_return--; if (debug_break_level > 0) { msg_silent = save_msg_silent; } else { @@ -732,12 +748,12 @@ static void report_pending(int action, int pending, void *value) if (debug_break_level > 0) { msg_silent = FALSE; // display messages } - ++no_wait_return; - msg_scroll = TRUE; // always scroll up, don't overwrite + no_wait_return++; + msg_scroll = true; // always scroll up, don't overwrite smsg(mesg, s); msg_puts("\n"); // don't overwrite this either cmdline_row = msg_row; - --no_wait_return; + no_wait_return--; if (debug_break_level > 0) { msg_silent = save_msg_silent; } @@ -2038,7 +2054,7 @@ int has_loop_cmd(char *p) // skip modifiers, white space and ':' for (;;) { while (*p == ' ' || *p == '\t' || *p == ':') { - ++p; + p++; } len = modifier_len(p); if (len == 0) { diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 235875fb91..9e3ac5e9c1 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -2,81 +2,7 @@ #define NVIM_EX_EVAL_H #include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/pos.h" // for linenr_T - -/* There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" - * was used. */ -#define CSF_TRUE 0x0001 // condition was TRUE -#define CSF_ACTIVE 0x0002 // current state is active -#define CSF_ELSE 0x0004 // ":else" has been passed -#define CSF_WHILE 0x0008 // is a ":while" -#define CSF_FOR 0x0010 // is a ":for" - -#define CSF_TRY 0x0100 // is a ":try" -#define CSF_FINALLY 0x0200 // ":finally" has been passed -#define CSF_THROWN 0x0800 // exception thrown to this try conditional -#define CSF_CAUGHT 0x1000 // exception caught by this try conditional -#define CSF_FINISHED 0x2000 // CSF_CAUGHT was handled by finish_exception() -#define CSF_SILENT 0x4000 // "emsg_silent" reset by ":try" -// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset -// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. - -/* - * What's pending for being reactivated at the ":endtry" of this try - * conditional: - */ -#define CSTP_NONE 0 // nothing pending in ":finally" clause -#define CSTP_ERROR 1 // an error is pending -#define CSTP_INTERRUPT 2 // an interrupt is pending -#define CSTP_THROW 4 // a throw is pending -#define CSTP_BREAK 8 // ":break" is pending -#define CSTP_CONTINUE 16 // ":continue" is pending -#define CSTP_RETURN 24 // ":return" is pending -#define CSTP_FINISH 32 // ":finish" is pending - -/* - * A list of error messages that can be converted to an exception. "throw_msg" - * is only set in the first element of the list. Usually, it points to the - * original message stored in that element, but sometimes it points to a later - * message in the list. See cause_errthrow() below. - */ -struct msglist { - char *msg; // original message - char *throw_msg; // msg to throw: usually original one - struct msglist *next; // next of several messages in a row -}; - -// The exception types. -typedef enum { - ET_USER, // exception caused by ":throw" command - ET_ERROR, // error exception - ET_INTERRUPT, // interrupt exception triggered by Ctrl-C -} except_type_T; - -/* - * Structure describing an exception. - * (don't use "struct exception", it's used by the math library). - */ -typedef struct vim_exception except_T; -struct vim_exception { - except_type_T type; // exception type - char *value; // exception value - struct msglist *messages; // message(s) causing error exception - char *throw_name; // name of the throw point - linenr_T throw_lnum; // line number of the throw point - except_T *caught; // next exception on the caught stack -}; - -/* - * Structure to save the error/interrupt/exception state between calls to - * enter_cleanup() and leave_cleanup(). Must be allocated as an automatic - * variable by the (common) caller of these functions. - */ -typedef struct cleanup_stuff cleanup_T; -struct cleanup_stuff { - int pending; // error/interrupt/exception state - except_T *exception; // exception value -}; +#include "nvim/ex_eval_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_eval.h.generated.h" diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h new file mode 100644 index 0000000000..9da0c9ad12 --- /dev/null +++ b/src/nvim/ex_eval_defs.h @@ -0,0 +1,79 @@ +#ifndef NVIM_EX_EVAL_DEFS_H +#define NVIM_EX_EVAL_DEFS_H + +#include "nvim/pos.h" // for linenr_T + +/// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" +/// was used. +enum { + CSF_TRUE = 0x0001, ///< condition was TRUE + CSF_ACTIVE = 0x0002, ///< current state is active + CSF_ELSE = 0x0004, ///< ":else" has been passed + CSF_WHILE = 0x0008, ///< is a ":while" + CSF_FOR = 0x0010, ///< is a ":for" + + CSF_TRY = 0x0100, ///< is a ":try" + CSF_FINALLY = 0x0200, ///< ":finally" has been passed + CSF_THROWN = 0x0800, ///< exception thrown to this try conditional + CSF_CAUGHT = 0x1000, ///< exception caught by this try conditional + CSF_FINISHED = 0x2000, ///< CSF_CAUGHT was handled by finish_exception() + CSF_SILENT = 0x4000, ///< "emsg_silent" reset by ":try" +}; +// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset +// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. + +/// What's pending for being reactivated at the ":endtry" of this try +/// conditional: +enum { + CSTP_NONE = 0, ///< nothing pending in ":finally" clause + CSTP_ERROR = 1, ///< an error is pending + CSTP_INTERRUPT = 2, ///< an interrupt is pending + CSTP_THROW = 4, ///< a throw is pending + CSTP_BREAK = 8, ///< ":break" is pending + CSTP_CONTINUE = 16, ///< ":continue" is pending + CSTP_RETURN = 24, ///< ":return" is pending + CSTP_FINISH = 32, ///< ":finish" is pending +}; + +/// A list of error messages that can be converted to an exception. "throw_msg" +/// is only set in the first element of the list. Usually, it points to the +/// original message stored in that element, but sometimes it points to a later +/// message in the list. See cause_errthrow(). +typedef struct msglist msglist_T; +struct msglist { + char *msg; ///< original message, allocated + char *throw_msg; ///< msg to throw: usually original one + char *sfile; ///< value from estack_sfile(), allocated + linenr_T slnum; ///< line number for "sfile" + msglist_T *next; ///< next of several messages in a row +}; + +/// The exception types. +typedef enum { + ET_USER, ///< exception caused by ":throw" command + ET_ERROR, ///< error exception + ET_INTERRUPT, ///< interrupt exception triggered by Ctrl-C +} except_type_T; + +/// Structure describing an exception. +/// (don't use "struct exception", it's used by the math library). +typedef struct vim_exception except_T; +struct vim_exception { + except_type_T type; ///< exception type + char *value; ///< exception value + msglist_T *messages; ///< message(s) causing error exception + char *throw_name; ///< name of the throw point + linenr_T throw_lnum; ///< line number of the throw point + except_T *caught; ///< next exception on the caught stack +}; + +/// Structure to save the error/interrupt/exception state between calls to +/// enter_cleanup() and leave_cleanup(). Must be allocated as an automatic +/// variable by the (common) caller of these functions. +typedef struct cleanup_stuff cleanup_T; +struct cleanup_stuff { + int pending; ///< error/interrupt/exception state + except_T *exception; ///< exception value +}; + +#endif // NVIM_EX_EVAL_DEFS_H diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 07a0e68884..c15d85967d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -15,13 +15,16 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/arabic.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/funcs.h" @@ -37,6 +40,8 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/globals.h" +#include "nvim/grid.h" +#include "nvim/help.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" @@ -63,9 +68,9 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sign.h" #include "nvim/state.h" @@ -143,7 +148,7 @@ struct cmdline_info { /// Last value of prompt_id, incremented when doing new prompt static unsigned last_prompt_id = 0; -// Struct to store the viewstate during 'incsearch' highlighting. +// Struct to store the viewstate during 'incsearch' highlighting and 'inccommand' preview. typedef struct { colnr_T vs_curswant; colnr_T vs_leftcol; @@ -195,6 +200,32 @@ typedef struct command_line_state { long *b_im_ptr; } CommandLineState; +typedef struct cmdpreview_win_info { + win_T *win; + pos_T save_w_cursor; + viewstate_T save_viewstate; + int save_w_p_cul; + int save_w_p_cuc; +} CpWinInfo; + +typedef struct cmdpreview_buf_info { + buf_T *buf; + time_t save_b_u_time_cur; + long save_b_u_seq_cur; + u_header_T *save_b_u_newhead; + long save_b_p_ul; + int save_b_changed; + varnumber_T save_changedtick; +} CpBufInfo; + +typedef struct cmdpreview_info { + kvec_t(CpWinInfo) win_info; + kvec_t(CpBufInfo) buf_info; + bool save_hls; + cmdmod_T save_cmdmod; + garray_T save_view; +} CpInfo; + typedef struct cmdline_info CmdlineInfo; /// The current cmdline_info. It is initialized in getcmdline() and after that @@ -214,12 +245,6 @@ static Array cmdline_block = ARRAY_DICT_INIT; */ typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *); -static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL }; -static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; // lastused entry -static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 }; -// identifying (unique) number of newest history entry -static int hislen = 0; // actual length of history tables - /// Flag for command_line_handle_key to ignore <C-c> /// /// Used if it was received while processing highlight function in order for @@ -243,26 +268,26 @@ static long cmdpreview_ns = 0; static int cmd_hkmap = 0; // Hebrew mapping during command line -static void save_viewstate(viewstate_T *vs) +static void save_viewstate(win_T *wp, viewstate_T *vs) FUNC_ATTR_NONNULL_ALL { - vs->vs_curswant = curwin->w_curswant; - vs->vs_leftcol = curwin->w_leftcol; - vs->vs_topline = curwin->w_topline; - vs->vs_topfill = curwin->w_topfill; - vs->vs_botline = curwin->w_botline; - vs->vs_empty_rows = curwin->w_empty_rows; + vs->vs_curswant = wp->w_curswant; + vs->vs_leftcol = wp->w_leftcol; + vs->vs_topline = wp->w_topline; + vs->vs_topfill = wp->w_topfill; + vs->vs_botline = wp->w_botline; + vs->vs_empty_rows = wp->w_empty_rows; } -static void restore_viewstate(viewstate_T *vs) +static void restore_viewstate(win_T *wp, viewstate_T *vs) FUNC_ATTR_NONNULL_ALL { - curwin->w_curswant = vs->vs_curswant; - curwin->w_leftcol = vs->vs_leftcol; - curwin->w_topline = vs->vs_topline; - curwin->w_topfill = vs->vs_topfill; - curwin->w_botline = vs->vs_botline; - curwin->w_empty_rows = vs->vs_empty_rows; + wp->w_curswant = vs->vs_curswant; + wp->w_leftcol = vs->vs_leftcol; + wp->w_topline = vs->vs_topline; + wp->w_topfill = vs->vs_topfill; + wp->w_botline = vs->vs_botline; + wp->w_empty_rows = vs->vs_empty_rows; } static void init_incsearch_state(incsearch_state_T *s) @@ -274,8 +299,8 @@ static void init_incsearch_state(incsearch_state_T *s) clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later s->search_start = curwin->w_cursor; - save_viewstate(&s->init_viewstate); - save_viewstate(&s->old_viewstate); + save_viewstate(curwin, &s->init_viewstate); + save_viewstate(curwin, &s->old_viewstate); } /// Completion for |:checkhealth| command. @@ -316,7 +341,6 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s int delim; char *end; char *dummy; - exarg_T ea; pos_T save_cursor; bool use_last_pat; bool retval = false; @@ -341,11 +365,12 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s } emsg_off++; - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; - ea.cmd = (char *)ccline.cmdbuff; - ea.addr_type = ADDR_LINES; + exarg_T ea = { + .line1 = 1, + .line2 = 1, + .cmd = (char *)ccline.cmdbuff, + .addr_type = ADDR_LINES, + }; cmdmod_T dummy_cmdmod; parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true); @@ -458,7 +483,6 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat { pos_T end_pos; proftime_T tm; - searchit_arg_T sia; int skiplen, patlen; char_u next_char; char_u use_last_pat; @@ -521,8 +545,9 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat search_flags += SEARCH_START; } ccline.cmdbuff[skiplen + patlen] = NUL; - memset(&sia, 0, sizeof(sia)); - sia.sa_tm = &tm; + searchit_arg_T sia = { + .sa_tm = &tm, + }; found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim, ccline.cmdbuff + skiplen, count, search_flags, &sia); @@ -555,7 +580,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat // first restore the old curwin values, so the screen is // positioned in the same way as the actual search command - restore_viewstate(&s->old_viewstate); + restore_viewstate(curwin, &s->old_viewstate); changed_cline_bef_curs(); update_topline(curwin); @@ -665,7 +690,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool } curwin->w_cursor = s->search_start; // -V519 } - restore_viewstate(&s->old_viewstate); + restore_viewstate(curwin, &s->old_viewstate); highlight_match = false; // by default search all lines @@ -736,7 +761,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init save_cmdline(&save_ccline); did_save_ccline = true; } else if (init_ccline) { - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); } if (s->firstc == -1) { @@ -869,7 +894,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init may_trigger_modechanged(); init_history(); - s->hiscnt = hislen; // set hiscnt to impossible history value + s->hiscnt = get_hislen(); // set hiscnt to impossible history value s->histype = hist_char2type(s->firstc); do_digraph(-1); // init digraph typeahead @@ -1664,7 +1689,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ update_topline(curwin); validate_cursor(); highlight_match = true; - save_viewstate(&s->old_viewstate); + save_viewstate(curwin, &s->old_viewstate); update_screen(NOT_VALID); highlight_match = false; redrawcmdline(); @@ -1682,12 +1707,12 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) for (;;) { // one step backwards if (!next_match) { - if (s->hiscnt == hislen) { + if (s->hiscnt == get_hislen()) { // first time - s->hiscnt = hisidx[s->histype]; - } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) { - s->hiscnt = hislen - 1; - } else if (s->hiscnt != hisidx[s->histype] + 1) { + s->hiscnt = *get_hisidx(s->histype); + } else if (s->hiscnt == 0 && *get_hisidx(s->histype) != get_hislen() - 1) { + s->hiscnt = get_hislen() - 1; + } else if (s->hiscnt != *get_hisidx(s->histype) + 1) { s->hiscnt--; } else { // at top of list @@ -1696,17 +1721,17 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) } } else { // one step forwards // on last entry, clear the line - if (s->hiscnt == hisidx[s->histype]) { - s->hiscnt = hislen; + if (s->hiscnt == *get_hisidx(s->histype)) { + s->hiscnt = get_hislen(); break; } // not on a history line, nothing to do - if (s->hiscnt == hislen) { + if (s->hiscnt == get_hislen()) { break; } - if (s->hiscnt == hislen - 1) { + if (s->hiscnt == get_hislen() - 1) { // wrap around s->hiscnt = 0; } else { @@ -1714,14 +1739,14 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) } } - if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { + if (s->hiscnt < 0 || get_histentry(s->histype)[s->hiscnt].hisstr == NULL) { s->hiscnt = s->save_hiscnt; break; } if ((s->c != K_UP && s->c != K_DOWN) || s->hiscnt == s->save_hiscnt - || STRNCMP(history[s->histype][s->hiscnt].hisstr, + || STRNCMP(get_histentry(s->histype)[s->hiscnt].hisstr, s->lookfor, (size_t)j) == 0) { break; } @@ -2103,7 +2128,7 @@ static int command_line_handle_key(CommandLineState *s) case K_KPAGEUP: case K_PAGEDOWN: case K_KPAGEDOWN: - if (s->histype == HIST_INVALID || hislen == 0 || s->firstc == NUL) { + if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) { // no history return command_line_not_changed(s); } @@ -2128,10 +2153,10 @@ static int command_line_handle_key(CommandLineState *s) XFREE_CLEAR(ccline.cmdbuff); s->xpc.xp_context = EXPAND_NOTHING; - if (s->hiscnt == hislen) { + if (s->hiscnt == get_hislen()) { p = s->lookfor; // back to the old one } else { - p = history[s->histype][s->hiscnt].hisstr; + p = get_histentry(s->histype)[s->hiscnt].hisstr; } if (s->histype == HIST_SEARCH @@ -2159,14 +2184,14 @@ static int command_line_handle_key(CommandLineState *s) if (i > 0) { ccline.cmdbuff[len] = '\\'; } - ++len; + len++; } if (i > 0) { ccline.cmdbuff[len] = p[j]; } } - ++len; + len++; } if (i == 0) { @@ -2396,6 +2421,126 @@ static void cmdpreview_close_win(void) } } +/// Save current state and prepare windows and buffers for command preview. +static void cmdpreview_prepare(CpInfo *cpinfo) +{ + kv_init(cpinfo->buf_info); + kv_init(cpinfo->win_info); + + FOR_ALL_WINDOWS_IN_TAB(win, curtab) { + buf_T *buf = win->w_buffer; + + // Don't save state of command preview buffer or preview window. + if (buf->handle == cmdpreview_bufnr) { + continue; + } + + CpBufInfo cp_bufinfo; + cp_bufinfo.buf = buf; + + cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur; + cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur; + cp_bufinfo.save_b_u_newhead = buf->b_u_newhead; + cp_bufinfo.save_b_p_ul = buf->b_p_ul; + cp_bufinfo.save_b_changed = buf->b_changed; + cp_bufinfo.save_changedtick = buf_get_changedtick(buf); + + kv_push(cpinfo->buf_info, cp_bufinfo); + + buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes + + CpWinInfo cp_wininfo; + cp_wininfo.win = win; + + // Save window cursor position and viewstate + cp_wininfo.save_w_cursor = win->w_cursor; + save_viewstate(win, &cp_wininfo.save_viewstate); + + // Save 'cursorline' and 'cursorcolumn' + cp_wininfo.save_w_p_cul = win->w_p_cul; + cp_wininfo.save_w_p_cuc = win->w_p_cuc; + + kv_push(cpinfo->win_info, cp_wininfo); + + win->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights + win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights + } + + cpinfo->save_hls = p_hls; + cpinfo->save_cmdmod = cmdmod; + win_size_save(&cpinfo->save_view); + save_search_patterns(); + + p_hls = false; // Don't show search highlighting during live substitution + cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers + cmdmod.cmod_tab = 0; // Disable :tab modifier + cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer +} + +// Restore the state of buffers and windows before command preview. +static void cmdpreview_restore_state(CpInfo *cpinfo) +{ + for (size_t i = 0; i < cpinfo->buf_info.size; i++) { + CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i]; + buf_T *buf = cp_bufinfo.buf; + + buf->b_changed = cp_bufinfo.save_b_changed; + + if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) { + int count = 0; + + // Calculate how many undo steps are necessary to restore earlier state. + for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead; + uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur; + uhp = uhp->uh_next.ptr, ++count) {} + + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + // Undo invisibly. This also moves the cursor! + if (!u_undo_and_forget(count)) { + abort(); + } + aucmd_restbuf(&aco); + + // Restore newhead. It is meaningless when curhead is valid, but we must + // restore it so that undotree() is identical before/after the preview. + buf->b_u_newhead = cp_bufinfo.save_b_u_newhead; + buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur; + } + if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) { + buf_set_changedtick(buf, cp_bufinfo.save_changedtick); + } + + buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels' + + // Clear preview highlights. + extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); + } + for (size_t i = 0; i < cpinfo->win_info.size; i++) { + CpWinInfo cp_wininfo = cpinfo->win_info.items[i]; + win_T *win = cp_wininfo.win; + + // Restore window cursor position and viewstate + win->w_cursor = cp_wininfo.save_w_cursor; + restore_viewstate(win, &cp_wininfo.save_viewstate); + + // Restore 'cursorline' and 'cursorcolumn' + win->w_p_cul = cp_wininfo.save_w_p_cul; + win->w_p_cuc = cp_wininfo.save_w_p_cuc; + + update_topline(win); + } + + cmdmod = cpinfo->save_cmdmod; // Restore cmdmod + p_hls = cpinfo->save_hls; // Restore 'hlsearch' + restore_search_patterns(); // Restore search patterns + win_size_restore(&cpinfo->save_view); // Restore window sizes + + ga_clear(&cpinfo->save_view); + kv_destroy(cpinfo->win_info); + kv_destroy(cpinfo->buf_info); +} + /// Show 'inccommand' preview if command is previewable. It works like this: /// 1. Store current undo information so we can revert to current state later. /// 2. Execute the preview callback with the parsed command, preview buffer number and preview @@ -2440,35 +2585,18 @@ static bool cmdpreview_may_show(CommandLineState *s) ea.line2 = lnum; } - time_t save_b_u_time_cur = curbuf->b_u_time_cur; - long save_b_u_seq_cur = curbuf->b_u_seq_cur; - u_header_T *save_b_u_newhead = curbuf->b_u_newhead; - long save_b_p_ul = curbuf->b_p_ul; - int save_b_changed = curbuf->b_changed; - int save_w_p_cul = curwin->w_p_cul; - int save_w_p_cuc = curwin->w_p_cuc; - bool save_hls = p_hls; - varnumber_T save_changedtick = buf_get_changedtick(curbuf); + CpInfo cpinfo; bool icm_split = *p_icm == 's'; // inccommand=split buf_T *cmdpreview_buf; win_T *cmdpreview_win; - cmdmod_T save_cmdmod = cmdmod; - cmdpreview = true; emsg_silent++; // Block error reporting as the command may be incomplete, // but still update v:errmsg msg_silent++; // Block messages, namely ones that prompt block_autocmds(); // Block events - garray_T save_view; - win_size_save(&save_view); // Save current window sizes - save_search_patterns(); // Save search patterns - curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes - curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights - curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights - p_hls = false; // Don't show search highlighting during live substitution - cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers - cmdmod.cmod_tab = 0; // Disable :tab modifier - cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer + + // Save current state and prepare for command preview. + cmdpreview_prepare(&cpinfo); // Open preview buffer if inccommand=split. if (!icm_split) { @@ -2476,12 +2604,14 @@ static bool cmdpreview_may_show(CommandLineState *s) } else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) { abort(); } - // Setup preview namespace if it's not already set. if (!cmdpreview_ns) { cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT); } + // Set cmdpreview state. + cmdpreview = true; + // Execute the preview callback and use its return value to determine whether to show preview or // open the preview window. The preview callback also handles doing the changes and highlights for // the preview. @@ -2500,7 +2630,7 @@ static bool cmdpreview_may_show(CommandLineState *s) cmdpreview_type = 1; } - // If preview callback is nonzero, update screen now. + // If preview callback return value is nonzero, update screen now. if (cmdpreview_type != 0) { int save_rd = RedrawingDisabled; RedrawingDisabled = 0; @@ -2512,44 +2642,13 @@ static bool cmdpreview_may_show(CommandLineState *s) if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) { cmdpreview_close_win(); } - // Clear preview highlights. - extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); - curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview + // Restore state. + cmdpreview_restore_state(&cpinfo); - if (curbuf->b_u_seq_cur != save_b_u_seq_cur) { - // Undo invisibly. This also moves the cursor! - while (curbuf->b_u_seq_cur != save_b_u_seq_cur) { - if (!u_undo_and_forget(1)) { - abort(); - } - } - // Restore newhead. It is meaningless when curhead is valid, but we must - // restore it so that undotree() is identical before/after the preview. - curbuf->b_u_newhead = save_b_u_newhead; - curbuf->b_u_time_cur = save_b_u_time_cur; - } - if (save_changedtick != buf_get_changedtick(curbuf)) { - buf_set_changedtick(curbuf, save_changedtick); - } - - cmdmod = save_cmdmod; // Restore cmdmod - p_hls = save_hls; // Restore 'hlsearch' - curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline' - curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn' - curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels' - restore_search_patterns(); // Restore search patterns - win_size_restore(&save_view); // Restore window sizes - ga_clear(&save_view); unblock_autocmds(); // Unblock events msg_silent--; // Unblock messages emsg_silent--; // Unblock error reporting - - // Restore the window "view". - curwin->w_cursor = s->is_state.save_cursor; - restore_viewstate(&s->is_state.old_viewstate); - update_topline(curwin); - redrawcmdline(); end: xfree(cmdline); @@ -2682,7 +2781,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at save_cmdline(&save_ccline); did_save_ccline = true; } else { - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); } ccline.prompt_id = last_prompt_id++; ccline.cmdprompt = (char_u *)prompt; @@ -3592,7 +3691,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) msg_col -= i; if (msg_col < 0) { msg_col += Columns; - --msg_row; + msg_row--; } } } @@ -3645,7 +3744,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) static void save_cmdline(struct cmdline_info *ccp) { *ccp = ccline; - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); ccline.prev_ccline = ccp; ccline.cmdbuff = NULL; // signal that ccline is not in use } @@ -3669,7 +3768,7 @@ static void restore_cmdline(struct cmdline_info *ccp) /// @returns FAIL for failure, OK otherwise static bool cmdline_paste(int regname, bool literally, bool remcr) { - char_u *arg; + char *arg; char_u *p; bool allocated; @@ -3702,7 +3801,7 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) // When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate // part of the word. - p = arg; + p = (char_u *)arg; if (p_is && regname == Ctrl_W) { char_u *w; int len; @@ -4138,9 +4237,9 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode if (findex == -1) { findex = xp->xp_numfiles; } - --findex; + findex--; } else { // mode == WILD_NEXT - ++findex; + findex++; } /* @@ -4880,7 +4979,7 @@ char_u *addstar(char_u *fname, size_t len, int context) && vim_strchr((char *)retval, '`') == NULL) { retval[len++] = '*'; } else if (len > 0 && retval[len - 1] == '$') { - --len; + len--; } retval[len] = NUL; } @@ -5028,58 +5127,6 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char *** return EXPAND_OK; } -// Cleanup matches for help tags: -// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first -// tag matches it. Otherwise remove "@en" if "en" is the only language. -static void cleanup_help_tags(int num_file, char **file) -{ - char_u buf[4]; - char_u *p = buf; - - if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { - *p++ = '@'; - *p++ = p_hlg[0]; - *p++ = p_hlg[1]; - } - *p = NUL; - - for (int i = 0; i < num_file; i++) { - int len = (int)STRLEN(file[i]) - 3; - if (len <= 0) { - continue; - } - if (STRCMP(file[i] + len, "@en") == 0) { - // Sorting on priority means the same item in another language may - // be anywhere. Search all items for a match up to the "@en". - int j; - for (j = 0; j < num_file; j++) { - if (j != i - && (int)STRLEN(file[j]) == len + 3 - && STRNCMP(file[i], file[j], len + 1) == 0) { - break; - } - } - if (j == num_file) { - // item only exists with @en, remove it - file[i][len] = NUL; - } - } - } - - if (*buf != NUL) { - for (int i = 0; i < num_file; i++) { - int len = (int)STRLEN(file[i]) - 3; - if (len <= 0) { - continue; - } - if (STRCMP(file[i] + len, buf) == 0) { - // remove the default language - file[i][len] = NUL; - } - } - } -} - typedef char *(*ExpandFunc)(expand_T *, int); /// Do the expansion based on xp->xp_context and "pat". @@ -5289,8 +5336,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f { EXPAND_SYNTAX, get_syntax_name, true, true }, { EXPAND_SYNTIME, get_syntime_arg, true, true }, { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, - { EXPAND_EVENTS, expand_get_event_name, true, true }, - { EXPAND_AUGROUP, expand_get_augroup_name, true, true }, + { EXPAND_EVENTS, expand_get_event_name, true, false }, + { EXPAND_AUGROUP, expand_get_augroup_name, true, false }, { EXPAND_CSCOPE, get_cscope_name, true, true }, { EXPAND_SIGN, get_sign_name, true, true }, { EXPAND_PROFILE, get_profile_name, true, true }, @@ -5521,7 +5568,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl } } if (*e != NUL) { - ++e; + e++; } } *file = ga.ga_data; @@ -5674,163 +5721,9 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) return OK; } -/// Expand color scheme, compiler or filetype names. -/// Search from 'runtimepath': -/// 'runtimepath'/{dirnames}/{pat}.vim -/// When "flags" has DIP_START: search also from 'start' of 'packpath': -/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim -/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath': -/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim -/// When "flags" has DIP_LUA: search also performed for .lua files -/// "dirnames" is an array with one or more directory names. -static int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirnames[]) -{ - *num_file = 0; - *file = NULL; - size_t pat_len = STRLEN(pat); - - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 10); - - // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic. - for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 7; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat); - globpath(p_rtp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat); - globpath(p_rtp, s, &ga, 0); - } - xfree(s); - } - - if (flags & DIP_START) { - for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 22; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } - xfree(s); - } - - for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 22; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } - xfree(s); - } - } - - if (flags & DIP_OPT) { - for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 20; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } - xfree(s); - } - - for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 20; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } - xfree(s); - } - } - - for (int i = 0; i < ga.ga_len; i++) { - char_u *match = ((char_u **)ga.ga_data)[i]; - char_u *s = match; - char_u *e = s + STRLEN(s); - if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0 - || ((flags & DIP_LUA) - && STRNICMP(e - 4, ".lua", 4) == 0))) { - e -= 4; - for (s = e; s > match; MB_PTR_BACK(match, s)) { - if (vim_ispathsep(*s)) { - break; - } - } - s++; - *e = NUL; - assert((e - s) + 1 >= 0); - memmove(match, s, (size_t)(e - s) + 1); - } - } - - if (GA_EMPTY(&ga)) { - return FAIL; - } - - /* Sort and remove duplicates which can happen when specifying multiple - * directories in dirnames. */ - ga_remove_duplicate_strings(&ga); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -/// Expand loadplugin names: -/// 'packpath'/pack/ * /opt/{pat} -static int ExpandPackAddDir(char_u *pat, int *num_file, char ***file) -{ - garray_T ga; - - *num_file = 0; - *file = NULL; - size_t pat_len = STRLEN(pat); - ga_init(&ga, (int)sizeof(char *), 10); - - size_t buflen = pat_len + 26; - char_u *s = xmalloc(buflen); - snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT - globpath(p_pp, s, &ga, 0); - snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT - globpath(p_pp, s, &ga, 0); - xfree(s); - - for (int i = 0; i < ga.ga_len; i++) { - char_u *match = ((char_u **)ga.ga_data)[i]; - s = (char_u *)path_tail((char *)match); - memmove(match, s, STRLEN(s) + 1); - } - - if (GA_EMPTY(&ga)) { - return FAIL; - } - - // Sort and remove duplicates which can happen when specifying multiple - // directories in dirnames. - ga_remove_duplicate_strings(&ga); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - /// Expand `file` for all comma-separated directories in `path`. /// Adds matches to `ga`. -void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) +void globpath(char *path, char_u *file, garray_T *ga, int expand_options) { expand_T xpc; ExpandInit(&xpc); @@ -5841,7 +5734,7 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) // Loop over all entries in {path}. while (*path != NUL) { // Copy one item of the path to buf[] and concatenate the file name. - copy_option_part((char **)&path, (char *)buf, MAXPATHL, ","); + copy_option_part(&path, (char *)buf, MAXPATHL, ","); if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { add_pathsep((char *)buf); STRCAT(buf, file); // NOLINT @@ -5868,309 +5761,6 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) xfree(buf); } -/********************************* -* Command line history stuff * -*********************************/ - -/// Translate a history character to the associated type number -static HistoryType hist_char2type(const int c) - FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT -{ - switch (c) { - case ':': - return HIST_CMD; - case '=': - return HIST_EXPR; - case '@': - return HIST_INPUT; - case '>': - return HIST_DEBUG; - case NUL: - case '/': - case '?': - return HIST_SEARCH; - default: - return HIST_INVALID; - } - // Silence -Wreturn-type - return 0; -} - -/* - * Table of history names. - * These names are used in :history and various hist...() functions. - * It is sufficient to give the significant prefix of a history name. - */ - -static char *(history_names[]) = -{ - "cmd", - "search", - "expr", - "input", - "debug", - NULL -}; - -/* - * Function given to ExpandGeneric() to obtain the possible first - * arguments of the ":history command. - */ -static char *get_history_arg(expand_T *xp, int idx) -{ - static char_u compl[2] = { NUL, NUL }; - char *short_names = ":=@>?/"; - int short_names_count = (int)STRLEN(short_names); - int history_name_count = ARRAY_SIZE(history_names) - 1; - - if (idx < short_names_count) { - compl[0] = (char_u)short_names[idx]; - return (char *)compl; - } - if (idx < short_names_count + history_name_count) { - return history_names[idx - short_names_count]; - } - if (idx == short_names_count + history_name_count) { - return "all"; - } - return NULL; -} - -/// Initialize command line history. -/// Also used to re-allocate history tables when size changes. -void init_history(void) -{ - assert(p_hi >= 0 && p_hi <= INT_MAX); - int newlen = (int)p_hi; - int oldlen = hislen; - - // If history tables size changed, reallocate them. - // Tables are circular arrays (current position marked by hisidx[type]). - // On copying them to the new arrays, we take the chance to reorder them. - if (newlen != oldlen) { - for (int type = 0; type < HIST_COUNT; type++) { - histentry_T *temp = (newlen - ? xmalloc((size_t)newlen * sizeof(*temp)) - : NULL); - - int j = hisidx[type]; - if (j >= 0) { - // old array gets partitioned this way: - // [0 , i1 ) --> newest entries to be deleted - // [i1 , i1 + l1) --> newest entries to be copied - // [i1 + l1 , i2 ) --> oldest entries to be deleted - // [i2 , i2 + l2) --> oldest entries to be copied - int l1 = MIN(j + 1, newlen); // how many newest to copy - int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy - int i1 = j + 1 - l1; // copy newest from here - int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here - - // copy as much entries as they fit to new table, reordering them - if (newlen) { - // copy oldest entries - memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp)); - // copy newest entries - memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp)); - } - - // delete entries that don't fit in newlen, if any - for (int i = 0; i < i1; i++) { - hist_free_entry(history[type] + i); - } - for (int i = i1 + l1; i < i2; i++) { - hist_free_entry(history[type] + i); - } - } - - // clear remaining space, if any - int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries - if (newlen) { - memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp)); - } - - hisidx[type] = l3 - 1; - xfree(history[type]); - history[type] = temp; - } - hislen = newlen; - } -} - -static inline void hist_free_entry(histentry_T *hisptr) - FUNC_ATTR_NONNULL_ALL -{ - xfree(hisptr->hisstr); - tv_list_unref(hisptr->additional_elements); - clear_hist_entry(hisptr); -} - -static inline void clear_hist_entry(histentry_T *hisptr) - FUNC_ATTR_NONNULL_ALL -{ - memset(hisptr, 0, sizeof(*hisptr)); -} - -/// Check if command line 'str' is already in history. -/// If 'move_to_front' is TRUE, matching entry is moved to end of history. -/// -/// @param move_to_front Move the entry to the front if it exists -static int in_history(int type, char_u *str, int move_to_front, int sep) -{ - int i; - int last_i = -1; - char_u *p; - - if (hisidx[type] < 0) { - return FALSE; - } - i = hisidx[type]; - do { - if (history[type][i].hisstr == NULL) { - return FALSE; - } - - /* For search history, check that the separator character matches as - * well. */ - p = history[type][i].hisstr; - if (STRCMP(str, p) == 0 - && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { - if (!move_to_front) { - return TRUE; - } - last_i = i; - break; - } - if (--i < 0) { - i = hislen - 1; - } - } while (i != hisidx[type]); - - if (last_i >= 0) { - list_T *const list = history[type][i].additional_elements; - str = history[type][i].hisstr; - while (i != hisidx[type]) { - if (++i >= hislen) { - i = 0; - } - history[type][last_i] = history[type][i]; - last_i = i; - } - tv_list_unref(list); - history[type][i].hisnum = ++hisnum[type]; - history[type][i].hisstr = str; - history[type][i].timestamp = os_time(); - history[type][i].additional_elements = NULL; - return true; - } - return false; -} - -/// Convert history name to its HIST_ equivalent -/// -/// Names are taken from the table above. When `name` is empty returns currently -/// active history or HIST_DEFAULT, depending on `return_default` argument. -/// -/// @param[in] name Converted name. -/// @param[in] len Name length. -/// @param[in] return_default Determines whether HIST_DEFAULT should be -/// returned or value based on `ccline.cmdfirstc`. -/// -/// @return Any value from HistoryType enum, including HIST_INVALID. May not -/// return HIST_DEFAULT unless return_default is true. -HistoryType get_histtype(const char *const name, const size_t len, const bool return_default) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - // No argument: use current history. - if (len == 0) { - return return_default ? HIST_DEFAULT : hist_char2type(ccline.cmdfirstc); - } - - for (HistoryType i = 0; history_names[i] != NULL; i++) { - if (STRNICMP(name, history_names[i], len) == 0) { - return i; - } - } - - if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) { - return hist_char2type(name[0]); - } - - return HIST_INVALID; -} - -static int last_maptick = -1; // last seen maptick - -/// Add the given string to the given history. If the string is already in the -/// history then it is moved to the front. "histype" may be one of the HIST_ -/// values. -/// -/// @parma in_map consider maptick when inside a mapping -/// @param sep separator character used (search hist) -void add_to_history(int histype, char_u *new_entry, int in_map, int sep) -{ - histentry_T *hisptr; - - if (hislen == 0 || histype == HIST_INVALID) { // no history - return; - } - assert(histype != HIST_DEFAULT); - - if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) { - return; - } - - /* - * Searches inside the same mapping overwrite each other, so that only - * the last line is kept. Be careful not to remove a line that was moved - * down, only lines that were added. - */ - if (histype == HIST_SEARCH && in_map) { - if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) { - // Current line is from the same mapping, remove it - hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; - hist_free_entry(hisptr); - --hisnum[histype]; - if (--hisidx[HIST_SEARCH] < 0) { - hisidx[HIST_SEARCH] = hislen - 1; - } - } - last_maptick = -1; - } - if (!in_history(histype, new_entry, true, sep)) { - if (++hisidx[histype] == hislen) { - hisidx[histype] = 0; - } - hisptr = &history[histype][hisidx[histype]]; - hist_free_entry(hisptr); - - // Store the separator after the NUL of the string. - size_t len = STRLEN(new_entry); - hisptr->hisstr = vim_strnsave(new_entry, len + 2); - hisptr->timestamp = os_time(); - hisptr->additional_elements = NULL; - hisptr->hisstr[len + 1] = (char_u)sep; - - hisptr->hisnum = ++hisnum[histype]; - if (histype == HIST_SEARCH && in_map) { - last_maptick = maptick; - } - } -} - -/* - * Get identifier of newest history entry. - * "histype" may be one of the HIST_ values. - */ -int get_history_idx(int histype) -{ - if (hislen == 0 || histype < 0 || histype >= HIST_COUNT - || hisidx[histype] < 0) { - return -1; - } - - return history[histype][hisidx[histype]].hisnum; -} - /// Get pointer to the command line info to use. save_cmdline() may clear /// ccline and put the previous value in ccline.prev_ccline. static struct cmdline_info *get_ccline_ptr(void) @@ -6292,168 +5882,10 @@ int get_cmdline_type(void) return p->cmdfirstc; } -/* - * Calculate history index from a number: - * num > 0: seen as identifying number of a history entry - * num < 0: relative position in history wrt newest entry - * "histype" may be one of the HIST_ values. - */ -static int calc_hist_idx(int histype, int num) -{ - int i; - histentry_T *hist; - int wrapped = FALSE; - - if (hislen == 0 || histype < 0 || histype >= HIST_COUNT - || (i = hisidx[histype]) < 0 || num == 0) { - return -1; - } - - hist = history[histype]; - if (num > 0) { - while (hist[i].hisnum > num) { - if (--i < 0) { - if (wrapped) { - break; - } - i += hislen; - wrapped = TRUE; - } - } - if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) { - return i; - } - } else if (-num <= hislen) { - i += num + 1; - if (i < 0) { - i += hislen; - } - if (hist[i].hisstr != NULL) { - return i; - } - } - return -1; -} - -/* - * Get a history entry by its index. - * "histype" may be one of the HIST_ values. - */ -char_u *get_history_entry(int histype, int idx) -{ - idx = calc_hist_idx(histype, idx); - if (idx >= 0) { - return history[histype][idx].hisstr; - } else { - return (char_u *)""; - } -} - -/// Clear all entries in a history -/// -/// @param[in] histype One of the HIST_ values. -/// -/// @return OK if there was something to clean and histype was one of HIST_ -/// values, FAIL otherwise. -int clr_history(const int histype) -{ - if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) { - histentry_T *hisptr = history[histype]; - for (int i = hislen; i--; hisptr++) { - hist_free_entry(hisptr); - } - hisidx[histype] = -1; // mark history as cleared - hisnum[histype] = 0; // reset identifier counter - return OK; - } - return FAIL; -} - -/* - * Remove all entries matching {str} from a history. - * "histype" may be one of the HIST_ values. - */ -int del_history_entry(int histype, char_u *str) +/// Return the first character of the current command line. +int get_cmdline_firstc(void) { - regmatch_T regmatch; - histentry_T *hisptr; - int idx; - int i; - int last; - bool found = false; - - regmatch.regprog = NULL; - regmatch.rm_ic = FALSE; // always match case - if (hislen != 0 - && histype >= 0 - && histype < HIST_COUNT - && *str != NUL - && (idx = hisidx[histype]) >= 0 - && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING)) - != NULL) { - i = last = idx; - do { - hisptr = &history[histype][i]; - if (hisptr->hisstr == NULL) { - break; - } - if (vim_regexec(®match, (char *)hisptr->hisstr, (colnr_T)0)) { - found = true; - hist_free_entry(hisptr); - } else { - if (i != last) { - history[histype][last] = *hisptr; - clear_hist_entry(hisptr); - } - if (--last < 0) { - last += hislen; - } - } - if (--i < 0) { - i += hislen; - } - } while (i != idx); - if (history[histype][idx].hisstr == NULL) { - hisidx[histype] = -1; - } - } - vim_regfree(regmatch.regprog); - return found; -} - -/* - * Remove an indexed entry from a history. - * "histype" may be one of the HIST_ values. - */ -int del_history_idx(int histype, int idx) -{ - int i, j; - - i = calc_hist_idx(histype, idx); - if (i < 0) { - return FALSE; - } - idx = hisidx[histype]; - hist_free_entry(&history[histype][i]); - - /* When deleting the last added search string in a mapping, reset - * last_maptick, so that the last added search string isn't deleted again. - */ - if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) { - last_maptick = -1; - } - - while (i != idx) { - j = (i + 1) % hislen; - history[histype][i] = history[histype][j]; - i = j; - } - clear_hist_entry(&history[histype][idx]); - if (--i < 0) { - i += hislen; - } - hisidx[histype] = i; - return TRUE; + return ccline.cmdfirstc; } /// Get indices that specify a range within a list (not a range of text lines @@ -6464,26 +5896,26 @@ int del_history_idx(int histype, int idx) /// @param num2 to /// /// @return OK if parsed successfully, otherwise FAIL. -int get_list_range(char_u **str, int *num1, int *num2) +int get_list_range(char **str, int *num1, int *num2) { int len; int first = false; varnumber_T num; - *str = (char_u *)skipwhite((char *)(*str)); + *str = skipwhite((*str)); if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range - vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false); + vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false); *str += len; *num1 = (int)num; first = true; } - *str = (char_u *)skipwhite((char *)(*str)); + *str = skipwhite((*str)); if (**str == ',') { // parse "to" part of range - *str = (char_u *)skipwhite((char *)(*str) + 1); - vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false); + *str = skipwhite((*str) + 1); + vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false); if (len > 0) { *num2 = (int)num; - *str = (char_u *)skipwhite((char *)(*str) + len); + *str = skipwhite((*str) + len); } else if (!first) { // no number given at all return FAIL; } @@ -6493,118 +5925,9 @@ int get_list_range(char_u **str, int *num1, int *num2) return OK; } -/* - * :history command - print a history - */ -void ex_history(exarg_T *eap) -{ - histentry_T *hist; - int histype1 = HIST_CMD; - int histype2 = HIST_CMD; - int hisidx1 = 1; - int hisidx2 = -1; - int idx; - int i, j, k; - char_u *end; - char_u *arg = (char_u *)eap->arg; - - if (hislen == 0) { - msg(_("'history' option is zero")); - return; - } - - if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) { - end = arg; - while (ASCII_ISALPHA(*end) - || vim_strchr(":=@>/?", *end) != NULL) { - end++; - } - histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false); - if (histype1 == HIST_INVALID) { - if (STRNICMP(arg, "all", end - arg) == 0) { - histype1 = 0; - histype2 = HIST_COUNT - 1; - } else { - semsg(_(e_trailing_arg), arg); - return; - } - } else { - histype2 = histype1; - } - } else { - end = arg; - } - if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { - semsg(_(e_trailing_arg), end); - return; - } - - for (; !got_int && histype1 <= histype2; ++histype1) { - STRCPY(IObuff, "\n # "); - assert(history_names[histype1] != NULL); - STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); - msg_puts_title((char *)IObuff); - idx = hisidx[histype1]; - hist = history[histype1]; - j = hisidx1; - k = hisidx2; - if (j < 0) { - j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum; - } - if (k < 0) { - k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum; - } - if (idx >= 0 && j <= k) { - for (i = idx + 1; !got_int; ++i) { - if (i == hislen) { - i = 0; - } - if (hist[i].hisstr != NULL - && hist[i].hisnum >= j && hist[i].hisnum <= k) { - msg_putchar('\n'); - snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', - hist[i].hisnum); - if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) { - trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff), - Columns - 10, IOSIZE - (int)STRLEN(IObuff)); - } else { - STRCAT(IObuff, hist[i].hisstr); - } - msg_outtrans((char *)IObuff); - ui_flush(); - } - if (i == idx) { - break; - } - } - } - } -} - -/// Translate a history type number to the associated character -int hist_type2char(int type) - FUNC_ATTR_CONST -{ - switch (type) { - case HIST_CMD: - return ':'; - case HIST_SEARCH: - return '/'; - case HIST_EXPR: - return '='; - case HIST_INPUT: - return '@'; - case HIST_DEBUG: - return '>'; - default: - abort(); - } - return NUL; -} - void cmdline_init(void) { - memset(&ccline, 0, sizeof(struct cmdline_info)); + CLEAR_FIELD(ccline); } /// Open a window on the current command line and history. Allow editing in @@ -6688,18 +6011,18 @@ static int open_cmdwin(void) // Fill the buffer with the history. init_history(); - if (hislen > 0 && histtype != HIST_INVALID) { - i = hisidx[histtype]; + if (get_hislen() > 0 && histtype != HIST_INVALID) { + i = *get_hisidx(histtype); if (i >= 0) { lnum = 0; do { - if (++i == hislen) { + if (++i == get_hislen()) { i = 0; } - if (history[histtype][i].hisstr != NULL) { - ml_append(lnum++, (char *)history[histtype][i].hisstr, (colnr_T)0, false); + if (get_histentry(histtype)[i].hisstr != NULL) { + ml_append(lnum++, (char *)get_histentry(histtype)[i].hisstr, (colnr_T)0, false); } - } while (i != hisidx[histtype]); + } while (i != *get_hisidx(histtype)); } } @@ -6904,90 +6227,6 @@ char *script_get(exarg_T *const eap, size_t *const lenp) return (char *)ga.ga_data; } -/// Iterate over history items -/// -/// @warning No history-editing functions must be run while iteration is in -/// progress. -/// -/// @param[in] iter Pointer to the last history entry. -/// @param[in] history_type Type of the history (HIST_*). Ignored if iter -/// parameter is not NULL. -/// @param[in] zero If true then zero (but not free) returned items. -/// -/// @warning When using this parameter user is -/// responsible for calling clr_history() -/// itself after iteration is over. If -/// clr_history() is not called behaviour is -/// undefined. No functions that work with -/// history must be called during iteration -/// in this case. -/// @param[out] hist Next history entry. -/// -/// @return Pointer used in next iteration or NULL to indicate that iteration -/// was finished. -const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero, - histentry_T *const hist) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4) -{ - *hist = (histentry_T) { - .hisstr = NULL - }; - if (hisidx[history_type] == -1) { - return NULL; - } - histentry_T *const hstart = &(history[history_type][0]); - histentry_T *const hlast = ( - &(history[history_type][hisidx[history_type]])); - const histentry_T *const hend = &(history[history_type][hislen - 1]); - histentry_T *hiter; - if (iter == NULL) { - histentry_T *hfirst = hlast; - do { - hfirst++; - if (hfirst > hend) { - hfirst = hstart; - } - if (hfirst->hisstr != NULL) { - break; - } - } while (hfirst != hlast); - hiter = hfirst; - } else { - hiter = (histentry_T *)iter; - } - if (hiter == NULL) { - return NULL; - } - *hist = *hiter; - if (zero) { - memset(hiter, 0, sizeof(*hiter)); - } - if (hiter == hlast) { - return NULL; - } - hiter++; - return (const void *)((hiter > hend) ? hstart : hiter); -} - -/// Get array of history items -/// -/// @param[in] history_type Type of the history to get array for. -/// @param[out] new_hisidx Location where last index in the new array should -/// be saved. -/// @param[out] new_hisnum Location where last history number in the new -/// history should be saved. -/// -/// @return Pointer to the array or NULL. -histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx, - int **const new_hisnum) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - init_history(); - *new_hisidx = &(hisidx[history_type]); - *new_hisnum = &(hisnum[history_type]); - return history[history_type]; -} - static void set_search_match(pos_T *t) { // First move cursor to end of match, then to the start. This diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 1cc6faf87c..2f6929924d 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -3,8 +3,6 @@ #include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds_defs.h" -#include "nvim/os/time.h" #include "nvim/regexp_defs.h" // Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. @@ -39,30 +37,8 @@ #define VSE_SHELL 1 ///< escape for a shell command #define VSE_BUFFER 2 ///< escape for a ":buffer" command -/// Present history tables -typedef enum { - HIST_DEFAULT = -2, ///< Default (current) history. - HIST_INVALID = -1, ///< Unknown history. - HIST_CMD = 0, ///< Colon commands. - HIST_SEARCH, ///< Search commands. - HIST_EXPR, ///< Expressions (e.g. from entering = register). - HIST_INPUT, ///< input() lines. - HIST_DEBUG, ///< Debug commands. -} HistoryType; - -/// Number of history tables -#define HIST_COUNT (HIST_DEBUG + 1) - typedef char *(*CompleteListItemGetter)(expand_T *, int); -/// History entry definition -typedef struct hist_entry { - int hisnum; ///< Entry identifier number. - char_u *hisstr; ///< Actual entry, separator char after the NUL. - Timestamp timestamp; ///< Time when entry was added. - list_T *additional_elements; ///< Additional entries from ShaDa file. -} histentry_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.h.generated.h" #endif diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 1d670afa6d..9495ae1c4b 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -13,12 +13,12 @@ #include <stdlib.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" @@ -34,6 +34,7 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/runtime.h" #include "nvim/vim.h" #include "nvim/window.h" diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index ebefd1157c..2d09e7aa71 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -50,6 +50,7 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/file_search.h" @@ -306,7 +307,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len); } if (*++path != NUL) { - ++path; + path++; } } else if (*path == NUL || !vim_isAbsName(path)) { #ifdef BACKSLASH_IN_FILENAME @@ -583,7 +584,7 @@ char_u *vim_findfile(void *search_ctx_arg) ff_stack_T *stackp = NULL; size_t len; char_u *p; - char_u *suf; + char *suf; ff_search_ctx_T *search_ctx; if (search_ctx_arg == NULL) { @@ -824,9 +825,9 @@ char_u *vim_findfile(void *search_ctx_arg) */ len = STRLEN(file_path); if (search_ctx->ffsc_tagfile) { - suf = (char_u *)""; + suf = ""; } else { - suf = curbuf->b_p_sua; + suf = (char *)curbuf->b_p_sua; } for (;;) { // if file exists and we didn't already find it @@ -891,7 +892,7 @@ char_u *vim_findfile(void *search_ctx_arg) break; } assert(MAXPATHL >= len); - copy_option_part((char **)&suf, (char *)file_path + len, MAXPATHL - len, ","); + copy_option_part(&suf, (char *)file_path + len, MAXPATHL - len, ","); } } } else { @@ -1401,11 +1402,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first char_u *path_option, int find_what, char_u *rel_fname, char_u *suffixes) { - static char_u *dir; - static int did_findfile_init = FALSE; + static char *dir; + static int did_findfile_init = false; char_u save_char; char_u *file_name = NULL; - char_u *buf = NULL; + char *buf = NULL; int rel_to_curdir; if (rel_fname != NULL && path_with_url((const char *)rel_fname)) { @@ -1482,7 +1483,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first /* When the file doesn't exist, try adding parts of * 'suffixesadd'. */ - buf = suffixes; + buf = (char *)suffixes; for (;;) { if ( (os_path_exists(NameBuff) @@ -1496,7 +1497,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } assert(MAXPATHL >= l); - copy_option_part((char **)&buf, (char *)NameBuff + l, MAXPATHL - l, ","); + copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ","); } } } @@ -1509,8 +1510,8 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first if (first == TRUE) { // vim_findfile_free_visited can handle a possible NULL pointer vim_findfile_free_visited(fdip_search_ctx); - dir = path_option; - did_findfile_init = FALSE; + dir = (char *)path_option; + did_findfile_init = false; } for (;;) { @@ -1536,13 +1537,13 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first // copy next path buf[0] = 0; - copy_option_part((char **)&dir, (char *)buf, MAXPATHL, " ,"); + copy_option_part(&dir, buf, MAXPATHL, " ,"); // get the stopdir string - r_ptr = vim_findfile_stopdir(buf); - fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find, - r_ptr, 100, FALSE, find_what, - fdip_search_ctx, FALSE, rel_fname); + r_ptr = vim_findfile_stopdir((char_u *)buf); + fdip_search_ctx = vim_findfile_init((char_u *)buf, ff_file_to_find, + r_ptr, 100, false, find_what, + fdip_search_ctx, false, rel_fname); if (fdip_search_ctx != NULL) { did_findfile_init = TRUE; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index b98984017b..d29a0f0a08 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -18,7 +18,9 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" @@ -48,7 +50,6 @@ #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sha256.h" #include "nvim/shada.h" @@ -631,7 +632,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } if (aborting()) { // autocmds may abort script processing - --no_wait_return; + no_wait_return--; msg_scroll = msg_save; curbuf->b_p_ro = TRUE; // must use "w!" now return FAIL; @@ -1196,11 +1197,11 @@ retry: } // Deal with a bad byte and continue with the next. - ++fromp; - --from_size; + fromp++; + from_size--; if (bad_char_behavior == BAD_KEEP) { *top++ = *(fromp - 1); - --to_size; + to_size--; } else if (bad_char_behavior != BAD_DROP) { *top++ = (char)bad_char_behavior; to_size--; @@ -1238,7 +1239,7 @@ retry: // Check for a trailing incomplete UTF-8 sequence tail = ptr + size - 1; while (tail > ptr && (*tail & 0xc0) == 0x80) { - --tail; + tail--; } if (tail + utf_byte2len(*tail) <= ptr + size) { tail = NULL; @@ -1556,7 +1557,7 @@ rewind_retry: * Keep it fast! */ if (fileformat == EOL_MAC) { - --ptr; + ptr--; while (++ptr, --size >= 0) { // catch most common case first if ((c = *ptr) != NUL && c != CAR && c != NL) { @@ -1577,20 +1578,20 @@ rewind_retry: if (read_undo_file) { sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); } - ++lnum; + lnum++; if (--read_count == 0) { error = true; // break loop line_start = ptr; // nothing left to write break; } } else { - --skip_count; + skip_count--; } line_start = ptr + 1; } } } else { - --ptr; + ptr--; while (++ptr, --size >= 0) { if ((c = *ptr) != NUL && c != NL) { // catch most common case continue; @@ -1633,14 +1634,14 @@ rewind_retry: if (read_undo_file) { sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); } - ++lnum; + lnum++; if (--read_count == 0) { error = true; // break loop line_start = ptr; // nothing left to write break; } } else { - --skip_count; + skip_count--; } line_start = ptr + 1; } @@ -1999,7 +2000,7 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) lnum = curbuf->b_ml.ml_line_count - linecnt + 1; for (s = p; s < endp; ++s) { if (*s == '\n') { - ++lnum; + lnum++; } } return lnum; @@ -2477,7 +2478,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } else { // less lines end -= old_line_count - buf->b_ml.ml_line_count; if (end < start) { - --no_wait_return; + no_wait_return--; msg_scroll = msg_save; emsg(_("E204: Autocommand changed number of lines in unexpected way")); return FAIL; @@ -4819,8 +4820,8 @@ int check_timestamps(int focus) } } } - --no_wait_return; - need_check_timestamps = FALSE; + no_wait_return--; + need_check_timestamps = false; if (need_wait_return && didit == 2) { // make sure msg isn't overwritten msg_puts("\n"); @@ -5122,7 +5123,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) // file, not reset the syntax highlighting, clear marks, diff status, etc. // Force the fileformat and encoding to be the same. if (reload_options) { - memset(&ea, 0, sizeof(ea)); + CLEAR_FIELD(ea); } else { prep_exarg(&ea, buf); } @@ -5580,14 +5581,14 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) char_u *regpat; char allow_dirs; bool match; - char_u *p; + char *p; tail = (char_u *)path_tail((char *)sfname); // try all patterns in 'wildignore' - p = list; + p = (char *)list; while (*p) { - copy_option_part((char **)&p, (char *)buf, ARRAY_SIZE(buf), ","); + copy_option_part(&p, (char *)buf, ARRAY_SIZE(buf), ","); regpat = (char_u *)file_pat_to_reg_pat((char *)buf, NULL, &allow_dirs, false); if (regpat == NULL) { break; @@ -5680,7 +5681,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '.'; reg_pat[i++] = '*'; while (p[1] == '*') { // "**" matches like "*" - ++p; + p++; } break; case '.': @@ -5768,7 +5769,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs case '}': reg_pat[i++] = '\\'; reg_pat[i++] = ')'; - --nested; + nested--; break; case ',': if (nested) { diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index 62d8b6142e..650977deac 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -1,7 +1,6 @@ #ifndef NVIM_FILEIO_H #define NVIM_FILEIO_H -#include "nvim/autocmd.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/garray.h" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 8f26e03a94..8ce24fd378 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -14,6 +14,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/ex_session.h" @@ -31,7 +32,7 @@ #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/plines.h" -#include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/undo.h" @@ -247,7 +248,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp // foldLevel() {{{2 /// @return fold level at line number "lnum" in the current window. -int foldLevel(linenr_T lnum) +static int foldLevel(linenr_T lnum) { // While updating the folds lines between invalid_top and invalid_bot have // an undefined fold level. Otherwise update the folds first. @@ -862,7 +863,7 @@ int foldMoveTo(const bool updown, const int dir, const long count) if (fp - (fold_T *)gap->ga_data >= gap->ga_len) { break; } - --fp; + fp--; } else { if (fp == (fold_T *)gap->ga_data) { break; @@ -1786,13 +1787,13 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin // foldtext_cleanup() {{{2 /// Remove 'foldmarker' and 'commentstring' from "str" (in-place). -void foldtext_cleanup(char_u *str) +static void foldtext_cleanup(char_u *str) { // Ignore leading and trailing white space in 'commentstring'. char_u *cms_start = (char_u *)skipwhite((char *)curbuf->b_p_cms); size_t cms_slen = STRLEN(cms_start); while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) { - --cms_slen; + cms_slen--; } // locate "%s" in 'commentstring', use the part before and after it. @@ -1804,7 +1805,7 @@ void foldtext_cleanup(char_u *str) // exclude white space before "%s" while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) { - --cms_slen; + cms_slen--; } // skip "%s" and white space after it @@ -3190,3 +3191,119 @@ static int put_fold_open_close(FILE *fd, fold_T *fp, linenr_T off) } // }}}1 + +/// "foldclosed()" and "foldclosedend()" functions +static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + linenr_T first; + linenr_T last; + if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { + if (end) { + rettv->vval.v_number = (varnumber_T)last; + } else { + rettv->vval.v_number = (varnumber_T)first; + } + return; + } + } + rettv->vval.v_number = -1; +} + +/// "foldclosed()" function +void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + foldclosed_both(argvars, rettv, false); +} + +/// "foldclosedend()" function +void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + foldclosed_both(argvars, rettv, true); +} + +/// "foldlevel()" function +void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const linenr_T lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = foldLevel(lnum); + } +} + +/// "foldtext()" function +void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + linenr_T foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); + linenr_T foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); + char_u *dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES); + if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { + // Find first non-empty line in the fold. + linenr_T lnum; + for (lnum = foldstart; lnum < foldend; lnum++) { + if (!linewhite(lnum)) { + break; + } + } + + // Find interesting text in this line. + char_u *s = (char_u *)skipwhite((char *)ml_get(lnum)); + // skip C comment-start + if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { + s = (char_u *)skipwhite((char *)s + 2); + if (*skipwhite((char *)s) == NUL && lnum + 1 < foldend) { + s = (char_u *)skipwhite((char *)ml_get(lnum + 1)); + if (*s == '*') { + s = (char_u *)skipwhite((char *)s + 1); + } + } + } + int count = foldend - foldstart + 1; + char *txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); + size_t len = STRLEN(txt) + + STRLEN(dashes) // for %s + + 20 // for %3ld + + STRLEN(s); // concatenated + char_u *r = xmalloc(len); + snprintf((char *)r, len, txt, dashes, count); + len = STRLEN(r); + STRCAT(r, s); + // remove 'foldmarker' and 'commentstring' + foldtext_cleanup(r + len); + rettv->vval.v_string = (char *)r; + } +} + +/// "foldtextresult(lnum)" function +void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u buf[FOLD_TEXT_LEN]; + static bool entered = false; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (entered) { + return; // reject recursive use + } + entered = true; + linenr_T lnum = tv_get_lnum(argvars); + // Treat illegal types and illegal string values for {lnum} the same. + if (lnum < 0) { + lnum = 0; + } + + foldinfo_T info = fold_info(curwin, lnum); + if (info.fi_lines > 0) { + char_u *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); + if (text == buf) { + text = vim_strsave(text); + } + rettv->vval.v_string = (char *)text; + } + + entered = false; +} diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua index aa96c97bc1..36553f4649 100644 --- a/src/nvim/generators/gen_unicode_tables.lua +++ b/src/nvim/generators/gen_unicode_tables.lua @@ -12,8 +12,8 @@ -- 2 then interval applies only to first, third, fifth, … character in range. -- Fourth value is number that should be added to the codepoint to yield -- folded/lower/upper codepoint. --- 4. emoji_width and emoji_all tables: sorted lists of non-overlapping closed --- intervals of Emoji characters. emoji_width contains all the characters +-- 4. emoji_wide and emoji_all tables: sorted lists of non-overlapping closed +-- intervals of Emoji characters. emoji_wide contains all the characters -- which don't have ambiguous or double width, and emoji_all has all Emojis. if arg[1] == '--help' then print('Usage:') @@ -288,7 +288,7 @@ local build_emoji_table = function(ut_fp, emojiprops, doublewidth, ambiwidth) end ut_fp:write('};\n') - ut_fp:write('static const struct interval emoji_width[] = {\n') + ut_fp:write('static const struct interval emoji_wide[] = {\n') for _, p in ipairs(emojiwidth) do ut_fp:write(make_range(p[1], p[2])) end diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 2b2889d4d6..a3af7dc372 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,7 +15,10 @@ #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/event/loop.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" @@ -40,7 +43,6 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -1336,7 +1338,7 @@ static void closescript(void) file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) { - --curscript; + curscript--; } } @@ -1695,6 +1697,149 @@ int char_avail(void) return retval != NUL; } +/// "getchar()" and "getcharstr()" functions +static void getchar_common(typval_T *argvars, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + varnumber_T n; + bool error = false; + + no_mapping++; + allow_keys++; + for (;;) { + // Position the cursor. Needed after a message that ends in a space, + // or if event processing caused a redraw. + ui_cursor_goto(msg_row, msg_col); + + if (argvars[0].v_type == VAR_UNKNOWN) { + // getchar(): blocking wait. + // TODO(bfredl): deduplicate shared logic with state_enter ? + if (!char_avail()) { + // flush output before waiting + ui_flush(); + (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + if (!multiqueue_empty(main_loop.events)) { + state_handle_k_event(); + continue; + } + } + n = safe_vgetc(); + } else if (tv_get_number_chk(&argvars[0], &error) == 1) { + // getchar(1): only check if char avail + n = vpeekc_any(); + } else if (error || vpeekc_any() == NUL) { + // illegal argument or getchar(0) and no char avail: return zero + n = 0; + } else { + // getchar(0) and char avail() != NUL: get a character. + // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE. + n = safe_vgetc(); + } + + if (n == K_IGNORE + || n == K_MOUSEMOVE + || n == K_VER_SCROLLBAR + || n == K_HOR_SCROLLBAR) { + continue; + } + break; + } + no_mapping--; + allow_keys--; + + if (!ui_has_messages()) { + // redraw the screen after getchar() + update_screen(CLEAR); + } + + set_vim_var_nr(VV_MOUSE_WIN, 0); + set_vim_var_nr(VV_MOUSE_WINID, 0); + set_vim_var_nr(VV_MOUSE_LNUM, 0); + set_vim_var_nr(VV_MOUSE_COL, 0); + + rettv->vval.v_number = n; + if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { + char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 + int i = 0; + + // Turn a special key into three bytes, plus modifier. + if (mod_mask != 0) { + temp[i++] = K_SPECIAL; + temp[i++] = KS_MODIFIER; + temp[i++] = (char_u)mod_mask; + } + if (IS_SPECIAL(n)) { + temp[i++] = K_SPECIAL; + temp[i++] = (char_u)K_SECOND(n); + temp[i++] = K_THIRD(n); + } else { + i += utf_char2bytes((int)n, (char *)temp + i); + } + assert(i < 10); + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char *)vim_strsave(temp); + + if (is_mouse_key((int)n)) { + int row = mouse_row; + int col = mouse_col; + int grid = mouse_grid; + linenr_T lnum; + win_T *wp; + int winnr = 1; + + if (row >= 0 && col >= 0) { + // Find the window at the mouse coordinates and compute the + // text position. + win_T *const win = mouse_find_win(&grid, &row, &col); + if (win == NULL) { + return; + } + (void)mouse_comp_pos(win, &row, &col, &lnum); + for (wp = firstwin; wp != win; wp = wp->w_next) { + winnr++; + } + set_vim_var_nr(VV_MOUSE_WIN, winnr); + set_vim_var_nr(VV_MOUSE_WINID, wp->handle); + set_vim_var_nr(VV_MOUSE_LNUM, lnum); + set_vim_var_nr(VV_MOUSE_COL, col + 1); + } + } + } +} + +/// "getchar()" function +void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getchar_common(argvars, rettv); +} + +/// "getcharstr()" function +void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getchar_common(argvars, rettv); + + if (rettv->v_type == VAR_NUMBER) { + char temp[7]; // mbyte-char: 6, NUL: 1 + const varnumber_T n = rettv->vval.v_number; + int i = 0; + + if (n != 0) { + i += utf_char2bytes((int)n, (char *)temp); + } + assert(i < 7); + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(temp); + } +} + +/// "getcharmod()" function +void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = mod_mask; +} + typedef enum { map_result_fail, // failed, break loop map_result_get, // get a character from typeahead @@ -2251,7 +2396,7 @@ static int vgetorpeek(bool advance) return NUL; } - ++vgetc_busy; + vgetc_busy++; if (advance) { KeyStuffed = FALSE; @@ -2638,7 +2783,7 @@ static int vgetorpeek(bool advance) gotchars(nop_buf, 3); } - --vgetc_busy; + vgetc_busy--; return c; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 317423ffa0..954b62883e 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -6,12 +6,14 @@ #include "nvim/ascii.h" #include "nvim/event/loop.h" -#include "nvim/ex_eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_eval_defs.h" #include "nvim/iconv.h" #include "nvim/macros.h" #include "nvim/mbyte.h" -#include "nvim/menu.h" +#include "nvim/menu_defs.h" #include "nvim/os/os_defs.h" +#include "nvim/runtime.h" #include "nvim/syntax_defs.h" #include "nvim/types.h" @@ -94,9 +96,6 @@ EXTERN struct nvim_stats_s { EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen -EXTERN NS ns_hl_active INIT(= 0); // current ns that defines highlights -EXTERN bool ns_hl_changed INIT(= false); // highlight need update - // We use 64-bit file functions here, if available. E.g. ftello() returns // off_t instead of long, which helps if long is 32 bit and off_t is 64 bit. // We assume that when fseeko() is available then ftello() is too. @@ -143,6 +142,7 @@ EXTERN int vgetc_char INIT(= 0); EXTERN int cmdline_row; EXTERN bool redraw_cmdline INIT(= false); // cmdline must be redrawn +EXTERN bool redraw_mode INIT(= false); // mode must be redrawn EXTERN bool clear_cmdline INIT(= false); // cmdline must be cleared EXTERN bool mode_displayed INIT(= false); // mode is being displayed EXTERN int cmdline_star INIT(= false); // cmdline is encrypted @@ -248,9 +248,6 @@ EXTERN int lines_left INIT(= -1); // lines left for listing EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate // messages -EXTERN char *sourcing_name INIT(= NULL); // name of error message source -EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file - EXTERN int ex_nesting_level INIT(= 0); // nesting level EXTERN int debug_break_level INIT(= -1); // break below this level EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message @@ -296,7 +293,7 @@ EXTERN int force_abort INIT(= false); /// same as the "msg" field of that element, but can be identical to the "msg" /// field of a later list element, when the "emsg_severe" flag was set when the /// emsg() call was made. -EXTERN struct msglist **msg_list INIT(= NULL); +EXTERN msglist_T **msg_list INIT(= NULL); /// When set, don't convert an error to an exception. Used when displaying the /// interrupt message or reporting an exception that is still uncaught at the @@ -348,8 +345,8 @@ EXTERN bool did_source_packages INIT(= false); // provider function call EXTERN struct caller_scope { sctx_T script_ctx; - char *sourcing_name, *autocmd_fname, *autocmd_match; - linenr_T sourcing_lnum; + estack_T es_entry; + char *autocmd_fname, *autocmd_match; int autocmd_bufnr; void *funccalp; } provider_caller_scope; diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 72e85c425d..f95ef3e705 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -1,10 +1,19 @@ // 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 +// Most of the routines in this file perform screen (grid) manipulations. The +// given operation is performed physically on the screen. The corresponding +// change is also made to the internal screen image. In this way, the editor +// anticipates the effect of editing changes on the appearance of the screen. +// That way, when we call update_screen() a complete redraw isn't usually +// necessary. Another advantage is that we can keep adding code to anticipate +// screen changes, and in the meantime, everything still works. +// +// The grid_*() functions write to the screen and handle updating grid->lines[]. + #include "nvim/arabic.h" #include "nvim/grid.h" #include "nvim/highlight.h" -#include "nvim/screen.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -780,3 +789,123 @@ void grid_assign_handle(ScreenGrid *grid) grid->handle = ++last_grid_handle; } } + +/// insert lines on the screen and move the existing lines down +/// 'line_count' is the number of lines to be inserted. +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// 'col' is the column from with we start inserting. +// +/// 'row', 'col' and 'end' are relative to the start of the region. +void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) +{ + int i; + int j; + unsigned temp; + + int row_off = 0; + grid_adjust(&grid, &row_off, &col); + row += row_off; + end += row_off; + + if (line_count <= 0) { + return; + } + + // Shift line_offset[] line_count down to reflect the inserted lines. + // Clear the inserted lines. + for (i = 0; i < line_count; i++) { + if (width != grid->cols) { + // need to copy part of a line + j = end - 1 - i; + while ((j -= line_count) >= row) { + linecopy(grid, j + line_count, j, col, width); + } + j += line_count; + grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); + grid->line_wraps[j] = false; + } else { + j = end - 1 - i; + temp = (unsigned)grid->line_offset[j]; + while ((j -= line_count) >= row) { + grid->line_offset[j + line_count] = grid->line_offset[j]; + grid->line_wraps[j + line_count] = grid->line_wraps[j]; + } + grid->line_offset[j + line_count] = temp; + grid->line_wraps[j + line_count] = false; + grid_clear_line(grid, temp, grid->cols, false); + } + } + + if (!grid->throttled) { + ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0); + } +} + +/// delete lines on the screen and move lines up. +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// When scrolling region used 'off' is the offset from the top for the region. +/// 'row' and 'end' are relative to the start of the region. +void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) +{ + int j; + int i; + unsigned temp; + + int row_off = 0; + grid_adjust(&grid, &row_off, &col); + row += row_off; + end += row_off; + + if (line_count <= 0) { + return; + } + + // Now shift line_offset[] line_count up to reflect the deleted lines. + // Clear the inserted lines. + for (i = 0; i < line_count; i++) { + if (width != grid->cols) { + // need to copy part of a line + j = row + i; + while ((j += line_count) <= end - 1) { + linecopy(grid, j - line_count, j, col, width); + } + j -= line_count; + grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); + grid->line_wraps[j] = false; + } else { + // whole width, moving the line pointers is faster + j = row + i; + temp = (unsigned)grid->line_offset[j]; + while ((j += line_count) <= end - 1) { + grid->line_offset[j - line_count] = grid->line_offset[j]; + grid->line_wraps[j - line_count] = grid->line_wraps[j]; + } + grid->line_offset[j - line_count] = temp; + grid->line_wraps[j - line_count] = false; + grid_clear_line(grid, temp, grid->cols, false); + } + } + + if (!grid->throttled) { + ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0); + } +} + +static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) +{ + unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col); + unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col); + + memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T)); + memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T)); +} + +win_T *get_win_by_grid_handle(handle_T handle) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_grid_alloc.handle == handle) { + return wp; + } + } + return NULL; +} diff --git a/src/nvim/grid.h b/src/nvim/grid.h index c38748940d..6a93bf3d90 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -6,6 +6,7 @@ #include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/grid_defs.h" +#include "nvim/mbyte.h" /// By default, all windows are drawn on a single rectangular grid, represented by /// this ScreenGrid instance. In multigrid mode each window will have its own @@ -18,6 +19,9 @@ EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT); #define DEFAULT_GRID_HANDLE 1 // handle for the default_grid +/// While resizing the screen this flag is set. +EXTERN bool resizing_screen INIT(= 0); + EXTERN schar_T *linebuf_char INIT(= NULL); EXTERN sattr_T *linebuf_attr INIT(= NULL); diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index d7f7b8eb92..6a1bbcb089 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -17,12 +17,13 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/eval.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/grid.h" #include "nvim/hardcopy.h" #include "nvim/highlight_group.h" +#include "nvim/indent.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -31,7 +32,7 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -292,8 +293,8 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ char *ret = NULL; char_u *stringp; char_u *colonp; - char_u *commap; - char_u *p; + char *commap; + char *p; size_t idx = 0; // init for GCC int len; @@ -315,9 +316,9 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ ret = N_("E550: Missing colon"); break; } - commap = (char_u *)vim_strchr((char *)stringp, ','); + commap = vim_strchr((char *)stringp, ','); if (commap == NULL) { - commap = option_str + STRLEN(option_str); + commap = (char *)option_str + STRLEN(option_str); } len = (int)(colonp - stringp); @@ -333,8 +334,8 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ break; } - p = colonp + 1; - table[idx].present = TRUE; + p = (char *)colonp + 1; + table[idx].present = true; if (table[idx].hasnum) { if (!ascii_isdigit(*p)) { @@ -342,15 +343,15 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ break; } - table[idx].number = getdigits_int((char **)&p, false, 0); + table[idx].number = getdigits_int(&p, false, 0); } - table[idx].string = p; + table[idx].string = (char_u *)p; table[idx].strlen = (int)(commap - p); - stringp = commap; + stringp = (char_u *)commap; if (*stringp == ',') { - ++stringp; + stringp++; } } @@ -632,8 +633,8 @@ void ex_hardcopy(exarg_T *eap) int page_line; int jobsplit; - memset(&settings, 0, sizeof(prt_settings_T)); - settings.has_color = TRUE; + CLEAR_FIELD(settings); + settings.has_color = true; if (*eap->arg == '>') { char *errormsg = NULL; @@ -734,7 +735,7 @@ void ex_hardcopy(exarg_T *eap) prt_pos_T page_prtpos; // print position at page start int side; - memset(&page_prtpos, 0, sizeof(prt_pos_T)); + CLEAR_FIELD(page_prtpos); page_prtpos.file_line = eap->line1; prtpos = page_prtpos; @@ -837,7 +838,7 @@ void ex_hardcopy(exarg_T *eap) } } if (settings.duplex && prtpos.file_line <= eap->line2) { - ++page_count; + page_count++; } // Remember the position where the next page starts. @@ -900,7 +901,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T } // syntax highlighting stuff. if (psettings->do_syntax) { - id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, FALSE); + id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, false); if (id > 0) { id = syn_get_final_id(id); } else { @@ -1718,7 +1719,7 @@ static bool prt_open_resource(struct prt_ps_resource_S *resource) semsg(_("E624: Can't open file \"%s\""), resource->filename); return false; } - memset(prt_resfile.buffer, NUL, PRT_FILE_BUFFER_LEN); + CLEAR_FIELD(prt_resfile.buffer); // Parse first line to ensure valid resource file prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u), diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 95ae7a152c..951e72ea52 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -45,7 +45,7 @@ char hash_removed; void hash_init(hashtab_T *ht) { // This zeroes all "ht_" entries and all the "hi_key" in "ht_smallarray". - memset(ht, 0, sizeof(hashtab_T)); + CLEAR_POINTER(ht); ht->ht_array = ht->ht_smallarray; ht->ht_mask = HT_INIT_SIZE - 1; } @@ -342,11 +342,13 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) hashitem_T *oldarray = keep_smallarray ? memcpy(temparray, ht->ht_smallarray, sizeof(temparray)) : ht->ht_array; + + if (newarray_is_small) { + CLEAR_FIELD(ht->ht_smallarray); + } hashitem_T *newarray = newarray_is_small ? ht->ht_smallarray - : xmalloc(sizeof(hashitem_T) * newsize); - - memset(newarray, 0, sizeof(hashitem_T) * newsize); + : xcalloc(newsize, sizeof(hashitem_T)); // Move all the items from the old array to the new one, placing them in // the right spot. The new array won't have any removed items, thus this diff --git a/src/nvim/help.c b/src/nvim/help.c new file mode 100644 index 0000000000..172d389e74 --- /dev/null +++ b/src/nvim/help.c @@ -0,0 +1,1178 @@ +// 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 + +// help.c: functions for Vim help + +#include <stdio.h> +#include <stdlib.h> + +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/help.h" +#include "nvim/memory.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/path.h" +#include "nvim/strings.h" +#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "help.c.generated.h" +#endif + +/// ":help": open a read-only window on a help file +void ex_help(exarg_T *eap) +{ + char *arg; + char *tag; + FILE *helpfd; // file descriptor of help file + int n; + int i; + win_T *wp; + int num_matches; + char **matches; + char *p; + int empty_fnum = 0; + int alt_fnum = 0; + buf_T *buf; + int len; + char *lang; + const bool old_KeyTyped = KeyTyped; + + if (eap != NULL) { + // A ":help" command ends at the first LF, or at a '|' that is + // followed by some text. Set nextcmd to the following command. + for (arg = eap->arg; *arg; arg++) { + if (*arg == '\n' || *arg == '\r' + || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { + *arg++ = NUL; + eap->nextcmd = arg; + break; + } + } + arg = eap->arg; + + if (eap->forceit && *arg == NUL && !curbuf->b_help) { + emsg(_("E478: Don't panic!")); + return; + } + + if (eap->skip) { // not executing commands + return; + } + } else { + arg = ""; + } + + // remove trailing blanks + p = arg + STRLEN(arg) - 1; + while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') { + *p-- = NUL; + } + + // Check for a specified language + lang = check_help_lang(arg); + + // When no argument given go to the index. + if (*arg == NUL) { + arg = "help.txt"; + } + + // Check if there is a match for the argument. + n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit); + + i = 0; + if (n != FAIL && lang != NULL) { + // Find first item with the requested language. + for (i = 0; i < num_matches; i++) { + len = (int)STRLEN(matches[i]); + if (len > 3 && matches[i][len - 3] == '@' + && STRICMP(matches[i] + len - 2, lang) == 0) { + break; + } + } + } + if (i >= num_matches || n == FAIL) { + if (lang != NULL) { + semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg); + } else { + semsg(_("E149: Sorry, no help for %s"), arg); + } + if (n != FAIL) { + FreeWild(num_matches, matches); + } + return; + } + + // The first match (in the requested language) is the best match. + tag = xstrdup(matches[i]); + FreeWild(num_matches, matches); + + // Re-use an existing help window or open a new one. + // Always open a new one for ":tab help". + if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { + if (cmdmod.cmod_tab != 0) { + wp = NULL; + } else { + wp = NULL; + FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { + if (bt_help(wp2->w_buffer)) { + wp = wp2; + break; + } + } + } + if (wp != NULL && wp->w_buffer->b_nwindows > 0) { + win_enter(wp, true); + } else { + // There is no help window yet. + // Try to open the file specified by the "helpfile" option. + if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) { + smsg(_("Sorry, help file \"%s\" not found"), p_hf); + goto erret; + } + fclose(helpfd); + + // Split off help window; put it at far top if no position + // specified, the current window is vertically split and + // narrow. + n = WSP_HELP; + if (cmdmod.cmod_split == 0 && curwin->w_width != Columns + && curwin->w_width < 80) { + n |= WSP_TOP; + } + if (win_split(0, n) == FAIL) { + goto erret; + } + + if (curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + + // Open help file (do_ecmd() will set b_help flag, readfile() will + // set b_p_ro flag). + // Set the alternate file to the previously edited file. + alt_fnum = curbuf->b_fnum; + (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, + ECMD_HIDE + ECMD_SET_HELP, + NULL); // buffer is still open, don't store info + + if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { + curwin->w_alt_fnum = alt_fnum; + } + empty_fnum = curbuf->b_fnum; + } + } + + restart_edit = 0; // don't want insert mode in help file + + // Restore KeyTyped, setting 'filetype=help' may reset it. + // It is needed for do_tag top open folds under the cursor. + KeyTyped = old_KeyTyped; + + do_tag((char_u *)tag, DT_HELP, 1, false, true); + + // Delete the empty buffer if we're not using it. Careful: autocommands + // may have jumped to another window, check that the buffer is not in a + // window. + if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { + buf = buflist_findnr(empty_fnum); + if (buf != NULL && buf->b_nwindows == 0) { + wipe_buffer(buf, true); + } + } + + // keep the previous alternate file + if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum + && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { + curwin->w_alt_fnum = alt_fnum; + } + +erret: + xfree(tag); +} + +/// ":helpclose": Close one help window +void ex_helpclose(exarg_T *eap) +{ + FOR_ALL_WINDOWS_IN_TAB(win, curtab) { + if (bt_help(win->w_buffer)) { + win_close(win, false, eap->forceit); + return; + } + } +} + +/// In an argument search for a language specifiers in the form "@xx". +/// Changes the "@" to NUL if found, and returns a pointer to "xx". +/// +/// @return NULL if not found. +char *check_help_lang(char *arg) +{ + int len = (int)STRLEN(arg); + + if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) + && ASCII_ISALPHA(arg[len - 1])) { + arg[len - 3] = NUL; // remove the '@' + return arg + len - 2; + } + return NULL; +} + +/// Return a heuristic indicating how well the given string matches. The +/// smaller the number, the better the match. This is the order of priorities, +/// from best match to worst match: +/// - Match with least alphanumeric characters is better. +/// - Match with least total characters is better. +/// - Match towards the start is better. +/// - Match starting with "+" is worse (feature instead of command) +/// Assumption is made that the matched_string passed has already been found to +/// match some string for which help is requested. webb. +/// +/// @param offset offset for match +/// @param wrong_case no matching case +/// +/// @return a heuristic indicating how well the given string matches. +int help_heuristic(char *matched_string, int offset, int wrong_case) + FUNC_ATTR_PURE +{ + int num_letters; + char *p; + + num_letters = 0; + for (p = matched_string; *p; p++) { + if (ASCII_ISALNUM(*p)) { + num_letters++; + } + } + + // Multiply the number of letters by 100 to give it a much bigger + // weighting than the number of characters. + // If there only is a match while ignoring case, add 5000. + // If the match starts in the middle of a word, add 10000 to put it + // somewhere in the last half. + // If the match is more than 2 chars from the start, multiply by 200 to + // put it after matches at the start. + if (offset > 0 + && ASCII_ISALNUM(matched_string[offset]) + && ASCII_ISALNUM(matched_string[offset - 1])) { + offset += 10000; + } else if (offset > 2) { + offset *= 200; + } + if (wrong_case) { + offset += 5000; + } + // Features are less interesting than the subjects themselves, but "+" + // alone is not a feature. + if (matched_string[0] == '+' && matched_string[1] != NUL) { + offset += 100; + } + return 100 * num_letters + (int)STRLEN(matched_string) + offset; +} + +/// Compare functions for qsort() below, that checks the help heuristics number +/// that has been put after the tagname by find_tags(). +static int help_compare(const void *s1, const void *s2) +{ + char *p1; + char *p2; + + p1 = *(char **)s1 + strlen(*(char **)s1) + 1; + p2 = *(char **)s2 + strlen(*(char **)s2) + 1; + + // Compare by help heuristic number first. + int cmp = strcmp(p1, p2); + if (cmp != 0) { + return cmp; + } + + // Compare by strings as tie-breaker when same heuristic number. + return strcmp(*(char **)s1, *(char **)s2); +} + +/// Find all help tags matching "arg", sort them and return in matches[], with +/// the number of matches in num_matches. +/// The matches will be sorted with a "best" match algorithm. +/// When "keep_lang" is true try keeping the language of the current buffer. +int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang) +{ + int i; + + // Specific tags that either have a specific replacement or won't go + // through the generic rules. + static char *(except_tbl[][2]) = { + { "*", "star" }, + { "g*", "gstar" }, + { "[*", "[star" }, + { "]*", "]star" }, + { ":*", ":star" }, + { "/*", "/star" }, // NOLINT + { "/\\*", "/\\\\star" }, + { "\"*", "quotestar" }, + { "**", "starstar" }, + { "cpo-*", "cpo-star" }, + { "/\\(\\)", "/\\\\(\\\\)" }, + { "/\\%(\\)", "/\\\\%(\\\\)" }, + { "?", "?" }, + { "??", "??" }, + { ":?", ":?" }, + { "?<CR>", "?<CR>" }, + { "g?", "g?" }, + { "g?g?", "g?g?" }, + { "g??", "g??" }, + { "-?", "-?" }, + { "q?", "q?" }, + { "v_g?", "v_g?" }, + { "/\\?", "/\\\\?" }, + { "/\\z(\\)", "/\\\\z(\\\\)" }, + { "\\=", "\\\\=" }, + { ":s\\=", ":s\\\\=" }, + { "[count]", "\\[count]" }, + { "[quotex]", "\\[quotex]" }, + { "[range]", "\\[range]" }, + { ":[range]", ":\\[range]" }, + { "[pattern]", "\\[pattern]" }, + { "\\|", "\\\\bar" }, + { "\\%$", "/\\\\%\\$" }, + { "s/\\~", "s/\\\\\\~" }, + { "s/\\U", "s/\\\\U" }, + { "s/\\L", "s/\\\\L" }, + { "s/\\1", "s/\\\\1" }, + { "s/\\2", "s/\\\\2" }, + { "s/\\3", "s/\\\\3" }, + { "s/\\9", "s/\\\\9" }, + { NULL, NULL } + }; + + static const char *(expr_table[]) = { + "!=?", "!~?", "<=?", "<?", "==?", "=~?", + ">=?", ">?", "is?", "isnot?" + }; + char *d = (char *)IObuff; // assume IObuff is long enough! + d[0] = NUL; + + if (STRNICMP(arg, "expr-", 5) == 0) { + // When the string starting with "expr-" and containing '?' and matches + // the table, it is taken literally (but ~ is escaped). Otherwise '?' + // is recognized as a wildcard. + for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) { + if (STRCMP(arg + 5, expr_table[i]) == 0) { + for (int si = 0, di = 0;; si++) { + if (arg[si] == '~') { + d[di++] = '\\'; + } + d[di++] = arg[si]; + if (arg[si] == NUL) { + break; + } + } + break; + } + } + } else { + // Recognize a few exceptions to the rule. Some strings that contain + // '*'are changed to "star", otherwise '*' is recognized as a wildcard. + for (i = 0; except_tbl[i][0] != NULL; i++) { + if (STRCMP(arg, except_tbl[i][0]) == 0) { + STRCPY(d, except_tbl[i][1]); + break; + } + } + } + + if (d[0] == NUL) { // no match in table + // Replace "\S" with "/\\S", etc. Otherwise every tag is matched. + // Also replace "\%^" and "\%(", they match every tag too. + // Also "\zs", "\z1", etc. + // Also "\@<", "\@=", "\@<=", etc. + // And also "\_$" and "\_^". + if (arg[0] == '\\' + && ((arg[1] != NUL && arg[2] == NUL) + || (vim_strchr("%_z@", arg[1]) != NULL + && arg[2] != NUL))) { + vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1); + // Check for "/\\_$", should be "/\\_\$" + if (d[3] == '_' && d[4] == '$') { + STRCPY(d + 4, "\\$"); + } + } else { + // Replace: + // "[:...:]" with "\[:...:]" + // "[++...]" with "\[++...]" + // "\{" with "\\{" -- matching "} \}" + if ((arg[0] == '[' && (arg[1] == ':' + || (arg[1] == '+' && arg[2] == '+'))) + || (arg[0] == '\\' && arg[1] == '{')) { + *d++ = '\\'; + } + + // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. + if (*arg == '(' && arg[1] == '\'') { + arg++; + } + for (const char *s = arg; *s; s++) { + // Replace "|" with "bar" and '"' with "quote" to match the name of + // the tags for these commands. + // Replace "*" with ".*" and "?" with "." to match command line + // completion. + // Insert a backslash before '~', '$' and '.' to avoid their + // special meaning. + if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!? + break; + } + switch (*s) { + case '|': + STRCPY(d, "bar"); + d += 3; + continue; + case '"': + STRCPY(d, "quote"); + d += 5; + continue; + case '*': + *d++ = '.'; + break; + case '?': + *d++ = '.'; + continue; + case '$': + case '.': + case '~': + *d++ = '\\'; + break; + } + + // Replace "^x" by "CTRL-X". Don't do this for "^_" to make + // ":help i_^_CTRL-D" work. + // Insert '-' before and after "CTRL-X" when applicable. + if (*s < ' ' + || (*s == '^' && s[1] + && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) { + if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') { + *d++ = '_'; // prepend a '_' to make x_CTRL-x + } + STRCPY(d, "CTRL-"); + d += 5; + if (*s < ' ') { + *d++ = (char)(*s + '@'); + if (d[-1] == '\\') { + *d++ = '\\'; // double a backslash + } + } else { + *d++ = *++s; + } + if (s[1] != NUL && s[1] != '_') { + *d++ = '_'; // append a '_' + } + continue; + } else if (*s == '^') { // "^" or "CTRL-^" or "^_" + *d++ = '\\'; + } else if (s[0] == '\\' && s[1] != '\\' && *arg == '/' && s == arg + 1) { + // Insert a backslash before a backslash after a slash, for search + // pattern tags: "/\|" --> "/\\|". + *d++ = '\\'; + } + + // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in + // "CTRL-\_CTRL-N" + if (STRNICMP(s, "CTRL-\\_", 7) == 0) { + STRCPY(d, "CTRL-\\\\"); + d += 7; + s += 6; + } + + *d++ = *s; + + // If tag contains "({" or "([", tag terminates at the "(". + // This is for help on functions, e.g.: abs({expr}). + if (*s == '(' && (s[1] == '{' || s[1] == '[')) { + break; + } + + // If tag starts with ', toss everything after a second '. Fixes + // CTRL-] on 'option'. (would include the trailing '.'). + if (*s == '\'' && s > arg && *arg == '\'') { + break; + } + // Also '{' and '}'. Fixes CTRL-] on '{address}'. + if (*s == '}' && s > arg && *arg == '{') { + break; + } + } + *d = NUL; + + if (*IObuff == '`') { + if ((char_u *)d > IObuff + 2 && d[-1] == '`') { + // remove the backticks from `command` + memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-2] = NUL; + } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { + // remove the backticks and comma from `command`, + memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-3] = NUL; + } else if ((char_u *)d > IObuff + 4 && d[-3] == '`' + && d[-2] == '\\' && d[-1] == '.') { + // remove the backticks and dot from `command`\. + memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-4] = NUL; + } + } + } + } + + *matches = NULL; + *num_matches = 0; + int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; + if (keep_lang) { + flags |= TAG_KEEP_LANG; + } + if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK + && *num_matches > 0) { + // Sort the matches found on the heuristic number that is after the + // tag name. + qsort((void *)(*matches), (size_t)(*num_matches), + sizeof(char_u *), help_compare); + // Delete more than TAG_MANY to reduce the size of the listing. + while (*num_matches > TAG_MANY) { + xfree((*matches)[--*num_matches]); + } + } + return OK; +} + +/// Cleanup matches for help tags: +/// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first +/// tag matches it. Otherwise remove "@en" if "en" is the only language. +void cleanup_help_tags(int num_file, char **file) +{ + char_u buf[4]; + char_u *p = buf; + + if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { + *p++ = '@'; + *p++ = p_hlg[0]; + *p++ = p_hlg[1]; + } + *p = NUL; + + for (int i = 0; i < num_file; i++) { + int len = (int)STRLEN(file[i]) - 3; + if (len <= 0) { + continue; + } + if (STRCMP(file[i] + len, "@en") == 0) { + // Sorting on priority means the same item in another language may + // be anywhere. Search all items for a match up to the "@en". + int j; + for (j = 0; j < num_file; j++) { + if (j != i + && (int)STRLEN(file[j]) == len + 3 + && STRNCMP(file[i], file[j], len + 1) == 0) { + break; + } + } + if (j == num_file) { + // item only exists with @en, remove it + file[i][len] = NUL; + } + } + } + + if (*buf != NUL) { + for (int i = 0; i < num_file; i++) { + int len = (int)STRLEN(file[i]) - 3; + if (len <= 0) { + continue; + } + if (STRCMP(file[i] + len, buf) == 0) { + // remove the default language + file[i][len] = NUL; + } + } + } +} + +/// Called when starting to edit a buffer for a help file. +void prepare_help_buffer(void) +{ + curbuf->b_help = true; + set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0); + + // Always set these options after jumping to a help tag, because the + // user may have an autocommand that gets in the way. + // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and + // latin1 word characters (for translated help files). + // Only set it when needed, buf_init_chartab() is some work. + char *p = "!-~,^*,^|,^\",192-255"; + if (STRCMP(curbuf->b_p_isk, p) != 0) { + set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); + check_buf_options(curbuf); + (void)buf_init_chartab(curbuf, false); + } + + // Don't use the global foldmethod. + set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0); + + curbuf->b_p_ts = 8; // 'tabstop' is 8. + curwin->w_p_list = false; // No list mode. + + curbuf->b_p_ma = false; // Not modifiable. + curbuf->b_p_bin = false; // Reset 'bin' before reading file. + curwin->w_p_nu = 0; // No line numbers. + curwin->w_p_rnu = 0; // No relative line numbers. + RESET_BINDING(curwin); // No scroll or cursor binding. + curwin->w_p_arab = false; // No arabic mode. + curwin->w_p_rl = false; // Help window is left-to-right. + curwin->w_p_fen = false; // No folding in the help window. + curwin->w_p_diff = false; // No 'diff'. + curwin->w_p_spell = false; // No spell checking. + + set_buflisted(false); +} + +/// After reading a help file: May cleanup a help buffer when syntax +/// highlighting is not used. +void fix_help_buffer(void) +{ + linenr_T lnum; + char *line; + bool in_example = false; + + // Set filetype to "help". + if (STRCMP(curbuf->b_p_ft, "help") != 0) { + curbuf->b_ro_locked++; + set_option_value("ft", 0L, "help", OPT_LOCAL); + curbuf->b_ro_locked--; + } + + if (!syntax_present(curwin)) { + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { + line = (char *)ml_get_buf(curbuf, lnum, false); + const size_t len = STRLEN(line); + if (in_example && len > 0 && !ascii_iswhite(line[0])) { + // End of example: non-white or '<' in first column. + if (line[0] == '<') { + // blank-out a '<' in the first column + line = (char *)ml_get_buf(curbuf, lnum, true); + line[0] = ' '; + } + in_example = false; + } + if (!in_example && len > 0) { + if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { + // blank-out a '>' in the last column (start of example) + line = (char *)ml_get_buf(curbuf, lnum, true); + line[len - 1] = ' '; + in_example = true; + } else if (line[len - 1] == '~') { + // blank-out a '~' at the end of line (header marker) + line = (char *)ml_get_buf(curbuf, lnum, true); + line[len - 1] = ' '; + } + } + } + } + + // In the "help.txt" and "help.abx" file, add the locally added help + // files. This uses the very first line in the help file. + char *const fname = path_tail(curbuf->b_fname); + if (FNAMECMP(fname, "help.txt") == 0 + || (FNAMENCMP(fname, "help.", 5) == 0 + && ASCII_ISALPHA(fname[5]) + && ASCII_ISALPHA(fname[6]) + && TOLOWER_ASC(fname[7]) == 'x' + && fname[8] == NUL)) { + for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) { + line = (char *)ml_get_buf(curbuf, lnum, false); + if (strstr(line, "*local-additions*") == NULL) { + continue; + } + + // Go through all directories in 'runtimepath', skipping + // $VIMRUNTIME. + char *p = (char *)p_rtp; + while (*p != NUL) { + copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); + char *const rt = vim_getenv("VIMRUNTIME"); + if (rt != NULL + && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { + int fcount; + char **fnames; + char *s; + vimconv_T vc; + char *cp; + + // Find all "doc/ *.txt" files in this directory. + if (!add_pathsep((char *)NameBuff) + || STRLCAT(NameBuff, "doc/*.??[tx]", // NOLINT + sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + continue; + } + + // Note: We cannot just do `&NameBuff` because it is a statically sized array + // so `NameBuff == &NameBuff` according to C semantics. + char *buff_list[1] = { (char *)NameBuff }; + if (gen_expand_wildcards(1, buff_list, &fcount, + &fnames, EW_FILE|EW_SILENT) == OK + && fcount > 0) { + // If foo.abx is found use it instead of foo.txt in + // the same directory. + for (int i1 = 0; i1 < fcount; i1++) { + for (int i2 = 0; i2 < fcount; i2++) { + if (i1 == i2) { + continue; + } + if (fnames[i1] == NULL || fnames[i2] == NULL) { + continue; + } + const char *const f1 = fnames[i1]; + const char *const f2 = fnames[i2]; + const char *const t1 = path_tail(f1); + const char *const t2 = path_tail(f2); + const char *const e1 = (char *)STRRCHR(t1, '.'); + const char *const e2 = (char *)STRRCHR(t2, '.'); + if (e1 == NULL || e2 == NULL) { + continue; + } + if (FNAMECMP(e1, ".txt") != 0 + && FNAMECMP(e1, fname + 4) != 0) { + // Not .txt and not .abx, remove it. + XFREE_CLEAR(fnames[i1]); + continue; + } + if (e1 - f1 != e2 - f2 + || FNAMENCMP(f1, f2, e1 - f1) != 0) { + continue; + } + if (FNAMECMP(e1, ".txt") == 0 + && FNAMECMP(e2, fname + 4) == 0) { + // use .abx instead of .txt + XFREE_CLEAR(fnames[i1]); + } + } + } + for (int fi = 0; fi < fcount; fi++) { + if (fnames[fi] == NULL) { + continue; + } + + FILE *const fd = os_fopen(fnames[fi], "r"); + if (fd == NULL) { + continue; + } + vim_fgets(IObuff, IOSIZE, fd); + if (IObuff[0] == '*' + && (s = vim_strchr((char *)IObuff + 1, '*')) + != NULL) { + TriState this_utf = kNone; + // Change tag definition to a + // reference and remove <CR>/<NL>. + IObuff[0] = '|'; + *s = '|'; + while (*s != NUL) { + if (*s == '\r' || *s == '\n') { + *s = NUL; + } + // The text is utf-8 when a byte + // above 127 is found and no + // illegal byte sequence is found. + if ((char_u)(*s) >= 0x80 && this_utf != kFalse) { + this_utf = kTrue; + const int l = utf_ptr2len(s); + if (l == 1) { + this_utf = kFalse; + } + s += l - 1; + } + s++; + } + // The help file is latin1 or utf-8; + // conversion to the current + // 'encoding' may be required. + vc.vc_type = CONV_NONE; + convert_setup(&vc, + (char_u *)(this_utf == kTrue ? "utf-8" : "latin1"), + p_enc); + if (vc.vc_type == CONV_NONE) { + // No conversion needed. + cp = (char *)IObuff; + } else { + // Do the conversion. If it fails + // use the unconverted text. + cp = (char *)string_convert(&vc, IObuff, NULL); + if (cp == NULL) { + cp = (char *)IObuff; + } + } + convert_setup(&vc, NULL, NULL); + + ml_append(lnum, cp, (colnr_T)0, false); + if ((char_u *)cp != IObuff) { + xfree(cp); + } + lnum++; + } + fclose(fd); + } + FreeWild(fcount, fnames); + } + } + xfree(rt); + } + break; + } + } +} + +/// ":exusage" +void ex_exusage(exarg_T *eap) +{ + do_cmdline_cmd("help ex-cmd-index"); +} + +/// ":viusage" +void ex_viusage(exarg_T *eap) +{ + do_cmdline_cmd("help normal-index"); +} + +/// Generate tags in one help directory +/// +/// @param dir Path to the doc directory +/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.) +/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for +/// French) +/// @param add_help_tags Whether to add the "help-tags" tag +/// @param ignore_writeerr ignore write error +static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags, + bool ignore_writeerr) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + int filecount; + char **files; + char *p1, *p2; + char *s; + TriState utf8 = kNone; + bool mix = false; // detected mixed encodings + + // Find all *.txt files. + size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); + if (dirlen >= MAXPATHL + || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT + || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + return; + } + + // Note: We cannot just do `&NameBuff` because it is a statically sized array + // so `NameBuff == &NameBuff` according to C semantics. + char *buff_list[1] = { (char *)NameBuff }; + const int res = gen_expand_wildcards(1, buff_list, &filecount, &files, + EW_FILE|EW_SILENT); + if (res == FAIL || filecount == 0) { + if (!got_int) { + semsg(_("E151: No match: %s"), NameBuff); + } + if (res != FAIL) { + FreeWild(filecount, files); + } + return; + } + + // Open the tags file for writing. + // Do this before scanning through all the files. + memcpy(NameBuff, dir, dirlen + 1); + if (!add_pathsep((char *)NameBuff) + || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + return; + } + + FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); + if (fd_tags == NULL) { + if (!ignore_writeerr) { + semsg(_("E152: Cannot open %s for writing"), NameBuff); + } + FreeWild(filecount, files); + return; + } + + // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" + // add the "help-tags" tag. + ga_init(&ga, (int)sizeof(char_u *), 100); + if (add_help_tags + || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) { + size_t s_len = 18 + STRLEN(tagfname); + s = xmalloc(s_len); + snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname); + GA_APPEND(char *, &ga, s); + } + + // Go over all the files and extract the tags. + for (int fi = 0; fi < filecount && !got_int; fi++) { + FILE *const fd = os_fopen(files[fi], "r"); + if (fd == NULL) { + semsg(_("E153: Unable to open %s for reading"), files[fi]); + continue; + } + const char *const fname = files[fi] + dirlen + 1; + + bool firstline = true; + while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { + if (firstline) { + // Detect utf-8 file by a non-ASCII char in the first line. + TriState this_utf8 = kNone; + for (s = (char *)IObuff; *s != NUL; s++) { + if ((char_u)(*s) >= 0x80) { + this_utf8 = kTrue; + const int l = utf_ptr2len(s); + if (l == 1) { + // Illegal UTF-8 byte sequence. + this_utf8 = kFalse; + break; + } + s += l - 1; + } + } + if (this_utf8 == kNone) { // only ASCII characters found + this_utf8 = kFalse; + } + if (utf8 == kNone) { // first file + utf8 = this_utf8; + } else if (utf8 != this_utf8) { + semsg(_("E670: Mix of help file encodings within a language: %s"), + files[fi]); + mix = !got_int; + got_int = true; + } + firstline = false; + } + p1 = vim_strchr((char *)IObuff, '*'); // find first '*' + while (p1 != NULL) { + p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'. + if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". + for (s = p1 + 1; s < p2; s++) { + if (*s == ' ' || *s == '\t' || *s == '|') { + break; + } + } + + // Only accept a *tag* when it consists of valid + // characters, there is white space before it and is + // followed by a white character or end-of-line. + if (s == p2 + && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') + && (vim_strchr(" \t\n\r", s[1]) != NULL + || s[1] == '\0')) { + *p2 = '\0'; + p1++; + size_t s_len= (size_t)(p2 - p1) + STRLEN(fname) + 2; + s = xmalloc(s_len); + GA_APPEND(char *, &ga, s); + snprintf(s, s_len, "%s\t%s", p1, fname); + + // find next '*' + p2 = vim_strchr(p2 + 1, '*'); + } + } + p1 = p2; + } + line_breakcheck(); + } + + fclose(fd); + } + + FreeWild(filecount, files); + + if (!got_int && ga.ga_data != NULL) { + // Sort the tags. + sort_strings(ga.ga_data, ga.ga_len); + + // Check for duplicates. + for (int i = 1; i < ga.ga_len; i++) { + p1 = ((char **)ga.ga_data)[i - 1]; + p2 = ((char **)ga.ga_data)[i]; + while (*p1 == *p2) { + if (*p2 == '\t') { + *p2 = NUL; + vim_snprintf((char *)NameBuff, MAXPATHL, + _("E154: Duplicate tag \"%s\" in file %s/%s"), + ((char_u **)ga.ga_data)[i], dir, p2 + 1); + emsg((char *)NameBuff); + *p2 = '\t'; + break; + } + p1++; + p2++; + } + } + + if (utf8 == kTrue) { + fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); + } + + // Write the tags into the file. + for (int i = 0; i < ga.ga_len; i++) { + s = ((char **)ga.ga_data)[i]; + if (STRNCMP(s, "help-tags\t", 10) == 0) { + // help-tags entry was added in formatted form + fputs(s, fd_tags); + } else { + fprintf(fd_tags, "%s\t/" "*", s); + for (p1 = s; *p1 != '\t'; p1++) { + // insert backslash before '\\' and '/' + if (*p1 == '\\' || *p1 == '/') { + putc('\\', fd_tags); + } + putc(*p1, fd_tags); + } + fprintf(fd_tags, "*\n"); + } + } + } + if (mix) { + got_int = false; // continue with other languages + } + + GA_DEEP_CLEAR_PTR(&ga); + fclose(fd_tags); // there is no check for an error... +} + +/// Generate tags in one help directory, taking care of translations. +static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) + FUNC_ATTR_NONNULL_ALL +{ + int len; + garray_T ga; + char lang[2]; + char ext[5]; + char fname[8]; + int filecount; + char **files; + + // Get a list of all files in the help directory and in subdirectories. + STRLCPY(NameBuff, dirname, sizeof(NameBuff)); + if (!add_pathsep((char *)NameBuff) + || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { + emsg(_(e_fnametoolong)); + return; + } + + // Note: We cannot just do `&NameBuff` because it is a statically sized array + // so `NameBuff == &NameBuff` according to C semantics. + char *buff_list[1] = { (char *)NameBuff }; + if (gen_expand_wildcards(1, buff_list, &filecount, &files, + EW_FILE|EW_SILENT) == FAIL + || filecount == 0) { + semsg(_("E151: No match: %s"), NameBuff); + return; + } + + // Go over all files in the directory to find out what languages are + // present. + int j; + ga_init(&ga, 1, 10); + for (int i = 0; i < filecount; i++) { + len = (int)STRLEN(files[i]); + if (len <= 4) { + continue; + } + + if (STRICMP(files[i] + len - 4, ".txt") == 0) { + // ".txt" -> language "en" + lang[0] = 'e'; + lang[1] = 'n'; + } else if (files[i][len - 4] == '.' + && ASCII_ISALPHA(files[i][len - 3]) + && ASCII_ISALPHA(files[i][len - 2]) + && TOLOWER_ASC(files[i][len - 1]) == 'x') { + // ".abx" -> language "ab" + lang[0] = (char)TOLOWER_ASC(files[i][len - 3]); + lang[1] = (char)TOLOWER_ASC(files[i][len - 2]); + } else { + continue; + } + + // Did we find this language already? + for (j = 0; j < ga.ga_len; j += 2) { + if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) { + break; + } + } + if (j == ga.ga_len) { + // New language, add it. + ga_grow(&ga, 2); + ((char *)ga.ga_data)[ga.ga_len++] = lang[0]; + ((char *)ga.ga_data)[ga.ga_len++] = lang[1]; + } + } + + // Loop over the found languages to generate a tags file for each one. + for (j = 0; j < ga.ga_len; j += 2) { + STRCPY(fname, "tags-xx"); + fname[5] = ((char *)ga.ga_data)[j]; + fname[6] = ((char *)ga.ga_data)[j + 1]; + if (fname[5] == 'e' && fname[6] == 'n') { + // English is an exception: use ".txt" and "tags". + fname[4] = NUL; + STRCPY(ext, ".txt"); + } else { + // Language "ab" uses ".abx" and "tags-ab". + STRCPY(ext, ".xxx"); + ext[1] = fname[5]; + ext[2] = fname[6]; + } + helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr); + } + + ga_clear(&ga); + FreeWild(filecount, files); +} + +static void helptags_cb(char *fname, void *cookie) + FUNC_ATTR_NONNULL_ALL +{ + do_helptags(fname, *(bool *)cookie, true); +} + +/// ":helptags" +void ex_helptags(exarg_T *eap) +{ + expand_T xpc; + char *dirname; + bool add_help_tags = false; + + // Check for ":helptags ++t {dir}". + if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { + add_help_tags = true; + eap->arg = skipwhite(eap->arg + 3); + } + + if (STRCMP(eap->arg, "ALL") == 0) { + do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags); + } else { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_DIRECTORIES; + dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL, + WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); + if (dirname == NULL || !os_isdir((char_u *)dirname)) { + semsg(_("E150: Not a directory: %s"), eap->arg); + } else { + do_helptags(dirname, add_help_tags, false); + } + xfree(dirname); + } +} diff --git a/src/nvim/help.h b/src/nvim/help.h new file mode 100644 index 0000000000..21e11392ee --- /dev/null +++ b/src/nvim/help.h @@ -0,0 +1,11 @@ +#ifndef NVIM_HELP_H +#define NVIM_HELP_H + +#include <stdbool.h> + +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "help.h.generated.h" +#endif +#endif // NVIM_HELP_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 71c7194479..c26b00df79 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -6,6 +6,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/decoration_provider.h" +#include "nvim/drawscreen.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" @@ -13,8 +14,7 @@ #include "nvim/map.h" #include "nvim/message.h" #include "nvim/option.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -32,7 +32,9 @@ static Map(int, int) blend_attr_entries = MAP_INIT; static Map(int, int) blendthrough_attr_entries = MAP_INIT; /// highlight entries private to a namespace -static Map(ColorKey, ColorItem) ns_hl; +static Map(ColorKey, ColorItem) ns_hls; +typedef int NSHlAttr[HLF_COUNT + 1]; +static PMap(handle_T) ns_hl_attr; void highlight_init(void) { @@ -147,42 +149,46 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) { - if ((attrs.rgb_ae_attr & HL_DEFAULT) - && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) { - return; - } if (ns_id == 0) { assert(dict); // set in global (':highlight') namespace set_hl_group(hl_id, attrs, dict, link_id); return; } + if ((attrs.rgb_ae_attr & HL_DEFAULT) + && map_has(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id))) { + return; + } DecorProvider *p = get_decor_provider(ns_id, true); int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); ColorItem it = { .attr_id = attr_id, .link_id = link_id, .version = p->hl_valid, - .is_default = (attrs.rgb_ae_attr & HL_DEFAULT) }; - map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it); + .is_default = (attrs.rgb_ae_attr & HL_DEFAULT), + .link_global = (attrs.rgb_ae_attr & HL_GLOBAL) }; + map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it); + p->hl_cached = false; } -int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) +int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) { static int recursive = 0; - if (ns_id < 0) { + if (*ns_hl < 0) { if (ns_hl_active <= 0) { return -1; } - ns_id = ns_hl_active; + *ns_hl = ns_hl_active; } + int ns_id = *ns_hl; + DecorProvider *p = get_decor_provider(ns_id, true); - ColorItem it = map_get(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id)); + ColorItem it = map_get(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? - bool valid_cache = it.version >= p->hl_valid; + bool valid_item = it.version >= p->hl_valid; - if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) { + if (!valid_item && p->hl_def != LUA_NOREF && !recursive) { MAXSIZE_TEMP_ARRAY(args, 3); ADD_C(args, INTEGER_OBJ((Integer)ns_id)); ADD_C(args, STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id)))); @@ -215,44 +221,76 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); it.version = p->hl_valid - tmp; it.is_default = attrs.rgb_ae_attr & HL_DEFAULT; - map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it); + it.link_global = attrs.rgb_ae_attr & HL_GLOBAL; + map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it); + valid_item = true; } - if (it.is_default && nodefault) { + if ((it.is_default && nodefault) || !valid_item) { return -1; } if (link) { - return it.attr_id >= 0 ? 0 : it.link_id; + if (it.attr_id >= 0) { + return 0; + } else { + if (it.link_global) { + *ns_hl = 0; + } + return it.link_id; + } } else { return it.attr_id; } } -bool win_check_ns_hl(win_T *wp) +bool hl_check_ns(void) { - if (ns_hl_changed) { - highlight_changed(); - if (wp) { - update_window_hl(wp, true); + int ns = 0; + if (ns_hl_fast > 0) { + ns = ns_hl_fast; + } else if (ns_hl_win >= 0) { + ns = ns_hl_win; + } else { + ns = ns_hl_global; + } + if (ns_hl_active == ns) { + return false; + } + + ns_hl_active = ns; + hl_attr_active = highlight_attr; + if (ns > 0) { + update_ns_hl(ns); + NSHlAttr *hl_def = (NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns); + if (hl_def) { + hl_attr_active = *hl_def; } - ns_hl_changed = false; - return true; } - return false; + need_highlight_changed = true; + return true; +} + +/// prepare for drawing window `wp` or global elements if NULL +/// +/// Note: pum should be drawn in the context of the current window! +bool win_check_ns_hl(win_T *wp) +{ + ns_hl_win = wp ? wp->w_ns_hl : -1; + return hl_check_ns(); } /// Get attribute code for a builtin highlight group. /// /// The final syntax group could be modified by hi-link or 'winhighlight'. -int hl_get_ui_attr(int idx, int final_id, bool optional) +int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional) { HlAttrs attrs = HLATTRS_INIT; bool available = false; if (final_id > 0) { - int syn_attr = syn_id2attr(final_id); - if (syn_attr != 0) { + int syn_attr = syn_ns_id2attr(ns_id, final_id, optional); + if (syn_attr > 0) { attrs = syn_attr2entry(syn_attr); available = true; } @@ -265,8 +303,6 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) if (pum_drawn()) { must_redraw_pum = true; } - } else if (idx == HLF_MSG) { - msg_grid.blending = attrs.hl_blend > -1; } if (optional && !available) { @@ -278,6 +314,21 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) void update_window_hl(win_T *wp, bool invalid) { + int ns_id = wp->w_ns_hl; + + update_ns_hl(ns_id); + if (ns_id != wp->w_ns_hl_active || wp->w_ns_hl_attr == NULL) { + wp->w_ns_hl_active = ns_id; + + wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns_id); + if (!wp->w_ns_hl_attr) { + // No specific highlights, use the defaults. + wp->w_ns_hl_attr = highlight_attr; + } + } + + int *hl_def = wp->w_ns_hl_attr; + if (!wp->w_hl_needs_update && !invalid) { return; } @@ -285,34 +336,17 @@ void update_window_hl(win_T *wp, bool invalid) // If a floating window is blending it always have a named // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named. - bool has_blend = wp->w_floating && wp->w_p_winbl != 0; // determine window specific background set in 'winhighlight' bool float_win = wp->w_floating && !wp->w_float_config.external; - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, - wp->w_hl_ids[HLF_INACTIVE], - !has_blend); - } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, - wp->w_hl_ids[HLF_NFLOAT], !has_blend); - } else if (wp->w_hl_id_normal != 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend); + if (float_win && hl_def[HLF_NFLOAT] != 0) { + wp->w_hl_attr_normal = hl_def[HLF_NFLOAT]; + } else if (hl_def[HLF_COUNT] > 0) { + wp->w_hl_attr_normal = hl_def[HLF_COUNT]; } else { wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } - // NOOOO! You cannot just pretend that "Normal" is just like any other - // syntax group! It needs at least 10 layers of special casing! Noooooo! - // - // haha, theme engine go brrr - int normality = syn_check_group(S_LEN("Normal")); - int ns_attr = ns_get_hl(-1, normality, false, false); - if (ns_attr > 0) { - // TODO(bfredl): hantera NormalNC and so on - wp->w_hl_attr_normal = ns_attr; - } - // if blend= attribute is not set, 'winblend' value overrides it. if (wp->w_floating && wp->w_p_winbl > 0) { HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal); @@ -322,28 +356,13 @@ void update_window_hl(win_T *wp, bool invalid) } } - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) { - wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), - wp->w_hl_attr_normal); - } - - for (int hlf = 0; hlf < HLF_COUNT; hlf++) { - int attr; - if (wp->w_hl_ids[hlf] != 0) { - attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); - } else { - attr = HL_ATTR(hlf); - } - wp->w_hl_attrs[hlf] = attr; - } - wp->w_float_config.shadow = false; if (wp->w_floating && wp->w_float_config.border) { for (int i = 0; i < 8; i++) { - int attr = wp->w_hl_attrs[HLF_BORDER]; + int attr = hl_def[HLF_BORDER]; if (wp->w_float_config.border_hl_ids[i]) { - attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i], - false); + attr = hl_get_ui_attr(ns_id, HLF_BORDER, + wp->w_float_config.border_hl_ids[i], false); HlAttrs a = syn_attr2entry(attr); if (a.hl_blend) { wp->w_float_config.shadow = true; @@ -355,6 +374,65 @@ void update_window_hl(win_T *wp, bool invalid) // shadow might cause blending check_blending(wp); + + // TODO(bfredl): this a bit ad-hoc. move it from highlight ns logic to 'winhl' + // implementation? + if (hl_def[HLF_INACTIVE] == 0) { + wp->w_hl_attr_normalnc = hl_combine_attr(HL_ATTR(HLF_INACTIVE), + wp->w_hl_attr_normal); + } else { + wp->w_hl_attr_normalnc = hl_def[HLF_INACTIVE]; + } +} + +void update_ns_hl(int ns_id) +{ + if (ns_id <= 0) { + return; + } + DecorProvider *p = get_decor_provider(ns_id, true); + if (p->hl_cached) { + return; + } + + NSHlAttr **alloc = (NSHlAttr **)pmap_ref(handle_T)(&ns_hl_attr, ns_id, true); + if (*alloc == NULL) { + *alloc = xmalloc(sizeof(**alloc)); + } + int *hl_attrs = **alloc; + + for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + int id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); + bool optional = (hlf == HLF_INACTIVE || hlf == HLF_NFLOAT); + hl_attrs[hlf] = hl_get_ui_attr(ns_id, hlf, id, optional); + } + + // NOOOO! You cannot just pretend that "Normal" is just like any other + // syntax group! It needs at least 10 layers of special casing! Noooooo! + // + // haha, tema engine go brrr + int normality = syn_check_group(S_LEN("Normal")); + hl_attrs[HLF_COUNT] = hl_get_ui_attr(ns_id, -1, normality, true); + + // hl_get_ui_attr might have invalidated the decor provider + p = get_decor_provider(ns_id, true); + p->hl_cached = true; +} + +int win_bg_attr(win_T *wp) +{ + if (ns_hl_fast < 0) { + int local = (wp == curwin) ? wp->w_hl_attr_normal : wp->w_hl_attr_normalnc; + if (local) { + return local; + } + } + + if (wp == curwin || hl_attr_active[HLF_INACTIVE] == 0) { + return hl_attr_active[HLF_COUNT]; + } else { + return hl_attr_active[HLF_INACTIVE]; + } } /// Gets HL_UNDERLINE highlight. @@ -403,7 +481,7 @@ void clear_hl_tables(bool reinit) map_destroy(int, int)(&combine_attr_entries); map_destroy(int, int)(&blend_attr_entries); map_destroy(int, int)(&blendthrough_attr_entries); - map_destroy(ColorKey, ColorItem)(&ns_hl); + map_destroy(ColorKey, ColorItem)(&ns_hls); } } @@ -437,52 +515,52 @@ int hl_combine_attr(int char_attr, int prim_attr) } HlAttrs char_aep = syn_attr2entry(char_attr); - HlAttrs spell_aep = syn_attr2entry(prim_attr); + HlAttrs prim_aep = syn_attr2entry(prim_attr); // start with low-priority attribute, and override colors if present below. HlAttrs new_en = char_aep; - if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) { - new_en.cterm_ae_attr = spell_aep.cterm_ae_attr; + if (prim_aep.cterm_ae_attr & HL_NOCOMBINE) { + new_en.cterm_ae_attr = prim_aep.cterm_ae_attr; } else { - new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr; + new_en.cterm_ae_attr |= prim_aep.cterm_ae_attr; } - if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) { - new_en.rgb_ae_attr = spell_aep.rgb_ae_attr; + if (prim_aep.rgb_ae_attr & HL_NOCOMBINE) { + new_en.rgb_ae_attr = prim_aep.rgb_ae_attr; } else { - new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr; + new_en.rgb_ae_attr |= prim_aep.rgb_ae_attr; } - if (spell_aep.cterm_fg_color > 0) { - new_en.cterm_fg_color = spell_aep.cterm_fg_color; + if (prim_aep.cterm_fg_color > 0) { + new_en.cterm_fg_color = prim_aep.cterm_fg_color; new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_FG_INDEXED)); } - if (spell_aep.cterm_bg_color > 0) { - new_en.cterm_bg_color = spell_aep.cterm_bg_color; + if (prim_aep.cterm_bg_color > 0) { + new_en.cterm_bg_color = prim_aep.cterm_bg_color; new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_BG_INDEXED)); } - if (spell_aep.rgb_fg_color >= 0) { - new_en.rgb_fg_color = spell_aep.rgb_fg_color; + if (prim_aep.rgb_fg_color >= 0) { + new_en.rgb_fg_color = prim_aep.rgb_fg_color; new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_FG_INDEXED)); } - if (spell_aep.rgb_bg_color >= 0) { - new_en.rgb_bg_color = spell_aep.rgb_bg_color; + if (prim_aep.rgb_bg_color >= 0) { + new_en.rgb_bg_color = prim_aep.rgb_bg_color; new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) - | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); + | (prim_aep.rgb_ae_attr & HL_BG_INDEXED)); } - if (spell_aep.rgb_sp_color >= 0) { - new_en.rgb_sp_color = spell_aep.rgb_sp_color; + if (prim_aep.rgb_sp_color >= 0) { + new_en.rgb_sp_color = prim_aep.rgb_sp_color; } - if (spell_aep.hl_blend >= 0) { - new_en.hl_blend = spell_aep.hl_blend; + if (prim_aep.hl_blend >= 0) { + new_en.hl_blend = prim_aep.hl_blend; } id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine, @@ -852,7 +930,6 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); - CHECK_FLAG(dict, mask, global, , HL_GLOBAL); if (HAS_KEY(dict->fg)) { fg = object_to_color(dict->fg, "fg", true, err); @@ -895,14 +972,21 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e return hlattrs; } - if (HAS_KEY(dict->link)) { + if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) { if (link_id) { - *link_id = object_to_hl_id(dict->link, "link", err); + if (HAS_KEY(dict->global_link)) { + *link_id = object_to_hl_id(dict->global_link, "link", err); + mask |= HL_GLOBAL; + } else { + *link_id = object_to_hl_id(dict->link, "link", err); + } + if (ERROR_SET(err)) { return hlattrs; } } else { - api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'"); + api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'", + HAS_KEY(dict->global_link) ? "global_link" : "link"); } } diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index ae63f83d65..50299bb91c 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "nvim/api/private/defs.h" +#include "nvim/buffer_defs.h" #include "nvim/highlight_defs.h" #include "nvim/ui.h" @@ -11,6 +12,13 @@ # include "highlight.h.generated.h" #endif +static inline int win_hl_attr(win_T *wp, int hlf) +{ + // wp->w_ns_hl_attr might be null if we check highlights + // prior to entering redraw + return ((wp->w_ns_hl_attr && ns_hl_fast < 0) ? wp->w_ns_hl_attr : hl_attr_active)[hlf]; +} + #define HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp) \ do { \ bool dark_ = (*p_bg == 'd'); \ diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index f41f980054..ffcb0f3f22 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -180,7 +180,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_CU] = "Cursor", }); -EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context. +EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context. EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups EXTERN int highlight_user[9]; // User[1-9] attributes EXTERN int highlight_stlnc[9]; // On top of user @@ -190,6 +190,13 @@ EXTERN RgbValue normal_fg INIT(= -1); EXTERN RgbValue normal_bg INIT(= -1); EXTERN RgbValue normal_sp INIT(= -1); +EXTERN NS ns_hl_global INIT(= 0); // global highlight namespace +EXTERN NS ns_hl_win INIT(= -1); // highlight namespace for the current window +EXTERN NS ns_hl_fast INIT(= -1); // highlight namespace specified in a fast callback +EXTERN NS ns_hl_active INIT(= 0); // currently active/cached namespace + +EXTERN int *hl_attr_active INIT(= highlight_attr); + typedef enum { kHlUnknown, kHlUI, @@ -219,8 +226,15 @@ typedef struct { int link_id; int version; bool is_default; + bool link_global; } ColorItem; -#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, \ - .version = -1, .is_default = false } +#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1, \ + .is_default = false, .link_global = false } + +/// highlight attributes with associated priorities +typedef struct { + int attr_id; + int priority; +} HlPriAttr; #endif // NVIM_HIGHLIGHT_DEFS_H diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index f6ec03fb14..77424de3b8 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -9,7 +9,10 @@ #include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/eval/vars.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" @@ -17,7 +20,6 @@ #include "nvim/match.h" #include "nvim/option.h" #include "nvim/runtime.h" -#include "nvim/screen.h" /// \addtogroup SG_SET /// @{ @@ -689,14 +691,14 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) g->sg_cleared = false; g->sg_link = link_id; g->sg_script_ctx = current_sctx; - g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_script_ctx.sc_lnum += SOURCING_LNUM; g->sg_set |= SG_LINK; if (is_default) { g->sg_deflink = link_id; g->sg_deflink_sctx = current_sctx; - g->sg_deflink_sctx.sc_lnum += sourcing_lnum; + g->sg_deflink_sctx.sc_lnum += SOURCING_LNUM; } - return; + goto update; } g->sg_cleared = false; @@ -733,7 +735,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) g->sg_blend = attrs.hl_blend; g->sg_script_ctx = current_sctx; - g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_script_ctx.sc_lnum += SOURCING_LNUM; g->sg_attr = hl_get_syn_attr(0, id, attrs); @@ -751,6 +753,12 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) ui_mode_info_set(); } } + +update: + if (!updating_screen) { + redraw_all_later(NOT_VALID); + } + need_highlight_changed = true; } /// Handle ":highlight" command @@ -861,7 +869,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { hlgroup->sg_deflink = to_id; hlgroup->sg_deflink_sctx = current_sctx; - hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + hlgroup->sg_deflink_sctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hlgroup->sg_deflink_sctx); } } @@ -871,7 +879,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // for the group, unless '!' is used if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) { + if (SOURCING_NAME == NULL && !dodefault) { emsg(_("E414: group has settings, highlight link ignored")); } } else if (hlgroup->sg_link != to_id @@ -882,7 +890,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } hlgroup->sg_link = to_id; hlgroup->sg_script_ctx = current_sctx; - hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + hlgroup->sg_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hlgroup->sg_script_ctx); hlgroup->sg_cleared = false; redraw_all_later(SOME_VALID); @@ -1272,7 +1280,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) set_hl_attr(idx); } hl_table[idx].sg_script_ctx = current_sctx; - hl_table[idx].sg_script_ctx.sc_lnum += sourcing_lnum; + hl_table[idx].sg_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hl_table[idx].sg_script_ctx); // Only call highlight_changed() once, after a sequence of highlight @@ -1765,7 +1773,7 @@ static int syn_add_group(const char *name, size_t len) // Append another syntax_highlight entry. HlGroup *hlgp = GA_APPEND_VIA_PTR(HlGroup, &highlight_ga); - memset(hlgp, 0, sizeof(*hlgp)); + CLEAR_POINTER(hlgp); hlgp->sg_name = (char_u *)arena_memdupz(&highlight_arena, name, len); hlgp->sg_rgb_bg = -1; hlgp->sg_rgb_fg = -1; @@ -1788,11 +1796,18 @@ static int syn_add_group(const char *name, size_t len) /// @see syn_attr2entry int syn_id2attr(int hl_id) { - hl_id = syn_get_final_id(hl_id); + return syn_ns_id2attr(-1, hl_id, false); +} + +int syn_ns_id2attr(int ns_id, int hl_id, bool optional) +{ + hl_id = syn_ns_get_final_id(&ns_id, hl_id); HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one - int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); - if (attr >= 0) { + int attr = ns_get_hl(&ns_id, hl_id, false, sgp->sg_set); + + // if a highlight group is optional, don't use the global value + if (attr >= 0 || (optional && ns_id > 0)) { return attr; } return sgp->sg_attr; @@ -1801,10 +1816,16 @@ int syn_id2attr(int hl_id) /// Translate a group ID to the final group ID (following links). int syn_get_final_id(int hl_id) { + int id = curwin->w_ns_hl_active; + return syn_ns_get_final_id(&id, hl_id); +} + +int syn_ns_get_final_id(int *ns_id, int hl_id) +{ int count; if (hl_id > highlight_ga.ga_len || hl_id < 1) { - return 0; // Can be called from eval!! + return 0; // Can be called from eval!! } // Follow links until there is no more. @@ -1812,10 +1833,10 @@ int syn_get_final_id(int hl_id) for (count = 100; --count >= 0;) { HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one - // ACHTUNG: when using "tmp" attribute (no link) the function might be + // TODO(bfredl): when using "tmp" attribute (no link) the function might be // called twice. it needs be smart enough to remember attr only to // syn_id2attr time - int check = ns_get_hl(-1, hl_id, true, sgp->sg_set); + int check = ns_get_hl(ns_id, hl_id, true, sgp->sg_set); if (check == 0) { return hl_id; // how dare! it broke the link! } else if (check > 0) { @@ -1863,7 +1884,7 @@ static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int HlGroup *const hlt = hl_table; if (id_alt == 0) { - memset(&hlt[hlcnt + i], 0, sizeof(HlGroup)); + CLEAR_POINTER(&hlt[hlcnt + i]); hlt[hlcnt + i].sg_cterm = highlight_attr[hlf]; hlt[hlcnt + i].sg_gui = highlight_attr[hlf]; } else { @@ -1913,19 +1934,22 @@ void highlight_changed(void) if (id == 0) { abort(); } - int final_id = syn_get_final_id(id); + int ns_id = -1; + int final_id = syn_ns_get_final_id(&ns_id, id); if (hlf == HLF_SNC) { id_SNC = final_id; } else if (hlf == HLF_S) { id_S = final_id; } - highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, + highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id, (hlf == HLF_INACTIVE || hlf == HLF_LC)); if (highlight_attr[hlf] != highlight_attr_last[hlf]) { if (hlf == HLF_MSG) { clear_cmdline = true; + HlAttrs attrs = syn_attr2entry(highlight_attr[hlf]); + msg_grid.blending = attrs.hl_blend > -1; } ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), highlight_attr[hlf]); @@ -1933,6 +1957,9 @@ void highlight_changed(void) } } + // sentinel value. used when no hightlight namespace is active + highlight_attr[HLF_COUNT] = 0; + // // Setup the user highlights // @@ -1945,7 +1972,7 @@ void highlight_changed(void) hlcnt = highlight_ga.ga_len; if (id_S == -1) { // Make sure id_S is always valid to simplify code below. Use the last entry - memset(&hl_table[hlcnt + 9], 0, sizeof(HlGroup)); + CLEAR_POINTER(&hl_table[hlcnt + 9]); id_S = hlcnt + 10; } for (int i = 0; i < 9; i++) { diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h index 1474588889..bf6bad1a86 100644 --- a/src/nvim/highlight_group.h +++ b/src/nvim/highlight_group.h @@ -1,7 +1,8 @@ #ifndef NVIM_HIGHLIGHT_GROUP_H #define NVIM_HIGHLIGHT_GROUP_H -#include "nvim/eval.h" +#include "nvim/api/private/helpers.h" +#include "nvim/highlight_defs.h" #include "nvim/types.h" #define MAX_HL_ID 20000 // maximum value for a highlight ID. diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 8d08c2fc19..689d1fce0d 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -18,9 +18,12 @@ #include <sys/types.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/eval.h" #include "nvim/event/stream.h" +#include "nvim/ex_eval.h" #include "nvim/fileio.h" #include "nvim/if_cscope.h" #include "nvim/memory.h" @@ -415,16 +418,15 @@ static int cs_add_common(char *arg1, char *arg2, char *flags) char *fname2 = NULL; char *ppath = NULL; size_t usedlen = 0; - char_u *fbuf = NULL; + char *fbuf = NULL; // get the filename (arg1), expand it, and try to stat it fname = xmalloc(MAXPATHL + 1); expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL); size_t len = STRLEN(fname); - fbuf = (char_u *)fname; - (void)modify_fname(":p", false, &usedlen, - &fname, (char **)&fbuf, &len); + fbuf = fname; + (void)modify_fname(":p", false, &usedlen, &fname, &fbuf, &len); if (fname == NULL) { goto add_err; } @@ -661,7 +663,7 @@ static char *cs_create_cmd(char *csoption, char *pattern) pat = pattern; if (search != 4 && search != 6) { while (ascii_iswhite(*pat)) { - ++pat; + pat++; } } diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 271498d41a..f18a6d7b32 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -11,6 +11,7 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/extmark.h" #include "nvim/indent.h" @@ -30,6 +31,313 @@ # include "indent.c.generated.h" #endif +/// Set the integer values corresponding to the string setting of 'vartabstop'. +/// "array" will be set, caller must free it if needed. +/// Return false for an error. +bool tabstop_set(char_u *var, long **array) +{ + long valcount = 1; + int t; + char_u *cp; + + if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { + *array = NULL; + return true; + } + + for (cp = var; *cp != NUL; cp++) { + if (cp == var || cp[-1] == ',') { + char *end; + + if (strtol((char *)cp, &end, 10) <= 0) { + if (cp != (char_u *)end) { + emsg(_(e_positive)); + } else { + semsg(_(e_invarg2), cp); + } + return false; + } + } + + if (ascii_isdigit(*cp)) { + continue; + } + if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) { + valcount++; + continue; + } + semsg(_(e_invarg2), var); + return false; + } + + *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long)); + (*array)[0] = valcount; + + t = 1; + for (cp = var; *cp != NUL;) { + int n = atoi((char *)cp); + + // Catch negative values, overflow and ridiculous big values. + if (n <= 0 || n > TABSTOP_MAX) { + semsg(_(e_invarg2), cp); + XFREE_CLEAR(*array); + return false; + } + (*array)[t++] = n; + while (*cp != NUL && *cp != ',') { + cp++; + } + if (*cp != NUL) { + cp++; + } + } + + return true; +} + +/// Calculate the number of screen spaces a tab will occupy. +/// If "vts" is set then the tab widths are taken from that array, +/// otherwise the value of ts is used. +int tabstop_padding(colnr_T col, long ts_arg, long *vts) +{ + long ts = ts_arg == 0 ? 8 : ts_arg; + colnr_T tabcol = 0; + int t; + long padding = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)(ts - (col % ts)); + } + + const long tabcount = vts[0]; + + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + padding = tabcol - col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]); + } + + return (int)padding; +} + +/// Find the size of the tab that covers a particular column. +int tabstop_at(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + long tab_size = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)ts; + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + tab_size = vts[t]; + break; + } + } + if (t > tabcount) { + tab_size = vts[tabcount]; + } + + return (int)tab_size; +} + +/// Find the column on which a tab starts. +colnr_T tabstop_start(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + + if (vts == NULL || vts[0] == 0) { + return (int)((col / ts) * ts); + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + return (int)(tabcol - vts[t]); + } + } + + const int excess = (int)(tabcol % vts[tabcount]); + return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]); +} + +/// Find the number of tabs and spaces necessary to get from one column +/// to another. +void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, int *ntabs, + int *nspcs) +{ + int spaces = end_col - start_col; + colnr_T tabcol = 0; + long padding = 0; + int t; + long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + + if (vts == NULL || vts[0] == 0) { + int tabs = 0; + + const int initspc = (int)(ts - (start_col % ts)); + if (spaces >= initspc) { + spaces -= initspc; + tabs++; + } + tabs += (int)(spaces / ts); + spaces -= (int)((spaces / ts) * ts); + + *ntabs = tabs; + *nspcs = spaces; + return; + } + + // Find the padding needed to reach the next tabstop. + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > start_col) { + padding = tabcol - start_col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]); + } + + // If the space needed is less than the padding no tabs can be used. + if (spaces < padding) { + *ntabs = 0; + *nspcs = spaces; + return; + } + + *ntabs = 1; + spaces -= (int)padding; + + // At least one tab has been used. See if any more will fit. + while (spaces != 0 && ++t <= tabcount) { + padding = vts[t]; + if (spaces < padding) { + *nspcs = spaces; + return; + } + *ntabs += 1; + spaces -= (int)padding; + } + + *ntabs += spaces / (int)vts[tabcount]; + *nspcs = spaces % (int)vts[tabcount]; +} + +/// See if two tabstop arrays contain the same values. +bool tabstop_eq(long *ts1, long *ts2) +{ + int t; + + if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { + return false; + } + if (ts1 == ts2) { + return true; + } + if (ts1[0] != ts2[0]) { + return false; + } + + for (t = 1; t <= ts1[0]; t++) { + if (ts1[t] != ts2[t]) { + return false; + } + } + + return true; +} + +/// Copy a tabstop array, allocating space for the new array. +int *tabstop_copy(long *oldts) +{ + long *newts; + int t; + + if (oldts == 0) { + return 0; + } + + newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long)); + for (t = 0; t <= oldts[0]; t++) { + newts[t] = oldts[t]; + } + + return (int *)newts; +} + +/// Return a count of the number of tabstops. +int tabstop_count(long *ts) +{ + return ts != NULL ? (int)ts[0] : 0; +} + +/// Return the first tabstop, or 8 if there are no tabstops defined. +int tabstop_first(long *ts) +{ + return ts != NULL ? (int)ts[1] : 8; +} + +/// Return the effective shiftwidth value for current buffer, using the +/// 'tabstop' value when 'shiftwidth' is zero. +int get_sw_value(buf_T *buf) +{ + long result = get_sw_value_col(buf, 0); + assert(result >= 0 && result <= INT_MAX); + return (int)result; +} + +/// Idem, using "pos". +long get_sw_value_pos(buf_T *buf, pos_T *pos) +{ + pos_T save_cursor = curwin->w_cursor; + long sw_value; + + curwin->w_cursor = *pos; + sw_value = get_sw_value_col(buf, get_nolist_virtcol()); + curwin->w_cursor = save_cursor; + return sw_value; +} + +/// Idem, using the first non-black in the current line. +long get_sw_value_indent(buf_T *buf) +{ + pos_T pos = curwin->w_cursor; + + pos.col = (colnr_T)getwhitecols_curline(); + return get_sw_value_pos(buf, &pos); +} + +/// Idem, using virtual column "col". +long get_sw_value_col(buf_T *buf, colnr_T col) +{ + return buf->b_p_sw ? buf->b_p_sw + : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); +} + +/// Return the effective softtabstop value for the current buffer, +/// using the shiftwidth value when 'softtabstop' is negative. +int get_sts_value(void) +{ + long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; + assert(result >= 0 && result <= INT_MAX); + return (int)result; +} + // Count the size (in window cells) of the indent in the current line. int get_indent(void) { @@ -769,10 +1077,10 @@ static int lisp_match(char_u *p) { char_u buf[LSIZE]; int len; - char_u *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords; + char *word = (char *)(*curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords); while (*word != NUL) { - (void)copy_option_part((char **)&word, (char *)buf, LSIZE, ","); + (void)copy_option_part(&word, (char *)buf, LSIZE, ","); len = (int)STRLEN(buf); if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) { diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index c5e030ce25..34a3de4f78 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -223,8 +223,8 @@ bool cin_is_cinword(const char_u *line) char_u *cinw_buf = xmalloc(cinw_len); line = (char_u *)skipwhite((char *)line); - for (char_u *cinw = curbuf->b_p_cinw; *cinw;) { - size_t len = copy_option_part((char **)&cinw, (char *)cinw_buf, cinw_len, ","); + for (char *cinw = (char *)curbuf->b_p_cinw; *cinw;) { + size_t len = copy_option_part(&cinw, (char *)cinw_buf, cinw_len, ","); if (STRNCMP(line, cinw_buf, len) == 0 && (!vim_iswordc(line[len]) || !vim_iswordc(line[len - 1]))) { retval = true; @@ -317,17 +317,17 @@ static bool cin_has_js_key(const char_u *text) if (*s == '\'' || *s == '"') { // can be 'key': or "key": quote = *s; - ++s; + s++; } if (!vim_isIDc(*s)) { // need at least one ID character return false; } while (vim_isIDc(*s)) { - ++s; + s++; } if (*s && *s == quote) { - ++s; + s++; } s = cin_skipcomment(s); @@ -519,8 +519,8 @@ bool cin_isscopedecl(const char_u *p) bool found = false; - for (char_u *cinsd = curbuf->b_p_cinsd; *cinsd;) { - const size_t len = copy_option_part((char **)&cinsd, (char *)cinsd_buf, cinsd_len, ","); + for (char *cinsd = (char *)curbuf->b_p_cinsd; *cinsd;) { + const size_t len = copy_option_part(&cinsd, (char *)cinsd_buf, cinsd_len, ","); if (STRNCMP(s, cinsd_buf, len) == 0) { const char_u *skip = cin_skipcomment(s + len); if (*skip == ':' && skip[1] != ':') { @@ -1601,8 +1601,8 @@ static int find_last_paren(const char_u *l, int start, int end) */ void parse_cino(buf_T *buf) { - char_u *p; - char_u *l; + char *p; + char *l; int divider; int fraction = 0; int sw = get_sw_value(buf); @@ -1740,16 +1740,16 @@ void parse_cino(buf_T *buf) // Handle C #pragma directives buf->b_ind_pragma = 0; - for (p = buf->b_p_cino; *p;) { + for (p = (char *)buf->b_p_cino; *p;) { l = p++; if (*p == '-') { p++; } - char_u *digits_start = p; // remember where the digits start - int n = getdigits_int((char **)&p, true, 0); + char *digits_start = p; // remember where the digits start + int n = getdigits_int(&p, true, 0); divider = 0; if (*p == '.') { // ".5s" means a fraction. - fraction = atoi((char *)++p); + fraction = atoi(++p); while (ascii_isdigit(*p)) { p++; if (divider) { @@ -1768,7 +1768,7 @@ void parse_cino(buf_T *buf) n += (sw * fraction + divider / 2) / divider; } } - ++p; + p++; } if (l[1] == '-') { n = -n; @@ -2052,7 +2052,7 @@ int get_c_indent(void) char lead_start[COM_MAX_LEN]; // start-comment string char lead_middle[COM_MAX_LEN]; // middle-comment string char lead_end[COM_MAX_LEN]; // end-comment string - char_u *p; + char *p; int start_align = 0; int start_off = 0; int done = FALSE; @@ -2063,7 +2063,7 @@ int get_c_indent(void) *lead_start = NUL; *lead_middle = NUL; - p = curbuf->b_p_com; + p = (char *)curbuf->b_p_com; while (*p != NUL) { int align = 0; int off = 0; @@ -2071,11 +2071,11 @@ int get_c_indent(void) while (*p != NUL && *p != ':') { if (*p == COM_START || *p == COM_END || *p == COM_MIDDLE) { - what = *p++; + what = (unsigned char)(*p++); } else if (*p == COM_LEFT || *p == COM_RIGHT) { - align = *p++; + align = (unsigned char)(*p++); } else if (ascii_isdigit(*p) || *p == '-') { - off = getdigits_int((char **)&p, true, 0); + off = getdigits_int(&p, true, 0); } else { p++; } @@ -2084,7 +2084,7 @@ int get_c_indent(void) if (*p == ':') { p++; } - (void)copy_option_part((char **)&p, lead_end, COM_MAX_LEN, ","); + (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); if (what == COM_START) { STRCPY(lead_start, lead_end); lead_start_len = (int)STRLEN(lead_start); @@ -3334,7 +3334,7 @@ int get_c_indent(void) amount += curbuf->b_ind_open_extra; } } - ++whilelevel; + whilelevel++; } /* * We are after a "normal" statement. @@ -3848,7 +3848,7 @@ static int find_match(int lookfor, linenr_T ourscope) * another "do", so increment whilelevel. XXX */ if (cin_iswhileofdo(look, curwin->w_cursor.lnum)) { - ++whilelevel; + whilelevel++; continue; } diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index a64d8e8f00..c525a49bc3 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -13,10 +13,12 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" @@ -33,9 +35,8 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/state.h" @@ -2139,7 +2140,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) } } else { word = tv_get_string_chk(tv); - memset(cptext, 0, sizeof(cptext)); + CLEAR_FIELD(cptext); } if (word == NULL || (!empty && *word == NUL)) { for (size_t i = 0; i < CPT_COUNT; i++) { @@ -2473,7 +2474,7 @@ static int ins_compl_get_exp(pos_T *ini) { static pos_T first_match_pos; static pos_T last_match_pos; - static char_u *e_cpt = (char_u *)""; // curr. entry in 'complete' + static char *e_cpt = ""; // curr. entry in 'complete' static bool found_all = false; // Found all matches of a // certain type. static buf_T *ins_buf = NULL; // buffer being scanned @@ -2502,8 +2503,7 @@ static int ins_compl_get_exp(pos_T *ini) } found_all = false; ins_buf = curbuf; - e_cpt = (compl_cont_status & CONT_LOCAL) - ? (char_u *)"." : curbuf->b_p_cpt; + e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : (char *)curbuf->b_p_cpt; last_match_pos = first_match_pos = *ini; } else if (ins_buf != curbuf && !buf_valid(ins_buf)) { ins_buf = curbuf; // In case the buffer was wiped out. @@ -2583,7 +2583,7 @@ static int ins_compl_get_exp(pos_T *ini) type = CTRL_X_THESAURUS; } if (*++e_cpt != ',' && *e_cpt != NUL) { - dict = e_cpt; + dict = (char_u *)e_cpt; dict_f = DICT_FIRST; } } else if (*e_cpt == 'i') { @@ -2600,7 +2600,7 @@ static int ins_compl_get_exp(pos_T *ini) } // in any case e_cpt is advanced to the next entry - (void)copy_option_part((char **)&e_cpt, (char *)IObuff, IOSIZE, ","); + (void)copy_option_part(&e_cpt, (char *)IObuff, IOSIZE, ","); found_all = true; if (type == -1) { diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 4d6e6090b8..49d49f76b9 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -59,7 +59,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) size_t other_keys_num = 0; // Number of keys that are not string, integral // or type keys. LuaTableProps ret; - memset(&ret, 0, sizeof(ret)); + CLEAR_FIELD(ret); if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { semsg(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 2); ret.type = kObjectTypeNil; diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 1c9e60e4b2..f6a85900ba 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -6,7 +6,7 @@ #include <stdint.h> #include "nvim/api/private/defs.h" -#include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/func_attr.h" typedef struct { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 661dbfc4c2..5d97f90bb1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -15,12 +15,13 @@ #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -37,7 +38,7 @@ #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/profile.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/undo.h" #include "nvim/usercmd.h" #include "nvim/version.h" @@ -447,7 +448,7 @@ static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread) FUNC_ATTR_NONNULL_ALL { nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state)); - memset(ref_state, 0, sizeof(*ref_state)); + CLEAR_POINTER(ref_state); ref_state->nil_ref = LUA_NOREF; ref_state->empty_dict_ref = LUA_NOREF; if (!is_thread) { @@ -1313,12 +1314,11 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) { - const linenr_T save_sourcing_lnum = sourcing_lnum; const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; - sourcing_lnum = 0; + estack_push(ETYPE_SCRIPT, name, 0); garray_T ga; char_u *line = NULL; @@ -1331,7 +1331,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) size_t len = strlen(code); nlua_typval_exec(code, len, name, NULL, 0, false, NULL); - sourcing_lnum = save_sourcing_lnum; + estack_pop(); current_sctx = save_current_sctx; ga_clear_strings(&ga); xfree(code); @@ -1906,7 +1906,7 @@ void nlua_set_sctx(sctx_T *current) break; } char *source_path = fix_fname(info->source + 1); - get_current_script_id((char_u *)source_path, current); + get_current_script_id(&source_path, current); xfree(source_path); current->sc_lnum = info->currentline; current->sc_seq = -1; diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 2afbbebfe7..78346fd81f 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -24,6 +24,8 @@ typedef struct { #endif } nlua_ref_state_t; +#define NLUA_EXEC_STATIC(cstr, arg, err) nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, err) + #define NLUA_CLEAR_REF(x) \ do { \ /* Take the address to avoid double evaluation. #1375 */ \ diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 6ba0056f48..5a82ae30b5 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -16,10 +16,11 @@ #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/eval.h" #include "nvim/eval/userfunc.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index 71f85385b6..b2b5dfedee 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -269,9 +269,9 @@ int nlua_xdl_diff(lua_State *lstate) xpparam_t params; xdemitcb_t ecb; - memset(&cfg, 0, sizeof(cfg)); - memset(¶ms, 0, sizeof(params)); - memset(&ecb, 0, sizeof(ecb)); + CLEAR_FIELD(cfg); + CLEAR_FIELD(params); + CLEAR_FIELD(ecb); NluaXdiffMode mode = kNluaXdiffModeUnified; diff --git a/src/nvim/main.c b/src/nvim/main.c index 494ff0b4af..fd31ba6c66 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -8,6 +8,7 @@ #include <stdint.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" @@ -16,6 +17,7 @@ #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" @@ -57,10 +59,10 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/profile.h" #include "nvim/quickfix.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/shada.h" #include "nvim/sign.h" #include "nvim/state.h" @@ -159,6 +161,7 @@ bool event_teardown(void) void early_init(mparm_T *paramp) { env_init(); + estack_init(); cmdline_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); @@ -1242,8 +1245,8 @@ static void command_line_scan(mparm_T *parmp) } else if (argv[0][0] == '-') { // "-S" followed by another option: use default session file. a = SESSION_FILE; - ++argc; - --argv; + argc++; + argv--; } else { a = argv[0]; } @@ -1418,7 +1421,7 @@ scripterror: * copied, so that they can be changed. */ static void init_params(mparm_T *paramp, int argc, char **argv) { - memset(paramp, 0, sizeof(*paramp)); + CLEAR_POINTER(paramp); paramp->argc = argc; paramp->argv = argv; paramp->use_debug_break_level = -1; @@ -1613,9 +1616,9 @@ static void create_windows(mparm_T *parmp) // Watch out for autocommands that delete a window. // // Don't execute Win/Buf Enter/Leave autocommands here - ++autocmd_no_enter; - ++autocmd_no_leave; - dorewind = TRUE; + autocmd_no_enter++; + autocmd_no_leave++; + dorewind = true; while (done++ < 1000) { if (dorewind) { if (parmp->window_layout == WIN_TABS) { @@ -1677,8 +1680,8 @@ static void create_windows(mparm_T *parmp) curwin = firstwin; } curbuf = curwin->w_buffer; - --autocmd_no_enter; - --autocmd_no_leave; + autocmd_no_enter--; + autocmd_no_leave--; } } @@ -1695,8 +1698,8 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) /* * Don't execute Win/Buf Enter/Leave autocommands here */ - ++autocmd_no_enter; - ++autocmd_no_leave; + autocmd_no_enter++; + autocmd_no_leave++; // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { @@ -1782,7 +1785,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) if (parmp->window_layout == WIN_TABS) { goto_tabpage(1); } - --autocmd_no_enter; + autocmd_no_enter--; // make the first window the current window win = firstwin; @@ -1796,7 +1799,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } win_enter(win, false); - --autocmd_no_leave; + autocmd_no_leave--; TIME_MSG("editing files in windows"); if (parmp->window_count > 1 && parmp->window_layout != WIN_TABS) { win_equal(curwin, false, 'b'); // adjust heights @@ -1814,12 +1817,12 @@ static void exe_pre_commands(mparm_T *parmp) if (cnt > 0) { curwin->w_cursor.lnum = 0; // just in case.. - sourcing_name = _("pre-vimrc command line"); + estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0); current_sctx.sc_sid = SID_CMDARG; for (i = 0; i < cnt; i++) { do_cmdline_cmd(cmds[i]); } - sourcing_name = NULL; + estack_pop(); current_sctx.sc_sid = 0; TIME_MSG("--cmd commands"); } @@ -1841,7 +1844,7 @@ static void exe_commands(mparm_T *parmp) if (parmp->tagname == NULL && curwin->w_cursor.lnum <= 1) { curwin->w_cursor.lnum = 0; } - sourcing_name = "command line"; + estack_push(ETYPE_ARGS, "command line", 0); current_sctx.sc_sid = SID_CARG; current_sctx.sc_seq = 0; for (i = 0; i < parmp->n_commands; i++) { @@ -1850,7 +1853,7 @@ static void exe_commands(mparm_T *parmp) xfree(parmp->commands[i]); } } - sourcing_name = NULL; + estack_pop(); current_sctx.sc_sid = 0; if (curwin->w_cursor.lnum == 0) { curwin->w_cursor.lnum = 1; @@ -2059,17 +2062,14 @@ static int execute_env(char *env) { const char *initstr = os_getenv(env); if (initstr != NULL) { - char_u *save_sourcing_name = (char_u *)sourcing_name; - linenr_T save_sourcing_lnum = sourcing_lnum; - sourcing_name = env; - sourcing_lnum = 0; + estack_push(ETYPE_ENV, env, 0); const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_ENV; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; do_cmdline_cmd((char *)initstr); - sourcing_name = (char *)save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + + estack_pop(); current_sctx = save_current_sctx; return OK; } diff --git a/src/nvim/main.h b/src/nvim/main.h index d5384ecc95..0c497a7c0e 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -2,7 +2,6 @@ #define NVIM_MAIN_H #include "nvim/event/loop.h" -#include "nvim/normal.h" // Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 342b1b0d47..64a798a27b 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -29,6 +29,7 @@ #include "nvim/message.h" #include "nvim/option.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -333,7 +334,7 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma { const char_u *to_parse = strargs; to_parse = (char_u *)skipwhite((char *)to_parse); - memset(mapargs, 0, sizeof(*mapargs)); + CLEAR_POINTER(mapargs); // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in // any order. @@ -469,7 +470,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, mp->m_script_ctx.sc_lnum = lnum; } else { mp->m_script_ctx = current_sctx; - mp->m_script_ctx.sc_lnum += sourcing_lnum; + mp->m_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&mp->m_script_ctx); } mp->m_desc = NULL; @@ -776,7 +777,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, mp->m_expr = args->expr; mp->m_replace_keycodes = args->replace_keycodes; mp->m_script_ctx = current_sctx; - mp->m_script_ctx.sc_lnum += sourcing_lnum; + mp->m_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&mp->m_script_ctx); if (args->desc != NULL) { mp->m_desc = xstrdup(args->desc); @@ -964,7 +965,7 @@ static int get_map_mode(char **cmdp, bool forceit) /// Clear all mappings (":mapclear") or abbreviations (":abclear"). /// "abbr" should be false for mappings, true for abbreviations. /// This function used to be called map_clear(). -static void do_mapclear(char_u *cmdp, char_u *arg, int forceit, int abbr) +static void do_mapclear(char *cmdp, char_u *arg, int forceit, int abbr) { int mode; int local; @@ -975,7 +976,7 @@ static void do_mapclear(char_u *cmdp, char_u *arg, int forceit, int abbr) return; } - mode = get_map_mode((char **)&cmdp, forceit); + mode = get_map_mode(&cmdp, forceit); map_clear_mode(curbuf, mode, local, abbr); } @@ -1051,9 +1052,9 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo int mode = 0; int retval; - char_u *buf = NULL; + char *buf = NULL; const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str), - (char **)&buf, REPTERM_DO_LT, + &buf, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); #define MAPMODE(mode, modechars, chr, modeflags) \ @@ -1194,14 +1195,14 @@ static char_u *translate_mapping(char_u *str, int cpo_flags) /// @param forceit true if '!' given /// @param isabbrev true if abbreviation /// @param isunmap true if unmap/unabbrev command -char_u *set_context_in_map_cmd(expand_T *xp, char_u *cmd, char_u *arg, bool forceit, bool isabbrev, +char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forceit, bool isabbrev, bool isunmap, cmdidx_T cmdidx) { if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) { xp->xp_context = EXPAND_NOTHING; } else { if (isunmap) { - expand_mapmodes = get_map_mode((char **)&cmd, forceit || isabbrev); + expand_mapmodes = get_map_mode(&cmd, forceit || isabbrev); } else { expand_mapmodes = MODE_INSERT | MODE_CMDLINE; if (!isabbrev) { @@ -2079,7 +2080,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } char *keys_buf = NULL; - char_u *alt_keys_buf = NULL; + char *alt_keys_buf = NULL; bool did_simplify = false; const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; const int mode = get_map_mode((char **)&which, 0); @@ -2096,9 +2097,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) // preferred for printing, like in do_map(). (void)replace_termcodes(keys, STRLEN(keys), - (char **)&alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, + &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS); - rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); + rhs = check_map((char_u *)alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); } if (!get_dict) { @@ -2439,13 +2440,13 @@ void ex_unmap(exarg_T *eap) /// ":mapclear" and friends. void ex_mapclear(exarg_T *eap) { - do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, eap->forceit, false); + do_mapclear(eap->cmd, (char_u *)eap->arg, eap->forceit, false); } /// ":abclear" and friends. void ex_abclear(exarg_T *eap) { - do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, true, true); + do_mapclear(eap->cmd, (char_u *)eap->arg, true, true); } /// Set, tweak, or remove a mapping in a mode. Acts as the implementation for diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 1fe3327b29..593275d489 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -19,7 +19,6 @@ #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -86,7 +85,7 @@ void clear_fmark(fmark_T *fm) FUNC_ATTR_NONNULL_ALL { free_fmark(*fm); - memset(fm, 0, sizeof(*fm)); + CLEAR_POINTER(fm); } /* @@ -1741,7 +1740,7 @@ void free_all_marks(void) free_xfmark(namedfm[i]); } } - memset(&namedfm[0], 0, sizeof(namedfm)); + CLEAR_FIELD(namedfm); } #endif diff --git a/src/nvim/match.c b/src/nvim/match.c index ba587c4141..1c34c9f004 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -7,14 +7,19 @@ #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/eval/funcs.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/match.h" #include "nvim/memline.h" +#include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "match.c.generated.h" @@ -692,7 +697,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match } // Highlight the match were the cursor is using the CurSearch // group. - if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC])) { + if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC))) { shl->attr_cur = win_hl_attr(wp, HLF_LC) ? win_hl_attr(wp, HLF_LC) : HL_ATTR(HLF_LC); } else { shl->attr_cur = shl->attr; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 223b4d6845..af9e214d92 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -39,6 +39,7 @@ #include "nvim/arabic.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/func_attr.h" @@ -49,7 +50,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/screen.h" @@ -74,6 +74,19 @@ struct interval { # include "unicode_tables.generated.h" #endif +static char e_list_item_nr_is_not_list[] + = N_("E1109: List item %d is not a List"); +static char e_list_item_nr_does_not_contain_3_numbers[] + = N_("E1110: List item %d does not contain 3 numbers"); +static char e_list_item_nr_range_invalid[] + = N_("E1111: List item %d range invalid"); +static char e_list_item_nr_cell_width_invalid[] + = N_("E1112: List item %d cell width invalid"); +static char e_overlapping_ranges_for_nr[] + = N_("E1113: Overlapping ranges for 0x%lx"); +static char e_only_values_of_0x100_and_higher_supported[] + = N_("E1114: Only values of 0x100 and higher supported"); + // To speed up BYTELEN(); keep a lookup table to quickly get the length in // bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes // which are illegal when used as the first byte have a 1. The NUL byte has @@ -472,13 +485,18 @@ static bool intable(const struct interval *table, size_t n_items, int c) int utf_char2cells(int c) { if (c >= 0x100) { + int n = cw_value(c); + if (n != 0) { + return n; + } + if (!utf_printable(c)) { return 6; // unprintable, displays <xxxx> } if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) { return 2; } - if (p_emoji && intable(emoji_width, ARRAY_SIZE(emoji_width), c)) { + if (p_emoji && intable(emoji_wide, ARRAY_SIZE(emoji_wide), c)) { return 2; } } else if (c >= 0x80 && !vim_isprintc(c)) { @@ -872,9 +890,9 @@ int utf_ptr2len_len(const char_u *p, int size) return len; } -/// Return the number of bytes occupied by a UTF-8 character in a string -/// +/// Return the number of bytes occupied by a UTF-8 character in a string. /// This includes following composing characters. +/// Returns zero for NUL. int utfc_ptr2len(const char *const p_in) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { @@ -988,8 +1006,9 @@ int utf_char2len(const int c) /// Convert Unicode character to UTF-8 string /// -/// @param c character to convert to \p buf -/// @param[out] buf UTF-8 string generated from \p c, does not add \0 +/// @param c character to convert to UTF-8 string in \p buf +/// @param[out] buf UTF-8 string generated from \p c, does not add \0 +/// must have room for at least 6 bytes /// @return Number of bytes (1-6). int utf_char2bytes(const int c, char *const buf) { @@ -1164,6 +1183,11 @@ int utf_class_tab(const int c, const uint64_t *const chartab) return 1; // punctuation } + // emoji + if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) { + return 3; + } + // binary search in table while (top >= bot) { mid = (bot + top) / 2; @@ -1176,11 +1200,6 @@ int utf_class_tab(const int c, const uint64_t *const chartab) } } - // emoji - if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) { - return 3; - } - // most other characters are "word" characters return 2; } @@ -1587,7 +1606,7 @@ void show_utf8(void) } sprintf((char *)IObuff + rlen, "%02x ", (line[i] == NL) ? NUL : line[i]); // NUL is stored as NL - --clen; + clen--; rlen += (int)STRLEN(IObuff + rlen); if (rlen > IOSIZE - 20) { break; @@ -1620,7 +1639,7 @@ int utf_head_off(const char_u *base, const char_u *p) // Move q to the first byte of this char. while (q > base && (*q & 0xc0) == 0x80) { - --q; + q--; } // Check for illegal sequence. Do allow an illegal byte after where we // started. @@ -1641,10 +1660,10 @@ int utf_head_off(const char_u *base, const char_u *p) if (arabic_maycombine(c)) { // Advance to get a sneak-peak at the next char const char_u *j = q; - --j; + j--; // Move j to the first byte of this char. while (j > base && (*j & 0xc0) == 0x80) { - --j; + j--; } if (arabic_combine(utf_ptr2char((char *)j), c)) { continue; @@ -1800,9 +1819,9 @@ bool utf_allow_break(int cc, int ncc) /// /// @param[in,out] fp Source of the character to copy. /// @param[in,out] tp Destination to copy to. -void mb_copy_char(const char_u **const fp, char_u **const tp) +void mb_copy_char(const char **const fp, char **const tp) { - const size_t l = (size_t)utfc_ptr2len((char *)(*fp)); + const size_t l = (size_t)utfc_ptr2len(*fp); memmove(*tp, *fp, l); *tp += l; @@ -2678,3 +2697,174 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp return retval; } + +/// Table set by setcellwidths(). +typedef struct { + long first; + long last; + char width; +} cw_interval_T; + +static cw_interval_T *cw_table = NULL; +static size_t cw_table_size = 0; + +/// Return the value of the cellwidth table for the character `c`. +/// +/// @param c The source character. +/// @return 1 or 2 when `c` is in the cellwidth table, 0 if not. +static int cw_value(int c) +{ + if (cw_table == NULL) { + return 0; + } + + // first quick check for Latin1 etc. characters + if (c < cw_table[0].first) { + return 0; + } + + // binary search in table + int bot = 0; + int top = (int)cw_table_size - 1; + while (top >= bot) { + int mid = (bot + top) / 2; + if (cw_table[mid].last < c) { + bot = mid + 1; + } else if (cw_table[mid].first > c) { + top = mid - 1; + } else { + return cw_table[mid].width; + } + } + return 0; +} + +static int tv_nr_compare(const void *a1, const void *a2) +{ + const listitem_T *const li1 = tv_list_first(*(const list_T **)a1); + const listitem_T *const li2 = tv_list_first(*(const list_T **)a2); + + return (int)(TV_LIST_ITEM_TV(li1)->vval.v_number - TV_LIST_ITEM_TV(li2)->vval.v_number); +} + +/// "setcellwidths()" function +void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + emsg(_(e_listreq)); + return; + } + const list_T *const l = argvars[0].vval.v_list; + if (tv_list_len(l) == 0) { + // Clearing the table. + xfree(cw_table); + cw_table = NULL; + cw_table_size = 0; + return; + } + + // Note: use list_T instead of listitem_T so that TV_LIST_ITEM_NEXT can be used properly below. + const list_T **ptrs = xmalloc(sizeof(const list_T *) * (size_t)tv_list_len(l)); + + // Check that all entries are a list with three numbers, the range is + // valid and the cell width is valid. + int item = 0; + TV_LIST_ITER_CONST(l, li, { + const typval_T *const li_tv = TV_LIST_ITEM_TV(li); + + if (li_tv->v_type != VAR_LIST || li_tv->vval.v_list == NULL) { + semsg(_(e_list_item_nr_is_not_list), item); + xfree(ptrs); + return; + } + + const list_T *const li_l = li_tv->vval.v_list; + ptrs[item] = li_l; + const listitem_T *lili = tv_list_first(li_l); + int i; + varnumber_T n1; + for (i = 0; lili != NULL; lili = TV_LIST_ITEM_NEXT(li_l, lili), i++) { + const typval_T *const lili_tv = TV_LIST_ITEM_TV(lili); + if (lili_tv->v_type != VAR_NUMBER) { + break; + } + if (i == 0) { + n1 = lili_tv->vval.v_number; + if (n1 < 0x100) { + emsg(_(e_only_values_of_0x100_and_higher_supported)); + xfree(ptrs); + return; + } + } else if (i == 1 && lili_tv->vval.v_number < n1) { + semsg(_(e_list_item_nr_range_invalid), item); + xfree(ptrs); + return; + } else if (i == 2 && (lili_tv->vval.v_number < 1 || lili_tv->vval.v_number > 2)) { + semsg(_(e_list_item_nr_cell_width_invalid), item); + xfree(ptrs); + return; + } + } + + if (i != 3) { + semsg(_(e_list_item_nr_does_not_contain_3_numbers), item); + xfree(ptrs); + return; + } + + item++; + }); + + // Sort the list on the first number. + qsort((void *)ptrs, (size_t)tv_list_len(l), sizeof(const list_T *), tv_nr_compare); + + cw_interval_T *table = xmalloc(sizeof(cw_interval_T) * (size_t)tv_list_len(l)); + + // Store the items in the new table. + for (item = 0; item < tv_list_len(l); item++) { + const list_T *const li_l = ptrs[item]; + const listitem_T *lili = tv_list_first(li_l); + const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number; + if (item > 0 && n1 <= table[item - 1].last) { + semsg(_(e_overlapping_ranges_for_nr), (long)n1); + xfree(ptrs); + xfree(table); + return; + } + table[item].first = n1; + lili = TV_LIST_ITEM_NEXT(li_l, lili); + table[item].last = TV_LIST_ITEM_TV(lili)->vval.v_number; + lili = TV_LIST_ITEM_NEXT(li_l, lili); + table[item].width = (char)TV_LIST_ITEM_TV(lili)->vval.v_number; + } + + xfree(ptrs); + + cw_interval_T *const cw_table_save = cw_table; + const size_t cw_table_size_save = cw_table_size; + cw_table = table; + cw_table_size = (size_t)tv_list_len(l); + + // Check that the new value does not conflict with 'listchars' or + // 'fillchars'. + const char *const error = check_chars_options(); + if (error != NULL) { + emsg(_(error)); + cw_table = cw_table_save; + cw_table_size = cw_table_size_save; + xfree(table); + return; + } + + xfree(cw_table_save); + redraw_all_later(NOT_VALID); +} + +void f_charclass(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (tv_check_for_string(&argvars[0]) == FAIL + || argvars[0].vval.v_string == NULL) { + return; + } + rettv->vval.v_number = mb_get_class((const char_u *)argvars[0].vval.v_string); +} diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index 1e5e332ad9..2a9afcbd03 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -5,8 +5,9 @@ #include <stdint.h> #include <string.h> +#include "nvim/eval/typval.h" #include "nvim/func_attr.h" -#include "nvim/iconv.h" +#include "nvim/mbyte_defs.h" #include "nvim/os/os_defs.h" // For indirect #include "nvim/types.h" // for char_u @@ -19,52 +20,6 @@ #define MB_BYTE2LEN(b) utf8len_tab[b] #define MB_BYTE2LEN_CHECK(b) (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b]) -// max length of an unicode char -#define MB_MAXCHAR 6 - -// properties used in enc_canon_table[] (first three mutually exclusive) -#define ENC_8BIT 0x01 -#define ENC_DBCS 0x02 -#define ENC_UNICODE 0x04 - -#define ENC_ENDIAN_B 0x10 // Unicode: Big endian -#define ENC_ENDIAN_L 0x20 // Unicode: Little endian - -#define ENC_2BYTE 0x40 // Unicode: UCS-2 -#define ENC_4BYTE 0x80 // Unicode: UCS-4 -#define ENC_2WORD 0x100 // Unicode: UTF-16 - -#define ENC_LATIN1 0x200 // Latin1 -#define ENC_LATIN9 0x400 // Latin9 -#define ENC_MACROMAN 0x800 // Mac Roman (not Macro Man! :-) - -/// Flags for vimconv_T -typedef enum { - CONV_NONE = 0, - CONV_TO_UTF8 = 1, - CONV_9_TO_UTF8 = 2, - CONV_TO_LATIN1 = 3, - CONV_TO_LATIN9 = 4, - CONV_ICONV = 5, -} ConvFlags; - -#define MBYTE_NONE_CONV { \ - .vc_type = CONV_NONE, \ - .vc_factor = 1, \ - .vc_fail = false, \ -} - -/// Structure used for string conversions -typedef struct { - int vc_type; ///< Zero or more ConvFlags. - int vc_factor; ///< Maximal expansion factor. -#ifdef HAVE_ICONV - iconv_t vc_fd; ///< Value for CONV_ICONV. -#endif - bool vc_fail; ///< What to do with invalid characters: if true, fail, - ///< otherwise use '?'. -} vimconv_T; - extern const uint8_t utf8len_tab_zero[256]; extern const uint8_t utf8len_tab[256]; diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h new file mode 100644 index 0000000000..53b01a211f --- /dev/null +++ b/src/nvim/mbyte_defs.h @@ -0,0 +1,56 @@ +#ifndef NVIM_MBYTE_DEFS_H +#define NVIM_MBYTE_DEFS_H + +#include <stdbool.h> + +#include "nvim/iconv.h" + +/// max length of an unicode char +enum { MB_MAXCHAR = 6, }; + +/// properties used in enc_canon_table[] (first three mutually exclusive) +enum { + ENC_8BIT = 0x01, + ENC_DBCS = 0x02, + ENC_UNICODE = 0x04, + + ENC_ENDIAN_B = 0x10, ///< Unicode: Big endian + ENC_ENDIAN_L = 0x20, ///< Unicode: Little endian + + ENC_2BYTE = 0x40, ///< Unicode: UCS-2 + ENC_4BYTE = 0x80, ///< Unicode: UCS-4 + ENC_2WORD = 0x100, ///< Unicode: UTF-16 + + ENC_LATIN1 = 0x200, ///< Latin1 + ENC_LATIN9 = 0x400, ///< Latin9 + ENC_MACROMAN = 0x800, ///< Mac Roman (not Macro Man! :-) +}; + +/// Flags for vimconv_T +typedef enum { + CONV_NONE = 0, + CONV_TO_UTF8 = 1, + CONV_9_TO_UTF8 = 2, + CONV_TO_LATIN1 = 3, + CONV_TO_LATIN9 = 4, + CONV_ICONV = 5, +} ConvFlags; + +#define MBYTE_NONE_CONV { \ + .vc_type = CONV_NONE, \ + .vc_factor = 1, \ + .vc_fail = false, \ +} + +/// Structure used for string conversions +typedef struct { + int vc_type; ///< Zero or more ConvFlags. + int vc_factor; ///< Maximal expansion factor. +#ifdef HAVE_ICONV + iconv_t vc_fd; ///< Value for CONV_ICONV. +#endif + bool vc_fail; ///< What to do with invalid characters: if true, fail, + ///< otherwise use '?'. +} vimconv_T; + +#endif // NVIM_MBYTE_DEFS_H diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index c828334eaf..216ee26620 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -821,7 +821,7 @@ static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) /// Initialize an empty hash table. static void mf_hash_init(mf_hashtab_T *mht) { - memset(mht, 0, sizeof(mf_hashtab_T)); + CLEAR_POINTER(mht); mht->mht_buckets = mht->mht_small_buckets; mht->mht_mask = MHT_INIT_SIZE - 1; } @@ -924,7 +924,7 @@ static void mf_hash_grow(mf_hashtab_T *mht) /// a power of two. mf_hashitem_T *tails[MHT_GROWTH_FACTOR]; - memset(tails, 0, sizeof(tails)); + CLEAR_FIELD(tails); for (mf_hashitem_T *mhi = mht->mht_buckets[i]; mhi != NULL; mhi = mhi->mhi_next) { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index fa3a400a68..23bc5d59c8 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -44,9 +44,11 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/func_attr.h" @@ -65,7 +67,6 @@ #include "nvim/os/process.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/spell.h" #include "nvim/strings.h" @@ -383,7 +384,7 @@ void ml_setname(buf_T *buf) bool success = false; memfile_T *mfp; char_u *fname; - char_u *dirp; + char *dirp; mfp = buf->b_ml.ml_mfp; if (mfp->mf_fd < 0) { // there is no swap file yet @@ -397,17 +398,14 @@ void ml_setname(buf_T *buf) return; } - /* - * Try all directories in the 'directory' option. - */ - dirp = p_dir; + // Try all directories in the 'directory' option. + dirp = (char *)p_dir; bool found_existing_dir = false; for (;;) { if (*dirp == NUL) { // tried all directories, fail break; } - fname = (char_u *)findswapname(buf, (char **)&dirp, (char *)mfp->mf_fname, - &found_existing_dir); + fname = (char_u *)findswapname(buf, &dirp, (char *)mfp->mf_fname, &found_existing_dir); // alloc's fname if (dirp == NULL) { // out of memory break; @@ -472,7 +470,7 @@ void ml_open_file(buf_T *buf) { memfile_T *mfp; char_u *fname; - char_u *dirp; + char *dirp; mfp = buf->b_ml.ml_mfp; if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf @@ -491,10 +489,8 @@ void ml_open_file(buf_T *buf) return; } - /* - * Try all directories in 'directory' option. - */ - dirp = p_dir; + // Try all directories in 'directory' option. + dirp = (char *)p_dir; bool found_existing_dir = false; for (;;) { if (*dirp == NUL) { @@ -503,8 +499,7 @@ void ml_open_file(buf_T *buf) // There is a small chance that between choosing the swap file name // and creating it, another Vim creates the file. In that case the // creation will fail and we will use another directory. - fname = (char_u *)findswapname(buf, (char **)&dirp, NULL, - &found_existing_dir); + fname = (char_u *)findswapname(buf, &dirp, NULL, &found_existing_dir); if (dirp == NULL) { break; // out of memory } @@ -1141,7 +1136,7 @@ void ml_recover(bool checkext) if (txt_start <= (int)HEADER_SIZE || txt_start >= (int)dp->db_txt_end) { p = (char_u *)"???"; - ++error; + error++; } else { p = (char_u *)dp + txt_start; } @@ -1210,10 +1205,10 @@ void ml_recover(bool checkext) if (got_int) { emsg(_("E311: Recovery Interrupted")); } else if (error) { - ++no_wait_return; + no_wait_return++; msg(">>>>>>>>>>>>>"); emsg(_("E312: Errors detected while recovering; look for lines starting with ???")); - --no_wait_return; + no_wait_return--; msg(_("See \":help E312\" for more information.")); msg(">>>>>>>>>>>>>"); } else { @@ -1272,7 +1267,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) int num_files; int file_count = 0; char **files; - char_u *dirp; + char *dirp; char_u *dir_name; char_u *fname_res = NULL; #ifdef HAVE_READLINK @@ -1299,12 +1294,12 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) // Do the loop for every directory in 'directory'. // First allocate some memory to put the directory name in. dir_name = xmalloc(STRLEN(p_dir) + 1); - dirp = p_dir; + dirp = (char *)p_dir; while (*dirp) { // Isolate a directory name from *dirp and put it in dir_name (we know // it is large enough, so use 31000 for length). // Advance dirp to next directory name. - (void)copy_option_part((char **)&dirp, (char *)dir_name, 31000, ","); + (void)copy_option_part(&dirp, (char *)dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir if (fname == NULL) { @@ -1395,7 +1390,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) file_count += num_files; if (nr <= file_count) { *fname_out = vim_strsave((char_u *)files[nr - 1 + num_files - file_count]); - dirp = (char_u *)""; // stop searching + dirp = ""; // stop searching } } else if (list) { if (dir_name[0] == '.' && dir_name[1] == NUL) { @@ -1660,12 +1655,12 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) p += i; // file name has been expanded to full path } if (STRCMP(p, names[num_names]) != 0) { - ++num_names; + num_names++; } else { xfree(names[num_names]); } } else { - ++num_names; + num_names++; } return num_names; @@ -2184,7 +2179,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b memmove((char *)dp_right + dp_right->db_txt_start, line, (size_t)len); - ++line_count_right; + line_count_right++; } /* * may move lines from the left/old block to the right/new one. @@ -2224,7 +2219,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b } memmove((char *)dp_left + dp_left->db_txt_start, line, (size_t)len); - ++line_count_left; + line_count_left++; } if (db_idx < 0) { // left block is new @@ -2998,9 +2993,9 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) * update high for insert/delete */ if (action == ML_INSERT) { - ++high; + high++; } else if (action == ML_DELETE) { - --high; + high--; } dp = hp->bh_data; @@ -3304,7 +3299,7 @@ static void attention_message(buf_T *buf, char_u *fname) { assert(buf->b_fname != NULL); - ++no_wait_return; + no_wait_return++; (void)emsg(_("E325: ATTENTION")); msg_puts(_("\nFound a swap file by the name \"")); msg_home_replace(fname); @@ -3339,7 +3334,7 @@ static void attention_message(buf_T *buf, char_u *fname) msg_outtrans((char *)fname); msg_puts(_("\"\n to avoid this message.\n")); cmdline_row = msg_row; - --no_wait_return; + no_wait_return--; } /// Trigger the SwapExists autocommands. diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 5ae7f7bbe3..fb36d4ccf4 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -9,6 +9,7 @@ #include <string.h> #include "nvim/api/extmark.h" +#include "nvim/arglist.h" #include "nvim/context.h" #include "nvim/decoration_provider.h" #include "nvim/eval.h" @@ -617,8 +618,10 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) #if defined(EXITFREE) +# include "nvim/autocmd.h" # include "nvim/buffer.h" # include "nvim/charset.h" +# include "nvim/cmdhist.h" # include "nvim/diff.h" # include "nvim/edit.h" # include "nvim/eval/typval.h" @@ -626,9 +629,9 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # include "nvim/ex_docmd.h" # include "nvim/ex_getln.h" # include "nvim/file_search.h" -# include "nvim/fileio.h" # include "nvim/fold.h" # include "nvim/getchar.h" +# include "nvim/grid.h" # include "nvim/mark.h" # include "nvim/mbyte.h" # include "nvim/memline.h" @@ -640,7 +643,6 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # include "nvim/path.h" # include "nvim/quickfix.h" # include "nvim/regexp.h" -# include "nvim/screen.h" # include "nvim/search.h" # include "nvim/spell.h" # include "nvim/syntax.h" diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 16802a4e50..c3cf4457fc 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -23,7 +23,7 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -952,7 +952,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for } while (*p != NUL && ascii_iswhite(*p)) { - ++p; + p++; } arg = after_dot = p; @@ -1659,26 +1659,19 @@ void ex_emenu(exarg_T *eap) if (arg[0] && ascii_iswhite(arg[1])) { switch (arg[0]) { case 'n': - mode_idx = MENU_INDEX_NORMAL; - break; + mode_idx = MENU_INDEX_NORMAL; break; case 'v': - mode_idx = MENU_INDEX_VISUAL; - break; + mode_idx = MENU_INDEX_VISUAL; break; case 's': - mode_idx = MENU_INDEX_SELECT; - break; + mode_idx = MENU_INDEX_SELECT; break; case 'o': - mode_idx = MENU_INDEX_OP_PENDING; - break; + mode_idx = MENU_INDEX_OP_PENDING; break; case 't': - mode_idx = MENU_INDEX_TERMINAL; - break; + mode_idx = MENU_INDEX_TERMINAL; break; case 'i': - mode_idx = MENU_INDEX_INSERT; - break; + mode_idx = MENU_INDEX_INSERT; break; case 'c': - mode_idx = MENU_INDEX_CMDLINE; - break; + mode_idx = MENU_INDEX_CMDLINE; break; default: semsg(_(e_invarg2), arg); return; @@ -1814,9 +1807,9 @@ static char *menu_skip_part(char *p) { while (*p != NUL && *p != '.' && !ascii_iswhite(*p)) { if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { - ++p; + p++; } - ++p; + p++; } return p; } diff --git a/src/nvim/menu.h b/src/nvim/menu.h index 9a60ebfb83..be294a1831 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -4,28 +4,9 @@ #include <stdbool.h> // for bool #include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/menu_defs.h" #include "nvim/types.h" // for char_u and expand_T -/// @} -/// note MENU_INDEX_TIP is not a 'real' mode - -/// Menu modes -/// \addtogroup MENU_MODES -/// @{ -#define MENU_NORMAL_MODE (1 << MENU_INDEX_NORMAL) -#define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL) -#define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT) -#define MENU_OP_PENDING_MODE (1 << MENU_INDEX_OP_PENDING) -#define MENU_INSERT_MODE (1 << MENU_INDEX_INSERT) -#define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) -#define MENU_TERMINAL_MODE (1 << MENU_INDEX_TERMINAL) -#define MENU_TIP_MODE (1 << MENU_INDEX_TIP) -#define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1) -/// @} - -/// Start a menu name with this to not include it on the main menu bar -#define MNU_HIDDEN_CHAR ']' - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "menu.h.generated.h" #endif diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h new file mode 100644 index 0000000000..5fdb222bde --- /dev/null +++ b/src/nvim/menu_defs.h @@ -0,0 +1,64 @@ +#ifndef NVIM_MENU_DEFS_H +#define NVIM_MENU_DEFS_H + +#include <stdbool.h> // for bool + +/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode +/// \addtogroup MENU_INDEX +/// @{ +enum { + MENU_INDEX_INVALID = -1, + MENU_INDEX_NORMAL = 0, + MENU_INDEX_VISUAL = 1, + MENU_INDEX_SELECT = 2, + MENU_INDEX_OP_PENDING = 3, + MENU_INDEX_INSERT = 4, + MENU_INDEX_CMDLINE = 5, + MENU_INDEX_TERMINAL = 6, + MENU_INDEX_TIP = 7, + MENU_MODES = 8, +}; +/// @} + +/// Menu modes +/// \addtogroup MENU_MODES +/// @{ +enum { + MENU_NORMAL_MODE = 1 << MENU_INDEX_NORMAL, + MENU_VISUAL_MODE = 1 << MENU_INDEX_VISUAL, + MENU_SELECT_MODE = 1 << MENU_INDEX_SELECT, + MENU_OP_PENDING_MODE = 1 << MENU_INDEX_OP_PENDING, + MENU_INSERT_MODE = 1 << MENU_INDEX_INSERT, + MENU_CMDLINE_MODE = 1 << MENU_INDEX_CMDLINE, + MENU_TERMINAL_MODE = 1 << MENU_INDEX_TERMINAL, + MENU_TIP_MODE = 1 << MENU_INDEX_TIP, + MENU_ALL_MODES = (1 << MENU_INDEX_TIP) - 1, +}; +/// @} +/// note MENU_INDEX_TIP is not a 'real' mode + +/// Start a menu name with this to not include it on the main menu bar +#define MNU_HIDDEN_CHAR ']' + +typedef struct VimMenu vimmenu_T; + +struct VimMenu { + int modes; ///< Which modes is this menu visible for + int enabled; ///< for which modes the menu is enabled + char *name; ///< Name of menu, possibly translated + char *dname; ///< Displayed Name ("name" without '&') + char *en_name; ///< "name" untranslated, NULL when + ///< was not translated + char *en_dname; ///< NULL when "dname" untranslated + int mnemonic; ///< mnemonic key (after '&') + char *actext; ///< accelerator text (after TAB) + long priority; ///< Menu order priority + char *strings[MENU_MODES]; ///< Mapped string for each mode + int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode + bool silent[MENU_MODES]; ///< A silent flag for each mode + vimmenu_T *children; ///< Children of sub-menu + vimmenu_T *parent; ///< Parent of menu + vimmenu_T *next; ///< Next item in menu +}; + +#endif // NVIM_MENU_DEFS_H diff --git a/src/nvim/message.c b/src/nvim/message.c index 7cccd046c9..684cf7207c 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -15,6 +15,7 @@ #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -23,7 +24,9 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/indent.h" #include "nvim/input.h" #include "nvim/keycodes.h" #include "nvim/main.h" @@ -38,7 +41,7 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -318,7 +321,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) if (entered >= 3) { return TRUE; } - ++entered; + entered++; // Add message to history (unless it's a repeated kept message or a // truncated message) @@ -355,7 +358,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) need_fileinfo = false; xfree(buf); - --entered; + entered--; return retval; } @@ -524,7 +527,7 @@ int smsg_attr_keep(int attr, const char *s, ...) * isn't printed each time when it didn't change. */ static int last_sourcing_lnum = 0; -static char_u *last_sourcing_name = NULL; +static char *last_sourcing_name = NULL; /// Reset the last used sourcing name/lnum. Makes sure it is displayed again /// for the next error message; @@ -534,16 +537,16 @@ void reset_last_sourcing(void) last_sourcing_lnum = 0; } -/// @return TRUE if "sourcing_name" differs from "last_sourcing_name". -static int other_sourcing_name(void) +/// @return true if "SOURCING_NAME" differs from "last_sourcing_name". +static bool other_sourcing_name(void) { - if (sourcing_name != NULL) { + if (SOURCING_NAME != NULL) { if (last_sourcing_name != NULL) { - return STRCMP(sourcing_name, last_sourcing_name) != 0; + return strcmp(SOURCING_NAME, last_sourcing_name) != 0; } - return TRUE; + return true; } - return FALSE; + return false; } /// Get the message about the source, as used for an error message @@ -553,11 +556,19 @@ static int other_sourcing_name(void) static char *get_emsg_source(void) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { - if (sourcing_name != NULL && other_sourcing_name()) { + if (SOURCING_NAME != NULL && other_sourcing_name()) { + char *sname = estack_sfile(ESTACK_NONE); + char *tofree = sname; + + if (sname == NULL) { + sname = SOURCING_NAME; + } + const char *const p = _("Error detected while processing %s:"); - const size_t buf_len = STRLEN(sourcing_name) + strlen(p) + 1; + const size_t buf_len = STRLEN(sname) + strlen(p) + 1; char *const buf = xmalloc(buf_len); - snprintf(buf, buf_len, p, sourcing_name); + snprintf(buf, buf_len, p, sname); + xfree(tofree); return buf; } return NULL; @@ -572,13 +583,13 @@ static char *get_emsg_lnum(void) { // lnum is 0 when executing a command from the command line // argument, we don't want a line number then - if (sourcing_name != NULL - && (other_sourcing_name() || sourcing_lnum != last_sourcing_lnum) - && sourcing_lnum != 0) { + if (SOURCING_NAME != NULL + && (other_sourcing_name() || SOURCING_LNUM != last_sourcing_lnum) + && SOURCING_LNUM != 0) { const char *const p = _("line %4ld:"); const size_t buf_len = 20 + strlen(p); char *const buf = xmalloc(buf_len); - snprintf(buf, buf_len, p, (long)sourcing_lnum); + snprintf(buf, buf_len, p, (long)SOURCING_LNUM); return buf; } return NULL; @@ -589,6 +600,15 @@ static char *get_emsg_lnum(void) /// is only displayed if it changed. void msg_source(int attr) { + static bool recursive = false; + + // Bail out if something called here causes an error. + if (recursive) { + return; + } + recursive = true; + + msg_scroll = true; // this will take more than one line no_wait_return++; char *p = get_emsg_source(); if (p != NULL) { @@ -599,19 +619,19 @@ void msg_source(int attr) if (p != NULL) { msg_attr(p, HL_ATTR(HLF_N)); xfree(p); - last_sourcing_lnum = sourcing_lnum; // only once for each line + last_sourcing_lnum = SOURCING_LNUM; // only once for each line } // remember the last sourcing name printed, also when it's empty - if (sourcing_name == NULL || other_sourcing_name()) { - xfree(last_sourcing_name); - if (sourcing_name == NULL) { - last_sourcing_name = NULL; - } else { - last_sourcing_name = vim_strsave((char_u *)sourcing_name); + if (SOURCING_NAME == NULL || other_sourcing_name()) { + XFREE_CLEAR(last_sourcing_name); + if (SOURCING_NAME != NULL) { + last_sourcing_name = xstrdup(SOURCING_NAME); } } - --no_wait_return; + no_wait_return--; + + recursive = false; } /// @return TRUE if not giving error messages right now: @@ -686,9 +706,9 @@ static bool emsg_multiline(const char *s, bool multiline) } // Log (silent) errors as debug messages. - if (sourcing_name != NULL && sourcing_lnum != 0) { + if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) { DLOG("(:silent) %s (%s (line %ld))", - s, sourcing_name, (long)sourcing_lnum); + s, SOURCING_NAME, (long)SOURCING_LNUM); } else { DLOG("(:silent) %s", s); } @@ -697,8 +717,8 @@ static bool emsg_multiline(const char *s, bool multiline) } // Log editor errors as INFO. - if (sourcing_name != NULL && sourcing_lnum != 0) { - ILOG("%s (%s (line %ld))", s, sourcing_name, (long)sourcing_lnum); + if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) { + ILOG("%s (%s (line %ld))", s, SOURCING_NAME, (long)SOURCING_LNUM); } else { ILOG("%s", s); } @@ -722,7 +742,6 @@ static bool emsg_multiline(const char *s, bool multiline) } emsg_on_display = true; // remember there is an error message - msg_scroll++; // don't overwrite a previous message attr = HL_ATTR(HLF_E); // set highlight mode for error messages if (msg_scrolled != 0) { need_wait_return = true; // needed in case emsg() is called after @@ -733,9 +752,8 @@ static bool emsg_multiline(const char *s, bool multiline) msg_ext_set_kind("emsg"); } - /* - * Display name and line number for the source of the error. - */ + // Display name and line number for the source of the error. + // Sets "msg_scroll". msg_source(attr); // Display the error message itself. @@ -812,8 +830,15 @@ static bool semsgv(const char *fmt, va_list ap) /// detected when fuzzing vim. void iemsg(const char *s) { + if (emsg_not_now()) { + return; + } + emsg(s); #ifdef ABORT_ON_INTERNAL_ERROR + set_vim_var_string(VV_ERRMSG, s, -1); + msg_putchar('\n'); // avoid overwriting the error message + ui_flush(); abort(); #endif } @@ -823,11 +848,17 @@ void iemsg(const char *s) /// detected when fuzzing vim. void siemsg(const char *s, ...) { + if (emsg_not_now()) { + return; + } + va_list ap; va_start(ap, s); (void)semsgv(s, ap); va_end(ap); #ifdef ABORT_ON_INTERNAL_ERROR + msg_putchar('\n'); // avoid overwriting the error message + ui_flush(); abort(); #endif } @@ -999,7 +1030,7 @@ int delete_first_msg(void) xfree(p->msg); hl_msg_free(p->multiattr); xfree(p); - --msg_hist_len; + msg_hist_len--; return OK; } @@ -1964,13 +1995,13 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) msg_col -= cw; if (msg_col == 0) { msg_col = Columns; - ++msg_row; + msg_row++; } } else { msg_col += cw; if (msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } return s + l; @@ -2123,7 +2154,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs int t_col = 0; // Screen cells todo, 0 when "t_s" not used. int l; int cw; - const char_u *sb_str = str; + const char *sb_str = (char *)str; int sb_col = msg_col; int wrap; int did_last_char; @@ -2208,7 +2239,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs if (p_more) { // Store text for scrolling back. - store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); + store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true); } inc_msg_scrolled(); @@ -2223,7 +2254,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs * for a character. */ if (lines_left > 0) { - --lines_left; + lines_left--; } if (p_more && lines_left == 0 && State != MODE_HITRETURN && !msg_no_more && !exmode_active) { @@ -2255,7 +2286,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs if (wrap && p_more && !recurse) { // Store text for scrolling back. - store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); + store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true); } if (*s == '\n') { // go to next line @@ -2272,7 +2303,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_col = 0; } else if (*s == '\b') { // go to previous char if (msg_col) { - --msg_col; + msg_col--; } } else if (*s == TAB) { // translate Tab into spaces do { @@ -2306,7 +2337,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs s += l - 1; } } - ++s; + s++; } // Output any postponed text. @@ -2314,7 +2345,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs t_puts(&t_col, t_s, s, attr); } if (p_more && !recurse) { - store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, false); + store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, false); } msg_check(); @@ -2457,7 +2488,7 @@ void msg_reset_scroll(void) static void inc_msg_scrolled(void) { if (*get_vim_var_str(VV_SCROLLSTART) == NUL) { - char *p = sourcing_name; + char *p = SOURCING_NAME; char *tofree = NULL; // v:scrollstart is empty, set it to the script/function name and line @@ -2468,7 +2499,7 @@ static void inc_msg_scrolled(void) size_t len = strlen(p) + 40; tofree = xmalloc(len); vim_snprintf(tofree, len, _("%s line %" PRId64), - p, (int64_t)sourcing_lnum); + p, (int64_t)SOURCING_LNUM); p = tofree; } set_vim_var_string(VV_SCROLLSTART, p, -1); @@ -2497,7 +2528,7 @@ static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE; /// @param sb_str start of string /// @param s just after string /// @param finish line ends -static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int finish) +static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int finish) { msgchunk_T *mp; @@ -2654,7 +2685,7 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) msg_col = mp->sb_msg_col; p = mp->sb_text; if (*p == '\n') { // don't display the line break - ++p; + p++; } msg_puts_display(p, -1, mp->sb_attr, TRUE); if (mp->sb_eol || mp->sb_next == NULL) { @@ -2683,7 +2714,7 @@ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) } if (msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } @@ -2938,7 +2969,7 @@ static int do_more_prompt(int typed_char) HL_ATTR(HLF_MSG)); for (i = 0; mp != NULL && i < Rows - 1; i++) { mp = disp_sb_line(i, mp); - ++msg_scrolled; + msg_scrolled++; } to_redraw = false; } @@ -3037,12 +3068,12 @@ static void msg_screen_putchar(int c, int attr) if (cmdmsg_rl) { if (--msg_col == 0) { msg_col = Columns; - ++msg_row; + msg_row++; } } else { if (++msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } } @@ -3339,7 +3370,7 @@ int redirecting(void) void verbose_enter(void) { if (*p_vfile != NUL) { - ++msg_silent; + msg_silent++; } } @@ -3358,7 +3389,7 @@ void verbose_leave(void) void verbose_enter_scroll(void) { if (*p_vfile != NUL) { - ++msg_silent; + msg_silent++; } else { // always scroll up, don't overwrite msg_scroll = TRUE; @@ -3520,7 +3551,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl * Since we wait for a keypress, don't make the * user press RETURN as well afterwards. */ - ++no_wait_return; + no_wait_return++; hotkeys = msg_show_console_dialog(message, buttons, dfltbutton); for (;;) { @@ -3569,7 +3600,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl msg_silent = save_msg_silent; State = oldState; setmouse(); - --no_wait_return; + no_wait_return--; msg_end_prompt(); return retval; @@ -3723,7 +3754,7 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int def } } else if (*r == DLG_HOTKEY_CHAR || first_hotkey) { if (*r == DLG_HOTKEY_CHAR) { - ++r; + r++; } first_hotkey = false; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index a4a521fa80..a8d0b3b584 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -8,13 +8,14 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/fold.h" +#include "nvim/grid.h" #include "nvim/memline.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/os_unix.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -386,7 +387,7 @@ retnomove: count = 0; for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count;) { if (curwin->w_topfill > 0) { - ++count; + count++; } else { count += plines_win(curwin, curwin->w_topline, true); } @@ -514,7 +515,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) break; // past end of file } row -= count; - ++lnum; + lnum++; } if (!retval) { diff --git a/src/nvim/move.c b/src/nvim/move.c index 6d4eb8ef49..1ed7acd012 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -21,16 +21,18 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/window.h" @@ -114,7 +116,7 @@ static void redraw_for_cursorcolumn(win_T *wp) FUNC_ATTR_NONNULL_ALL { if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { - if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC]) && using_hlsearch())) { + if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) { // When 'cursorcolumn' is set or "CurSearch" is in use // need to redraw with SOME_VALID. redraw_later(wp, SOME_VALID); @@ -1056,7 +1058,7 @@ bool scrolldown(long line_count, int byfold) // A sequence of folded lines only counts for one logical line linenr_T first; if (hasFolding(curwin->w_topline, &first, NULL)) { - ++done; + done++; if (!byfold) { line_count -= curwin->w_topline - first - 1; } @@ -1092,7 +1094,7 @@ bool scrolldown(long line_count, int byfold) while (wrow >= curwin->w_height_inner && curwin->w_cursor.lnum > 1) { linenr_T first; if (hasFolding(curwin->w_cursor.lnum, &first, NULL)) { - --wrow; + wrow--; if (first == 1) { curwin->w_cursor.lnum = 1; } else { @@ -1406,8 +1408,8 @@ void scroll_cursor_top(int min_scroll, int always) } if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) { - --top; - ++bot; + top--; + bot++; } else { top = curwin->w_cursor.lnum - 1; bot = curwin->w_cursor.lnum + 1; @@ -1453,8 +1455,8 @@ void scroll_cursor_top(int min_scroll, int always) extra += i; new_topline = top; - --top; - ++bot; + top--; + bot++; } /* @@ -1664,7 +1666,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) for (i = 0; i < scrolled && boff.lnum < curwin->w_botline;) { botline_forw(curwin, &boff); i += boff.height; - ++line_count; + line_count++; } if (i < scrolled) { // below curwin->w_botline, don't scroll line_count = 9999; @@ -1726,7 +1728,7 @@ void scroll_cursor_halfway(int atend) } else { ++below; // count a "~" line if (atend) { - ++used; + used++; } } } @@ -1835,7 +1837,7 @@ void cursor_correct(void) if (topline < botline) { above += win_get_fill(curwin, topline + 1); } - ++topline; + topline++; } } if (topline == botline || botline == 0) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2d752eade7..c87c0cbb6e 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -18,10 +18,13 @@ #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval.h" #include "nvim/eval/userfunc.h" #include "nvim/event/loop.h" #include "nvim/ex_cmds.h" @@ -32,7 +35,8 @@ #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" +#include "nvim/help.h" #include "nvim/indent.h" #include "nvim/keycodes.h" #include "nvim/log.h" @@ -41,6 +45,7 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -50,11 +55,12 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/plines.h" +#include "nvim/profile.h" #include "nvim/quickfix.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -465,7 +471,7 @@ void normal_enter(bool cmdwin, bool noexmode) static void normal_prepare(NormalState *s) { - memset(&s->ca, 0, sizeof(s->ca)); // also resets ca.retval + CLEAR_FIELD(s->ca); // also resets s->ca.retval s->ca.oap = &s->oa; // Use a count remembered from before entering an operator. After typing "3d" @@ -1276,10 +1282,10 @@ static void normal_redraw(NormalState *s) if (VIsual_active) { redraw_curbuf_later(INVERTED); // update inverted part - update_screen(INVERTED); + update_screen(0); } else if (must_redraw) { update_screen(0); - } else if (redraw_cmdline || clear_cmdline) { + } else if (redraw_cmdline || clear_cmdline || redraw_mode) { showmode(); } @@ -1832,7 +1838,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } if (jump_flags) { jump_flags = jump_to_mouse(jump_flags, NULL, which_button); - update_curbuf(VIsual_active ? INVERTED : VALID); + redraw_curbuf_later(VIsual_active ? INVERTED : VALID); + update_screen(0); setcursor(); ui_flush(); // Update before showing popup menu } @@ -2383,7 +2390,7 @@ static bool find_is_eval_item(const char_u *const ptr, int *const colp, int *con /// /// If text is found, a pointer to the text is put in "*text". This /// points into the current buffer line and is not always NUL terminated. -size_t find_ident_under_cursor(char_u **text, int find_type) +size_t find_ident_under_cursor(char **text, int find_type) FUNC_ATTR_NONNULL_ARG(1) { return find_ident_at_pos(curwin, curwin->w_cursor.lnum, @@ -2394,7 +2401,7 @@ size_t find_ident_under_cursor(char_u **text, int find_type) /// However: Uses 'iskeyword' from the current window!. /// /// @param textcol column where "text" starts, can be NULL -size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char_u **text, int *textcol, +size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text, int *textcol, int find_type) FUNC_ATTR_NONNULL_ARG(1, 4) { @@ -2470,7 +2477,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char_u **te return 0; } ptr += col; - *text = ptr; + *text = (char *)ptr; if (textcol != NULL) { *textcol = col; } @@ -3035,9 +3042,9 @@ static void nv_page(cmdarg_T *cap) static void nv_gd(oparg_T *oap, int nchar, int thisblock) { size_t len; - char_u *ptr; + char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 - || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { + || !find_decl((char_u *)ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); } else { if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { @@ -3557,7 +3564,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) if (checkclearop(cap->oap)) { return OK; } - char_u *ptr = NULL; + char *ptr = NULL; size_t len; if (VIsual_active && !get_visual_text(cap, &ptr, &len)) { return FAIL; @@ -3572,7 +3579,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) len = spell_move_to(curwin, FORWARD, true, true, NULL); emsg_off--; if (len != 0 && curwin->w_cursor.col <= pos.col) { - ptr = ml_get_pos(&curwin->w_cursor); + ptr = (char *)ml_get_pos(&curwin->w_cursor); } curwin->w_cursor = pos; } @@ -3581,7 +3588,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) return FAIL; } assert(len <= INT_MAX); - spell_add_word(ptr, (int)len, + spell_add_word((char_u *)ptr, (int)len, nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD, (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, undo); @@ -4148,7 +4155,7 @@ void do_nv_ident(int c1, int c2) cmdarg_T ca; clear_oparg(&oa); - memset(&ca, 0, sizeof(ca)); + CLEAR_FIELD(ca); ca.oap = &oa; ca.cmdchar = c1; ca.nchar = c2; @@ -4157,7 +4164,7 @@ void do_nv_ident(int c1, int c2) /// 'K' normal-mode command. Get the command to lookup the keyword under the /// cursor. -static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char_u **ptr_arg, +static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char **ptr_arg, size_t n, char *buf, size_t buf_size) { if (kp_help) { @@ -4176,7 +4183,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c return n; } - char_u *ptr = *ptr_arg; + char *ptr = *ptr_arg; // An external command will probably use an argument starting // with "-" as an option. To avoid trouble we skip the "-". @@ -4226,7 +4233,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c /// g ']' :tselect for current identifier static void nv_ident(cmdarg_T *cap) { - char_u *ptr = NULL; + char *ptr = NULL; char_u *p; size_t n = 0; // init for GCC int cmdchar; @@ -4272,7 +4279,7 @@ static void nv_ident(cmdarg_T *cap) assert(*kp != NUL); // option.c:do_set() should default to ":help" if empty. bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command bool kp_help = (STRCMP(kp, ":he") == 0 || STRCMP(kp, ":help") == 0); - if (kp_help && *skipwhite((char *)ptr) == NUL) { + if (kp_help && *skipwhite(ptr) == NUL) { emsg(_(e_noident)); // found white space only return; } @@ -4288,9 +4295,9 @@ static void nv_ident(cmdarg_T *cap) // Call setpcmark() first, so "*``" puts the cursor back where // it was. setpcmark(); - curwin->w_cursor.col = (colnr_T)(ptr - get_cursor_line_ptr()); + curwin->w_cursor.col = (colnr_T)(ptr - (char *)get_cursor_line_ptr()); - if (!g_cmd && vim_iswordp(ptr)) { + if (!g_cmd && vim_iswordp((char_u *)ptr)) { STRCPY(buf, "\\<"); } no_smartcase = true; // don't use 'smartcase' now @@ -4327,13 +4334,13 @@ static void nv_ident(cmdarg_T *cap) // Now grab the chars in the identifier if (cmdchar == 'K' && !kp_help) { - ptr = vim_strnsave(ptr, n); + ptr = xstrnsave(ptr, n); if (kp_ex) { // Escape the argument properly for an Ex command p = (char_u *)vim_strsave_fnameescape((const char *)ptr, VSE_NONE); } else { // Escape the argument properly for a shell command - p = vim_strsave_shellescape(ptr, true, true); + p = vim_strsave_shellescape((char_u *)ptr, true, true); } xfree(ptr); char *newbuf = xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1); @@ -4364,11 +4371,11 @@ static void nv_ident(cmdarg_T *cap) } // When current byte is a part of multibyte character, copy all // bytes of that character. - const size_t len = (size_t)(utfc_ptr2len((char *)ptr) - 1); + const size_t len = (size_t)(utfc_ptr2len(ptr) - 1); for (size_t i = 0; i < len && n > 0; i++, n--) { - *p++ = *ptr++; + *p++ = (char_u)(*ptr++); } - *p++ = *ptr++; + *p++ = (char_u)(*ptr++); } *p = NUL; } @@ -4376,12 +4383,14 @@ static void nv_ident(cmdarg_T *cap) // Execute the command. if (cmdchar == '*' || cmdchar == '#') { if (!g_cmd - && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) { + && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), (char_u *)ptr))) { STRCAT(buf, "\\>"); } + // put pattern in search history init_history(); add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0, NULL); } else { @@ -4406,7 +4415,7 @@ static void nv_ident(cmdarg_T *cap) /// @param lenp return: length of selected text /// /// @return false if more than one line selected. -bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) +bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp) { if (VIsual_mode != 'V') { unadjust_for_sel(); @@ -4418,14 +4427,14 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) return false; } if (VIsual_mode == 'V') { - *pp = get_cursor_line_ptr(); + *pp = (char *)get_cursor_line_ptr(); *lenp = STRLEN(*pp); } else { if (lt(curwin->w_cursor, VIsual)) { - *pp = ml_get_pos(&curwin->w_cursor); + *pp = (char *)ml_get_pos(&curwin->w_cursor); *lenp = (size_t)VIsual.col - (size_t)curwin->w_cursor.col + 1; } else { - *pp = ml_get_pos(&VIsual); + *pp = (char *)ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } if (**pp == NUL) { @@ -4433,7 +4442,7 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) } if (*lenp > 0) { // Correct the length to include all bytes of the last character. - *lenp += (size_t)(utfc_ptr2len((char *)(*pp) + (*lenp - 1)) - 1); + *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); } } reset_VIsual_and_resel(); @@ -4842,7 +4851,7 @@ static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrap cap->oap->use_reg_one = true; curwin->w_set_curswant = true; - memset(&sia, 0, sizeof(sia)); + CLEAR_FIELD(sia); i = do_search(cap->oap, dir, dir, pat, cap->count1, opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { @@ -5044,15 +5053,15 @@ static void nv_brackets(cmdarg_T *cap) // fwd bwd fwd bwd fwd bwd // identifier "]i" "[i" "]I" "[I" "]^I" "[^I" // define "]d" "[d" "]D" "[D" "]^D" "[^D" - char_u *ptr; + char *ptr; size_t len; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { clearop(cap->oap); } else { // Make a copy, if the line was changed it will be freed. - ptr = vim_strnsave(ptr, len); - find_pattern_in_path(ptr, 0, len, true, + ptr = xstrnsave(ptr, len); + find_pattern_in_path((char_u *)ptr, 0, len, true, cap->count0 == 0 ? !isupper(cap->nchar) : false, (((cap->nchar & 0xf) == ('d' & 0xf)) ? FIND_DEFINE @@ -6923,6 +6932,10 @@ static void nv_esc(cmdarg_T *cap) } } + if (restart_edit != 0) { + redraw_mode = true; // remove "-- (insert) --" + } + restart_edit = 0; if (cmdwin_type != 0) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 142a19e6d3..e121118fe9 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -17,6 +17,7 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -25,7 +26,6 @@ #include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/globals.h" @@ -48,7 +48,6 @@ #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/plines.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -1177,14 +1176,14 @@ static int stuff_yank(int regname, char_u *p) } yankreg_T *reg = get_yank_register(regname, YREG_YANK); if (is_append_register(regname) && reg->y_array != NULL) { - char_u **pp = (char_u **)&(reg->y_array[reg->y_size - 1]); + char **pp = &(reg->y_array[reg->y_size - 1]); char_u *lp = xmalloc(STRLEN(*pp) + STRLEN(p) + 1); STRCPY(lp, *pp); // TODO(philix): use xstpcpy() in stuff_yank() STRCAT(lp, p); xfree(p); xfree(*pp); - *pp = lp; + *pp = (char *)lp; } else { free_register(reg); set_yreg_additional_data(reg, NULL); @@ -1459,7 +1458,7 @@ int insert_reg(int regname, bool literally_arg) return FAIL; } - char_u *arg; + char *arg; if (regname == '.') { // Insert last inserted text. retval = stuff_inserted(NUL, 1L, true); } else if (get_spec_reg(regname, &arg, &allocated, true)) { @@ -1524,15 +1523,6 @@ static void stuffescaped(const char *arg, bool literally) } } -/// If "regname" is a special register, return true and store a pointer to its -/// value in "argp". -/// -/// @param allocated return: true when value was allocated -/// @param errmsg give error message when failing -/// -/// @return true if "regname" is a special register, -bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg); - /// Converts a yankreg to a dict which can be used as an argument to the // userregfunc. static dict_T* yankreg_to_dict(yankreg_T* yankreg) { @@ -1606,14 +1596,14 @@ static int eval_yank_userreg(const char_u *ufn, int regname, yankreg_T *reg) return ret; } -// If "regname" is a special register, return true and store a pointer to its -// value in "argp". -bool get_spec_reg( - int regname, - char_u **argp, - bool *allocated, // return: true when value was allocated - bool errmsg // give error message when failing -) +/// If "regname" is a special register, return true and store a pointer to its +/// value in "argp". +/// +/// @param allocated return: true when value was allocated +/// @param errmsg give error message when failing +/// +/// @return true if "regname" is a special register, +bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) { size_t cnt; @@ -1624,15 +1614,15 @@ bool get_spec_reg( if (errmsg) { check_fname(); // will give emsg if not set } - *argp = (char_u *)curbuf->b_fname; + *argp = curbuf->b_fname; return true; case '#': // alternate file name - *argp = (char_u *)getaltfname(errmsg); // may give emsg if not set + *argp = getaltfname(errmsg); // may give emsg if not set return true; case '=': // result of expression - *argp = get_expr_line(); + *argp = (char *)get_expr_line(); *allocated = true; return true; @@ -1640,18 +1630,18 @@ bool get_spec_reg( if (last_cmdline == NULL && errmsg) { emsg(_(e_nolastcmd)); } - *argp = last_cmdline; + *argp = (char *)last_cmdline; return true; case '/': // last search-pattern if (last_search_pat() == NULL && errmsg) { emsg(_(e_noprevre)); } - *argp = last_search_pat(); + *argp = (char *)last_search_pat(); return true; case '.': // last inserted text - *argp = get_last_insert_save(); + *argp = (char *)get_last_insert_save(); *allocated = true; if (*argp == NULL && errmsg) { emsg(_(e_noinstext)); @@ -1663,8 +1653,9 @@ bool get_spec_reg( if (!errmsg) { return false; } - *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), - 1L, NULL); + *argp + = (char *)file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), + 1L, NULL); *allocated = true; return true; @@ -1676,7 +1667,7 @@ bool get_spec_reg( cnt = find_ident_under_cursor(argp, (regname == Ctrl_W ? (FIND_IDENT|FIND_STRING) : FIND_STRING)); - *argp = cnt ? vim_strnsave(*argp, cnt) : NULL; + *argp = cnt ? xstrnsave(*argp, cnt) : NULL; *allocated = true; return true; @@ -1685,11 +1676,11 @@ bool get_spec_reg( return false; } - *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); + *argp = (char *)ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); return true; case '_': // black hole: always empty - *argp = (char_u *)""; + *argp = ""; return true; default: /* User-defined registers. */ @@ -2202,8 +2193,8 @@ static int op_replace(oparg_T *oap, int c) // times. if (utf_char2cells(c) > 1) { if ((numc & 1) && !bd.is_short) { - ++bd.endspaces; - ++n; + bd.endspaces++; + n++; } numc = numc / 2; } @@ -3259,7 +3250,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) int delcount; int incr = 0; struct block_def bd; - char_u **y_array = NULL; + char **y_array = NULL; linenr_T nr_lines = 0; pos_T new_cursor; int indent; @@ -3268,7 +3259,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) bool first_indent = true; int lendiff = 0; pos_T old_pos; - char_u *insert_string = NULL; + char *insert_string = NULL; bool allocated = false; long cnt; const pos_T orig_start = curbuf->b_op_start; @@ -3391,10 +3382,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) * Loop twice: count the number of lines and save them. */ for (;;) { y_size = 0; - ptr = insert_string; + ptr = (char_u *)insert_string; while (ptr != NULL) { if (y_array != NULL) { - y_array[y_size] = ptr; + y_array[y_size] = (char *)ptr; } y_size++; ptr = (char_u *)vim_strchr((char *)ptr, '\n'); @@ -3402,7 +3393,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_array != NULL) { *ptr = NUL; } - ++ptr; + ptr++; // A trailing '\n' makes the register linewise. if (*ptr == NUL) { y_type = kMTLineWise; @@ -3413,7 +3404,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_array != NULL) { break; } - y_array = (char_u **)xmalloc(y_size * sizeof(char_u *)); + y_array = xmalloc(y_size * sizeof(char_u *)); } } else { y_size = 1; // use fake one-line yank register @@ -3434,7 +3425,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) y_type = reg->y_type; y_width = reg->y_width; y_size = reg->y_size; - y_array = (char_u **)reg->y_array; + y_array = reg->y_array; } if (curbuf->terminal) { @@ -3643,7 +3634,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // block spaces = y_width + 1; for (int j = 0; j < yanklen; j++) { - spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); + spaces -= lbr_chartabsize(NULL, (char_u *)(&y_array[i][j]), 0); } if (spaces < 0) { spaces = 0; @@ -3744,12 +3735,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } curbuf->b_op_start = curwin->w_cursor; - } - /* - * Line mode: BACKWARD is the same as FORWARD on the previous line - */ - else if (dir == BACKWARD) { - --lnum; + } else if (dir == BACKWARD) { + // Line mode: BACKWARD is the same as FORWARD on the previous line + lnum--; } new_cursor = curwin->w_cursor; @@ -3882,13 +3870,13 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) for (; i < y_size; i++) { if ((y_type != kMTCharWise || i < y_size - 1)) { - if (ml_append(lnum, (char *)y_array[i], (colnr_T)0, false) == FAIL) { + if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) { goto error; } new_lnum++; } lnum++; - ++nr_lines; + nr_lines++; if (flags & PUT_FIXINDENT) { old_pos = curwin->w_cursor; curwin->w_cursor.lnum = lnum; @@ -3972,8 +3960,8 @@ error: if (col > 1) { curbuf->b_op_end.col = col - 1; if (len > 0) { - curbuf->b_op_end.col -= utf_head_off(y_array[y_size - 1], - y_array[y_size - 1] + len - 1); + curbuf->b_op_end.col -= utf_head_off((char_u *)y_array[y_size - 1], + (char_u *)y_array[y_size - 1] + len - 1); } } else { curbuf->b_op_end.col = 0; @@ -4264,9 +4252,9 @@ static void dis_msg(const char_u *p, bool skip_esc) /// comment. char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_comment) { - char_u *comment_flags = NULL; + char *comment_flags = NULL; int lead_len; - int leader_offset = get_last_leader_offset((char *)line, (char **)&comment_flags); + int leader_offset = get_last_leader_offset((char *)line, &comment_flags); *is_comment = false; if (leader_offset != -1) { @@ -4288,7 +4276,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co return line; } - lead_len = get_leader_len((char *)line, (char **)&comment_flags, false, include_space); + lead_len = get_leader_len((char *)line, &comment_flags, false, include_space); if (lead_len == 0) { return line; @@ -4303,7 +4291,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co || *comment_flags == ':') { break; } - ++comment_flags; + comment_flags++; } // If we found a colon, it means that we are not processing a line @@ -4587,7 +4575,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in } } else { while (ascii_iswhite(line1[idx1])) { - ++idx1; + idx1++; } } } @@ -5000,7 +4988,7 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, */ flags = *leader_flags; while (*flags && *flags != ':' && *flags != COM_END) { - ++flags; + flags++; } } @@ -5759,16 +5747,16 @@ void *get_reg_contents(int regname, int flags) return NULL; } - char_u *retval; + char *retval; bool allocated; if (get_spec_reg(regname, &retval, &allocated, false)) { if (retval == NULL) { return NULL; } if (allocated) { - return get_reg_wrap_one_line(retval, flags); + return get_reg_wrap_one_line((char_u *)retval, flags); } - return get_reg_wrap_one_line(vim_strsave(retval), flags); + return get_reg_wrap_one_line(vim_strsave((char_u *)retval), flags); } yankreg_T *reg = get_yank_register(regname, YREG_PASTE); @@ -5863,11 +5851,11 @@ void write_reg_contents(int name, const char_u *str, ssize_t len, int must_appen write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0L); } -void write_reg_contents_lst(int name, char_u **strings, bool must_append, MotionType yank_type, +void write_reg_contents_lst(int name, char **strings, bool must_append, MotionType yank_type, colnr_T block_len) { if (name == '/' || name == '=') { - char_u *s = strings[0]; + char_u *s = (char_u *)strings[0]; if (strings[0] == NULL) { s = (char_u *)""; } else if (strings[1] != NULL) { @@ -5889,7 +5877,7 @@ void write_reg_contents_lst(int name, char_u **strings, bool must_append, Motion return; } - str_to_reg(reg, yank_type, (char_u *)strings, STRLEN((char_u *)strings), + str_to_reg(reg, yank_type, (char *)strings, STRLEN((char_u *)strings), block_len, true); finish_write_reg(name, reg, old_y_previous); } @@ -5977,7 +5965,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { return; } - str_to_reg(reg, yank_type, str, (size_t)len, block_len, false); + str_to_reg(reg, yank_type, (char *)str, (size_t)len, block_len, false); finish_write_reg(name, reg, old_y_previous); } @@ -5991,7 +5979,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a /// @param len length of the string (Ignored when str_list=true.) /// @param blocklen width of visual block, or -1 for "I don't know." /// @param str_list True if str is `char_u **`. -static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str, size_t len, +static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, size_t len, colnr_T blocklen, bool str_list) FUNC_ATTR_NONNULL_ALL { @@ -6033,9 +6021,8 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str } // Grow the register array to hold the pointers to the new lines. - char_u **pp = xrealloc(y_ptr->y_array, - (y_ptr->y_size + newlines) * sizeof(char_u *)); - y_ptr->y_array = (char **)pp; + char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char_u *)); + y_ptr->y_array = pp; size_t lnum = y_ptr->y_size; // The current line number. @@ -6053,7 +6040,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str } } else { size_t line_len; - for (const char_u *start = str, *end = str + len; + for (const char_u *start = (char_u *)str, *end = (char_u *)str + len; start < end + extraline; start += line_len + 1, lnum++) { assert(end - start >= 0); @@ -6076,7 +6063,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str xfree(pp[lnum]); append = false; // only first line is appended } - pp[lnum] = (char_u *)s; + pp[lnum] = s; // Convert NULs to '\n' to prevent truncation. memchrsub(pp[lnum], NUL, '\n', s_len); @@ -6095,7 +6082,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str void clear_oparg(oparg_T *oap) { - memset(oap, 0, sizeof(oparg_T)); + CLEAR_POINTER(oap); } /// Count the number of bytes, characters and "words" in a line. diff --git a/src/nvim/option.c b/src/nvim/option.c index ba77d12a3d..9fe007a453 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -27,13 +27,16 @@ #include <stdlib.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -49,6 +52,7 @@ #include "nvim/hardcopy.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" @@ -66,12 +70,13 @@ #include "nvim/os/os.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" +#include "nvim/popupmenu.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -82,7 +87,9 @@ #ifdef WIN32 # include "nvim/os/pty_conpty_win.h" #endif +#include "nvim/api/extmark.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -978,7 +985,7 @@ int do_set(char *arg, int opt_flags) varnumber_T value; int key; uint32_t flags; // flags for current option - char_u *varp = NULL; // pointer to variable for current option + char *varp = NULL; // pointer to variable for current option int did_show = false; // already showed one value int adding; // "opt+=arg" int prepending; // "opt^=arg" @@ -1106,7 +1113,7 @@ int do_set(char *arg, int opt_flags) } flags = options[opt_idx].flags; - varp = get_varp_scope(&(options[opt_idx]), opt_flags); + varp = (char *)get_varp_scope(&(options[opt_idx]), opt_flags); } else { flags = P_STRING; } @@ -1188,7 +1195,7 @@ int do_set(char *arg, int opt_flags) showoneopt(&options[opt_idx], opt_flags); if (p_verbose > 0) { // Mention where the option was last set. - if (varp == options[opt_idx].var) { + if (varp == (char *)options[opt_idx].var) { option_last_set_msg(options[opt_idx].last_set); } else if ((int)options[opt_idx].indir & PV_WIN) { option_last_set_msg(curwin->w_p_script_ctx[ @@ -1250,8 +1257,7 @@ int do_set(char *arg, int opt_flags) } } - errmsg = set_bool_option(opt_idx, varp, (int)value, - opt_flags); + errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags); } else { // Numeric or string. if (vim_strchr("=:&<", nextchar) == NULL || prefix != 1) { @@ -1310,7 +1316,7 @@ int do_set(char *arg, int opt_flags) if (removing) { value = *(long *)varp - value; } - errmsg = set_num_option(opt_idx, varp, (long)value, + errmsg = set_num_option(opt_idx, (char_u *)varp, (long)value, errbuf, sizeof(errbuf), opt_flags); } else if (opt_idx >= 0) { // String. @@ -1333,7 +1339,7 @@ int do_set(char *arg, int opt_flags) // reset, use the global value here. if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && ((int)options[opt_idx].indir & PV_BOTH)) { - varp = options[opt_idx].var; + varp = (char *)options[opt_idx].var; } // The old value is kept until we are sure that the @@ -1382,23 +1388,16 @@ int do_set(char *arg, int opt_flags) } else { arg++; // jump to after the '=' or ':' - /* - * Set 'keywordprg' to ":help" if an empty - * value was passed to :set by the user. - * Misuse errbuf[] for the resulting string. - */ - if (varp == (char_u *)&p_kp - && (*arg == NUL || *arg == ' ')) { + // Set 'keywordprg' to ":help" if an empty + // value was passed to :set by the user. + // Misuse errbuf[] for the resulting string. + if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) { STRCPY(errbuf, ":help"); save_arg = (char_u *)arg; arg = errbuf; - } - /* - * Convert 'backspace' number to string, for - * adding, prepending and removing string. - */ - else if (varp == (char_u *)&p_bs - && ascii_isdigit(**(char_u **)varp)) { + } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) { + // Convert 'backspace' number to string, for + // adding, prepending and removing string. i = getdigits_int((char **)varp, true, 0); switch (i) { case 0: @@ -1425,14 +1424,10 @@ int do_set(char *arg, int opt_flags) origval_g = *(char_u **)varp; } oldval = *(char_u **)varp; - } - /* - * Convert 'whichwrap' number to string, for - * backwards compatibility with Vim 3.0. - * Misuse errbuf[] for the resulting string. - */ - else if (varp == (char_u *)&p_ww - && ascii_isdigit(*arg)) { + } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) { + // Convert 'whichwrap' number to string, for + // backwards compatibility with Vim 3.0. + // Misuse errbuf[] for the resulting string. *errbuf = NUL; i = getdigits_int(&arg, true, 0); if (i & 1) { @@ -1452,14 +1447,11 @@ int do_set(char *arg, int opt_flags) } save_arg = (char_u *)arg; arg = errbuf; - } - /* - * Remove '>' before 'dir' and 'bdir', for - * backwards compatibility with version 3.0 - */ - else if (*arg == '>' - && (varp == (char_u *)&p_dir - || varp == (char_u *)&p_bdir)) { + } else if (*arg == '>' + && (varp == (char *)&p_dir + || varp == (char *)&p_bdir)) { + // Remove '>' before 'dir' and 'bdir', for + // backwards compatibility with version 3.0 arg++; } @@ -1993,7 +1985,7 @@ static void didset_options(void) (void)check_cedit(); // initialize the table for 'breakat'. fill_breakat_flags(); - didset_window_options(curwin); + didset_window_options(curwin, true); } // More side effects of setting options. @@ -2070,6 +2062,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_cpt); check_string_option(&buf->b_p_cfu); check_string_option(&buf->b_p_ofu); + check_string_option(&buf->b_p_urf); check_string_option(&buf->b_p_keymap); check_string_option(&buf->b_p_gp); check_string_option(&buf->b_p_mp); @@ -2324,7 +2317,7 @@ static char *set_string_option(const int opt_idx, const char *const value, const /// Return true if "val" is a valid name: only consists of alphanumeric ASCII /// characters or characters in "allowed". -static bool valid_name(const char_u *val, const char *allowed) +bool valid_name(const char_u *val, const char *allowed) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { for (const char_u *s = val; *s != NUL; s++) { @@ -2344,25 +2337,6 @@ static bool valid_filetype(const char_u *val) return valid_name(val, ".-_"); } -/// Return true if "val" is a valid 'spelllang' value. -bool valid_spelllang(const char_u *val) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return valid_name(val, ".-_,@"); -} - -/// Return true if "val" is a valid 'spellfile' value. -static bool valid_spellfile(const char_u *val) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - for (const char_u *s = val; *s != NUL; s++) { - if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { - return false; - } - } - return true; -} - /// Handle setting 'mousescroll'. /// @return error message, NULL if it's OK. static char *check_mousescroll(char *string) @@ -2441,7 +2415,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c size_t errbuflen, int opt_flags, int *value_checked) { char *errmsg = NULL; - char_u *s, *p; + char *s, *p; int did_chartab = false; char_u **gvarp; bool free_oldval = (options[opt_idx].flags & P_ALLOCED); @@ -2530,7 +2504,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c errmsg = check_colorcolumn(curwin); } else if (varp == &p_hlg) { // 'helplang' // Check for "", "ab", "ab,cd", etc. - for (s = p_hlg; *s != NUL; s += 3) { + for (s = (char *)p_hlg; *s != NUL; s += 3) { if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { errmsg = e_invarg; break; @@ -2578,18 +2552,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { errmsg = e_invarg; } else { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { - errmsg = _("E834: Conflicts with value of 'listchars'"); - goto ambw_end; - } - if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { - errmsg = _("E835: Conflicts with value of 'fillchars'"); - goto ambw_end; - } - } -ambw_end: - {} // clint prefers {} over ; as an empty statement + errmsg = check_chars_options(); } } else if (varp == &p_bg) { // 'background' if (check_opt_strings(p_bg, p_bg_values, false) == OK) { @@ -2647,9 +2610,9 @@ ambw_end: if (errmsg == NULL) { // canonize the value, so that STRCMP() can be used on it - p = enc_canonize(*varp); + p = (char *)enc_canonize(*varp); xfree(*varp); - *varp = p; + *varp = (char_u *)p; if (varp == &p_enc) { // only encoding=utf-8 allowed if (STRCMP(p_enc, "utf-8") != 0) { @@ -2661,9 +2624,9 @@ ambw_end: } } else if (varp == &p_penc) { // Canonize printencoding if VIM standard one - p = enc_canonize(p_penc); + p = (char *)enc_canonize(p_penc); xfree(p_penc); - p_penc = p; + p_penc = (char_u *)p; } else if (varp == &curbuf->b_p_keymap) { if (!valid_filetype(*varp)) { errmsg = e_invarg; @@ -2726,17 +2689,17 @@ ambw_end: errmsg = e_invarg; } } else if (gvarp == &p_mps) { // 'matchpairs' - for (p = *varp; *p != NUL; p++) { + for (p = (char *)(*varp); *p != NUL; p++) { int x2 = -1; int x3 = -1; - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); if (*p != NUL) { - x2 = *p++; + x2 = (unsigned char)(*p++); } if (*p != NUL) { - x3 = utf_ptr2char((char *)p); - p += utfc_ptr2len((char *)p); + x3 = utf_ptr2char(p); + p += utfc_ptr2len(p); } if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { errmsg = e_invarg; @@ -2747,7 +2710,7 @@ ambw_end: } } } else if (gvarp == &p_com) { // 'comments' - for (s = *varp; *s;) { + for (s = (char *)(*varp); *s;) { while (*s && *s != ':') { if (vim_strchr(COM_ALL, *s) == NULL && !ascii_isdigit(*s) && *s != '-') { @@ -2770,7 +2733,7 @@ ambw_end: } s++; } - s = skip_to_option_part(s); + s = (char *)skip_to_option_part((char_u *)s); } } else if (varp == &p_lcs) { // global 'listchars' errmsg = set_chars_option(curwin, varp, false); @@ -2826,7 +2789,7 @@ ambw_end: // there would be a disconnect between the check for P_ALLOCED at the start // of the function and the set of P_ALLOCED at the end of the function. free_oldval = (options[opt_idx].flags & P_ALLOCED); - for (s = p_shada; *s;) { + for (s = (char *)p_shada; *s;) { // Check it's a valid character if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) { errmsg = illegal_char(errbuf, errbuflen, *s); @@ -2871,8 +2834,8 @@ ambw_end: errmsg = N_("E528: Must specify a ' value"); } } else if (gvarp == &p_sbr) { // 'showbreak' - for (s = *varp; *s;) { - if (ptr2cells((char *)s) != 1) { + for (s = (char *)(*varp); *s;) { + if (ptr2cells(s) != 1) { errmsg = N_("E595: 'showbreak' contains unprintable or wide character"); } MB_PTR_ADV(s); @@ -2996,13 +2959,13 @@ ambw_end: if (varp == &p_ruf) { // reset ru_wid first ru_wid = 0; } - s = *varp; + s = (char *)(*varp); if (varp == &p_ruf && *s == '%') { // set ru_wid if 'ruf' starts with "%99(" if (*++s == '-') { // ignore a '-' s++; } - wid = getdigits_int((char **)&s, true, 0); + wid = getdigits_int(&s, true, 0); if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) { ru_wid = wid; } else { @@ -3010,7 +2973,7 @@ ambw_end: } } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { // check 'statusline', 'winbar' or 'tabline' only if it doesn't start with "%!" - errmsg = check_stl_option((char *)s); + errmsg = check_stl_option(s); } if (varp == &p_ruf && errmsg == NULL) { comp_col(); @@ -3021,7 +2984,7 @@ ambw_end: } } else if (gvarp == &p_cpt) { // check if it is a valid value for 'complete' -- Acevedo - for (s = *varp; *s;) { + for (s = (char *)(*varp); *s;) { while (*s == ',' || *s == ' ') { s++; } @@ -3090,11 +3053,11 @@ ambw_end: p = NULL; (void)replace_termcodes((char *)p_pt, STRLEN(p_pt), - (char **)&p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, + &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); if (p != NULL) { free_string_option(p_pt); - p_pt = p; + p_pt = (char_u *)p; } } } else if (varp == &p_bs) { // 'backspace' @@ -3113,10 +3076,10 @@ ambw_end: unsigned int *flags; if (opt_flags & OPT_LOCAL) { - p = curbuf->b_p_tc; + p = (char *)curbuf->b_p_tc; flags = &curbuf->b_tc_flags; } else { - p = p_tc; + p = (char *)p_tc; flags = &tc_flags; } @@ -3124,7 +3087,7 @@ ambw_end: // make the local value empty: use the global value *flags = 0; } else if (*p == NUL - || opt_strings_flags(p, p_tc_values, flags, false) != OK) { + || opt_strings_flags((char_u *)p, p_tc_values, flags, false) != OK) { errmsg = e_invarg; } } else if (varp == &p_cmp) { // 'casemap' @@ -3150,10 +3113,10 @@ ambw_end: foldUpdateAll(curwin); } } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' - p = (char_u *)vim_strchr((char *)(*varp), ','); + p = vim_strchr((char *)(*varp), ','); if (p == NULL) { errmsg = N_("E536: comma required"); - } else if (p == *varp || p[1] == NUL) { + } else if ((char_u *)p == *varp || p[1] == NUL) { errmsg = e_invarg; } else if (foldmethodIsMarker(curwin)) { foldUpdateAll(curwin); @@ -3198,7 +3161,7 @@ ambw_end: } } else if (varp == &p_csqf) { if (p_csqf != NULL) { - p = p_csqf; + p = (char *)p_csqf; while (*p != NUL) { if (vim_strchr(CSQF_CMDS, *p) == NULL || p[1] == NUL @@ -3310,22 +3273,22 @@ ambw_end: // Options that are a list of flags. p = NULL; if (varp == &p_ww) { // 'whichwrap' - p = (char_u *)WW_ALL; + p = WW_ALL; } if (varp == &p_shm) { // 'shortmess' - p = (char_u *)SHM_ALL; + p = (char *)SHM_ALL; } else if (varp == (char_u **)&(p_cpo)) { // 'cpoptions' - p = (char_u *)CPO_VI; + p = CPO_VI; } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' - p = (char_u *)FO_ALL; + p = FO_ALL; } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' - p = (char_u *)COCU_ALL; + p = COCU_ALL; } else if (varp == &p_mouse) { // 'mouse' - p = (char_u *)MOUSE_ALL; + p = MOUSE_ALL; } if (p != NULL) { - for (s = *varp; *s; s++) { - if (vim_strchr((char *)p, *s) == NULL) { + for (s = (char *)(*varp); *s; s++) { + if (vim_strchr(p, *s) == NULL) { errmsg = illegal_char(errbuf, errbuflen, *s); break; } @@ -3360,7 +3323,7 @@ ambw_end: && ((int)options[opt_idx].indir & PV_BOTH)) { /* global option with local value set to use global value; free * the local value and make it empty */ - p = get_varp_scope(&(options[opt_idx]), OPT_LOCAL); + p = (char *)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); free_string_option(*(char_u **)p); *(char_u **)p = empty_option; } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { @@ -3423,14 +3386,14 @@ ambw_end: * Use the first name in 'spelllang' up to '_region' or * '.encoding'. */ - for (p = q; *p != NUL; p++) { + for (p = (char *)q; *p != NUL; p++) { if (!ASCII_ISALNUM(*p) && *p != '-') { break; } } - if (p > q) { + if (p > (char *)q) { vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - q), q); + (int)(p - (char *)q), q); source_runtime((char *)fname, DIP_ALL); } } @@ -3450,12 +3413,6 @@ ambw_end: return errmsg; } -/// Simple int comparison function for use with qsort() -static int int_cmp(const void *a, const void *b) -{ - return *(const int *)a - *(const int *)b; -} - /// Handle setting 'signcolumn' for value 'val' /// /// @return OK when the value is valid, FAIL otherwise @@ -3486,356 +3443,12 @@ int check_signcolumn(char_u *val) return FAIL; } -/// Handle setting 'colorcolumn' or 'textwidth' in window "wp". -/// -/// @return error message, NULL if it's OK. -char *check_colorcolumn(win_T *wp) -{ - char_u *s; - int col; - unsigned int count = 0; - int color_cols[256]; - int j = 0; - - if (wp->w_buffer == NULL) { - return NULL; // buffer was closed - } - - for (s = wp->w_p_cc; *s != NUL && count < 255;) { - if (*s == '-' || *s == '+') { - // -N and +N: add to 'textwidth' - col = (*s == '-') ? -1 : 1; - s++; - if (!ascii_isdigit(*s)) { - return e_invarg; - } - col = col * getdigits_int((char **)&s, true, 0); - if (wp->w_buffer->b_p_tw == 0) { - goto skip; // 'textwidth' not set, skip this item - } - assert((col >= 0 - && wp->w_buffer->b_p_tw <= INT_MAX - col - && wp->w_buffer->b_p_tw + col >= INT_MIN) - || (col < 0 - && wp->w_buffer->b_p_tw >= INT_MIN - col - && wp->w_buffer->b_p_tw + col <= INT_MAX)); - col += (int)wp->w_buffer->b_p_tw; - if (col < 0) { - goto skip; - } - } else if (ascii_isdigit(*s)) { - col = getdigits_int((char **)&s, true, 0); - } else { - return e_invarg; - } - color_cols[count++] = col - 1; // 1-based to 0-based -skip: - if (*s == NUL) { - break; - } - if (*s != ',') { - return e_invarg; - } - if (*++s == NUL) { - return e_invarg; // illegal trailing comma as in "set cc=80," - } - } - - xfree(wp->w_p_cc_cols); - if (count == 0) { - wp->w_p_cc_cols = NULL; - } else { - wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1)); - /* sort the columns for faster usage on screen redraw inside - * win_line() */ - qsort(color_cols, count, sizeof(int), int_cmp); - - for (unsigned int i = 0; i < count; i++) { - // skip duplicates - if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { - wp->w_p_cc_cols[j++] = color_cols[i]; - } - } - wp->w_p_cc_cols[j] = -1; // end marker - } - - return NULL; // no error -} - void check_blending(win_T *wp) { wp->w_grid_alloc.blending = wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow); } -/// Calls mb_cptr2char_adv(p) and returns the character. -/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used. -/// Returns 0 for invalid hex or invalid UTF-8 byte. -static int get_encoded_char_adv(char_u **p) -{ - char_u *s = *p; - - if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { - int64_t num = 0; - int bytes; - int n; - for (bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { - *p += 2; - n = hexhex2nr(*p); - if (n < 0) { - return 0; - } - num = num * 256 + n; - } - *p += 2; - return (int)num; - } - - // TODO(bfredl): use schar_T representation and utfc_ptr2len - int clen = utf_ptr2len((char *)s); - int c = mb_cptr2char_adv((const char_u **)p); - if (clen == 1 && c > 127) { // Invalid UTF-8 byte - return 0; - } - return c; -} - -/// Handle setting 'listchars' or 'fillchars'. -/// Assume monocell characters -/// -/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs -/// @return error message, NULL if it's OK. -static char *set_chars_option(win_T *wp, char_u **varp, bool set) -{ - int round, i, len, len2, entries; - char_u *p, *s; - int c1; - int c2 = 0; - int c3 = 0; - char_u *last_multispace = NULL; // Last occurrence of "multispace:" - char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" - int multispace_len = 0; // Length of lcs-multispace string - int lead_multispace_len = 0; // Length of lcs-leadmultispace string - - struct chars_tab { - int *cp; ///< char value - char *name; ///< char id - int def; ///< default value - }; - struct chars_tab *tab; - - struct chars_tab fcs_tab[] = { - { &wp->w_p_fcs_chars.colorcol, "colorcol", NUL }, - { &wp->w_p_fcs_chars.stl, "stl", ' ' }, - { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, - { &wp->w_p_fcs_chars.wbr, "wbr", ' ' }, - { &wp->w_p_fcs_chars.horiz, "horiz", 9472 }, // ─ - { &wp->w_p_fcs_chars.horizup, "horizup", 9524 }, // ┴ - { &wp->w_p_fcs_chars.horizdown, "horizdown", 9516 }, // ┬ - { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ - { &wp->w_p_fcs_chars.vertleft, "vertleft", 9508 }, // ┤ - { &wp->w_p_fcs_chars.vertright, "vertright", 9500 }, // ├ - { &wp->w_p_fcs_chars.verthoriz, "verthoriz", 9532 }, // ┼ - { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · - { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, - { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, - { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ - { &wp->w_p_fcs_chars.diff, "diff", '-' }, - { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, - { &wp->w_p_fcs_chars.eob, "eob", '~' }, - }; - struct chars_tab lcs_tab[] = { - { &wp->w_p_lcs_chars.eol, "eol", NUL }, - { &wp->w_p_lcs_chars.ext, "extends", NUL }, - { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL }, - { &wp->w_p_lcs_chars.prec, "precedes", NUL }, - { &wp->w_p_lcs_chars.space, "space", NUL }, - { &wp->w_p_lcs_chars.tab2, "tab", NUL }, - { &wp->w_p_lcs_chars.lead, "lead", NUL }, - { &wp->w_p_lcs_chars.trail, "trail", NUL }, - { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, - }; - - if (varp == &p_lcs || varp == &wp->w_p_lcs) { - tab = lcs_tab; - entries = ARRAY_SIZE(lcs_tab); - if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { - varp = &p_lcs; - } - } else { - tab = fcs_tab; - entries = ARRAY_SIZE(fcs_tab); - if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { - varp = &p_fcs; - } - if (*p_ambw == 'd') { - // XXX: If ambiwidth=double then some characters take 2 columns, - // which is forbidden (TUI limitation?). Set old defaults. - fcs_tab[3].def = '-'; - fcs_tab[4].def = '-'; - fcs_tab[5].def = '-'; - fcs_tab[6].def = '|'; - fcs_tab[7].def = '|'; - fcs_tab[8].def = '|'; - fcs_tab[9].def = '+'; - fcs_tab[10].def = '-'; - fcs_tab[13].def = '|'; - } - } - - // first round: check for valid value, second round: assign values - for (round = 0; round <= (set ? 1 : 0); round++) { - if (round > 0) { - // After checking that the value is valid: set defaults - for (i = 0; i < entries; i++) { - if (tab[i].cp != NULL) { - *(tab[i].cp) = tab[i].def; - } - } - if (varp == &p_lcs || varp == &wp->w_p_lcs) { - wp->w_p_lcs_chars.tab1 = NUL; - wp->w_p_lcs_chars.tab3 = NUL; - - xfree(wp->w_p_lcs_chars.multispace); - if (multispace_len > 0) { - wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int)); - wp->w_p_lcs_chars.multispace[multispace_len] = NUL; - } else { - wp->w_p_lcs_chars.multispace = NULL; - } - - xfree(wp->w_p_lcs_chars.leadmultispace); - if (lead_multispace_len > 0) { - wp->w_p_lcs_chars.leadmultispace - = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int)); - wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL; - } else { - wp->w_p_lcs_chars.leadmultispace = NULL; - } - } - } - p = *varp; - while (*p) { - for (i = 0; i < entries; i++) { - len = (int)STRLEN(tab[i].name); - if (STRNCMP(p, tab[i].name, len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - c2 = c3 = 0; - s = p + len + 1; - c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { - if (*s == NUL) { - return e_invarg; - } - c2 = get_encoded_char_adv(&s); - if (c2 == 0 || char2cells(c2) > 1) { - return e_invarg; - } - if (!(*s == ',' || *s == NUL)) { - c3 = get_encoded_char_adv(&s); - if (c3 == 0 || char2cells(c3) > 1) { - return e_invarg; - } - } - } - if (*s == ',' || *s == NUL) { - if (round > 0) { - if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { - wp->w_p_lcs_chars.tab1 = c1; - wp->w_p_lcs_chars.tab2 = c2; - wp->w_p_lcs_chars.tab3 = c3; - } else if (tab[i].cp != NULL) { - *(tab[i].cp) = c1; - } - } - p = s; - break; - } - } - } - - if (i == entries) { - len = (int)STRLEN("multispace"); - len2 = (int)STRLEN("leadmultispace"); - if ((varp == &p_lcs || varp == &wp->w_p_lcs) - && STRNCMP(p, "multispace", len) == 0 - && p[len] == ':' - && p[len + 1] != NUL) { - s = p + len + 1; - if (round == 0) { - // Get length of lcs-multispace string in the first round - last_multispace = p; - multispace_len = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - multispace_len++; - } - if (multispace_len == 0) { - // lcs-multispace cannot be an empty string - return e_invarg; - } - p = s; - } else { - int multispace_pos = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (p == last_multispace) { - wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; - } - } - p = s; - } - } else if ((varp == &p_lcs || varp == &wp->w_p_lcs) - && STRNCMP(p, "leadmultispace", len2) == 0 - && p[len2] == ':' - && p[len2 + 1] != NUL) { - s = p + len2 + 1; - if (round == 0) { - // get length of lcs-leadmultispace string in first round - last_lmultispace = p; - lead_multispace_len = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (c1 == 0 || char2cells(c1) > 1) { - return e_invarg; - } - lead_multispace_len++; - } - if (lead_multispace_len == 0) { - // lcs-leadmultispace cannot be an empty string - return e_invarg; - } - p = s; - } else { - int multispace_pos = 0; - while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); - if (p == last_lmultispace) { - wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1; - } - } - p = s; - } - } else { - return e_invarg; - } - } - if (*p == ',') { - p++; - } - } - } - - return NULL; // no error -} - /// Check validity of options with the 'statusline' format. /// Return an untranslated error message or NULL. char *check_stl_option(char *s) @@ -3906,63 +3519,30 @@ char *check_stl_option(char *s) return NULL; } -static char *did_set_spell_option(bool is_spellfile) +/// Handle setting `winhighlight' in window "wp" +bool parse_winhl_opt(win_T *wp) { - char *errmsg = NULL; + const char *p = (const char *)wp->w_p_winhl; - if (is_spellfile) { - int l = (int)STRLEN(curwin->w_s->b_p_spf); - if (l > 0 - && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) { - errmsg = e_invarg; + if (!*p) { + if (wp->w_ns_hl_winhl && wp->w_ns_hl == wp->w_ns_hl_winhl) { + wp->w_ns_hl = 0; + wp->w_hl_needs_update = true; } - } - if (errmsg == NULL) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == curbuf && wp->w_p_spell) { - errmsg = did_set_spelllang(wp); - break; - } - } + return true; } - return errmsg; -} - -/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. -/// Return error message when failed, NULL when OK. -static char *compile_cap_prog(synblock_T *synblock) - FUNC_ATTR_NONNULL_ALL -{ - regprog_T *rp = synblock->b_cap_prog; - char_u *re; - - if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) { - synblock->b_cap_prog = NULL; + if (wp->w_ns_hl_winhl == 0) { + wp->w_ns_hl_winhl = (int)nvim_create_namespace(NULL_STRING); } else { - // Prepend a ^ so that we only match at one column - re = concat_str((char_u *)"^", synblock->b_p_spc); - synblock->b_cap_prog = vim_regcomp((char *)re, RE_MAGIC); - xfree(re); - if (synblock->b_cap_prog == NULL) { - synblock->b_cap_prog = rp; // restore the previous program - return e_invarg; - } + // namespace already exist. invalidate existing items + DecorProvider *dp = get_decor_provider(wp->w_ns_hl_winhl, true); + dp->hl_valid++; } + wp->w_ns_hl = wp->w_ns_hl_winhl; + int ns_hl = wp->w_ns_hl; - vim_regfree(rp); - return NULL; -} - -/// Handle setting `winhighlight' in window "wp" -static bool parse_winhl_opt(win_T *wp) -{ - int w_hl_id_normal = 0; - int w_hl_ids[HLF_COUNT] = { 0 }; - int hlf; - - const char *p = (const char *)wp->w_p_winhl; while (*p) { char *colon = strchr(p, ':'); if (!colon) { @@ -3973,27 +3553,15 @@ static bool parse_winhl_opt(win_T *wp) char *commap = xstrchrnul(hi, ','); size_t len = (size_t)(commap - hi); int hl_id = len ? syn_check_group(hi, len) : -1; + int hl_id_link = nlen ? syn_check_group(p, nlen) : 0; - if (strncmp("Normal", p, nlen) == 0) { - w_hl_id_normal = hl_id; - } else { - for (hlf = 0; hlf < HLF_COUNT; hlf++) { - if (strlen(hlf_names[hlf]) == nlen - && strncmp(hlf_names[hlf], p, nlen) == 0) { - w_hl_ids[hlf] = hl_id; - break; - } - } - if (hlf == HLF_COUNT) { - return false; - } - } + HlAttrs attrs = HLATTRS_INIT; + attrs.rgb_ae_attr |= HL_GLOBAL; + ns_hl_def(ns_hl, hl_id_link, attrs, hl_id, NULL); p = *commap ? commap + 1 : ""; } - wp->w_hl_id_normal = w_hl_id_normal; - memcpy(wp->w_hl_ids, w_hl_ids, sizeof(w_hl_ids)); wp->w_hl_needs_update = true; return true; } @@ -4009,7 +3577,7 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) .script_ctx = { script_ctx.sc_sid, script_ctx.sc_seq, - script_ctx.sc_lnum + sourcing_lnum + script_ctx.sc_lnum + SOURCING_LNUM }, current_channel_id }; @@ -5667,7 +5235,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 char_u *s; char_u *buf = NULL; char_u *part = NULL; - char_u *p; + char *p; if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; @@ -5703,14 +5271,14 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 if (put_eol(fd) == FAIL) { goto fail; } - p = buf; + p = (char *)buf; while (*p != NUL) { // for each comma separated option part, append value to // the option, :set rtp+=value if (fprintf(fd, "%s %s+=", cmd, name) < 0) { goto fail; } - (void)copy_option_part((char **)&p, (char *)part, size, ","); + (void)copy_option_part(&p, (char *)part, size, ","); if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) { goto fail; } @@ -5771,47 +5339,6 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) return OK; } -/// Compute columns for ruler and shown command. 'sc_col' is also used to -/// decide what the maximum length of a message on the status line can be. -/// If there is a status line for the last window, 'sc_col' is independent -/// of 'ru_col'. - -#define COL_RULER 17 // columns needed by standard ruler - -void comp_col(void) -{ - int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); - - sc_col = 0; - ru_col = 0; - if (p_ru) { - ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; - // no last status line, adjust sc_col - if (!last_has_status) { - sc_col = ru_col; - } - } - if (p_sc) { - sc_col += SHOWCMD_COLS; - if (!p_ru || last_has_status) { // no need for separating space - sc_col++; - } - } - assert(sc_col >= 0 - && INT_MIN + sc_col <= Columns); - sc_col = Columns - sc_col; - assert(ru_col >= 0 - && INT_MIN + ru_col <= Columns); - ru_col = Columns - ru_col; - if (sc_col <= 0) { // screen too narrow, will become a mess - sc_col = 1; - } - if (ru_col <= 0) { - ru_col = 1; - } - set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); -} - // Unset local option value, similar to ":set opt<". void unset_global_local_option(char *name, void *from) { @@ -6157,6 +5684,7 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curwin->w_p_cocu); case PV_COLE: return (char_u *)&(curwin->w_p_cole); + case PV_AI: return (char_u *)&(curbuf->b_p_ai); case PV_BIN: @@ -6317,7 +5845,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to) { copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt); copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt); - didset_window_options(wp_to); + didset_window_options(wp_to, true); } /// Copy the options from one winopt_T to another. @@ -6442,7 +5970,7 @@ void clear_winopt(winopt_T *wop) clear_string_option((char_u **)&wop->wo_wbr); } -void didset_window_options(win_T *wp) +void didset_window_options(win_T *wp, bool valid_cursor) { check_colorcolumn(wp); briopt_check(wp); @@ -6451,7 +5979,7 @@ void didset_window_options(win_T *wp) set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl check_blending(wp); - set_winbar_win(wp, false); + set_winbar_win(wp, false, valid_cursor); wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } @@ -6514,7 +6042,7 @@ void buf_copy_options(buf_T *buf, int flags) } if (should_copy || (flags & BCO_ALWAYS)) { - memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx)); + CLEAR_FIELD(buf->b_p_script_ctx); init_buf_opt_idx(); // Don't copy the options specific to a help buffer when // BCO_NOHELP is given or the options were initialized already @@ -7569,313 +7097,6 @@ int check_ff_value(char_u *p) return check_opt_strings(p, p_ff_values, false); } -// Set the integer values corresponding to the string setting of 'vartabstop'. -// "array" will be set, caller must free it if needed. -// Return false for an error. -bool tabstop_set(char_u *var, long **array) -{ - long valcount = 1; - int t; - char_u *cp; - - if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { - *array = NULL; - return true; - } - - for (cp = var; *cp != NUL; cp++) { - if (cp == var || cp[-1] == ',') { - char_u *end; - - if (strtol((char *)cp, (char **)&end, 10) <= 0) { - if (cp != end) { - emsg(_(e_positive)); - } else { - semsg(_(e_invarg2), cp); - } - return false; - } - } - - if (ascii_isdigit(*cp)) { - continue; - } - if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) { - valcount++; - continue; - } - semsg(_(e_invarg2), var); - return false; - } - - *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long)); - (*array)[0] = valcount; - - t = 1; - for (cp = var; *cp != NUL;) { - int n = atoi((char *)cp); - - // Catch negative values, overflow and ridiculous big values. - if (n <= 0 || n > TABSTOP_MAX) { - semsg(_(e_invarg2), cp); - XFREE_CLEAR(*array); - return false; - } - (*array)[t++] = n; - while (*cp != NUL && *cp != ',') { - cp++; - } - if (*cp != NUL) { - cp++; - } - } - - return true; -} - -// Calculate the number of screen spaces a tab will occupy. -// If "vts" is set then the tab widths are taken from that array, -// otherwise the value of ts is used. -int tabstop_padding(colnr_T col, long ts_arg, long *vts) -{ - long ts = ts_arg == 0 ? 8 : ts_arg; - colnr_T tabcol = 0; - int t; - long padding = 0; - - if (vts == NULL || vts[0] == 0) { - return (int)(ts - (col % ts)); - } - - const long tabcount = vts[0]; - - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > col) { - padding = tabcol - col; - break; - } - } - if (t > tabcount) { - padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]); - } - - return (int)padding; -} - -// Find the size of the tab that covers a particular column. -int tabstop_at(colnr_T col, long ts, long *vts) -{ - colnr_T tabcol = 0; - int t; - long tab_size = 0; - - if (vts == NULL || vts[0] == 0) { - return (int)ts; - } - - const long tabcount = vts[0]; - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > col) { - tab_size = vts[t]; - break; - } - } - if (t > tabcount) { - tab_size = vts[tabcount]; - } - - return (int)tab_size; -} - -// Find the column on which a tab starts. -colnr_T tabstop_start(colnr_T col, long ts, long *vts) -{ - colnr_T tabcol = 0; - int t; - - if (vts == NULL || vts[0] == 0) { - return (int)((col / ts) * ts); - } - - const long tabcount = vts[0]; - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > col) { - return (int)(tabcol - vts[t]); - } - } - - const int excess = (int)(tabcol % vts[tabcount]); - return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]); -} - -// Find the number of tabs and spaces necessary to get from one column -// to another. -void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, int *ntabs, - int *nspcs) -{ - int spaces = end_col - start_col; - colnr_T tabcol = 0; - long padding = 0; - int t; - long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; - - if (vts == NULL || vts[0] == 0) { - int tabs = 0; - - const int initspc = (int)(ts - (start_col % ts)); - if (spaces >= initspc) { - spaces -= initspc; - tabs++; - } - tabs += (int)(spaces / ts); - spaces -= (int)((spaces / ts) * ts); - - *ntabs = tabs; - *nspcs = spaces; - return; - } - - // Find the padding needed to reach the next tabstop. - const long tabcount = vts[0]; - for (t = 1; t <= tabcount; t++) { - tabcol += (colnr_T)vts[t]; - if (tabcol > start_col) { - padding = tabcol - start_col; - break; - } - } - if (t > tabcount) { - padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]); - } - - // If the space needed is less than the padding no tabs can be used. - if (spaces < padding) { - *ntabs = 0; - *nspcs = spaces; - return; - } - - *ntabs = 1; - spaces -= (int)padding; - - // At least one tab has been used. See if any more will fit. - while (spaces != 0 && ++t <= tabcount) { - padding = vts[t]; - if (spaces < padding) { - *nspcs = spaces; - return; - } - *ntabs += 1; - spaces -= (int)padding; - } - - *ntabs += spaces / (int)vts[tabcount]; - *nspcs = spaces % (int)vts[tabcount]; -} - -// See if two tabstop arrays contain the same values. -bool tabstop_eq(long *ts1, long *ts2) -{ - int t; - - if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { - return false; - } - if (ts1 == ts2) { - return true; - } - if (ts1[0] != ts2[0]) { - return false; - } - - for (t = 1; t <= ts1[0]; t++) { - if (ts1[t] != ts2[t]) { - return false; - } - } - - return true; -} - -// Copy a tabstop array, allocating space for the new array. -int *tabstop_copy(long *oldts) -{ - long *newts; - int t; - - if (oldts == 0) { - return 0; - } - - newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long)); - for (t = 0; t <= oldts[0]; t++) { - newts[t] = oldts[t]; - } - - return (int *)newts; -} - -// Return a count of the number of tabstops. -int tabstop_count(long *ts) -{ - return ts != NULL ? (int)ts[0] : 0; -} - -// Return the first tabstop, or 8 if there are no tabstops defined. -int tabstop_first(long *ts) -{ - return ts != NULL ? (int)ts[1] : 8; -} - -/// Return the effective shiftwidth value for current buffer, using the -/// 'tabstop' value when 'shiftwidth' is zero. -int get_sw_value(buf_T *buf) -{ - long result = get_sw_value_col(buf, 0); - assert(result >= 0 && result <= INT_MAX); - return (int)result; -} - -// Idem, using the first non-black in the current line. -long get_sw_value_indent(buf_T *buf) -{ - pos_T pos = curwin->w_cursor; - - pos.col = (colnr_T)getwhitecols_curline(); - return get_sw_value_pos(buf, &pos); -} - -// Idem, using "pos". -long get_sw_value_pos(buf_T *buf, pos_T *pos) -{ - pos_T save_cursor = curwin->w_cursor; - long sw_value; - - curwin->w_cursor = *pos; - sw_value = get_sw_value_col(buf, get_nolist_virtcol()); - curwin->w_cursor = save_cursor; - return sw_value; -} - -// Idem, using virtual column "col". -long get_sw_value_col(buf_T *buf, colnr_T col) -{ - return buf->b_p_sw ? buf->b_p_sw - : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); -} - -/// Return the effective softtabstop value for the current buffer, -/// using the shiftwidth value when 'softtabstop' is negative. -int get_sts_value(void) -{ - long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; - assert(result >= 0 && result <= INT_MAX); - return (int)result; -} - /// This is called when 'breakindentopt' is changed and when a window is /// initialized static bool briopt_check(win_T *wp) diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index eaa56ffe63..98ef4bd0f6 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -10,7 +10,6 @@ #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/ex_getln.h" -#include "nvim/fileio.h" #include "nvim/macros.h" #include "nvim/map.h" #include "nvim/memory.h" @@ -580,18 +579,18 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix); - char_u *src = (char_u *)skipwhite((char *)srcp); + char *src = skipwhite((char *)srcp); dstlen--; // leave one char space for "\," while (*src && dstlen > 0) { // Skip over `=expr`. if (src[0] == '`' && src[1] == '=') { - var = src; + var = (char_u *)src; src += 2; - (void)skip_expr((char **)&src); + (void)skip_expr(&src); if (*src == '`') { src++; } - size_t len = (size_t)(src - var); + size_t len = (size_t)(src - (char *)var); if (len > (size_t)dstlen) { len = (size_t)dstlen; } @@ -608,7 +607,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // The variable name is copied into dst temporarily, because it may // be a string in read-only memory and a NUL needs to be appended. if (*src != '~') { // environment var - tail = src + 1; + tail = (char_u *)src + 1; var = dst; int c = dstlen - 1; @@ -646,11 +645,11 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo || vim_ispathsep(src[1]) || vim_strchr(" ,\t\n", src[1]) != NULL) { var = (char_u *)homedir; - tail = src + 1; + tail = (char_u *)src + 1; } else { // user directory #if defined(UNIX) // Copy ~user to dst[], so we can put a NUL after it. - tail = src; + tail = (char_u *)src; var = dst; int c = dstlen - 1; while (c-- > 0 @@ -723,7 +722,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo tail++; } dst += c; - src = tail; + src = (char *)tail; copy_char = false; } if (mustfree) { @@ -737,17 +736,17 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // ":edit foo ~ foo". at_start = false; if (src[0] == '\\' && src[1] != NUL) { - *dst++ = *src++; + *dst++ = (char_u)(*src++); dstlen--; } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } if (dstlen > 0) { - *dst++ = *src++; + *dst++ = (char_u)(*src++); dstlen--; if (prefix != NULL - && src - prefix_len >= srcp + && src - prefix_len >= (char *)srcp && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) { at_start = true; } @@ -1069,9 +1068,8 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si must_free = true; size_t usedlen = 0; size_t flen = strlen(homedir_env_mod); - char_u *fbuf = NULL; - (void)modify_fname(":p", false, &usedlen, - &homedir_env_mod, (char **)&fbuf, &flen); + char *fbuf = NULL; + (void)modify_fname(":p", false, &usedlen, &homedir_env_mod, &fbuf, &flen); flen = strlen(homedir_env_mod); assert(homedir_env_mod != homedir_env); if (vim_ispathsep(homedir_env_mod[flen - 1])) { diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 901a1bc5a6..0d62a5f5f9 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -144,25 +144,6 @@ bool os_isdir(const char_u *name) return true; } -/// Check if the given path is a directory and is executable. -/// Gives the same results as `os_isdir()` on Windows. -/// -/// @return `true` if `name` is a directory and executable. -bool os_isdir_executable(const char *name) - FUNC_ATTR_NONNULL_ALL -{ - int32_t mode = os_getperm(name); - if (mode < 0) { - return false; - } - -#ifdef WIN32 - return (S_ISDIR(mode)); -#else - return (S_ISDIR(mode) && (S_IXUSR & mode)); -#endif -} - /// Check what `name` is: /// @return NODE_NORMAL: file or directory (or doesn't exist) /// NODE_WRITABLE: writable device, socket, fifo, etc. @@ -339,7 +320,7 @@ static bool is_executable_ext(const char *name, char **abspath) const char *ext_end = ext; size_t ext_len = - copy_option_part((char_u **)&ext_end, (char_u *)buf_end, + copy_option_part(&ext_end, (char_u *)buf_end, sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR); if (ext_len != 0) { bool in_pathext = nameext_len == ext_len @@ -1051,7 +1032,7 @@ int os_remove(const char *path) bool os_fileinfo(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ARG(2) { - memset(file_info, 0, sizeof(*file_info)); + CLEAR_POINTER(file_info); return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } @@ -1063,7 +1044,7 @@ bool os_fileinfo(const char *path, FileInfo *file_info) bool os_fileinfo_link(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ARG(2) { - memset(file_info, 0, sizeof(*file_info)); + CLEAR_POINTER(file_info); if (path == NULL) { return false; } @@ -1087,7 +1068,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; - memset(file_info, 0, sizeof(*file_info)); + CLEAR_POINTER(file_info); fs_loop_lock(); bool ok = uv_fs_fstat(&fs_loop, &request, diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index c47a891c18..bfe6d59dc6 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -8,10 +8,9 @@ #include "nvim/api/private/defs.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" -#include "nvim/ex_cmds2.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/keycodes.h" #include "nvim/main.h" @@ -19,6 +18,7 @@ #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" +#include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/ui.h" diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h index c243db4fa5..15e7c3da0c 100644 --- a/src/nvim/os/pty_conpty_win.h +++ b/src/nvim/os/pty_conpty_win.h @@ -1,6 +1,9 @@ #ifndef NVIM_OS_PTY_CONPTY_WIN_H #define NVIM_OS_PTY_CONPTY_WIN_H +#include "nvim/lib/kvec.h" +#include "nvim/os/input.h" + #ifndef HPCON # define HPCON VOID * #endif diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 8f2018c1f4..dd44cb1ce7 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -25,6 +25,7 @@ #include "nvim/os/shell.h" #include "nvim/os/signal.h" #include "nvim/path.h" +#include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/tag.h" diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 581f025a0f..e592570966 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -9,10 +9,10 @@ #endif #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/eval.h" #include "nvim/event/loop.h" #include "nvim/event/signal.h" -#include "nvim/fileio.h" #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/main.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index a0b09bcec2..caea11debd 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -227,7 +227,7 @@ char_u *get_past_head(const char_u *path) #endif while (vim_ispathsep(*retval)) { - ++retval; + retval++; } return (char_u *)retval; @@ -666,8 +666,8 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, for (p = buf + wildoff; p < s; ++p) { if (rem_backslash(p)) { STRMOVE(p, p + 1); - --e; - --s; + e--; + s--; } } @@ -695,11 +695,11 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, regmatch.rm_ic = true; // Always ignore case on Windows. #endif if (flags & (EW_NOERROR | EW_NOTWILD)) { - ++emsg_silent; + emsg_silent++; } regmatch.regprog = vim_regcomp(pat, RE_MAGIC); if (flags & (EW_NOERROR | EW_NOTWILD)) { - --emsg_silent; + emsg_silent--; } xfree(pat); @@ -742,9 +742,9 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, * find matches. */ STRCPY(buf + len, "/**"); STRCPY(buf + len + 3, path_end); - ++stardepth; + stardepth++; (void)do_path_expand(gap, buf, len + 1, flags, true); - --stardepth; + stardepth--; } STRCPY(buf + len, path_end); @@ -1141,7 +1141,7 @@ static int expand_in_path(garray_T *const gap, char_u *const pattern, const int if (flags & EW_ADDSLASH) { glob_flags |= WILD_ADD_SLASH; } - globpath(paths, pattern, gap, glob_flags); + globpath((char *)paths, pattern, gap, glob_flags); xfree(paths); return gap->ga_len; @@ -1401,7 +1401,7 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags) cmd = skipwhite(cmd); // skip over white space p = cmd; while (*p != NUL && *p != '\r' && *p != '\n') { // skip over entry - ++p; + p++; } // add an entry if it is not empty if (p > cmd) { @@ -1409,11 +1409,11 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags) *p = NUL; addfile(gap, (char_u *)cmd, flags); *p = i; - ++cnt; + cnt++; } cmd = p; while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n')) { - ++cmd; + cmd++; } } @@ -1656,12 +1656,12 @@ void simplify_filename(char_u *filename) *p = NUL; } else { if (p > start && tail[-1] == '.') { - --p; + p--; } STRMOVE(p, tail); // strip previous component } - --components; + components--; } } else if (p == start && !relative) { // leading "/.." or "/../" STRMOVE(p, tail); // strip ".." or "../" diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmenu.c index 4accddfce0..92db2db735 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmenu.c @@ -1,12 +1,10 @@ // 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 -/// @file popupmnu.c +/// @file popupmenu.c /// /// Popup menu (PUM) -#include "nvim/popupmnu.h" - #include <assert.h> #include <inttypes.h> #include <stdbool.h> @@ -15,9 +13,11 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/insexpand.h" #include "nvim/memline.h" @@ -25,8 +25,7 @@ #include "nvim/menu.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -59,7 +58,7 @@ static bool pum_external = false; static bool pum_invalid = false; // the screen was just cleared #ifdef INCLUDE_GENERATED_DECLARATIONS -#include "popupmnu.c.generated.h" +# include "popupmenu.c.generated.h" #endif #define PUM_DEF_HEIGHT 10 #define PUM_DEF_WIDTH 15 @@ -472,7 +471,7 @@ void pum_redraw(void) / (pum_size - pum_height); } - for (i = 0; i < pum_height; ++i) { + for (i = 0; i < pum_height; i++) { idx = i + pum_first; attr = (idx == pum_selected) ? attr_select : attr_norm; @@ -492,7 +491,7 @@ void pum_redraw(void) grid_col = col_off; totwidth = 0; - for (round = 1; round <= 3; ++round) { + for (round = 1; round <= 3; round++) { width = 0; s = NULL; @@ -663,11 +662,11 @@ void pum_redraw(void) /// @param n /// @param repeat /// -/// @returns TRUE when the window was resized and the location of the popup +/// @returns true when the window was resized and the location of the popup /// menu must be recomputed. -static int pum_set_selected(int n, int repeat) +static bool pum_set_selected(int n, int repeat) { - int resized = FALSE; + int resized = false; int context = pum_height / 2; pum_selected = n; @@ -800,12 +799,12 @@ static int pum_set_selected(int n, int repeat) if (curwin->w_height < lnum) { win_setheight((int)lnum); - resized = TRUE; + resized = true; } } curbuf->b_changed = false; - curbuf->b_p_ma = FALSE; + curbuf->b_p_ma = false; curwin->w_cursor.lnum = 1; curwin->w_cursor.col = 0; @@ -819,7 +818,7 @@ static int pum_set_selected(int n, int repeat) // window is not resized, skip the preview window's // status line redrawing. if (ins_compl_active() && !resized) { - curwin->w_redr_status = FALSE; + curwin->w_redr_status = false; } // Return cursor to where we were @@ -928,7 +927,7 @@ void pum_recompose(void) /// Gets the height of the menu. /// /// @return the height of the popup menu, the number of entries visible. -/// Only valid when pum_visible() returns TRUE! +/// Only valid when pum_visible() returns true! int pum_get_height(void) { if (pum_external) { @@ -1051,7 +1050,7 @@ static void pum_execute_menu(vimmenu_T *menu, int mode) for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected) { - memset(&ea, 0, sizeof(ea)); + CLEAR_FIELD(ea); execute_menu(&ea, mp, -1); break; } diff --git a/src/nvim/popupmnu.h b/src/nvim/popupmenu.h index 7d3f4c6f51..851ad31486 100644 --- a/src/nvim/popupmnu.h +++ b/src/nvim/popupmenu.h @@ -1,5 +1,5 @@ -#ifndef NVIM_POPUPMNU_H -#define NVIM_POPUPMNU_H +#ifndef NVIM_POPUPMENU_H +#define NVIM_POPUPMENU_H #include "nvim/grid_defs.h" #include "nvim/macros.h" @@ -17,6 +17,6 @@ typedef struct { EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT); #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "popupmnu.h.generated.h" +# include "popupmenu.h.generated.h" #endif -#endif // NVIM_POPUPMNU_H +#endif // NVIM_POPUPMENU_H diff --git a/src/nvim/profile.c b/src/nvim/profile.c index fe7bd2e912..d4f3756f4d 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -6,16 +6,32 @@ #include <stdio.h> #include "nvim/assert.h" +#include "nvim/charset.h" +#include "nvim/debugger.h" +#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/globals.h" // for the global `time_fd` (startuptime) -#include "nvim/os/os_defs.h" +#include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/profile.h" +#include "nvim/runtime.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "profile.c.generated.h" #endif +/// Struct used in sn_prl_ga for every line of a script. +typedef struct sn_prl_S { + int snp_count; ///< nr of times line was executed + proftime_T sn_prl_total; ///< time spent in a line + children + proftime_T sn_prl_self; ///< time spent in a line itself +} sn_prl_T; + +#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) + static proftime_T prof_wait_time; /// Gets the current time. @@ -195,6 +211,652 @@ int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST return profile_signed(tm2 - tm1) < 0 ? -1 : 1; } +static char *profile_fname = NULL; + +/// Reset all profiling information. +void profile_reset(void) +{ + // Reset sourced files. + for (int id = 1; id <= script_items.ga_len; id++) { + scriptitem_T *si = &SCRIPT_ITEM(id); + if (si->sn_prof_on) { + si->sn_prof_on = false; + si->sn_pr_force = false; + si->sn_pr_child = profile_zero(); + si->sn_pr_nest = 0; + si->sn_pr_count = 0; + si->sn_pr_total = profile_zero(); + si->sn_pr_self = profile_zero(); + si->sn_pr_start = profile_zero(); + si->sn_pr_children = profile_zero(); + ga_clear(&si->sn_prl_ga); + si->sn_prl_start = profile_zero(); + si->sn_prl_children = profile_zero(); + si->sn_prl_wait = profile_zero(); + si->sn_prl_idx = -1; + si->sn_prl_execed = 0; + } + } + + // Reset functions. + hashtab_T *const functbl = func_tbl_get(); + size_t todo = functbl->ht_used; + hashitem_T *hi = functbl->ht_array; + + for (; todo > (size_t)0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + ufunc_T *uf = HI2UF(hi); + if (uf->uf_prof_initialized) { + uf->uf_profiling = 0; + uf->uf_tm_count = 0; + uf->uf_tm_total = profile_zero(); + uf->uf_tm_self = profile_zero(); + uf->uf_tm_children = profile_zero(); + + for (int i = 0; i < uf->uf_lines.ga_len; i++) { + uf->uf_tml_count[i] = 0; + uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0; + } + + uf->uf_tml_start = profile_zero(); + uf->uf_tml_children = profile_zero(); + uf->uf_tml_wait = profile_zero(); + uf->uf_tml_idx = -1; + uf->uf_tml_execed = 0; + } + } + } + + XFREE_CLEAR(profile_fname); +} + +/// ":profile cmd args" +void ex_profile(exarg_T *eap) +{ + static proftime_T pause_time; + + char *e; + int len; + + e = (char *)skiptowhite((char_u *)eap->arg); + len = (int)(e - eap->arg); + e = skipwhite(e); + + if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { + xfree(profile_fname); + profile_fname = (char *)expand_env_save_opt((char_u *)e, true); + do_profiling = PROF_YES; + profile_set_wait(profile_zero()); + set_vim_var_nr(VV_PROFILING, 1L); + } else if (do_profiling == PROF_NONE) { + emsg(_("E750: First use \":profile start {fname}\"")); + } else if (STRCMP(eap->arg, "stop") == 0) { + profile_dump(); + do_profiling = PROF_NONE; + set_vim_var_nr(VV_PROFILING, 0L); + profile_reset(); + } else if (STRCMP(eap->arg, "pause") == 0) { + if (do_profiling == PROF_YES) { + pause_time = profile_start(); + } + do_profiling = PROF_PAUSED; + } else if (STRCMP(eap->arg, "continue") == 0) { + if (do_profiling == PROF_PAUSED) { + pause_time = profile_end(pause_time); + profile_set_wait(profile_add(profile_get_wait(), pause_time)); + } + do_profiling = PROF_YES; + } else if (STRCMP(eap->arg, "dump") == 0) { + profile_dump(); + } else { + // The rest is similar to ":breakadd". + ex_breakadd(eap); + } +} + +/// Command line expansion for :profile. +static enum { + PEXP_SUBCMD, ///< expand :profile sub-commands + PEXP_FUNC, ///< expand :profile func {funcname} +} pexpand_what; + +static char *pexpand_cmds[] = { + "continue", + "dump", + "file", + "func", + "pause", + "start", + "stop", + NULL +}; + +/// Function given to ExpandGeneric() to obtain the profile command +/// specific expansion. +char *get_profile_name(expand_T *xp, int idx) + FUNC_ATTR_PURE +{ + switch (pexpand_what) { + case PEXP_SUBCMD: + return pexpand_cmds[idx]; + // case PEXP_FUNC: TODO + default: + return NULL; + } +} + +/// Handle command line completion for :profile command. +void set_context_in_profile_cmd(expand_T *xp, const char *arg) +{ + // Default: expand subcommands. + xp->xp_context = EXPAND_PROFILE; + pexpand_what = PEXP_SUBCMD; + xp->xp_pattern = (char *)arg; + + char_u *const end_subcmd = skiptowhite((const char_u *)arg); + if (*end_subcmd == NUL) { + return; + } + + if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) { + xp->xp_context = EXPAND_FILES; + xp->xp_pattern = skipwhite((char *)end_subcmd); + return; + } + + // TODO(tarruda): expand function names after "func" + xp->xp_context = EXPAND_NOTHING; +} + +static proftime_T inchar_time; + +/// Called when starting to wait for the user to type a character. +void prof_inchar_enter(void) +{ + inchar_time = profile_start(); +} + +/// Called when finished waiting for the user to type a character. +void prof_inchar_exit(void) +{ + inchar_time = profile_end(inchar_time); + profile_set_wait(profile_add(profile_get_wait(), inchar_time)); +} + +/// @return true when a function defined in the current script should be +/// profiled. +bool prof_def_func(void) + FUNC_ATTR_PURE +{ + if (current_sctx.sc_sid > 0) { + return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force; + } + return false; +} + +/// Print the count and times for one function or function line. +/// +/// @param prefer_self when equal print only self time +static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self, + bool prefer_self) +{ + if (count > 0) { + fprintf(fd, "%5d ", count); + if (prefer_self && profile_equal(*total, *self)) { + fprintf(fd, " "); + } else { + fprintf(fd, "%s ", profile_msg(*total)); + } + if (!prefer_self && profile_equal(*total, *self)) { + fprintf(fd, " "); + } else { + fprintf(fd, "%s ", profile_msg(*self)); + } + } else { + fprintf(fd, " "); + } +} + +/// @param prefer_self when equal print only self time +static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self) +{ + int i; + ufunc_T *fp; + + fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title); + fprintf(fd, "count total (s) self (s) function\n"); + for (i = 0; i < 20 && i < st_len; i++) { + fp = sorttab[i]; + prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self, + prefer_self); + if (fp->uf_name[0] == K_SPECIAL) { + fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); + } else { + fprintf(fd, " %s()\n", fp->uf_name); + } + } + fprintf(fd, "\n"); +} + +/// Compare function for total time sorting. +static int prof_total_cmp(const void *s1, const void *s2) +{ + ufunc_T *p1 = *(ufunc_T **)s1; + ufunc_T *p2 = *(ufunc_T **)s2; + return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); +} + +/// Compare function for self time sorting. +static int prof_self_cmp(const void *s1, const void *s2) +{ + ufunc_T *p1 = *(ufunc_T **)s1; + ufunc_T *p2 = *(ufunc_T **)s2; + return profile_cmp(p1->uf_tm_self, p2->uf_tm_self); +} + +/// Start profiling function "fp". +void func_do_profile(ufunc_T *fp) +{ + int len = fp->uf_lines.ga_len; + + if (!fp->uf_prof_initialized) { + if (len == 0) { + len = 1; // avoid getting error for allocating zero bytes + } + fp->uf_tm_count = 0; + fp->uf_tm_self = profile_zero(); + fp->uf_tm_total = profile_zero(); + + if (fp->uf_tml_count == NULL) { + fp->uf_tml_count = xcalloc((size_t)len, sizeof(int)); + } + + if (fp->uf_tml_total == NULL) { + fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T)); + } + + if (fp->uf_tml_self == NULL) { + fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T)); + } + + fp->uf_tml_idx = -1; + fp->uf_prof_initialized = true; + } + + fp->uf_profiling = true; +} + +/// Prepare profiling for entering a child or something else that is not +/// counted for the script/function itself. +/// Should always be called in pair with prof_child_exit(). +/// +/// @param tm place to store waittime +void prof_child_enter(proftime_T *tm) +{ + funccall_T *fc = get_current_funccal(); + + if (fc != NULL && fc->func->uf_profiling) { + fc->prof_child = profile_start(); + } + + script_prof_save(tm); +} + +/// Take care of time spent in a child. +/// Should always be called after prof_child_enter(). +/// +/// @param tm where waittime was stored +void prof_child_exit(proftime_T *tm) +{ + funccall_T *fc = get_current_funccal(); + + if (fc != NULL && fc->func->uf_profiling) { + fc->prof_child = profile_end(fc->prof_child); + // don't count waiting time + fc->prof_child = profile_sub_wait(*tm, fc->prof_child); + fc->func->uf_tm_children = + profile_add(fc->func->uf_tm_children, fc->prof_child); + fc->func->uf_tml_children = + profile_add(fc->func->uf_tml_children, fc->prof_child); + } + script_prof_restore(tm); +} + +/// Called when starting to read a function line. +/// "sourcing_lnum" must be correct! +/// When skipping lines it may not actually be executed, but we won't find out +/// until later and we need to store the time now. +void func_line_start(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && SOURCING_LNUM >= 1 && SOURCING_LNUM <= fp->uf_lines.ga_len) { + fp->uf_tml_idx = SOURCING_LNUM - 1; + // Skip continuation lines. + while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { + fp->uf_tml_idx--; + } + fp->uf_tml_execed = false; + fp->uf_tml_start = profile_start(); + fp->uf_tml_children = profile_zero(); + fp->uf_tml_wait = profile_get_wait(); + } +} + +/// Called when actually executing a function line. +void func_line_exec(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && fp->uf_tml_idx >= 0) { + fp->uf_tml_execed = true; + } +} + +/// Called when done with a function line. +void func_line_end(void *cookie) +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && fp->uf_tml_idx >= 0) { + if (fp->uf_tml_execed) { + fp->uf_tml_count[fp->uf_tml_idx]++; + fp->uf_tml_start = profile_end(fp->uf_tml_start); + fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start); + fp->uf_tml_total[fp->uf_tml_idx] = + profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start); + fp->uf_tml_self[fp->uf_tml_idx] = + profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start, + fp->uf_tml_children); + } + fp->uf_tml_idx = -1; + } +} + +/// Dump the profiling results for all functions in file "fd". +static void func_dump_profile(FILE *fd) +{ + hashtab_T *const functbl = func_tbl_get(); + hashitem_T *hi; + int todo; + ufunc_T *fp; + ufunc_T **sorttab; + int st_len = 0; + + todo = (int)functbl->ht_used; + if (todo == 0) { + return; // nothing to dump + } + + sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo); + + for (hi = functbl->ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (fp->uf_prof_initialized) { + sorttab[st_len++] = fp; + + if (fp->uf_name[0] == K_SPECIAL) { + fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3); + } else { + fprintf(fd, "FUNCTION %s()\n", fp->uf_name); + } + if (fp->uf_script_ctx.sc_sid != 0) { + bool should_free; + const LastSet last_set = (LastSet){ + .script_ctx = fp->uf_script_ctx, + .channel_id = 0, + }; + char *p = (char *)get_scriptname(last_set, &should_free); + fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", + p, fp->uf_script_ctx.sc_lnum); + if (should_free) { + xfree(p); + } + } + if (fp->uf_tm_count == 1) { + fprintf(fd, "Called 1 time\n"); + } else { + fprintf(fd, "Called %d times\n", fp->uf_tm_count); + } + fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total)); + fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + for (int i = 0; i < fp->uf_lines.ga_len; i++) { + if (FUNCLINE(fp, i) == NULL) { + continue; + } + prof_func_line(fd, fp->uf_tml_count[i], + &fp->uf_tml_total[i], &fp->uf_tml_self[i], true); + fprintf(fd, "%s\n", FUNCLINE(fp, i)); + } + fprintf(fd, "\n"); + } + } + } + + if (st_len > 0) { + qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), + prof_total_cmp); + prof_sort_list(fd, sorttab, st_len, "TOTAL", false); + qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), + prof_self_cmp); + prof_sort_list(fd, sorttab, st_len, "SELF", true); + } + + xfree(sorttab); +} + +/// Start profiling a script. +void profile_init(scriptitem_T *si) +{ + si->sn_pr_count = 0; + si->sn_pr_total = profile_zero(); + si->sn_pr_self = profile_zero(); + + ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); + si->sn_prl_idx = -1; + si->sn_prof_on = true; + si->sn_pr_nest = 0; +} + +/// Save time when starting to invoke another script or function. +/// +/// @param tm place to store wait time +void script_prof_save(proftime_T *tm) +{ + scriptitem_T *si; + + if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && si->sn_pr_nest++ == 0) { + si->sn_pr_child = profile_start(); + } + } + *tm = profile_get_wait(); +} + +/// Count time spent in children after invoking another script or function. +void script_prof_restore(proftime_T *tm) +{ + scriptitem_T *si; + + if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && --si->sn_pr_nest == 0) { + si->sn_pr_child = profile_end(si->sn_pr_child); + // don't count wait time + si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); + si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); + si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); + } + } +} + +/// Dump the profiling results for all scripts in file "fd". +static void script_dump_profile(FILE *fd) +{ + scriptitem_T *si; + FILE *sfd; + sn_prl_T *pp; + + for (int id = 1; id <= script_items.ga_len; id++) { + si = &SCRIPT_ITEM(id); + if (si->sn_prof_on) { + fprintf(fd, "SCRIPT %s\n", si->sn_name); + if (si->sn_pr_count == 1) { + fprintf(fd, "Sourced 1 time\n"); + } else { + fprintf(fd, "Sourced %d times\n", si->sn_pr_count); + } + fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total)); + fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + sfd = os_fopen((char *)si->sn_name, "r"); + if (sfd == NULL) { + fprintf(fd, "Cannot open file!\n"); + } else { + // Keep going till the end of file, so that trailing + // continuation lines are listed. + for (int i = 0;; i++) { + if (vim_fgets(IObuff, IOSIZE, sfd)) { + break; + } + // When a line has been truncated, append NL, taking care + // of multi-byte characters . + if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) { + int n = IOSIZE - 2; + + // Move to the first byte of this char. + // utf_head_off() doesn't work, because it checks + // for a truncated character. + while (n > 0 && (IObuff[n] & 0xc0) == 0x80) { + n--; + } + + IObuff[n] = NL; + IObuff[n + 1] = NUL; + } + if (i < si->sn_prl_ga.ga_len + && (pp = &PRL_ITEM(si, i))->snp_count > 0) { + fprintf(fd, "%5d ", pp->snp_count); + if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) { + fprintf(fd, " "); + } else { + fprintf(fd, "%s ", profile_msg(pp->sn_prl_total)); + } + fprintf(fd, "%s ", profile_msg(pp->sn_prl_self)); + } else { + fprintf(fd, " "); + } + fprintf(fd, "%s", IObuff); + } + fclose(sfd); + } + fprintf(fd, "\n"); + } + } +} + +/// Dump the profiling info. +void profile_dump(void) +{ + FILE *fd; + + if (profile_fname != NULL) { + fd = os_fopen(profile_fname, "w"); + if (fd == NULL) { + semsg(_(e_notopen), profile_fname); + } else { + script_dump_profile(fd); + func_dump_profile(fd); + fclose(fd); + } + } +} + +/// Called when starting to read a script line. +/// "sourcing_lnum" must be correct! +/// When skipping lines it may not actually be executed, but we won't find out +/// until later and we need to store the time now. +void script_line_start(void) +{ + scriptitem_T *si; + sn_prl_T *pp; + + if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { + return; + } + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && SOURCING_LNUM >= 1) { + // Grow the array before starting the timer, so that the time spent + // here isn't counted. + (void)ga_grow(&si->sn_prl_ga, SOURCING_LNUM - si->sn_prl_ga.ga_len); + si->sn_prl_idx = SOURCING_LNUM - 1; + while (si->sn_prl_ga.ga_len <= si->sn_prl_idx + && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { + // Zero counters for a line that was not used before. + pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len); + pp->snp_count = 0; + pp->sn_prl_total = profile_zero(); + pp->sn_prl_self = profile_zero(); + si->sn_prl_ga.ga_len++; + } + si->sn_prl_execed = false; + si->sn_prl_start = profile_start(); + si->sn_prl_children = profile_zero(); + si->sn_prl_wait = profile_get_wait(); + } +} + +/// Called when actually executing a function line. +void script_line_exec(void) +{ + scriptitem_T *si; + + if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { + return; + } + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && si->sn_prl_idx >= 0) { + si->sn_prl_execed = true; + } +} + +/// Called when done with a function line. +void script_line_end(void) +{ + scriptitem_T *si; + sn_prl_T *pp; + + if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { + return; + } + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && si->sn_prl_idx >= 0 + && si->sn_prl_idx < si->sn_prl_ga.ga_len) { + if (si->sn_prl_execed) { + pp = &PRL_ITEM(si, si->sn_prl_idx); + pp->snp_count++; + si->sn_prl_start = profile_end(si->sn_prl_start); + si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start); + pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start); + pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start, + si->sn_prl_children); + } + si->sn_prl_idx = -1; + } +} + /// globals for use in the startuptime related functionality (time_*). static proftime_T g_start_time; static proftime_T g_prev_time; diff --git a/src/nvim/profile.h b/src/nvim/profile.h index 17c35c5eb7..547d11185f 100644 --- a/src/nvim/profile.h +++ b/src/nvim/profile.h @@ -4,7 +4,8 @@ #include <stdint.h> #include <time.h> -typedef uint64_t proftime_T; +#include "nvim/ex_cmds_defs.h" +#include "nvim/runtime.h" #define TIME_MSG(s) do { \ if (time_fd != NULL) time_msg(s, NULL); \ diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 99129bd15e..1c416a872b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -9,10 +9,12 @@ #include <string.h> #include "nvim/api/private/helpers.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" @@ -22,6 +24,7 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/help.h" #include "nvim/highlight_group.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -37,7 +40,6 @@ #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -1224,7 +1226,7 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title) qi->qf_curlist = qi->qf_listcount++; } qf_list_T *qfl = qf_get_curlist(qi); - memset(qfl, 0, sizeof(qf_list_T)); + CLEAR_POINTER(qfl); qf_store_title(qfl, qf_title); qfl->qfl_type = qi->qfl_type; qfl->qf_id = ++last_qf_id; @@ -3119,7 +3121,7 @@ void qf_list(exarg_T *eap) } int idx1 = 1; int idx2 = -1; - if (!get_list_range((char_u **)&arg, &idx1, &idx2) || *arg != NUL) { + if (!get_list_range(&arg, &idx1, &idx2) || *arg != NUL) { semsg(_(e_trailing_arg), arg); return; } @@ -5299,7 +5301,7 @@ static bool existing_swapfile(const buf_T *buf) /// :{count}vimgrep /{pattern}/[g][j] {file} ... static int vgr_process_args(exarg_T *eap, vgr_args_T *args) { - memset(args, 0, sizeof(*args)); + CLEAR_POINTER(args); args->regmatch.regprog = NULL; args->qf_title = xstrdup(qf_cmdtitle(*eap->cmdlinep)); @@ -5779,7 +5781,7 @@ static int get_qfline_items(qfline_T *qfp, list_T *list) /// If qf_idx is -1, use the current list. Otherwise, use the specified list. /// If eidx is not 0, then return only the specified entry. Otherwise return /// all the entries. -int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, list_T *list) +static int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, list_T *list) { qf_info_T *qi = qi_arg; @@ -6149,7 +6151,7 @@ static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict) /// Return quickfix/location list details (title) as a dictionary. /// 'what' contains the details to return. If 'list_idx' is -1, /// then current list is used. Otherwise the specified list is used. -int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) +static int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; dictitem_T *di = NULL; @@ -7157,3 +7159,137 @@ void ex_helpgrep(exarg_T *eap) } } } + +static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) +{ + if (what_arg->v_type == VAR_UNKNOWN) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + if (is_qf || wp != NULL) { + (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list); + } + } else { + tv_dict_alloc_ret(rettv); + if (is_qf || wp != NULL) { + if (what_arg->v_type == VAR_DICT) { + dict_T *d = what_arg->vval.v_dict; + + if (d != NULL) { + qf_get_properties(wp, d, rettv->vval.v_dict); + } + } else { + emsg(_(e_dictreq)); + } + } + } +} + +/// "getloclist()" function +void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + get_qf_loc_list(false, wp, &argvars[1], rettv); +} + +/// "getqflist()" functions +void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_qf_loc_list(true, NULL, &argvars[0], rettv); +} + +/// Create quickfix/location list from VimL values +/// +/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid +/// args argument in which case errors out, including VAR_UNKNOWN parameters. +/// +/// @param[in,out] wp Window to create location list for. May be NULL in +/// which case quickfix list will be created. +/// @param[in] args [list, action, what] +/// @param[in] args[0] Quickfix list contents. +/// @param[in] args[1] Optional. Action to perform: +/// append to an existing list, replace its content, +/// or create a new one. +/// @param[in] args[2] Optional. Quickfix list properties or title. +/// Defaults to caller function name. +/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. +static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(2, 3) +{ + static char *e_invact = N_("E927: Invalid action: '%s'"); + const char *title = NULL; + char action = ' '; + static int recursive = 0; + rettv->vval.v_number = -1; + dict_T *what = NULL; + + typval_T *list_arg = &args[0]; + if (list_arg->v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } else if (recursive != 0) { + emsg(_(e_au_recursive)); + return; + } + + typval_T *action_arg = &args[1]; + if (action_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (action_arg->v_type != VAR_STRING) { + emsg(_(e_stringreq)); + return; + } + const char *const act = tv_get_string_chk(action_arg); + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { + action = *act; + } else { + semsg(_(e_invact), act); + return; + } + + typval_T *const what_arg = &args[2]; + if (what_arg->v_type == VAR_UNKNOWN) { + // Option argument was not given. + goto skip_args; + } else if (what_arg->v_type == VAR_STRING) { + title = tv_get_string_chk(what_arg); + if (!title) { + // Type error. Error already printed by tv_get_string_chk(). + return; + } + } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) { + what = what_arg->vval.v_dict; + } else { + emsg(_(e_dictreq)); + return; + } + +skip_args: + if (!title) { + title = (wp ? ":setloclist()" : ":setqflist()"); + } + + recursive++; + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char *)title, what) == OK) { + rettv->vval.v_number = 0; + } + recursive--; +} + +/// "setloclist()" function +void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + win_T *win = find_win_by_nr_or_id(&argvars[0]); + if (win != NULL) { + set_qf_ll_list(win, &argvars[1], rettv); + } +} + +/// "setqflist()" function +void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_qf_ll_list(NULL, argvars, rettv); +} diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index fbbf904f8b..b7ec4bf94e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -20,7 +20,6 @@ #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds2.h" #include "nvim/garray.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -28,6 +27,7 @@ #include "nvim/message.h" #include "nvim/os/input.h" #include "nvim/plines.h" +#include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -481,16 +481,14 @@ static char_u *skip_anyof(char *p) return (char_u *)p; } -/* - * Skip past regular expression. - * Stop at end of "startp" or where "dirc" is found ('/', '?', etc). - * Take care of characters with a backslash in front of it. - * Skip strings inside [ and ]. - * When "newp" is not NULL and "dirc" is '?', make an allocated copy of the - * expression and change "\?" to "?". If "*newp" is not NULL the expression - * is changed in-place. - */ -char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) +/// Skip past regular expression. +/// Stop at end of "startp" or where "dirc" is found ('/', '?', etc). +/// Take care of characters with a backslash in front of it. +/// Skip strings inside [ and ]. +/// When "newp" is not NULL and "dirc" is '?', make an allocated copy of the +/// expression and change "\?" to "?". If "*newp" is not NULL the expression +/// is changed in-place. +char_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp) { int mymagic; char_u *p = startp; @@ -516,8 +514,8 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) if (dirc == '?' && newp != NULL && p[1] == '?') { // change "\?" to "?", make a copy first. if (*newp == NULL) { - *newp = vim_strsave(startp); - p = *newp + (p - startp); + *newp = (char *)vim_strsave(startp); + p = (char_u *)(*newp) + (p - startp); } STRMOVE(p, p + 1); } else { @@ -823,7 +821,7 @@ static int64_t gethexchrs(int maxinputlen) } nr <<= 4; nr |= hex2nr(c); - ++regparse; + regparse++; } if (i == 0) { @@ -880,7 +878,7 @@ static int64_t getoctchrs(void) } nr <<= 3; nr |= hex2nr(c); - ++regparse; + regparse++; } if (i == 0) { @@ -2097,8 +2095,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des dst++; } - ++s; - --len; + s++; + len--; } } } diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index fff5ae9b7f..769d2ceeef 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -466,7 +466,7 @@ static void regcomp_start(char_u *expr, int re_flags) // num_complex_braces = 0; regnpar = 1; - memset(had_endbrace, 0, sizeof(had_endbrace)); + CLEAR_FIELD(had_endbrace); regnzpar = 1; re_has_z = 0; regsize = 0L; @@ -2189,7 +2189,7 @@ collection: while (*regparse != NUL && *regparse != ']') { if (*regparse == '-') { - ++regparse; + regparse++; // The '-' is not used for a range at the end and // after or before a '\n'. if (*regparse == ']' || *regparse == NUL @@ -2619,7 +2619,7 @@ static char_u *regpiece(int *flagp) regoptail(ret, regnode(BACK)); regoptail(ret, ret); reginsert_limits(BRACE_LIMITS, minval, maxval, ret); - ++num_complex_braces; + num_complex_braces++; } if (minval > 0 && maxval > 0) { *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH))); @@ -2792,7 +2792,7 @@ static char_u *reg(int paren, int *flagp) EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL); } parno = regnpar; - ++regnpar; + regnpar++; ret = regnode(MOPEN + parno); } else if (paren == REG_NPAREN) { // Make a NOPEN node. @@ -3181,7 +3181,7 @@ static int regrepeat(char_u *p, long maxcount) } else { break; } - ++count; + count++; } break; @@ -3299,7 +3299,7 @@ do_class: } else { break; } - ++count; + count++; } break; @@ -3415,7 +3415,7 @@ do_class: break; } scan += len; - ++count; + count++; } } } @@ -3453,7 +3453,7 @@ do_class: } scan++; } - ++count; + count++; } break; diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 09f244c2f6..b313dfe877 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -15,7 +15,6 @@ #include <stdbool.h> #include "nvim/pos.h" -#include "nvim/profile.h" #include "nvim/types.h" /* diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 1a5c250664..554def5b8a 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -5055,7 +5055,7 @@ skip_add: // avoid compiler warnings save_ptr = NULL; - memset(&save_multipos, 0, sizeof(save_multipos)); + CLEAR_FIELD(save_multipos); // Set the position (with "off" added) in the subexpression. Save // and restore it when it was in use. Otherwise fill any gap. @@ -5180,7 +5180,7 @@ skip_add: save_ptr = sub->list.line[subidx].end; sub->list.line[subidx].end = rex.input + off; // avoid compiler warnings - memset(&save_multipos, 0, sizeof(save_multipos)); + CLEAR_FIELD(save_multipos); } subs = addstate(l, state->out, subs, pim, off_arg); @@ -5488,7 +5488,7 @@ static void nfa_save_listids(nfa_regprog_T *prog, int *list) for (i = prog->nstate; --i >= 0;) { list[i] = p->lastlist[1]; p->lastlist[1] = 0; - ++p; + p++; } } @@ -5503,7 +5503,7 @@ static void nfa_restore_listids(nfa_regprog_T *prog, int *list) p = &prog->state[0]; for (i = prog->nstate; --i >= 0;) { p->lastlist[1] = list[i]; - ++p; + p++; } } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 5d28d624fe..edcaa27e2b 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -7,20 +7,155 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" +#include "nvim/debugger.h" #include "nvim/eval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" +#include "nvim/ex_getln.h" #include "nvim/lua/executor.h" +#include "nvim/memline.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/profile.h" #include "nvim/runtime.h" #include "nvim/vim.h" +/// Structure used to store info for each sourced file. +/// It is shared between do_source() and getsourceline(). +/// This is required, because it needs to be handed to do_cmdline() and +/// sourcing can be done recursively. +struct source_cookie { + FILE *fp; ///< opened file for sourcing + char *nextline; ///< if not NULL: line that was read ahead + linenr_T sourcing_lnum; ///< line number of the source file + int finished; ///< ":finish" used +#if defined(USE_CRNL) + int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS + bool error; ///< true if LF found after CR-LF +#endif + linenr_T breakpoint; ///< next line with breakpoint or zero + char *fname; ///< name of sourced file + int dbg_tick; ///< debug_tick when breakpoint was set + int level; ///< top nesting level of sourced file + vimconv_T conv; ///< type of conversion +}; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "runtime.c.generated.h" #endif +garray_T exestack = { 0, 0, sizeof(estack_T), 50, NULL }; +garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL }; + +/// Initialize the execution stack. +void estack_init(void) +{ + ga_grow(&exestack, 10); + estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len; + entry->es_type = ETYPE_TOP; + entry->es_name = NULL; + entry->es_lnum = 0; + entry->es_info.ufunc = NULL; + exestack.ga_len++; +} + +/// Add an item to the execution stack. +/// @return the new entry +estack_T *estack_push(etype_T type, char *name, linenr_T lnum) +{ + ga_grow(&exestack, 1); + estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len; + entry->es_type = type; + entry->es_name = name; + entry->es_lnum = lnum; + entry->es_info.ufunc = NULL; + exestack.ga_len++; + return entry; +} + +/// Add a user function to the execution stack. +void estack_push_ufunc(ufunc_T *ufunc, linenr_T lnum) +{ + estack_T *entry = estack_push(ETYPE_UFUNC, + (char *)(ufunc->uf_name_exp != NULL + ? ufunc->uf_name_exp : ufunc->uf_name), + lnum); + if (entry != NULL) { + entry->es_info.ufunc = ufunc; + } +} + +/// Take an item off of the execution stack. +void estack_pop(void) +{ + if (exestack.ga_len > 1) { + exestack.ga_len--; + } +} + +/// Get the current value for <sfile> in allocated memory. +/// @param which ESTACK_SFILE for <sfile> and ESTACK_STACK for <stack>. +char *estack_sfile(estack_arg_T which) +{ + estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + if (which == ESTACK_SFILE && entry->es_type != ETYPE_UFUNC) { + if (entry->es_name == NULL) { + return NULL; + } + return xstrdup(entry->es_name); + } + + // Give information about each stack entry up to the root. + // For a function we compose the call stack, as it was done in the past: + // "function One[123]..Two[456]..Three" + garray_T ga; + ga_init(&ga, sizeof(char), 100); + etype_T last_type = ETYPE_SCRIPT; + for (int idx = 0; idx < exestack.ga_len; idx++) { + entry = ((estack_T *)exestack.ga_data) + idx; + if (entry->es_name != NULL) { + size_t len = strlen(entry->es_name) + 15; + char *type_name = ""; + if (entry->es_type != last_type) { + switch (entry->es_type) { + case ETYPE_SCRIPT: + type_name = "script "; break; + case ETYPE_UFUNC: + type_name = "function "; break; + default: + type_name = ""; break; + } + last_type = entry->es_type; + } + len += strlen(type_name); + ga_grow(&ga, (int)len); + linenr_T lnum = idx == exestack.ga_len - 1 + ? which == ESTACK_STACK ? SOURCING_LNUM : 0 + : entry->es_lnum; + char *dots = idx == exestack.ga_len - 1 ? "" : ".."; + if (lnum == 0) { + // For the bottom entry of <sfile>: do not add the line number, + // it is used in <slnum>. Also leave it out when the number is + // not set. + vim_snprintf((char *)ga.ga_data + ga.ga_len, len, "%s%s%s", + type_name, entry->es_name, dots); + } else { + vim_snprintf((char *)ga.ga_data + ga.ga_len, len, "%s%s[%" PRIdLINENR "]%s", + type_name, entry->es_name, lnum, dots); + } + ga.ga_len += (int)strlen((char *)ga.ga_data + ga.ga_len); + } + } + + return (char *)ga.ga_data; +} + static bool runtime_search_path_valid = false; static int *runtime_search_path_ref = NULL; static RuntimeSearchPath runtime_search_path; @@ -90,10 +225,10 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, } // Loop over all entries in 'runtimepath'. - char_u *rtp = rtp_copy; + char *rtp = (char *)rtp_copy; while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { // Copy the path from 'runtimepath' to buf[]. - copy_option_part((char **)&rtp, buf, MAXPATHL, ","); + copy_option_part(&rtp, buf, MAXPATHL, ","); size_t buflen = STRLEN(buf); // Skip after or non-after directories. @@ -114,12 +249,11 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, tail = (char_u *)buf + STRLEN(buf); // Loop over all patterns in "name" - char_u *np = (char_u *)name; + char *np = name; while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. assert(MAXPATHL >= (tail - (char_u *)buf)); - copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)), - "\t "); + copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -248,11 +382,11 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void tail = buf + STRLEN(buf); // Loop over all patterns in "name" - char_u *np = name; + char *np = (char *)name; while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. assert(MAXPATHL >= (tail - buf)); - copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); + copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -991,6 +1125,160 @@ void ex_packadd(exarg_T *eap) } } +/// Expand color scheme, compiler or filetype names. +/// Search from 'runtimepath': +/// 'runtimepath'/{dirnames}/{pat}.vim +/// When "flags" has DIP_START: search also from 'start' of 'packpath': +/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim +/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath': +/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim +/// When "flags" has DIP_LUA: search also performed for .lua files +/// "dirnames" is an array with one or more directory names. +int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirnames[]) +{ + *num_file = 0; + *file = NULL; + size_t pat_len = STRLEN(pat); + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 10); + + // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic. + for (int i = 0; dirnames[i] != NULL; i++) { + size_t size = STRLEN(dirnames[i]) + pat_len + 7; + char_u *s = xmalloc(size); + snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat); + globpath((char *)p_rtp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat); + globpath((char *)p_rtp, s, &ga, 0); + } + xfree(s); + } + + if (flags & DIP_START) { + for (int i = 0; dirnames[i] != NULL; i++) { + size_t size = STRLEN(dirnames[i]) + pat_len + 22; + char_u *s = xmalloc(size); + snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + } + xfree(s); + } + + for (int i = 0; dirnames[i] != NULL; i++) { + size_t size = STRLEN(dirnames[i]) + pat_len + 22; + char_u *s = xmalloc(size); + snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + } + xfree(s); + } + } + + if (flags & DIP_OPT) { + for (int i = 0; dirnames[i] != NULL; i++) { + size_t size = STRLEN(dirnames[i]) + pat_len + 20; + char_u *s = xmalloc(size); + snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + } + xfree(s); + } + + for (int i = 0; dirnames[i] != NULL; i++) { + size_t size = STRLEN(dirnames[i]) + pat_len + 20; + char_u *s = xmalloc(size); + snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + } + xfree(s); + } + } + + for (int i = 0; i < ga.ga_len; i++) { + char_u *match = ((char_u **)ga.ga_data)[i]; + char_u *s = match; + char_u *e = s + STRLEN(s); + if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0 + || ((flags & DIP_LUA) + && STRNICMP(e - 4, ".lua", 4) == 0))) { + e -= 4; + for (s = e; s > match; MB_PTR_BACK(match, s)) { + if (vim_ispathsep(*s)) { + break; + } + } + s++; + *e = NUL; + assert((e - s) + 1 >= 0); + memmove(match, s, (size_t)(e - s) + 1); + } + } + + if (GA_EMPTY(&ga)) { + return FAIL; + } + + // Sort and remove duplicates which can happen when specifying multiple + // directories in dirnames. + ga_remove_duplicate_strings(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/// Expand loadplugin names: +/// 'packpath'/pack/ * /opt/{pat} +int ExpandPackAddDir(char_u *pat, int *num_file, char ***file) +{ + garray_T ga; + + *num_file = 0; + *file = NULL; + size_t pat_len = STRLEN(pat); + ga_init(&ga, (int)sizeof(char *), 10); + + size_t buflen = pat_len + 26; + char_u *s = xmalloc(buflen); + snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT + globpath((char *)p_pp, s, &ga, 0); + xfree(s); + + for (int i = 0; i < ga.ga_len; i++) { + char_u *match = ((char_u **)ga.ga_data)[i]; + s = (char_u *)path_tail((char *)match); + memmove(match, s, STRLEN(s) + 1); + } + + if (GA_EMPTY(&ga)) { + return FAIL; + } + + // Sort and remove duplicates which can happen when specifying multiple + // directories in dirnames. + ga_remove_duplicate_strings(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + /// Append string with escaped commas static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT @@ -1282,3 +1570,901 @@ freeall: return rtp; } #undef NVIM_SIZE + +static void cmd_source(char *fname, exarg_T *eap) +{ + if (eap != NULL && *fname == NUL) { + cmd_source_buffer(eap); + } else if (eap != NULL && eap->forceit) { + // ":source!": read Normal mode commands + // Need to execute the commands directly. This is required at least + // for: + // - ":g" command busy + // - after ":argdo", ":windo" or ":bufdo" + // - another command follows + // - inside a loop + openscript((char_u *)fname, global_busy || listcmd_busy || eap->nextcmd != NULL + || eap->cstack->cs_idx >= 0); + + // ":source" read ex commands + } else if (do_source(fname, false, DOSO_NONE) == FAIL) { + semsg(_(e_notopen), fname); + } +} + +/// ":source [{fname}]" +void ex_source(exarg_T *eap) +{ + cmd_source(eap->arg, eap); +} + +/// ":options" +void ex_options(exarg_T *eap) +{ + char buf[500]; + bool multi_mods = 0; + + buf[0] = NUL; + (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); + + os_setenv("OPTWIN_CMD", buf, 1); + cmd_source(SYS_OPTWIN_FILE, NULL); +} + +/// ":source" and associated commands. +/// +/// @return address holding the next breakpoint line for a source cookie +linenr_T *source_breakpoint(void *cookie) +{ + return &((struct source_cookie *)cookie)->breakpoint; +} + +/// @return the address holding the debug tick for a source cookie. +int *source_dbg_tick(void *cookie) +{ + return &((struct source_cookie *)cookie)->dbg_tick; +} + +/// @return the nesting level for a source cookie. +int source_level(void *cookie) + FUNC_ATTR_PURE +{ + return ((struct source_cookie *)cookie)->level; +} + +/// Special function to open a file without handle inheritance. +/// If possible the handle is closed on exec(). +static FILE *fopen_noinh_readbin(char *filename) +{ +#ifdef WIN32 + int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0); +#else + int fd_tmp = os_open(filename, O_RDONLY, 0); +#endif + + if (fd_tmp < 0) { + return NULL; + } + + (void)os_set_cloexec(fd_tmp); + + return fdopen(fd_tmp, READBIN); +} + +/// Concatenate VimL line if it starts with a line continuation into a growarray +/// (excluding the continuation chars and leading whitespace) +/// +/// @note Growsize of the growarray may be changed to speed up concatenations! +/// +/// @param ga the growarray to append to +/// @param init_growsize the starting growsize value of the growarray +/// @param p pointer to the beginning of the line to consider +/// @param len the length of this line +/// +/// @return true if this line did begin with a continuation (the next line +/// should also be considered, if it exists); false otherwise +static bool concat_continued_line(garray_T *const ga, const int init_growsize, + const char_u *const p, size_t len) + FUNC_ATTR_NONNULL_ALL +{ + const char *const line = (char *)skipwhite_len(p, len); + len -= (size_t)((char_u *)line - p); + // Skip lines starting with '\" ', concat lines starting with '\' + if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) { + return true; + } else if (len == 0 || line[0] != '\\') { + return false; + } + if (ga->ga_len > init_growsize) { + ga_set_growsize(ga, MIN(ga->ga_len, 8000)); + } + ga_concat_len(ga, line + 1, len - 1); + return true; +} + +typedef struct { + linenr_T curr_lnum; + const linenr_T final_lnum; +} GetBufferLineCookie; + +typedef struct { + char *buf; + size_t offset; +} GetStrLineCookie; + +/// Get one full line from a sourced string (in-memory, no file). +/// Called by do_cmdline() when it's called from do_source_str(). +/// +/// @return pointer to allocated line, or NULL for end-of-file or +/// some error. +static char *get_str_line(int c, void *cookie, int indent, bool do_concat) +{ + GetStrLineCookie *p = cookie; + if (STRLEN(p->buf) <= p->offset) { + return NULL; + } + const char *line = p->buf + p->offset; + const char *eol = (char *)skip_to_newline((char_u *)line); + garray_T ga; + ga_init(&ga, sizeof(char_u), 400); + ga_concat_len(&ga, line, (size_t)(eol - line)); + if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { + while (eol[0] != NUL) { + line = eol + 1; + const char_u *const next_eol = skip_to_newline((char_u *)line); + if (!concat_continued_line(&ga, 400, (char_u *)line, (size_t)(next_eol - (char_u *)line))) { + break; + } + eol = (char *)next_eol; + } + } + ga_append(&ga, NUL); + p->offset = (size_t)(eol - p->buf) + 1; + return ga.ga_data; +} + +/// Create a new script item and allocate script-local vars. @see new_script_vars +/// +/// @param name File name of the script. NULL for anonymous :source. +/// @param[out] sid_out SID of the new item. +/// +/// @return pointer to the created script item. +scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) +{ + static scid_T last_current_SID = 0; + const scid_T sid = ++last_current_SID; + if (sid_out != NULL) { + *sid_out = sid; + } + ga_grow(&script_items, sid - script_items.ga_len); + while (script_items.ga_len < sid) { + script_items.ga_len++; + SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; + SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false; + } + SCRIPT_ITEM(sid).sn_name = (char_u *)name; + new_script_vars(sid); // Allocate the local script variables to use for this script. + return &SCRIPT_ITEM(sid); +} + +static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name) +{ + char *save_sourcing_name = SOURCING_NAME; + linenr_T save_sourcing_lnum = SOURCING_LNUM; + char sourcing_name_buf[256]; + char *sname; + if (save_sourcing_name == NULL) { + sname = (char *)traceback_name; + } else { + snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf), + "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name, + save_sourcing_lnum); + sname = sourcing_name_buf; + } + estack_push(ETYPE_SCRIPT, sname, 0); + + const sctx_T save_current_sctx = current_sctx; + if (current_sctx.sc_sid != SID_LUA) { + current_sctx.sc_sid = SID_STR; + } + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = save_sourcing_lnum; + funccal_entry_T entry; + save_funccal(&entry); + int retval = do_cmdline(NULL, fgetline, cookie, + DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); + estack_pop(); + current_sctx = save_current_sctx; + restore_funccal(); + return retval; +} + +static void cmd_source_buffer(const exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + if (curbuf == NULL) { + return; + } + garray_T ga; + ga_init(&ga, sizeof(char_u), 400); + const linenr_T final_lnum = eap->line2; + // Copy the contents to be executed. + for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { + // Adjust growsize to current length to speed up concatenating many lines. + if (ga.ga_len > 400) { + ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); + } + ga_concat(&ga, (char *)ml_get(curr_lnum)); + ga_append(&ga, NL); + } + ((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL; + const GetStrLineCookie cookie = { + .buf = ga.ga_data, + .offset = 0, + }; + if (curbuf->b_fname + && path_with_extension((const char *)curbuf->b_fname, "lua")) { + nlua_source_using_linegetter(get_str_line, (void *)&cookie, + ":source (no file)"); + } else { + source_using_linegetter((void *)&cookie, get_str_line, + ":source (no file)"); + } + ga_clear(&ga); +} + +/// Executes lines in `src` as Ex commands. +/// +/// @see do_source() +int do_source_str(const char *cmd, const char *traceback_name) +{ + GetStrLineCookie cookie = { + .buf = (char *)cmd, + .offset = 0, + }; + return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); +} + +/// When fname is a 'lua' file nlua_exec_file() is invoked to source it. +/// Otherwise reads the file `fname` and executes its lines as Ex commands. +/// +/// This function may be called recursively! +/// +/// @see do_source_str +/// +/// @param fname +/// @param check_other check for .vimrc and _vimrc +/// @param is_vimrc DOSO_ value +/// +/// @return FAIL if file could not be opened, OK otherwise +int do_source(char *fname, int check_other, int is_vimrc) +{ + struct source_cookie cookie; + char *p; + char *fname_exp; + uint8_t *firstline = NULL; + int retval = FAIL; + int save_debug_break_level = debug_break_level; + scriptitem_T *si = NULL; + proftime_T wait_start; + bool trigger_source_post = false; + + p = expand_env_save(fname); + if (p == NULL) { + return retval; + } + fname_exp = fix_fname(p); + xfree(p); + if (fname_exp == NULL) { + return retval; + } + if (os_isdir((char_u *)fname_exp)) { + smsg(_("Cannot source a directory: \"%s\""), fname); + goto theend; + } + + // Apply SourceCmd autocommands, they should get the file and source it. + if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) + && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, + false, curbuf)) { + retval = aborting() ? FAIL : OK; + if (retval == OK) { + // Apply SourcePost autocommands. + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } + goto theend; + } + + // Apply SourcePre autocommands, they may get the file. + apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf); + + cookie.fp = fopen_noinh_readbin(fname_exp); + if (cookie.fp == NULL && check_other) { + // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, + // and ".exrc" by "_exrc" or vice versa. + p = path_tail(fname_exp); + if ((*p == '.' || *p == '_') + && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) { + *p = (*p == '_') ? '.' : '_'; + cookie.fp = fopen_noinh_readbin(fname_exp); + } + } + + if (cookie.fp == NULL) { + if (p_verbose > 1) { + verbose_enter(); + if (SOURCING_NAME == NULL) { + smsg(_("could not source \"%s\""), fname); + } else { + smsg(_("line %" PRId64 ": could not source \"%s\""), + (int64_t)SOURCING_LNUM, fname); + } + verbose_leave(); + } + goto theend; + } + + // The file exists. + // - In verbose mode, give a message. + // - For a vimrc file, may want to call vimrc_found(). + if (p_verbose > 1) { + verbose_enter(); + if (SOURCING_NAME == NULL) { + smsg(_("sourcing \"%s\""), fname); + } else { + smsg(_("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname); + } + verbose_leave(); + } + if (is_vimrc == DOSO_VIMRC) { + vimrc_found(fname_exp, "MYVIMRC"); + } + +#ifdef USE_CRNL + // If no automatic file format: Set default to CR-NL. + if (*p_ffs == NUL) { + cookie.fileformat = EOL_DOS; + } else { + cookie.fileformat = EOL_UNKNOWN; + } + cookie.error = false; +#endif + + cookie.nextline = NULL; + cookie.sourcing_lnum = 0; + cookie.finished = false; + + // Check if this script has a breakpoint. + cookie.breakpoint = dbg_find_breakpoint(true, (char_u *)fname_exp, (linenr_T)0); + cookie.fname = fname_exp; + cookie.dbg_tick = debug_tick; + + cookie.level = ex_nesting_level; + + // start measuring script load time if --startuptime was passed and + // time_fd was successfully opened afterwards. + proftime_T rel_time; + proftime_T start_time; + FILE * const l_time_fd = time_fd; + if (l_time_fd != NULL) { + time_push(&rel_time, &start_time); + } + + const int l_do_profiling = do_profiling; + if (l_do_profiling == PROF_YES) { + prof_child_enter(&wait_start); // entering a child now + } + + // Don't use local function variables, if called from a function. + // Also starts profiling timer for nested script. + funccal_entry_T funccalp_entry; + save_funccal(&funccalp_entry); + + const sctx_T save_current_sctx = current_sctx; + si = get_current_script_id(&fname_exp, ¤t_sctx); + + // Keep the sourcing name/lnum, for recursive calls. + estack_push(ETYPE_SCRIPT, (char *)si->sn_name, 0); + + if (l_do_profiling == PROF_YES) { + bool forceit = false; + + // Check if we do profiling for this script. + if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) { + profile_init(si); + si->sn_pr_force = forceit; + } + if (si->sn_prof_on) { + si->sn_pr_count++; + si->sn_pr_start = profile_start(); + si->sn_pr_children = profile_zero(); + } + } + + cookie.conv.vc_type = CONV_NONE; // no conversion + + if (path_with_extension((const char *)fname_exp, "lua")) { + const sctx_T current_sctx_backup = current_sctx; + current_sctx.sc_sid = SID_LUA; + current_sctx.sc_lnum = 0; + // Source the file as lua + nlua_exec_file((const char *)fname_exp); + current_sctx = current_sctx_backup; + } else { + // Read the first line so we can check for a UTF-8 BOM. + firstline = (uint8_t *)getsourceline(0, (void *)&cookie, 0, true); + if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef + && firstline[1] == 0xbb && firstline[2] == 0xbf) { + // Found BOM; setup conversion, skip over BOM and recode the line. + convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc); + p = (char *)string_convert(&cookie.conv, (char_u *)firstline + 3, NULL); + if (p == NULL) { + p = xstrdup((char *)firstline + 3); + } + xfree(firstline); + firstline = (uint8_t *)p; + } + // Call do_cmdline, which will call getsourceline() to get the lines. + do_cmdline((char *)firstline, getsourceline, (void *)&cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + } + retval = OK; + + if (l_do_profiling == PROF_YES) { + // Get "si" again, "script_items" may have been reallocated. + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on) { + si->sn_pr_start = profile_end(si->sn_pr_start); + si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start); + si->sn_pr_total = profile_add(si->sn_pr_total, si->sn_pr_start); + si->sn_pr_self = profile_self(si->sn_pr_self, si->sn_pr_start, + si->sn_pr_children); + } + } + + if (got_int) { + emsg(_(e_interr)); + } + estack_pop(); + if (p_verbose > 1) { + verbose_enter(); + smsg(_("finished sourcing %s"), fname); + if (SOURCING_NAME != NULL) { + smsg(_("continuing in %s"), SOURCING_NAME); + } + verbose_leave(); + } + + if (l_time_fd != NULL) { + vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); + time_msg((char *)IObuff, &start_time); + time_pop(rel_time); + } + + if (!got_int) { + trigger_source_post = true; + } + + // After a "finish" in debug mode, need to break at first command of next + // sourced file. + if (save_debug_break_level > ex_nesting_level + && debug_break_level == ex_nesting_level) { + debug_break_level++; + } + + current_sctx = save_current_sctx; + restore_funccal(); + if (l_do_profiling == PROF_YES) { + prof_child_exit(&wait_start); // leaving a child now + } + fclose(cookie.fp); + xfree(cookie.nextline); + xfree(firstline); + convert_setup(&cookie.conv, NULL, NULL); + + if (trigger_source_post) { + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } + +theend: + xfree(fname_exp); + return retval; +} + +/// Check if fname was sourced before to finds its SID. +/// If it's new, generate a new SID. +/// +/// @param[in,out] fnamep pointer to file path of script +/// @param[out] ret_sctx sctx of this script +scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx) +{ + static int last_current_SID_seq = 0; + + sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, + .sc_lnum = 0, + .sc_sid = 0 }; + scriptitem_T *si = NULL; + + assert(script_items.ga_len >= 0); + for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) { + // We used to check inode here, but that doesn't work: + // - If a script is edited and written, it may get a different + // inode number, even though to the user it is the same script. + // - If a script is deleted and another script is written, with a + // different name, the inode may be re-used. + si = &SCRIPT_ITEM(script_sctx.sc_sid); + if (si->sn_name != NULL && FNAMECMP(si->sn_name, *fnamep) == 0) { + // Found it! + break; + } + } + if (script_sctx.sc_sid == 0) { + si = new_script_item(*fnamep, &script_sctx.sc_sid); + *fnamep = xstrdup((char *)si->sn_name); + } + if (ret_sctx != NULL) { + *ret_sctx = script_sctx; + } + + return si; +} + +/// ":scriptnames" +void ex_scriptnames(exarg_T *eap) +{ + if (eap->addr_count > 0) { + // :script {scriptId}: edit the script + if (eap->line2 < 1 || eap->line2 > script_items.ga_len) { + emsg(_(e_invarg)); + } else { + eap->arg = (char *)SCRIPT_ITEM(eap->line2).sn_name; + do_exedit(eap, NULL); + } + return; + } + + for (int i = 1; i <= script_items.ga_len && !got_int; i++) { + if (SCRIPT_ITEM(i).sn_name != NULL) { + home_replace(NULL, (char *)SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true); + vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); + if (!message_filtered(IObuff)) { + msg_putchar('\n'); + msg_outtrans((char *)IObuff); + line_breakcheck(); + } + } + } +} + +#if defined(BACKSLASH_IN_FILENAME) +/// Fix slashes in the list of script names for 'shellslash'. +void scriptnames_slash_adjust(void) +{ + for (int i = 1; i <= script_items.ga_len; i++) { + if (SCRIPT_ITEM(i).sn_name != NULL) { + slash_adjust(SCRIPT_ITEM(i).sn_name); + } + } +} + +#endif + +/// Get a pointer to a script name. Used for ":verbose set". +/// Message appended to "Last set from " +char_u *get_scriptname(LastSet last_set, bool *should_free) +{ + *should_free = false; + + switch (last_set.script_ctx.sc_sid) { + case SID_MODELINE: + return (char_u *)_("modeline"); + case SID_CMDARG: + return (char_u *)_("--cmd argument"); + case SID_CARG: + return (char_u *)_("-c argument"); + case SID_ENV: + return (char_u *)_("environment variable"); + case SID_ERROR: + return (char_u *)_("error handler"); + case SID_WINLAYOUT: + return (char_u *)_("changed window size"); + case SID_LUA: + return (char_u *)_("Lua"); + case SID_API_CLIENT: + snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); + return IObuff; + case SID_STR: + return (char_u *)_("anonymous :source"); + default: { + char *const sname = (char *)SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name; + if (sname == NULL) { + snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"), + last_set.script_ctx.sc_sid); + return IObuff; + } + + *should_free = true; + return (char_u *)home_replace_save(NULL, sname); + } + } +} + +#if defined(EXITFREE) +void free_scriptnames(void) +{ + profile_reset(); + +# define FREE_SCRIPTNAME(item) xfree((item)->sn_name) + GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME); +} +#endif + +linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie) + FUNC_ATTR_PURE +{ + return fgetline == getsourceline + ? ((struct source_cookie *)cookie)->sourcing_lnum + : SOURCING_LNUM; +} + +/// Get one full line from a sourced file. +/// Called by do_cmdline() when it's called from do_source(). +/// +/// @return pointer to the line in allocated memory, or NULL for end-of-file or +/// some error. +char *getsourceline(int c, void *cookie, int indent, bool do_concat) +{ + struct source_cookie *sp = (struct source_cookie *)cookie; + char *line; + char *p; + + // If breakpoints have been added/deleted need to check for it. + if (sp->dbg_tick < debug_tick) { + sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM); + sp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) { + script_line_end(); + } + // Set the current sourcing line number. + SOURCING_LNUM = sp->sourcing_lnum + 1; + // Get current line. If there is a read-ahead line, use it, otherwise get + // one now. + if (sp->finished) { + line = NULL; + } else if (sp->nextline == NULL) { + line = get_one_sourceline(sp); + } else { + line = sp->nextline; + sp->nextline = NULL; + sp->sourcing_lnum++; + } + if (line != NULL && do_profiling == PROF_YES) { + script_line_start(); + } + + // Only concatenate lines starting with a \ when 'cpoptions' doesn't + // contain the 'C' flag. + if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { + // compensate for the one line read-ahead + sp->sourcing_lnum--; + + // Get the next line and concatenate it when it starts with a + // backslash. We always need to read the next line, keep it in + // sp->nextline. + // Also check for a comment in between continuation lines: "\ . + sp->nextline = get_one_sourceline(sp); + if (sp->nextline != NULL + && (*(p = skipwhite(sp->nextline)) == '\\' + || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) { + garray_T ga; + + ga_init(&ga, (int)sizeof(char_u), 400); + ga_concat(&ga, line); + while (sp->nextline != NULL + && concat_continued_line(&ga, 400, (char_u *)sp->nextline, + STRLEN(sp->nextline))) { + xfree(sp->nextline); + sp->nextline = get_one_sourceline(sp); + } + ga_append(&ga, NUL); + xfree(line); + line = ga.ga_data; + } + } + + if (line != NULL && sp->conv.vc_type != CONV_NONE) { + char *s; + + // Convert the encoding of the script line. + s = (char *)string_convert(&sp->conv, (char_u *)line, NULL); + if (s != NULL) { + xfree(line); + line = s; + } + } + + // Did we encounter a breakpoint? + if (sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) { + dbg_breakpoint((char_u *)sp->fname, SOURCING_LNUM); + // Find next breakpoint. + sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM); + sp->dbg_tick = debug_tick; + } + + return line; +} + +static char *get_one_sourceline(struct source_cookie *sp) +{ + garray_T ga; + int len; + int c; + char *buf; +#ifdef USE_CRNL + int has_cr; // CR-LF found +#endif + bool have_read = false; + + // use a growarray to store the sourced line + ga_init(&ga, 1, 250); + + // Loop until there is a finished line (or end-of-file). + sp->sourcing_lnum++; + for (;;) { + // make room to read at least 120 (more) characters + ga_grow(&ga, 120); + buf = ga.ga_data; + +retry: + errno = 0; + if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, + sp->fp) == NULL) { + if (errno == EINTR) { + goto retry; + } + + break; + } + len = ga.ga_len + (int)STRLEN(buf + ga.ga_len); +#ifdef USE_CRNL + // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the + // CTRL-Z by its own, or after a NL. + if ((len == 1 || (len >= 2 && buf[len - 2] == '\n')) + && sp->fileformat == EOL_DOS + && buf[len - 1] == Ctrl_Z) { + buf[len - 1] = NUL; + break; + } +#endif + + have_read = true; + ga.ga_len = len; + + // If the line was longer than the buffer, read more. + if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') { + continue; + } + + if (len >= 1 && buf[len - 1] == '\n') { // remove trailing NL +#ifdef USE_CRNL + has_cr = (len >= 2 && buf[len - 2] == '\r'); + if (sp->fileformat == EOL_UNKNOWN) { + if (has_cr) { + sp->fileformat = EOL_DOS; + } else { + sp->fileformat = EOL_UNIX; + } + } + + if (sp->fileformat == EOL_DOS) { + if (has_cr) { // replace trailing CR + buf[len - 2] = '\n'; + len--; + ga.ga_len--; + } else { // lines like ":map xx yy^M" will have failed + if (!sp->error) { + msg_source(HL_ATTR(HLF_W)); + emsg(_("W15: Warning: Wrong line separator, ^M may be missing")); + } + sp->error = true; + sp->fileformat = EOL_UNIX; + } + } +#endif + // The '\n' is escaped if there is an odd number of ^V's just + // before it, first set "c" just before the 'V's and then check + // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo + for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {} + if ((len & 1) != (c & 1)) { // escaped NL, read more + sp->sourcing_lnum++; + continue; + } + + buf[len - 1] = NUL; // remove the NL + } + + // Check for ^C here now and then, so recursive :so can be broken. + line_breakcheck(); + break; + } + + if (have_read) { + return ga.ga_data; + } + + xfree(ga.ga_data); + return NULL; +} + +/// ":scriptencoding": Set encoding conversion for a sourced script. +/// Without the multi-byte feature it's simply ignored. +void ex_scriptencoding(exarg_T *eap) +{ + struct source_cookie *sp; + char *name; + + if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { + emsg(_("E167: :scriptencoding used outside of a sourced file")); + return; + } + + if (*eap->arg != NUL) { + name = (char *)enc_canonize((char_u *)eap->arg); + } else { + name = eap->arg; + } + + // Setup for conversion from the specified encoding to 'encoding'. + sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie); + convert_setup(&sp->conv, (char_u *)name, p_enc); + + if (name != eap->arg) { + xfree(name); + } +} + +/// ":finish": Mark a sourced file as finished. +void ex_finish(exarg_T *eap) +{ + if (getline_equal(eap->getline, eap->cookie, getsourceline)) { + do_finish(eap, false); + } else { + emsg(_("E168: :finish used outside of a sourced file")); + } +} + +/// Mark a sourced file as finished. Possibly makes the ":finish" pending. +/// Also called for a pending finish at the ":endtry" or after returning from +/// an extra do_cmdline(). "reanimate" is used in the latter case. +void do_finish(exarg_T *eap, int reanimate) +{ + int idx; + + if (reanimate) { + ((struct source_cookie *)getline_cookie(eap->getline, + eap->cookie))->finished = false; + } + + // Cleanup (and deactivate) conditionals, but stop when a try conditional + // not in its finally clause (which then is to be executed next) is found. + // In this case, make the ":finish" pending for execution at the ":endtry". + // Otherwise, finish normally. + idx = cleanup_conditionals(eap->cstack, 0, true); + if (idx >= 0) { + eap->cstack->cs_pending[idx] = CSTP_FINISH; + report_make_pending(CSTP_FINISH, NULL); + } else { + ((struct source_cookie *)getline_cookie(eap->getline, + eap->cookie))->finished = true; + } +} + +/// @return true when a sourced file had the ":finish" command: Don't give error +/// message for missing ":endif". +/// false when not sourcing a file. +bool source_finished(LineGetter fgetline, void *cookie) +{ + return getline_equal(fgetline, cookie, getsourceline) + && ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished; +} diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h index d83ec00185..a255c6c096 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -3,7 +3,76 @@ #include <stdbool.h> -#include "nvim/ex_docmd.h" +#include "nvim/autocmd.h" +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_eval_defs.h" + +typedef enum { + ETYPE_TOP, ///< toplevel + ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx + ETYPE_UFUNC, ///< user function, use es_info.ufunc + ETYPE_AUCMD, ///< autocomand, use es_info.aucmd + ETYPE_MODELINE, ///< modeline, use es_info.sctx + ETYPE_EXCEPT, ///< exception, use es_info.exception + ETYPE_ARGS, ///< command line argument + ETYPE_ENV, ///< environment variable + ETYPE_INTERNAL, ///< internal operation + ETYPE_SPELL, ///< loading spell file +} etype_T; + +/// Entry in the execution stack "exestack". +typedef struct { + linenr_T es_lnum; ///< replaces "sourcing_lnum" + char *es_name; ///< replaces "sourcing_name" + etype_T es_type; + union { + sctx_T *sctx; ///< script and modeline info + ufunc_T *ufunc; ///< function info + AutoPatCmd *aucmd; ///< autocommand info + except_T *except; ///< exception info + } es_info; +} estack_T; + +/// Stack of execution contexts. Each entry is an estack_T. +/// Current context is at ga_len - 1. +extern garray_T exestack; +/// name of error message source +#define SOURCING_NAME (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_name) +/// line number in the message source or zero +#define SOURCING_LNUM (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_lnum) + +/// Argument for estack_sfile(). +typedef enum { + ESTACK_NONE, + ESTACK_SFILE, + ESTACK_STACK, +} estack_arg_T; + +typedef struct scriptitem_S { + char_u *sn_name; + bool sn_prof_on; ///< true when script is/was profiled + bool sn_pr_force; ///< forceit: profile functions in this script + proftime_T sn_pr_child; ///< time set when going into first child + int sn_pr_nest; ///< nesting for sn_pr_child + // profiling the script as a whole + int sn_pr_count; ///< nr of times sourced + proftime_T sn_pr_total; ///< time spent in script + children + proftime_T sn_pr_self; ///< time spent in script itself + proftime_T sn_pr_start; ///< time at script start + proftime_T sn_pr_children; ///< time in children after script start + // profiling the script per line + garray_T sn_prl_ga; ///< things stored for every line + proftime_T sn_prl_start; ///< start time for current line + proftime_T sn_prl_children; ///< time spent in children for this line + proftime_T sn_prl_wait; ///< wait start time for current line + linenr_T sn_prl_idx; ///< index of line being timed; -1 if none + int sn_prl_execed; ///< line being timed was executed +} scriptitem_T; + +/// Growarray to store info about already sourced scripts. +extern garray_T script_items; +#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) typedef void (*DoInRuntimepathCB)(char *, void *); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 6f4400e531..b343b167f8 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1,629 +1,52 @@ // 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 -// screen.c: code for displaying on the screen -// +// screen.c: Lower level code for displaying on the screen. +// grid.c contains some other lower-level code. + // Output to the screen (console, terminal emulator or GUI window) is minimized // by remembering what is already on the screen, and only updating the parts // that changed. -// -// The grid_*() functions write to the screen and handle updating grid->lines[]. -// -// update_screen() is the function that updates all windows and status lines. -// It is called from the main loop when must_redraw is non-zero. It may be -// called from other places when an immediate screen update is needed. -// -// The part of the buffer that is displayed in a window is set with: -// - w_topline (first buffer line in window) -// - w_topfill (filler lines above the first line) -// - w_leftcol (leftmost window cell in window), -// - w_skipcol (skipped window cells of first line) -// -// Commands that only move the cursor around in a window, do not need to take -// action to update the display. The main loop will check if w_topline is -// valid and update it (scroll the window) when needed. -// -// Commands that scroll a window change w_topline and must call -// check_cursor() to move the cursor into the visible part of the window, and -// call redraw_later(wp, VALID) to have the window displayed by update_screen() -// later. -// -// Commands that change text in the buffer must call changed_bytes() or -// changed_lines() to mark the area that changed and will require updating -// later. The main loop will call update_screen(), which will update each -// window that shows the changed buffer. This assumes text above the change -// can remain displayed as it is. Text after the change may need updating for -// scrolling, folding and syntax highlighting. -// -// Commands that change how a window is displayed (e.g., setting 'list') or -// invalidate the contents of a window in another way (e.g., change fold -// settings), must call redraw_later(wp, NOT_VALID) to have the whole window -// redisplayed by update_screen() later. -// -// Commands that change how a buffer is displayed (e.g., setting 'tabstop') -// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the -// buffer redisplayed by update_screen() later. -// -// Commands that change highlighting and possibly cause a scroll too must call -// redraw_later(wp, SOME_VALID) to update the whole window but still use -// scrolling to avoid redrawing everything. But the length of displayed lines -// must not change, use NOT_VALID then. -// -// Commands that move the window position must call redraw_later(wp, NOT_VALID). -// TODO(neovim): should minimize redrawing by scrolling when possible. -// -// Commands that change everything (e.g., resizing the screen) must call -// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR). -// -// Things that are handled indirectly: -// - When messages scroll the screen up, msg_scrolled will be set and -// update_screen() called to redraw. -/// #include <assert.h> #include <inttypes.h> #include <stdbool.h> #include <string.h> -#include "nvim/api/extmark.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/ui.h" -#include "nvim/api/vim.h" -#include "nvim/arabic.h" -#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" -#include "nvim/cursor_shape.h" -#include "nvim/decoration.h" -#include "nvim/decoration_provider.h" -#include "nvim/diff.h" -#include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/indent.h" -#include "nvim/insexpand.h" -#include "nvim/lib/kvec.h" -#include "nvim/log.h" -#include "nvim/lua/executor.h" -#include "nvim/main.h" -#include "nvim/mark.h" -#include "nvim/match.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" #include "nvim/menu.h" -#include "nvim/message.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/option.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/path.h" -#include "nvim/plines.h" -#include "nvim/popupmnu.h" -#include "nvim/quickfix.h" +#include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/sign.h" -#include "nvim/spell.h" #include "nvim/state.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/terminal.h" -#include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/undo.h" -#include "nvim/version.h" -#include "nvim/vim.h" #include "nvim/window.h" -#define MB_FILLER_CHAR '<' /* character used when a double-width character - * doesn't fit. */ - -static match_T search_hl; // used for 'hlsearch' highlight matching - -// for line_putchar. Contains the state that needs to be remembered from -// putting one character to the next. -typedef struct { - const char *p; - int prev_c; // previous Arabic character - int prev_c1; // first composing char for prev_c -} LineState; -#define LINE_STATE(p) { p, 0, 0 } - -/// Whether to call "ui_call_grid_resize" in win_grid_alloc -static bool send_grid_resize = false; - -static bool conceal_cursor_used = false; - -static bool redraw_popupmenu = false; -static bool msg_grid_invalid = false; - -static bool resizing = false; - -typedef struct { - NS ns_id; - uint64_t mark_id; - int win_row; - int win_col; -} WinExtmark; -static kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif -static char *provider_err = NULL; - -/// Redraw a window later, with update_screen(type). -/// -/// Set must_redraw only if not already set to a higher value. -/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. -void redraw_later(win_T *wp, int type) - FUNC_ATTR_NONNULL_ALL -{ - if (!exiting && wp->w_redr_type < type) { - wp->w_redr_type = type; - if (type >= NOT_VALID) { - wp->w_lines_valid = 0; - } - if (must_redraw < type) { // must_redraw is the maximum of all windows - must_redraw = type; - } - } -} - -/* - * Mark all windows to be redrawn later. - */ -void redraw_all_later(int type) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_later(wp, type); - } - // This may be needed when switching tabs. - if (must_redraw < type) { - must_redraw = type; - } -} - -void screen_invalidate_highlights(void) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_later(wp, NOT_VALID); - wp->w_grid_alloc.valid = false; - } -} - -/* - * Mark all windows that are editing the current buffer to be updated later. - */ -void redraw_curbuf_later(int type) -{ - redraw_buf_later(curbuf, type); -} - -void redraw_buf_later(buf_T *buf, int type) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { - redraw_later(wp, type); - } - } -} - -void redraw_buf_line_later(buf_T *buf, linenr_T line) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf - && line >= wp->w_topline && line < wp->w_botline) { - redrawWinline(wp, line); - } - } -} - -void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf - && lastline >= wp->w_topline && firstline < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { - wp->w_redraw_top = firstline; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { - wp->w_redraw_bot = lastline; - } - redraw_later(wp, VALID); - } - } -} - -/* - * Changed something in the current window, at buffer line "lnum", that - * requires that line and possibly other lines to be redrawn. - * Used when entering/leaving Insert mode with the cursor on a folded line. - * Used to remove the "$" from a change command. - * Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot - * may become invalid and the whole window will have to be redrawn. - */ -void redrawWinline(win_T *wp, linenr_T lnum) - FUNC_ATTR_NONNULL_ALL -{ - if (lnum >= wp->w_topline - && lnum < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { - wp->w_redraw_top = lnum; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { - wp->w_redraw_bot = lnum; - } - redraw_later(wp, VALID); - } -} - -/// called when the status bars for the buffer 'buf' need to be updated -void redraw_buf_status_later(buf_T *buf) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && (wp->w_status_height || (wp == curwin && global_stl_height()) - || wp->w_winbar_height)) { - wp->w_redr_status = true; - if (must_redraw < VALID) { - must_redraw = VALID; - } - } - } -} - -void redraw_win_signcol(win_T *wp) -{ - // If we can compute a change in the automatic sizing of the sign column - // under 'signcolumn=auto:X' and signs currently placed in the buffer, better - // figuring it out here so we can redraw the entire screen for it. - int scwidth = wp->w_scwidth; - wp->w_scwidth = win_signcol_count(wp); - if (wp->w_scwidth != scwidth) { - changed_line_abv_curs_win(wp); - } -} - -/// Update all windows that are editing the current buffer. -void update_curbuf(int type) -{ - redraw_curbuf_later(type); - update_screen(type); -} - -/// Redraw the parts of the screen that is marked for redraw. -/// -/// Most code shouldn't call this directly, rather use redraw_later() and -/// and redraw_all_later() to mark parts of the screen as needing a redraw. -/// -/// @param type set to a NOT_VALID to force redraw of entire screen -int update_screen(int type) -{ - static bool did_intro = false; - bool is_stl_global = global_stl_height() > 0; - - // Don't do anything if the screen structures are (not yet) valid. - // A VimResized autocmd can invoke redrawing in the middle of a resize, - // which would bypass the checks in screen_resize for popupmenu etc. - if (!default_grid.chars || resizing) { - return FAIL; - } - - // May have postponed updating diffs. - if (need_diff_redraw) { - diff_redraw(true); - } - - if (must_redraw) { - if (type < must_redraw) { // use maximal type - type = must_redraw; - } - - // must_redraw is reset here, so that when we run into some weird - // reason to redraw while busy redrawing (e.g., asynchronous - // scrolling), or update_topline() in win_update() will cause a - // scroll, or a decoration provider requires a redraw, the screen - // will be redrawn later or in win_update(). - must_redraw = 0; - } - - // Need to update w_lines[]. - if (curwin->w_lines_valid == 0 && type < NOT_VALID) { - type = NOT_VALID; - } - - /* Postpone the redrawing when it's not needed and when being called - * recursively. */ - if (!redrawing() || updating_screen) { - must_redraw = type; - if (type > INVERTED_ALL) { - curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now - } - return FAIL; - } - updating_screen = 1; - - display_tick++; // let syntax code know we're in a next round of - // display updating - - // Tricky: vim code can reset msg_scrolled behind our back, so need - // separate bookkeeping for now. - if (msg_did_scroll) { - msg_did_scroll = false; - msg_scrolled_at_flush = 0; - } - - if (type >= CLEAR || !default_grid.valid) { - ui_comp_set_screen_valid(false); - } - - // if the screen was scrolled up when displaying a message, scroll it down - if (msg_scrolled || msg_grid_invalid) { - clear_cmdline = true; - int valid = MAX(Rows - msg_scrollsize(), 0); - if (msg_grid.chars) { - // non-displayed part of msg_grid is considered invalid. - for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { - grid_clear_line(&msg_grid, msg_grid.line_offset[i], - msg_grid.cols, false); - } - } - if (msg_use_msgsep()) { - msg_grid.throttled = false; - // CLEAR is already handled - if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { - ui_comp_set_screen_valid(false); - for (int i = valid; i < Rows - p_ch; i++) { - grid_clear_line(&default_grid, default_grid.line_offset[i], - Columns, false); - } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - continue; - } - if (W_ENDROW(wp) > valid) { - wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); - } - if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { - wp->w_redr_status = true; - } - } - if (is_stl_global && Rows - p_ch - 1 > valid) { - curwin->w_redr_status = true; - } - } - msg_grid_set_pos(Rows - (int)p_ch, false); - msg_grid_invalid = false; - } else if (msg_scrolled > Rows - 5) { // clearing is faster - type = CLEAR; - } else if (type != CLEAR) { - check_for_delay(false); - grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - continue; - } - if (wp->w_winrow < msg_scrolled) { - if (W_ENDROW(wp) > msg_scrolled - && wp->w_redr_type < REDRAW_TOP - && wp->w_lines_valid > 0 - && wp->w_topline == wp->w_lines[0].wl_lnum) { - wp->w_upd_rows = msg_scrolled - wp->w_winrow; - wp->w_redr_type = REDRAW_TOP; - } else { - wp->w_redr_type = NOT_VALID; - if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) { - wp->w_redr_status = true; - } - } - } - } - if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { - curwin->w_redr_status = true; - } - redraw_cmdline = true; - redraw_tabline = true; - } - msg_scrolled = 0; - msg_scrolled_at_flush = 0; - need_wait_return = false; - } - - win_ui_flush(); - msg_ext_check_clear(); - - // reset cmdline_row now (may have been changed temporarily) - compute_cmdrow(); - - bool hl_changed = false; - // Check for changed highlighting - if (need_highlight_changed) { - highlight_changed(); - hl_changed = true; - } - - if (type == CLEAR) { // first clear screen - screenclear(); // will reset clear_cmdline - cmdline_screen_cleared(); // clear external cmdline state - type = NOT_VALID; - // must_redraw may be set indirectly, avoid another redraw later - must_redraw = 0; - } else if (!default_grid.valid) { - grid_invalidate(&default_grid); - default_grid.valid = true; - } - - // After disabling msgsep the grid might not have been deallocated yet, - // hence we also need to check msg_grid.chars - if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) { - grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0); - } - - ui_comp_set_screen_valid(true); - - DecorProviders providers; - decor_providers_start(&providers, type, &provider_err); - - // "start" callback could have changed highlights for global elements - if (win_check_ns_hl(NULL)) { - redraw_cmdline = true; - redraw_tabline = true; - } - - if (clear_cmdline) { // going to clear cmdline (done below) - check_for_delay(false); - } - - /* Force redraw when width of 'number' or 'relativenumber' column - * changes. */ - if (curwin->w_redr_type < NOT_VALID - && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) - ? number_width(curwin) : 0)) { - curwin->w_redr_type = NOT_VALID; - } - - /* - * Only start redrawing if there is really something to do. - */ - if (type == INVERTED) { - update_curswant(); - } - if (curwin->w_redr_type < type - && !((type == VALID - && curwin->w_lines[0].wl_valid - && curwin->w_topfill == curwin->w_old_topfill - && curwin->w_botfill == curwin->w_old_botfill - && curwin->w_topline == curwin->w_lines[0].wl_lnum) - || (type == INVERTED - && VIsual_active - && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum - && curwin->w_old_visual_mode == VIsual_mode - && (curwin->w_valid & VALID_VIRTCOL) - && curwin->w_old_curswant == curwin->w_curswant) - )) { - curwin->w_redr_type = type; - } - - // Redraw the tab pages line if needed. - if (redraw_tabline || type >= NOT_VALID) { - update_window_hl(curwin, type >= NOT_VALID); - FOR_ALL_TABS(tp) { - if (tp != curtab) { - update_window_hl(tp->tp_curwin, type >= NOT_VALID); - } - } - draw_tabline(); - } - - /* - * Correct stored syntax highlighting info for changes in each displayed - * buffer. Each buffer must only be done once. - */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - update_window_hl(wp, type >= NOT_VALID || hl_changed); - - buf_T *buf = wp->w_buffer; - if (buf->b_mod_set) { - if (buf->b_mod_tick_syn < display_tick - && syntax_present(wp)) { - syn_stack_apply_changes(buf); - buf->b_mod_tick_syn = display_tick; - } - - if (buf->b_mod_tick_decor < display_tick) { - decor_providers_invoke_buf(buf, &providers, &provider_err); - buf->b_mod_tick_decor = display_tick; - } - } - } - - /* - * Go from top to bottom through the windows, redrawing the ones that need - * it. - */ - bool did_one = false; - search_hl.rm.regprog = NULL; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { - grid_invalidate(&wp->w_grid_alloc); - wp->w_redr_type = NOT_VALID; - } - - // reallocate grid if needed. - win_grid_alloc(wp); - - if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { - win_redr_border(wp); - } +static char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'"); +static char e_conflicts_with_value_of_fillchars[] = N_("E835: Conflicts with value of 'fillchars'"); - if (wp->w_redr_type != 0) { - if (!did_one) { - did_one = true; - start_search_hl(); - } - win_update(wp, &providers); - } - - // redraw status line and window bar after the window to minimize cursor movement - if (wp->w_redr_status) { - win_redr_winbar(wp); - win_redr_status(wp); - } - } - - end_search_hl(); - - // May need to redraw the popup menu. - if (pum_drawn() && must_redraw_pum) { - pum_redraw(); - } - - /* Reset b_mod_set flags. Going through all windows is probably faster - * than going through all buffers (there could be many buffers). */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - wp->w_buffer->b_mod_set = false; - } - - updating_screen = 0; - - /* Clear or redraw the command line. Done last, because scrolling may - * mess up the command line. */ - if (clear_cmdline || redraw_cmdline) { - showmode(); - } - - // May put up an introductory message when not editing a file - if (!did_intro) { - maybe_intro_message(); - } - did_intro = true; - - decor_providers_invoke_end(&providers, &provider_err); - kvi_destroy(providers); - - // either cmdline is cleared, not drawn or mode is last drawn - cmdline_was_last_drawn = false; - return OK; -} - -// Return true if the cursor line in window "wp" may be concealed, according -// to the 'concealcursor' option. +/// Return true if the cursor line in window "wp" may be concealed, according +/// to the 'concealcursor' option. bool conceal_cursor_line(const win_T *wp) FUNC_ATTR_NONNULL_ALL { @@ -646,20 +69,6 @@ bool conceal_cursor_line(const win_T *wp) return vim_strchr((char *)wp->w_p_cocu, c) != NULL; } -// Check if the cursor line needs to be redrawn because of 'concealcursor'. -// -// When cursor is moved at the same time, both lines will be redrawn regardless. -void conceal_check_cursor_line(void) -{ - bool should_conceal = conceal_cursor_line(curwin); - if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) { - redrawWinline(curwin, curwin->w_cursor.lnum); - // Need to recompute cursor column, e.g., when starting Visual mode - // without concealing. - curs_columns(curwin, true); - } -} - /// Whether cursorline is drawn in a special way /// /// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. @@ -669,1081 +78,6 @@ bool win_cursorline_standout(const win_T *wp) return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } -/* - * Update a single window. - * - * This may cause the windows below it also to be redrawn (when clearing the - * screen or scrolling lines). - * - * How the window is redrawn depends on wp->w_redr_type. Each type also - * implies the one below it. - * NOT_VALID redraw the whole window - * SOME_VALID redraw the whole window but do scroll when possible - * REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID - * INVERTED redraw the changed part of the Visual area - * INVERTED_ALL redraw the whole Visual area - * VALID 1. scroll up/down to adjust for a changed w_topline - * 2. update lines at the top when scrolled down - * 3. redraw changed text: - * - if wp->w_buffer->b_mod_set set, update lines between - * b_mod_top and b_mod_bot. - * - if wp->w_redraw_top non-zero, redraw lines between - * wp->w_redraw_top and wp->w_redr_bot. - * - continue redrawing when syntax status is invalid. - * 4. if scrolled up, update lines at the bottom. - * This results in three areas that may need updating: - * top: from first row to top_end (when scrolled down) - * mid: from mid_start to mid_end (update inversion or changed text) - * bot: from bot_start to last row (when scrolled up) - */ -static void win_update(win_T *wp, DecorProviders *providers) -{ - bool called_decor_providers = false; -win_update_start: - ; - buf_T *buf = wp->w_buffer; - int type; - int top_end = 0; /* Below last row of the top area that needs - updating. 0 when no top area updating. */ - int mid_start = 999; /* first row of the mid area that needs - updating. 999 when no mid area updating. */ - int mid_end = 0; /* Below last row of the mid area that needs - updating. 0 when no mid area updating. */ - int bot_start = 999; /* first row of the bot area that needs - updating. 999 when no bot area updating */ - bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit - bool top_to_mod = false; // redraw above mod_top - - int row; // current window row to display - linenr_T lnum; // current buffer lnum to display - int idx; // current index in w_lines[] - int srow; // starting row of the current line - - bool eof = false; // if true, we hit the end of the file - bool didline = false; // if true, we finished the last line - int i; - long j; - static bool recursive = false; // being called recursively - const linenr_T old_botline = wp->w_botline; - // Remember what happened to the previous line. -#define DID_NONE 1 // didn't update a line -#define DID_LINE 2 // updated a normal line -#define DID_FOLD 3 // updated a folded line - int did_update = DID_NONE; - linenr_T syntax_last_parsed = 0; // last parsed text line - linenr_T mod_top = 0; - linenr_T mod_bot = 0; - int save_got_int; - - type = wp->w_redr_type; - - if (type >= NOT_VALID) { - wp->w_redr_status = true; - wp->w_lines_valid = 0; - } - - // Window is zero-height: Only need to draw the separator - if (wp->w_grid.rows == 0) { - // draw the horizontal separator below this window - draw_hsep_win(wp); - draw_sep_connectors_win(wp); - wp->w_redr_type = 0; - return; - } - - // Window is zero-width: Only need to draw the separator. - if (wp->w_grid.cols == 0) { - // draw the vertical separator right of this window - draw_vsep_win(wp); - draw_sep_connectors_win(wp); - wp->w_redr_type = 0; - return; - } - - redraw_win_signcol(wp); - - init_search_hl(wp, &search_hl); - - /* Force redraw when width of 'number' or 'relativenumber' column - * changes. */ - i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; - if (wp->w_nrwidth != i) { - type = NOT_VALID; - wp->w_nrwidth = i; - - if (buf->terminal) { - terminal_check_size(buf->terminal); - } - } else if (buf->b_mod_set - && buf->b_mod_xlines != 0 - && wp->w_redraw_top != 0) { - // When there are both inserted/deleted lines and specific lines to be - // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw - // everything (only happens when redrawing is off for while). - type = NOT_VALID; - } else { - /* - * Set mod_top to the first line that needs displaying because of - * changes. Set mod_bot to the first line after the changes. - */ - mod_top = wp->w_redraw_top; - if (wp->w_redraw_bot != 0) { - mod_bot = wp->w_redraw_bot + 1; - } else { - mod_bot = 0; - } - if (buf->b_mod_set) { - if (mod_top == 0 || mod_top > buf->b_mod_top) { - mod_top = buf->b_mod_top; - /* Need to redraw lines above the change that may be included - * in a pattern match. */ - if (syntax_present(wp)) { - mod_top -= buf->b_s.b_syn_sync_linebreaks; - if (mod_top < 1) { - mod_top = 1; - } - } - } - if (mod_bot == 0 || mod_bot < buf->b_mod_bot) { - mod_bot = buf->b_mod_bot; - } - - // When 'hlsearch' is on and using a multi-line search pattern, a - // change in one line may make the Search highlighting in a - // previous line invalid. Simple solution: redraw all visible - // lines above the change. - // Same for a match pattern. - if (search_hl.rm.regprog != NULL - && re_multiline(search_hl.rm.regprog)) { - top_to_mod = true; - } else { - const matchitem_T *cur = wp->w_match_head; - while (cur != NULL) { - if (cur->match.regprog != NULL - && re_multiline(cur->match.regprog)) { - top_to_mod = true; - break; - } - cur = cur->next; - } - } - } - if (mod_top != 0 && hasAnyFolding(wp)) { - linenr_T lnumt, lnumb; - - /* - * A change in a line can cause lines above it to become folded or - * unfolded. Find the top most buffer line that may be affected. - * If the line was previously folded and displayed, get the first - * line of that fold. If the line is folded now, get the first - * folded line. Use the minimum of these two. - */ - - /* Find last valid w_lines[] entry above mod_top. Set lnumt to - * the line below it. If there is no valid entry, use w_topline. - * Find the first valid w_lines[] entry below mod_bot. Set lnumb - * to this line. If there is no valid entry, use MAXLNUM. */ - lnumt = wp->w_topline; - lnumb = MAXLNUM; - for (i = 0; i < wp->w_lines_valid; ++i) { - if (wp->w_lines[i].wl_valid) { - if (wp->w_lines[i].wl_lastlnum < mod_top) { - lnumt = wp->w_lines[i].wl_lastlnum + 1; - } - if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) { - lnumb = wp->w_lines[i].wl_lnum; - // When there is a fold column it might need updating - // in the next line ("J" just above an open fold). - if (compute_foldcolumn(wp, 0) > 0) { - lnumb++; - } - } - } - } - - (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); - if (mod_top > lnumt) { - mod_top = lnumt; - } - - // Now do the same for the bottom line (one above mod_bot). - mod_bot--; - (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); - mod_bot++; - if (mod_bot < lnumb) { - mod_bot = lnumb; - } - } - - /* When a change starts above w_topline and the end is below - * w_topline, start redrawing at w_topline. - * If the end of the change is above w_topline: do like no change was - * made, but redraw the first line to find changes in syntax. */ - if (mod_top != 0 && mod_top < wp->w_topline) { - if (mod_bot > wp->w_topline) { - mod_top = wp->w_topline; - } else if (syntax_present(wp)) { - top_end = 1; - } - } - - /* When line numbers are displayed need to redraw all lines below - * inserted/deleted lines. */ - if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) { - mod_bot = MAXLNUM; - } - } - wp->w_redraw_top = 0; // reset for next time - wp->w_redraw_bot = 0; - - /* - * When only displaying the lines at the top, set top_end. Used when - * window has scrolled down for msg_scrolled. - */ - if (type == REDRAW_TOP) { - j = 0; - for (i = 0; i < wp->w_lines_valid; ++i) { - j += wp->w_lines[i].wl_size; - if (j >= wp->w_upd_rows) { - top_end = (int)j; - break; - } - } - if (top_end == 0) { - // not found (cannot happen?): redraw everything - type = NOT_VALID; - } else { - // top area defined, the rest is VALID - type = VALID; - } - } - - /* - * If there are no changes on the screen that require a complete redraw, - * handle three cases: - * 1: we are off the top of the screen by a few lines: scroll down - * 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up - * 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in - * w_lines[] that needs updating. - */ - if ((type == VALID || type == SOME_VALID - || type == INVERTED || type == INVERTED_ALL) - && !wp->w_botfill && !wp->w_old_botfill) { - if (mod_top != 0 - && wp->w_topline == mod_top - && (!wp->w_lines[0].wl_valid - || wp->w_topline == wp->w_lines[0].wl_lnum)) { - // w_topline is the first changed line and window is not scrolled, - // the scrolling from changed lines will be done further down. - } else if (wp->w_lines[0].wl_valid - && (wp->w_topline < wp->w_lines[0].wl_lnum - || (wp->w_topline == wp->w_lines[0].wl_lnum - && wp->w_topfill > wp->w_old_topfill) - )) { - /* - * New topline is above old topline: May scroll down. - */ - if (hasAnyFolding(wp)) { - linenr_T ln; - - /* count the number of lines we are off, counting a sequence - * of folded lines as one */ - j = 0; - for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { - j++; - if (j >= wp->w_grid.rows - 2) { - break; - } - (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL); - } - } else { - j = wp->w_lines[0].wl_lnum - wp->w_topline; - } - if (j < wp->w_grid.rows - 2) { // not too far off - i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); - // insert extra lines for previously invisible filler lines - if (wp->w_lines[0].wl_lnum != wp->w_topline) { - i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; - } - if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off - // Try to insert the correct number of lines. - // If not the last window, delete the lines at the bottom. - // win_ins_lines may fail when the terminal can't do it. - win_scroll_lines(wp, 0, i); - if (wp->w_lines_valid != 0) { - // Need to update rows that are new, stop at the - // first one that scrolled down. - top_end = i; - scrolled_down = true; - - // Move the entries that were scrolled, disable - // the entries for the lines to be redrawn. - if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { - wp->w_lines_valid = wp->w_grid.rows; - } - for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { - wp->w_lines[idx] = wp->w_lines[idx - j]; - } - while (idx >= 0) { - wp->w_lines[idx--].wl_valid = false; - } - } - } else { - mid_start = 0; // redraw all lines - } - } else { - mid_start = 0; // redraw all lines - } - } else { - /* - * New topline is at or below old topline: May scroll up. - * When topline didn't change, find first entry in w_lines[] that - * needs updating. - */ - - // try to find wp->w_topline in wp->w_lines[].wl_lnum - j = -1; - row = 0; - for (i = 0; i < wp->w_lines_valid; i++) { - if (wp->w_lines[i].wl_valid - && wp->w_lines[i].wl_lnum == wp->w_topline) { - j = i; - break; - } - row += wp->w_lines[i].wl_size; - } - if (j == -1) { - /* if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all - * lines */ - mid_start = 0; - } else { - /* - * Try to delete the correct number of lines. - * wp->w_topline is at wp->w_lines[i].wl_lnum. - */ - /* If the topline didn't change, delete old filler lines, - * otherwise delete filler lines of the new topline... */ - if (wp->w_lines[0].wl_lnum == wp->w_topline) { - row += wp->w_old_topfill; - } else { - row += win_get_fill(wp, wp->w_topline); - } - // ... but don't delete new filler lines. - row -= wp->w_topfill; - if (row > 0) { - win_scroll_lines(wp, 0, -row); - bot_start = wp->w_grid.rows - row; - } - if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { - /* - * Skip the lines (below the deleted lines) that are still - * valid and don't need redrawing. Copy their info - * upwards, to compensate for the deleted lines. Set - * bot_start to the first row that needs redrawing. - */ - bot_start = 0; - idx = 0; - for (;;) { - wp->w_lines[idx] = wp->w_lines[j]; - /* stop at line that didn't fit, unless it is still - * valid (no lines deleted) */ - if (row > 0 && bot_start + row - + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) { - wp->w_lines_valid = idx + 1; - break; - } - bot_start += wp->w_lines[idx++].wl_size; - - // stop at the last valid entry in w_lines[].wl_size - if (++j >= wp->w_lines_valid) { - wp->w_lines_valid = idx; - break; - } - } - - // Correct the first entry for filler lines at the top - // when it won't get updated below. - if (win_may_fill(wp) && bot_start > 0) { - wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true) - + wp->w_topfill); - } - } - } - } - - // When starting redraw in the first line, redraw all lines. - if (mid_start == 0) { - mid_end = wp->w_grid.rows; - } - } else { - // Not VALID or INVERTED: redraw all lines. - mid_start = 0; - mid_end = wp->w_grid.rows; - } - - if (type == SOME_VALID) { - // SOME_VALID: redraw all lines. - mid_start = 0; - mid_end = wp->w_grid.rows; - type = NOT_VALID; - } - - // check if we are updating or removing the inverted part - if ((VIsual_active && buf == curwin->w_buffer) - || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) { - linenr_T from, to; - - if (VIsual_active) { - if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) { - // If the type of Visual selection changed, redraw the whole - // selection. Also when the ownership of the X selection is - // gained or lost. - if (curwin->w_cursor.lnum < VIsual.lnum) { - from = curwin->w_cursor.lnum; - to = VIsual.lnum; - } else { - from = VIsual.lnum; - to = curwin->w_cursor.lnum; - } - // redraw more when the cursor moved as well - if (wp->w_old_cursor_lnum < from) { - from = wp->w_old_cursor_lnum; - } - if (wp->w_old_cursor_lnum > to) { - to = wp->w_old_cursor_lnum; - } - if (wp->w_old_visual_lnum < from) { - from = wp->w_old_visual_lnum; - } - if (wp->w_old_visual_lnum > to) { - to = wp->w_old_visual_lnum; - } - } else { - /* - * Find the line numbers that need to be updated: The lines - * between the old cursor position and the current cursor - * position. Also check if the Visual position changed. - */ - if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) { - from = curwin->w_cursor.lnum; - to = wp->w_old_cursor_lnum; - } else { - from = wp->w_old_cursor_lnum; - to = curwin->w_cursor.lnum; - if (from == 0) { // Visual mode just started - from = to; - } - } - - if (VIsual.lnum != wp->w_old_visual_lnum - || VIsual.col != wp->w_old_visual_col) { - if (wp->w_old_visual_lnum < from - && wp->w_old_visual_lnum != 0) { - from = wp->w_old_visual_lnum; - } - if (wp->w_old_visual_lnum > to) { - to = wp->w_old_visual_lnum; - } - if (VIsual.lnum < from) { - from = VIsual.lnum; - } - if (VIsual.lnum > to) { - to = VIsual.lnum; - } - } - } - - /* - * If in block mode and changed column or curwin->w_curswant: - * update all lines. - * First compute the actual start and end column. - */ - if (VIsual_mode == Ctrl_V) { - colnr_T fromc, toc; - unsigned int save_ve_flags = curwin->w_ve_flags; - - if (curwin->w_p_lbr) { - curwin->w_ve_flags = VE_ALL; - } - - getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); - toc++; - curwin->w_ve_flags = save_ve_flags; - // Highlight to the end of the line, unless 'virtualedit' has - // "block". - if (curwin->w_curswant == MAXCOL) { - if (get_ve_flags() & VE_BLOCK) { - pos_T pos; - int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; - - // Need to find the longest line. - toc = 0; - pos.coladd = 0; - for (pos.lnum = curwin->w_cursor.lnum; - cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; - pos.lnum += cursor_above ? 1 : -1) { - colnr_T t; - - pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); - getvvcol(wp, &pos, NULL, NULL, &t); - if (toc < t) { - toc = t; - } - } - toc++; - } else { - toc = MAXCOL; - } - } - - if (fromc != wp->w_old_cursor_fcol - || toc != wp->w_old_cursor_lcol) { - if (from > VIsual.lnum) { - from = VIsual.lnum; - } - if (to < VIsual.lnum) { - to = VIsual.lnum; - } - } - wp->w_old_cursor_fcol = fromc; - wp->w_old_cursor_lcol = toc; - } - } else { - // Use the line numbers of the old Visual area. - if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) { - from = wp->w_old_cursor_lnum; - to = wp->w_old_visual_lnum; - } else { - from = wp->w_old_visual_lnum; - to = wp->w_old_cursor_lnum; - } - } - - /* - * There is no need to update lines above the top of the window. - */ - if (from < wp->w_topline) { - from = wp->w_topline; - } - - /* - * If we know the value of w_botline, use it to restrict the update to - * the lines that are visible in the window. - */ - if (wp->w_valid & VALID_BOTLINE) { - if (from >= wp->w_botline) { - from = wp->w_botline - 1; - } - if (to >= wp->w_botline) { - to = wp->w_botline - 1; - } - } - - /* - * Find the minimal part to be updated. - * Watch out for scrolling that made entries in w_lines[] invalid. - * E.g., CTRL-U makes the first half of w_lines[] invalid and sets - * top_end; need to redraw from top_end to the "to" line. - * A middle mouse click with a Visual selection may change the text - * above the Visual area and reset wl_valid, do count these for - * mid_end (in srow). - */ - if (mid_start > 0) { - lnum = wp->w_topline; - idx = 0; - srow = 0; - if (scrolled_down) { - mid_start = top_end; - } else { - mid_start = 0; - } - while (lnum < from && idx < wp->w_lines_valid) { // find start - if (wp->w_lines[idx].wl_valid) { - mid_start += wp->w_lines[idx].wl_size; - } else if (!scrolled_down) { - srow += wp->w_lines[idx].wl_size; - } - ++idx; - if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) { - lnum = wp->w_lines[idx].wl_lnum; - } else { - ++lnum; - } - } - srow += mid_start; - mid_end = wp->w_grid.rows; - for (; idx < wp->w_lines_valid; idx++) { // find end - if (wp->w_lines[idx].wl_valid - && wp->w_lines[idx].wl_lnum >= to + 1) { - // Only update until first row of this line - mid_end = srow; - break; - } - srow += wp->w_lines[idx].wl_size; - } - } - } - - if (VIsual_active && buf == curwin->w_buffer) { - wp->w_old_visual_mode = (char)VIsual_mode; - wp->w_old_cursor_lnum = curwin->w_cursor.lnum; - wp->w_old_visual_lnum = VIsual.lnum; - wp->w_old_visual_col = VIsual.col; - wp->w_old_curswant = curwin->w_curswant; - } else { - wp->w_old_visual_mode = 0; - wp->w_old_cursor_lnum = 0; - wp->w_old_visual_lnum = 0; - wp->w_old_visual_col = 0; - } - - // reset got_int, otherwise regexp won't work - save_got_int = got_int; - got_int = 0; - // Set the time limit to 'redrawtime'. - proftime_T syntax_tm = profile_setlimit(p_rdt); - syn_set_timeout(&syntax_tm); - - /* - * Update all the window rows. - */ - idx = 0; // first entry in w_lines[].wl_size - row = 0; - srow = 0; - lnum = wp->w_topline; // first line shown in window - - win_extmark_arr.size = 0; - - decor_redraw_reset(buf, &decor_state); - - DecorProviders line_providers; - decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); - (void)win_signcol_count(wp); // check if provider changed signcol width - if (must_redraw != 0) { - must_redraw = 0; - if (!called_decor_providers) { - called_decor_providers = true; - goto win_update_start; - } - } - - bool cursorline_standout = win_cursorline_standout(wp); - - for (;;) { - /* stop updating when reached the end of the window (check for _past_ - * the end of the window is at the end of the loop) */ - if (row == wp->w_grid.rows) { - didline = true; - break; - } - - // stop updating when hit the end of the file - if (lnum > buf->b_ml.ml_line_count) { - eof = true; - break; - } - - /* Remember the starting row of the line that is going to be dealt - * with. It is used further down when the line doesn't fit. */ - srow = row; - - // Update a line when it is in an area that needs updating, when it - // has changes or w_lines[idx] is invalid. - // "bot_start" may be halfway a wrapped line after using - // win_scroll_lines(), check if the current line includes it. - // When syntax folding is being used, the saved syntax states will - // already have been updated, we can't see where the syntax state is - // the same again, just update until the end of the window. - if (row < top_end - || (row >= mid_start && row < mid_end) - || top_to_mod - || idx >= wp->w_lines_valid - || (row + wp->w_lines[idx].wl_size > bot_start) - || (mod_top != 0 - && (lnum == mod_top - || (lnum >= mod_top - && (lnum < mod_bot - || did_update == DID_FOLD - || (did_update == DID_LINE - && syntax_present(wp) - && ((foldmethodIsSyntax(wp) - && hasAnyFolding(wp)) - || syntax_check_changed(lnum))) - // match in fixed position might need redraw - // if lines were inserted or deleted - || (wp->w_match_head != NULL - && buf->b_mod_xlines != 0))))) - || (cursorline_standout && lnum == wp->w_cursor.lnum) - || lnum == wp->w_last_cursorline) { - if (lnum == mod_top) { - top_to_mod = false; - } - - /* - * When at start of changed lines: May scroll following lines - * up or down to minimize redrawing. - * Don't do this when the change continues until the end. - * Don't scroll when dollar_vcol >= 0, keep the "$". - * Don't scroll when redrawing the top, scrolled already above. - */ - if (lnum == mod_top - && mod_bot != MAXLNUM - && !(dollar_vcol >= 0 && mod_bot == mod_top + 1) - && row >= top_end) { - int old_rows = 0; - int new_rows = 0; - int xtra_rows; - linenr_T l; - - /* Count the old number of window rows, using w_lines[], which - * should still contain the sizes for the lines as they are - * currently displayed. */ - for (i = idx; i < wp->w_lines_valid; ++i) { - /* Only valid lines have a meaningful wl_lnum. Invalid - * lines are part of the changed area. */ - if (wp->w_lines[i].wl_valid - && wp->w_lines[i].wl_lnum == mod_bot) { - break; - } - old_rows += wp->w_lines[i].wl_size; - if (wp->w_lines[i].wl_valid - && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) { - /* Must have found the last valid entry above mod_bot. - * Add following invalid entries. */ - ++i; - while (i < wp->w_lines_valid - && !wp->w_lines[i].wl_valid) { - old_rows += wp->w_lines[i++].wl_size; - } - break; - } - } - - if (i >= wp->w_lines_valid) { - /* We can't find a valid line below the changed lines, - * need to redraw until the end of the window. - * Inserting/deleting lines has no use. */ - bot_start = 0; - } else { - /* Able to count old number of rows: Count new window - * rows, and may insert/delete lines */ - j = idx; - for (l = lnum; l < mod_bot; l++) { - if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { - new_rows++; - } else if (l == wp->w_topline) { - new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill; - } else { - new_rows += plines_win(wp, l, true); - } - j++; - if (new_rows > wp->w_grid.rows - row - 2) { - // it's getting too much, must redraw the rest - new_rows = 9999; - break; - } - } - xtra_rows = new_rows - old_rows; - if (xtra_rows < 0) { - /* May scroll text up. If there is not enough - * remaining text or scrolling fails, must redraw the - * rest. If scrolling works, must redraw the text - * below the scrolled text. */ - if (row - xtra_rows >= wp->w_grid.rows - 2) { - mod_bot = MAXLNUM; - } else { - win_scroll_lines(wp, row, xtra_rows); - bot_start = wp->w_grid.rows + xtra_rows; - } - } else if (xtra_rows > 0) { - /* May scroll text down. If there is not enough - * remaining text of scrolling fails, must redraw the - * rest. */ - if (row + xtra_rows >= wp->w_grid.rows - 2) { - mod_bot = MAXLNUM; - } else { - win_scroll_lines(wp, row + old_rows, xtra_rows); - if (top_end > row + old_rows) { - // Scrolled the part at the top that requires - // updating down. - top_end += xtra_rows; - } - } - } - - /* When not updating the rest, may need to move w_lines[] - * entries. */ - if (mod_bot != MAXLNUM && i != j) { - if (j < i) { - int x = row + new_rows; - - // move entries in w_lines[] upwards - for (;;) { - // stop at last valid entry in w_lines[] - if (i >= wp->w_lines_valid) { - wp->w_lines_valid = (int)j; - break; - } - wp->w_lines[j] = wp->w_lines[i]; - // stop at a line that won't fit - if (x + (int)wp->w_lines[j].wl_size - > wp->w_grid.rows) { - wp->w_lines_valid = (int)j + 1; - break; - } - x += wp->w_lines[j++].wl_size; - ++i; - } - if (bot_start > x) { - bot_start = x; - } - } else { // j > i - // move entries in w_lines[] downwards - j -= i; - wp->w_lines_valid += (linenr_T)j; - if (wp->w_lines_valid > wp->w_grid.rows) { - wp->w_lines_valid = wp->w_grid.rows; - } - for (i = wp->w_lines_valid; i - j >= idx; i--) { - wp->w_lines[i] = wp->w_lines[i - j]; - } - - /* The w_lines[] entries for inserted lines are - * now invalid, but wl_size may be used above. - * Reset to zero. */ - while (i >= idx) { - wp->w_lines[i].wl_size = 0; - wp->w_lines[i--].wl_valid = FALSE; - } - } - } - } - } - - /* - * When lines are folded, display one line for all of them. - * Otherwise, display normally (can be several display lines when - * 'wrap' is on). - */ - foldinfo_T foldinfo = fold_info(wp, lnum); - - if (foldinfo.fi_lines == 0 - && idx < wp->w_lines_valid - && wp->w_lines[idx].wl_valid - && wp->w_lines[idx].wl_lnum == lnum - && lnum > wp->w_topline - && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) - && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows - && win_get_fill(wp, lnum) == 0) { - // This line is not going to fit. Don't draw anything here, - // will draw "@ " lines below. - row = wp->w_grid.rows + 1; - } else { - prepare_search_hl(wp, &search_hl, lnum); - // Let the syntax stuff know we skipped a few lines. - if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum - && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); - } - - // Display one line - row = win_line(wp, lnum, srow, - foldinfo.fi_lines ? srow : wp->w_grid.rows, - mod_top == 0, false, foldinfo, &line_providers); - - if (foldinfo.fi_lines == 0) { - wp->w_lines[idx].wl_folded = false; - wp->w_lines[idx].wl_lastlnum = lnum; - did_update = DID_LINE; - syntax_last_parsed = lnum; - } else { - foldinfo.fi_lines--; - wp->w_lines[idx].wl_folded = true; - wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; - did_update = DID_FOLD; - } - } - - wp->w_lines[idx].wl_lnum = lnum; - wp->w_lines[idx].wl_valid = true; - - if (row > wp->w_grid.rows) { // past end of grid - // we may need the size of that too long line later on - if (dollar_vcol == -1) { - wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true); - } - idx++; - break; - } - if (dollar_vcol == -1) { - wp->w_lines[idx].wl_size = (uint16_t)(row - srow); - } - idx++; - lnum += foldinfo.fi_lines + 1; - } else { - if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { - // 'relativenumber' set and cursor moved vertically: The - // text doesn't need to be drawn, but the number column does. - foldinfo_T info = fold_info(wp, lnum); - (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true, - info, &line_providers); - } - - // This line does not need to be drawn, advance to the next one. - row += wp->w_lines[idx++].wl_size; - if (row > wp->w_grid.rows) { // past end of screen - break; - } - lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; - did_update = DID_NONE; - } - - if (lnum > buf->b_ml.ml_line_count) { - eof = true; - break; - } - } - /* - * End of loop over all window lines. - */ - - // Now that the window has been redrawn with the old and new cursor line, - // update w_last_cursorline. - wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; - - wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; - - if (idx > wp->w_lines_valid) { - wp->w_lines_valid = idx; - } - - /* - * Let the syntax stuff know we stop parsing here. - */ - if (syntax_last_parsed != 0 && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); - } - - /* - * If we didn't hit the end of the file, and we didn't finish the last - * line we were working on, then the line didn't fit. - */ - wp->w_empty_rows = 0; - wp->w_filler_rows = 0; - if (!eof && !didline) { - int at_attr = hl_combine_attr(wp->w_hl_attr_normal, - win_hl_attr(wp, HLF_AT)); - if (lnum == wp->w_topline) { - /* - * Single line that does not fit! - * Don't overwrite it, it can be edited. - */ - wp->w_botline = lnum + 1; - } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) { - // Window ends in filler lines. - wp->w_botline = lnum; - wp->w_filler_rows = wp->w_grid.rows - srow; - } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" - int scr_row = wp->w_grid.rows - 1; - - // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr); - - grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, - '@', ' ', at_attr); - set_empty_rows(wp, srow); - wp->w_botline = lnum; - } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" - int start_col = wp->w_grid.cols - 3; - - // Last line isn't finished: Display "@@@" at the end. - grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, - MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr); - set_empty_rows(wp, srow); - wp->w_botline = lnum; - } else { - win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT); - wp->w_botline = lnum; - } - } else { - if (eof) { // we hit the end of the file - wp->w_botline = buf->b_ml.ml_line_count + 1; - j = win_get_fill(wp, wp->w_botline); - if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) { - // Display filler text below last line. win_line() will check - // for ml_line_count+1 and only draw filler lines - foldinfo_T info = FOLDINFO_INIT; - row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, - false, false, info, &line_providers); - } - } else if (dollar_vcol == -1) { - wp->w_botline = lnum; - } - - // make sure the rest of the screen is blank - // write the 'eob' character to rows that aren't part of the file. - win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows, - HLF_EOB); - } - - kvi_destroy(line_providers); - - if (wp->w_redr_type >= REDRAW_TOP) { - draw_vsep_win(wp); - draw_hsep_win(wp); - draw_sep_connectors_win(wp); - } - syn_set_timeout(NULL); - - // Reset the type of redrawing required, the window has been updated. - wp->w_redr_type = 0; - wp->w_old_topfill = wp->w_topfill; - wp->w_old_botfill = wp->w_botfill; - - // Send win_extmarks if needed - for (size_t n = 0; n < kv_size(win_extmark_arr); n++) { - ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle, - kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id, - kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col); - } - - if (dollar_vcol == -1) { - /* - * There is a trick with w_botline. If we invalidate it on each - * change that might modify it, this will cause a lot of expensive - * calls to plines_win() in update_topline() each time. Therefore the - * value of w_botline is often approximated, and this value is used to - * compute the value of w_topline. If the value of w_botline was - * wrong, check that the value of w_topline is correct (cursor is on - * the visible part of the text). If it's not, we need to redraw - * again. Mostly this just means scrolling up a few lines, so it - * doesn't look too bad. Only do this for the current window (where - * changes are relevant). - */ - wp->w_valid |= VALID_BOTLINE; - wp->w_viewport_invalid = true; - if (wp == curwin && wp->w_botline != old_botline && !recursive) { - recursive = true; - curwin->w_valid &= ~VALID_TOPLINE; - update_topline(curwin); // may invalidate w_botline again - if (must_redraw != 0) { - // Don't update for changes in buffer again. - i = curbuf->b_mod_set; - curbuf->b_mod_set = false; - win_update(curwin, providers); - must_redraw = 0; - curbuf->b_mod_set = i; - } - recursive = false; - } - } - - // restore got_int, unless CTRL-C was hit while redrawing - if (!got_int) { - got_int = save_got_int; - } -} - /// Returns width of the signcolumn that should be used for the whole window /// /// @param wp window we want signcolumn width from @@ -1783,7 +117,7 @@ static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, /// Clear lines near the end of the window and mark the unused lines with "c1". /// Use "c2" as filler character. /// When "draw_margin" is true, then draw the sign/fold/number columns. -static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl) +void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl) { assert(hl >= 0 && hl < HLF_COUNT); int n = 0; @@ -1808,7 +142,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i } } - int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, (int)hl)); + int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl)); if (wp->w_p_rl) { grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n, @@ -1822,20 +156,9 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i set_empty_rows(wp, row); } -/// Advance **color_cols -/// -/// @return true when there are columns to draw. -static bool advance_color_col(int vcol, int **color_cols) -{ - while (**color_cols >= 0 && vcol > **color_cols) { - ++*color_cols; - } - return **color_cols >= 0; -} - -// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much -// space is available for window "wp", minus "col". -static int compute_foldcolumn(win_T *wp, int col) +/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much +/// space is available for window "wp", minus "col". +int compute_foldcolumn(win_T *wp, int col) { int fdc = win_fdccol_count(wp); int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw; @@ -1847,63 +170,6 @@ static int compute_foldcolumn(win_T *wp, int col) return fdc; } -/// Put a single char from an UTF-8 buffer into a line buffer. -/// -/// Handles composing chars and arabic shaping state. -static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol) -{ - const char_u *p = (char_u *)s->p; - int cells = utf_ptr2cells((char *)p); - int c_len = utfc_ptr2len((char *)p); - int u8c, u8cc[MAX_MCO]; - if (cells > maxcells) { - return -1; - } - u8c = utfc_ptr2char(p, u8cc); - 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], ' '); - } - goto done; - } else if (*p < 0x80 && u8cc[0] == 0) { - schar_from_ascii(dest[0], (char)(*p)); - s->prev_c = u8c; - } else { - if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { - // Do Arabic shaping. - int pc, pc1, nc; - int pcc[MAX_MCO]; - int firstbyte = *p; - - // The idea of what is the previous and next - // character depends on 'rightleft'. - if (rl) { - pc = s->prev_c; - pc1 = s->prev_c1; - nc = utf_ptr2char((char *)p + c_len); - s->prev_c1 = u8cc[0]; - } else { - pc = utfc_ptr2char(p + c_len, pcc); - nc = s->prev_c; - pc1 = pcc[0]; - } - s->prev_c = u8c; - - u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc); - } else { - s->prev_c = u8c; - } - schar_from_cc(dest[0], u8c, u8cc); - } - if (cells > 1) { - dest[1][0] = 0; - } -done: - s->p += c_len; - return cells; -} - /// Fills the foldcolumn at "p" for window "wp". /// Only to be called when 'foldcolumn' > 0. /// @@ -1913,7 +179,7 @@ done: /// /// Assume monocell characters /// @return number of chars added to \param p -static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) +size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) { int i = 0; int level; @@ -1968,2634 +234,43 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc); } -static inline void provider_err_virt_text(linenr_T lnum, char *err) -{ - Decoration err_decor = DECORATION_INIT; - int hl_err = syn_check_group(S_LEN("ErrorMsg")); - kv_push(err_decor.virt_text, - ((VirtTextChunk){ .text = provider_err, - .hl_id = hl_err })); - err_decor.virt_text_width = (int)mb_string2cells(err); - decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0); -} - -static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) -{ - long num; - char *fmt = "%*ld "; - - if (wp->w_p_nu && !wp->w_p_rnu) { - // 'number' + 'norelativenumber' - num = (long)lnum; - } else { - // 'relativenumber', don't use negative numbers - num = labs((long)get_cursor_rel_lnum(wp, lnum)); - if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { - // 'number' + 'relativenumber' - num = lnum; - fmt = "%-*ld "; - } - } - - snprintf((char *)buf, buf_len, fmt, number_width(wp), num); -} - -/// Display line "lnum" of window 'wp' on the screen. -/// wp->w_virtcol needs to be valid. -/// -/// @param lnum line to display -/// @param startrow first row relative to window grid -/// @param endrow last grid row to be redrawn -/// @param nochange not updating for changed text -/// @param number_only only update the number column -/// @param foldinfo fold info for this line -/// @param[in, out] providers decoration providers active this line -/// items will be disables if they cause errors -/// or explicitly return `false`. -/// -/// @return the number of last row the line occupies. -static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, - bool number_only, foldinfo_T foldinfo, DecorProviders *providers) -{ - int c = 0; // init for GCC - long vcol = 0; // virtual column (for tabs) - long vcol_sbr = -1; // virtual column after showbreak - long vcol_prev = -1; // "vcol" of previous character - char_u *line; // current line - char_u *ptr; // current position in "line" - int row; // row in the window, excl w_winrow - ScreenGrid *grid = &wp->w_grid; // grid specific to the window - - char_u extra[57]; // sign, line number and 'fdc' must - // fit in here - int n_extra = 0; // number of extra chars - char_u *p_extra = NULL; // string of extra chars, plus NUL - char_u *p_extra_free = NULL; // p_extra needs to be freed - int c_extra = NUL; // extra chars, all the same - int c_final = NUL; // final char, mandatory if set - int extra_attr = 0; // attributes when n_extra != 0 - static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying - // curwin->w_p_lcs_chars.eol at - // end-of-line - int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used - int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used - bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; - - // saved "extra" items for when draw_state becomes WL_LINE (again) - int saved_n_extra = 0; - char_u *saved_p_extra = NULL; - int saved_c_extra = 0; - int saved_c_final = 0; - int saved_char_attr = 0; - - int n_attr = 0; // chars with special attr - int saved_attr2 = 0; // char_attr saved for n_attr - int n_attr3 = 0; // chars with overruling special attr - int saved_attr3 = 0; // char_attr saved for n_attr3 - - int n_skip = 0; // nr of chars to skip for 'nowrap' - - int fromcol = -10; // start of inverting - int tocol = MAXCOL; // end of inverting - int fromcol_prev = -2; // start of inverting after cursor - bool noinvcur = false; // don't invert the cursor - bool lnum_in_visual_area = false; - pos_T pos; - long v; - - int char_attr = 0; // attributes for next character - bool attr_pri = false; // char_attr has priority - bool area_highlighting = false; // Visual or incsearch highlighting in this line - int attr = 0; // attributes for area highlighting - int area_attr = 0; // attributes desired by highlighting - int search_attr = 0; // attributes desired by 'hlsearch' - int vcol_save_attr = 0; // saved attr for 'cursorcolumn' - int syntax_attr = 0; // attributes desired by syntax - int has_syntax = FALSE; // this buffer has syntax highl. - int save_did_emsg; - int eol_hl_off = 0; // 1 if highlighted char after EOL - bool draw_color_col = false; // highlight colorcolumn - int *color_cols = NULL; // pointer to according columns array - bool has_spell = false; // this buffer has spell checking -#define SPWORDLEN 150 - char_u nextline[SPWORDLEN * 2]; // text with start of the next line - int nextlinecol = 0; // column where nextline[] starts - int nextline_idx = 0; /* index in nextline[] where next line - starts */ - int spell_attr = 0; // attributes desired by spelling - int word_end = 0; // last byte with same spell_attr - static linenr_T checked_lnum = 0; // line number for "checked_col" - static int checked_col = 0; /* column in "checked_lnum" up to which - * there are no spell errors */ - static int cap_col = -1; // column to check for Cap word - static linenr_T capcol_lnum = 0; // line number where "cap_col" - int cur_checked_col = 0; // checked column for current line - int extra_check = 0; // has syntax or linebreak - int multi_attr = 0; // attributes desired by multibyte - int mb_l = 1; // multi-byte byte length - int mb_c = 0; // decoded multi-byte character - bool mb_utf8 = false; // screen char is UTF-8 char - int u8cc[MAX_MCO]; // composing UTF-8 chars - int filler_lines; // nr of filler lines to be drawn - int filler_todo; // nr of filler lines still to do + 1 - hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting - int change_start = MAXCOL; // first col of changed area - int change_end = -1; // last col of changed area - colnr_T trailcol = MAXCOL; // start of trailing spaces - colnr_T leadcol = 0; // start of leading spaces - bool in_multispace = false; // in multiple consecutive spaces - int multispace_pos = 0; // position in lcs-multispace string - bool need_showbreak = false; // overlong line, skip first x chars - sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs - int num_signs; // number of signs for line - int line_attr = 0; // attribute for the whole line - int line_attr_save; - int line_attr_lowprio = 0; // low-priority attribute for the line - int line_attr_lowprio_save; - int prev_c = 0; // previous Arabic character - int prev_c1 = 0; // first composing char for prev_c - - bool search_attr_from_match = false; // if search_attr is from :match - bool has_decor = false; // this buffer has decoration - int win_col_offset = 0; // offset for window columns - - char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext - - bool area_active = false; - - int cul_attr = 0; // set when 'cursorline' active - // 'cursorlineopt' has "screenline" and cursor is in this line - bool cul_screenline = false; - // margin columns for the screen line, needed for when 'cursorlineopt' - // contains "screenline" - int left_curline_col = 0; - int right_curline_col = 0; - - // draw_state: items that are drawn in sequence: -#define WL_START 0 // nothing done yet -#define WL_CMDLINE (WL_START + 1) // cmdline window column -#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' -#define WL_SIGN (WL_FOLD + 1) // column for signs -#define WL_NR (WL_SIGN + 1) // line number -#define WL_BRI (WL_NR + 1) // 'breakindent' -#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' -#define WL_LINE (WL_SBR + 1) // text in the line - int draw_state = WL_START; // what to draw next - - int syntax_flags = 0; - int syntax_seqnr = 0; - int prev_syntax_id = 0; - int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); - bool is_concealing = false; - int boguscols = 0; ///< nonexistent columns added to - ///< force wrapping - int vcol_off = 0; ///< offset for concealed characters - int did_wcol = false; - int match_conc = 0; ///< cchar for match functions - int old_boguscols = 0; -#define VCOL_HLC (vcol - vcol_off) -#define FIX_FOR_BOGUSCOLS \ - { \ - n_extra += vcol_off; \ - vcol -= vcol_off; \ - vcol_off = 0; \ - col -= boguscols; \ - old_boguscols = boguscols; \ - boguscols = 0; \ - } - - if (startrow > endrow) { // past the end already! - return startrow; - } - - row = startrow; - - buf_T *buf = wp->w_buffer; - bool end_fill = (lnum == buf->b_ml.ml_line_count + 1); - - if (!number_only) { - // To speed up the loop below, set extra_check when there is linebreak, - // trailing white space and/or syntax processing to be done. - extra_check = wp->w_p_lbr; - if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow - && !has_fold && !end_fill) { - // Prepare for syntax highlighting in this line. When there is an - // error, stop syntax highlighting. - save_did_emsg = did_emsg; - did_emsg = false; - syntax_start(wp, lnum); - if (did_emsg) { - wp->w_s->b_syn_error = true; - } else { - did_emsg = save_did_emsg; - if (!wp->w_s->b_syn_slow) { - has_syntax = true; - extra_check = true; - } - } - } - - has_decor = decor_redraw_line(buf, lnum - 1, &decor_state); - - providers_invoke_line(wp, providers, lnum - 1, &has_decor, &provider_err); - - if (provider_err) { - provider_err_virt_text(lnum, provider_err); - has_decor = true; - provider_err = NULL; - } - - if (has_decor) { - extra_check = true; - } - - // Check for columns to display for 'colorcolumn'. - color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; - if (color_cols != NULL) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - if (wp->w_p_spell - && !has_fold - && !end_fill - && *wp->w_s->b_p_spl != NUL - && !GA_EMPTY(&wp->w_s->b_langp) - && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { - // Prepare for spell checking. - has_spell = true; - extra_check = true; - - // Get the start of the next line, so that words that wrap to the next - // line are found too: "et<line-break>al.". - // Trick: skip a few chars for C/shell/Vim comments - nextline[SPWORDLEN] = NUL; - if (lnum < wp->w_buffer->b_ml.ml_line_count) { - line = ml_get_buf(wp->w_buffer, lnum + 1, false); - spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); - } - - // When a word wrapped from the previous line the start of the current - // line is valid. - if (lnum == checked_lnum) { - cur_checked_col = checked_col; - } - checked_lnum = 0; - - // When there was a sentence end in the previous line may require a - // word starting with capital in this line. In line 1 always check - // the first word. - if (lnum != capcol_lnum) { - cap_col = -1; - } - if (lnum == 1) { - cap_col = 0; - } - capcol_lnum = 0; - } - - // handle Visual active in this window - if (VIsual_active && wp->w_buffer == curwin->w_buffer) { - pos_T *top, *bot; - - if (ltoreq(curwin->w_cursor, VIsual)) { - // Visual is after curwin->w_cursor - top = &curwin->w_cursor; - bot = &VIsual; - } else { - // Visual is before curwin->w_cursor - top = &VIsual; - bot = &curwin->w_cursor; - } - lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); - if (VIsual_mode == Ctrl_V) { - // block mode - if (lnum_in_visual_area) { - fromcol = wp->w_old_cursor_fcol; - tocol = wp->w_old_cursor_lcol; - } - } else { - // non-block mode - if (lnum > top->lnum && lnum <= bot->lnum) { - fromcol = 0; - } else if (lnum == top->lnum) { - if (VIsual_mode == 'V') { // linewise - fromcol = 0; - } else { - getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL); - if (gchar_pos(top) == NUL) { - tocol = fromcol + 1; - } - } - } - if (VIsual_mode != 'V' && lnum == bot->lnum) { - if (*p_sel == 'e' && bot->col == 0 - && bot->coladd == 0) { - fromcol = -10; - tocol = MAXCOL; - } else if (bot->col == MAXCOL) { - tocol = MAXCOL; - } else { - pos = *bot; - if (*p_sel == 'e') { - getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL); - } else { - getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol); - tocol++; - } - } - } - } - - // Check if the char under the cursor should be inverted (highlighted). - if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin - && cursor_is_block_during_visual(*p_sel == 'e')) { - noinvcur = true; - } - - // if inverting in this line set area_highlighting - if (fromcol >= 0) { - area_highlighting = true; - attr = win_hl_attr(wp, HLF_V); - } - // handle 'incsearch' and ":s///c" highlighting - } else if (highlight_match - && wp == curwin - && !has_fold - && lnum >= curwin->w_cursor.lnum - && lnum <= curwin->w_cursor.lnum + search_match_lines) { - if (lnum == curwin->w_cursor.lnum) { - getvcol(curwin, &(curwin->w_cursor), - (colnr_T *)&fromcol, NULL, NULL); - } else { - fromcol = 0; - } - if (lnum == curwin->w_cursor.lnum + search_match_lines) { - pos.lnum = lnum; - pos.col = search_match_endcol; - getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); - } - // do at least one character; happens when past end of line - if (fromcol == tocol && search_match_endcol) { - tocol = fromcol + 1; - } - area_highlighting = true; - attr = win_hl_attr(wp, HLF_I); - } - } - - filler_lines = diff_check(wp, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { - if (diff_find_change(wp, lnum, &change_start, &change_end)) { - diff_hlf = HLF_ADD; // added line - } else if (change_start == 0) { - diff_hlf = HLF_TXD; // changed text - } else { - diff_hlf = HLF_CHD; // changed line - } - } else { - diff_hlf = HLF_ADD; // added line - } - filler_lines = 0; - area_highlighting = true; - } - VirtLines virt_lines = KV_INITIAL_VALUE; - int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); - filler_lines += n_virt_lines; - if (lnum == wp->w_topline) { - filler_lines = wp->w_topfill; - n_virt_lines = MIN(n_virt_lines, filler_lines); - } - filler_todo = filler_lines; - - // Cursor line highlighting for 'cursorline' in the current window. - if (lnum == wp->w_cursor.lnum) { - // Do not show the cursor line in the text when Visual mode is active, - // because it's not clear what is selected then. - if (wp->w_p_cul && !(wp == curwin && VIsual_active) - && wp->w_p_culopt_flags != CULOPT_NBR) { - cul_screenline = (wp->w_p_wrap - && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); - if (!cul_screenline) { - cul_attr = win_hl_attr(wp, HLF_CUL); - HlAttrs ae = syn_attr2entry(cul_attr); - // We make a compromise here (#7383): - // * low-priority CursorLine if fg is not set - // * high-priority ("same as Vim" priority) CursorLine if fg is set - if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { - line_attr_lowprio = cul_attr; - } else { - if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) - && qf_current_entry(wp) == lnum) { - line_attr = hl_combine_attr(cul_attr, line_attr); - } else { - line_attr = cul_attr; - } - } - } else { - margin_columns_win(wp, &left_curline_col, &right_curline_col); - } - area_highlighting = true; - } - } - - memset(sattrs, 0, sizeof(sattrs)); - num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); - decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs); - - // If this line has a sign with line highlighting set line_attr. - // TODO(bfredl, vigoux): this should not take priority over decoration! - sign_attrs_T *sattr = sign_get_attr(SIGN_LINEHL, sattrs, 0, 1); - if (sattr != NULL) { - line_attr = sattr->sat_linehl; - } - - // Highlight the current line in the quickfix window. - if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { - line_attr = win_hl_attr(wp, HLF_QFL); - } - - if (line_attr_lowprio || line_attr) { - area_highlighting = true; - } - - if (cul_screenline) { - line_attr_save = line_attr; - line_attr_lowprio_save = line_attr_lowprio; - } - - line = end_fill ? (char_u *)"" : ml_get_buf(wp->w_buffer, lnum, false); - ptr = line; - - if (has_spell && !number_only) { - // For checking first word with a capital skip white space. - if (cap_col == 0) { - cap_col = (int)getwhitecols(line); - } - - /* To be able to spell-check over line boundaries copy the end of the - * current line into nextline[]. Above the start of the next line was - * copied to nextline[SPWORDLEN]. */ - if (nextline[SPWORDLEN] == NUL) { - // No next line or it is empty. - nextlinecol = MAXCOL; - nextline_idx = 0; - } else { - v = (long)STRLEN(line); - if (v < SPWORDLEN) { - /* Short line, use it completely and append the start of the - * next line. */ - nextlinecol = 0; - memmove(nextline, line, (size_t)v); - STRMOVE(nextline + v, nextline + SPWORDLEN); - nextline_idx = (int)v + 1; - } else { - // Long line, use only the last SPWORDLEN bytes. - nextlinecol = (int)v - SPWORDLEN; - memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 - nextline_idx = SPWORDLEN + 1; - } - } - } - - if (wp->w_p_list && !has_fold && !end_fill) { - if (wp->w_p_lcs_chars.space - || wp->w_p_lcs_chars.multispace != NULL - || wp->w_p_lcs_chars.leadmultispace != NULL - || wp->w_p_lcs_chars.trail - || wp->w_p_lcs_chars.lead - || wp->w_p_lcs_chars.nbsp) { - extra_check = true; - } - // find start of trailing whitespace - if (wp->w_p_lcs_chars.trail) { - trailcol = (colnr_T)STRLEN(ptr); - while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { - trailcol--; - } - trailcol += (colnr_T)(ptr - line); - } - // find end of leading whitespace - if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { - leadcol = 0; - while (ascii_iswhite(ptr[leadcol])) { - leadcol++; - } - if (ptr[leadcol] == NUL) { - // in a line full of spaces all of them are treated as trailing - leadcol = (colnr_T)0; - } else { - // keep track of the first column not filled with spaces - leadcol += (colnr_T)(ptr - line) + 1; - } - } - } - - /* - * 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the - * first character to be displayed. - */ - if (wp->w_p_wrap) { - v = wp->w_skipcol; - } else { - v = wp->w_leftcol; - } - if (v > 0 && !number_only) { - char_u *prev_ptr = ptr; - while (vcol < v && *ptr != NUL) { - c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); - vcol += c; - prev_ptr = ptr; - MB_PTR_ADV(ptr); - } - - // When: - // - 'cuc' is set, or - // - 'colorcolumn' is set, or - // - 'virtualedit' is set, or - // - the visual mode is active, - // the end of the line may be before the start of the displayed part. - if (vcol < v && (wp->w_p_cuc - || draw_color_col - || virtual_active() - || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { - vcol = v; - } - - /* Handle a character that's not completely on the screen: Put ptr at - * that character but skip the first few screen characters. */ - if (vcol > v) { - vcol -= c; - ptr = prev_ptr; - // If the character fits on the screen, don't need to skip it. - // Except for a TAB. - if (utf_ptr2cells((char *)ptr) >= c || *ptr == TAB) { - n_skip = (int)(v - vcol); - } - } - - /* - * Adjust for when the inverted text is before the screen, - * and when the start of the inverted text is before the screen. - */ - if (tocol <= vcol) { - fromcol = 0; - } else if (fromcol >= 0 && fromcol < vcol) { - fromcol = (int)vcol; - } - - // When w_skipcol is non-zero, first line needs 'showbreak' - if (wp->w_p_wrap) { - need_showbreak = true; - } - // When spell checking a word we need to figure out the start of the - // word and if it's badly spelled or not. - if (has_spell) { - size_t len; - colnr_T linecol = (colnr_T)(ptr - line); - hlf_T spell_hlf = HLF_COUNT; - - pos = wp->w_cursor; - wp->w_cursor.lnum = lnum; - wp->w_cursor.col = linecol; - len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); - - // spell_move_to() may call ml_get() and make "line" invalid - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + linecol; - - if (len == 0 || (int)wp->w_cursor.col > ptr - line) { - /* no bad word found at line start, don't check until end of a - * word */ - spell_hlf = HLF_COUNT; - word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); - } else { - // bad word found, use attributes until end of word - assert(len <= INT_MAX); - word_end = wp->w_cursor.col + (int)len + 1; - - // Turn index into actual attributes. - if (spell_hlf != HLF_COUNT) { - spell_attr = highlight_attr[spell_hlf]; - } - } - wp->w_cursor = pos; - - // Need to restart syntax highlighting for this line. - if (has_syntax) { - syntax_start(wp, lnum); - } - } - } - - /* - * Correct highlighting for cursor that can't be disabled. - * Avoids having to check this for each character. - */ - if (fromcol >= 0) { - if (noinvcur) { - if ((colnr_T)fromcol == wp->w_virtcol) { - /* highlighting starts at cursor, let it start just after the - * cursor */ - fromcol_prev = fromcol; - fromcol = -1; - } else if ((colnr_T)fromcol < wp->w_virtcol) { - // restart highlighting after the cursor - fromcol_prev = wp->w_virtcol; - } - } - if (fromcol >= tocol) { - fromcol = -1; - } - } - - if (!number_only && !has_fold && !end_fill) { - v = ptr - line; - area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, - &line, &search_hl, &search_attr, - &search_attr_from_match); - ptr = line + v; // "line" may have been updated - } - - int off = 0; // Offset relative start of line - int col = 0; // Visual column on screen. - if (wp->w_p_rl) { - // Rightleft window: process the text in the normal direction, but put - // it in linebuf_char[off] from right to left. Start at the - // rightmost column of the window. - col = grid->cols - 1; - off += col; - } - - // won't highlight after TERM_ATTRS_MAX columns - int term_attrs[TERM_ATTRS_MAX] = { 0 }; - if (wp->w_buffer->terminal) { - terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs); - extra_check = true; - } - - int sign_idx = 0; - // Repeat for the whole displayed line. - for (;;) { - int has_match_conc = 0; ///< match wants to conceal - int decor_conceal = 0; - - bool did_decrement_ptr = false; - - // Skip this quickly when working on the text. - if (draw_state != WL_LINE) { - if (cul_screenline) { - cul_attr = 0; - line_attr = line_attr_save; - line_attr_lowprio = line_attr_lowprio_save; - } - - if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { - draw_state = WL_CMDLINE; - if (cmdwin_type != 0 && wp == curwin) { - // Draw the cmdline character. - n_extra = 1; - c_extra = cmdwin_type; - c_final = NUL; - char_attr = win_hl_attr(wp, HLF_AT); - } - } - - if (draw_state == WL_FOLD - 1 && n_extra == 0) { - int fdc = compute_foldcolumn(wp, 0); - - draw_state = WL_FOLD; - if (fdc > 0) { - // Draw the 'foldcolumn'. Allocate a buffer, "extra" may - // already be in use. - xfree(p_extra_free); - p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1); - n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); - p_extra_free[n_extra] = NUL; - p_extra = p_extra_free; - c_extra = NUL; - c_final = NUL; - if (use_cursor_line_sign(wp, lnum)) { - char_attr = win_hl_attr(wp, HLF_CLF); - } else { - char_attr = win_hl_attr(wp, HLF_FC); - } - } - } - - // sign column, this is hit until sign_idx reaches count - if (draw_state == WL_SIGN - 1 && n_extra == 0) { - draw_state = WL_SIGN; - /* Show the sign column when there are any signs in this - * buffer or when using Netbeans. */ - if (wp->w_scwidth > 0) { - get_sign_display_info(false, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, - &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, &char_attr, sign_idx); - sign_idx++; - if (sign_idx < wp->w_scwidth) { - draw_state = WL_SIGN - 1; - } else { - sign_idx = 0; - } - } - } - - if (draw_state == WL_NR - 1 && n_extra == 0) { - draw_state = WL_NR; - /* Display the absolute or relative line number. After the - * first fill with blanks when the 'n' flag isn't in 'cpo' */ - if ((wp->w_p_nu || wp->w_p_rnu) - && (row == startrow + filler_lines - || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { - // If 'signcolumn' is set to 'number' and a sign is present - // in 'lnum', then display the sign instead of the line - // number. - if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' - && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) { - get_sign_display_info(true, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, - &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, &char_attr, sign_idx); - } else { - // Draw the line number (empty space after wrapping). - if (row == startrow + filler_lines) { - get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra)); - if (wp->w_skipcol > 0) { - for (p_extra = extra; *p_extra == ' '; p_extra++) { - *p_extra = '-'; - } - } - if (wp->w_p_rl) { // reverse line numbers - // like rl_mirror(), but keep the space at the end - char_u *p2 = (char_u *)skipwhite((char *)extra); - p2 = skiptowhite(p2) - 1; - for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) { - const char_u t = *p1; - *p1 = *p2; - *p2 = t; - } - } - p_extra = extra; - c_extra = NUL; - } else { - c_extra = ' '; - } - c_final = NUL; - n_extra = number_width(wp) + 1; - char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines, sattrs); - } - } - } - - if (draw_state == WL_NR && n_extra == 0) { - win_col_offset = off; - } - - if (wp->w_briopt_sbr && draw_state == WL_BRI - 1 - && n_extra == 0 && *get_showbreak_value(wp) != NUL) { - // draw indent after showbreak value - draw_state = WL_BRI; - } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) { - // after the showbreak, draw the breakindent - draw_state = WL_BRI - 1; - } - - // draw 'breakindent': indent wrapped text accordingly - if (draw_state == WL_BRI - 1 && n_extra == 0) { - draw_state = WL_BRI; - // if need_showbreak is set, breakindent also applies - if (wp->w_p_bri && (row != startrow || need_showbreak) - && filler_lines == 0) { - char_attr = 0; - - if (diff_hlf != (hlf_T)0) { - char_attr = win_hl_attr(wp, (int)diff_hlf); - } - p_extra = NULL; - c_extra = ' '; - c_final = NUL; - n_extra = - get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); - if (row == startrow) { - n_extra -= win_col_off2(wp); - if (n_extra < 0) { - n_extra = 0; - } - } - if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { - need_showbreak = false; - } - // Correct end of highlighted area for 'breakindent', - // required wen 'linebreak' is also set. - if (tocol == vcol) { - tocol += n_extra; - } - } - } - - if (draw_state == WL_SBR - 1 && n_extra == 0) { - draw_state = WL_SBR; - if (filler_todo > filler_lines - n_virt_lines) { - // TODO(bfredl): check this doesn't inhibit TUI-style - // clear-to-end-of-line. - c_extra = ' '; - c_final = NUL; - if (wp->w_p_rl) { - n_extra = col + 1; - } else { - n_extra = grid->cols - col; - } - char_attr = 0; - } else if (filler_todo > 0) { - // draw "deleted" diff line(s) - if (char2cells(wp->w_p_fcs_chars.diff) > 1) { - c_extra = '-'; - c_final = NUL; - } else { - c_extra = wp->w_p_fcs_chars.diff; - c_final = NUL; - } - if (wp->w_p_rl) { - n_extra = col + 1; - } else { - n_extra = grid->cols - col; - } - char_attr = win_hl_attr(wp, HLF_DED); - } - char_u *const sbr = get_showbreak_value(wp); - if (*sbr != NUL && need_showbreak) { - // Draw 'showbreak' at the start of each broken line. - p_extra = sbr; - c_extra = NUL; - c_final = NUL; - n_extra = (int)STRLEN(sbr); - char_attr = win_hl_attr(wp, HLF_AT); - if (wp->w_skipcol == 0 || !wp->w_p_wrap) { - need_showbreak = false; - } - vcol_sbr = vcol + mb_charlen(sbr); - // Correct end of highlighted area for 'showbreak', - // required when 'linebreak' is also set. - if (tocol == vcol) { - tocol += n_extra; - } - // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. - if (cul_attr) { - char_attr = hl_combine_attr(cul_attr, char_attr); - } - } - } - - if (draw_state == WL_LINE - 1 && n_extra == 0) { - sign_idx = 0; - draw_state = WL_LINE; - - if (has_decor && row == startrow + filler_lines) { - // hide virt_text on text hidden by 'nowrap' - decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state); - } - - if (saved_n_extra) { - // Continue item from end of wrapped line. - n_extra = saved_n_extra; - c_extra = saved_c_extra; - c_final = saved_c_final; - p_extra = saved_p_extra; - char_attr = saved_char_attr; - } else { - char_attr = 0; - } - } - } - - if (cul_screenline && draw_state == WL_LINE - && vcol >= left_curline_col - && vcol < right_curline_col) { - cul_attr = win_hl_attr(wp, HLF_CUL); - HlAttrs ae = syn_attr2entry(cul_attr); - if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { - line_attr_lowprio = cul_attr; - } else { - if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) - && qf_current_entry(wp) == lnum) { - line_attr = hl_combine_attr(cul_attr, line_attr); - } else { - line_attr = cul_attr; - } - } - } - - // When still displaying '$' of change command, stop at cursor - if (((dollar_vcol >= 0 - && wp == curwin - && lnum == wp->w_cursor.lnum - && vcol >= (long)wp->w_virtcol) - || (number_only && draw_state > WL_NR)) - && filler_todo <= 0) { - draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); - grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, - wp->w_hl_attr_normal, false); - // Pretend we have finished updating the window. Except when - // 'cursorcolumn' is set. - if (wp->w_p_cuc) { - row = wp->w_cline_row + wp->w_cline_height; - } else { - row = grid->rows; - } - break; - } - - if (draw_state == WL_LINE - && has_fold - && col == win_col_offset - && n_extra == 0 - && row == startrow) { - char_attr = win_hl_attr(wp, HLF_FL); - - linenr_T lnume = lnum + foldinfo.fi_lines - 1; - memset(buf_fold, ' ', FOLD_TEXT_LEN); - p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold); - n_extra = (int)STRLEN(p_extra); - - if (p_extra != buf_fold) { - xfree(p_extra_free); - p_extra_free = p_extra; - } - c_extra = NUL; - c_final = NUL; - p_extra[n_extra] = NUL; - } - - if (draw_state == WL_LINE - && has_fold - && col < grid->cols - && n_extra == 0 - && row == startrow) { - // fill rest of line with 'fold' - c_extra = wp->w_p_fcs_chars.fold; - c_final = NUL; - - n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col); - } - - if (draw_state == WL_LINE - && has_fold - && col >= grid->cols - && n_extra != 0 - && row == startrow) { - // Truncate the folding. - n_extra = 0; - } - - if (draw_state == WL_LINE && (area_highlighting || has_spell)) { - // handle Visual or match highlighting in this line - if (vcol == fromcol - || (vcol + 1 == fromcol && n_extra == 0 - && utf_ptr2cells((char *)ptr) > 1) - || ((int)vcol_prev == fromcol_prev - && vcol_prev < vcol // not at margin - && vcol < tocol)) { - area_attr = attr; // start highlighting - if (area_highlighting) { - area_active = true; - } - } else if (area_attr != 0 && (vcol == tocol - || (noinvcur - && (colnr_T)vcol == wp->w_virtcol))) { - area_attr = 0; // stop highlighting - area_active = false; - } - - if (!n_extra) { - // Check for start/end of 'hlsearch' and other matches. - // After end, check for start/end of next match. - // When another match, have to check for start again. - v = (ptr - line); - search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &search_hl, &has_match_conc, - &match_conc, lcs_eol_one, &search_attr_from_match); - ptr = line + v; // "line" may have been changed - - // Do not allow a conceal over EOL otherwise EOL will be missed - // and bad things happen. - if (*ptr == NUL) { - has_match_conc = 0; - } - } - - if (diff_hlf != (hlf_T)0) { - if (diff_hlf == HLF_CHD && ptr - line >= change_start - && n_extra == 0) { - diff_hlf = HLF_TXD; // changed text - } - if (diff_hlf == HLF_TXD && ptr - line > change_end - && n_extra == 0) { - diff_hlf = HLF_CHD; // changed line - } - line_attr = win_hl_attr(wp, (int)diff_hlf); - // Overlay CursorLine onto diff-mode highlight. - if (cul_attr) { - line_attr = 0 != line_attr_lowprio // Low-priority CursorLine - ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr), - hl_get_underline()) - : hl_combine_attr(line_attr, cul_attr); - } - } - - // Decide which of the highlight attributes to use. - attr_pri = true; - - if (area_attr != 0) { - char_attr = hl_combine_attr(line_attr, area_attr); - if (!highlight_match) { - // let search highlight show in Visual area if possible - char_attr = hl_combine_attr(search_attr, char_attr); - } - } else if (search_attr != 0) { - char_attr = hl_combine_attr(line_attr, search_attr); - } - // Use line_attr when not in the Visual or 'incsearch' area - // (area_attr may be 0 when "noinvcur" is set). - else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL) - || vcol < fromcol || vcol_prev < fromcol_prev - || vcol >= tocol)) { - char_attr = line_attr; - } else { - attr_pri = false; - if (has_syntax) { - char_attr = syntax_attr; - } else { - char_attr = 0; - } - } - } - - // Get the next character to put on the screen. - // - // The "p_extra" points to the extra stuff that is inserted to - // represent special characters (non-printable stuff) and other - // things. When all characters are the same, c_extra is used. - // If c_final is set, it will compulsorily be used at the end. - // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past - // "p_extra[n_extra]". - // For the '$' of the 'list' option, n_extra == 1, p_extra == "". - if (n_extra > 0) { - if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { - c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; - mb_c = c; // doesn't handle non-utf-8 multi-byte! - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } else { - assert(p_extra != NULL); - c = *p_extra; - mb_c = c; - // If the UTF-8 character is more than one byte: - // Decode it into "mb_c". - mb_l = utfc_ptr2len((char *)p_extra); - mb_utf8 = false; - if (mb_l > n_extra) { - mb_l = 1; - } else if (mb_l > 1) { - mb_c = utfc_ptr2char(p_extra, u8cc); - mb_utf8 = true; - c = 0xc0; - } - if (mb_l == 0) { // at the NUL at end-of-line - mb_l = 1; - } - - // If a double-width char doesn't fit display a '>' in the last column. - if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1)) - && utf_char2cells(mb_c) == 2) { - c = '>'; - mb_c = c; - mb_l = 1; - (void)mb_l; - multi_attr = win_hl_attr(wp, HLF_AT); - - if (cul_attr) { - multi_attr = 0 != line_attr_lowprio - ? hl_combine_attr(cul_attr, multi_attr) - : hl_combine_attr(multi_attr, cul_attr); - } - - // put the pointer back to output the double-width - // character at the start of the next line. - n_extra++; - p_extra--; - } else { - n_extra -= mb_l - 1; - p_extra += mb_l - 1; - } - p_extra++; - } - n_extra--; - } else if (foldinfo.fi_lines > 0) { - // skip writing the buffer line itself - c = NUL; - XFREE_CLEAR(p_extra_free); - } else { - int c0; - - XFREE_CLEAR(p_extra_free); - - // Get a character from the line itself. - c0 = c = *ptr; - mb_c = c; - // If the UTF-8 character is more than one byte: Decode it - // into "mb_c". - mb_l = utfc_ptr2len((char *)ptr); - mb_utf8 = false; - if (mb_l > 1) { - mb_c = utfc_ptr2char(ptr, u8cc); - // Overlong encoded ASCII or ASCII with composing char - // is displayed normally, except a NUL. - if (mb_c < 0x80) { - c0 = c = mb_c; - } - mb_utf8 = true; - - // At start of the line we can have a composing char. - // Draw it as a space with a composing char. - if (utf_iscomposing(mb_c)) { - int i; - - for (i = MAX_MCO - 1; i > 0; i--) { - u8cc[i] = u8cc[i - 1]; - } - u8cc[0] = mb_c; - mb_c = ' '; - } - } - - if ((mb_l == 1 && c >= 0x80) - || (mb_l >= 1 && mb_c == 0) - || (mb_l > 1 && (!vim_isprintc(mb_c)))) { - // Illegal UTF-8 byte: display as <xx>. - // Non-BMP character : display as ? or fullwidth ?. - transchar_hex((char *)extra, mb_c); - if (wp->w_p_rl) { // reverse - rl_mirror(extra); - } - - p_extra = extra; - c = *p_extra; - mb_c = mb_ptr2char_adv((const char_u **)&p_extra); - mb_utf8 = (c >= 0x80); - n_extra = (int)STRLEN(p_extra); - c_extra = NUL; - c_final = NUL; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_8); - saved_attr2 = char_attr; // save current attr - } - } else if (mb_l == 0) { // at the NUL at end-of-line - mb_l = 1; - } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) { - // Do Arabic shaping. - int pc, pc1, nc; - int pcc[MAX_MCO]; - - // The idea of what is the previous and next - // character depends on 'rightleft'. - if (wp->w_p_rl) { - pc = prev_c; - pc1 = prev_c1; - nc = utf_ptr2char((char *)ptr + mb_l); - prev_c1 = u8cc[0]; - } else { - pc = utfc_ptr2char(ptr + mb_l, pcc); - nc = prev_c; - pc1 = pcc[0]; - } - prev_c = mb_c; - - mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); - } else { - prev_c = mb_c; - } - // If a double-width char doesn't fit display a '>' in the - // last column; the character is displayed at the start of the - // next line. - if ((wp->w_p_rl ? (col <= 0) : - (col >= grid->cols - 1)) - && utf_char2cells(mb_c) == 2) { - c = '>'; - mb_c = c; - mb_utf8 = false; - mb_l = 1; - multi_attr = win_hl_attr(wp, HLF_AT); - // Put pointer back so that the character will be - // displayed at the start of the next line. - ptr--; - did_decrement_ptr = true; - } else if (*ptr != NUL) { - ptr += mb_l - 1; - } - - // If a double-width char doesn't fit at the left side display a '<' in - // the first column. Don't do this for unprintable characters. - if (n_skip > 0 && mb_l > 1 && n_extra == 0) { - n_extra = 1; - c_extra = MB_FILLER_CHAR; - c_final = NUL; - c = ' '; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_AT); - saved_attr2 = char_attr; // save current attr - } - mb_c = c; - mb_utf8 = false; - mb_l = 1; - } - ptr++; - - if (extra_check) { - bool can_spell = true; - - /* Get syntax attribute, unless still at the start of the line - * (double-wide char that doesn't fit). */ - v = (ptr - line); - if (has_syntax && v > 0) { - /* Get the syntax attribute for the character. If there - * is an error, disable syntax highlighting. */ - save_did_emsg = did_emsg; - did_emsg = FALSE; - - syntax_attr = get_syntax_attr((colnr_T)v - 1, - has_spell ? &can_spell : NULL, false); - - if (did_emsg) { - wp->w_s->b_syn_error = TRUE; - has_syntax = FALSE; - } else { - did_emsg = save_did_emsg; - } - - if (wp->w_s->b_syn_slow) { - has_syntax = false; - } - - // Need to get the line again, a multi-line regexp may - // have made it invalid. - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + v; - - if (!attr_pri) { - if (cul_attr) { - char_attr = 0 != line_attr_lowprio - ? hl_combine_attr(cul_attr, syntax_attr) - : hl_combine_attr(syntax_attr, cul_attr); - } else { - char_attr = syntax_attr; - } - } else { - char_attr = hl_combine_attr(syntax_attr, char_attr); - } - // no concealing past the end of the line, it interferes - // with line highlighting. - if (c == NUL) { - syntax_flags = 0; - } else { - syntax_flags = get_syntax_info(&syntax_seqnr); - } - } else if (!attr_pri) { - char_attr = 0; - } - - /* Check spelling (unless at the end of the line). - * Only do this when there is no syntax highlighting, the - * @Spell cluster is not used or the current syntax item - * contains the @Spell cluster. */ - v = (ptr - line); - if (has_spell && v >= word_end && v > cur_checked_col) { - spell_attr = 0; - if (!attr_pri) { - char_attr = syntax_attr; - } - if (c != 0 && (!has_syntax || can_spell)) { - char_u *prev_ptr; - char_u *p; - int len; - hlf_T spell_hlf = HLF_COUNT; - prev_ptr = ptr - mb_l; - v -= mb_l - 1; - - /* Use nextline[] if possible, it has the start of the - * next line concatenated. */ - if ((prev_ptr - line) - nextlinecol >= 0) { - p = nextline + ((prev_ptr - line) - nextlinecol); - } else { - p = prev_ptr; - } - cap_col -= (int)(prev_ptr - line); - size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); - assert(tmplen <= INT_MAX); - len = (int)tmplen; - word_end = (int)v + len; - - /* In Insert mode only highlight a word that - * doesn't touch the cursor. */ - if (spell_hlf != HLF_COUNT - && (State & MODE_INSERT) - && wp->w_cursor.lnum == lnum - && wp->w_cursor.col >= - (colnr_T)(prev_ptr - line) - && wp->w_cursor.col < (colnr_T)word_end) { - spell_hlf = HLF_COUNT; - spell_redraw_lnum = lnum; - } - - if (spell_hlf == HLF_COUNT && p != prev_ptr - && (p - nextline) + len > nextline_idx) { - /* Remember that the good word continues at the - * start of the next line. */ - checked_lnum = lnum + 1; - checked_col = (int)((p - nextline) + len - nextline_idx); - } - - // Turn index into actual attributes. - if (spell_hlf != HLF_COUNT) { - spell_attr = highlight_attr[spell_hlf]; - } - - if (cap_col > 0) { - if (p != prev_ptr - && (p - nextline) + cap_col >= nextline_idx) { - /* Remember that the word in the next line - * must start with a capital. */ - capcol_lnum = lnum + 1; - cap_col = (int)((p - nextline) + cap_col - - nextline_idx); - } else { - // Compute the actual column. - cap_col += (int)(prev_ptr - line); - } - } - } - } - if (spell_attr != 0) { - if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, spell_attr); - } else { - char_attr = hl_combine_attr(spell_attr, char_attr); - } - } - - if (wp->w_buffer->terminal) { - char_attr = hl_combine_attr(term_attrs[vcol], char_attr); - } - - if (has_decor && v > 0) { - bool selected = (area_active || (area_highlighting && noinvcur - && (colnr_T)vcol == wp->w_virtcol)); - int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off, - selected, &decor_state); - if (extmark_attr != 0) { - if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, extmark_attr); - } else { - char_attr = hl_combine_attr(extmark_attr, char_attr); - } - } - - decor_conceal = decor_state.conceal; - if (decor_conceal && decor_state.conceal_char) { - decor_conceal = 2; // really?? - } - } - - // Found last space before word: check for line break. - if (wp->w_p_lbr && c0 == c && vim_isbreak(c) - && !vim_isbreak((int)(*ptr))) { - int mb_off = utf_head_off(line, ptr - 1); - char_u *p = ptr - (mb_off + 1); - // TODO: is passing p for start of the line OK? - n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; - - // We have just drawn the showbreak value, no need to add - // space for it again. - if (vcol == vcol_sbr) { - n_extra -= mb_charlen(get_showbreak_value(wp)); - if (n_extra < 0) { - n_extra = 0; - } - } - - if (c == TAB && n_extra + col > grid->cols) { - n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array) - 1; - } - c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; - c_final = NUL; - if (ascii_iswhite(c)) { - if (c == TAB) { - // See "Tab alignment" below. - FIX_FOR_BOGUSCOLS; - } - if (!wp->w_p_list) { - c = ' '; - } - } - } - - in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); - if (!in_multispace) { - multispace_pos = 0; - } - - // 'list': Change char 160 to 'nbsp' and space to 'space'. - // But not when the character is followed by a composing - // character (use mb_l to check that). - if (wp->w_p_list - && ((((c == 160 && mb_l == 1) - || (mb_utf8 - && ((mb_c == 160 && mb_l == 2) - || (mb_c == 0x202f && mb_l == 3)))) - && wp->w_p_lcs_chars.nbsp) - || (c == ' ' - && mb_l == 1 - && (wp->w_p_lcs_chars.space - || (in_multispace && wp->w_p_lcs_chars.multispace != NULL)) - && ptr - line >= leadcol - && ptr - line <= trailcol))) { - if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) { - c = wp->w_p_lcs_chars.multispace[multispace_pos++]; - if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { - multispace_pos = 0; - } - } else { - c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; - } - n_attr = 1; - extra_attr = win_hl_attr(wp, HLF_0); - saved_attr2 = char_attr; // save current attr - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } - - if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol) - || (leadcol != 0 && ptr < line + leadcol))) { - if (leadcol != 0 && in_multispace && ptr < line + leadcol - && wp->w_p_lcs_chars.leadmultispace != NULL) { - c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++]; - if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { - multispace_pos = 0; - } - } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) { - c = wp->w_p_lcs_chars.trail; - } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) { - c = wp->w_p_lcs_chars.lead; - } else if (leadcol != 0 && wp->w_p_lcs_chars.space) { - c = wp->w_p_lcs_chars.space; - } - - n_attr = 1; - extra_attr = win_hl_attr(wp, HLF_0); - saved_attr2 = char_attr; // save current attr - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } - } - - /* - * Handling of non-printable characters. - */ - if (!vim_isprintc(c)) { - // when getting a character from the file, we may have to - // turn it into something else on the way to putting it on the screen. - if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - int tab_len = 0; - long vcol_adjusted = vcol; // removed showbreak length - char_u *const sbr = get_showbreak_value(wp); - - // Only adjust the tab_len, when at the first column after the - // showbreak value was drawn. - if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) { - vcol_adjusted = vcol - mb_charlen(sbr); - } - // tab amount depends on current column - tab_len = tabstop_padding((colnr_T)vcol_adjusted, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array) - 1; - - if (!wp->w_p_lbr || !wp->w_p_list) { - n_extra = tab_len; - } else { - char_u *p; - int i; - int saved_nextra = n_extra; - - if (vcol_off > 0) { - // there are characters to conceal - tab_len += vcol_off; - } - // boguscols before FIX_FOR_BOGUSCOLS macro from above. - if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0 - && n_extra > tab_len) { - tab_len += n_extra - tab_len; - } - - // If n_extra > 0, it gives the number of chars - // to use for a tab, else we need to calculate the width - // for a tab. - int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); - if (wp->w_p_lcs_chars.tab3) { - len += utf_char2len(wp->w_p_lcs_chars.tab3); - } - if (n_extra > 0) { - len += n_extra - tab_len; - } - c = wp->w_p_lcs_chars.tab1; - p = xmalloc((size_t)len + 1); - memset(p, ' ', (size_t)len); - p[len] = NUL; - xfree(p_extra_free); - p_extra_free = p; - for (i = 0; i < tab_len; i++) { - if (*p == NUL) { - tab_len = i; - break; - } - int lcs = wp->w_p_lcs_chars.tab2; - - // if tab3 is given, use it for the last char - if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { - lcs = wp->w_p_lcs_chars.tab3; - } - p += utf_char2bytes(lcs, (char *)p); - n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); - } - p_extra = p_extra_free; - - // n_extra will be increased by FIX_FOX_BOGUSCOLS - // macro below, so need to adjust for that here - if (vcol_off > 0) { - n_extra -= vcol_off; - } - } - - { - int vc_saved = vcol_off; - - // Tab alignment should be identical regardless of - // 'conceallevel' value. So tab compensates of all - // previous concealed characters, and thus resets - // vcol_off and boguscols accumulated so far in the - // line. Note that the tab can be longer than - // 'tabstop' when there are concealed characters. - FIX_FOR_BOGUSCOLS; - - // Make sure, the highlighting for the tab char will be - // correctly set further below (effectively reverts the - // FIX_FOR_BOGSUCOLS macro). - if (n_extra == tab_len + vc_saved && wp->w_p_list - && wp->w_p_lcs_chars.tab1) { - tab_len += vc_saved; - } - } - - mb_utf8 = false; // don't draw as UTF-8 - if (wp->w_p_list) { - c = (n_extra == 0 && wp->w_p_lcs_chars.tab3) - ? wp->w_p_lcs_chars.tab3 - : wp->w_p_lcs_chars.tab1; - if (wp->w_p_lbr) { - c_extra = NUL; // using p_extra from above - } else { - c_extra = wp->w_p_lcs_chars.tab2; - } - c_final = wp->w_p_lcs_chars.tab3; - n_attr = tab_len + 1; - extra_attr = win_hl_attr(wp, HLF_0); - saved_attr2 = char_attr; // save current attr - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } - } else { - c_final = NUL; - c_extra = ' '; - c = ' '; - } - } else if (c == NUL - && (wp->w_p_list - || ((fromcol >= 0 || fromcol_prev >= 0) - && tocol > vcol - && VIsual_mode != Ctrl_V - && (wp->w_p_rl ? (col >= 0) : (col < grid->cols)) - && !(noinvcur - && lnum == wp->w_cursor.lnum - && (colnr_T)vcol == wp->w_virtcol))) - && lcs_eol_one > 0) { - // Display a '$' after the line or highlight an extra - // character if the line break is included. - // For a diff line the highlighting continues after the "$". - if (diff_hlf == (hlf_T)0 - && line_attr == 0 - && line_attr_lowprio == 0) { - // In virtualedit, visual selections may extend beyond end of line - if (area_highlighting && virtual_active() - && tocol != MAXCOL && vcol < tocol) { - n_extra = 0; - } else { - p_extra = at_end_str; - n_extra = 1; - c_extra = NUL; - c_final = NUL; - } - } - if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { - c = wp->w_p_lcs_chars.eol; - } else { - c = ' '; - } - lcs_eol_one = -1; - ptr--; // put it back at the NUL - extra_attr = win_hl_attr(wp, HLF_AT); - n_attr = 1; - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; // don't draw as UTF-8 - } - } else if (c != NUL) { - p_extra = transchar_buf(wp->w_buffer, c); - if (n_extra == 0) { - n_extra = byte2cells(c) - 1; - } - if ((dy_flags & DY_UHEX) && wp->w_p_rl) { - rl_mirror(p_extra); // reverse "<12>" - } - c_extra = NUL; - c_final = NUL; - if (wp->w_p_lbr) { - char_u *p; - - c = *p_extra; - p = xmalloc((size_t)n_extra + 1); - memset(p, ' ', (size_t)n_extra); - STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); // NOLINT(runtime/printf) - p[n_extra] = NUL; - xfree(p_extra_free); - p_extra_free = p_extra = p; - } else { - n_extra = byte2cells(c) - 1; - c = *p_extra++; - } - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_8); - saved_attr2 = char_attr; // save current attr - mb_utf8 = false; // don't draw as UTF-8 - } else if (VIsual_active - && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') - && virtual_active() - && tocol != MAXCOL - && vcol < tocol - && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) { - c = ' '; - ptr--; // put it back at the NUL - } - } - - if (wp->w_p_cole > 0 - && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) - && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) - && !(lnum_in_visual_area && vim_strchr((char *)wp->w_p_cocu, 'v') == NULL)) { - char_attr = conceal_attr; - if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0) - || has_match_conc > 1 || decor_conceal > 1) - && (syn_get_sub_char() != NUL - || (has_match_conc && match_conc) - || (decor_conceal && decor_state.conceal_char) - || wp->w_p_cole == 1) - && wp->w_p_cole != 3) { - // First time at this concealed item: display one - // character. - if (has_match_conc && match_conc) { - c = match_conc; - } else if (decor_conceal && decor_state.conceal_char) { - c = decor_state.conceal_char; - if (decor_state.conceal_attr) { - char_attr = decor_state.conceal_attr; - } - } else if (syn_get_sub_char() != NUL) { - c = syn_get_sub_char(); - } else if (wp->w_p_lcs_chars.conceal != NUL) { - c = wp->w_p_lcs_chars.conceal; - } else { - c = ' '; - } - - prev_syntax_id = syntax_seqnr; - - if (n_extra > 0) { - vcol_off += n_extra; - } - vcol += n_extra; - if (wp->w_p_wrap && n_extra > 0) { - if (wp->w_p_rl) { - col -= n_extra; - boguscols -= n_extra; - } else { - boguscols += n_extra; - col += n_extra; - } - } - n_extra = 0; - n_attr = 0; - } else if (n_skip == 0) { - is_concealing = true; - n_skip = 1; - } - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; // don't draw as UTF-8 - } - } else { - prev_syntax_id = 0; - is_concealing = false; - } - - if (n_skip > 0 && did_decrement_ptr) { - // not showing the '>', put pointer back to avoid getting stuck - ptr++; - } - } // end of printing from buffer content - - /* In the cursor line and we may be concealing characters: correct - * the cursor column when we reach its position. */ - if (!did_wcol && draw_state == WL_LINE - && wp == curwin && lnum == wp->w_cursor.lnum - && conceal_cursor_line(wp) - && (int)wp->w_virtcol <= vcol + n_skip) { - if (wp->w_p_rl) { - wp->w_wcol = grid->cols - col + boguscols - 1; - } else { - wp->w_wcol = col - boguscols; - } - wp->w_wrow = row; - did_wcol = true; - wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; - } - - // Don't override visual selection highlighting. - if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) { - char_attr = hl_combine_attr(char_attr, extra_attr); - } - - // Handle the case where we are in column 0 but not on the first - // character of the line and the user wants us to show us a - // special character (via 'listchars' option "precedes:<char>". - if (lcs_prec_todo != NUL - && wp->w_p_list - && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) - && filler_todo <= 0 - && draw_state > WL_NR - && c != NUL) { - c = wp->w_p_lcs_chars.prec; - lcs_prec_todo = NUL; - if (utf_char2cells(mb_c) > 1) { - // Double-width character being overwritten by the "precedes" - // character, need to fill up half the character. - c_extra = MB_FILLER_CHAR; - c_final = NUL; - n_extra = 1; - n_attr = 2; - extra_attr = win_hl_attr(wp, HLF_AT); - } - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; // don't draw as UTF-8 - } - saved_attr3 = char_attr; // save current attr - char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr - n_attr3 = 1; - } - - // At end of the text line or just after the last character. - if (c == NUL && eol_hl_off == 0) { - // flag to indicate whether prevcol equals startcol of search_hl or - // one of the matches - bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &search_hl, - (long)(ptr - line) - 1); - - // Invert at least one char, used for Visual and empty line or - // highlight match at end of line. If it's beyond the last - // char on the screen, just overwrite that one (tricky!) Not - // needed when a '$' was displayed for 'list'. - if (wp->w_p_lcs_chars.eol == lcs_eol_one - && ((area_attr != 0 && vcol == fromcol - && (VIsual_mode != Ctrl_V - || lnum == VIsual.lnum - || lnum == curwin->w_cursor.lnum)) - // highlight 'hlsearch' match at end of line - || prevcol_hl_flag)) { - int n = 0; - - if (wp->w_p_rl) { - if (col < 0) { - n = 1; - } - } else { - if (col >= grid->cols) { - n = -1; - } - } - if (n != 0) { - // At the window boundary, highlight the last character - // instead (better than nothing). - off += n; - col += n; - } else { - // Add a blank character to highlight. - schar_from_ascii(linebuf_char[off], ' '); - } - if (area_attr == 0 && !has_fold) { - // Use attributes from match with highest priority among - // 'search_hl' and the match list. - get_search_match_hl(wp, &search_hl, (long)(ptr - line), &char_attr); - } - - int eol_attr = char_attr; - if (cul_attr) { - eol_attr = hl_combine_attr(cul_attr, eol_attr); - } - linebuf_attr[off] = eol_attr; - if (wp->w_p_rl) { - --col; - --off; - } else { - ++col; - ++off; - } - ++vcol; - eol_hl_off = 1; - } - // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. - if (wp->w_p_wrap) { - v = wp->w_skipcol; - } else { - v = wp->w_leftcol; - } - - // check if line ends before left margin - if (vcol < v + col - win_col_off(wp)) { - vcol = v + col - win_col_off(wp); - } - // Get rid of the boguscols now, we want to draw until the right - // edge for 'cursorcolumn'. - col -= boguscols; - // boguscols = 0; // Disabled because value never read after this - - if (draw_color_col) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - bool has_virttext = false; - // Make sure alignment is the same regardless - // if listchars=eol:X is used or not. - int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0 - ? 1 : 0); - - if (has_decor) { - has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr, - col + eol_skip); - } - - if (((wp->w_p_cuc - && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off - && (int)wp->w_virtcol < - (long)grid->cols * (row - startrow + 1) + v - && lnum != wp->w_cursor.lnum) - || draw_color_col || line_attr_lowprio || line_attr - || diff_hlf != (hlf_T)0 || has_virttext)) { - int rightmost_vcol = 0; - int i; - - if (wp->w_p_cuc) { - rightmost_vcol = wp->w_virtcol; - } - - if (draw_color_col) { - // determine rightmost colorcolumn to possibly draw - for (i = 0; color_cols[i] >= 0; i++) { - if (rightmost_vcol < color_cols[i]) { - rightmost_vcol = color_cols[i]; - } - } - } - - int cuc_attr = win_hl_attr(wp, HLF_CUC); - int mc_attr = win_hl_attr(wp, HLF_MC); - - int diff_attr = 0; - if (diff_hlf == HLF_TXD) { - diff_hlf = HLF_CHD; - } - if (diff_hlf != 0) { - diff_attr = win_hl_attr(wp, (int)diff_hlf); - } - - int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr); - if (base_attr || line_attr || has_virttext) { - rightmost_vcol = INT_MAX; - } - - int col_stride = wp->w_p_rl ? -1 : 1; - - while (wp->w_p_rl ? col >= 0 : col < grid->cols) { - schar_from_ascii(linebuf_char[off], ' '); - col += col_stride; - if (draw_color_col) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - int col_attr = base_attr; - - if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { - col_attr = cuc_attr; - } else if (draw_color_col && VCOL_HLC == *color_cols) { - col_attr = mc_attr; - // Draw the colorcolumn character. - c = wp->w_p_fcs_chars.colorcol; - schar_from_char(linebuf_char[off], c); - } - - col_attr = hl_combine_attr(col_attr, line_attr); - - linebuf_attr[off] = col_attr; - off += col_stride; - - if (VCOL_HLC >= rightmost_vcol) { - break; - } - - vcol += 1; - } - } - - // TODO(bfredl): integrate with the common beyond-the-end-loop - if (wp->w_buffer->terminal) { - // terminal buffers may need to highlight beyond the end of the - // logical line - int n = wp->w_p_rl ? -1 : 1; - while (col >= 0 && col < grid->cols) { - schar_from_ascii(linebuf_char[off], ' '); - linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol]; - off += n; - vcol += n; - col += n; - } - } - - draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); - grid_put_linebuf(grid, row, 0, col, grid->cols, wp->w_p_rl, wp, - wp->w_hl_attr_normal, false); - row++; - - /* - * Update w_cline_height and w_cline_folded if the cursor line was - * updated (saves a call to plines_win() later). - */ - if (wp == curwin && lnum == curwin->w_cursor.lnum) { - curwin->w_cline_row = startrow; - curwin->w_cline_height = row - startrow; - curwin->w_cline_folded = foldinfo.fi_lines > 0; - curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); - conceal_cursor_used = conceal_cursor_line(curwin); - } - break; - } - - // Show "extends" character from 'listchars' if beyond the line end and - // 'list' is set. - if (wp->w_p_lcs_chars.ext != NUL - && draw_state == WL_LINE - && wp->w_p_list - && !wp->w_p_wrap - && filler_todo <= 0 - && (wp->w_p_rl ? col == 0 : col == grid->cols - 1) - && !has_fold - && (*ptr != NUL - || lcs_eol_one > 0 - || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { - c = wp->w_p_lcs_chars.ext; - char_attr = win_hl_attr(wp, HLF_AT); - mb_c = c; - if (utf_char2len(c) > 1) { - mb_utf8 = true; - u8cc[0] = 0; - c = 0xc0; - } else { - mb_utf8 = false; - } - } - - // advance to the next 'colorcolumn' - if (draw_color_col) { - draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); - } - - // Highlight the cursor column if 'cursorcolumn' is set. But don't - // highlight the cursor position itself. - // Also highlight the 'colorcolumn' if it is different than - // 'cursorcolumn' - // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak' - // options are set - vcol_save_attr = -1; - if ((draw_state == WL_LINE - || draw_state == WL_BRI - || draw_state == WL_SBR) - && !lnum_in_visual_area - && search_attr == 0 - && area_attr == 0 - && filler_todo <= 0) { - if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol - && lnum != wp->w_cursor.lnum) { - vcol_save_attr = char_attr; - char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); - } else if (draw_color_col && VCOL_HLC == *color_cols) { - vcol_save_attr = char_attr; - char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); - } - } - - // Apply lowest-priority line attr now, so everything can override it. - if (draw_state == WL_LINE) { - char_attr = hl_combine_attr(line_attr_lowprio, char_attr); - } - - // Store character to be displayed. - // Skip characters that are left of the screen for 'nowrap'. - vcol_prev = vcol; - if (draw_state < WL_LINE || n_skip <= 0) { - // - // Store the character. - // - if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { - // A double-wide character is: put first half in left cell. - off--; - col--; - } - if (mb_utf8) { - schar_from_cc(linebuf_char[off], mb_c, u8cc); - } else { - schar_from_ascii(linebuf_char[off], (char)c); - } - if (multi_attr) { - linebuf_attr[off] = multi_attr; - multi_attr = 0; - } else { - linebuf_attr[off] = char_attr; - } - - if (utf_char2cells(mb_c) > 1) { - // Need to fill two screen columns. - off++; - col++; - // UTF-8: Put a 0 in the second screen char. - linebuf_char[off][0] = 0; - if (draw_state > WL_NR && filler_todo <= 0) { - vcol++; - } - // When "tocol" is halfway through a character, set it to the end of - // the character, otherwise highlighting won't stop. - if (tocol == vcol) { - tocol++; - } - if (wp->w_p_rl) { - // now it's time to backup one cell - --off; - --col; - } - } - if (wp->w_p_rl) { - --off; - --col; - } else { - ++off; - ++col; - } - } else if (wp->w_p_cole > 0 && is_concealing) { - --n_skip; - ++vcol_off; - if (n_extra > 0) { - vcol_off += n_extra; - } - if (wp->w_p_wrap) { - /* - * Special voodoo required if 'wrap' is on. - * - * Advance the column indicator to force the line - * drawing to wrap early. This will make the line - * take up the same screen space when parts are concealed, - * so that cursor line computations aren't messed up. - * - * To avoid the fictitious advance of 'col' causing - * trailing junk to be written out of the screen line - * we are building, 'boguscols' keeps track of the number - * of bad columns we have advanced. - */ - if (n_extra > 0) { - vcol += n_extra; - if (wp->w_p_rl) { - col -= n_extra; - boguscols -= n_extra; - } else { - col += n_extra; - boguscols += n_extra; - } - n_extra = 0; - n_attr = 0; - } - - if (utf_char2cells(mb_c) > 1) { - // Need to fill two screen columns. - if (wp->w_p_rl) { - --boguscols; - --col; - } else { - ++boguscols; - ++col; - } - } - - if (wp->w_p_rl) { - --boguscols; - --col; - } else { - ++boguscols; - ++col; - } - } else { - if (n_extra > 0) { - vcol += n_extra; - n_extra = 0; - n_attr = 0; - } - } - } else { - --n_skip; - } - - /* Only advance the "vcol" when after the 'number' or 'relativenumber' - * column. */ - if (draw_state > WL_NR - && filler_todo <= 0) { - ++vcol; - } - - if (vcol_save_attr >= 0) { - char_attr = vcol_save_attr; - } - - // restore attributes after "predeces" in 'listchars' - if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { - char_attr = saved_attr3; - } - - // restore attributes after last 'listchars' or 'number' char - if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) { - char_attr = saved_attr2; - } - - /* - * At end of screen line and there is more to come: Display the line - * so far. If there is no more to display it is caught above. - */ - if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols)) - && foldinfo.fi_lines == 0 - && (draw_state != WL_LINE - || *ptr != NUL - || filler_todo > 0 - || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL - && p_extra != at_end_str) - || (n_extra != 0 - && (c_extra != NUL || *p_extra != NUL)))) { - bool wrap = wp->w_p_wrap // Wrapping enabled. - && filler_todo <= 0 // Not drawing diff filler lines. - && lcs_eol_one != -1 // Haven't printed the lcs_eol character. - && row != endrow - 1 // Not the last line being displayed. - && (grid->cols == Columns // Window spans the width of the screen, - || ui_has(kUIMultigrid)) // or has dedicated grid. - && !wp->w_p_rl; // Not right-to-left. - - int draw_col = col - boguscols; - if (filler_todo > 0) { - int index = filler_todo - (filler_lines - n_virt_lines); - if (index > 0) { - int i = (int)kv_size(virt_lines) - index; - assert(i >= 0); - int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset; - draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line, - kHlModeReplace, grid->cols, offset); - } - } else { - draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row); - } - - grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, - wp, wp->w_hl_attr_normal, wrap); - if (wrap) { - ScreenGrid *current_grid = grid; - int current_row = row, dummy_col = 0; // dummy_col unused - grid_adjust(¤t_grid, ¤t_row, &dummy_col); - - // Force a redraw of the first column of the next line. - current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; - - // Remember that the line wraps, used for modeless copy. - current_grid->line_wraps[current_row] = true; - } - - boguscols = 0; - row++; - - /* When not wrapping and finished diff lines, or when displayed - * '$' and highlighting until last column, break here. */ - if ((!wp->w_p_wrap - && filler_todo <= 0 - ) || lcs_eol_one == -1) { - break; - } - - // When the window is too narrow draw all "@" lines. - if (draw_state != WL_LINE && filler_todo <= 0) { - win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT); - row = endrow; - } - - // When line got too long for screen break here. - if (row == endrow) { - ++row; - break; - } - - col = 0; - off = 0; - if (wp->w_p_rl) { - col = grid->cols - 1; // col is not used if breaking! - off += col; - } - - // reset the drawing state for the start of a wrapped line - draw_state = WL_START; - saved_n_extra = n_extra; - saved_p_extra = p_extra; - saved_c_extra = c_extra; - saved_c_final = c_final; - saved_char_attr = char_attr; - n_extra = 0; - lcs_prec_todo = wp->w_p_lcs_chars.prec; - if (filler_todo <= 0) { - need_showbreak = true; - } - filler_todo--; - // When the filler lines are actually below the last line of the - // file, don't draw the line itself, break here. - if (filler_todo == 0 && (wp->w_botfill || end_fill)) { - break; - } - } - } // for every character in the line - - // After an empty line check first word for capital. - if (*skipwhite((char *)line) == NUL) { - capcol_lnum = lnum + 1; - cap_col = 0; - } - - kv_destroy(virt_lines); - xfree(p_extra_free); - return row; -} - -void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, int win_row) -{ - DecorState *state = &decor_state; - int right_pos = max_col; - bool do_eol = state->eol_col > -1; - for (size_t i = 0; i < kv_size(state->active); i++) { - DecorRange *item = &kv_A(state->active, i); - if (!(item->start_row == state->row - && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) { - continue; - } - if (item->win_col == -1) { - if (item->decor.virt_text_pos == kVTRightAlign) { - right_pos -= item->decor.virt_text_width; - item->win_col = right_pos; - } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - item->win_col = state->eol_col; - } else if (item->decor.virt_text_pos == kVTWinCol) { - item->win_col = MAX(item->decor.col + col_off, 0); - } - } - if (item->win_col < 0) { - continue; - } - int col; - if (item->decor.ui_watched) { - // send mark position to UI - col = item->win_col; - WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col }; - kv_push(win_extmark_arr, m); - } - if (kv_size(item->decor.virt_text)) { - col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, - item->decor.hl_mode, max_col, item->win_col - col_off); - } - item->win_col = -2; // deactivate - if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - state->eol_col = col + 1; - } - - *end_col = MAX(*end_col, col); - } -} - -static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, - int vcol) -{ - LineState s = LINE_STATE(""); - int virt_attr = 0; - size_t virt_pos = 0; - - while (col < max_col) { - if (!*s.p) { - if (virt_pos >= kv_size(vt)) { - break; - } - virt_attr = 0; - do { - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_combine_attr(virt_attr, - hl_id > 0 ? syn_id2attr(hl_id) : 0); - virt_pos++; - } while (!s.p && virt_pos < kv_size(vt)); - if (!s.p) { - break; - } - } - if (!*s.p) { - continue; - } - int attr; - bool through = false; - if (hl_mode == kHlModeCombine) { - attr = hl_combine_attr(linebuf_attr[col], virt_attr); - } else if (hl_mode == kHlModeBlend) { - through = (*s.p == ' '); - attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); - } else { - attr = virt_attr; - } - schar_T dummy[2]; - int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], - max_col - col, false, vcol); - // if we failed to emit a char, we still need to advance - cells = MAX(cells, 1); - - for (int c = 0; c < cells; c++) { - linebuf_attr[col++] = attr; - } - vcol += cells; - } - return col; -} - -// Return true if CursorLineSign highlight is to be used. -static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) -{ - return wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR); -} - -/// Return true if CursorLineNr highlight is to be used for the number column. -/// -/// - 'cursorline' must be set -/// - lnum must be the cursor line -/// - 'cursorlineopt' has "number" -/// - don't highlight filler lines (when in diff mode) -/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number -/// itself on the first screenline of the wrapped line, otherwise highlight the number column of -/// all screenlines of the wrapped line. -static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) -{ - return wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR) - && (row == startrow + filler_lines - || (row > startrow + filler_lines - && (wp->w_p_culopt_flags & CULOPT_LINE))); -} - -static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, - sign_attrs_T *sattrs) -{ - sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); - if (num_sattr != NULL) { - // :sign defined with "numhl" highlight. - return num_sattr->sat_numhl; - } - - if (wp->w_p_rnu) { - if (lnum < wp->w_cursor.lnum) { - // Use LineNrAbove - return win_hl_attr(wp, HLF_LNA); - } - if (lnum > wp->w_cursor.lnum) { - // Use LineNrBelow - return win_hl_attr(wp, HLF_LNB); - } - } - - if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - return win_hl_attr(wp, HLF_CLN); - } - - return win_hl_attr(wp, HLF_N); -} - -// Get information needed to display the sign in line 'lnum' in window 'wp'. -// If 'nrcol' is TRUE, the sign is going to be displayed in the number column. -// Otherwise the sign is going to be displayed in the sign column. -// -// @param count max number of signs -// @param[out] n_extrap number of characters from pp_extra to display -// @param sign_idxp Index of the displayed sign -static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[], - int row, int startrow, int filler_lines, int filler_todo, - int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, - char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx) -{ - // Draw cells with the sign value or blank. - *c_extrap = ' '; - *c_finalp = NUL; - if (nrcol) { - *n_extrap = number_width(wp) + 1; - } else { - if (use_cursor_line_sign(wp, lnum)) { - *char_attrp = win_hl_attr(wp, HLF_CLS); - } else { - *char_attrp = win_hl_attr(wp, HLF_SC); - } - *n_extrap = win_signcol_width(wp); - } - - if (row == startrow + filler_lines && filler_todo <= 0) { - sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, sign_idx, wp->w_scwidth); - if (sattr != NULL) { - *pp_extra = sattr->sat_text; - if (*pp_extra != NULL) { - *c_extrap = NUL; - *c_finalp = NUL; - - if (nrcol) { - int n, width = number_width(wp) - 2; - for (n = 0; n < width; n++) { - extra[n] = ' '; - } - extra[n] = NUL; - STRCAT(extra, *pp_extra); - STRCAT(extra, " "); - *pp_extra = extra; - *n_extrap = (int)STRLEN(*pp_extra); - } else { - int symbol_blen = (int)STRLEN(*pp_extra); - - // TODO(oni-link): Is sign text already extended to - // full cell width? - assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra))); - // symbol(s) bytes + (filling spaces) (one byte each) - *n_extrap = symbol_blen + win_signcol_width(wp) - - (int)mb_string2cells((char *)(*pp_extra)); - - assert(extra_size > (size_t)symbol_blen); - memset(extra, ' ', extra_size); - memcpy(extra, *pp_extra, (size_t)symbol_blen); - - *pp_extra = extra; - (*pp_extra)[*n_extrap] = NUL; - } - } - - if (use_cursor_line_sign(wp, lnum) && sattr->sat_culhl > 0) { - *char_attrp = sattr->sat_culhl; - } else { - *char_attrp = sattr->sat_texthl; - } - } - } -} - -/* - * Mirror text "str" for right-left displaying. - * Only works for single-byte characters (e.g., numbers). - */ +/// Mirror text "str" for right-left displaying. +/// Only works for single-byte characters (e.g., numbers). void rl_mirror(char_u *str) { char_u *p1, *p2; char_u t; - for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; ++p1, --p2) { + for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; p1++, p2--) { t = *p1; *p1 = *p2; *p2 = t; } } -/// Mark all status lines and window bars for redraw; used after first :cd -void status_redraw_all(void) -{ - bool is_stl_global = global_stl_height() != 0; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) - || wp->w_winbar_height) { - wp->w_redr_status = true; - redraw_later(wp, VALID); - } - } -} - -/// Marks all status lines and window bars of the current buffer for redraw. -void status_redraw_curbuf(void) +/// Get the length of an item as it will be shown in the status line. +static int status_match_len(expand_T *xp, char_u *s) { - status_redraw_buf(curbuf); -} + int len = 0; -/// Marks all status lines and window bars of the given buffer for redraw. -void status_redraw_buf(buf_T *buf) -{ - bool is_stl_global = global_stl_height() != 0; + int emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) - || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { - wp->w_redr_status = true; - redraw_later(wp, VALID); - } + // Check for menu separators - replace with '|'. + if (emenu && menu_is_separator((char *)s)) { + return 1; } -} -/* - * Redraw all status lines that need to be redrawn. - */ -void redraw_statuslines(void) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_status) { - win_redr_winbar(wp); - win_redr_status(wp); - } - } - if (redraw_tabline) { - draw_tabline(); + while (*s != NUL) { + s += skip_status_match_char(xp, s); + len += ptr2cells((char *)s); + MB_PTR_ADV(s); } + + return len; } -/* - * Redraw all status lines at the bottom of frame "frp". - */ +/// Redraw all status lines at the bottom of frame "frp". void win_redraw_last_status(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { @@ -4615,131 +290,8 @@ void win_redraw_last_status(const frame_T *frp) } } -/// Draw the vertical separator right of window "wp" -static void draw_vsep_win(win_T *wp) -{ - int hl; - int c; - - if (wp->w_vsep_width) { - // draw the vertical separator right of this window - c = fillchar_vsep(wp, &hl); - grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), - W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); - } -} - -/// Draw the horizontal separator below window "wp" -static void draw_hsep_win(win_T *wp) -{ - int hl; - int c; - - if (wp->w_hsep_height) { - // draw the horizontal separator below this window - c = fillchar_hsep(wp, &hl); - grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, - wp->w_wincol, W_ENDCOL(wp), c, c, hl); - } -} - -/// Get the separator connector for specified window corner of window "wp" -static int get_corner_sep_connector(win_T *wp, WindowCorner corner) -{ - // It's impossible for windows to be connected neither vertically nor horizontally - // So if they're not vertically connected, assume they're horizontally connected - if (vsep_connected(wp, corner)) { - if (hsep_connected(wp, corner)) { - return wp->w_p_fcs_chars.verthoriz; - } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { - return wp->w_p_fcs_chars.vertright; - } else { - return wp->w_p_fcs_chars.vertleft; - } - } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { - return wp->w_p_fcs_chars.horizdown; - } else { - return wp->w_p_fcs_chars.horizup; - } -} - -/// Draw separator connecting characters on the corners of window "wp" -static void draw_sep_connectors_win(win_T *wp) -{ - // Don't draw separator connectors unless global statusline is enabled and the window has - // either a horizontal or vertical separator - if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { - return; - } - - int hl = win_hl_attr(wp, HLF_C); - - // Determine which edges of the screen the window is located on so we can avoid drawing separators - // on corners contained in those edges - bool win_at_top; - bool win_at_bottom = wp->w_hsep_height == 0; - bool win_at_left; - bool win_at_right = wp->w_vsep_width == 0; - frame_T *frp; - - for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { - if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { - break; - } - } - win_at_top = frp->fr_parent == NULL; - for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { - if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { - break; - } - } - win_at_left = frp->fr_parent == NULL; - - // Draw the appropriate separator connector in every corner where drawing them is necessary - if (!(win_at_top || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), - wp->w_winrow - 1, wp->w_wincol - 1, hl); - } - if (!(win_at_top || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), - wp->w_winrow - 1, W_ENDCOL(wp), hl); - } - if (!(win_at_bottom || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), - W_ENDROW(wp), wp->w_wincol - 1, hl); - } - if (!(win_at_bottom || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), - W_ENDROW(wp), W_ENDCOL(wp), hl); - } -} - -/// Get the length of an item as it will be shown in the status line. -static int status_match_len(expand_T *xp, char_u *s) -{ - int len = 0; - - int emenu = (xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES); - - // Check for menu separators - replace with '|'. - if (emenu && menu_is_separator((char *)s)) { - return 1; - } - - while (*s != NUL) { - s += skip_status_match_char(xp, s); - len += ptr2cells((char *)s); - MB_PTR_ADV(s); - } - - return len; -} - -/* - * Return the number of characters that should be skipped in a status match. - * These are backslashes used for escaping. Do show backslashes in help tags. - */ +/// Return the number of characters that should be skipped in a status match. +/// These are backslashes used for escaping. Do show backslashes in help tags. static int skip_status_match_char(expand_T *xp, char_u *s) { if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) @@ -4864,7 +416,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int len += l; clen += l; } else { - for (; *s != NUL; ++s) { + for (; *s != NUL; s++) { s += skip_status_match_char(xp, s); clen += ptr2cells((char *)s); if ((l = utfc_ptr2len((char *)s)) > 1) { @@ -4891,7 +443,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int if (i != num_matches) { *(buf + len++) = '>'; - ++clen; + clen++; } buf[len] = NUL; @@ -4944,190 +496,6 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int xfree(buf); } -/// Redraw the status line of window `wp`. -/// -/// If inversion is possible we use it. Else '=' characters are used. -static void win_redr_status(win_T *wp) -{ - int row; - int col; - char_u *p; - int len; - int fillchar; - int attr; - int width; - int this_ru_col; - bool is_stl_global = global_stl_height() > 0; - static int busy = false; - - // May get here recursively when 'statusline' (indirectly) - // invokes ":redrawstatus". Simply ignore the call then. - if (busy - // Also ignore if wildmenu is showing. - || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { - return; - } - busy = true; - - wp->w_redr_status = false; - if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { - // no status line, either global statusline is enabled or the window is a last window - redraw_cmdline = true; - } else if (!redrawing()) { - // Don't redraw right now, do it later. Don't update status line when - // popup menu is visible and may be drawn over it - wp->w_redr_status = true; - } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { - // redraw custom status line - redraw_custom_statusline(wp); - } else { - fillchar = fillchar_status(&attr, wp); - width = is_stl_global ? Columns : wp->w_width; - - get_trans_bufname(wp->w_buffer); - p = NameBuff; - len = (int)STRLEN(p); - - if (bt_help(wp->w_buffer) - || wp->w_p_pvw - || bufIsChanged(wp->w_buffer) - || wp->w_buffer->b_p_ro) { - *(p + len++) = ' '; - } - if (bt_help(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); - len += (int)STRLEN(p + len); - } - if (wp->w_p_pvw) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); - len += (int)STRLEN(p + len); - } - if (bufIsChanged(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]"); - len += (int)STRLEN(p + len); - } - if (wp->w_buffer->b_p_ro) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); - // len += (int)STRLEN(p + len); // dead assignment - } - - this_ru_col = ru_col - (Columns - width); - if (this_ru_col < (width + 1) / 2) { - this_ru_col = (width + 1) / 2; - } - if (this_ru_col <= 1) { - p = (char_u *)"<"; // No room for file name! - len = 1; - } else { - int clen = 0, i; - - // Count total number of display cells. - clen = (int)mb_string2cells((char *)p); - - // Find first character that will fit. - // Going from start to end is much faster for DBCS. - for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; - i += utfc_ptr2len((char *)p + i)) { - clen -= utf_ptr2cells((char *)p + i); - } - len = clen; - if (i > 0) { - p = p + i - 1; - *p = '<'; - ++len; - } - } - - row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); - col = is_stl_global ? 0 : wp->w_wincol; - grid_puts(&default_grid, p, row, col, attr); - grid_fill(&default_grid, row, row + 1, len + col, - this_ru_col + col, fillchar, fillchar, attr); - - if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL) - && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) { - grid_puts(&default_grid, NameBuff, row, - (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr); - } - - win_redr_ruler(wp, true); - } - - /* - * May need to draw the character below the vertical separator. - */ - if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { - if (stl_connected(wp)) { - fillchar = fillchar_status(&attr, wp); - } else { - fillchar = fillchar_vsep(wp, &attr); - } - grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); - } - busy = FALSE; -} - -/* - * Redraw the status line according to 'statusline' and take care of any - * errors encountered. - */ -static void redraw_custom_statusline(win_T *wp) -{ - static bool entered = false; - int saved_did_emsg = did_emsg; - - /* When called recursively return. This can happen when the statusline - * contains an expression that triggers a redraw. */ - if (entered) { - return; - } - entered = true; - - did_emsg = false; - win_redr_custom(wp, false, false); - if (did_emsg) { - // When there is an error disable the statusline, otherwise the - // display is messed up with errors and a redraw triggers the problem - // again and again. - set_string_option_direct("statusline", -1, "", - OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); - } - did_emsg |= saved_did_emsg; - entered = false; -} - -static void win_redr_winbar(win_T *wp) -{ - static bool entered = false; - - // Return when called recursively. This can happen when the winbar contains an expression - // that triggers a redraw. - if (entered) { - return; - } - entered = true; - - if (wp->w_winbar_height == 0 || !redrawing()) { - // Do nothing. - } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { - int saved_did_emsg = did_emsg; - - did_emsg = false; - win_redr_custom(wp, true, false); - if (did_emsg) { - // When there is an error disable the winbar, otherwise the - // display is messed up with errors and a redraw triggers the problem - // again and again. - set_string_option_direct("winbar", -1, "", - OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); - } - did_emsg |= saved_did_emsg; - } - entered = false; -} - /// Only call if (wp->w_vsep_width != 0). /// /// @return true if the status line of window "wp" is connected to the status @@ -5152,77 +520,6 @@ bool stl_connected(win_T *wp) return false; } -/// Check if horizontal separator of window "wp" at specified window corner is connected to the -/// horizontal separator of another window -/// Assumes global statusline is enabled -static bool hsep_connected(win_T *wp, WindowCorner corner) -{ - bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); - int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) - ? wp->w_winrow - 1 : W_ENDROW(wp); - frame_T *fr = wp->w_frame; - - while (fr->fr_parent != NULL) { - if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { - fr = before ? fr->fr_prev : fr->fr_next; - break; - } - fr = fr->fr_parent; - } - if (fr->fr_parent == NULL) { - return false; - } - while (fr->fr_layout != FR_LEAF) { - fr = fr->fr_child; - if (fr->fr_parent->fr_layout == FR_ROW && before) { - while (fr->fr_next != NULL) { - fr = fr->fr_next; - } - } else { - while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { - fr = fr->fr_next; - } - } - } - - return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); -} - -/// Check if vertical separator of window "wp" at specified window corner is connected to the -/// vertical separator of another window -static bool vsep_connected(win_T *wp, WindowCorner corner) -{ - bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); - int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) - ? wp->w_wincol - 1 : W_ENDCOL(wp); - frame_T *fr = wp->w_frame; - - while (fr->fr_parent != NULL) { - if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { - fr = before ? fr->fr_prev : fr->fr_next; - break; - } - fr = fr->fr_parent; - } - if (fr->fr_parent == NULL) { - return false; - } - while (fr->fr_layout != FR_LEAF) { - fr = fr->fr_child; - if (fr->fr_parent->fr_layout == FR_COL && before) { - while (fr->fr_next != NULL) { - fr = fr->fr_next; - } - } else { - while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { - fr = fr->fr_next; - } - } - } - - return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); -} - /// Get the value to show for the language mappings, active 'keymap'. /// /// @param fmt format string containing one %s item @@ -5266,7 +563,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) /// Redraw the status line, window bar or ruler of window "wp". /// When "wp" is NULL redraw the tab pages line from 'tabline'. -static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) { static bool entered = false; int attr; @@ -5290,9 +587,9 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) ScreenGrid *grid = &default_grid; - /* There is a tiny chance that this gets called recursively: When - * redrawing a status line triggers redrawing the ruler or tabline. - * Avoid trouble by not allowing recursion. */ + // There is a tiny chance that this gets called recursively: When + // redrawing a status line triggers redrawing the ruler or tabline. + // Avoid trouble by not allowing recursion. if (entered) { return; } @@ -5389,14 +686,14 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) goto theend; } - /* Temporarily reset 'cursorbind', we don't want a side effect from moving - * the cursor away and back. */ + // Temporarily reset 'cursorbind', we don't want a side effect from moving + // the cursor away and back. ewp = wp == NULL ? curwin : wp; p_crb_save = ewp->w_p_crb; - ewp->w_p_crb = FALSE; + ewp->w_p_crb = false; - /* Make a copy, because the statusline may include a function call that - * might change the option value and free the memory. */ + // Make a copy, because the statusline may include a function call that + // might change the option value and free the memory. stl = vim_strsave(stl); width = build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox, @@ -5417,9 +714,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) } buf[len] = NUL; - /* - * Draw each snippet with the specified highlighting. - */ + // Draw each snippet with the specified highlighting. grid_puts_line_start(grid, row); curattr = attr; @@ -5483,134 +778,23 @@ theend: entered = false; } -static void win_redr_border(win_T *wp) -{ - wp->w_redr_border = false; - if (!(wp->w_floating && wp->w_float_config.border)) { - return; - } - - ScreenGrid *grid = &wp->w_grid_alloc; - - schar_T *chars = wp->w_float_config.border_chars; - int *attrs = wp->w_float_config.border_attr; - - int *adj = wp->w_border_adj; - int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; - - char* title = wp->w_float_config.title; - size_t n_title = wp->w_float_config.n_title; - stl_hlrec_t* title_hl = wp->w_float_config.title_hl; - - int m8[MAX_MCO + 1]; - int cc; - int len; - int t_attr = title_hl != NULL && title_hl->userhl - ? syn_id2attr(title_hl->userhl) - : 0; - t_attr = hl_combine_attr(attrs[1], t_attr); - - int title_pos = 2; - switch (wp->w_float_config.title_pos) { - case kTitleLeft: - title_pos = 2; - break; - case kTitleRight: - title_pos = icol - 2 - vim_strsize(title); - break; - case kTitleCenter: - title_pos = (icol - vim_strsize(title)) / 2 - 1; - break; - } - title_pos = title_pos < 2 ? 2 : title_pos; - - if (adj[0]) { - grid_puts_line_start(grid, 0); - if (adj[3]) { - grid_put_schar(grid, 0, 0, chars[0], attrs[0]); - } - for (int i = 0; i < icol; i++) { - schar_T ch; - int attr; - // Draw the title if in the correct position. - if (i > title_pos && n_title > 0 && i < icol - 2) { - cc = utfc_ptr2char((char_u*) title, m8); - len = utfc_ptr2len(title); - n_title -= len; - title += len; - - while (title_hl != NULL && - (title_hl + 1)->start != NULL && - (title_hl + 1)->start < title) { - ++ title_hl; - t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl)); - } - - schar_from_cc(ch, cc, m8); - attr = t_attr; - } else { - memcpy(ch, chars[1], sizeof(schar_T)); - attr = attrs[1]; - } - grid_put_schar(grid, 0, i + adj[3], ch, attr); - } - if (adj[1]) { - grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); - } - grid_puts_line_flush(false); - } - - for (int i = 0; i < irow; i++) { - if (adj[3]) { - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); - grid_puts_line_flush(false); - } - if (adj[1]) { - int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); - grid_puts_line_flush(false); - } - } - - if (adj[2]) { - grid_puts_line_start(grid, irow + adj[0]); - if (adj[3]) { - grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); - } - for (int i = 0; i < icol; i++) { - int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; - grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); - } - if (adj[1]) { - grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); - } - grid_puts_line_flush(false); - } -} - -/* - * Prepare for 'hlsearch' highlighting. - */ -static void start_search_hl(void) +/// Prepare for 'hlsearch' highlighting. +void start_search_hl(void) { if (p_hls && !no_hlsearch) { end_search_hl(); // just in case it wasn't called before - last_pat_prog(&search_hl.rm); + last_pat_prog(&screen_search_hl.rm); // Set the time limit to 'redrawtime'. - search_hl.tm = profile_setlimit(p_rdt); + screen_search_hl.tm = profile_setlimit(p_rdt); } } -/* - * Clean up for 'hlsearch' highlighting. - */ -static void end_search_hl(void) +/// Clean up for 'hlsearch' highlighting. +void end_search_hl(void) { - if (search_hl.rm.regprog != NULL) { - vim_regfree(search_hl.rm.regprog); - search_hl.rm.regprog = NULL; + if (screen_search_hl.rm.regprog != NULL) { + vim_regfree(screen_search_hl.rm.regprog); + screen_search_hl.rm.regprog = NULL; } } @@ -5630,105 +814,6 @@ void check_for_delay(bool check_msg_scroll) } } -/// Resize the screen to Rows and Columns. -/// -/// Allocate default_grid.chars[] and other grid arrays. -/// -/// There may be some time between setting Rows and Columns and (re)allocating -/// default_grid arrays. This happens when starting up and when -/// (manually) changing the screen size. Always use default_grid.rows and -/// default_grid.Columns to access items in default_grid.chars[]. Use Rows -/// and Columns for positioning text etc. where the final size of the screen is -/// needed. -void screenalloc(void) -{ - // It's possible that we produce an out-of-memory message below, which - // will cause this function to be called again. To break the loop, just - // return here. - if (resizing) { - return; - } - resizing = true; - - int retry_count = 0; - -retry: - // Allocation of the screen buffers is done only when the size changes and - // when Rows and Columns have been set and we have started doing full - // screen stuff. - if ((default_grid.chars != NULL - && Rows == default_grid.rows - && Columns == default_grid.cols - ) - || Rows == 0 - || Columns == 0 - || (!full_screen && default_grid.chars == NULL)) { - resizing = false; - return; - } - - /* - * Note that the window sizes are updated before reallocating the arrays, - * thus we must not redraw here! - */ - ++RedrawingDisabled; - - // win_new_screensize will recompute floats position, but tell the - // compositor to not redraw them yet - ui_comp_set_screen_valid(false); - if (msg_grid.chars) { - msg_grid_invalid = true; - } - - win_new_screensize(); // fit the windows in the new sized screen - - comp_col(); // recompute columns for shown command and ruler - - // We're changing the size of the screen. - // - Allocate new arrays for default_grid - // - Move lines from the old arrays into the new arrays, clear extra - // lines (unless the screen is going to be cleared). - // - Free the old arrays. - // - // If anything fails, make grid arrays NULL, so we don't do anything! - // Continuing with the old arrays may result in a crash, because the - // size is wrong. - - grid_alloc(&default_grid, Rows, Columns, true, true); - StlClickDefinition *new_tab_page_click_defs = - xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs)); - - stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - xfree(tab_page_click_defs); - - tab_page_click_defs = new_tab_page_click_defs; - tab_page_click_defs_size = Columns; - - default_grid.comp_height = Rows; - default_grid.comp_width = Columns; - - default_grid.row_offset = 0; - default_grid.col_offset = 0; - default_grid.handle = DEFAULT_GRID_HANDLE; - - must_redraw = CLEAR; // need to clear the screen later - - RedrawingDisabled--; - - /* - * Do not apply autocommands more than 3 times to avoid an endless loop - * in case applying autocommands always changes Rows or Columns. - */ - if (starting == 0 && ++retry_count <= 3) { - apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); - // In rare cases, autocommands may have altered Rows or Columns, - // jump back to check if we need to allocate the screen again. - goto retry; - } - - resizing = false; -} - /// Clear status line, window bar or tab page line click definition table /// /// @param[out] tpcd Table to clear. @@ -5745,67 +830,6 @@ void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click } } -void screenclear(void) -{ - check_for_delay(false); - screenalloc(); // allocate screen buffers if size changed - - int i; - - if (starting == NO_SCREEN || default_grid.chars == NULL) { - return; - } - - // blank out the default grid - for (i = 0; i < default_grid.rows; i++) { - grid_clear_line(&default_grid, default_grid.line_offset[i], - default_grid.cols, true); - default_grid.line_wraps[i] = false; - } - - ui_call_grid_clear(1); // clear the display - ui_comp_set_screen_valid(true); - - clear_cmdline = false; - mode_displayed = false; - - redraw_all_later(NOT_VALID); - redraw_cmdline = true; - redraw_tabline = true; - redraw_popupmenu = true; - pum_invalidate(); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - wp->w_redr_type = CLEAR; - } - } - if (must_redraw == CLEAR) { - must_redraw = NOT_VALID; // no need to clear again - } - compute_cmdrow(); - msg_row = cmdline_row; // put cursor on last line for messages - msg_col = 0; - msg_scrolled = 0; // can't scroll back - msg_didany = false; - msg_didout = false; - if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) { - grid_invalidate(&msg_grid); - msg_grid_validate(); - msg_grid_invalid = false; - clear_cmdline = true; - } -} - -/// Copy part of a grid line for vertically split window. -static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) -{ - unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col); - unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col); - - memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T)); - memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T)); -} - /// Set cursor to its position in the current window. void setcursor(void) { @@ -5835,9 +859,9 @@ void setcursor_mayforce(bool force) } } -/// Scroll 'line_count' lines at 'row' in window 'wp'. +/// Scroll `line_count` lines at 'row' in window 'wp'. /// -/// Positive `line_count' means scrolling down, so that more space is available +/// Positive `line_count` means scrolling down, so that more space is available /// at 'row'. Negative `line_count` implies deleting lines at `row`. void win_scroll_lines(win_T *wp, int row, int line_count) { @@ -5859,126 +883,29 @@ void win_scroll_lines(win_T *wp, int row, int line_count) } } -/* - * The rest of the routines in this file perform screen manipulations. The - * given operation is performed physically on the screen. The corresponding - * change is also made to the internal screen image. In this way, the editor - * anticipates the effect of editing changes on the appearance of the screen. - * That way, when we call screenupdate a complete redraw isn't usually - * necessary. Another advantage is that we can keep adding code to anticipate - * screen changes, and in the meantime, everything still works. - */ - -/// insert lines on the screen and move the existing lines down -/// 'line_count' is the number of lines to be inserted. -/// 'end' is the line after the scrolled part. Normally it is Rows. -/// 'col' is the column from with we start inserting. -// -/// 'row', 'col' and 'end' are relative to the start of the region. -void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) -{ - int i; - int j; - unsigned temp; - - int row_off = 0; - grid_adjust(&grid, &row_off, &col); - row += row_off; - end += row_off; - - if (line_count <= 0) { - return; - } - - // Shift line_offset[] line_count down to reflect the inserted lines. - // Clear the inserted lines. - for (i = 0; i < line_count; i++) { - if (width != grid->cols) { - // need to copy part of a line - j = end - 1 - i; - while ((j -= line_count) >= row) { - linecopy(grid, j + line_count, j, col, width); - } - j += line_count; - grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; - } else { - j = end - 1 - i; - temp = (unsigned)grid->line_offset[j]; - while ((j -= line_count) >= row) { - grid->line_offset[j + line_count] = grid->line_offset[j]; - grid->line_wraps[j + line_count] = grid->line_wraps[j]; - } - grid->line_offset[j + line_count] = temp; - grid->line_wraps[j + line_count] = false; - grid_clear_line(grid, temp, grid->cols, false); - } - } - - if (!grid->throttled) { - ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0); - } -} - -/// delete lines on the screen and move lines up. -/// 'end' is the line after the scrolled part. Normally it is Rows. -/// When scrolling region used 'off' is the offset from the top for the region. -/// 'row' and 'end' are relative to the start of the region. -void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) +/// @return true when postponing displaying the mode message: when not redrawing +/// or inside a mapping. +bool skip_showmode(void) { - int j; - int i; - unsigned temp; - - int row_off = 0; - grid_adjust(&grid, &row_off, &col); - row += row_off; - end += row_off; - - if (line_count <= 0) { - return; - } - - // Now shift line_offset[] line_count up to reflect the deleted lines. - // Clear the inserted lines. - for (i = 0; i < line_count; i++) { - if (width != grid->cols) { - // need to copy part of a line - j = row + i; - while ((j += line_count) <= end - 1) { - linecopy(grid, j - line_count, j, col, width); - } - j -= line_count; - grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; - } else { - // whole width, moving the line pointers is faster - j = row + i; - temp = (unsigned)grid->line_offset[j]; - while ((j += line_count) <= end - 1) { - grid->line_offset[j - line_count] = grid->line_offset[j]; - grid->line_wraps[j - line_count] = grid->line_wraps[j]; - } - grid->line_offset[j - line_count] = temp; - grid->line_wraps[j - line_count] = false; - grid_clear_line(grid, temp, grid->cols, false); - } - } - - if (!grid->throttled) { - ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0); + // Call char_avail() only when we are going to show something, because it + // takes a bit of time. redrawing() may also call char_avail(). + if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) { + redraw_mode = true; // show mode later + return true; } + return false; } -// Show the current mode and ruler. -// -// If clear_cmdline is true, clear the rest of the cmdline. -// If clear_cmdline is false there may be a message there that needs to be -// cleared only if a mode is shown. -// Return the length of the message (0 if no message). +/// Show the current mode and ruler. +/// +/// If clear_cmdline is true, clear the rest of the cmdline. +/// If clear_cmdline is false there may be a message there that needs to be +/// cleared only if a mode is shown. +/// If redraw_mode is true show or clear the mode. +/// @return the length of the message (0 if no message). int showmode(void) { - int need_clear; + bool need_clear; int length = 0; int do_mode; int attr; @@ -5999,12 +926,8 @@ int showmode(void) || restart_edit != NUL || VIsual_active)); if (do_mode || reg_recording != 0) { - // Don't show mode right now, when not redrawing or inside a mapping. - // Call char_avail() only when we are going to show something, because - // it takes a bit of time. - if (!redrawing() || (char_avail() && !KeyTyped) || msg_silent != 0) { - redraw_cmdline = true; // show mode later - return 0; + if (skip_showmode()) { + return 0; // show mode later } bool nwr_save = need_wait_return; @@ -6128,7 +1051,7 @@ int showmode(void) msg_puts_attr(" --", attr); } - need_clear = TRUE; + need_clear = true; } if (reg_recording != 0 && edit_submode == NULL // otherwise it gets too long @@ -6138,7 +1061,7 @@ int showmode(void) } mode_displayed = true; - if (need_clear || clear_cmdline) { + if (need_clear || clear_cmdline || redraw_mode) { msg_clr_eos(); } msg_didout = false; // overwrite this message @@ -6150,6 +1073,9 @@ int showmode(void) } else if (clear_cmdline && msg_silent == 0) { // Clear the whole command line. Will reset "clear_cmdline". msg_clr_cmdline(); + } else if (redraw_mode) { + msg_pos_mode(); + msg_clr_eos(); } // NB: also handles clearing the showmode if it was empty or disabled @@ -6167,14 +1093,13 @@ int showmode(void) win_redr_ruler(last, true); } redraw_cmdline = false; + redraw_mode = false; clear_cmdline = false; return length; } -/* - * Position for a mode message. - */ +/// Position for a mode message. static void msg_pos_mode(void) { msg_col = 0; @@ -6216,19 +1141,13 @@ static void recording_mode(int attr) { msg_puts_attr(_("recording"), attr); if (!shortmess(SHM_RECORDING)) { - char s[10]; - int len = (*utf_char2len)(reg_recording); - utf_char2bytes(reg_recording, s + 2); - s[0] = ' '; - s[1] = '@'; - s[len + 2] = 0; + char s[4]; + snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); msg_puts_attr(s, attr); } } -/* - * Draw the tab pages line at the top of the Vim window. - */ +/// Draw the tab pages line at the top of the Vim window. void draw_tabline(void) { int tabcount = 0; @@ -6246,8 +1165,7 @@ void draw_tabline(void) int attr_fill = HL_ATTR(HLF_TPF); char_u *p; int room; - int use_sep_chars = (t_colors < 8 - ); + int use_sep_chars = (t_colors < 8); if (default_grid.chars == NULL) { return; @@ -6281,7 +1199,7 @@ void draw_tabline(void) did_emsg |= saved_did_emsg; } else { FOR_ALL_TABS(tp) { - ++tabcount; + tabcount++; } if (tabcount > 0) { @@ -6403,7 +1321,7 @@ void draw_tabline(void) redraw_tabline = false; } -void ui_ext_tabline_update(void) +static void ui_ext_tabline_update(void) { Arena arena = ARENA_EMPTY; arena_start(&arena, &ui_ext_fixblk); @@ -6450,10 +1368,6 @@ void ui_ext_tabline_update(void) arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); } -/* - * Get buffer name for "buf" into NameBuff[]. - * Takes care of special buffer names and translates special characters. - */ void get_trans_bufname(buf_T *buf) { if (buf_spname(buf) != NULL) { @@ -6464,9 +1378,7 @@ void get_trans_bufname(buf_T *buf) trans_characters((char *)NameBuff, MAXPATHL); } -/* - * Get the character to use in a status line. Get its attributes in "*attr". - */ +/// Get the character to use in a status line. Get its attributes in "*attr". int fillchar_status(int *attr, win_T *wp) { int fill; @@ -6494,7 +1406,7 @@ int fillchar_status(int *attr, win_T *wp) /// Get the character to use in a separator between vertically split windows. /// Get its attributes in "*attr". -static int fillchar_vsep(win_T *wp, int *attr) +int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); return wp->w_p_fcs_chars.vert; @@ -6502,59 +1414,26 @@ static int fillchar_vsep(win_T *wp, int *attr) /// Get the character to use in a separator between horizontally split windows. /// Get its attributes in "*attr". -static int fillchar_hsep(win_T *wp, int *attr) +int fillchar_hsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); return wp->w_p_fcs_chars.horiz; } -/* - * Return TRUE if redrawing should currently be done. - */ -int redrawing(void) +/// Return true if redrawing should currently be done. +bool redrawing(void) { return !RedrawingDisabled && !(p_lz && char_avail() && !KeyTyped && !do_redraw); } -/* - * Return TRUE if printing messages should currently be done. - */ -int messaging(void) +/// Return true if printing messages should currently be done. +bool messaging(void) { return !(p_lz && char_avail() && !KeyTyped) && ui_has_messages(); } -/// Show current status info in ruler and various other places -/// -/// @param always if false, only show ruler if position has changed. -void showruler(bool always) -{ - if (!always && !redrawing()) { - return; - } - if ((*p_stl != NUL || *curwin->w_p_stl != NUL) - && (curwin->w_status_height || global_stl_height())) { - redraw_custom_statusline(curwin); - } else { - win_redr_ruler(curwin, always); - } - if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) { - win_redr_winbar(curwin); - } - - if (need_maketitle - || (p_icon && (stl_syntax & STL_IN_ICON)) - || (p_title && (stl_syntax & STL_IN_TITLE))) { - maketitle(); - } - // Redraw the tab pages line if needed. - if (redraw_tabline) { - draw_tabline(); - } -} - -static void win_redr_ruler(win_T *wp, bool always) +void win_redr_ruler(win_T *wp, bool always) { bool is_stl_global = global_stl_height() > 0; static bool did_show_ext_ruler = false; @@ -6564,10 +1443,8 @@ static void win_redr_ruler(win_T *wp, bool always) return; } - /* - * Check if cursor.lnum is valid, since win_redr_ruler() may be called - * after deleting lines, before cursor.lnum is corrected. - */ + // Check if cursor.lnum is valid, since win_redr_ruler() may be called + // after deleting lines, before cursor.lnum is corrected. if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { return; } @@ -6595,9 +1472,7 @@ static void win_redr_ruler(win_T *wp, bool always) empty_line = true; } - /* - * Only draw the ruler when something changed. - */ + // Only draw the ruler when something changed. validate_virtcol_win(wp); if (redraw_cmdline || always @@ -6651,23 +1526,19 @@ static void win_redr_ruler(win_T *wp, bool always) #define RULER_BUF_LEN 70 char buffer[RULER_BUF_LEN]; - /* - * Some sprintfs return the length, some return a pointer. - * To avoid portability problems we use strlen() here. - */ + // Some sprintfs return the length, some return a pointer. + // To avoid portability problems we use strlen() here. vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", - (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L - : (int64_t)wp->w_cursor.lnum); + (wp->w_buffer->b_ml.ml_flags & + ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum); size_t len = STRLEN(buffer); col_print(buffer + len, RULER_BUF_LEN - len, empty_line ? 0 : (int)wp->w_cursor.col + 1, (int)virtcol + 1); - /* - * Add a "50%" if there is room for it. - * On the last line, don't print in the last column (scrolls the - * screen up on some terminals). - */ + // Add a "50%" if there is room for it. + // On the last line, don't print in the last column (scrolls the + // screen up on some terminals). int i = (int)STRLEN(buffer); get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); int o = i + vim_strsize(buffer + i + 1); @@ -6731,11 +1602,49 @@ static void win_redr_ruler(win_T *wp, bool always) } } -/* - * Return the width of the 'number' and 'relativenumber' column. - * Caller may need to check if 'number' or 'relativenumber' is set. - * Otherwise it depends on 'numberwidth' and the line count. - */ +#define COL_RULER 17 // columns needed by standard ruler + +/// Compute columns for ruler and shown command. 'sc_col' is also used to +/// decide what the maximum length of a message on the status line can be. +/// If there is a status line for the last window, 'sc_col' is independent +/// of 'ru_col'. +void comp_col(void) +{ + int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); + + sc_col = 0; + ru_col = 0; + if (p_ru) { + ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; + // no last status line, adjust sc_col + if (!last_has_status) { + sc_col = ru_col; + } + } + if (p_sc) { + sc_col += SHOWCMD_COLS; + if (!p_ru || last_has_status) { // no need for separating space + sc_col++; + } + } + assert(sc_col >= 0 + && INT_MIN + sc_col <= Columns); + sc_col = Columns - sc_col; + assert(ru_col >= 0 + && INT_MIN + ru_col <= Columns); + ru_col = Columns - ru_col; + if (sc_col <= 0) { // screen too narrow, will become a mess + sc_col = 1; + } + if (ru_col <= 0) { + ru_col = 1; + } + set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); +} + +/// Return the width of the 'number' and 'relativenumber' column. +/// Caller may need to check if 'number' or 'relativenumber' is set. +/// Otherwise it depends on 'numberwidth' and the line count. int number_width(win_T *wp) { int n; @@ -6757,7 +1666,7 @@ int number_width(win_T *wp) n = 0; do { lnum /= 10; - ++n; + n++; } while (lnum > 0); // 'numberwidth' gives the minimal width plus one @@ -6776,155 +1685,283 @@ int number_width(win_T *wp) return n; } -/// Used when 'cursorlineopt' contains "screenline": compute the margins between -/// which the highlighting is used. -static void margin_columns_win(win_T *wp, int *left_col, int *right_col) +/// Calls mb_cptr2char_adv(p) and returns the character. +/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used. +/// Returns 0 for invalid hex or invalid UTF-8 byte. +static int get_encoded_char_adv(char_u **p) { - // cache previous calculations depending on w_virtcol - static int saved_w_virtcol; - static win_T *prev_wp; - static int prev_left_col; - static int prev_right_col; - static int prev_col_off; - - int cur_col_off = win_col_off(wp); - int width1; - int width2; - - if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp - && prev_col_off == cur_col_off) { - *right_col = prev_right_col; - *left_col = prev_left_col; - return; - } - - width1 = wp->w_width - cur_col_off; - width2 = width1 + win_col_off2(wp); - - *left_col = 0; - *right_col = width1; + char_u *s = *p; - if (wp->w_virtcol >= (colnr_T)width1) { - *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; - } - if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { - *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; + if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { + int64_t num = 0; + int bytes; + int n; + for (bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { + *p += 2; + n = hexhex2nr(*p); + if (n < 0) { + return 0; + } + num = num * 256 + n; + } + *p += 2; + return (int)num; } - // cache values - prev_left_col = *left_col; - prev_right_col = *right_col; - prev_wp = wp; - saved_w_virtcol = wp->w_virtcol; - prev_col_off = cur_col_off; + // TODO(bfredl): use schar_T representation and utfc_ptr2len + int clen = utf_ptr2len((char *)s); + int c = mb_cptr2char_adv((const char_u **)p); + if (clen == 1 && c > 127) { // Invalid UTF-8 byte + return 0; + } + return c; } -/// Set dimensions of the Nvim application "screen". -void screen_resize(int width, int height) -{ - // Avoid recursiveness, can happen when setting the window size causes - // another window-changed signal. - if (updating_screen || resizing_screen) { - return; - } +/// Handle setting 'listchars' or 'fillchars'. +/// Assume monocell characters +/// +/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs +/// @return error message, NULL if it's OK. +char *set_chars_option(win_T *wp, char_u **varp, bool set) +{ + int round, i, len, len2, entries; + char_u *p, *s; + int c1; + int c2 = 0; + int c3 = 0; + char_u *last_multispace = NULL; // Last occurrence of "multispace:" + char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" + int multispace_len = 0; // Length of lcs-multispace string + int lead_multispace_len = 0; // Length of lcs-leadmultispace string + + struct chars_tab { + int *cp; ///< char value + char *name; ///< char id + int def; ///< default value + }; + struct chars_tab *tab; + + // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case. + struct chars_tab fcs_tab[] = { + { &wp->w_p_fcs_chars.stl, "stl", ' ' }, + { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, + { &wp->w_p_fcs_chars.wbr, "wbr", ' ' }, + { &wp->w_p_fcs_chars.horiz, "horiz", char2cells(0x2500) == 1 ? 0x2500 : '-' }, // ─ + { &wp->w_p_fcs_chars.horizup, "horizup", char2cells(0x2534) == 1 ? 0x2534 : '-' }, // ┴ + { &wp->w_p_fcs_chars.horizdown, "horizdown", char2cells(0x252c) == 1 ? 0x252c : '-' }, // ┬ + { &wp->w_p_fcs_chars.vert, "vert", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │ + { &wp->w_p_fcs_chars.vertleft, "vertleft", char2cells(0x2524) == 1 ? 0x2524 : '|' }, // ┤ + { &wp->w_p_fcs_chars.vertright, "vertright", char2cells(0x251c) == 1 ? 0x251c : '|' }, // ├ + { &wp->w_p_fcs_chars.verthoriz, "verthoriz", char2cells(0x253c) == 1 ? 0x253c : '+' }, // ┼ + { &wp->w_p_fcs_chars.fold, "fold", char2cells(0x00b7) == 1 ? 0x00b7 : '-' }, // · + { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, + { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, + { &wp->w_p_fcs_chars.foldsep, "foldsep", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │ + { &wp->w_p_fcs_chars.diff, "diff", '-' }, + { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, + { &wp->w_p_fcs_chars.eob, "eob", '~' }, + { &wp->w_p_fcs_chars.colorcol, "colorcol", ' ' }, + }; + struct chars_tab lcs_tab[] = { + { &wp->w_p_lcs_chars.eol, "eol", NUL }, + { &wp->w_p_lcs_chars.ext, "extends", NUL }, + { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL }, + { &wp->w_p_lcs_chars.prec, "precedes", NUL }, + { &wp->w_p_lcs_chars.space, "space", NUL }, + { &wp->w_p_lcs_chars.tab2, "tab", NUL }, + { &wp->w_p_lcs_chars.lead, "lead", NUL }, + { &wp->w_p_lcs_chars.trail, "trail", NUL }, + { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, + }; - if (width < 0 || height < 0) { // just checking... - return; + if (varp == &p_lcs || varp == &wp->w_p_lcs) { + tab = lcs_tab; + entries = ARRAY_SIZE(lcs_tab); + if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { + varp = &p_lcs; + } + } else { + tab = fcs_tab; + entries = ARRAY_SIZE(fcs_tab); + if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { + varp = &p_fcs; + } } - if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { - // postpone the resizing - State = MODE_SETWSIZE; - return; - } + // first round: check for valid value, second round: assign values + for (round = 0; round <= (set ? 1 : 0); round++) { + if (round > 0) { + // After checking that the value is valid: set defaults + for (i = 0; i < entries; i++) { + if (tab[i].cp != NULL) { + *(tab[i].cp) = tab[i].def; + } + } + if (varp == &p_lcs || varp == &wp->w_p_lcs) { + wp->w_p_lcs_chars.tab1 = NUL; + wp->w_p_lcs_chars.tab3 = NUL; - /* curwin->w_buffer can be NULL when we are closing a window and the - * buffer has already been closed and removing a scrollbar causes a resize - * event. Don't resize then, it will happen after entering another buffer. - */ - if (curwin->w_buffer == NULL) { - return; - } + xfree(wp->w_p_lcs_chars.multispace); + if (multispace_len > 0) { + wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int)); + wp->w_p_lcs_chars.multispace[multispace_len] = NUL; + } else { + wp->w_p_lcs_chars.multispace = NULL; + } - resizing_screen = true; + xfree(wp->w_p_lcs_chars.leadmultispace); + if (lead_multispace_len > 0) { + wp->w_p_lcs_chars.leadmultispace + = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int)); + wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL; + } else { + wp->w_p_lcs_chars.leadmultispace = NULL; + } + } + } + p = *varp; + while (*p) { + for (i = 0; i < entries; i++) { + len = (int)STRLEN(tab[i].name); + if (STRNCMP(p, tab[i].name, len) == 0 + && p[len] == ':' + && p[len + 1] != NUL) { + c2 = c3 = 0; + s = p + len + 1; + c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { + if (*s == NUL) { + return e_invarg; + } + c2 = get_encoded_char_adv(&s); + if (c2 == 0 || char2cells(c2) > 1) { + return e_invarg; + } + if (!(*s == ',' || *s == NUL)) { + c3 = get_encoded_char_adv(&s); + if (c3 == 0 || char2cells(c3) > 1) { + return e_invarg; + } + } + } + if (*s == ',' || *s == NUL) { + if (round > 0) { + if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { + wp->w_p_lcs_chars.tab1 = c1; + wp->w_p_lcs_chars.tab2 = c2; + wp->w_p_lcs_chars.tab3 = c3; + } else if (tab[i].cp != NULL) { + *(tab[i].cp) = c1; + } + } + p = s; + break; + } + } + } - Rows = height; - Columns = width; - check_screensize(); - int max_p_ch = Rows - min_rows() + 1; - if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) { - p_ch = max_p_ch ? max_p_ch : 1; + if (i == entries) { + len = (int)STRLEN("multispace"); + len2 = (int)STRLEN("leadmultispace"); + if ((varp == &p_lcs || varp == &wp->w_p_lcs) + && STRNCMP(p, "multispace", len) == 0 + && p[len] == ':' + && p[len + 1] != NUL) { + s = p + len + 1; + if (round == 0) { + // Get length of lcs-multispace string in the first round + last_multispace = p; + multispace_len = 0; + while (*s != NUL && *s != ',') { + c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + multispace_len++; + } + if (multispace_len == 0) { + // lcs-multispace cannot be an empty string + return e_invarg; + } + p = s; + } else { + int multispace_pos = 0; + while (*s != NUL && *s != ',') { + c1 = get_encoded_char_adv(&s); + if (p == last_multispace) { + wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; + } + } + p = s; + } + } else if ((varp == &p_lcs || varp == &wp->w_p_lcs) + && STRNCMP(p, "leadmultispace", len2) == 0 + && p[len2] == ':' + && p[len2 + 1] != NUL) { + s = p + len2 + 1; + if (round == 0) { + // get length of lcs-leadmultispace string in first round + last_lmultispace = p; + lead_multispace_len = 0; + while (*s != NUL && *s != ',') { + c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + lead_multispace_len++; + } + if (lead_multispace_len == 0) { + // lcs-leadmultispace cannot be an empty string + return e_invarg; + } + p = s; + } else { + int multispace_pos = 0; + while (*s != NUL && *s != ',') { + c1 = get_encoded_char_adv(&s); + if (p == last_lmultispace) { + wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1; + } + } + p = s; + } + } else { + return e_invarg; + } + } + if (*p == ',') { + p++; + } + } } - height = Rows; - width = Columns; - p_lines = Rows; - p_columns = Columns; - ui_call_grid_resize(1, width, height); - - send_grid_resize = true; - /// The window layout used to be adjusted here, but it now happens in - /// screenalloc() (also invoked from screenclear()). That is because the - /// recursize "resizing_screen" check above may skip this, but not screenalloc(). + return NULL; // no error +} - if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { - screenclear(); +/// Check all global and local values of 'listchars' and 'fillchars'. +/// May set different defaults in case character widths change. +/// +/// @return an untranslated error message if any of them is invalid, NULL otherwise. +char *check_chars_options(void) +{ + if (set_chars_option(curwin, &p_lcs, false) != NULL) { + return e_conflicts_with_value_of_listchars; } - - if (starting != NO_SCREEN) { - maketitle(); - changed_line_abv_curs(); - invalidate_botline(); - - /* - * We only redraw when it's needed: - * - While at the more prompt or executing an external command, don't - * redraw, but position the cursor. - * - While editing the command line, only redraw that. - * - in Ex mode, don't redraw anything. - * - Otherwise, redraw right now, and position the cursor. - * Always need to call update_screen() or screenalloc(), to make - * sure Rows/Columns and the size of the screen is correct! - */ - if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM - || exmode_active) { - screenalloc(); - if (msg_grid.chars) { - msg_grid_validate(); - } - // TODO(bfredl): sometimes messes up the output. Implement clear+redraw - // also for the pager? (or: what if the pager was just a modal window?) - ui_comp_set_screen_valid(true); - repeat_message(); - } else { - if (curwin->w_p_scb) { - do_check_scrollbind(true); - } - if (State & MODE_CMDLINE) { - redraw_popupmenu = false; - update_screen(NOT_VALID); - redrawcmdline(); - if (pum_drawn()) { - cmdline_pum_display(false); - } - } else { - update_topline(curwin); - if (pum_drawn()) { - // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. - // For now make sure the nested update_screen(0) won't redraw the - // pum at the old position. Try to untangle this later. - redraw_popupmenu = false; - ins_compl_show_pum(); - } - update_screen(NOT_VALID); - if (redrawing()) { - setcursor(); - } - } + if (set_chars_option(curwin, &p_fcs, false) != NULL) { + return e_conflicts_with_value_of_fillchars; + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { + return e_conflicts_with_value_of_listchars; + } + if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { + return e_conflicts_with_value_of_fillchars; } - ui_flush(); } - resizing_screen = false; + return NULL; } /// Check if the new Nvim application "screen" dimensions are valid. @@ -6945,13 +1982,3 @@ void check_screensize(void) Columns = 10000; } } - -win_T *get_win_by_grid_handle(handle_T handle) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_grid_alloc.handle == handle) { - return wp; - } - } - return NULL; -} diff --git a/src/nvim/screen.h b/src/nvim/screen.h index 9eda5223f1..ea1c58cd80 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -4,31 +4,10 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" -#include "nvim/grid.h" -#include "nvim/pos.h" -#include "nvim/types.h" +#include "nvim/fold.h" +#include "nvim/grid_defs.h" -// flags for update_screen() -// The higher the value, the higher the priority -#define VALID 10 // buffer not changed, or changes marked - // with b_mod_* -#define INVERTED 20 // redisplay inverted part that changed -#define INVERTED_ALL 25 // redisplay whole inverted part -#define REDRAW_TOP 30 // display first w_upd_rows screen lines -#define SOME_VALID 35 // like NOT_VALID but may scroll -#define NOT_VALID 40 // buffer needs complete redraw -#define CLEAR 50 // screen messed up, clear it - -/// corner value flags for hsep_connected and vsep_connected -typedef enum { - WC_TOP_LEFT = 0, - WC_TOP_RIGHT, - WC_BOTTOM_LEFT, - WC_BOTTOM_RIGHT, -} WindowCorner; - -// Maximum columns for terminal highlight attributes -#define TERM_ATTRS_MAX 1024 +EXTERN match_T screen_search_hl; // used for 'hlsearch' highlight matching /// Array defining what should be done when tabline is clicked EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL); @@ -39,13 +18,6 @@ EXTERN long tab_page_click_defs_size INIT(= 0); #define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width) #define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height) -// While redrawing the screen this flag is set. It means the screen size -// ('lines' and 'rows') must not be changed. -EXTERN bool updating_screen INIT(= 0); - -// While resizing the screen this flag is set. -EXTERN bool resizing_screen INIT(= 0); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.h.generated.h" #endif diff --git a/src/nvim/search.c b/src/nvim/search.c index 403e2f3aa4..c820817a71 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -15,12 +15,14 @@ #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdhist.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/funcs.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" @@ -42,8 +44,8 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -271,7 +273,7 @@ void free_search_patterns(void) free_spat(&spats[0]); free_spat(&spats[1]); - memset(spats, 0, sizeof(spats)); + CLEAR_FIELD(spats); if (mr_pattern_alloced) { xfree(mr_pattern); @@ -431,7 +433,7 @@ void set_last_csearch(int c, char_u *s, int len) if (len) { memcpy(lastc_bytes, s, (size_t)len); } else { - memset(lastc_bytes, 0, sizeof(lastc_bytes)); + CLEAR_FIELD(lastc_bytes); } } @@ -513,7 +515,7 @@ void last_pat_prog(regmmatch_T *regmatch) } ++emsg_off; // So it doesn't beep if bad expr (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch); - --emsg_off; + emsg_off--; } /// Lowest level search function. @@ -1036,7 +1038,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, char_u *p; long c; char_u *dircp; - char_u *strcopy = NULL; + char *strcopy = NULL; char_u *ps; char_u *msgbuf = NULL; size_t len; @@ -1123,13 +1125,13 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, * Find end of regular expression. * If there is a matching '/' or '?', toss it. */ - ps = strcopy; + ps = (char_u *)strcopy; p = skip_regexp(pat, search_delim, p_magic, &strcopy); - if (strcopy != ps) { + if (strcopy != (char *)ps) { // made a copy of "pat" to change "\?" to "?" searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); - pat = strcopy; - searchstr = strcopy; + pat = (char_u *)strcopy; + searchstr = (char_u *)strcopy; } if (*p == search_delim) { dircp = p; // remember where we put the NUL @@ -1160,9 +1162,9 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } else { // single '+' spats[0].off.off = 1; } - ++p; + p++; while (ascii_isdigit(*p)) { // skip number - ++p; + p++; } } @@ -1426,7 +1428,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, emsg(_("E386: Expected '?' or '/' after ';'")); goto end_do_search; } - ++pat; + pat++; } if (options & SEARCH_MARK) { @@ -2136,10 +2138,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } if (*ptr == '"' && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\'')) { - ++do_quotes; + do_quotes++; } if (*ptr == '\\' && ptr[1] != NUL) { - ++ptr; + ptr++; } } do_quotes &= 1; // result is 1 with even number of quotes @@ -2342,7 +2344,7 @@ int check_linecomment(const char_u *line) && !is_pos_in_string(line, (colnr_T)(p - line))) { break; } - ++p; + p++; } } @@ -2629,7 +2631,7 @@ bool findpar(bool *pincl, int dir, long count, int what, int both) } setpcmark(); if (both && *ml_get(curr) == '}') { // include line with '}' - ++curr; + curr++; } curwin->w_cursor.lnum = curr; if (curr == curbuf->b_ml.ml_line_count && what != '}') { @@ -2667,7 +2669,7 @@ static int inmacro(char_u *opt, char_u *s) && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { break; } - ++macro; + macro++; if (macro[0] == NUL) { break; } @@ -3110,7 +3112,7 @@ int current_word(oparg_T *oap, long count, int include, int bigword) oap->start = start_pos; oap->motion_type = kMTCharWise; } - --count; + count--; } /* @@ -3160,7 +3162,7 @@ int current_word(oparg_T *oap, long count, int include, int bigword) } } } - --count; + count--; } if (include_white && (cls() != 0 @@ -3323,7 +3325,7 @@ extend: } else { ncount = count; if (start_blank) { - --ncount; + ncount--; } } if (ncount > 0) { @@ -3851,7 +3853,7 @@ extend: break; } } - --start_lnum; + start_lnum--; } /* @@ -3859,13 +3861,13 @@ extend: */ end_lnum = start_lnum; while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { - ++end_lnum; + end_lnum++; } end_lnum--; i = (int)count; if (!include && white_in_front) { - --i; + i--; } while (i--) { if (end_lnum == curbuf->b_ml.ml_line_count) { @@ -3877,14 +3879,12 @@ extend: } if (include || !do_white) { - ++end_lnum; - /* - * skip to end of paragraph - */ + end_lnum++; + // skip to end of paragraph while (end_lnum < curbuf->b_ml.ml_line_count && !linewhite(end_lnum + 1) && !startPS(end_lnum + 1, 0, 0)) { - ++end_lnum; + end_lnum++; } } @@ -3898,7 +3898,7 @@ extend: if (include || do_white) { while (end_lnum < curbuf->b_ml.ml_line_count && linewhite(end_lnum + 1)) { - ++end_lnum; + end_lnum++; } } } @@ -3909,7 +3909,7 @@ extend: */ if (!white_in_front && !linewhite(end_lnum) && include) { while (start_lnum > 1 && linewhite(start_lnum - 1)) { - --start_lnum; + start_lnum--; } } @@ -3983,7 +3983,7 @@ static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *e if (escape != NULL) { while (col_start - n > 0 && vim_strchr((char *)escape, line[col_start - n - 1]) != NULL) { - ++n; + n++; } } if (n & 1) { @@ -4169,11 +4169,11 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) if (include) { if (ascii_iswhite(line[col_end + 1])) { while (ascii_iswhite(line[col_end + 1])) { - ++col_end; + col_end++; } } else { while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { - --col_start; + col_start--; } } } @@ -4551,7 +4551,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst static buf_T *lbuf = NULL; proftime_T start; - memset(stat, 0, sizeof(searchstat_T)); + CLEAR_POINTER(stat); if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) { stat->cur = cur; @@ -5467,7 +5467,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } did_show = true; while (depth_displayed < depth && !got_int) { - ++depth_displayed; + depth_displayed++; for (i = 0; i < depth_displayed; i++) { msg_puts(" "); } @@ -5509,11 +5509,11 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo // Avoid checking before the start of the line, can // happen if \zs appears in the regexp. if (p[-1] == '"' || p[-1] == '<') { - --p; - ++i; + p--; + i++; } if (p[i] == '"' || p[i] == '>') { - ++i; + i++; } } save_char = p[i]; @@ -5561,7 +5561,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo // Something wrong. We will forget one of our already visited files // now. xfree(files[old_files].name); - ++old_files; + old_files++; } files[depth].name = curr_fname = new_fname; files[depth].lnum = 0; @@ -5860,7 +5860,7 @@ exit_matched: while (depth >= 0 && !already && vim_fgets(line = file_line, LSIZE, files[depth].fp)) { fclose(files[depth].fp); - --old_files; + old_files--; files[old_files].name = files[depth].name; files[old_files].matched = files[depth].matched; depth--; @@ -5948,10 +5948,10 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, if (fp != NULL) { // We used fgets(), so get rid of newline at end if (p >= line && *p == '\n') { - --p; + p--; } if (p >= line && *p == '\r') { - --p; + p--; } *(p + 1) = NUL; } @@ -5996,7 +5996,7 @@ void get_search_pattern(SearchPattern *const pat) void get_substitute_pattern(SearchPattern *const pat) { memcpy(pat, &(spats[1]), sizeof(spats[1])); - memset(&(pat->off), 0, sizeof(pat->off)); + CLEAR_FIELD(pat->off); } /// Set last search pattern @@ -6012,7 +6012,7 @@ void set_substitute_pattern(const SearchPattern pat) { free_spat(&spats[1]); memcpy(&(spats[1]), &pat, sizeof(spats[1])); - memset(&(spats[1].off), 0, sizeof(spats[1].off)); + CLEAR_FIELD(spats[1].off); } /// Set last used search pattern diff --git a/src/nvim/shada.c b/src/nvim/shada.c index cd3b967a9f..0927473be0 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,11 +16,12 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/cmdhist.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" +#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/globals.h" @@ -2188,7 +2189,7 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret); FileMarks *const filemarks = &kh_val(&wms->file_marks, k); if (kh_ret > 0) { - memset(filemarks, 0, sizeof(*filemarks)); + CLEAR_POINTER(filemarks); } if (entry.timestamp > filemarks->greatest_timestamp) { filemarks->greatest_timestamp = entry.timestamp; @@ -2451,6 +2452,27 @@ static inline void find_removable_bufs(khash_t(bufset) *removable_bufs) } } +/// Translate a history type number to the associated character +static int hist_type2char(const int type) + FUNC_ATTR_CONST +{ + switch (type) { + case HIST_CMD: + return ':'; + case HIST_SEARCH: + return '/'; + case HIST_EXPR: + return '='; + case HIST_INPUT: + return '@'; + case HIST_DEBUG: + return '>'; + default: + abort(); + } + return NUL; +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2730,7 +2752,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret); FileMarks *const filemarks = &kh_val(&wms->file_marks, k); if (kh_ret > 0) { - memset(filemarks, 0, sizeof(*filemarks)); + CLEAR_POINTER(filemarks); } do { fmark_T fm; @@ -3456,7 +3478,7 @@ shada_read_next_item_start: // data union are NULL so they are safe to xfree(). This is needed in case // somebody calls goto shada_read_next_item_error before anything is set in // the switch. - memset(entry, 0, sizeof(*entry)); + CLEAR_POINTER(entry); if (sd_reader->eof) { return kSDReadStatusFinished; } diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 7b6b55fede..f1ddbfd147 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -9,6 +9,7 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval/funcs.h" #include "nvim/ex_docmd.h" @@ -16,7 +17,6 @@ #include "nvim/highlight_group.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/syntax.h" #include "nvim/vim.h" @@ -442,27 +442,24 @@ static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group /// @param max_signs the number of signs, with priority for the ones /// with the highest Ids. /// @return Attrs of the matching sign, or NULL -sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int max_signs) +SignTextAttrs *sign_get_attr(int idx, SignTextAttrs sattrs[], int max_signs) { - sign_attrs_T *matches[SIGN_SHOW_MAX]; - int nr_matches = 0; + SignTextAttrs *matches[SIGN_SHOW_MAX]; + int sattr_matches = 0; for (int i = 0; i < SIGN_SHOW_MAX; i++) { - if ((type == SIGN_TEXT && sattrs[i].sat_text != NULL) - || (type == SIGN_LINEHL && sattrs[i].sat_linehl != 0) - || (type == SIGN_NUMHL && sattrs[i].sat_numhl != 0)) { - matches[nr_matches] = &sattrs[i]; - nr_matches++; + if (sattrs[i].text != NULL) { + matches[sattr_matches++] = &sattrs[i]; // attr list is sorted with most important (priority, id), thus we // may stop as soon as we have max_signs matches - if (nr_matches >= max_signs) { + if (sattr_matches >= max_signs) { break; } } } - if (nr_matches > idx) { - return matches[nr_matches - idx - 1]; + if (sattr_matches > idx) { + return matches[sattr_matches - idx - 1]; } return NULL; @@ -474,12 +471,12 @@ sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int m /// @param lnum Line in which to search /// @param sattrs Output array for attrs /// @return Number of signs of which attrs were found -int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) +int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriAttr *num_attrs, + HlPriAttr *line_attrs, HlPriAttr *cul_attrs) { sign_entry_T *sign; - sign_T *sp; - int nr_matches = 0; + int sattr_matches = 0; FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (sign->se_lnum > lnum) { @@ -488,37 +485,39 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) break; } - if (sign->se_lnum == lnum) { - sign_attrs_T sattr; - memset(&sattr, 0, sizeof(sattr)); - sattr.sat_typenr = sign->se_typenr; - sp = find_sign_by_typenr(sign->se_typenr); - if (sp != NULL) { - sattr.sat_text = sp->sn_text; - if (sattr.sat_text != NULL && sp->sn_text_hl != 0) { - sattr.sat_texthl = syn_id2attr(sp->sn_text_hl); - } - if (sp->sn_line_hl != 0) { - sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); - } - if (sp->sn_cul_hl != 0) { - sattr.sat_culhl = syn_id2attr(sp->sn_cul_hl); - } - if (sp->sn_num_hl != 0) { - sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); - } - // Store the priority so we can mesh in extmark signs later - sattr.sat_prio = sign->se_priority; - } + if (sign->se_lnum < lnum) { + continue; + } - sattrs[nr_matches] = sattr; - nr_matches++; - if (nr_matches == SIGN_SHOW_MAX) { - break; + sign_T *sp = find_sign_by_typenr(sign->se_typenr); + if (sp == NULL) { + continue; + } + + if (sp->sn_text != NULL && sattr_matches < SIGN_SHOW_MAX) { + sattrs[sattr_matches++] = (SignTextAttrs) { + .text = sp->sn_text, + .hl_attr_id = sp->sn_text_hl == 0 ? 0 : syn_id2attr(sp->sn_text_hl), + .priority = sign->se_priority + }; + } + + struct { HlPriAttr *dest; int hl; } cattrs[] = { + { line_attrs, sp->sn_line_hl }, + { num_attrs, sp->sn_num_hl }, + { cul_attrs, sp->sn_cul_hl }, + { NULL, -1 }, + }; + for (int i = 0; cattrs[i].dest; i++) { + if (cattrs[i].hl != 0 && sign->se_priority >= cattrs[i].dest->priority) { + *cattrs[i].dest = (HlPriAttr) { + .attr_id = syn_id2attr(cattrs[i].hl), + .priority = sign->se_priority + }; } } } - return nr_matches; + return sattr_matches; } /// Delete sign 'id' in group 'group' from buffer 'buf'. diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index e4ece71846..a4fb325ec8 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -33,15 +33,11 @@ struct sign_entry { }; /// Sign attributes. Used by the screen refresh routines. -typedef struct sign_attrs_S { - int sat_typenr; - char_u *sat_text; - int sat_texthl; - int sat_linehl; - int sat_culhl; - int sat_numhl; - int sat_prio; // Used for inserting extmark signs -} sign_attrs_T; +typedef struct { + char_u *text; + int hl_attr_id; + int priority; +} SignTextAttrs; #define SIGN_SHOW_MAX 9 diff --git a/src/nvim/spell.c b/src/nvim/spell.c index ceb35af4b8..1e44d328b3 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -56,18 +56,6 @@ // Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a // specific word. -// Use this to adjust the score after finding suggestions, based on the -// suggested word sounding like the bad word. This is much faster than doing -// it for every possible suggestion. -// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" -// vs "ht") and goes down in the list. -// Used when 'spellsuggest' is set to "best". -#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) - -// Do the opposite: based on a maximum end score and a known sound score, -// compute the maximum word score that can be used. -#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) - #include <assert.h> #include <inttypes.h> #include <limits.h> @@ -84,15 +72,12 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" -#include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/garray.h" -#include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/input.h" #include "nvim/insexpand.h" @@ -108,20 +93,14 @@ #include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" #include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/ui.h" #include "nvim/undo.h" -// only used for su_badflags -#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI - -#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) - // Result values. Lower number is accepted over higher one. #define SP_BANNED (-1) #define SP_RARE 0 @@ -136,104 +115,6 @@ slang_T *first_lang = NULL; // file used for "zG" and "zW" char_u *int_wordlist = NULL; -typedef struct wordcount_S { - uint16_t wc_count; // nr of times word was seen - char_u wc_word[1]; // word, actually longer -} wordcount_T; - -#define WC_KEY_OFF offsetof(wordcount_T, wc_word) -#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF)) -#define MAXWORDCOUNT 0xffff - -// Information used when looking for suggestions. -typedef struct suginfo_S { - garray_T su_ga; // suggestions, contains "suggest_T" - int su_maxcount; // max. number of suggestions displayed - int su_maxscore; // maximum score for adding to su_ga - int su_sfmaxscore; // idem, for when doing soundfold words - garray_T su_sga; // like su_ga, sound-folded scoring - char_u *su_badptr; // start of bad word in line - int su_badlen; // length of detected bad word in line - int su_badflags; // caps flags for bad word - char_u su_badword[MAXWLEN]; // bad word truncated at su_badlen - char_u su_fbadword[MAXWLEN]; // su_badword case-folded - char_u su_sal_badword[MAXWLEN]; // su_badword soundfolded - hashtab_T su_banned; // table with banned words - slang_T *su_sallang; // default language for sound folding -} suginfo_T; - -// One word suggestion. Used in "si_ga". -typedef struct { - char_u *st_word; // suggested word, allocated string - int st_wordlen; // STRLEN(st_word) - int st_orglen; // length of replaced text - int st_score; // lower is better - int st_altscore; // used when st_score compares equal - bool st_salscore; // st_score is for soundalike - bool st_had_bonus; // bonus already included in score - slang_T *st_slang; // language used for sound folding -} suggest_T; - -#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) - -// True if a word appears in the list of banned words. -#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) - -// Number of suggestions kept when cleaning up. We need to keep more than -// what is displayed, because when rescore_suggestions() is called the score -// may change and wrong suggestions may be removed later. -#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \ - 130 ? 150 : (su)->su_maxcount + 20) - -// Threshold for sorting and cleaning up suggestions. Don't want to keep lots -// of suggestions that are not going to be displayed. -#define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50) - -// score for various changes -#define SCORE_SPLIT 149 // split bad word -#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS -#define SCORE_ICASE 52 // slightly different case -#define SCORE_REGION 200 // word is for different region -#define SCORE_RARE 180 // rare word -#define SCORE_SWAP 75 // swap two characters -#define SCORE_SWAP3 110 // swap two characters in three -#define SCORE_REP 65 // REP replacement -#define SCORE_SUBST 93 // substitute a character -#define SCORE_SIMILAR 33 // substitute a similar character -#define SCORE_SUBCOMP 33 // substitute a composing character -#define SCORE_DEL 94 // delete a character -#define SCORE_DELDUP 66 // delete a duplicated character -#define SCORE_DELCOMP 28 // delete a composing character -#define SCORE_INS 96 // insert a character -#define SCORE_INSDUP 67 // insert a duplicate character -#define SCORE_INSCOMP 30 // insert a composing character -#define SCORE_NONWORD 103 // change non-word to word char - -#define SCORE_FILE 30 // suggestion from a file -#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower. - // 350 allows for about three changes. - -#define SCORE_COMMON1 30 // subtracted for words seen before -#define SCORE_COMMON2 40 // subtracted for words often seen -#define SCORE_COMMON3 50 // subtracted for words very often seen -#define SCORE_THRES2 10 // word count threshold for COMMON2 -#define SCORE_THRES3 100 // word count threshold for COMMON3 - -// When trying changed soundfold words it becomes slow when trying more than -// two changes. With less than two changes it's slightly faster but we miss a -// few good suggestions. In rare cases we need to try three of four changes. -#define SCORE_SFMAX1 200 // maximum score for first try -#define SCORE_SFMAX2 300 // maximum score for second try -#define SCORE_SFMAX3 400 // maximum score for third try - -#define SCORE_BIG (SCORE_INS * 3) // big difference -#define SCORE_MAXMAX 999999 // accept any score -#define SCORE_LIMITMAX 350 // for spell_edit_score_limit() - -// for spell_edit_score_limit() we need to know the minimum value of -// SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS -#define SCORE_EDIT_MIN SCORE_SIMILAR - // Structure to store info for word matching. typedef struct matchinf_S { langp_T *mi_lp; // info for language and region @@ -289,38 +170,10 @@ typedef struct syl_item_S { spelltab_T spelltab; int did_set_spelltab; -// structure used to store soundfolded words that add_sound_suggest() has -// handled already. -typedef struct { - int16_t sft_score; // lowest score used - char_u sft_word[1]; // soundfolded word, actually longer -} sftword_T; - -typedef struct { - int badi; - int goodi; - int score; -} limitscore_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "spell.c.generated.h" #endif -// values for ts_isdiff -#define DIFF_NONE 0 // no different byte (yet) -#define DIFF_YES 1 // different byte found -#define DIFF_INSERT 2 // inserting character - -// values for ts_flags -#define TSF_PREFIXOK 1 // already checked that prefix is OK -#define TSF_DIDSPLIT 2 // tried split at this point -#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index - -// special values ts_prefixdepth -#define PFD_NOPREFIX 0xff // not using prefixes -#define PFD_PREFIXTREE 0xfe // walking through the prefix tree -#define PFD_NOTSPECIAL 0xfd // highest value that's not special - // mode values for find_word #define FIND_FOLDWORD 0 // find word case-folded #define FIND_KEEPWORD 1 // find keep-case word @@ -331,8 +184,8 @@ typedef struct { char *e_format = N_("E759: Format error in spell file"); // Remember what "z?" replaced. -static char_u *repl_from = NULL; -static char_u *repl_to = NULL; +char_u *repl_from = NULL; +char_u *repl_to = NULL; /// Main spell-checking function. /// "ptr" points to a character that could be the start of a word. @@ -374,7 +227,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou return 1; } - memset(&mi, 0, sizeof(matchinf_T)); + CLEAR_FIELD(mi); // A number is always OK. Also skip hexadecimal numbers 0xFF99 and // 0X99FF. But always do check spelling to find "3GPP" and "11 @@ -639,13 +492,13 @@ static void find_word(matchinf_T *mip, int mode) } endlen[endidxcnt] = wlen; endidx[endidxcnt++] = arridx++; - --len; + len--; // Skip over the zeros, there can be several flag/region // combinations. while (len > 0 && byts[arridx] == 0) { - ++arridx; - --len; + arridx++; + len--; } if (len == 0) { break; // no children, word must end here @@ -683,8 +536,8 @@ static void find_word(matchinf_T *mip, int mode) // Continue at the child (if there is one). arridx = idxs[lo]; - ++wlen; - --flen; + wlen++; + flen--; // One space in the good word may stand for several spaces in the // checked word. @@ -696,8 +549,8 @@ static void find_word(matchinf_T *mip, int mode) if (ptr[wlen] != ' ' && ptr[wlen] != TAB) { break; } - ++wlen; - --flen; + wlen++; + flen--; } } } @@ -708,7 +561,7 @@ static void find_word(matchinf_T *mip, int mode) // Verify that one of the possible endings is valid. Try the longest // first. while (endidxcnt > 0) { - --endidxcnt; + endidxcnt--; arridx = endidx[endidxcnt]; wlen = endlen[endidxcnt]; @@ -1050,7 +903,7 @@ static void find_word(matchinf_T *mip, int mode) /// end of ptr[wlen] and the second part matches after it. /// /// @param gap &sl_comppat -static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) +bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) { char_u *p; int len; @@ -1072,7 +925,7 @@ static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) // Returns true if "flags" is a valid sequence of compound flags and "word" // does not have too many syllables. -static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) +bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) FUNC_ATTR_NONNULL_ALL { char_u uflags[MAXWLEN * 2] = { 0 }; @@ -1101,37 +954,11 @@ static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags return true; } -// Returns true when the sequence of flags in "compflags" plus "flag" can -// possibly form a valid compounded word. This also checks the COMPOUNDRULE -// lines if they don't contain wildcards. -static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag) -{ - // If the flag doesn't appear in sl_compstartflags or sl_compallflags - // then it can't possibly compound. - if (!byte_in_str(sp->ts_complen == sp->ts_compsplit - ? slang->sl_compstartflags : slang->sl_compallflags, flag)) { - return false; - } - - // If there are no wildcards, we can check if the flags collected so far - // possibly can form a match with COMPOUNDRULE patterns. This only - // makes sense when we have two or more words. - if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) { - compflags[sp->ts_complen] = (char_u)flag; - compflags[sp->ts_complen + 1] = NUL; - bool v = match_compoundrule(slang, compflags + sp->ts_compsplit); - compflags[sp->ts_complen] = NUL; - return v; - } - - return true; -} - // Returns true if the compound flags in compflags[] match the start of any // compound rule. This is used to stop trying a compound if the flags // collected so far can't possibly match any compound rule. // Caller must check that slang->sl_comprules is not NULL. -static bool match_compoundrule(slang_T *slang, char_u *compflags) +bool match_compoundrule(slang_T *slang, char_u *compflags) { char_u *p; int i; @@ -1154,7 +981,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) bool match = false; // compare against all the flags in [] - ++p; + p++; while (*p != ']' && *p != NUL) { if (*p++ == c) { match = true; @@ -1166,7 +993,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) } else if (*p != c) { break; // flag of word doesn't match flag in pattern } - ++p; + p++; } // Skip to the next "/", where the next pattern starts. @@ -1188,8 +1015,8 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) /// @param totprefcnt nr of prefix IDs /// @param arridx idx in sl_pidxs[] /// @param cond_req only use prefixes with a condition -static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, - bool cond_req) +int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, + bool cond_req) { int prefcnt; int pidx; @@ -1282,8 +1109,8 @@ static void find_prefix(matchinf_T *mip, int mode) mip->mi_prefarridx = arridx; mip->mi_prefcnt = len; while (len > 0 && byts[arridx] == 0) { - ++arridx; - --len; + arridx++; + len--; } mip->mi_prefcnt -= len; @@ -1332,8 +1159,8 @@ static void find_prefix(matchinf_T *mip, int mode) // Continue at the child (if there is one). arridx = idxs[lo]; - ++wlen; - --flen; + wlen++; + flen--; } } @@ -1368,7 +1195,7 @@ static int fold_more(matchinf_T *mip) /// /// @param wordflags Flags for the checked word. /// @param treeflags Flags for the word in the spell tree. -static bool spell_valid_case(int wordflags, int treeflags) +bool spell_valid_case(int wordflags, int treeflags) { return (wordflags == WF_ALLCAP && (treeflags & WF_FIXCAP) == 0) || ((treeflags & (WF_ALLCAP | WF_KEEPCAP)) == 0 @@ -1377,7 +1204,7 @@ static bool spell_valid_case(int wordflags, int treeflags) } // Returns true if spell checking is not enabled. -static bool no_spell_checking(win_T *wp) +bool no_spell_checking(win_T *wp) { if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) { @@ -1581,7 +1408,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att capcol = -1; } else { if (lnum < wp->w_buffer->b_ml.ml_line_count) { - ++lnum; + lnum++; } else if (!p_ws) { break; // at first line and 'nowrapscan' } else { @@ -1609,7 +1436,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // Capcol skips over the inserted space. - --capcol; + capcol--; // But after empty line check first word in next line if (empty_line) { @@ -1903,38 +1730,6 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) } } -/// Adjust the score of common words. -/// -/// @param split word was split, less bonus -static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split) -{ - wordcount_T *wc; - int bonus; - int newscore; - - hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word); - if (!HASHITEM_EMPTY(hi)) { - wc = HI2WC(hi); - if (wc->wc_count < SCORE_THRES2) { - bonus = SCORE_COMMON1; - } else if (wc->wc_count < SCORE_THRES3) { - bonus = SCORE_COMMON2; - } else { - bonus = SCORE_COMMON3; - } - if (split) { - newscore = score - bonus / 2; - } else { - newscore = score - bonus; - } - if (newscore < 0) { - return 0; - } - return newscore; - } - return score; -} - // Returns true if byte "n" appears in "str". // Like strchr() but independent of locale. bool byte_in_str(char_u *str, int n) @@ -2016,7 +1811,7 @@ static int count_syllables(slang_T *slang, const char_u *word) } } if (len != 0) { // found a match, count syllable - ++cnt; + cnt++; skip = false; } else { // No recognized syllable item, at least a syllable char then? @@ -2038,7 +1833,7 @@ static int count_syllables(slang_T *slang, const char_u *word) char *did_set_spelllang(win_T *wp) { garray_T ga; - char_u *splp; + char *splp; char_u *region; char_u region_cp[3]; bool filename; @@ -2050,7 +1845,7 @@ char *did_set_spelllang(win_T *wp) int len; char_u *p; int round; - char_u *spf; + char *spf; char_u *use_region = NULL; bool dont_use_region = false; bool nobreak = false; @@ -2080,9 +1875,9 @@ char *did_set_spelllang(win_T *wp) wp->w_s->b_cjk = 0; // Loop over comma separated language names. - for (splp = spl_copy; *splp != NUL;) { + for (splp = (char *)spl_copy; *splp != NUL;) { // Get one language name. - copy_option_part((char **)&splp, (char *)lang, MAXWLEN, ","); + copy_option_part(&splp, (char *)lang, MAXWLEN, ","); region = NULL; len = (int)STRLEN(lang); @@ -2204,8 +1999,8 @@ char *did_set_spelllang(win_T *wp) // round 1: load first name in 'spellfile'. // round 2: load second name in 'spellfile. // etc. - spf = curwin->w_s->b_p_spf; - for (round = 0; round == 0 || *spf != NUL; ++round) { + spf = (char *)curwin->w_s->b_p_spf; + for (round = 0; round == 0 || *spf != NUL; round++) { if (round == 0) { // Internal wordlist, if there is one. if (int_wordlist == NULL) { @@ -2214,7 +2009,7 @@ char *did_set_spelllang(win_T *wp) int_wordlist_spl(spf_name); } else { // One entry in 'spellfile'. - copy_option_part((char **)&spf, (char *)spf_name, MAXPATHL - 5, ","); + copy_option_part(&spf, (char *)spf_name, MAXPATHL - 5, ","); STRCAT(spf_name, ".spl"); // If it was already found above then skip it. @@ -2338,7 +2133,7 @@ theend: // Clear the midword characters for buffer "buf". static void clear_midword(win_T *wp) { - memset(wp->w_s->b_spell_ismw, 0, 256); + CLEAR_FIELD(wp->w_s->b_spell_ismw); XFREE_CLEAR(wp->w_s->b_spell_ismw_mb); } @@ -2444,51 +2239,6 @@ int captype(char_u *word, char_u *end) return 0; } -// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a -// capital. So that make_case_word() can turn WOrd into Word. -// Add ALLCAP for "WOrD". -static int badword_captype(char_u *word, char_u *end) - FUNC_ATTR_NONNULL_ALL -{ - int flags = captype(word, end); - int c; - int l, u; - bool first; - char_u *p; - - if (flags & WF_KEEPCAP) { - // Count the number of UPPER and lower case letters. - l = u = 0; - first = false; - for (p = word; p < end; MB_PTR_ADV(p)) { - c = utf_ptr2char((char *)p); - if (SPELL_ISUPPER(c)) { - ++u; - if (p == word) { - first = true; - } - } else { - ++l; - } - } - - // If there are more UPPER than lower case letters suggest an - // ALLCAP word. Otherwise, if the first letter is UPPER then - // suggest ONECAP. Exception: "ALl" most likely should be "All", - // require three upper case letters. - if (u > l && u > 2) { - flags |= WF_ALLCAP; - } else if (first) { - flags |= WF_ONECAP; - } - - if (u >= 2 && l >= 2) { // maCARONI maCAroni - flags |= WF_MIXCAP; - } - } - return flags; -} - // Delete the internal wordlist and its .spl file. void spell_delete_wordlist(void) { @@ -2547,36 +2297,6 @@ void spell_reload(void) } } -// Opposite of offset2bytes(). -// "pp" points to the bytes and is advanced over it. -// Returns the offset. -static int bytes2offset(char_u **pp) -{ - char_u *p = *pp; - int nr; - int c; - - c = *p++; - if ((c & 0x80) == 0x00) { // 1 byte - nr = c - 1; - } else if ((c & 0xc0) == 0x80) { // 2 bytes - nr = (c & 0x3f) - 1; - nr = nr * 255 + (*p++ - 1); - } else if ((c & 0xe0) == 0xc0) { // 3 bytes - nr = (c & 0x1f) - 1; - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - } else { // 4 bytes - nr = (c & 0x0f) - 1; - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - } - - *pp = p; - return nr; -} - // Open a spell buffer. This is a nameless buffer that is not in the buffer // list and only contains text lines. Can use a swapfile to reduce memory // use. @@ -2610,9 +2330,9 @@ void clear_spell_chartab(spelltab_T *sp) { int i; - // Init everything to false. - memset(sp->st_isw, false, sizeof(sp->st_isw)); - memset(sp->st_isu, false, sizeof(sp->st_isu)); + // Init everything to false (zero). + CLEAR_FIELD(sp->st_isw); + CLEAR_FIELD(sp->st_isu); for (i = 0; i < 256; i++) { sp->st_fold[i] = (char_u)i; @@ -2665,7 +2385,7 @@ void init_spell_chartab(void) /// Thus this only works properly when past the first character of the word. /// /// @param wp Buffer used. -static bool spell_iswordp(const char_u *p, const win_T *wp) +bool spell_iswordp(const char_u *p, const win_T *wp) FUNC_ATTR_NONNULL_ALL { int c; @@ -2783,298 +2503,9 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle return OK; } -// values for sps_flags -#define SPS_BEST 1 -#define SPS_FAST 2 -#define SPS_DOUBLE 4 - -static int sps_flags = SPS_BEST; // flags from 'spellsuggest' -static int sps_limit = 9999; // max nr of suggestions given - -// Check the 'spellsuggest' option. Return FAIL if it's wrong. -// Sets "sps_flags" and "sps_limit". -int spell_check_sps(void) -{ - char_u *p; - char_u *s; - char_u buf[MAXPATHL]; - int f; - - sps_flags = 0; - sps_limit = 9999; - - for (p = p_sps; *p != NUL;) { - copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); - - f = 0; - if (ascii_isdigit(*buf)) { - s = buf; - sps_limit = getdigits_int((char **)&s, true, 0); - if (*s != NUL && !ascii_isdigit(*s)) { - f = -1; - } - } else if (STRCMP(buf, "best") == 0) { - f = SPS_BEST; - } else if (STRCMP(buf, "fast") == 0) { - f = SPS_FAST; - } else if (STRCMP(buf, "double") == 0) { - f = SPS_DOUBLE; - } else if (STRNCMP(buf, "expr:", 5) != 0 - && STRNCMP(buf, "file:", 5) != 0) { - f = -1; - } - - if (f == -1 || (sps_flags != 0 && f != 0)) { - sps_flags = SPS_BEST; - sps_limit = 9999; - return FAIL; - } - if (f != 0) { - sps_flags = f; - } - } - - if (sps_flags == 0) { - sps_flags = SPS_BEST; - } - - return OK; -} - -// "z=": Find badly spelled word under or after the cursor. -// Give suggestions for the properly spelled word. -// In Visual mode use the highlighted word as the bad word. -// When "count" is non-zero use that suggestion. -void spell_suggest(int count) -{ - char_u *line; - pos_T prev_cursor = curwin->w_cursor; - char_u wcopy[MAXWLEN + 2]; - char_u *p; - int c; - suginfo_T sug; - suggest_T *stp; - int mouse_used; - int need_cap; - int limit; - int selected = count; - int badlen = 0; - int msg_scroll_save = msg_scroll; - const int wo_spell_save = curwin->w_p_spell; - - if (!curwin->w_p_spell) { - did_set_spelllang(curwin); - curwin->w_p_spell = true; - } - - if (*curwin->w_s->b_p_spl == NUL) { - emsg(_(e_no_spell)); - return; - } - - if (VIsual_active) { - // Use the Visually selected text as the bad word. But reject - // a multi-line selection. - if (curwin->w_cursor.lnum != VIsual.lnum) { - vim_beep(BO_SPELL); - return; - } - badlen = (int)curwin->w_cursor.col - (int)VIsual.col; - if (badlen < 0) { - badlen = -badlen; - } else { - curwin->w_cursor.col = VIsual.col; - } - badlen++; - end_visual_mode(); - } else - // Find the start of the badly spelled word. - if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 - || curwin->w_cursor.col > prev_cursor.col) { - // No bad word or it starts after the cursor: use the word under the - // cursor. - curwin->w_cursor = prev_cursor; - line = get_cursor_line_ptr(); - p = line + curwin->w_cursor.col; - // Backup to before start of word. - while (p > line && spell_iswordp_nmw(p, curwin)) { - MB_PTR_BACK(line, p); - } - // Forward to start of word. - while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { - MB_PTR_ADV(p); - } - - if (!spell_iswordp_nmw(p, curwin)) { // No word found. - beep_flush(); - return; - } - curwin->w_cursor.col = (colnr_T)(p - line); - } - - // Get the word and its length. - - // Figure out if the word should be capitalised. - need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); - - // Make a copy of current line since autocommands may free the line. - line = vim_strsave(get_cursor_line_ptr()); - - // Get the list of suggestions. Limit to 'lines' - 2 or the number in - // 'spellsuggest', whatever is smaller. - if (sps_limit > Rows - 2) { - limit = Rows - 2; - } else { - limit = sps_limit; - } - spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, - true, need_cap, true); - - if (GA_EMPTY(&sug.su_ga)) { - msg(_("Sorry, no suggestions")); - } else if (count > 0) { - if (count > sug.su_ga.ga_len) { - smsg(_("Sorry, only %" PRId64 " suggestions"), - (int64_t)sug.su_ga.ga_len); - } - } else { - // When 'rightleft' is set the list is drawn right-left. - cmdmsg_rl = curwin->w_p_rl; - if (cmdmsg_rl) { - msg_col = Columns - 1; - } - - // List the suggestions. - msg_start(); - msg_row = Rows - 1; // for when 'cmdheight' > 1 - lines_left = Rows; // avoid more prompt - vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), - sug.su_badlen, sug.su_badptr); - if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) { - // And now the rabbit from the high hat: Avoid showing the - // untranslated message rightleft. - vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", - sug.su_badlen, sug.su_badptr); - } - msg_puts((const char *)IObuff); - msg_clr_eos(); - msg_putchar('\n'); - - msg_scroll = TRUE; - for (int i = 0; i < sug.su_ga.ga_len; ++i) { - stp = &SUG(sug.su_ga, i); - - // The suggested word may replace only part of the bad word, add - // the not replaced part. But only when it's not getting too long. - STRLCPY(wcopy, stp->st_word, MAXWLEN + 1); - int el = sug.su_badlen - stp->st_orglen; - if (el > 0 && stp->st_wordlen + el <= MAXWLEN) { - STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1); - } - vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); - if (cmdmsg_rl) { - rl_mirror(IObuff); - } - msg_puts((const char *)IObuff); - - vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); - msg_puts((const char *)IObuff); - - // The word may replace more than "su_badlen". - if (sug.su_badlen < stp->st_orglen) { - vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), - stp->st_orglen, sug.su_badptr); - msg_puts((const char *)IObuff); - } - - if (p_verbose > 0) { - // Add the score. - if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { - vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", - stp->st_salscore ? "s " : "", - stp->st_score, stp->st_altscore); - } else { - vim_snprintf((char *)IObuff, IOSIZE, " (%d)", - stp->st_score); - } - if (cmdmsg_rl) { - // Mirror the numbers, but keep the leading space. - rl_mirror(IObuff + 1); - } - msg_advance(30); - msg_puts((const char *)IObuff); - } - msg_putchar('\n'); - } - - cmdmsg_rl = FALSE; - msg_col = 0; - // Ask for choice. - selected = prompt_for_number(&mouse_used); - - if (ui_has(kUIMessages)) { - ui_call_msg_clear(); - } - - if (mouse_used) { - selected -= lines_left; - } - lines_left = Rows; // avoid more prompt - // don't delay for 'smd' in normal_cmd() - msg_scroll = msg_scroll_save; - } - - if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) { - // Save the from and to text for :spellrepall. - XFREE_CLEAR(repl_from); - XFREE_CLEAR(repl_to); - - stp = &SUG(sug.su_ga, selected - 1); - if (sug.su_badlen > stp->st_orglen) { - // Replacing less than "su_badlen", append the remainder to - // repl_to. - repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen); - vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, - sug.su_badlen - stp->st_orglen, - sug.su_badptr + stp->st_orglen); - repl_to = vim_strsave(IObuff); - } else { - // Replacing su_badlen or more, use the whole word. - repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen); - repl_to = vim_strsave(stp->st_word); - } - - // Replace the word. - p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); - c = (int)(sug.su_badptr - line); - memmove(p, line, (size_t)c); - STRCPY(p + c, stp->st_word); - STRCAT(p, sug.su_badptr + stp->st_orglen); - - // For redo we use a change-word command. - ResetRedobuff(); - AppendToRedobuff("ciw"); - AppendToRedobuffLit((char *)p + c, - stp->st_wordlen + sug.su_badlen - stp->st_orglen); - AppendCharToRedobuff(ESC); - - // "p" may be freed here - ml_replace(curwin->w_cursor.lnum, (char *)p, false); - curwin->w_cursor.col = c; - - inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); - } else { - curwin->w_cursor = prev_cursor; - } - - spell_find_cleanup(&sug); - xfree(line); - curwin->w_p_spell = wo_spell_save; -} - // Check if the word at line "lnum" column "col" is required to start with a // capital. This uses 'spellcapcheck' of the current buffer. -static bool check_need_cap(linenr_T lnum, colnr_T col) +bool check_need_cap(linenr_T lnum, colnr_T col) { bool need_cap = false; char_u *line; @@ -3176,10 +2607,10 @@ void ex_spellrepall(exarg_T *eap) changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); if (curwin->w_cursor.lnum != prev_lnum) { - ++sub_nlines; + sub_nlines++; prev_lnum = curwin->w_cursor.lnum; } - ++sub_nsubs; + sub_nsubs++; } curwin->w_cursor.col += (colnr_T)STRLEN(repl_to); } @@ -3195,336 +2626,6 @@ void ex_spellrepall(exarg_T *eap) } } -/// Find spell suggestions for "word". Return them in the growarray "*gap" as -/// a list of allocated strings. -/// -/// @param maxcount maximum nr of suggestions -/// @param need_cap 'spellcapcheck' matched -void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive) -{ - suginfo_T sug; - suggest_T *stp; - char_u *wcopy; - - spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); - - // Make room in "gap". - ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); - ga_grow(gap, sug.su_ga.ga_len); - for (int i = 0; i < sug.su_ga.ga_len; ++i) { - stp = &SUG(sug.su_ga, i); - - // The suggested word may replace only part of "word", add the not - // replaced part. - wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1); - STRCPY(wcopy, stp->st_word); - STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen); - ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy; - } - - spell_find_cleanup(&sug); -} - -/// Find spell suggestions for the word at the start of "badptr". -/// Return the suggestions in "su->su_ga". -/// The maximum number of suggestions is "maxcount". -/// Note: does use info for the current window. -/// This is based on the mechanisms of Aspell, but completely reimplemented. -/// -/// @param badlen length of bad word or 0 if unknown -/// @param banbadword don't include badword in suggestions -/// @param need_cap word should start with capital -static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, - bool banbadword, bool need_cap, bool interactive) -{ - hlf_T attr = HLF_COUNT; - char_u buf[MAXPATHL]; - char_u *p; - bool do_combine = false; - char_u *sps_copy; - static bool expr_busy = false; - int c; - langp_T *lp; - bool did_intern = false; - - // Set the info in "*su". - memset(su, 0, sizeof(suginfo_T)); - ga_init(&su->su_ga, (int)sizeof(suggest_T), 10); - ga_init(&su->su_sga, (int)sizeof(suggest_T), 10); - if (*badptr == NUL) { - return; - } - hash_init(&su->su_banned); - - su->su_badptr = badptr; - if (badlen != 0) { - su->su_badlen = badlen; - } else { - size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false); - assert(tmplen <= INT_MAX); - su->su_badlen = (int)tmplen; - } - su->su_maxcount = maxcount; - su->su_maxscore = SCORE_MAXINIT; - - if (su->su_badlen >= MAXWLEN) { - su->su_badlen = MAXWLEN - 1; // just in case - } - STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, - MAXWLEN); - - // TODO(vim): make this work if the case-folded text is longer than the - // original text. Currently an illegal byte causes wrong pointer - // computations. - su->su_fbadword[su->su_badlen] = NUL; - - // get caps flags for bad word - su->su_badflags = badword_captype(su->su_badptr, - su->su_badptr + su->su_badlen); - if (need_cap) { - su->su_badflags |= WF_ONECAP; - } - - // Find the default language for sound folding. We simply use the first - // one in 'spelllang' that supports sound folding. That's good for when - // using multiple files for one language, it's not that bad when mixing - // languages (e.g., "pl,en"). - for (int i = 0; i < curbuf->b_s.b_langp.ga_len; ++i) { - lp = LANGP_ENTRY(curbuf->b_s.b_langp, i); - if (lp->lp_sallang != NULL) { - su->su_sallang = lp->lp_sallang; - break; - } - } - - // Soundfold the bad word with the default sound folding, so that we don't - // have to do this many times. - if (su->su_sallang != NULL) { - spell_soundfold(su->su_sallang, su->su_fbadword, true, - su->su_sal_badword); - } - - // If the word is not capitalised and spell_check() doesn't consider the - // word to be bad then it might need to be capitalised. Add a suggestion - // for that. - c = utf_ptr2char((char *)su->su_badptr); - if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) { - make_case_word(su->su_badword, buf, WF_ONECAP); - add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE, - 0, true, su->su_sallang, false); - } - - // Ban the bad word itself. It may appear in another region. - if (banbadword) { - add_banned(su, su->su_badword); - } - - // Make a copy of 'spellsuggest', because the expression may change it. - sps_copy = vim_strsave(p_sps); - - // Loop over the items in 'spellsuggest'. - for (p = sps_copy; *p != NUL;) { - copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); - - if (STRNCMP(buf, "expr:", 5) == 0) { - // Evaluate an expression. Skip this when called recursively, - // when using spellsuggest() in the expression. - if (!expr_busy) { - expr_busy = true; - spell_suggest_expr(su, buf + 5); - expr_busy = false; - } - } else if (STRNCMP(buf, "file:", 5) == 0) { - // Use list of suggestions in a file. - spell_suggest_file(su, buf + 5); - } else if (!did_intern) { - // Use internal method once. - spell_suggest_intern(su, interactive); - if (sps_flags & SPS_DOUBLE) { - do_combine = true; - } - did_intern = true; - } - } - - xfree(sps_copy); - - if (do_combine) { - // Combine the two list of suggestions. This must be done last, - // because sorting changes the order again. - score_combine(su); - } -} - -// Find suggestions by evaluating expression "expr". -static void spell_suggest_expr(suginfo_T *su, char_u *expr) -{ - int score; - const char *p; - - // The work is split up in a few parts to avoid having to export - // suginfo_T. - // First evaluate the expression and get the resulting list. - list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr); - if (list != NULL) { - // Loop over the items in the list. - TV_LIST_ITER(list, li, { - if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { - // Get the word and the score from the items. - score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); - if (score >= 0 && score <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, - score, 0, true, su->su_sallang, false); - } - } - }); - tv_list_unref(list); - } - - // Remove bogus suggestions, sort and truncate at "maxcount". - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); -} - -// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'. -static void spell_suggest_file(suginfo_T *su, char_u *fname) -{ - FILE *fd; - char_u line[MAXWLEN * 2]; - char_u *p; - int len; - char_u cword[MAXWLEN]; - - // Open the file. - fd = os_fopen((char *)fname, "r"); - if (fd == NULL) { - semsg(_(e_notopen), fname); - return; - } - - // Read it line by line. - while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) { - line_breakcheck(); - - p = (char_u *)vim_strchr((char *)line, '/'); - if (p == NULL) { - continue; // No Tab found, just skip the line. - } - *p++ = NUL; - if (STRICMP(su->su_badword, line) == 0) { - // Match! Isolate the good word, until CR or NL. - for (len = 0; p[len] >= ' '; len++) {} - p[len] = NUL; - - // If the suggestion doesn't have specific case duplicate the case - // of the bad word. - if (captype(p, NULL) == 0) { - make_case_word(p, cword, su->su_badflags); - p = cword; - } - - add_suggestion(su, &su->su_ga, p, su->su_badlen, - SCORE_FILE, 0, true, su->su_sallang, false); - } - } - - fclose(fd); - - // Remove bogus suggestions, sort and truncate at "maxcount". - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); -} - -// Find suggestions for the internal method indicated by "sps_flags". -static void spell_suggest_intern(suginfo_T *su, bool interactive) -{ - // Load the .sug file(s) that are available and not done yet. - suggest_load_files(); - - // 1. Try special cases, such as repeating a word: "the the" -> "the". - // - // Set a maximum score to limit the combination of operations that is - // tried. - suggest_try_special(su); - - // 2. Try inserting/deleting/swapping/changing a letter, use REP entries - // from the .aff file and inserting a space (split the word). - suggest_try_change(su); - - // For the resulting top-scorers compute the sound-a-like score. - if (sps_flags & SPS_DOUBLE) { - score_comp_sal(su); - } - - // 3. Try finding sound-a-like words. - if ((sps_flags & SPS_FAST) == 0) { - if (sps_flags & SPS_BEST) { - // Adjust the word score for the suggestions found so far for how - // they sounds like. - rescore_suggestions(su); - } - - // While going through the soundfold tree "su_maxscore" is the score - // for the soundfold word, limits the changes that are being tried, - // and "su_sfmaxscore" the rescored score, which is set by - // cleanup_suggestions(). - // First find words with a small edit distance, because this is much - // faster and often already finds the top-N suggestions. If we didn't - // find many suggestions try again with a higher edit distance. - // "sl_sounddone" is used to avoid doing the same word twice. - suggest_try_soundalike_prep(); - su->su_maxscore = SCORE_SFMAX1; - su->su_sfmaxscore = SCORE_MAXINIT * 3; - suggest_try_soundalike(su); - if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { - // We didn't find enough matches, try again, allowing more - // changes to the soundfold word. - su->su_maxscore = SCORE_SFMAX2; - suggest_try_soundalike(su); - if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { - // Still didn't find enough matches, try again, allowing even - // more changes to the soundfold word. - su->su_maxscore = SCORE_SFMAX3; - suggest_try_soundalike(su); - } - } - su->su_maxscore = su->su_sfmaxscore; - suggest_try_soundalike_finish(); - } - - // When CTRL-C was hit while searching do show the results. Only clear - // got_int when using a command, not for spellsuggest(). - os_breakcheck(); - if (interactive && got_int) { - (void)vgetc(); - got_int = FALSE; - } - - if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) { - if (sps_flags & SPS_BEST) { - // Adjust the word score for how it sounds like. - rescore_suggestions(su); - } - - // Remove bogus suggestions, sort and truncate at "maxcount". - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); - } -} - -// Free the info put in "*su" by spell_find_suggest(). -static void spell_find_cleanup(suginfo_T *su) -{ -#define FREE_SUG_WORD(sug) xfree((sug)->st_word) - // Free the suggestions. - GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD); - GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD); - - // Free the banned words. - hash_clear_all(&su->su_banned, 0); -} - /// Make a copy of "word", with the first letter upper or lower cased, to /// "wcopy[MAXWLEN]". "word" must not be empty. /// The result is NUL terminated. @@ -3547,7 +2648,7 @@ void onecap_copy(char_u *word, char_u *wcopy, bool upper) // Make a copy of "word" with all the letters upper cased into // "wcopy[MAXWLEN]". The result is NUL terminated. -static void allcap_copy(char_u *word, char_u *wcopy) +void allcap_copy(char_u *word, char_u *wcopy) { char_u *d = wcopy; for (char_u *s = word; *s != NUL;) { @@ -3571,1383 +2672,9 @@ static void allcap_copy(char_u *word, char_u *wcopy) *d = NUL; } -// Try finding suggestions by recognizing specific situations. -static void suggest_try_special(suginfo_T *su) -{ - int c; - char_u word[MAXWLEN]; - - // Recognize a word that is repeated: "the the". - char_u *p = skiptowhite(su->su_fbadword); - size_t len = (size_t)(p - su->su_fbadword); - p = (char_u *)skipwhite((char *)p); - if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) { - // Include badflags: if the badword is onecap or allcap - // use that for the goodword too: "The the" -> "The". - c = su->su_fbadword[len]; - su->su_fbadword[len] = NUL; - make_case_word(su->su_fbadword, word, su->su_badflags); - su->su_fbadword[len] = (char_u)c; - - // Give a soundalike score of 0, compute the score as if deleting one - // character. - add_suggestion(su, &su->su_ga, word, su->su_badlen, - RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false); - } -} - -// Measure how much time is spent in each state. -// Output is dumped in "suggestprof". - -#ifdef SUGGEST_PROFILE -proftime_T current; -proftime_T total; -proftime_T times[STATE_FINAL + 1]; -long counts[STATE_FINAL + 1]; - -static void prof_init(void) -{ - for (int i = 0; i <= STATE_FINAL; i++) { - profile_zero(×[i]); - counts[i] = 0; - } - profile_start(¤t); - profile_start(&total); -} - -// call before changing state -static void prof_store(state_T state) -{ - profile_end(¤t); - profile_add(×[state], ¤t); - counts[state]++; - profile_start(¤t); -} -# define PROF_STORE(state) prof_store(state); - -static void prof_report(char *name) -{ - FILE *fd = fopen("suggestprof", "a"); - - profile_end(&total); - fprintf(fd, "-----------------------\n"); - fprintf(fd, "%s: %s\n", name, profile_msg(&total)); - for (int i = 0; i <= STATE_FINAL; i++) { - fprintf(fd, "%d: %s ("%" PRId64)\n", i, profile_msg(×[i]), counts[i]); - } - fclose(fd); -} -#else -# define PROF_STORE(state) -#endif - -// Try finding suggestions by adding/removing/swapping letters. - -static void suggest_try_change(suginfo_T *su) -{ - char_u fword[MAXWLEN]; // copy of the bad word, case-folded - int n; - char_u *p; - langp_T *lp; - - // We make a copy of the case-folded bad word, so that we can modify it - // to find matches (esp. REP items). Append some more text, changing - // chars after the bad word may help. - STRCPY(fword, su->su_fbadword); - n = (int)STRLEN(fword); - p = su->su_badptr + su->su_badlen; - (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n); - - // Make sure the resulting text is not longer than the original text. - n = (int)STRLEN(su->su_badptr); - if (n < MAXWLEN) { - fword[n] = NUL; - } - - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - - // If reloading a spell file fails it's still in the list but - // everything has been cleared. - if (lp->lp_slang->sl_fbyts == NULL) { - continue; - } - - // Try it for this language. Will add possible suggestions. - // -#ifdef SUGGEST_PROFILE - prof_init(); -#endif - suggest_trie_walk(su, lp, fword, false); -#ifdef SUGGEST_PROFILE - prof_report("try_change"); -#endif - } -} - -// Check the maximum score, if we go over it we won't try this change. -#define TRY_DEEPER(su, stack, depth, add) \ - ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) - -// Try finding suggestions by adding/removing/swapping letters. -// -// This uses a state machine. At each node in the tree we try various -// operations. When trying if an operation works "depth" is increased and the -// stack[] is used to store info. This allows combinations, thus insert one -// character, replace one and delete another. The number of changes is -// limited by su->su_maxscore. -// -// After implementing this I noticed an article by Kemal Oflazer that -// describes something similar: "Error-tolerant Finite State Recognition with -// Applications to Morphological Analysis and Spelling Correction" (1996). -// The implementation in the article is simplified and requires a stack of -// unknown depth. The implementation here only needs a stack depth equal to -// the length of the word. -// -// This is also used for the sound-folded word, "soundfold" is true then. -// The mechanism is the same, but we find a match with a sound-folded word -// that comes from one or more original words. Each of these words may be -// added, this is done by add_sound_suggest(). -// Don't use: -// the prefix tree or the keep-case tree -// "su->su_badlen" -// anything to do with upper and lower case -// anything to do with word or non-word characters ("spell_iswordp()") -// banned words -// word flags (rare, region, compounding) -// word splitting for now -// "similar_chars()" -// use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep" -static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool soundfold) -{ - char_u tword[MAXWLEN]; // good word collected so far - trystate_T stack[MAXWLEN]; - char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; - // concatenation of prefix compound - // words and split word. NUL terminated - // when going deeper but not when coming - // back. - char_u compflags[MAXWLEN]; // compound flags, one for each word - trystate_T *sp; - int newscore; - int score; - char_u *byts, *fbyts, *pbyts; - idx_T *idxs, *fidxs, *pidxs; - int depth; - int c, c2, c3; - int n = 0; - int flags; - garray_T *gap; - idx_T arridx; - int len; - char_u *p; - fromto_T *ftp; - int fl = 0, tl; - int repextra = 0; // extra bytes in fword[] from REP item - slang_T *slang = lp->lp_slang; - int fword_ends; - bool goodword_ends; -#ifdef DEBUG_TRIEWALK - // Stores the name of the change made at each level. - char_u changename[MAXWLEN][80]; -#endif - int breakcheckcount = 1000; - bool compound_ok; - - // Go through the whole case-fold tree, try changes at each node. - // "tword[]" contains the word collected from nodes in the tree. - // "fword[]" the word we are trying to match with (initially the bad - // word). - depth = 0; - sp = &stack[0]; - memset(sp, 0, sizeof(trystate_T)); // -V512 - sp->ts_curi = 1; - - if (soundfold) { - // Going through the soundfold tree. - byts = fbyts = slang->sl_sbyts; - idxs = fidxs = slang->sl_sidxs; - pbyts = NULL; - pidxs = NULL; - sp->ts_prefixdepth = PFD_NOPREFIX; - sp->ts_state = STATE_START; - } else { - // When there are postponed prefixes we need to use these first. At - // the end of the prefix we continue in the case-fold tree. - fbyts = slang->sl_fbyts; - fidxs = slang->sl_fidxs; - pbyts = slang->sl_pbyts; - pidxs = slang->sl_pidxs; - if (pbyts != NULL) { - byts = pbyts; - idxs = pidxs; - sp->ts_prefixdepth = PFD_PREFIXTREE; - sp->ts_state = STATE_NOPREFIX; // try without prefix first - } else { - byts = fbyts; - idxs = fidxs; - sp->ts_prefixdepth = PFD_NOPREFIX; - sp->ts_state = STATE_START; - } - } - - // The loop may take an indefinite amount of time. Break out after five - // sectonds. TODO(vim): add an option for the time limit. - proftime_T time_limit = profile_setlimit(5000); - - // Loop to find all suggestions. At each round we either: - // - For the current state try one operation, advance "ts_curi", - // increase "depth". - // - When a state is done go to the next, set "ts_state". - // - When all states are tried decrease "depth". - while (depth >= 0 && !got_int) { - sp = &stack[depth]; - switch (sp->ts_state) { - case STATE_START: - case STATE_NOPREFIX: - // Start of node: Deal with NUL bytes, which means - // tword[] may end here. - arridx = sp->ts_arridx; // current node in the tree - len = byts[arridx]; // bytes in this node - arridx += sp->ts_curi; // index of current byte - - if (sp->ts_prefixdepth == PFD_PREFIXTREE) { - // Skip over the NUL bytes, we use them later. - for (n = 0; n < len && byts[arridx + n] == 0; n++) {} - sp->ts_curi = (int16_t)(sp->ts_curi + n); - - // Always past NUL bytes now. - n = (int)sp->ts_state; - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_ENDNUL; - sp->ts_save_badflags = (char_u)su->su_badflags; - - // At end of a prefix or at start of prefixtree: check for - // following word. - if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { - // Set su->su_badflags to the caps type at this position. - // Use the caps type until here for the prefix itself. - n = nofold_len(fword, sp->ts_fidx, su->su_badptr); - flags = badword_captype(su->su_badptr, su->su_badptr + n); - su->su_badflags = badword_captype(su->su_badptr + n, - su->su_badptr + su->su_badlen); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "prefix"); -#endif - go_deeper(stack, depth, 0); - ++depth; - sp = &stack[depth]; - sp->ts_prefixdepth = (char_u)(depth - 1); - byts = fbyts; - idxs = fidxs; - sp->ts_arridx = 0; - - // Move the prefix to preword[] with the right case - // and make find_keepcap_word() works. - tword[sp->ts_twordlen] = NUL; - make_case_word(tword + sp->ts_splitoff, - preword + sp->ts_prewordlen, flags); - sp->ts_prewordlen = (char_u)STRLEN(preword); - sp->ts_splitoff = sp->ts_twordlen; - } - break; - } - - if (sp->ts_curi > len || byts[arridx] != 0) { - // Past bytes in node and/or past NUL bytes. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_ENDNUL; - sp->ts_save_badflags = (char_u)su->su_badflags; - break; - } - - // End of word in tree. - ++sp->ts_curi; // eat one NUL byte - - flags = (int)idxs[arridx]; - - // Skip words with the NOSUGGEST flag. - if (flags & WF_NOSUGGEST) { - break; - } - - fword_ends = (fword[sp->ts_fidx] == NUL - || (soundfold - ? ascii_iswhite(fword[sp->ts_fidx]) - : !spell_iswordp(fword + sp->ts_fidx, curwin))); - tword[sp->ts_twordlen] = NUL; - - if (sp->ts_prefixdepth <= PFD_NOTSPECIAL - && (sp->ts_flags & TSF_PREFIXOK) == 0 - && pbyts != NULL) { - // There was a prefix before the word. Check that the prefix - // can be used with this word. - // Count the length of the NULs in the prefix. If there are - // none this must be the first try without a prefix. - n = stack[sp->ts_prefixdepth].ts_arridx; - len = pbyts[n++]; - for (c = 0; c < len && pbyts[n + c] == 0; c++) {} - if (c > 0) { - c = valid_word_prefix(c, n, flags, - tword + sp->ts_splitoff, slang, false); - if (c == 0) { - break; - } - - // Use the WF_RARE flag for a rare prefix. - if (c & WF_RAREPFX) { - flags |= WF_RARE; - } - - // Tricky: when checking for both prefix and compounding - // we run into the prefix flag first. - // Remember that it's OK, so that we accept the prefix - // when arriving at a compound flag. - sp->ts_flags |= TSF_PREFIXOK; - } - } - - // Check NEEDCOMPOUND: can't use word without compounding. Do try - // appending another compound word below. - if (sp->ts_complen == sp->ts_compsplit && fword_ends - && (flags & WF_NEEDCOMP)) { - goodword_ends = false; - } else { - goodword_ends = true; - } - - p = NULL; - compound_ok = true; - if (sp->ts_complen > sp->ts_compsplit) { - if (slang->sl_nobreak) { - // There was a word before this word. When there was no - // change in this word (it was correct) add the first word - // as a suggestion. If this word was corrected too, we - // need to check if a correct word follows. - if (sp->ts_fidx - sp->ts_splitfidx - == sp->ts_twordlen - sp->ts_splitoff - && STRNCMP(fword + sp->ts_splitfidx, - tword + sp->ts_splitoff, - sp->ts_fidx - sp->ts_splitfidx) == 0) { - preword[sp->ts_prewordlen] = NUL; - newscore = score_wordcount_adj(slang, sp->ts_score, - preword + sp->ts_prewordlen, - sp->ts_prewordlen > 0); - // Add the suggestion if the score isn't too bad. - if (newscore <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, preword, - sp->ts_splitfidx - repextra, - newscore, 0, false, - lp->lp_sallang, false); - } - break; - } - } else { - // There was a compound word before this word. If this - // word does not support compounding then give up - // (splitting is tried for the word without compound - // flag). - if (((unsigned)flags >> 24) == 0 - || sp->ts_twordlen - sp->ts_splitoff - < slang->sl_compminlen) { - break; - } - // For multi-byte chars check character length against - // COMPOUNDMIN. - if (slang->sl_compminlen > 0 - && mb_charlen(tword + sp->ts_splitoff) - < slang->sl_compminlen) { - break; - } - - compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); - compflags[sp->ts_complen + 1] = NUL; - STRLCPY(preword + sp->ts_prewordlen, - tword + sp->ts_splitoff, - sp->ts_twordlen - sp->ts_splitoff + 1); - - // Verify CHECKCOMPOUNDPATTERN rules. - if (match_checkcompoundpattern(preword, sp->ts_prewordlen, - &slang->sl_comppat)) { - compound_ok = false; - } - - if (compound_ok) { - p = preword; - while (*skiptowhite(p) != NUL) { - p = (char_u *)skipwhite((char *)skiptowhite(p)); - } - if (fword_ends && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { - // Compound is not allowed. But it may still be - // possible if we add another (short) word. - compound_ok = false; - } - } - - // Get pointer to last char of previous word. - p = preword + sp->ts_prewordlen; - MB_PTR_BACK(preword, p); - } - } - - // Form the word with proper case in preword. - // If there is a word from a previous split, append. - // For the soundfold tree don't change the case, simply append. - if (soundfold) { - STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff); - } else if (flags & WF_KEEPCAP) { - // Must find the word in the keep-case tree. - find_keepcap_word(slang, tword + sp->ts_splitoff, - preword + sp->ts_prewordlen); - } else { - // Include badflags: If the badword is onecap or allcap - // use that for the goodword too. But if the badword is - // allcap and it's only one char long use onecap. - c = su->su_badflags; - if ((c & WF_ALLCAP) - && su->su_badlen == - utfc_ptr2len((char *)su->su_badptr)) { - c = WF_ONECAP; - } - c |= flags; - - // When appending a compound word after a word character don't - // use Onecap. - if (p != NULL && spell_iswordp_nmw(p, curwin)) { - c &= ~WF_ONECAP; - } - make_case_word(tword + sp->ts_splitoff, - preword + sp->ts_prewordlen, c); - } - - if (!soundfold) { - // Don't use a banned word. It may appear again as a good - // word, thus remember it. - if (flags & WF_BANNED) { - add_banned(su, preword + sp->ts_prewordlen); - break; - } - if ((sp->ts_complen == sp->ts_compsplit - && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen)) - || WAS_BANNED(su, (char *)preword)) { - if (slang->sl_compprog == NULL) { - break; - } - // the word so far was banned but we may try compounding - goodword_ends = false; - } - } - - newscore = 0; - if (!soundfold) { // soundfold words don't have flags - if ((flags & WF_REGION) - && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { - newscore += SCORE_REGION; - } - if (flags & WF_RARE) { - newscore += SCORE_RARE; - } - - if (!spell_valid_case(su->su_badflags, - captype(preword + sp->ts_prewordlen, NULL))) { - newscore += SCORE_ICASE; - } - } - - // TODO: how about splitting in the soundfold tree? - if (fword_ends - && goodword_ends - && sp->ts_fidx >= sp->ts_fidxtry - && compound_ok) { - // The badword also ends: add suggestions. -#ifdef DEBUG_TRIEWALK - if (soundfold && STRCMP(preword, "smwrd") == 0) { - int j; - - // print the stack of changes that brought us here - smsg("------ %s -------", fword); - for (j = 0; j < depth; ++j) { - smsg("%s", changename[j]); - } - } -#endif - if (soundfold) { - // For soundfolded words we need to find the original - // words, the edit distance and then add them. - add_sound_suggest(su, preword, sp->ts_score, lp); - } else if (sp->ts_fidx > 0) { - // Give a penalty when changing non-word char to word - // char, e.g., "thes," -> "these". - p = fword + sp->ts_fidx; - MB_PTR_BACK(fword, p); - if (!spell_iswordp(p, curwin) && *preword != NUL) { - p = preword + STRLEN(preword); - MB_PTR_BACK(preword, p); - if (spell_iswordp(p, curwin)) { - newscore += SCORE_NONWORD; - } - } - - // Give a bonus to words seen before. - score = score_wordcount_adj(slang, - sp->ts_score + newscore, - preword + sp->ts_prewordlen, - sp->ts_prewordlen > 0); - - // Add the suggestion if the score isn't too bad. - if (score <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, preword, - sp->ts_fidx - repextra, - score, 0, false, lp->lp_sallang, false); - - if (su->su_badflags & WF_MIXCAP) { - // We really don't know if the word should be - // upper or lower case, add both. - c = captype(preword, NULL); - if (c == 0 || c == WF_ALLCAP) { - make_case_word(tword + sp->ts_splitoff, - preword + sp->ts_prewordlen, - c == 0 ? WF_ALLCAP : 0); - - add_suggestion(su, &su->su_ga, preword, - sp->ts_fidx - repextra, - score + SCORE_ICASE, 0, false, - lp->lp_sallang, false); - } - } - } - } - } - - // Try word split and/or compounding. - if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends) - // Don't split in the middle of a character - && (sp->ts_tcharlen == 0)) { - bool try_compound; - int try_split; - - // If past the end of the bad word don't try a split. - // Otherwise try changing the next word. E.g., find - // suggestions for "the the" where the second "the" is - // different. It's done like a split. - // TODO: word split for soundfold words - try_split = (sp->ts_fidx - repextra < su->su_badlen) - && !soundfold; - - // Get here in several situations: - // 1. The word in the tree ends: - // If the word allows compounding try that. Otherwise try - // a split by inserting a space. For both check that a - // valid words starts at fword[sp->ts_fidx]. - // For NOBREAK do like compounding to be able to check if - // the next word is valid. - // 2. The badword does end, but it was due to a change (e.g., - // a swap). No need to split, but do check that the - // following word is valid. - // 3. The badword and the word in the tree end. It may still - // be possible to compound another (short) word. - try_compound = false; - if (!soundfold - && !slang->sl_nocompoundsugs - && slang->sl_compprog != NULL - && ((unsigned)flags >> 24) != 0 - && sp->ts_twordlen - sp->ts_splitoff - >= slang->sl_compminlen - && (slang->sl_compminlen == 0 - || mb_charlen(tword + sp->ts_splitoff) - >= slang->sl_compminlen) - && (slang->sl_compsylmax < MAXWLEN - || sp->ts_complen + 1 - sp->ts_compsplit - < slang->sl_compmax) - && (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) { - try_compound = true; - compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); - compflags[sp->ts_complen + 1] = NUL; - } - - // For NOBREAK we never try splitting, it won't make any word - // valid. - if (slang->sl_nobreak && !slang->sl_nocompoundsugs) { - try_compound = true; - } else if (!fword_ends - && try_compound - && (sp->ts_flags & TSF_DIDSPLIT) == 0) { - // If we could add a compound word, and it's also possible to - // split at this point, do the split first and set - // TSF_DIDSPLIT to avoid doing it again. - try_compound = false; - sp->ts_flags |= TSF_DIDSPLIT; - --sp->ts_curi; // do the same NUL again - compflags[sp->ts_complen] = NUL; - } else { - sp->ts_flags &= (char_u) ~TSF_DIDSPLIT; - } - - if (try_split || try_compound) { - if (!try_compound && (!fword_ends || !goodword_ends)) { - // If we're going to split need to check that the - // words so far are valid for compounding. If there - // is only one word it must not have the NEEDCOMPOUND - // flag. - if (sp->ts_complen == sp->ts_compsplit - && (flags & WF_NEEDCOMP)) { - break; - } - p = preword; - while (*skiptowhite(p) != NUL) { - p = (char_u *)skipwhite((char *)skiptowhite(p)); - } - if (sp->ts_complen > sp->ts_compsplit - && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { - break; - } - - if (slang->sl_nosplitsugs) { - newscore += SCORE_SPLIT_NO; - } else { - newscore += SCORE_SPLIT; - } - - // Give a bonus to words seen before. - newscore = score_wordcount_adj(slang, newscore, - preword + sp->ts_prewordlen, true); - } - - if (TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - if (!try_compound && !fword_ends) { - sprintf(changename[depth], "%.*s-%s: split", - sp->ts_twordlen, tword, fword + sp->ts_fidx); - } else { - sprintf(changename[depth], "%.*s-%s: compound", - sp->ts_twordlen, tword, fword + sp->ts_fidx); - } -#endif - // Save things to be restored at STATE_SPLITUNDO. - sp->ts_save_badflags = (char_u)su->su_badflags; - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SPLITUNDO; - - ++depth; - sp = &stack[depth]; - - // Append a space to preword when splitting. - if (!try_compound && !fword_ends) { - STRCAT(preword, " "); - } - sp->ts_prewordlen = (char_u)STRLEN(preword); - sp->ts_splitoff = sp->ts_twordlen; - sp->ts_splitfidx = sp->ts_fidx; - - // If the badword has a non-word character at this - // position skip it. That means replacing the - // non-word character with a space. Always skip a - // character when the word ends. But only when the - // good word can end. - if (((!try_compound && !spell_iswordp_nmw(fword - + sp->ts_fidx, - curwin)) - || fword_ends) - && fword[sp->ts_fidx] != NUL - && goodword_ends) { - int l; - - l = utfc_ptr2len((char *)fword + sp->ts_fidx); - if (fword_ends) { - // Copy the skipped character to preword. - memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l); - sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l); - preword[sp->ts_prewordlen] = NUL; - } else { - sp->ts_score -= SCORE_SPLIT - SCORE_SUBST; - } - sp->ts_fidx = (char_u)(sp->ts_fidx + l); - } - - // When compounding include compound flag in - // compflags[] (already set above). When splitting we - // may start compounding over again. - if (try_compound) { - ++sp->ts_complen; - } else { - sp->ts_compsplit = sp->ts_complen; - } - sp->ts_prefixdepth = PFD_NOPREFIX; - - // set su->su_badflags to the caps type at this - // position - n = nofold_len(fword, sp->ts_fidx, su->su_badptr); - su->su_badflags = badword_captype(su->su_badptr + n, - su->su_badptr + su->su_badlen); - - // Restart at top of the tree. - sp->ts_arridx = 0; - - // If there are postponed prefixes, try these too. - if (pbyts != NULL) { - byts = pbyts; - idxs = pidxs; - sp->ts_prefixdepth = PFD_PREFIXTREE; - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_NOPREFIX; - } - } - } - } - break; - - case STATE_SPLITUNDO: - // Undo the changes done for word split or compound word. - su->su_badflags = sp->ts_save_badflags; - - // Continue looking for NUL bytes. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_START; - - // In case we went into the prefix tree. - byts = fbyts; - idxs = fidxs; - break; - - case STATE_ENDNUL: - // Past the NUL bytes in the node. - su->su_badflags = sp->ts_save_badflags; - if (fword[sp->ts_fidx] == NUL - && sp->ts_tcharlen == 0) { - // The badword ends, can't use STATE_PLAIN. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_DEL; - break; - } - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_PLAIN; - FALLTHROUGH; - - case STATE_PLAIN: - // Go over all possible bytes at this node, add each to tword[] - // and use child node. "ts_curi" is the index. - arridx = sp->ts_arridx; - if (sp->ts_curi > byts[arridx]) { - // Done all bytes at this node, do next state. When still at - // already changed bytes skip the other tricks. - PROF_STORE(sp->ts_state) - if (sp->ts_fidx >= sp->ts_fidxtry) { - sp->ts_state = STATE_DEL; - } else { - sp->ts_state = STATE_FINAL; - } - } else { - arridx += sp->ts_curi++; - c = byts[arridx]; - - // Normal byte, go one level deeper. If it's not equal to the - // byte in the bad word adjust the score. But don't even try - // when the byte was already changed. And don't try when we - // just deleted this byte, accepting it is always cheaper than - // delete + substitute. - if (c == fword[sp->ts_fidx] - || (sp->ts_tcharlen > 0 - && sp->ts_isdiff != DIFF_NONE)) { - newscore = 0; - } else { - newscore = SCORE_SUBST; - } - if ((newscore == 0 - || (sp->ts_fidx >= sp->ts_fidxtry - && ((sp->ts_flags & TSF_DIDDEL) == 0 - || c != fword[sp->ts_delidx]))) - && TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - if (newscore > 0) { - sprintf(changename[depth], "%.*s-%s: subst %c to %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - fword[sp->ts_fidx], c); - } else { - sprintf(changename[depth], "%.*s-%s: accept %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - fword[sp->ts_fidx]); - } -#endif - ++depth; - sp = &stack[depth]; - if (fword[sp->ts_fidx] != NUL) { - sp->ts_fidx++; - } - tword[sp->ts_twordlen++] = (char_u)c; - sp->ts_arridx = idxs[arridx]; - if (newscore == SCORE_SUBST) { - sp->ts_isdiff = DIFF_YES; - } - // Multi-byte characters are a bit complicated to - // handle: They differ when any of the bytes differ - // and then their length may also differ. - if (sp->ts_tcharlen == 0) { - // First byte. - sp->ts_tcharidx = 0; - sp->ts_tcharlen = MB_BYTE2LEN(c); - sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1); - sp->ts_isdiff = (newscore != 0) - ? DIFF_YES : DIFF_NONE; - } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) { - // When inserting trail bytes don't advance in the - // bad word. - sp->ts_fidx--; - } - if (++sp->ts_tcharidx == sp->ts_tcharlen) { - // Last byte of character. - if (sp->ts_isdiff == DIFF_YES) { - // Correct ts_fidx for the byte length of the - // character (we didn't check that before). - sp->ts_fidx = (char_u)(sp->ts_fcharstart - + utfc_ptr2len((char *)fword + sp->ts_fcharstart)); - - // For changing a composing character adjust - // the score from SCORE_SUBST to - // SCORE_SUBCOMP. - if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen - - sp->ts_tcharlen)) - && utf_iscomposing(utf_ptr2char((char *)fword - + sp->ts_fcharstart))) { - sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; - } else if (!soundfold - && slang->sl_has_map - && similar_chars(slang, - utf_ptr2char((char *)tword + sp->ts_twordlen - - sp->ts_tcharlen), - utf_ptr2char((char *)fword + sp->ts_fcharstart))) { - // For a similar character adjust score from - // SCORE_SUBST to SCORE_SIMILAR. - sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR; - } - } else if (sp->ts_isdiff == DIFF_INSERT - && sp->ts_twordlen > sp->ts_tcharlen) { - p = tword + sp->ts_twordlen - sp->ts_tcharlen; - c = utf_ptr2char((char *)p); - if (utf_iscomposing(c)) { - // Inserting a composing char doesn't - // count that much. - sp->ts_score -= SCORE_INS - SCORE_INSCOMP; - } else { - // If the previous character was the same, - // thus doubling a character, give a bonus - // to the score. Also for the soundfold - // tree (might seem illogical but does - // give better scores). - MB_PTR_BACK(tword, p); - if (c == utf_ptr2char((char *)p)) { - sp->ts_score -= SCORE_INS - SCORE_INSDUP; - } - } - } - - // Starting a new char, reset the length. - sp->ts_tcharlen = 0; - } - } - } - break; - - case STATE_DEL: - // When past the first byte of a multi-byte char don't try - // delete/insert/swap a character. - if (sp->ts_tcharlen > 0) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - // Try skipping one character in the bad word (delete it). - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_INS_PREP; - sp->ts_curi = 1; - if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') { - // Deleting a vowel at the start of a word counts less, see - // soundalike_score(). - newscore = 2 * SCORE_DEL / 3; - } else { - newscore = SCORE_DEL; - } - if (fword[sp->ts_fidx] != NUL - && TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: delete %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - fword[sp->ts_fidx]); -#endif - ++depth; - - // Remember what character we deleted, so that we can avoid - // inserting it again. - stack[depth].ts_flags |= TSF_DIDDEL; - stack[depth].ts_delidx = sp->ts_fidx; - - // Advance over the character in fword[]. Give a bonus to the - // score if the same character is following "nn" -> "n". It's - // a bit illogical for soundfold tree but it does give better - // results. - c = utf_ptr2char((char *)fword + sp->ts_fidx); - stack[depth].ts_fidx = - (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)fword + sp->ts_fidx)); - if (utf_iscomposing(c)) { - stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; - } else if (c == utf_ptr2char((char *)fword + stack[depth].ts_fidx)) { - stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; - } - - break; - } - FALLTHROUGH; - - case STATE_INS_PREP: - if (sp->ts_flags & TSF_DIDDEL) { - // If we just deleted a byte then inserting won't make sense, - // a substitute is always cheaper. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP; - break; - } - - // skip over NUL bytes - n = sp->ts_arridx; - for (;;) { - if (sp->ts_curi > byts[n]) { - // Only NUL bytes at this node, go to next state. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP; - break; - } - if (byts[n + sp->ts_curi] != NUL) { - // Found a byte to insert. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_INS; - break; - } - ++sp->ts_curi; - } - break; - - case STATE_INS: - // Insert one byte. Repeat this for each possible byte at this - // node. - n = sp->ts_arridx; - if (sp->ts_curi > byts[n]) { - // Done all bytes at this node, go to next state. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP; - break; - } - - // Do one more byte at this node, but: - // - Skip NUL bytes. - // - Skip the byte if it's equal to the byte in the word, - // accepting that byte is always better. - n += sp->ts_curi++; - c = byts[n]; - if (soundfold && sp->ts_twordlen == 0 && c == '*') { - // Inserting a vowel at the start of a word counts less, - // see soundalike_score(). - newscore = 2 * SCORE_INS / 3; - } else { - newscore = SCORE_INS; - } - if (c != fword[sp->ts_fidx] - && TRY_DEEPER(su, stack, depth, newscore)) { - go_deeper(stack, depth, newscore); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: insert %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - c); -#endif - ++depth; - sp = &stack[depth]; - tword[sp->ts_twordlen++] = (char_u)c; - sp->ts_arridx = idxs[n]; - fl = MB_BYTE2LEN(c); - if (fl > 1) { - // There are following bytes for the same character. - // We must find all bytes before trying - // delete/insert/swap/etc. - sp->ts_tcharlen = (char_u)fl; - sp->ts_tcharidx = 1; - sp->ts_isdiff = DIFF_INSERT; - } - if (fl == 1) { - // If the previous character was the same, thus doubling a - // character, give a bonus to the score. Also for - // soundfold words (illogical but does give a better - // score). - if (sp->ts_twordlen >= 2 - && tword[sp->ts_twordlen - 2] == c) { - sp->ts_score -= SCORE_INS - SCORE_INSDUP; - } - } - } - break; - - case STATE_SWAP: - // Swap two bytes in the bad word: "12" -> "21". - // We change "fword" here, it's changed back afterwards at - // STATE_UNSWAP. - p = fword + sp->ts_fidx; - c = *p; - if (c == NUL) { - // End of word, can't swap or replace. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - - // Don't swap if the first character is not a word character. - // SWAP3 etc. also don't make sense then. - if (!soundfold && !spell_iswordp(p, curwin)) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - if (p[n] == NUL) { - c2 = NUL; - } else if (!soundfold && !spell_iswordp(p + n, curwin)) { - c2 = c; // don't swap non-word char - } else { - c2 = utf_ptr2char((char *)p + n); - } - - // When the second character is NUL we can't swap. - if (c2 == NUL) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - - // When characters are identical, swap won't do anything. - // Also get here if the second char is not a word character. - if (c == c2) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_SWAP3; - break; - } - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) { - go_deeper(stack, depth, SCORE_SWAP); -#ifdef DEBUG_TRIEWALK - snprintf(changename[depth], sizeof(changename[0]), - "%.*s-%s: swap %c and %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - c, c2); -#endif - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_UNSWAP; - depth++; - fl = utf_char2len(c2); - memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); - } else { - // If this swap doesn't work then SWAP3 won't either. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - case STATE_UNSWAP: - // Undo the STATE_SWAP swap: "21" -> "12". - p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - c = utf_ptr2char((char *)p + n); - memmove(p + utfc_ptr2len((char *)p + n), p, (size_t)n); - utf_char2bytes(c, (char *)p); - - FALLTHROUGH; - - case STATE_SWAP3: - // Swap two bytes, skipping one: "123" -> "321". We change - // "fword" here, it's changed back afterwards at STATE_UNSWAP3. - p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - fl = utf_ptr2len((char *)p + n); - c2 = utf_ptr2char((char *)p + n); - if (!soundfold && !spell_iswordp(p + n + fl, curwin)) { - c3 = c; // don't swap non-word char - } else { - c3 = utf_ptr2char((char *)p + n + fl); - } - - // When characters are identical: "121" then SWAP3 result is - // identical, ROT3L result is same as SWAP: "211", ROT3L result is - // same as SWAP on next char: "112". Thus skip all swapping. - // Also skip when c3 is NUL. - // Also get here when the third character is not a word character. - // Second character may any char: "a.b" -> "b.a" - if (c == c3 || c3 == NUL) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { - go_deeper(stack, depth, SCORE_SWAP3); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: swap3 %c and %c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - c, c3); -#endif - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_UNSWAP3; - depth++; - tl = utf_char2len(c3); - memmove(p, p + n + fl, (size_t)tl); - utf_char2bytes(c2, (char *)p + tl); - utf_char2bytes(c, (char *)p + fl + tl); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl); - } else { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - case STATE_UNSWAP3: - // Undo STATE_SWAP3: "321" -> "123" - p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - c2 = utf_ptr2char((char *)p + n); - fl = utfc_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n + fl); - tl = utfc_ptr2len((char *)p + n + fl); - memmove(p + fl + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - utf_char2bytes(c2, (char *)p + tl); - p = p + tl; - - if (!soundfold && !spell_iswordp(p, curwin)) { - // Middle char is not a word char, skip the rotate. First and - // third char were already checked at swap and swap3. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - break; - } - - // Rotate three characters left: "123" -> "231". We change - // "fword" here, it's changed back afterwards at STATE_UNROT3L. - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { - go_deeper(stack, depth, SCORE_SWAP3); -#ifdef DEBUG_TRIEWALK - p = fword + sp->ts_fidx; - sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - p[0], p[1], p[2]); -#endif - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_UNROT3L; - ++depth; - p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - fl = utf_ptr2len((char *)p + n); - fl += utf_ptr2len((char *)p + n + fl); - memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); - } else { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - case STATE_UNROT3L: - // Undo ROT3L: "231" -> "123" - p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - n += utfc_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n); - tl = utfc_ptr2len((char *)p + n); - memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - - // Rotate three bytes right: "123" -> "312". We change "fword" - // here, it's changed back afterwards at STATE_UNROT3R. - if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { - go_deeper(stack, depth, SCORE_SWAP3); -#ifdef DEBUG_TRIEWALK - p = fword + sp->ts_fidx; - sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - p[0], p[1], p[2]); -#endif - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_UNROT3R; - ++depth; - p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - n += utf_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n); - tl = utf_ptr2len((char *)p + n); - memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl); - } else { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_INI; - } - break; - - case STATE_UNROT3R: - // Undo ROT3R: "312" -> "123" - p = fword + sp->ts_fidx; - c = utf_ptr2char((char *)p); - tl = utfc_ptr2len((char *)p); - n = utfc_ptr2len((char *)p + tl); - n += utfc_ptr2len((char *)p + tl + n); - memmove(p, p + tl, (size_t)n); - utf_char2bytes(c, (char *)p + n); - - FALLTHROUGH; - - case STATE_REP_INI: - // Check if matching with REP items from the .aff file would work. - // Quickly skip if: - // - there are no REP items and we are not in the soundfold trie - // - the score is going to be too high anyway - // - already applied a REP item or swapped here - if ((lp->lp_replang == NULL && !soundfold) - || sp->ts_score + SCORE_REP >= su->su_maxscore - || sp->ts_fidx < sp->ts_fidxtry) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - - // Use the first byte to quickly find the first entry that may - // match. If the index is -1 there is none. - if (soundfold) { - sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]]; - } else { - sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; - } - - if (sp->ts_curi < 0) { - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - break; - } - - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP; - FALLTHROUGH; - - case STATE_REP: - // Try matching with REP items from the .aff file. For each match - // replace the characters and check if the resulting word is - // valid. - p = fword + sp->ts_fidx; - - if (soundfold) { - gap = &slang->sl_repsal; - } else { - gap = &lp->lp_replang->sl_rep; - } - while (sp->ts_curi < gap->ga_len) { - ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; - if (*ftp->ft_from != *p) { - // past possible matching entries - sp->ts_curi = (char_u)gap->ga_len; - break; - } - if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 - && TRY_DEEPER(su, stack, depth, SCORE_REP)) { - go_deeper(stack, depth, SCORE_REP); -#ifdef DEBUG_TRIEWALK - sprintf(changename[depth], "%.*s-%s: replace %s with %s", - sp->ts_twordlen, tword, fword + sp->ts_fidx, - ftp->ft_from, ftp->ft_to); -#endif - // Need to undo this afterwards. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP_UNDO; - - // Change the "from" to the "to" string. - ++depth; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); - if (fl != tl) { - STRMOVE(p + tl, p + fl); - repextra += tl - fl; - } - memmove(p, ftp->ft_to, (size_t)tl); - stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl); - stack[depth].ts_tcharlen = 0; - break; - } - } - - if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) { - // No (more) matches. - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_FINAL; - } - - break; - - case STATE_REP_UNDO: - // Undo a REP replacement and continue with the next one. - if (soundfold) { - gap = &slang->sl_repsal; - } else { - gap = &lp->lp_replang->sl_rep; - } - ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); - p = fword + sp->ts_fidx; - if (fl != tl) { - STRMOVE(p + fl, p + tl); - repextra -= tl - fl; - } - memmove(p, ftp->ft_from, (size_t)fl); - PROF_STORE(sp->ts_state) - sp->ts_state = STATE_REP; - break; - - default: - // Did all possible states at this level, go up one level. - --depth; - - if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) { - // Continue in or go back to the prefix tree. - byts = pbyts; - idxs = pidxs; - } - - // Don't check for CTRL-C too often, it takes time. - if (--breakcheckcount == 0) { - os_breakcheck(); - breakcheckcount = 1000; - if (profile_passed_limit(time_limit)) { - got_int = true; - } - } - } - } -} - -// Go one level deeper in the tree. -static void go_deeper(trystate_T *stack, int depth, int score_add) -{ - stack[depth + 1] = stack[depth]; - stack[depth + 1].ts_state = STATE_START; - stack[depth + 1].ts_score = stack[depth].ts_score + score_add; - stack[depth + 1].ts_curi = 1; // start just after length byte - stack[depth + 1].ts_flags = 0; -} - // Case-folding may change the number of bytes: Count nr of chars in // fword[flen] and return the byte length of that many chars in "word". -static int nofold_len(char_u *fword, int flen, char_u *word) +int nofold_len(char_u *fword, int flen, char_u *word) { char_u *p; int i = 0; @@ -4961,677 +2688,8 @@ static int nofold_len(char_u *fword, int flen, char_u *word) return (int)(p - word); } -// "fword" is a good word with case folded. Find the matching keep-case -// words and put it in "kword". -// Theoretically there could be several keep-case words that result in the -// same case-folded word, but we only find one... -static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) -{ - char_u uword[MAXWLEN]; // "fword" in upper-case - int depth; - idx_T tryidx; - - // The following arrays are used at each depth in the tree. - idx_T arridx[MAXWLEN]; - int round[MAXWLEN]; - int fwordidx[MAXWLEN]; - int uwordidx[MAXWLEN]; - int kwordlen[MAXWLEN]; - - int flen, ulen; - int l; - int len; - int c; - idx_T lo, hi, m; - char_u *p; - char_u *byts = slang->sl_kbyts; // array with bytes of the words - idx_T *idxs = slang->sl_kidxs; // array with indexes - - if (byts == NULL) { - // array is empty: "cannot happen" - *kword = NUL; - return; - } - - // Make an all-cap version of "fword". - allcap_copy(fword, uword); - - // Each character needs to be tried both case-folded and upper-case. - // All this gets very complicated if we keep in mind that changing case - // may change the byte length of a multi-byte character... - depth = 0; - arridx[0] = 0; - round[0] = 0; - fwordidx[0] = 0; - uwordidx[0] = 0; - kwordlen[0] = 0; - while (depth >= 0) { - if (fword[fwordidx[depth]] == NUL) { - // We are at the end of "fword". If the tree allows a word to end - // here we have found a match. - if (byts[arridx[depth] + 1] == 0) { - kword[kwordlen[depth]] = NUL; - return; - } - - // kword is getting too long, continue one level up - --depth; - } else if (++round[depth] > 2) { - // tried both fold-case and upper-case character, continue one - // level up - --depth; - } else { - // round[depth] == 1: Try using the folded-case character. - // round[depth] == 2: Try using the upper-case character. - flen = utf_ptr2len((char *)fword + fwordidx[depth]); - ulen = utf_ptr2len((char *)uword + uwordidx[depth]); - if (round[depth] == 1) { - p = fword + fwordidx[depth]; - l = flen; - } else { - p = uword + uwordidx[depth]; - l = ulen; - } - - for (tryidx = arridx[depth]; l > 0; --l) { - // Perform a binary search in the list of accepted bytes. - len = byts[tryidx++]; - c = *p++; - lo = tryidx; - hi = tryidx + len - 1; - while (lo < hi) { - m = (lo + hi) / 2; - if (byts[m] > c) { - hi = m - 1; - } else if (byts[m] < c) { - lo = m + 1; - } else { - lo = hi = m; - break; - } - } - - // Stop if there is no matching byte. - if (hi < lo || byts[lo] != c) { - break; - } - - // Continue at the child (if there is one). - tryidx = idxs[lo]; - } - - if (l == 0) { - // Found the matching char. Copy it to "kword" and go a - // level deeper. - if (round[depth] == 1) { - STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], - flen); - kwordlen[depth + 1] = kwordlen[depth] + flen; - } else { - STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], - ulen); - kwordlen[depth + 1] = kwordlen[depth] + ulen; - } - fwordidx[depth + 1] = fwordidx[depth] + flen; - uwordidx[depth + 1] = uwordidx[depth] + ulen; - - ++depth; - arridx[depth] = tryidx; - round[depth] = 0; - } - } - } - - // Didn't find it: "cannot happen". - *kword = NUL; -} - -// Compute the sound-a-like score for suggestions in su->su_ga and add them to -// su->su_sga. -static void score_comp_sal(suginfo_T *su) -{ - langp_T *lp; - char_u badsound[MAXWLEN]; - int i; - suggest_T *stp; - suggest_T *sstp; - int score; - - ga_grow(&su->su_sga, su->su_ga.ga_len); - - // Use the sound-folding of the first language that supports it. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { - // soundfold the bad word - spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound); - - for (i = 0; i < su->su_ga.ga_len; ++i) { - stp = &SUG(su->su_ga, i); - - // Case-fold the suggested word, sound-fold it and compute the - // sound-a-like score. - score = stp_sal_score(stp, su, lp->lp_slang, badsound); - if (score < SCORE_MAXMAX) { - // Add the suggestion. - sstp = &SUG(su->su_sga, su->su_sga.ga_len); - sstp->st_word = vim_strsave(stp->st_word); - sstp->st_wordlen = stp->st_wordlen; - sstp->st_score = score; - sstp->st_altscore = 0; - sstp->st_orglen = stp->st_orglen; - ++su->su_sga.ga_len; - } - } - break; - } - } -} - -// Combine the list of suggestions in su->su_ga and su->su_sga. -// They are entwined. -static void score_combine(suginfo_T *su) -{ - garray_T ga; - garray_T *gap; - langp_T *lp; - suggest_T *stp; - char_u *p; - char_u badsound[MAXWLEN]; - int round; - slang_T *slang = NULL; - - // Add the alternate score to su_ga. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { - // soundfold the bad word - slang = lp->lp_slang; - spell_soundfold(slang, su->su_fbadword, true, badsound); - - for (int i = 0; i < su->su_ga.ga_len; ++i) { - stp = &SUG(su->su_ga, i); - stp->st_altscore = stp_sal_score(stp, su, slang, badsound); - if (stp->st_altscore == SCORE_MAXMAX) { - stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4; - } else { - stp->st_score = (stp->st_score * 3 - + stp->st_altscore) / 4; - } - stp->st_salscore = false; - } - break; - } - } - - if (slang == NULL) { // Using "double" without sound folding. - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, - su->su_maxcount); - return; - } - - // Add the alternate score to su_sga. - for (int i = 0; i < su->su_sga.ga_len; ++i) { - stp = &SUG(su->su_sga, i); - stp->st_altscore = spell_edit_score(slang, - su->su_badword, stp->st_word); - if (stp->st_score == SCORE_MAXMAX) { - stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; - } else { - stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8; - } - stp->st_salscore = true; - } - - // Remove bad suggestions, sort the suggestions and truncate at "maxcount" - // for both lists. - check_suggestions(su, &su->su_ga); - (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); - check_suggestions(su, &su->su_sga); - (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount); - - ga_init(&ga, (int)sizeof(suginfo_T), 1); - ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len); - - stp = &SUG(ga, 0); - for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; ++i) { - // round 1: get a suggestion from su_ga - // round 2: get a suggestion from su_sga - for (round = 1; round <= 2; ++round) { - gap = round == 1 ? &su->su_ga : &su->su_sga; - if (i < gap->ga_len) { - // Don't add a word if it's already there. - p = SUG(*gap, i).st_word; - int j; - for (j = 0; j < ga.ga_len; ++j) { - if (STRCMP(stp[j].st_word, p) == 0) { - break; - } - } - if (j == ga.ga_len) { - stp[ga.ga_len++] = SUG(*gap, i); - } else { - xfree(p); - } - } - } - } - - ga_clear(&su->su_ga); - ga_clear(&su->su_sga); - - // Truncate the list to the number of suggestions that will be displayed. - if (ga.ga_len > su->su_maxcount) { - for (int i = su->su_maxcount; i < ga.ga_len; ++i) { - xfree(stp[i].st_word); - } - ga.ga_len = su->su_maxcount; - } - - su->su_ga = ga; -} - -/// For the goodword in "stp" compute the soundalike score compared to the -/// badword. -/// -/// @param badsound sound-folded badword -static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound) -{ - char_u *p; - char_u *pbad; - char_u *pgood; - char_u badsound2[MAXWLEN]; - char_u fword[MAXWLEN]; - char_u goodsound[MAXWLEN]; - char_u goodword[MAXWLEN]; - int lendiff; - - lendiff = su->su_badlen - stp->st_orglen; - if (lendiff >= 0) { - pbad = badsound; - } else { - // soundfold the bad word with more characters following - (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN); - - // When joining two words the sound often changes a lot. E.g., "t he" - // sounds like "t h" while "the" sounds like "@". Avoid that by - // removing the space. Don't do it when the good word also contains a - // space. - if (ascii_iswhite(su->su_badptr[su->su_badlen]) - && *skiptowhite(stp->st_word) == NUL) { - for (p = fword; *(p = skiptowhite(p)) != NUL;) { - STRMOVE(p, p + 1); - } - } - - spell_soundfold(slang, fword, true, badsound2); - pbad = badsound2; - } - - if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) { - // Add part of the bad word to the good word, so that we soundfold - // what replaces the bad word. - STRCPY(goodword, stp->st_word); - STRLCPY(goodword + stp->st_wordlen, - su->su_badptr + su->su_badlen - lendiff, lendiff + 1); - pgood = goodword; - } else { - pgood = stp->st_word; - } - - // Sound-fold the word and compute the score for the difference. - spell_soundfold(slang, pgood, false, goodsound); - - return soundalike_score(goodsound, pbad); -} - -static sftword_T dumsft; -#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) -#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) - -// Prepare for calling suggest_try_soundalike(). -static void suggest_try_soundalike_prep(void) -{ - langp_T *lp; - slang_T *slang; - - // Do this for all languages that support sound folding and for which a - // .sug file has been loaded. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - slang = lp->lp_slang; - if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { - // prepare the hashtable used by add_sound_suggest() - hash_init(&slang->sl_sounddone); - } - } -} - -// Find suggestions by comparing the word in a sound-a-like form. -// Note: This doesn't support postponed prefixes. -static void suggest_try_soundalike(suginfo_T *su) -{ - char_u salword[MAXWLEN]; - langp_T *lp; - slang_T *slang; - - // Do this for all languages that support sound folding and for which a - // .sug file has been loaded. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - slang = lp->lp_slang; - if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { - // soundfold the bad word - spell_soundfold(slang, su->su_fbadword, true, salword); - - // try all kinds of inserts/deletes/swaps/etc. - // TODO: also soundfold the next words, so that we can try joining - // and splitting -#ifdef SUGGEST_PROFILE - prof_init(); -#endif - suggest_trie_walk(su, lp, salword, true); -#ifdef SUGGEST_PROFILE - prof_report("soundalike"); -#endif - } - } -} - -// Finish up after calling suggest_try_soundalike(). -static void suggest_try_soundalike_finish(void) -{ - langp_T *lp; - slang_T *slang; - int todo; - hashitem_T *hi; - - // Do this for all languages that support sound folding and for which a - // .sug file has been loaded. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { - lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - slang = lp->lp_slang; - if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { - // Free the info about handled words. - todo = (int)slang->sl_sounddone.ht_used; - for (hi = slang->sl_sounddone.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - xfree(HI2SFT(hi)); - --todo; - } - } - - // Clear the hashtable, it may also be used by another region. - hash_clear(&slang->sl_sounddone); - hash_init(&slang->sl_sounddone); - } - } -} - -/// A match with a soundfolded word is found. Add the good word(s) that -/// produce this soundfolded word. -/// -/// @param score soundfold score -static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp) -{ - slang_T *slang = lp->lp_slang; // language for sound folding - int sfwordnr; - char_u *nrline; - int orgnr; - char_u theword[MAXWLEN]; - int i; - int wlen; - char_u *byts; - idx_T *idxs; - int n; - int wordcount; - int wc; - int goodscore; - hash_T hash; - hashitem_T *hi; - sftword_T *sft; - int bc, gc; - int limit; - - // It's very well possible that the same soundfold word is found several - // times with different scores. Since the following is quite slow only do - // the words that have a better score than before. Use a hashtable to - // remember the words that have been done. - hash = hash_hash(goodword); - const size_t goodword_len = STRLEN(goodword); - hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len, - hash); - if (HASHITEM_EMPTY(hi)) { - sft = xmalloc(sizeof(sftword_T) + goodword_len); - sft->sft_score = (int16_t)score; - memcpy(sft->sft_word, goodword, goodword_len + 1); - hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash); - } else { - sft = HI2SFT(hi); - if (score >= sft->sft_score) { - return; - } - sft->sft_score = (int16_t)score; - } - - // Find the word nr in the soundfold tree. - sfwordnr = soundfold_find(slang, goodword); - if (sfwordnr < 0) { - internal_error("add_sound_suggest()"); - return; - } - - // Go over the list of good words that produce this soundfold word - nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); - orgnr = 0; - while (*nrline != NUL) { - // The wordnr was stored in a minimal nr of bytes as an offset to the - // previous wordnr. - orgnr += bytes2offset(&nrline); - - byts = slang->sl_fbyts; - idxs = slang->sl_fidxs; - - // Lookup the word "orgnr" one of the two tries. - n = 0; - wordcount = 0; - for (wlen = 0; wlen < MAXWLEN - 3; ++wlen) { - i = 1; - if (wordcount == orgnr && byts[n + 1] == NUL) { - break; // found end of word - } - if (byts[n + 1] == NUL) { - ++wordcount; - } - - // skip over the NUL bytes - for (; byts[n + i] == NUL; ++i) { - if (i > byts[n]) { // safety check - STRCPY(theword + wlen, "BAD"); - wlen += 3; - goto badword; - } - } - - // One of the siblings must have the word. - for (; i < byts[n]; ++i) { - wc = idxs[idxs[n + i]]; // nr of words under this byte - if (wordcount + wc > orgnr) { - break; - } - wordcount += wc; - } - - theword[wlen] = byts[n + i]; - n = idxs[n + i]; - } -badword: - theword[wlen] = NUL; - - // Go over the possible flags and regions. - for (; i <= byts[n] && byts[n + i] == NUL; ++i) { - char_u cword[MAXWLEN]; - char_u *p; - int flags = (int)idxs[n + i]; - - // Skip words with the NOSUGGEST flag - if (flags & WF_NOSUGGEST) { - continue; - } - - if (flags & WF_KEEPCAP) { - // Must find the word in the keep-case tree. - find_keepcap_word(slang, theword, cword); - p = cword; - } else { - flags |= su->su_badflags; - if ((flags & WF_CAPMASK) != 0) { - // Need to fix case according to "flags". - make_case_word(theword, cword, flags); - p = cword; - } else { - p = theword; - } - } - - // Add the suggestion. - if (sps_flags & SPS_DOUBLE) { - // Add the suggestion if the score isn't too bad. - if (score <= su->su_maxscore) { - add_suggestion(su, &su->su_sga, p, su->su_badlen, - score, 0, false, slang, false); - } - } else { - // Add a penalty for words in another region. - if ((flags & WF_REGION) - && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { - goodscore = SCORE_REGION; - } else { - goodscore = 0; - } - - // Add a small penalty for changing the first letter from - // lower to upper case. Helps for "tath" -> "Kath", which is - // less common than "tath" -> "path". Don't do it when the - // letter is the same, that has already been counted. - gc = utf_ptr2char((char *)p); - if (SPELL_ISUPPER(gc)) { - bc = utf_ptr2char((char *)su->su_badword); - if (!SPELL_ISUPPER(bc) - && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) { - goodscore += SCORE_ICASE / 2; - } - } - - // Compute the score for the good word. This only does letter - // insert/delete/swap/replace. REP items are not considered, - // which may make the score a bit higher. - // Use a limit for the score to make it work faster. Use - // MAXSCORE(), because RESCORE() will change the score. - // If the limit is very high then the iterative method is - // inefficient, using an array is quicker. - limit = MAXSCORE(su->su_sfmaxscore - goodscore, score); - if (limit > SCORE_LIMITMAX) { - goodscore += spell_edit_score(slang, su->su_badword, p); - } else { - goodscore += spell_edit_score_limit(slang, su->su_badword, - p, limit); - } - - // When going over the limit don't bother to do the rest. - if (goodscore < SCORE_MAXMAX) { - // Give a bonus to words seen before. - goodscore = score_wordcount_adj(slang, goodscore, p, false); - - // Add the suggestion if the score isn't too bad. - goodscore = RESCORE(goodscore, score); - if (goodscore <= su->su_sfmaxscore) { - add_suggestion(su, &su->su_ga, p, su->su_badlen, - goodscore, score, true, slang, true); - } - } - } - } - } -} - -// Find word "word" in fold-case tree for "slang" and return the word number. -static int soundfold_find(slang_T *slang, char_u *word) -{ - idx_T arridx = 0; - int len; - int wlen = 0; - int c; - char_u *ptr = word; - char_u *byts; - idx_T *idxs; - int wordnr = 0; - - byts = slang->sl_sbyts; - idxs = slang->sl_sidxs; - - for (;;) { - // First byte is the number of possible bytes. - len = byts[arridx++]; - - // If the first possible byte is a zero the word could end here. - // If the word ends we found the word. If not skip the NUL bytes. - c = ptr[wlen]; - if (byts[arridx] == NUL) { - if (c == NUL) { - break; - } - - // Skip over the zeros, there can be several. - while (len > 0 && byts[arridx] == NUL) { - ++arridx; - --len; - } - if (len == 0) { - return -1; // no children, word should have ended here - } - ++wordnr; - } - - // If the word ends we didn't find it. - if (c == NUL) { - return -1; - } - - // Perform a binary search in the list of accepted bytes. - if (c == TAB) { // <Tab> is handled like <Space> - c = ' '; - } - while (byts[arridx] < c) { - // The word count is in the first idxs[] entry of the child. - wordnr += idxs[idxs[arridx]]; - ++arridx; - if (--len == 0) { // end of the bytes, didn't find it - return -1; - } - } - if (byts[arridx] != c) { // didn't find the byte - return -1; - } - - // Continue at the child (if there is one). - arridx = idxs[arridx]; - ++wlen; - - // One space in the good word may stand for several spaces in the - // checked word. - if (c == ' ') { - while (ptr[wlen] == ' ' || ptr[wlen] == TAB) { - ++wlen; - } - } - } - - return wordnr; -} - // Copy "fword" to "cword", fixing case according to "flags". -static void make_case_word(char_u *fword, char_u *cword, int flags) +void make_case_word(char_u *fword, char_u *cword, int flags) { if (flags & WF_ALLCAP) { // Make it all upper-case @@ -5645,291 +2703,6 @@ static void make_case_word(char_u *fword, char_u *cword, int flags) } } -// Returns true if "c1" and "c2" are similar characters according to the MAP -// lines in the .aff file. -static bool similar_chars(slang_T *slang, int c1, int c2) -{ - int m1, m2; - char buf[MB_MAXBYTES + 1]; - hashitem_T *hi; - - if (c1 >= 256) { - buf[utf_char2bytes(c1, (char *)buf)] = 0; - hi = hash_find(&slang->sl_map_hash, buf); - if (HASHITEM_EMPTY(hi)) { - m1 = 0; - } else { - m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); - } - } else { - m1 = slang->sl_map_array[c1]; - } - if (m1 == 0) { - return false; - } - - if (c2 >= 256) { - buf[utf_char2bytes(c2, (char *)buf)] = 0; - hi = hash_find(&slang->sl_map_hash, buf); - if (HASHITEM_EMPTY(hi)) { - m2 = 0; - } else { - m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); - } - } else { - m2 = slang->sl_map_array[c2]; - } - - return m1 == m2; -} - -/// Adds a suggestion to the list of suggestions. -/// For a suggestion that is already in the list the lowest score is remembered. -/// -/// @param gap either su_ga or su_sga -/// @param badlenarg len of bad word replaced with "goodword" -/// @param had_bonus value for st_had_bonus -/// @param slang language for sound folding -/// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score. -static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg, - int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf) -{ - int goodlen; // len of goodword changed - int badlen; // len of bad word changed - suggest_T *stp; - suggest_T new_sug; - - // Minimize "badlen" for consistency. Avoids that changing "the the" to - // "thee the" is added next to changing the first "the" the "thee". - const char_u *pgood = goodword + STRLEN(goodword); - char_u *pbad = su->su_badptr + badlenarg; - for (;;) { - goodlen = (int)(pgood - goodword); - badlen = (int)(pbad - su->su_badptr); - if (goodlen <= 0 || badlen <= 0) { - break; - } - MB_PTR_BACK(goodword, pgood); - MB_PTR_BACK(su->su_badptr, pbad); - if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) { - break; - } - } - - if (badlen == 0 && goodlen == 0) { - // goodword doesn't change anything; may happen for "the the" changing - // the first "the" to itself. - return; - } - - int i; - if (GA_EMPTY(gap)) { - i = -1; - } else { - // Check if the word is already there. Also check the length that is - // being replaced "thes," -> "these" is a different suggestion from - // "thes" -> "these". - stp = &SUG(*gap, 0); - for (i = gap->ga_len; --i >= 0; ++stp) { - if (stp->st_wordlen == goodlen - && stp->st_orglen == badlen - && STRNCMP(stp->st_word, goodword, goodlen) == 0) { - // Found it. Remember the word with the lowest score. - if (stp->st_slang == NULL) { - stp->st_slang = slang; - } - - new_sug.st_score = score; - new_sug.st_altscore = altscore; - new_sug.st_had_bonus = had_bonus; - - if (stp->st_had_bonus != had_bonus) { - // Only one of the two had the soundalike score computed. - // Need to do that for the other one now, otherwise the - // scores can't be compared. This happens because - // suggest_try_change() doesn't compute the soundalike - // word to keep it fast, while some special methods set - // the soundalike score to zero. - if (had_bonus) { - rescore_one(su, stp); - } else { - new_sug.st_word = stp->st_word; - new_sug.st_wordlen = stp->st_wordlen; - new_sug.st_slang = stp->st_slang; - new_sug.st_orglen = badlen; - rescore_one(su, &new_sug); - } - } - - if (stp->st_score > new_sug.st_score) { - stp->st_score = new_sug.st_score; - stp->st_altscore = new_sug.st_altscore; - stp->st_had_bonus = new_sug.st_had_bonus; - } - break; - } - } - } - - if (i < 0) { - // Add a suggestion. - stp = GA_APPEND_VIA_PTR(suggest_T, gap); - stp->st_word = vim_strnsave(goodword, (size_t)goodlen); - stp->st_wordlen = goodlen; - stp->st_score = score; - stp->st_altscore = altscore; - stp->st_had_bonus = had_bonus; - stp->st_orglen = badlen; - stp->st_slang = slang; - - // If we have too many suggestions now, sort the list and keep - // the best suggestions. - if (gap->ga_len > SUG_MAX_COUNT(su)) { - if (maxsf) { - su->su_sfmaxscore = cleanup_suggestions(gap, - su->su_sfmaxscore, SUG_CLEAN_COUNT(su)); - } else { - su->su_maxscore = cleanup_suggestions(gap, - su->su_maxscore, SUG_CLEAN_COUNT(su)); - } - } - } -} - -/// Suggestions may in fact be flagged as errors. Esp. for banned words and -/// for split words, such as "the the". Remove these from the list here. -/// -/// @param gap either su_ga or su_sga -static void check_suggestions(suginfo_T *su, garray_T *gap) -{ - suggest_T *stp; - char_u longword[MAXWLEN + 1]; - int len; - hlf_T attr; - - if (gap->ga_len == 0) { - return; - } - stp = &SUG(*gap, 0); - for (int i = gap->ga_len - 1; i >= 0; --i) { - // Need to append what follows to check for "the the". - STRLCPY(longword, stp[i].st_word, MAXWLEN + 1); - len = stp[i].st_wordlen; - STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen, - MAXWLEN - len + 1); - attr = HLF_COUNT; - (void)spell_check(curwin, longword, &attr, NULL, false); - if (attr != HLF_COUNT) { - // Remove this entry. - xfree(stp[i].st_word); - --gap->ga_len; - if (i < gap->ga_len) { - memmove(stp + i, stp + i + 1, sizeof(suggest_T) * (size_t)(gap->ga_len - i)); - } - } - } -} - -// Add a word to be banned. -static void add_banned(suginfo_T *su, char_u *word) -{ - char_u *s; - hash_T hash; - hashitem_T *hi; - - hash = hash_hash(word); - const size_t word_len = STRLEN(word); - hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash); - if (HASHITEM_EMPTY(hi)) { - s = xmemdupz(word, word_len); - hash_add_item(&su->su_banned, hi, s, hash); - } -} - -// Recompute the score for all suggestions if sound-folding is possible. This -// is slow, thus only done for the final results. -static void rescore_suggestions(suginfo_T *su) -{ - if (su->su_sallang != NULL) { - for (int i = 0; i < su->su_ga.ga_len; ++i) { - rescore_one(su, &SUG(su->su_ga, i)); - } - } -} - -// Recompute the score for one suggestion if sound-folding is possible. -static void rescore_one(suginfo_T *su, suggest_T *stp) -{ - slang_T *slang = stp->st_slang; - char_u sal_badword[MAXWLEN]; - char_u *p; - - // Only rescore suggestions that have no sal score yet and do have a - // language. - if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) { - if (slang == su->su_sallang) { - p = su->su_sal_badword; - } else { - spell_soundfold(slang, su->su_fbadword, true, sal_badword); - p = sal_badword; - } - - stp->st_altscore = stp_sal_score(stp, su, slang, p); - if (stp->st_altscore == SCORE_MAXMAX) { - stp->st_altscore = SCORE_BIG; - } - stp->st_score = RESCORE(stp->st_score, stp->st_altscore); - stp->st_had_bonus = true; - } -} - -// Function given to qsort() to sort the suggestions on st_score. -// First on "st_score", then "st_altscore" then alphabetically. -static int sug_compare(const void *s1, const void *s2) -{ - suggest_T *p1 = (suggest_T *)s1; - suggest_T *p2 = (suggest_T *)s2; - int n = p1->st_score - p2->st_score; - - if (n == 0) { - n = p1->st_altscore - p2->st_altscore; - if (n == 0) { - n = STRICMP(p1->st_word, p2->st_word); - } - } - return n; -} - -/// Cleanup the suggestions: -/// - Sort on score. -/// - Remove words that won't be displayed. -/// -/// @param keep nr of suggestions to keep -/// -/// @return the maximum score in the list or "maxscore" unmodified. -static int cleanup_suggestions(garray_T *gap, int maxscore, int keep) - FUNC_ATTR_NONNULL_ALL -{ - if (gap->ga_len > 0) { - // Sort the list. - qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); - - // Truncate the list to the number of suggestions that will be displayed. - if (gap->ga_len > keep) { - suggest_T *const stp = &SUG(*gap, 0); - - for (int i = keep; i < gap->ga_len; i++) { - xfree(stp[i].st_word); - } - gap->ga_len = keep; - if (keep >= 1) { - return stp[keep - 1].st_score; - } - } - } - return maxscore; -} - /// Soundfold a string, for soundfold() /// /// @param[in] word Word to soundfold. @@ -6128,12 +2901,12 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) if ((pf = smp[n].sm_oneof_w) != NULL) { // Check for match with one of the chars in "sm_oneof". while (*pf != NUL && *pf != word[i + k]) { - ++pf; + pf++; } if (*pf == NUL) { continue; } - ++k; + k++; } char_u *s = smp[n].sm_rules; pri = 5; // default priority @@ -6203,12 +2976,12 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Check for match with one of the chars in // "sm_oneof". while (*pf != NUL && *pf != word[i + k0]) { - ++pf; + pf++; } if (*pf == NUL) { continue; } - ++k0; + k0++; } p0 = 5; @@ -6339,499 +3112,6 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) res[l] = NUL; } -/// Compute a score for two sound-a-like words. -/// This permits up to two inserts/deletes/swaps/etc. to keep things fast. -/// Instead of a generic loop we write out the code. That keeps it fast by -/// avoiding checks that will not be possible. -/// -/// @param goodstart sound-folded good word -/// @param badstart sound-folded bad word -static int soundalike_score(char_u *goodstart, char_u *badstart) -{ - char_u *goodsound = goodstart; - char_u *badsound = badstart; - int goodlen; - int badlen; - int n; - char_u *pl, *ps; - char_u *pl2, *ps2; - int score = 0; - - // Adding/inserting "*" at the start (word starts with vowel) shouldn't be - // counted so much, vowels in the middle of the word aren't counted at all. - if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) { - if ((badsound[0] == NUL && goodsound[1] == NUL) - || (goodsound[0] == NUL && badsound[1] == NUL)) { - // changing word with vowel to word without a sound - return SCORE_DEL; - } - if (badsound[0] == NUL || goodsound[0] == NUL) { - // more than two changes - return SCORE_MAXMAX; - } - - if (badsound[1] == goodsound[1] - || (badsound[1] != NUL - && goodsound[1] != NUL - && badsound[2] == goodsound[2])) { - // handle like a substitute - } else { - score = 2 * SCORE_DEL / 3; - if (*badsound == '*') { - ++badsound; - } else { - ++goodsound; - } - } - } - - goodlen = (int)STRLEN(goodsound); - badlen = (int)STRLEN(badsound); - - // Return quickly if the lengths are too different to be fixed by two - // changes. - n = goodlen - badlen; - if (n < -2 || n > 2) { - return SCORE_MAXMAX; - } - - if (n > 0) { - pl = goodsound; // goodsound is longest - ps = badsound; - } else { - pl = badsound; // badsound is longest - ps = goodsound; - } - - // Skip over the identical part. - while (*pl == *ps && *pl != NUL) { - ++pl; - ++ps; - } - - switch (n) { - case -2: - case 2: - // Must delete two characters from "pl". - ++pl; // first delete - while (*pl == *ps) { - ++pl; - ++ps; - } - // strings must be equal after second delete - if (STRCMP(pl + 1, ps) == 0) { - return score + SCORE_DEL * 2; - } - - // Failed to compare. - break; - - case -1: - case 1: - // Minimal one delete from "pl" required. - - // 1: delete - pl2 = pl + 1; - ps2 = ps; - while (*pl2 == *ps2) { - if (*pl2 == NUL) { // reached the end - return score + SCORE_DEL; - } - ++pl2; - ++ps2; - } - - // 2: delete then swap, then rest must be equal - if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { - return score + SCORE_DEL + SCORE_SWAP; - } - - // 3: delete then substitute, then the rest must be equal - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { - return score + SCORE_DEL + SCORE_SUBST; - } - - // 4: first swap then delete - if (pl[0] == ps[1] && pl[1] == ps[0]) { - pl2 = pl + 2; // swap, skip two chars - ps2 = ps + 2; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - // delete a char and then strings must be equal - if (STRCMP(pl2 + 1, ps2) == 0) { - return score + SCORE_SWAP + SCORE_DEL; - } - } - - // 5: first substitute then delete - pl2 = pl + 1; // substitute, skip one char - ps2 = ps + 1; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - // delete a char and then strings must be equal - if (STRCMP(pl2 + 1, ps2) == 0) { - return score + SCORE_SUBST + SCORE_DEL; - } - - // Failed to compare. - break; - - case 0: - // Lengths are equal, thus changes must result in same length: An - // insert is only possible in combination with a delete. - // 1: check if for identical strings - if (*pl == NUL) { - return score; - } - - // 2: swap - if (pl[0] == ps[1] && pl[1] == ps[0]) { - pl2 = pl + 2; // swap, skip two chars - ps2 = ps + 2; - while (*pl2 == *ps2) { - if (*pl2 == NUL) { // reached the end - return score + SCORE_SWAP; - } - ++pl2; - ++ps2; - } - // 3: swap and swap again - if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { - return score + SCORE_SWAP + SCORE_SWAP; - } - - // 4: swap and substitute - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { - return score + SCORE_SWAP + SCORE_SUBST; - } - } - - // 5: substitute - pl2 = pl + 1; - ps2 = ps + 1; - while (*pl2 == *ps2) { - if (*pl2 == NUL) { // reached the end - return score + SCORE_SUBST; - } - ++pl2; - ++ps2; - } - - // 6: substitute and swap - if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { - return score + SCORE_SUBST + SCORE_SWAP; - } - - // 7: substitute and substitute - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { - return score + SCORE_SUBST + SCORE_SUBST; - } - - // 8: insert then delete - pl2 = pl; - ps2 = ps + 1; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - if (STRCMP(pl2 + 1, ps2) == 0) { - return score + SCORE_INS + SCORE_DEL; - } - - // 9: delete then insert - pl2 = pl + 1; - ps2 = ps; - while (*pl2 == *ps2) { - ++pl2; - ++ps2; - } - if (STRCMP(pl2, ps2 + 1) == 0) { - return score + SCORE_INS + SCORE_DEL; - } - - // Failed to compare. - break; - } - - return SCORE_MAXMAX; -} - -// Compute the "edit distance" to turn "badword" into "goodword". The less -// deletes/inserts/substitutes/swaps are required the lower the score. -// -// The algorithm is described by Du and Chang, 1992. -// The implementation of the algorithm comes from Aspell editdist.cpp, -// edit_distance(). It has been converted from C++ to C and modified to -// support multi-byte characters. -static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) -{ - int *cnt; - int j, i; - int t; - int bc, gc; - int pbc, pgc; - int wbadword[MAXWLEN]; - int wgoodword[MAXWLEN]; - - // Lengths with NUL. - int badlen; - int goodlen; - { - // Get the characters from the multi-byte strings and put them in an - // int array for easy access. - badlen = 0; - for (const char_u *p = badword; *p != NUL;) { - wbadword[badlen++] = mb_cptr2char_adv(&p); - } - wbadword[badlen++] = 0; - goodlen = 0; - for (const char_u *p = goodword; *p != NUL;) { - wgoodword[goodlen++] = mb_cptr2char_adv(&p); - } - wgoodword[goodlen++] = 0; - } - - // We use "cnt" as an array: CNT(badword_idx, goodword_idx). -#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)] - cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1)); - - CNT(0, 0) = 0; - for (j = 1; j <= goodlen; ++j) { - CNT(0, j) = CNT(0, j - 1) + SCORE_INS; - } - - for (i = 1; i <= badlen; ++i) { - CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL; - for (j = 1; j <= goodlen; j++) { - bc = wbadword[i - 1]; - gc = wgoodword[j - 1]; - if (bc == gc) { - CNT(i, j) = CNT(i - 1, j - 1); - } else { - // Use a better score when there is only a case difference. - if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { - CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1); - } else { - // For a similar character use SCORE_SIMILAR. - if (slang != NULL - && slang->sl_has_map - && similar_chars(slang, gc, bc)) { - CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1); - } else { - CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1); - } - } - - if (i > 1 && j > 1) { - pbc = wbadword[i - 2]; - pgc = wgoodword[j - 2]; - if (bc == pgc && pbc == gc) { - t = SCORE_SWAP + CNT(i - 2, j - 2); - if (t < CNT(i, j)) { - CNT(i, j) = t; - } - } - } - t = SCORE_DEL + CNT(i - 1, j); - if (t < CNT(i, j)) { - CNT(i, j) = t; - } - t = SCORE_INS + CNT(i, j - 1); - if (t < CNT(i, j)) { - CNT(i, j) = t; - } - } - } - } - - i = CNT(badlen - 1, goodlen - 1); - xfree(cnt); - return i; -} - -// Like spell_edit_score(), but with a limit on the score to make it faster. -// May return SCORE_MAXMAX when the score is higher than "limit". -// -// This uses a stack for the edits still to be tried. -// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support -// for multi-byte characters. -static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit) -{ - return spell_edit_score_limit_w(slang, badword, goodword, limit); -} - -// Multi-byte version of spell_edit_score_limit(). -// Keep it in sync with the above! -static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit) -{ - limitscore_T stack[10]; // allow for over 3 * 2 edits - int stackidx; - int bi, gi; - int bi2, gi2; - int bc, gc; - int score; - int score_off; - int minscore; - int round; - int wbadword[MAXWLEN]; - int wgoodword[MAXWLEN]; - - // Get the characters from the multi-byte strings and put them in an - // int array for easy access. - bi = 0; - for (const char_u *p = badword; *p != NUL;) { - wbadword[bi++] = mb_cptr2char_adv(&p); - } - wbadword[bi++] = 0; - gi = 0; - for (const char_u *p = goodword; *p != NUL;) { - wgoodword[gi++] = mb_cptr2char_adv(&p); - } - wgoodword[gi++] = 0; - - // The idea is to go from start to end over the words. So long as - // characters are equal just continue, this always gives the lowest score. - // When there is a difference try several alternatives. Each alternative - // increases "score" for the edit distance. Some of the alternatives are - // pushed unto a stack and tried later, some are tried right away. At the - // end of the word the score for one alternative is known. The lowest - // possible score is stored in "minscore". - stackidx = 0; - bi = 0; - gi = 0; - score = 0; - minscore = limit + 1; - - for (;;) { - // Skip over an equal part, score remains the same. - for (;;) { - bc = wbadword[bi]; - gc = wgoodword[gi]; - - if (bc != gc) { // stop at a char that's different - break; - } - if (bc == NUL) { // both words end - if (score < minscore) { - minscore = score; - } - goto pop; // do next alternative - } - ++bi; - ++gi; - } - - if (gc == NUL) { // goodword ends, delete badword chars - do { - if ((score += SCORE_DEL) >= minscore) { - goto pop; // do next alternative - } - } while (wbadword[++bi] != NUL); - minscore = score; - } else if (bc == NUL) { // badword ends, insert badword chars - do { - if ((score += SCORE_INS) >= minscore) { - goto pop; // do next alternative - } - } while (wgoodword[++gi] != NUL); - minscore = score; - } else { // both words continue - // If not close to the limit, perform a change. Only try changes - // that may lead to a lower score than "minscore". - // round 0: try deleting a char from badword - // round 1: try inserting a char in badword - for (round = 0; round <= 1; ++round) { - score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS); - if (score_off < minscore) { - if (score_off + SCORE_EDIT_MIN >= minscore) { - // Near the limit, rest of the words must match. We - // can check that right now, no need to push an item - // onto the stack. - bi2 = bi + 1 - round; - gi2 = gi + round; - while (wgoodword[gi2] == wbadword[bi2]) { - if (wgoodword[gi2] == NUL) { - minscore = score_off; - break; - } - ++bi2; - ++gi2; - } - } else { - // try deleting a character from badword later - stack[stackidx].badi = bi + 1 - round; - stack[stackidx].goodi = gi + round; - stack[stackidx].score = score_off; - ++stackidx; - } - } - } - - if (score + SCORE_SWAP < minscore) { - // If swapping two characters makes a match then the - // substitution is more expensive, thus there is no need to - // try both. - if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) { - // Swap two characters, that is: skip them. - gi += 2; - bi += 2; - score += SCORE_SWAP; - continue; - } - } - - // Substitute one character for another which is the same - // thing as deleting a character from both goodword and badword. - // Use a better score when there is only a case difference. - if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { - score += SCORE_ICASE; - } else { - // For a similar character use SCORE_SIMILAR. - if (slang != NULL - && slang->sl_has_map - && similar_chars(slang, gc, bc)) { - score += SCORE_SIMILAR; - } else { - score += SCORE_SUBST; - } - } - - if (score < minscore) { - // Do the substitution. - ++gi; - ++bi; - continue; - } - } -pop: - // Get here to try the next alternative, pop it from the stack. - if (stackidx == 0) { // stack is empty, finished - break; - } - - // pop an item from the stack - --stackidx; - gi = stack[stackidx].goodi; - bi = stack[stackidx].badi; - score = stack[stackidx].score; - } - - // When the score goes over "limit" it may actually be much higher. - // Return a very large number to avoid going below the limit when giving a - // bonus. - if (minscore > limit) { - return SCORE_MAXMAX; - } - return minscore; -} - // ":spellinfo" void ex_spellinfo(exarg_T *eap) { @@ -7005,7 +3285,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) && (pat == NULL || !ins_compl_interrupted())) { if (curi[depth] > byts[arridx[depth]]) { // Done all bytes at this node, go up one level. - --depth; + depth--; line_breakcheck(); ins_compl_check_keys(50, false); } else { @@ -7038,7 +3318,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) dump_word(slang, word, pat, dir, dumpflags, flags, lnum); if (pat == NULL) { - ++lnum; + lnum++; } } @@ -7063,7 +3343,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) assert(depth >= 0); if (depth <= patlen && mb_strnicmp(word, pat, (size_t)depth) != 0) { - --depth; + depth--; } } } @@ -7201,7 +3481,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi len = byts[n]; if (curi[depth] > len) { // Done all bytes at this node, go up one level. - --depth; + depth--; line_breakcheck(); } else { // Do one more byte at this node. @@ -7224,7 +3504,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } @@ -7240,7 +3520,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } } @@ -7327,3 +3607,71 @@ int expand_spelling(linenr_T lnum, char_u *pat, char ***matchp) *matchp = ga.ga_data; return ga.ga_len; } + +/// Return true if "val" is a valid 'spelllang' value. +bool valid_spelllang(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_,@"); +} + +/// Return true if "val" is a valid 'spellfile' value. +bool valid_spellfile(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (const char_u *s = val; *s != NUL; s++) { + if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { + return false; + } + } + return true; +} + +char *did_set_spell_option(bool is_spellfile) +{ + char *errmsg = NULL; + + if (is_spellfile) { + int l = (int)STRLEN(curwin->w_s->b_p_spf); + if (l > 0 + && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) { + errmsg = e_invarg; + } + } + + if (errmsg == NULL) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == curbuf && wp->w_p_spell) { + errmsg = did_set_spelllang(wp); + break; + } + } + } + + return errmsg; +} + +/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'. +/// Return error message when failed, NULL when OK. +char *compile_cap_prog(synblock_T *synblock) + FUNC_ATTR_NONNULL_ALL +{ + regprog_T *rp = synblock->b_cap_prog; + char_u *re; + + if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) { + synblock->b_cap_prog = NULL; + } else { + // Prepend a ^ so that we only match at one column + re = concat_str((char_u *)"^", synblock->b_p_spc); + synblock->b_cap_prog = vim_regcomp((char *)re, RE_MAGIC); + xfree(re); + if (synblock->b_cap_prog == NULL) { + synblock->b_cap_prog = rp; // restore the previous program + return e_invarg; + } + } + + vim_regfree(rp); + return NULL; +} diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 222d103f5d..61f722b9ee 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -35,6 +35,8 @@ typedef int idx_T; #define WF_FIXCAP 0x40 // keep-case word, allcap not allowed #define WF_KEEPCAP 0x80 // keep-case word +#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) + // for <flags2>, shifted up one byte to be used in wn_flags #define WF_HAS_AFF 0x0100 // word includes affix #define WF_NEEDCOMP 0x0200 // word only valid in compound @@ -208,56 +210,6 @@ typedef struct { char_u st_upper[256]; // chars: upper case } spelltab_T; -// For finding suggestions: At each node in the tree these states are tried: -typedef enum { - STATE_START = 0, // At start of node check for NUL bytes (goodword - // ends); if badword ends there is a match, otherwise - // try splitting word. - STATE_NOPREFIX, // try without prefix - STATE_SPLITUNDO, // Undo splitting. - STATE_ENDNUL, // Past NUL bytes at start of the node. - STATE_PLAIN, // Use each byte of the node. - STATE_DEL, // Delete a byte from the bad word. - STATE_INS_PREP, // Prepare for inserting bytes. - STATE_INS, // Insert a byte in the bad word. - STATE_SWAP, // Swap two bytes. - STATE_UNSWAP, // Undo swap two characters. - STATE_SWAP3, // Swap two characters over three. - STATE_UNSWAP3, // Undo Swap two characters over three. - STATE_UNROT3L, // Undo rotate three characters left - STATE_UNROT3R, // Undo rotate three characters right - STATE_REP_INI, // Prepare for using REP items. - STATE_REP, // Use matching REP items from the .aff file. - STATE_REP_UNDO, // Undo a REP item replacement. - STATE_FINAL, // End of this node. -} state_T; - -// Struct to keep the state at each level in suggest_try_change(). -typedef struct trystate_S { - state_T ts_state; // state at this level, STATE_ - int ts_score; // score - idx_T ts_arridx; // index in tree array, start of node - int16_t ts_curi; // index in list of child nodes - char_u ts_fidx; // index in fword[], case-folded bad word - char_u ts_fidxtry; // ts_fidx at which bytes may be changed - char_u ts_twordlen; // valid length of tword[] - char_u ts_prefixdepth; // stack depth for end of prefix or - // PFD_PREFIXTREE or PFD_NOPREFIX - char_u ts_flags; // TSF_ flags - char_u ts_tcharlen; // number of bytes in tword character - char_u ts_tcharidx; // current byte index in tword character - char_u ts_isdiff; // DIFF_ values - char_u ts_fcharstart; // index in fword where badword char started - char_u ts_prewordlen; // length of word in "preword[]" - char_u ts_splitoff; // index in "tword" after last split - char_u ts_splitfidx; // "ts_fidx" at word split - char_u ts_complen; // nr of compound words used - char_u ts_compsplit; // index for "compflags" where word was spit - char_u ts_save_badflags; // su_badflags saved here - char_u ts_delidx; // index in fword for char that was deleted, - // valid when "ts_flags" has TSF_DIDDEL -} trystate_T; - // Use our own character-case definitions, because the current locale may // differ from what the .spl file uses. // These must not be called with negative number! @@ -290,4 +242,17 @@ typedef enum { SPELL_ADD_RARE = 2, } SpellAddType; +typedef struct wordcount_S { + uint16_t wc_count; ///< nr of times word was seen + char_u wc_word[1]; ///< word, actually longer +} wordcount_T; + +#define WC_KEY_OFF offsetof(wordcount_T, wc_word) +#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF)) +#define MAXWORDCOUNT 0xffff + +// Remember what "z?" replaced. +extern char_u *repl_from; +extern char_u *repl_to; + #endif // NVIM_SPELL_DEFS_H diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 9f21e24d4c..be1373f617 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -230,9 +230,11 @@ #include <stdio.h> #include <wctype.h> +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" #include "nvim/ex_cmds2.h" #include "nvim/fileio.h" #include "nvim/memline.h" @@ -242,7 +244,7 @@ #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/spell.h" #include "nvim/spell_defs.h" #include "nvim/spellfile.h" @@ -576,11 +578,10 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile char_u *p; int n; int len; - char_u *save_sourcing_name = (char_u *)sourcing_name; - linenr_T save_sourcing_lnum = sourcing_lnum; slang_T *lp = NULL; int c = 0; int res; + bool did_estack_push = false; fd = os_fopen((char *)fname, "r"); if (fd == NULL) { @@ -612,8 +613,8 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile } // Set sourcing_name, so that error messages mention the file name. - sourcing_name = (char *)fname; - sourcing_lnum = 0; + estack_push(ETYPE_SPELL, (char *)fname, 0); + did_estack_push = true; // <HEADER>: <fileID> const int scms_ret = spell_check_magic_string(fd); @@ -809,8 +810,9 @@ endOK: if (fd != NULL) { fclose(fd); } - sourcing_name = (char *)save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; + if (did_estack_push) { + estack_pop(); + } return lp; } @@ -838,7 +840,7 @@ static void tree_count_words(char_u *byts, idx_T *idxs) wordcount[depth - 1] += wordcount[depth]; } - --depth; + depth--; fast_breakcheck(); } else { // Do one more byte at this node. @@ -853,12 +855,12 @@ static void tree_count_words(char_u *byts, idx_T *idxs) // Skip over any other NUL bytes (same word with different // flags). while (byts[n + 1] == 0) { - ++n; - ++curi[depth]; + n++; + curi[depth]++; } } else { // Normal char, go one level deeper to count the words. - ++depth; + depth++; arridx[depth] = idxs[n]; curi[depth] = 1; wordcount[depth] = 0; @@ -1369,21 +1371,21 @@ static int read_compound(FILE *fd, slang_T *slang, int len) if (todo < 2) { return SP_FORMERROR; // need at least two bytes } - --todo; + todo--; c = getc(fd); // <compmax> if (c < 2) { c = MAXWLEN; } slang->sl_compmax = c; - --todo; + todo--; c = getc(fd); // <compminlen> if (c < 1) { c = 0; } slang->sl_compminlen = c; - --todo; + todo--; c = getc(fd); // <compsylmax> if (c < 1) { c = MAXWLEN; @@ -1394,9 +1396,9 @@ static int read_compound(FILE *fd, slang_T *slang, int len) if (c != 0) { ungetc(c, fd); // be backwards compatible with Vim 7.0b } else { - --todo; + todo--; c = getc(fd); // only use the lower byte for now - --todo; + todo--; slang->sl_compoptions = c; gap = &slang->sl_comppat; @@ -1408,8 +1410,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) ga_init(gap, sizeof(char_u *), c); ga_grow(gap, c); while (--c >= 0) { - ((char_u **)(gap->ga_data))[gap->ga_len++] = - read_cnt_string(fd, 1, &cnt); + ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt); // <comppatlen> <comppattext> if (cnt < 0) { return cnt; @@ -2064,7 +2065,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Read all the lines in the file one by one. while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) { line_breakcheck(); - ++lnum; + lnum++; // Skip comment lines. if (*rline == '#') { @@ -2091,7 +2092,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) itemcnt = 0; for (p = line;;) { while (*p != NUL && *p <= ' ') { // skip white space and CR/NL - ++p; + p++; } if (*p == NUL) { break; @@ -2103,11 +2104,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // A few items have arbitrary text argument, don't split them. if (itemcnt == 2 && spell_info_item(items[0])) { while (*p >= ' ' || *p == TAB) { // skip until CR/NL - ++p; + p++; } } else { while (*p > ' ') { // skip until white space or CR/NL - ++p; + p++; } } if (*p == NUL) { @@ -2300,18 +2301,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Only add the couple if it isn't already there. for (i = 0; i < gap->ga_len - 1; i += 2) { - if (STRCMP(((char_u **)(gap->ga_data))[i], items[1]) == 0 - && STRCMP(((char_u **)(gap->ga_data))[i + 1], - items[2]) == 0) { + if (STRCMP(((char **)(gap->ga_data))[i], items[1]) == 0 + && STRCMP(((char **)(gap->ga_data))[i + 1], items[2]) == 0) { break; } } if (i >= gap->ga_len) { ga_grow(gap, 2); - ((char_u **)(gap->ga_data))[gap->ga_len++] - = getroom_save(spin, items[1]); - ((char_u **)(gap->ga_data))[gap->ga_len++] - = getroom_save(spin, items[2]); + ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[1]); + ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[2]); } } else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2) && syllable == NULL) { @@ -2387,7 +2385,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Check for the "S" flag, which apparently means that another // block with the same affix name is following. if (itemcnt > lasti && STRCMP(items[lasti], "S") == 0) { - ++lasti; + lasti++; cur_aff->ah_follows = true; } else { cur_aff->ah_follows = false; @@ -2809,7 +2807,7 @@ static void aff_process_flags(afffile_T *affile, affentry_T *entry) } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } if (*entry->ae_flags == NUL) { @@ -2945,7 +2943,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla *tp++ = (char_u)id; } if (aff->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } } @@ -2968,7 +2966,7 @@ static void check_renumber(spellinfo_T *spin) // Returns true if flag "flag" appears in affix list "afflist". static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) { - char_u *p; + char *p; unsigned n; switch (flagtype) { @@ -2977,7 +2975,7 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) case AFT_CAPLONG: case AFT_LONG: - for (p = afflist; *p != NUL;) { + for (p = (char *)afflist; *p != NUL;) { n = (unsigned)mb_ptr2char_adv((const char_u **)&p); if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z')) && *p != NUL) { @@ -2990,8 +2988,8 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) break; case AFT_NUM: - for (p = afflist; *p != NUL;) { - int digits = getdigits_int((char **)&p, true, 0); + for (p = (char *)afflist; *p != NUL;) { + int digits = getdigits_int(&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; if (n == 0) { @@ -3072,7 +3070,7 @@ static void spell_free_aff(afffile_T *aff) todo = (int)ht->ht_used; for (hi = ht->ht_array; todo > 0; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; ah = HI2AH(hi); for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next) { vim_regfree(ae->ae_prog); @@ -3142,7 +3140,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // the hashtable. while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) { line_breakcheck(); - ++lnum; + lnum++; if (line[0] == '#' || line[0] == '/') { continue; // comment line } @@ -3150,7 +3148,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // the word is kept to allow multi-word terms like "et al.". l = (int)STRLEN(line); while (l > 0 && line[l - 1] <= ' ') { - --l; + l--; } if (l == 0) { continue; // empty line @@ -3186,7 +3184,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // Skip non-ASCII words when "spin->si_ascii" is true. if (spin->si_ascii && has_non_ascii(w)) { - ++non_ascii; + non_ascii++; xfree(pc); continue; } @@ -3227,7 +3225,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) smsg(_("First duplicate word in %s line %d: %s"), fname, lnum, dw); } - ++duplicate; + duplicate++; } else { hash_add_item(&ht, hi, dw, hash); } @@ -3362,7 +3360,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } @@ -3392,7 +3390,7 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } @@ -3438,7 +3436,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff todo = (int)ht->ht_used; for (hi = ht->ht_array; todo > 0 && retval == OK; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; ah = HI2AH(hi); // Check that the affix combines, if required, and that the word @@ -3686,7 +3684,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) // Read all the lines in the file one by one. while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) { line_breakcheck(); - ++lnum; + lnum++; // Skip comment lines. if (*rline == '#') { @@ -3696,7 +3694,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) // Remove CR, LF and white space from the end. l = (int)STRLEN(rline); while (l > 0 && rline[l - 1] <= ' ') { - --l; + l--; } if (l == 0) { continue; // empty or blank line @@ -3719,7 +3717,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) } if (*line == '/') { - ++line; + line++; if (STRNCMP(line, "encoding=", 9) == 0) { if (spin->si_conv.vc_type != CONV_NONE) { smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"), @@ -3802,13 +3800,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) fname, lnum, p); break; } - ++p; + p++; } } // Skip non-ASCII words when "spin->si_ascii" is true. if (spin->si_ascii && has_non_ascii(line)) { - ++non_ascii; + non_ascii++; continue; } @@ -4146,8 +4144,8 @@ static wordnode_T *get_wordnode(spellinfo_T *spin) } else { n = spin->si_first_free; spin->si_first_free = n->wn_child; - memset(n, 0, sizeof(wordnode_T)); - --spin->si_free_count; + CLEAR_POINTER(n); + spin->si_free_count--; } #ifdef SPELL_PRINTTREE if (n != NULL) { @@ -4173,7 +4171,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) cnt += deref_wordnode(spin, np->wn_child); } free_wordnode(spin, np); - ++cnt; + cnt++; } ++cnt; // length field } @@ -4248,7 +4246,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo // Note that with "child" we mean not just the node that is pointed to, // but the whole list of siblings of which the child node is the first. for (np = node; np != NULL && !got_int; np = np->wn_sibling) { - ++len; + len++; if ((child = np->wn_child) != NULL) { // Compress the child first. This fills hashkey. compressed += node_compress(spin, child, ht, tot); @@ -4581,7 +4579,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) if (round == 2) { // <word> fwv &= fwrite(hi->hi_key, l, 1, fd); } - --todo; + todo--; } } if (round == 1) { @@ -4644,8 +4642,8 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) size_t l = STRLEN(spin->si_compflags); assert(spin->si_comppat.ga_len >= 0); - for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; ++i) { - l += STRLEN(((char_u **)(spin->si_comppat.ga_data))[i]) + 1; + for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { + l += STRLEN(((char **)(spin->si_comppat.ga_data))[i]) + 1; } put_bytes(fd, l + 7, 4); // <sectionlen> @@ -4655,8 +4653,8 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(0, fd); // for Vim 7.0b compatibility putc(spin->si_compoptions, fd); // <compoptions> put_bytes(fd, (uintmax_t)spin->si_comppat.ga_len, 2); // <comppatcount> - for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; ++i) { - char_u *p = ((char_u **)(spin->si_comppat.ga_data))[i]; + for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { + char *p = ((char **)(spin->si_comppat.ga_data))[i]; assert(STRLEN(p) < INT_MAX); putc((int)STRLEN(p), fd); // <comppatlen> fwv &= fwrite(p, STRLEN(p), 1, fd); // <comppattext> @@ -4782,7 +4780,7 @@ static int put_node(FILE *fd, wordnode_T *node, int idx, int regionmask, bool pr // Count the number of siblings. int siblingcount = 0; for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) { - ++siblingcount; + siblingcount++; } // Write the sibling count. @@ -5012,7 +5010,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) wordcount[depth - 1] += wordcount[depth]; } - --depth; + depth--; line_breakcheck(); } else { // Do one more byte at this node. @@ -5033,8 +5031,8 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) return FAIL; } - ++words_done; - ++wordcount[depth]; + words_done++; + wordcount[depth]++; // Reset the block count each time to avoid compression // kicking in. @@ -5282,7 +5280,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool bool error = false; spellinfo_T spin; - memset(&spin, 0, sizeof(spin)); + CLEAR_FIELD(spin); spin.si_verbose = !added_word; spin.si_ascii = ascii; spin.si_followup = true; @@ -5815,7 +5813,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) size_t totlen = 2 + (size_t)gap->ga_len; // <prefcondcnt> and <condlen> bytes for (int i = 0; i < gap->ga_len; i++) { // <prefcond> : <condlen> <condstr> - char_u *p = ((char_u **)gap->ga_data)[i]; + char *p = ((char **)gap->ga_data)[i]; if (p != NULL) { size_t len = STRLEN(p); if (fd != NULL) { diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c new file mode 100644 index 0000000000..b4a9bed437 --- /dev/null +++ b/src/nvim/spellsuggest.c @@ -0,0 +1,3800 @@ +// 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 + +// spellsuggest.c: functions for spelling suggestions + +#include "nvim/ascii.h" +#include "nvim/change.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/eval.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/getchar.h" +#include "nvim/hashtab.h" +#include "nvim/input.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" +#include "nvim/os/fs.h" +#include "nvim/os/input.h" +#include "nvim/profile.h" +#include "nvim/screen.h" +#include "nvim/spell.h" +#include "nvim/spell_defs.h" +#include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" +#include "nvim/strings.h" +#include "nvim/ui.h" +#include "nvim/undo.h" +#include "nvim/vim.h" + +// Use this to adjust the score after finding suggestions, based on the +// suggested word sounding like the bad word. This is much faster than doing +// it for every possible suggestion. +// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" +// vs "ht") and goes down in the list. +// Used when 'spellsuggest' is set to "best". +#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) + +// Do the opposite: based on a maximum end score and a known sound score, +// compute the maximum word score that can be used. +#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) + +// only used for su_badflags +#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI + +/// Information used when looking for suggestions. +typedef struct suginfo_S { + garray_T su_ga; ///< suggestions, contains "suggest_T" + int su_maxcount; ///< max. number of suggestions displayed + int su_maxscore; ///< maximum score for adding to su_ga + int su_sfmaxscore; ///< idem, for when doing soundfold words + garray_T su_sga; ///< like su_ga, sound-folded scoring + char_u *su_badptr; ///< start of bad word in line + int su_badlen; ///< length of detected bad word in line + int su_badflags; ///< caps flags for bad word + char_u su_badword[MAXWLEN]; ///< bad word truncated at su_badlen + char_u su_fbadword[MAXWLEN]; ///< su_badword case-folded + char_u su_sal_badword[MAXWLEN]; ///< su_badword soundfolded + hashtab_T su_banned; ///< table with banned words + slang_T *su_sallang; ///< default language for sound folding +} suginfo_T; + +/// One word suggestion. Used in "si_ga". +typedef struct { + char_u *st_word; ///< suggested word, allocated string + int st_wordlen; ///< STRLEN(st_word) + int st_orglen; ///< length of replaced text + int st_score; ///< lower is better + int st_altscore; ///< used when st_score compares equal + bool st_salscore; ///< st_score is for soundalike + bool st_had_bonus; ///< bonus already included in score + slang_T *st_slang; ///< language used for sound folding +} suggest_T; + +#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) + +// True if a word appears in the list of banned words. +#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) + +// Number of suggestions kept when cleaning up. We need to keep more than +// what is displayed, because when rescore_suggestions() is called the score +// may change and wrong suggestions may be removed later. +#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \ + 130 ? 150 : (su)->su_maxcount + 20) + +// Threshold for sorting and cleaning up suggestions. Don't want to keep lots +// of suggestions that are not going to be displayed. +#define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50) + +// score for various changes +#define SCORE_SPLIT 149 // split bad word +#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS +#define SCORE_ICASE 52 // slightly different case +#define SCORE_REGION 200 // word is for different region +#define SCORE_RARE 180 // rare word +#define SCORE_SWAP 75 // swap two characters +#define SCORE_SWAP3 110 // swap two characters in three +#define SCORE_REP 65 // REP replacement +#define SCORE_SUBST 93 // substitute a character +#define SCORE_SIMILAR 33 // substitute a similar character +#define SCORE_SUBCOMP 33 // substitute a composing character +#define SCORE_DEL 94 // delete a character +#define SCORE_DELDUP 66 // delete a duplicated character +#define SCORE_DELCOMP 28 // delete a composing character +#define SCORE_INS 96 // insert a character +#define SCORE_INSDUP 67 // insert a duplicate character +#define SCORE_INSCOMP 30 // insert a composing character +#define SCORE_NONWORD 103 // change non-word to word char + +#define SCORE_FILE 30 // suggestion from a file +#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower. + // 350 allows for about three changes. + +#define SCORE_COMMON1 30 // subtracted for words seen before +#define SCORE_COMMON2 40 // subtracted for words often seen +#define SCORE_COMMON3 50 // subtracted for words very often seen +#define SCORE_THRES2 10 // word count threshold for COMMON2 +#define SCORE_THRES3 100 // word count threshold for COMMON3 + +// When trying changed soundfold words it becomes slow when trying more than +// two changes. With less than two changes it's slightly faster but we miss a +// few good suggestions. In rare cases we need to try three of four changes. +#define SCORE_SFMAX1 200 // maximum score for first try +#define SCORE_SFMAX2 300 // maximum score for second try +#define SCORE_SFMAX3 400 // maximum score for third try + +#define SCORE_BIG (SCORE_INS * 3) // big difference +#define SCORE_MAXMAX 999999 // accept any score +#define SCORE_LIMITMAX 350 // for spell_edit_score_limit() + +// for spell_edit_score_limit() we need to know the minimum value of +// SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS +#define SCORE_EDIT_MIN SCORE_SIMILAR + +/// For finding suggestions: At each node in the tree these states are tried: +typedef enum { + STATE_START = 0, ///< At start of node check for NUL bytes (goodword + ///< ends); if badword ends there is a match, otherwise + ///< try splitting word. + STATE_NOPREFIX, ///< try without prefix + STATE_SPLITUNDO, ///< Undo splitting. + STATE_ENDNUL, ///< Past NUL bytes at start of the node. + STATE_PLAIN, ///< Use each byte of the node. + STATE_DEL, ///< Delete a byte from the bad word. + STATE_INS_PREP, ///< Prepare for inserting bytes. + STATE_INS, ///< Insert a byte in the bad word. + STATE_SWAP, ///< Swap two bytes. + STATE_UNSWAP, ///< Undo swap two characters. + STATE_SWAP3, ///< Swap two characters over three. + STATE_UNSWAP3, ///< Undo Swap two characters over three. + STATE_UNROT3L, ///< Undo rotate three characters left + STATE_UNROT3R, ///< Undo rotate three characters right + STATE_REP_INI, ///< Prepare for using REP items. + STATE_REP, ///< Use matching REP items from the .aff file. + STATE_REP_UNDO, ///< Undo a REP item replacement. + STATE_FINAL, ///< End of this node. +} state_T; + +/// Struct to keep the state at each level in suggest_try_change(). +typedef struct trystate_S { + state_T ts_state; ///< state at this level, STATE_ + int ts_score; ///< score + idx_T ts_arridx; ///< index in tree array, start of node + int16_t ts_curi; ///< index in list of child nodes + char_u ts_fidx; ///< index in fword[], case-folded bad word + char_u ts_fidxtry; ///< ts_fidx at which bytes may be changed + char_u ts_twordlen; ///< valid length of tword[] + char_u ts_prefixdepth; ///< stack depth for end of prefix or + ///< PFD_PREFIXTREE or PFD_NOPREFIX + char_u ts_flags; ///< TSF_ flags + char_u ts_tcharlen; ///< number of bytes in tword character + char_u ts_tcharidx; ///< current byte index in tword character + char_u ts_isdiff; ///< DIFF_ values + char_u ts_fcharstart; ///< index in fword where badword char started + char_u ts_prewordlen; ///< length of word in "preword[]" + char_u ts_splitoff; ///< index in "tword" after last split + char_u ts_splitfidx; ///< "ts_fidx" at word split + char_u ts_complen; ///< nr of compound words used + char_u ts_compsplit; ///< index for "compflags" where word was spit + char_u ts_save_badflags; ///< su_badflags saved here + char_u ts_delidx; ///< index in fword for char that was deleted, + ///< valid when "ts_flags" has TSF_DIDDEL +} trystate_T; + +// values for ts_isdiff +#define DIFF_NONE 0 // no different byte (yet) +#define DIFF_YES 1 // different byte found +#define DIFF_INSERT 2 // inserting character + +// values for ts_flags +#define TSF_PREFIXOK 1 // already checked that prefix is OK +#define TSF_DIDSPLIT 2 // tried split at this point +#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index + +// special values ts_prefixdepth +#define PFD_NOPREFIX 0xff // not using prefixes +#define PFD_PREFIXTREE 0xfe // walking through the prefix tree +#define PFD_NOTSPECIAL 0xfd // highest value that's not special + +static long spell_suggest_timeout = 5000; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "spellsuggest.c.generated.h" +#endif + +/// Returns true when the sequence of flags in "compflags" plus "flag" can +/// possibly form a valid compounded word. This also checks the COMPOUNDRULE +/// lines if they don't contain wildcards. +static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag) +{ + // If the flag doesn't appear in sl_compstartflags or sl_compallflags + // then it can't possibly compound. + if (!byte_in_str(sp->ts_complen == sp->ts_compsplit + ? slang->sl_compstartflags : slang->sl_compallflags, flag)) { + return false; + } + + // If there are no wildcards, we can check if the flags collected so far + // possibly can form a match with COMPOUNDRULE patterns. This only + // makes sense when we have two or more words. + if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) { + compflags[sp->ts_complen] = (char_u)flag; + compflags[sp->ts_complen + 1] = NUL; + bool v = match_compoundrule(slang, compflags + sp->ts_compsplit); + compflags[sp->ts_complen] = NUL; + return v; + } + + return true; +} + +/// Adjust the score of common words. +/// +/// @param split word was split, less bonus +static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split) +{ + wordcount_T *wc; + int bonus; + int newscore; + + hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word); + if (!HASHITEM_EMPTY(hi)) { + wc = HI2WC(hi); + if (wc->wc_count < SCORE_THRES2) { + bonus = SCORE_COMMON1; + } else if (wc->wc_count < SCORE_THRES3) { + bonus = SCORE_COMMON2; + } else { + bonus = SCORE_COMMON3; + } + if (split) { + newscore = score - bonus / 2; + } else { + newscore = score - bonus; + } + if (newscore < 0) { + return 0; + } + return newscore; + } + return score; +} + +/// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a +/// capital. So that make_case_word() can turn WOrd into Word. +/// Add ALLCAP for "WOrD". +static int badword_captype(char_u *word, char_u *end) + FUNC_ATTR_NONNULL_ALL +{ + int flags = captype(word, end); + int c; + int l, u; + bool first; + char_u *p; + + if (flags & WF_KEEPCAP) { + // Count the number of UPPER and lower case letters. + l = u = 0; + first = false; + for (p = word; p < end; MB_PTR_ADV(p)) { + c = utf_ptr2char((char *)p); + if (SPELL_ISUPPER(c)) { + u++; + if (p == word) { + first = true; + } + } else { + l++; + } + } + + // If there are more UPPER than lower case letters suggest an + // ALLCAP word. Otherwise, if the first letter is UPPER then + // suggest ONECAP. Exception: "ALl" most likely should be "All", + // require three upper case letters. + if (u > l && u > 2) { + flags |= WF_ALLCAP; + } else if (first) { + flags |= WF_ONECAP; + } + + if (u >= 2 && l >= 2) { // maCARONI maCAroni + flags |= WF_MIXCAP; + } + } + return flags; +} + +/// Opposite of offset2bytes(). +/// "pp" points to the bytes and is advanced over it. +/// +/// @return the offset. +static int bytes2offset(char_u **pp) +{ + char_u *p = *pp; + int nr; + int c; + + c = *p++; + if ((c & 0x80) == 0x00) { // 1 byte + nr = c - 1; + } else if ((c & 0xc0) == 0x80) { // 2 bytes + nr = (c & 0x3f) - 1; + nr = nr * 255 + (*p++ - 1); + } else if ((c & 0xe0) == 0xc0) { // 3 bytes + nr = (c & 0x1f) - 1; + nr = nr * 255 + (*p++ - 1); + nr = nr * 255 + (*p++ - 1); + } else { // 4 bytes + nr = (c & 0x0f) - 1; + nr = nr * 255 + (*p++ - 1); + nr = nr * 255 + (*p++ - 1); + nr = nr * 255 + (*p++ - 1); + } + + *pp = p; + return nr; +} + +// values for sps_flags +#define SPS_BEST 1 +#define SPS_FAST 2 +#define SPS_DOUBLE 4 + +static int sps_flags = SPS_BEST; ///< flags from 'spellsuggest' +static int sps_limit = 9999; ///< max nr of suggestions given + +/// Check the 'spellsuggest' option. Return FAIL if it's wrong. +/// Sets "sps_flags" and "sps_limit". +int spell_check_sps(void) +{ + char *p; + char *s; + char_u buf[MAXPATHL]; + int f; + + sps_flags = 0; + sps_limit = 9999; + + for (p = (char *)p_sps; *p != NUL;) { + copy_option_part(&p, (char *)buf, MAXPATHL, ","); + + f = 0; + if (ascii_isdigit(*buf)) { + s = (char *)buf; + sps_limit = getdigits_int(&s, true, 0); + if (*s != NUL && !ascii_isdigit(*s)) { + f = -1; + } + } else if (STRCMP(buf, "best") == 0) { + f = SPS_BEST; + } else if (STRCMP(buf, "fast") == 0) { + f = SPS_FAST; + } else if (STRCMP(buf, "double") == 0) { + f = SPS_DOUBLE; + } else if (STRNCMP(buf, "expr:", 5) != 0 + && STRNCMP(buf, "file:", 5) != 0 + && (STRNCMP(buf, "timeout:", 8) != 0 + || (!ascii_isdigit(buf[8]) + && !(buf[8] == '-' && ascii_isdigit(buf[9]))))) { + f = -1; + } + + if (f == -1 || (sps_flags != 0 && f != 0)) { + sps_flags = SPS_BEST; + sps_limit = 9999; + return FAIL; + } + if (f != 0) { + sps_flags = f; + } + } + + if (sps_flags == 0) { + sps_flags = SPS_BEST; + } + + return OK; +} + +/// "z=": Find badly spelled word under or after the cursor. +/// Give suggestions for the properly spelled word. +/// In Visual mode use the highlighted word as the bad word. +/// When "count" is non-zero use that suggestion. +void spell_suggest(int count) +{ + char_u *line; + pos_T prev_cursor = curwin->w_cursor; + char_u wcopy[MAXWLEN + 2]; + char_u *p; + int c; + suginfo_T sug; + suggest_T *stp; + int mouse_used; + int need_cap; + int limit; + int selected = count; + int badlen = 0; + int msg_scroll_save = msg_scroll; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + emsg(_(e_no_spell)); + return; + } + + if (VIsual_active) { + // Use the Visually selected text as the bad word. But reject + // a multi-line selection. + if (curwin->w_cursor.lnum != VIsual.lnum) { + vim_beep(BO_SPELL); + return; + } + badlen = (int)curwin->w_cursor.col - (int)VIsual.col; + if (badlen < 0) { + badlen = -badlen; + } else { + curwin->w_cursor.col = VIsual.col; + } + badlen++; + end_visual_mode(); + // Find the start of the badly spelled word. + } else if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 + || curwin->w_cursor.col > prev_cursor.col) { + // No bad word or it starts after the cursor: use the word under the + // cursor. + curwin->w_cursor = prev_cursor; + line = get_cursor_line_ptr(); + p = line + curwin->w_cursor.col; + // Backup to before start of word. + while (p > line && spell_iswordp_nmw(p, curwin)) { + MB_PTR_BACK(line, p); + } + // Forward to start of word. + while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { + MB_PTR_ADV(p); + } + + if (!spell_iswordp_nmw(p, curwin)) { // No word found. + beep_flush(); + return; + } + curwin->w_cursor.col = (colnr_T)(p - line); + } + + // Get the word and its length. + + // Figure out if the word should be capitalised. + need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); + + // Make a copy of current line since autocommands may free the line. + line = vim_strsave(get_cursor_line_ptr()); + spell_suggest_timeout = 5000; + + // Get the list of suggestions. Limit to 'lines' - 2 or the number in + // 'spellsuggest', whatever is smaller. + if (sps_limit > Rows - 2) { + limit = Rows - 2; + } else { + limit = sps_limit; + } + spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, + true, need_cap, true); + + if (GA_EMPTY(&sug.su_ga)) { + msg(_("Sorry, no suggestions")); + } else if (count > 0) { + if (count > sug.su_ga.ga_len) { + smsg(_("Sorry, only %" PRId64 " suggestions"), + (int64_t)sug.su_ga.ga_len); + } + } else { + // When 'rightleft' is set the list is drawn right-left. + cmdmsg_rl = curwin->w_p_rl; + if (cmdmsg_rl) { + msg_col = Columns - 1; + } + + // List the suggestions. + msg_start(); + msg_row = Rows - 1; // for when 'cmdheight' > 1 + lines_left = Rows; // avoid more prompt + vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), + sug.su_badlen, sug.su_badptr); + if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) { + // And now the rabbit from the high hat: Avoid showing the + // untranslated message rightleft. + vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", + sug.su_badlen, sug.su_badptr); + } + msg_puts((const char *)IObuff); + msg_clr_eos(); + msg_putchar('\n'); + + msg_scroll = true; + for (int i = 0; i < sug.su_ga.ga_len; i++) { + stp = &SUG(sug.su_ga, i); + + // The suggested word may replace only part of the bad word, add + // the not replaced part. But only when it's not getting too long. + STRLCPY(wcopy, stp->st_word, MAXWLEN + 1); + int el = sug.su_badlen - stp->st_orglen; + if (el > 0 && stp->st_wordlen + el <= MAXWLEN) { + STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1); + } + vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); + if (cmdmsg_rl) { + rl_mirror(IObuff); + } + msg_puts((const char *)IObuff); + + vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); + msg_puts((const char *)IObuff); + + // The word may replace more than "su_badlen". + if (sug.su_badlen < stp->st_orglen) { + vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), + stp->st_orglen, sug.su_badptr); + msg_puts((const char *)IObuff); + } + + if (p_verbose > 0) { + // Add the score. + if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { + vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", + stp->st_salscore ? "s " : "", + stp->st_score, stp->st_altscore); + } else { + vim_snprintf((char *)IObuff, IOSIZE, " (%d)", + stp->st_score); + } + if (cmdmsg_rl) { + // Mirror the numbers, but keep the leading space. + rl_mirror(IObuff + 1); + } + msg_advance(30); + msg_puts((const char *)IObuff); + } + msg_putchar('\n'); + } + + cmdmsg_rl = false; + msg_col = 0; + // Ask for choice. + selected = prompt_for_number(&mouse_used); + + if (ui_has(kUIMessages)) { + ui_call_msg_clear(); + } + + if (mouse_used) { + selected -= lines_left; + } + lines_left = Rows; // avoid more prompt + // don't delay for 'smd' in normal_cmd() + msg_scroll = msg_scroll_save; + } + + if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) { + // Save the from and to text for :spellrepall. + XFREE_CLEAR(repl_from); + XFREE_CLEAR(repl_to); + + stp = &SUG(sug.su_ga, selected - 1); + if (sug.su_badlen > stp->st_orglen) { + // Replacing less than "su_badlen", append the remainder to + // repl_to. + repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen); + vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, + sug.su_badlen - stp->st_orglen, + sug.su_badptr + stp->st_orglen); + repl_to = vim_strsave(IObuff); + } else { + // Replacing su_badlen or more, use the whole word. + repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen); + repl_to = vim_strsave(stp->st_word); + } + + // Replace the word. + p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); + c = (int)(sug.su_badptr - line); + memmove(p, line, (size_t)c); + STRCPY(p + c, stp->st_word); + STRCAT(p, sug.su_badptr + stp->st_orglen); + + // For redo we use a change-word command. + ResetRedobuff(); + AppendToRedobuff("ciw"); + AppendToRedobuffLit((char *)p + c, + stp->st_wordlen + sug.su_badlen - stp->st_orglen); + AppendCharToRedobuff(ESC); + + // "p" may be freed here + ml_replace(curwin->w_cursor.lnum, (char *)p, false); + curwin->w_cursor.col = c; + + inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); + } else { + curwin->w_cursor = prev_cursor; + } + + spell_find_cleanup(&sug); + xfree(line); + curwin->w_p_spell = wo_spell_save; +} + +/// Find spell suggestions for "word". Return them in the growarray "*gap" as +/// a list of allocated strings. +/// +/// @param maxcount maximum nr of suggestions +/// @param need_cap 'spellcapcheck' matched +void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive) +{ + suginfo_T sug; + suggest_T *stp; + char_u *wcopy; + + spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); + + // Make room in "gap". + ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); + ga_grow(gap, sug.su_ga.ga_len); + for (int i = 0; i < sug.su_ga.ga_len; i++) { + stp = &SUG(sug.su_ga, i); + + // The suggested word may replace only part of "word", add the not + // replaced part. + wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1); + STRCPY(wcopy, stp->st_word); + STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen); + ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy; + } + + spell_find_cleanup(&sug); +} + +/// Find spell suggestions for the word at the start of "badptr". +/// Return the suggestions in "su->su_ga". +/// The maximum number of suggestions is "maxcount". +/// Note: does use info for the current window. +/// This is based on the mechanisms of Aspell, but completely reimplemented. +/// +/// @param badlen length of bad word or 0 if unknown +/// @param banbadword don't include badword in suggestions +/// @param need_cap word should start with capital +static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, + bool banbadword, bool need_cap, bool interactive) +{ + hlf_T attr = HLF_COUNT; + char_u buf[MAXPATHL]; + char *p; + bool do_combine = false; + char_u *sps_copy; + static bool expr_busy = false; + int c; + langp_T *lp; + bool did_intern = false; + + // Set the info in "*su". + CLEAR_POINTER(su); + ga_init(&su->su_ga, (int)sizeof(suggest_T), 10); + ga_init(&su->su_sga, (int)sizeof(suggest_T), 10); + if (*badptr == NUL) { + return; + } + hash_init(&su->su_banned); + + su->su_badptr = badptr; + if (badlen != 0) { + su->su_badlen = badlen; + } else { + size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false); + assert(tmplen <= INT_MAX); + su->su_badlen = (int)tmplen; + } + su->su_maxcount = maxcount; + su->su_maxscore = SCORE_MAXINIT; + + if (su->su_badlen >= MAXWLEN) { + su->su_badlen = MAXWLEN - 1; // just in case + } + STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); + (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, + MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + su->su_fbadword[su->su_badlen] = NUL; + + // get caps flags for bad word + su->su_badflags = badword_captype(su->su_badptr, + su->su_badptr + su->su_badlen); + if (need_cap) { + su->su_badflags |= WF_ONECAP; + } + + // Find the default language for sound folding. We simply use the first + // one in 'spelllang' that supports sound folding. That's good for when + // using multiple files for one language, it's not that bad when mixing + // languages (e.g., "pl,en"). + for (int i = 0; i < curbuf->b_s.b_langp.ga_len; i++) { + lp = LANGP_ENTRY(curbuf->b_s.b_langp, i); + if (lp->lp_sallang != NULL) { + su->su_sallang = lp->lp_sallang; + break; + } + } + + // Soundfold the bad word with the default sound folding, so that we don't + // have to do this many times. + if (su->su_sallang != NULL) { + spell_soundfold(su->su_sallang, su->su_fbadword, true, + su->su_sal_badword); + } + + // If the word is not capitalised and spell_check() doesn't consider the + // word to be bad then it might need to be capitalised. Add a suggestion + // for that. + c = utf_ptr2char((char *)su->su_badptr); + if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) { + make_case_word(su->su_badword, buf, WF_ONECAP); + add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE, + 0, true, su->su_sallang, false); + } + + // Ban the bad word itself. It may appear in another region. + if (banbadword) { + add_banned(su, su->su_badword); + } + + // Make a copy of 'spellsuggest', because the expression may change it. + sps_copy = vim_strsave(p_sps); + + // Loop over the items in 'spellsuggest'. + for (p = (char *)sps_copy; *p != NUL;) { + copy_option_part(&p, (char *)buf, MAXPATHL, ","); + + if (STRNCMP(buf, "expr:", 5) == 0) { + // Evaluate an expression. Skip this when called recursively, + // when using spellsuggest() in the expression. + if (!expr_busy) { + expr_busy = true; + spell_suggest_expr(su, buf + 5); + expr_busy = false; + } + } else if (STRNCMP(buf, "file:", 5) == 0) { + // Use list of suggestions in a file. + spell_suggest_file(su, buf + 5); + } else if (STRNCMP(buf, "timeout:", 8) == 0) { + // Limit the time searching for suggestions. + spell_suggest_timeout = atol((char *)buf + 8); + } else if (!did_intern) { + // Use internal method once. + spell_suggest_intern(su, interactive); + if (sps_flags & SPS_DOUBLE) { + do_combine = true; + } + did_intern = true; + } + } + + xfree(sps_copy); + + if (do_combine) { + // Combine the two list of suggestions. This must be done last, + // because sorting changes the order again. + score_combine(su); + } +} + +/// Find suggestions by evaluating expression "expr". +static void spell_suggest_expr(suginfo_T *su, char_u *expr) +{ + int score; + const char *p; + + // The work is split up in a few parts to avoid having to export + // suginfo_T. + // First evaluate the expression and get the resulting list. + list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr); + if (list != NULL) { + // Loop over the items in the list. + TV_LIST_ITER(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + // Get the word and the score from the items. + score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); + if (score >= 0 && score <= su->su_maxscore) { + add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, + score, 0, true, su->su_sallang, false); + } + } + }); + tv_list_unref(list); + } + + // Remove bogus suggestions, sort and truncate at "maxcount". + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); +} + +/// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'. +static void spell_suggest_file(suginfo_T *su, char_u *fname) +{ + FILE *fd; + char_u line[MAXWLEN * 2]; + char_u *p; + int len; + char_u cword[MAXWLEN]; + + // Open the file. + fd = os_fopen((char *)fname, "r"); + if (fd == NULL) { + semsg(_(e_notopen), fname); + return; + } + + // Read it line by line. + while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) { + line_breakcheck(); + + p = (char_u *)vim_strchr((char *)line, '/'); + if (p == NULL) { + continue; // No Tab found, just skip the line. + } + *p++ = NUL; + if (STRICMP(su->su_badword, line) == 0) { + // Match! Isolate the good word, until CR or NL. + for (len = 0; p[len] >= ' '; len++) {} + p[len] = NUL; + + // If the suggestion doesn't have specific case duplicate the case + // of the bad word. + if (captype(p, NULL) == 0) { + make_case_word(p, cword, su->su_badflags); + p = cword; + } + + add_suggestion(su, &su->su_ga, p, su->su_badlen, + SCORE_FILE, 0, true, su->su_sallang, false); + } + } + + fclose(fd); + + // Remove bogus suggestions, sort and truncate at "maxcount". + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); +} + +/// Find suggestions for the internal method indicated by "sps_flags". +static void spell_suggest_intern(suginfo_T *su, bool interactive) +{ + // Load the .sug file(s) that are available and not done yet. + suggest_load_files(); + + // 1. Try special cases, such as repeating a word: "the the" -> "the". + // + // Set a maximum score to limit the combination of operations that is + // tried. + suggest_try_special(su); + + // 2. Try inserting/deleting/swapping/changing a letter, use REP entries + // from the .aff file and inserting a space (split the word). + suggest_try_change(su); + + // For the resulting top-scorers compute the sound-a-like score. + if (sps_flags & SPS_DOUBLE) { + score_comp_sal(su); + } + + // 3. Try finding sound-a-like words. + if ((sps_flags & SPS_FAST) == 0) { + if (sps_flags & SPS_BEST) { + // Adjust the word score for the suggestions found so far for how + // they sounds like. + rescore_suggestions(su); + } + + // While going through the soundfold tree "su_maxscore" is the score + // for the soundfold word, limits the changes that are being tried, + // and "su_sfmaxscore" the rescored score, which is set by + // cleanup_suggestions(). + // First find words with a small edit distance, because this is much + // faster and often already finds the top-N suggestions. If we didn't + // find many suggestions try again with a higher edit distance. + // "sl_sounddone" is used to avoid doing the same word twice. + suggest_try_soundalike_prep(); + su->su_maxscore = SCORE_SFMAX1; + su->su_sfmaxscore = SCORE_MAXINIT * 3; + suggest_try_soundalike(su); + if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { + // We didn't find enough matches, try again, allowing more + // changes to the soundfold word. + su->su_maxscore = SCORE_SFMAX2; + suggest_try_soundalike(su); + if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) { + // Still didn't find enough matches, try again, allowing even + // more changes to the soundfold word. + su->su_maxscore = SCORE_SFMAX3; + suggest_try_soundalike(su); + } + } + su->su_maxscore = su->su_sfmaxscore; + suggest_try_soundalike_finish(); + } + + // When CTRL-C was hit while searching do show the results. Only clear + // got_int when using a command, not for spellsuggest(). + os_breakcheck(); + if (interactive && got_int) { + (void)vgetc(); + got_int = false; + } + + if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) { + if (sps_flags & SPS_BEST) { + // Adjust the word score for how it sounds like. + rescore_suggestions(su); + } + + // Remove bogus suggestions, sort and truncate at "maxcount". + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); + } +} + +/// Free the info put in "*su" by spell_find_suggest(). +static void spell_find_cleanup(suginfo_T *su) +{ +#define FREE_SUG_WORD(sug) xfree((sug)->st_word) + // Free the suggestions. + GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD); + GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD); + + // Free the banned words. + hash_clear_all(&su->su_banned, 0); +} + +/// Try finding suggestions by recognizing specific situations. +static void suggest_try_special(suginfo_T *su) +{ + int c; + char_u word[MAXWLEN]; + + // Recognize a word that is repeated: "the the". + char_u *p = skiptowhite(su->su_fbadword); + size_t len = (size_t)(p - su->su_fbadword); + p = (char_u *)skipwhite((char *)p); + if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) { + // Include badflags: if the badword is onecap or allcap + // use that for the goodword too: "The the" -> "The". + c = su->su_fbadword[len]; + su->su_fbadword[len] = NUL; + make_case_word(su->su_fbadword, word, su->su_badflags); + su->su_fbadword[len] = (char_u)c; + + // Give a soundalike score of 0, compute the score as if deleting one + // character. + add_suggestion(su, &su->su_ga, word, su->su_badlen, + RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false); + } +} + +// Measure how much time is spent in each state. +// Output is dumped in "suggestprof". + +#ifdef SUGGEST_PROFILE +proftime_T current; +proftime_T total; +proftime_T times[STATE_FINAL + 1]; +long counts[STATE_FINAL + 1]; + +static void prof_init(void) +{ + for (int i = 0; i <= STATE_FINAL; i++) { + profile_zero(×[i]); + counts[i] = 0; + } + profile_start(¤t); + profile_start(&total); +} + +/// call before changing state +static void prof_store(state_T state) +{ + profile_end(¤t); + profile_add(×[state], ¤t); + counts[state]++; + profile_start(¤t); +} +# define PROF_STORE(state) prof_store(state); + +static void prof_report(char *name) +{ + FILE *fd = fopen("suggestprof", "a"); + + profile_end(&total); + fprintf(fd, "-----------------------\n"); + fprintf(fd, "%s: %s\n", name, profile_msg(&total)); + for (int i = 0; i <= STATE_FINAL; i++) { + fprintf(fd, "%d: %s ("%" PRId64)\n", i, profile_msg(×[i]), counts[i]); + } + fclose(fd); +} +#else +# define PROF_STORE(state) +#endif + +/// Try finding suggestions by adding/removing/swapping letters. +static void suggest_try_change(suginfo_T *su) +{ + char_u fword[MAXWLEN]; // copy of the bad word, case-folded + int n; + char_u *p; + langp_T *lp; + + // We make a copy of the case-folded bad word, so that we can modify it + // to find matches (esp. REP items). Append some more text, changing + // chars after the bad word may help. + STRCPY(fword, su->su_fbadword); + n = (int)STRLEN(fword); + p = su->su_badptr + su->su_badlen; + (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n); + + // Make sure the resulting text is not longer than the original text. + n = (int)STRLEN(su->su_badptr); + if (n < MAXWLEN) { + fword[n] = NUL; + } + + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + + // If reloading a spell file fails it's still in the list but + // everything has been cleared. + if (lp->lp_slang->sl_fbyts == NULL) { + continue; + } + + // Try it for this language. Will add possible suggestions. +#ifdef SUGGEST_PROFILE + prof_init(); +#endif + suggest_trie_walk(su, lp, fword, false); +#ifdef SUGGEST_PROFILE + prof_report("try_change"); +#endif + } +} + +// Check the maximum score, if we go over it we won't try this change. +#define TRY_DEEPER(su, stack, depth, add) \ + ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) + +/// Try finding suggestions by adding/removing/swapping letters. +/// +/// This uses a state machine. At each node in the tree we try various +/// operations. When trying if an operation works "depth" is increased and the +/// stack[] is used to store info. This allows combinations, thus insert one +/// character, replace one and delete another. The number of changes is +/// limited by su->su_maxscore. +/// +/// After implementing this I noticed an article by Kemal Oflazer that +/// describes something similar: "Error-tolerant Finite State Recognition with +/// Applications to Morphological Analysis and Spelling Correction" (1996). +/// The implementation in the article is simplified and requires a stack of +/// unknown depth. The implementation here only needs a stack depth equal to +/// the length of the word. +/// +/// This is also used for the sound-folded word, "soundfold" is true then. +/// The mechanism is the same, but we find a match with a sound-folded word +/// that comes from one or more original words. Each of these words may be +/// added, this is done by add_sound_suggest(). +/// Don't use: +/// the prefix tree or the keep-case tree +/// "su->su_badlen" +/// anything to do with upper and lower case +/// anything to do with word or non-word characters ("spell_iswordp()") +/// banned words +/// word flags (rare, region, compounding) +/// word splitting for now +/// "similar_chars()" +/// use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep" +static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool soundfold) +{ + char_u tword[MAXWLEN]; // good word collected so far + trystate_T stack[MAXWLEN]; + char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; + // concatenation of prefix compound + // words and split word. NUL terminated + // when going deeper but not when coming + // back. + char_u compflags[MAXWLEN]; // compound flags, one for each word + trystate_T *sp; + int newscore; + int score; + char_u *byts, *fbyts, *pbyts; + idx_T *idxs, *fidxs, *pidxs; + int depth; + int c, c2, c3; + int n = 0; + int flags; + garray_T *gap; + idx_T arridx; + int len; + char_u *p; + fromto_T *ftp; + int fl = 0, tl; + int repextra = 0; // extra bytes in fword[] from REP item + slang_T *slang = lp->lp_slang; + int fword_ends; + bool goodword_ends; +#ifdef DEBUG_TRIEWALK + // Stores the name of the change made at each level. + char_u changename[MAXWLEN][80]; +#endif + int breakcheckcount = 1000; + bool compound_ok; + + // Go through the whole case-fold tree, try changes at each node. + // "tword[]" contains the word collected from nodes in the tree. + // "fword[]" the word we are trying to match with (initially the bad + // word). + depth = 0; + sp = &stack[0]; + CLEAR_POINTER(sp); // -V1068 + sp->ts_curi = 1; + + if (soundfold) { + // Going through the soundfold tree. + byts = fbyts = slang->sl_sbyts; + idxs = fidxs = slang->sl_sidxs; + pbyts = NULL; + pidxs = NULL; + sp->ts_prefixdepth = PFD_NOPREFIX; + sp->ts_state = STATE_START; + } else { + // When there are postponed prefixes we need to use these first. At + // the end of the prefix we continue in the case-fold tree. + fbyts = slang->sl_fbyts; + fidxs = slang->sl_fidxs; + pbyts = slang->sl_pbyts; + pidxs = slang->sl_pidxs; + if (pbyts != NULL) { + byts = pbyts; + idxs = pidxs; + sp->ts_prefixdepth = PFD_PREFIXTREE; + sp->ts_state = STATE_NOPREFIX; // try without prefix first + } else { + byts = fbyts; + idxs = fidxs; + sp->ts_prefixdepth = PFD_NOPREFIX; + sp->ts_state = STATE_START; + } + } + + // The loop may take an indefinite amount of time. Break out after some + // time. + proftime_T time_limit; + if (spell_suggest_timeout > 0) { + time_limit = profile_setlimit(spell_suggest_timeout); + } + + // Loop to find all suggestions. At each round we either: + // - For the current state try one operation, advance "ts_curi", + // increase "depth". + // - When a state is done go to the next, set "ts_state". + // - When all states are tried decrease "depth". + while (depth >= 0 && !got_int) { + sp = &stack[depth]; + switch (sp->ts_state) { + case STATE_START: + case STATE_NOPREFIX: + // Start of node: Deal with NUL bytes, which means + // tword[] may end here. + arridx = sp->ts_arridx; // current node in the tree + len = byts[arridx]; // bytes in this node + arridx += sp->ts_curi; // index of current byte + + if (sp->ts_prefixdepth == PFD_PREFIXTREE) { + // Skip over the NUL bytes, we use them later. + for (n = 0; n < len && byts[arridx + n] == 0; n++) {} + sp->ts_curi = (int16_t)(sp->ts_curi + n); + + // Always past NUL bytes now. + n = (int)sp->ts_state; + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_ENDNUL; + sp->ts_save_badflags = (char_u)su->su_badflags; + + // At end of a prefix or at start of prefixtree: check for + // following word. + if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { + // Set su->su_badflags to the caps type at this position. + // Use the caps type until here for the prefix itself. + n = nofold_len(fword, sp->ts_fidx, su->su_badptr); + flags = badword_captype(su->su_badptr, su->su_badptr + n); + su->su_badflags = badword_captype(su->su_badptr + n, + su->su_badptr + su->su_badlen); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf) +#endif + go_deeper(stack, depth, 0); + depth++; + sp = &stack[depth]; + sp->ts_prefixdepth = (char_u)(depth - 1); + byts = fbyts; + idxs = fidxs; + sp->ts_arridx = 0; + + // Move the prefix to preword[] with the right case + // and make find_keepcap_word() works. + tword[sp->ts_twordlen] = NUL; + make_case_word(tword + sp->ts_splitoff, + preword + sp->ts_prewordlen, flags); + sp->ts_prewordlen = (char_u)STRLEN(preword); + sp->ts_splitoff = sp->ts_twordlen; + } + break; + } + + if (sp->ts_curi > len || byts[arridx] != 0) { + // Past bytes in node and/or past NUL bytes. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_ENDNUL; + sp->ts_save_badflags = (char_u)su->su_badflags; + break; + } + + // End of word in tree. + sp->ts_curi++; // eat one NUL byte + + flags = (int)idxs[arridx]; + + // Skip words with the NOSUGGEST flag. + if (flags & WF_NOSUGGEST) { + break; + } + + fword_ends = (fword[sp->ts_fidx] == NUL + || (soundfold + ? ascii_iswhite(fword[sp->ts_fidx]) + : !spell_iswordp(fword + sp->ts_fidx, curwin))); + tword[sp->ts_twordlen] = NUL; + + if (sp->ts_prefixdepth <= PFD_NOTSPECIAL + && (sp->ts_flags & TSF_PREFIXOK) == 0 + && pbyts != NULL) { + // There was a prefix before the word. Check that the prefix + // can be used with this word. + // Count the length of the NULs in the prefix. If there are + // none this must be the first try without a prefix. + n = stack[sp->ts_prefixdepth].ts_arridx; + len = pbyts[n++]; + for (c = 0; c < len && pbyts[n + c] == 0; c++) {} + if (c > 0) { + c = valid_word_prefix(c, n, flags, + tword + sp->ts_splitoff, slang, false); + if (c == 0) { + break; + } + + // Use the WF_RARE flag for a rare prefix. + if (c & WF_RAREPFX) { + flags |= WF_RARE; + } + + // Tricky: when checking for both prefix and compounding + // we run into the prefix flag first. + // Remember that it's OK, so that we accept the prefix + // when arriving at a compound flag. + sp->ts_flags |= TSF_PREFIXOK; + } + } + + // Check NEEDCOMPOUND: can't use word without compounding. Do try + // appending another compound word below. + if (sp->ts_complen == sp->ts_compsplit && fword_ends + && (flags & WF_NEEDCOMP)) { + goodword_ends = false; + } else { + goodword_ends = true; + } + + p = NULL; + compound_ok = true; + if (sp->ts_complen > sp->ts_compsplit) { + if (slang->sl_nobreak) { + // There was a word before this word. When there was no + // change in this word (it was correct) add the first word + // as a suggestion. If this word was corrected too, we + // need to check if a correct word follows. + if (sp->ts_fidx - sp->ts_splitfidx + == sp->ts_twordlen - sp->ts_splitoff + && STRNCMP(fword + sp->ts_splitfidx, + tword + sp->ts_splitoff, + sp->ts_fidx - sp->ts_splitfidx) == 0) { + preword[sp->ts_prewordlen] = NUL; + newscore = score_wordcount_adj(slang, sp->ts_score, + preword + sp->ts_prewordlen, + sp->ts_prewordlen > 0); + // Add the suggestion if the score isn't too bad. + if (newscore <= su->su_maxscore) { + add_suggestion(su, &su->su_ga, preword, + sp->ts_splitfidx - repextra, + newscore, 0, false, + lp->lp_sallang, false); + } + break; + } + } else { + // There was a compound word before this word. If this + // word does not support compounding then give up + // (splitting is tried for the word without compound + // flag). + if (((unsigned)flags >> 24) == 0 + || sp->ts_twordlen - sp->ts_splitoff + < slang->sl_compminlen) { + break; + } + // For multi-byte chars check character length against + // COMPOUNDMIN. + if (slang->sl_compminlen > 0 + && mb_charlen(tword + sp->ts_splitoff) + < slang->sl_compminlen) { + break; + } + + compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); + compflags[sp->ts_complen + 1] = NUL; + STRLCPY(preword + sp->ts_prewordlen, + tword + sp->ts_splitoff, + sp->ts_twordlen - sp->ts_splitoff + 1); + + // Verify CHECKCOMPOUNDPATTERN rules. + if (match_checkcompoundpattern(preword, sp->ts_prewordlen, + &slang->sl_comppat)) { + compound_ok = false; + } + + if (compound_ok) { + p = preword; + while (*skiptowhite(p) != NUL) { + p = (char_u *)skipwhite((char *)skiptowhite(p)); + } + if (fword_ends && !can_compound(slang, p, + compflags + sp->ts_compsplit)) { + // Compound is not allowed. But it may still be + // possible if we add another (short) word. + compound_ok = false; + } + } + + // Get pointer to last char of previous word. + p = preword + sp->ts_prewordlen; + MB_PTR_BACK(preword, p); + } + } + + // Form the word with proper case in preword. + // If there is a word from a previous split, append. + // For the soundfold tree don't change the case, simply append. + if (soundfold) { + STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff); + } else if (flags & WF_KEEPCAP) { + // Must find the word in the keep-case tree. + find_keepcap_word(slang, tword + sp->ts_splitoff, + preword + sp->ts_prewordlen); + } else { + // Include badflags: If the badword is onecap or allcap + // use that for the goodword too. But if the badword is + // allcap and it's only one char long use onecap. + c = su->su_badflags; + if ((c & WF_ALLCAP) + && su->su_badlen == + utfc_ptr2len((char *)su->su_badptr)) { + c = WF_ONECAP; + } + c |= flags; + + // When appending a compound word after a word character don't + // use Onecap. + if (p != NULL && spell_iswordp_nmw(p, curwin)) { + c &= ~WF_ONECAP; + } + make_case_word(tword + sp->ts_splitoff, + preword + sp->ts_prewordlen, c); + } + + if (!soundfold) { + // Don't use a banned word. It may appear again as a good + // word, thus remember it. + if (flags & WF_BANNED) { + add_banned(su, preword + sp->ts_prewordlen); + break; + } + if ((sp->ts_complen == sp->ts_compsplit + && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen)) + || WAS_BANNED(su, (char *)preword)) { + if (slang->sl_compprog == NULL) { + break; + } + // the word so far was banned but we may try compounding + goodword_ends = false; + } + } + + newscore = 0; + if (!soundfold) { // soundfold words don't have flags + if ((flags & WF_REGION) + && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { + newscore += SCORE_REGION; + } + if (flags & WF_RARE) { + newscore += SCORE_RARE; + } + + if (!spell_valid_case(su->su_badflags, + captype(preword + sp->ts_prewordlen, NULL))) { + newscore += SCORE_ICASE; + } + } + + // TODO(vim): how about splitting in the soundfold tree? + if (fword_ends + && goodword_ends + && sp->ts_fidx >= sp->ts_fidxtry + && compound_ok) { + // The badword also ends: add suggestions. +#ifdef DEBUG_TRIEWALK + if (soundfold && STRCMP(preword, "smwrd") == 0) { + int j; + + // print the stack of changes that brought us here + smsg("------ %s -------", fword); + for (j = 0; j < depth; j++) { + smsg("%s", changename[j]); + } + } +#endif + if (soundfold) { + // For soundfolded words we need to find the original + // words, the edit distance and then add them. + add_sound_suggest(su, preword, sp->ts_score, lp); + } else if (sp->ts_fidx > 0) { + // Give a penalty when changing non-word char to word + // char, e.g., "thes," -> "these". + p = fword + sp->ts_fidx; + MB_PTR_BACK(fword, p); + if (!spell_iswordp(p, curwin) && *preword != NUL) { + p = preword + STRLEN(preword); + MB_PTR_BACK(preword, p); + if (spell_iswordp(p, curwin)) { + newscore += SCORE_NONWORD; + } + } + + // Give a bonus to words seen before. + score = score_wordcount_adj(slang, + sp->ts_score + newscore, + preword + sp->ts_prewordlen, + sp->ts_prewordlen > 0); + + // Add the suggestion if the score isn't too bad. + if (score <= su->su_maxscore) { + add_suggestion(su, &su->su_ga, preword, + sp->ts_fidx - repextra, + score, 0, false, lp->lp_sallang, false); + + if (su->su_badflags & WF_MIXCAP) { + // We really don't know if the word should be + // upper or lower case, add both. + c = captype(preword, NULL); + if (c == 0 || c == WF_ALLCAP) { + make_case_word(tword + sp->ts_splitoff, + preword + sp->ts_prewordlen, + c == 0 ? WF_ALLCAP : 0); + + add_suggestion(su, &su->su_ga, preword, + sp->ts_fidx - repextra, + score + SCORE_ICASE, 0, false, + lp->lp_sallang, false); + } + } + } + } + } + + // Try word split and/or compounding. + if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends) + // Don't split in the middle of a character + && (sp->ts_tcharlen == 0)) { + bool try_compound; + int try_split; + + // If past the end of the bad word don't try a split. + // Otherwise try changing the next word. E.g., find + // suggestions for "the the" where the second "the" is + // different. It's done like a split. + // TODO(vim): word split for soundfold words + try_split = (sp->ts_fidx - repextra < su->su_badlen) + && !soundfold; + + // Get here in several situations: + // 1. The word in the tree ends: + // If the word allows compounding try that. Otherwise try + // a split by inserting a space. For both check that a + // valid words starts at fword[sp->ts_fidx]. + // For NOBREAK do like compounding to be able to check if + // the next word is valid. + // 2. The badword does end, but it was due to a change (e.g., + // a swap). No need to split, but do check that the + // following word is valid. + // 3. The badword and the word in the tree end. It may still + // be possible to compound another (short) word. + try_compound = false; + if (!soundfold + && !slang->sl_nocompoundsugs + && slang->sl_compprog != NULL + && ((unsigned)flags >> 24) != 0 + && sp->ts_twordlen - sp->ts_splitoff + >= slang->sl_compminlen + && (slang->sl_compminlen == 0 + || mb_charlen(tword + sp->ts_splitoff) + >= slang->sl_compminlen) + && (slang->sl_compsylmax < MAXWLEN + || sp->ts_complen + 1 - sp->ts_compsplit + < slang->sl_compmax) + && (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) { + try_compound = true; + compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); + compflags[sp->ts_complen + 1] = NUL; + } + + // For NOBREAK we never try splitting, it won't make any word + // valid. + if (slang->sl_nobreak && !slang->sl_nocompoundsugs) { + try_compound = true; + } else if (!fword_ends + && try_compound + && (sp->ts_flags & TSF_DIDSPLIT) == 0) { + // If we could add a compound word, and it's also possible to + // split at this point, do the split first and set + // TSF_DIDSPLIT to avoid doing it again. + try_compound = false; + sp->ts_flags |= TSF_DIDSPLIT; + sp->ts_curi--; // do the same NUL again + compflags[sp->ts_complen] = NUL; + } else { + sp->ts_flags &= (char_u) ~TSF_DIDSPLIT; + } + + if (try_split || try_compound) { + if (!try_compound && (!fword_ends || !goodword_ends)) { + // If we're going to split need to check that the + // words so far are valid for compounding. If there + // is only one word it must not have the NEEDCOMPOUND + // flag. + if (sp->ts_complen == sp->ts_compsplit + && (flags & WF_NEEDCOMP)) { + break; + } + p = preword; + while (*skiptowhite(p) != NUL) { + p = (char_u *)skipwhite((char *)skiptowhite(p)); + } + if (sp->ts_complen > sp->ts_compsplit + && !can_compound(slang, p, + compflags + sp->ts_compsplit)) { + break; + } + + if (slang->sl_nosplitsugs) { + newscore += SCORE_SPLIT_NO; + } else { + newscore += SCORE_SPLIT; + } + + // Give a bonus to words seen before. + newscore = score_wordcount_adj(slang, newscore, + preword + sp->ts_prewordlen, true); + } + + if (TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + if (!try_compound && !fword_ends) { + sprintf(changename[depth], "%.*s-%s: split", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx); + } else { + sprintf(changename[depth], "%.*s-%s: compound", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx); + } +#endif + // Save things to be restored at STATE_SPLITUNDO. + sp->ts_save_badflags = (char_u)su->su_badflags; + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SPLITUNDO; + + depth++; + sp = &stack[depth]; + + // Append a space to preword when splitting. + if (!try_compound && !fword_ends) { + STRCAT(preword, " "); + } + sp->ts_prewordlen = (char_u)STRLEN(preword); + sp->ts_splitoff = sp->ts_twordlen; + sp->ts_splitfidx = sp->ts_fidx; + + // If the badword has a non-word character at this + // position skip it. That means replacing the + // non-word character with a space. Always skip a + // character when the word ends. But only when the + // good word can end. + if (((!try_compound && !spell_iswordp_nmw(fword + + sp->ts_fidx, + curwin)) + || fword_ends) + && fword[sp->ts_fidx] != NUL + && goodword_ends) { + int l; + + l = utfc_ptr2len((char *)fword + sp->ts_fidx); + if (fword_ends) { + // Copy the skipped character to preword. + memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l); + sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l); + preword[sp->ts_prewordlen] = NUL; + } else { + sp->ts_score -= SCORE_SPLIT - SCORE_SUBST; + } + sp->ts_fidx = (char_u)(sp->ts_fidx + l); + } + + // When compounding include compound flag in + // compflags[] (already set above). When splitting we + // may start compounding over again. + if (try_compound) { + sp->ts_complen++; + } else { + sp->ts_compsplit = sp->ts_complen; + } + sp->ts_prefixdepth = PFD_NOPREFIX; + + // set su->su_badflags to the caps type at this + // position + n = nofold_len(fword, sp->ts_fidx, su->su_badptr); + su->su_badflags = badword_captype(su->su_badptr + n, + su->su_badptr + su->su_badlen); + + // Restart at top of the tree. + sp->ts_arridx = 0; + + // If there are postponed prefixes, try these too. + if (pbyts != NULL) { + byts = pbyts; + idxs = pidxs; + sp->ts_prefixdepth = PFD_PREFIXTREE; + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_NOPREFIX; + } + } + } + } + break; + + case STATE_SPLITUNDO: + // Undo the changes done for word split or compound word. + su->su_badflags = sp->ts_save_badflags; + + // Continue looking for NUL bytes. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_START; + + // In case we went into the prefix tree. + byts = fbyts; + idxs = fidxs; + break; + + case STATE_ENDNUL: + // Past the NUL bytes in the node. + su->su_badflags = sp->ts_save_badflags; + if (fword[sp->ts_fidx] == NUL + && sp->ts_tcharlen == 0) { + // The badword ends, can't use STATE_PLAIN. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_DEL; + break; + } + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_PLAIN; + FALLTHROUGH; + + case STATE_PLAIN: + // Go over all possible bytes at this node, add each to tword[] + // and use child node. "ts_curi" is the index. + arridx = sp->ts_arridx; + if (sp->ts_curi > byts[arridx]) { + // Done all bytes at this node, do next state. When still at + // already changed bytes skip the other tricks. + PROF_STORE(sp->ts_state) + if (sp->ts_fidx >= sp->ts_fidxtry) { + sp->ts_state = STATE_DEL; + } else { + sp->ts_state = STATE_FINAL; + } + } else { + arridx += sp->ts_curi++; + c = byts[arridx]; + + // Normal byte, go one level deeper. If it's not equal to the + // byte in the bad word adjust the score. But don't even try + // when the byte was already changed. And don't try when we + // just deleted this byte, accepting it is always cheaper than + // delete + substitute. + if (c == fword[sp->ts_fidx] + || (sp->ts_tcharlen > 0 + && sp->ts_isdiff != DIFF_NONE)) { + newscore = 0; + } else { + newscore = SCORE_SUBST; + } + if ((newscore == 0 + || (sp->ts_fidx >= sp->ts_fidxtry + && ((sp->ts_flags & TSF_DIDDEL) == 0 + || c != fword[sp->ts_delidx]))) + && TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + if (newscore > 0) { + sprintf(changename[depth], "%.*s-%s: subst %c to %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + fword[sp->ts_fidx], c); + } else { + sprintf(changename[depth], "%.*s-%s: accept %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + fword[sp->ts_fidx]); + } +#endif + depth++; + sp = &stack[depth]; + if (fword[sp->ts_fidx] != NUL) { + sp->ts_fidx++; + } + tword[sp->ts_twordlen++] = (char_u)c; + sp->ts_arridx = idxs[arridx]; + if (newscore == SCORE_SUBST) { + sp->ts_isdiff = DIFF_YES; + } + // Multi-byte characters are a bit complicated to + // handle: They differ when any of the bytes differ + // and then their length may also differ. + if (sp->ts_tcharlen == 0) { + // First byte. + sp->ts_tcharidx = 0; + sp->ts_tcharlen = MB_BYTE2LEN(c); + sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1); + sp->ts_isdiff = (newscore != 0) + ? DIFF_YES : DIFF_NONE; + } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) { + // When inserting trail bytes don't advance in the + // bad word. + sp->ts_fidx--; + } + if (++sp->ts_tcharidx == sp->ts_tcharlen) { + // Last byte of character. + if (sp->ts_isdiff == DIFF_YES) { + // Correct ts_fidx for the byte length of the + // character (we didn't check that before). + sp->ts_fidx = (char_u)(sp->ts_fcharstart + + utfc_ptr2len((char *)fword + sp->ts_fcharstart)); + + // For changing a composing character adjust + // the score from SCORE_SUBST to + // SCORE_SUBCOMP. + if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen + - sp->ts_tcharlen)) + && utf_iscomposing(utf_ptr2char((char *)fword + + sp->ts_fcharstart))) { + sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; + } else if (!soundfold + && slang->sl_has_map + && similar_chars(slang, + utf_ptr2char((char *)tword + sp->ts_twordlen - + sp->ts_tcharlen), + utf_ptr2char((char *)fword + sp->ts_fcharstart))) { + // For a similar character adjust score from + // SCORE_SUBST to SCORE_SIMILAR. + sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR; + } + } else if (sp->ts_isdiff == DIFF_INSERT + && sp->ts_twordlen > sp->ts_tcharlen) { + p = tword + sp->ts_twordlen - sp->ts_tcharlen; + c = utf_ptr2char((char *)p); + if (utf_iscomposing(c)) { + // Inserting a composing char doesn't + // count that much. + sp->ts_score -= SCORE_INS - SCORE_INSCOMP; + } else { + // If the previous character was the same, + // thus doubling a character, give a bonus + // to the score. Also for the soundfold + // tree (might seem illogical but does + // give better scores). + MB_PTR_BACK(tword, p); + if (c == utf_ptr2char((char *)p)) { + sp->ts_score -= SCORE_INS - SCORE_INSDUP; + } + } + } + + // Starting a new char, reset the length. + sp->ts_tcharlen = 0; + } + } + } + break; + + case STATE_DEL: + // When past the first byte of a multi-byte char don't try + // delete/insert/swap a character. + if (sp->ts_tcharlen > 0) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + // Try skipping one character in the bad word (delete it). + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_INS_PREP; + sp->ts_curi = 1; + if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') { + // Deleting a vowel at the start of a word counts less, see + // soundalike_score(). + newscore = 2 * SCORE_DEL / 3; + } else { + newscore = SCORE_DEL; + } + if (fword[sp->ts_fidx] != NUL + && TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: delete %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + fword[sp->ts_fidx]); +#endif + depth++; + + // Remember what character we deleted, so that we can avoid + // inserting it again. + stack[depth].ts_flags |= TSF_DIDDEL; + stack[depth].ts_delidx = sp->ts_fidx; + + // Advance over the character in fword[]. Give a bonus to the + // score if the same character is following "nn" -> "n". It's + // a bit illogical for soundfold tree but it does give better + // results. + c = utf_ptr2char((char *)fword + sp->ts_fidx); + stack[depth].ts_fidx = + (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)fword + sp->ts_fidx)); + if (utf_iscomposing(c)) { + stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; + } else if (c == utf_ptr2char((char *)fword + stack[depth].ts_fidx)) { + stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; + } + + break; + } + FALLTHROUGH; + + case STATE_INS_PREP: + if (sp->ts_flags & TSF_DIDDEL) { + // If we just deleted a byte then inserting won't make sense, + // a substitute is always cheaper. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP; + break; + } + + // skip over NUL bytes + n = sp->ts_arridx; + for (;;) { + if (sp->ts_curi > byts[n]) { + // Only NUL bytes at this node, go to next state. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP; + break; + } + if (byts[n + sp->ts_curi] != NUL) { + // Found a byte to insert. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_INS; + break; + } + sp->ts_curi++; + } + break; + + case STATE_INS: + // Insert one byte. Repeat this for each possible byte at this + // node. + n = sp->ts_arridx; + if (sp->ts_curi > byts[n]) { + // Done all bytes at this node, go to next state. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP; + break; + } + + // Do one more byte at this node, but: + // - Skip NUL bytes. + // - Skip the byte if it's equal to the byte in the word, + // accepting that byte is always better. + n += sp->ts_curi++; + c = byts[n]; + if (soundfold && sp->ts_twordlen == 0 && c == '*') { + // Inserting a vowel at the start of a word counts less, + // see soundalike_score(). + newscore = 2 * SCORE_INS / 3; + } else { + newscore = SCORE_INS; + } + if (c != fword[sp->ts_fidx] + && TRY_DEEPER(su, stack, depth, newscore)) { + go_deeper(stack, depth, newscore); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: insert %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + c); +#endif + depth++; + sp = &stack[depth]; + tword[sp->ts_twordlen++] = (char_u)c; + sp->ts_arridx = idxs[n]; + fl = MB_BYTE2LEN(c); + if (fl > 1) { + // There are following bytes for the same character. + // We must find all bytes before trying + // delete/insert/swap/etc. + sp->ts_tcharlen = (char_u)fl; + sp->ts_tcharidx = 1; + sp->ts_isdiff = DIFF_INSERT; + } + if (fl == 1) { + // If the previous character was the same, thus doubling a + // character, give a bonus to the score. Also for + // soundfold words (illogical but does give a better + // score). + if (sp->ts_twordlen >= 2 + && tword[sp->ts_twordlen - 2] == c) { + sp->ts_score -= SCORE_INS - SCORE_INSDUP; + } + } + } + break; + + case STATE_SWAP: + // Swap two bytes in the bad word: "12" -> "21". + // We change "fword" here, it's changed back afterwards at + // STATE_UNSWAP. + p = fword + sp->ts_fidx; + c = *p; + if (c == NUL) { + // End of word, can't swap or replace. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + + // Don't swap if the first character is not a word character. + // SWAP3 etc. also don't make sense then. + if (!soundfold && !spell_iswordp(p, curwin)) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + + n = utf_ptr2len((char *)p); + c = utf_ptr2char((char *)p); + if (p[n] == NUL) { + c2 = NUL; + } else if (!soundfold && !spell_iswordp(p + n, curwin)) { + c2 = c; // don't swap non-word char + } else { + c2 = utf_ptr2char((char *)p + n); + } + + // When the second character is NUL we can't swap. + if (c2 == NUL) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + + // When characters are identical, swap won't do anything. + // Also get here if the second char is not a word character. + if (c == c2) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_SWAP3; + break; + } + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) { + go_deeper(stack, depth, SCORE_SWAP); +#ifdef DEBUG_TRIEWALK + snprintf(changename[depth], sizeof(changename[0]), + "%.*s-%s: swap %c and %c", + sp->ts_twordlen, tword, fword + sp->ts_fidx, + c, c2); +#endif + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_UNSWAP; + depth++; + fl = utf_char2len(c2); + memmove(p, p + n, (size_t)fl); + utf_char2bytes(c, (char *)p + fl); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); + } else { + // If this swap doesn't work then SWAP3 won't either. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + case STATE_UNSWAP: + // Undo the STATE_SWAP swap: "21" -> "12". + p = fword + sp->ts_fidx; + n = utfc_ptr2len((char *)p); + c = utf_ptr2char((char *)p + n); + memmove(p + utfc_ptr2len((char *)p + n), p, (size_t)n); + utf_char2bytes(c, (char *)p); + + FALLTHROUGH; + + case STATE_SWAP3: + // Swap two bytes, skipping one: "123" -> "321". We change + // "fword" here, it's changed back afterwards at STATE_UNSWAP3. + p = fword + sp->ts_fidx; + n = utf_ptr2len((char *)p); + c = utf_ptr2char((char *)p); + fl = utf_ptr2len((char *)p + n); + c2 = utf_ptr2char((char *)p + n); + if (!soundfold && !spell_iswordp(p + n + fl, curwin)) { + c3 = c; // don't swap non-word char + } else { + c3 = utf_ptr2char((char *)p + n + fl); + } + + // When characters are identical: "121" then SWAP3 result is + // identical, ROT3L result is same as SWAP: "211", ROT3L result is + // same as SWAP on next char: "112". Thus skip all swapping. + // Also skip when c3 is NUL. + // Also get here when the third character is not a word character. + // Second character may any char: "a.b" -> "b.a" + if (c == c3 || c3 == NUL) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { + go_deeper(stack, depth, SCORE_SWAP3); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: swap3 %c and %c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + c, c3); +#endif + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_UNSWAP3; + depth++; + tl = utf_char2len(c3); + memmove(p, p + n + fl, (size_t)tl); + utf_char2bytes(c2, (char *)p + tl); + utf_char2bytes(c, (char *)p + fl + tl); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl); + } else { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + case STATE_UNSWAP3: + // Undo STATE_SWAP3: "321" -> "123" + p = fword + sp->ts_fidx; + n = utfc_ptr2len((char *)p); + c2 = utf_ptr2char((char *)p + n); + fl = utfc_ptr2len((char *)p + n); + c = utf_ptr2char((char *)p + n + fl); + tl = utfc_ptr2len((char *)p + n + fl); + memmove(p + fl + tl, p, (size_t)n); + utf_char2bytes(c, (char *)p); + utf_char2bytes(c2, (char *)p + tl); + p = p + tl; + + if (!soundfold && !spell_iswordp(p, curwin)) { + // Middle char is not a word char, skip the rotate. First and + // third char were already checked at swap and swap3. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + break; + } + + // Rotate three characters left: "123" -> "231". We change + // "fword" here, it's changed back afterwards at STATE_UNROT3L. + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { + go_deeper(stack, depth, SCORE_SWAP3); +#ifdef DEBUG_TRIEWALK + p = fword + sp->ts_fidx; + sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + p[0], p[1], p[2]); +#endif + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_UNROT3L; + depth++; + p = fword + sp->ts_fidx; + n = utf_ptr2len((char *)p); + c = utf_ptr2char((char *)p); + fl = utf_ptr2len((char *)p + n); + fl += utf_ptr2len((char *)p + n + fl); + memmove(p, p + n, (size_t)fl); + utf_char2bytes(c, (char *)p + fl); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); + } else { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + case STATE_UNROT3L: + // Undo ROT3L: "231" -> "123" + p = fword + sp->ts_fidx; + n = utfc_ptr2len((char *)p); + n += utfc_ptr2len((char *)p + n); + c = utf_ptr2char((char *)p + n); + tl = utfc_ptr2len((char *)p + n); + memmove(p + tl, p, (size_t)n); + utf_char2bytes(c, (char *)p); + + // Rotate three bytes right: "123" -> "312". We change "fword" + // here, it's changed back afterwards at STATE_UNROT3R. + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) { + go_deeper(stack, depth, SCORE_SWAP3); +#ifdef DEBUG_TRIEWALK + p = fword + sp->ts_fidx; + sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + p[0], p[1], p[2]); +#endif + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_UNROT3R; + depth++; + p = fword + sp->ts_fidx; + n = utf_ptr2len((char *)p); + n += utf_ptr2len((char *)p + n); + c = utf_ptr2char((char *)p + n); + tl = utf_ptr2len((char *)p + n); + memmove(p + tl, p, (size_t)n); + utf_char2bytes(c, (char *)p); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl); + } else { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_INI; + } + break; + + case STATE_UNROT3R: + // Undo ROT3R: "312" -> "123" + p = fword + sp->ts_fidx; + c = utf_ptr2char((char *)p); + tl = utfc_ptr2len((char *)p); + n = utfc_ptr2len((char *)p + tl); + n += utfc_ptr2len((char *)p + tl + n); + memmove(p, p + tl, (size_t)n); + utf_char2bytes(c, (char *)p + n); + + FALLTHROUGH; + + case STATE_REP_INI: + // Check if matching with REP items from the .aff file would work. + // Quickly skip if: + // - there are no REP items and we are not in the soundfold trie + // - the score is going to be too high anyway + // - already applied a REP item or swapped here + if ((lp->lp_replang == NULL && !soundfold) + || sp->ts_score + SCORE_REP >= su->su_maxscore + || sp->ts_fidx < sp->ts_fidxtry) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + + // Use the first byte to quickly find the first entry that may + // match. If the index is -1 there is none. + if (soundfold) { + sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]]; + } else { + sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; + } + + if (sp->ts_curi < 0) { + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + break; + } + + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP; + FALLTHROUGH; + + case STATE_REP: + // Try matching with REP items from the .aff file. For each match + // replace the characters and check if the resulting word is + // valid. + p = fword + sp->ts_fidx; + + if (soundfold) { + gap = &slang->sl_repsal; + } else { + gap = &lp->lp_replang->sl_rep; + } + while (sp->ts_curi < gap->ga_len) { + ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; + if (*ftp->ft_from != *p) { + // past possible matching entries + sp->ts_curi = (char_u)gap->ga_len; + break; + } + if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 + && TRY_DEEPER(su, stack, depth, SCORE_REP)) { + go_deeper(stack, depth, SCORE_REP); +#ifdef DEBUG_TRIEWALK + sprintf(changename[depth], "%.*s-%s: replace %s with %s", // NOLINT(runtime/printf) + sp->ts_twordlen, tword, fword + sp->ts_fidx, + ftp->ft_from, ftp->ft_to); +#endif + // Need to undo this afterwards. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP_UNDO; + + // Change the "from" to the "to" string. + depth++; + fl = (int)STRLEN(ftp->ft_from); + tl = (int)STRLEN(ftp->ft_to); + if (fl != tl) { + STRMOVE(p + tl, p + fl); + repextra += tl - fl; + } + memmove(p, ftp->ft_to, (size_t)tl); + stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl); + stack[depth].ts_tcharlen = 0; + break; + } + } + + if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) { + // No (more) matches. + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_FINAL; + } + + break; + + case STATE_REP_UNDO: + // Undo a REP replacement and continue with the next one. + if (soundfold) { + gap = &slang->sl_repsal; + } else { + gap = &lp->lp_replang->sl_rep; + } + ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1; + fl = (int)STRLEN(ftp->ft_from); + tl = (int)STRLEN(ftp->ft_to); + p = fword + sp->ts_fidx; + if (fl != tl) { + STRMOVE(p + fl, p + tl); + repextra -= tl - fl; + } + memmove(p, ftp->ft_from, (size_t)fl); + PROF_STORE(sp->ts_state) + sp->ts_state = STATE_REP; + break; + + default: + // Did all possible states at this level, go up one level. + depth--; + + if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) { + // Continue in or go back to the prefix tree. + byts = pbyts; + idxs = pidxs; + } + + // Don't check for CTRL-C too often, it takes time. + if (--breakcheckcount == 0) { + os_breakcheck(); + breakcheckcount = 1000; + if (spell_suggest_timeout > 0 && profile_passed_limit(time_limit)) { + got_int = true; + } + } + } + } +} + +/// Go one level deeper in the tree. +static void go_deeper(trystate_T *stack, int depth, int score_add) +{ + stack[depth + 1] = stack[depth]; + stack[depth + 1].ts_state = STATE_START; + stack[depth + 1].ts_score = stack[depth].ts_score + score_add; + stack[depth + 1].ts_curi = 1; // start just after length byte + stack[depth + 1].ts_flags = 0; +} + +/// "fword" is a good word with case folded. Find the matching keep-case +/// words and put it in "kword". +/// Theoretically there could be several keep-case words that result in the +/// same case-folded word, but we only find one... +static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) +{ + char_u uword[MAXWLEN]; // "fword" in upper-case + int depth; + idx_T tryidx; + + // The following arrays are used at each depth in the tree. + idx_T arridx[MAXWLEN]; + int round[MAXWLEN]; + int fwordidx[MAXWLEN]; + int uwordidx[MAXWLEN]; + int kwordlen[MAXWLEN]; + + int flen, ulen; + int l; + int len; + int c; + idx_T lo, hi, m; + char_u *p; + char_u *byts = slang->sl_kbyts; // array with bytes of the words + idx_T *idxs = slang->sl_kidxs; // array with indexes + + if (byts == NULL) { + // array is empty: "cannot happen" + *kword = NUL; + return; + } + + // Make an all-cap version of "fword". + allcap_copy(fword, uword); + + // Each character needs to be tried both case-folded and upper-case. + // All this gets very complicated if we keep in mind that changing case + // may change the byte length of a multi-byte character... + depth = 0; + arridx[0] = 0; + round[0] = 0; + fwordidx[0] = 0; + uwordidx[0] = 0; + kwordlen[0] = 0; + while (depth >= 0) { + if (fword[fwordidx[depth]] == NUL) { + // We are at the end of "fword". If the tree allows a word to end + // here we have found a match. + if (byts[arridx[depth] + 1] == 0) { + kword[kwordlen[depth]] = NUL; + return; + } + + // kword is getting too long, continue one level up + depth--; + } else if (++round[depth] > 2) { + // tried both fold-case and upper-case character, continue one + // level up + depth--; + } else { + // round[depth] == 1: Try using the folded-case character. + // round[depth] == 2: Try using the upper-case character. + flen = utf_ptr2len((char *)fword + fwordidx[depth]); + ulen = utf_ptr2len((char *)uword + uwordidx[depth]); + if (round[depth] == 1) { + p = fword + fwordidx[depth]; + l = flen; + } else { + p = uword + uwordidx[depth]; + l = ulen; + } + + for (tryidx = arridx[depth]; l > 0; l--) { + // Perform a binary search in the list of accepted bytes. + len = byts[tryidx++]; + c = *p++; + lo = tryidx; + hi = tryidx + len - 1; + while (lo < hi) { + m = (lo + hi) / 2; + if (byts[m] > c) { + hi = m - 1; + } else if (byts[m] < c) { + lo = m + 1; + } else { + lo = hi = m; + break; + } + } + + // Stop if there is no matching byte. + if (hi < lo || byts[lo] != c) { + break; + } + + // Continue at the child (if there is one). + tryidx = idxs[lo]; + } + + if (l == 0) { + // Found the matching char. Copy it to "kword" and go a + // level deeper. + if (round[depth] == 1) { + STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], // NOLINT(runtime/printf) + flen); + kwordlen[depth + 1] = kwordlen[depth] + flen; + } else { + STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], // NOLINT(runtime/printf) + ulen); + kwordlen[depth + 1] = kwordlen[depth] + ulen; + } + fwordidx[depth + 1] = fwordidx[depth] + flen; + uwordidx[depth + 1] = uwordidx[depth] + ulen; + + depth++; + arridx[depth] = tryidx; + round[depth] = 0; + } + } + } + + // Didn't find it: "cannot happen". + *kword = NUL; +} + +/// Compute the sound-a-like score for suggestions in su->su_ga and add them to +/// su->su_sga. +static void score_comp_sal(suginfo_T *su) +{ + langp_T *lp; + char_u badsound[MAXWLEN]; + int i; + suggest_T *stp; + suggest_T *sstp; + int score; + + ga_grow(&su->su_sga, su->su_ga.ga_len); + + // Use the sound-folding of the first language that supports it. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { + // soundfold the bad word + spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound); + + for (i = 0; i < su->su_ga.ga_len; i++) { + stp = &SUG(su->su_ga, i); + + // Case-fold the suggested word, sound-fold it and compute the + // sound-a-like score. + score = stp_sal_score(stp, su, lp->lp_slang, badsound); + if (score < SCORE_MAXMAX) { + // Add the suggestion. + sstp = &SUG(su->su_sga, su->su_sga.ga_len); + sstp->st_word = vim_strsave(stp->st_word); + sstp->st_wordlen = stp->st_wordlen; + sstp->st_score = score; + sstp->st_altscore = 0; + sstp->st_orglen = stp->st_orglen; + su->su_sga.ga_len++; + } + } + break; + } + } +} + +/// Combine the list of suggestions in su->su_ga and su->su_sga. +/// They are entwined. +static void score_combine(suginfo_T *su) +{ + garray_T ga; + garray_T *gap; + langp_T *lp; + suggest_T *stp; + char_u *p; + char_u badsound[MAXWLEN]; + int round; + slang_T *slang = NULL; + + // Add the alternate score to su_ga. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { + // soundfold the bad word + slang = lp->lp_slang; + spell_soundfold(slang, su->su_fbadword, true, badsound); + + for (int i = 0; i < su->su_ga.ga_len; i++) { + stp = &SUG(su->su_ga, i); + stp->st_altscore = stp_sal_score(stp, su, slang, badsound); + if (stp->st_altscore == SCORE_MAXMAX) { + stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4; + } else { + stp->st_score = (stp->st_score * 3 + stp->st_altscore) / 4; + } + stp->st_salscore = false; + } + break; + } + } + + if (slang == NULL) { // Using "double" without sound folding. + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, + su->su_maxcount); + return; + } + + // Add the alternate score to su_sga. + for (int i = 0; i < su->su_sga.ga_len; i++) { + stp = &SUG(su->su_sga, i); + stp->st_altscore = spell_edit_score(slang, + su->su_badword, stp->st_word); + if (stp->st_score == SCORE_MAXMAX) { + stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; + } else { + stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8; + } + stp->st_salscore = true; + } + + // Remove bad suggestions, sort the suggestions and truncate at "maxcount" + // for both lists. + check_suggestions(su, &su->su_ga); + (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); + check_suggestions(su, &su->su_sga); + (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount); + + ga_init(&ga, (int)sizeof(suginfo_T), 1); + ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len); + + stp = &SUG(ga, 0); + for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; i++) { + // round 1: get a suggestion from su_ga + // round 2: get a suggestion from su_sga + for (round = 1; round <= 2; round++) { + gap = round == 1 ? &su->su_ga : &su->su_sga; + if (i < gap->ga_len) { + // Don't add a word if it's already there. + p = SUG(*gap, i).st_word; + int j; + for (j = 0; j < ga.ga_len; j++) { + if (STRCMP(stp[j].st_word, p) == 0) { + break; + } + } + if (j == ga.ga_len) { + stp[ga.ga_len++] = SUG(*gap, i); + } else { + xfree(p); + } + } + } + } + + ga_clear(&su->su_ga); + ga_clear(&su->su_sga); + + // Truncate the list to the number of suggestions that will be displayed. + if (ga.ga_len > su->su_maxcount) { + for (int i = su->su_maxcount; i < ga.ga_len; i++) { + xfree(stp[i].st_word); + } + ga.ga_len = su->su_maxcount; + } + + su->su_ga = ga; +} + +/// For the goodword in "stp" compute the soundalike score compared to the +/// badword. +/// +/// @param badsound sound-folded badword +static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound) +{ + char_u *p; + char_u *pbad; + char_u *pgood; + char_u badsound2[MAXWLEN]; + char_u fword[MAXWLEN]; + char_u goodsound[MAXWLEN]; + char_u goodword[MAXWLEN]; + int lendiff; + + lendiff = su->su_badlen - stp->st_orglen; + if (lendiff >= 0) { + pbad = badsound; + } else { + // soundfold the bad word with more characters following + (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN); + + // When joining two words the sound often changes a lot. E.g., "t he" + // sounds like "t h" while "the" sounds like "@". Avoid that by + // removing the space. Don't do it when the good word also contains a + // space. + if (ascii_iswhite(su->su_badptr[su->su_badlen]) + && *skiptowhite(stp->st_word) == NUL) { + for (p = fword; *(p = skiptowhite(p)) != NUL;) { + STRMOVE(p, p + 1); + } + } + + spell_soundfold(slang, fword, true, badsound2); + pbad = badsound2; + } + + if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) { + // Add part of the bad word to the good word, so that we soundfold + // what replaces the bad word. + STRCPY(goodword, stp->st_word); + STRLCPY(goodword + stp->st_wordlen, + su->su_badptr + su->su_badlen - lendiff, lendiff + 1); + pgood = goodword; + } else { + pgood = stp->st_word; + } + + // Sound-fold the word and compute the score for the difference. + spell_soundfold(slang, pgood, false, goodsound); + + return soundalike_score(goodsound, pbad); +} + +/// structure used to store soundfolded words that add_sound_suggest() has +/// handled already. +typedef struct { + int16_t sft_score; ///< lowest score used + char_u sft_word[1]; ///< soundfolded word, actually longer +} sftword_T; + +static sftword_T dumsft; +#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) +#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) + +/// Prepare for calling suggest_try_soundalike(). +static void suggest_try_soundalike_prep(void) +{ + langp_T *lp; + slang_T *slang; + + // Do this for all languages that support sound folding and for which a + // .sug file has been loaded. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + slang = lp->lp_slang; + if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { + // prepare the hashtable used by add_sound_suggest() + hash_init(&slang->sl_sounddone); + } + } +} + +/// Find suggestions by comparing the word in a sound-a-like form. +/// Note: This doesn't support postponed prefixes. +static void suggest_try_soundalike(suginfo_T *su) +{ + char_u salword[MAXWLEN]; + langp_T *lp; + slang_T *slang; + + // Do this for all languages that support sound folding and for which a + // .sug file has been loaded. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + slang = lp->lp_slang; + if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { + // soundfold the bad word + spell_soundfold(slang, su->su_fbadword, true, salword); + + // try all kinds of inserts/deletes/swaps/etc. + // TODO(vim): also soundfold the next words, so that we can try joining + // and splitting +#ifdef SUGGEST_PROFILE + prof_init(); +#endif + suggest_trie_walk(su, lp, salword, true); +#ifdef SUGGEST_PROFILE + prof_report("soundalike"); +#endif + } + } +} + +/// Finish up after calling suggest_try_soundalike(). +static void suggest_try_soundalike_finish(void) +{ + langp_T *lp; + slang_T *slang; + int todo; + hashitem_T *hi; + + // Do this for all languages that support sound folding and for which a + // .sug file has been loaded. + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { + lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); + slang = lp->lp_slang; + if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { + // Free the info about handled words. + todo = (int)slang->sl_sounddone.ht_used; + for (hi = slang->sl_sounddone.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + xfree(HI2SFT(hi)); + todo--; + } + } + + // Clear the hashtable, it may also be used by another region. + hash_clear(&slang->sl_sounddone); + hash_init(&slang->sl_sounddone); + } + } +} + +/// A match with a soundfolded word is found. Add the good word(s) that +/// produce this soundfolded word. +/// +/// @param score soundfold score +static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp) +{ + slang_T *slang = lp->lp_slang; // language for sound folding + int sfwordnr; + char_u *nrline; + int orgnr; + char_u theword[MAXWLEN]; + int i; + int wlen; + char_u *byts; + idx_T *idxs; + int n; + int wordcount; + int wc; + int goodscore; + hash_T hash; + hashitem_T *hi; + sftword_T *sft; + int bc, gc; + int limit; + + // It's very well possible that the same soundfold word is found several + // times with different scores. Since the following is quite slow only do + // the words that have a better score than before. Use a hashtable to + // remember the words that have been done. + hash = hash_hash(goodword); + const size_t goodword_len = STRLEN(goodword); + hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len, + hash); + if (HASHITEM_EMPTY(hi)) { + sft = xmalloc(sizeof(sftword_T) + goodword_len); + sft->sft_score = (int16_t)score; + memcpy(sft->sft_word, goodword, goodword_len + 1); + hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash); + } else { + sft = HI2SFT(hi); + if (score >= sft->sft_score) { + return; + } + sft->sft_score = (int16_t)score; + } + + // Find the word nr in the soundfold tree. + sfwordnr = soundfold_find(slang, goodword); + if (sfwordnr < 0) { + internal_error("add_sound_suggest()"); + return; + } + + // Go over the list of good words that produce this soundfold word + nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); + orgnr = 0; + while (*nrline != NUL) { + // The wordnr was stored in a minimal nr of bytes as an offset to the + // previous wordnr. + orgnr += bytes2offset(&nrline); + + byts = slang->sl_fbyts; + idxs = slang->sl_fidxs; + + // Lookup the word "orgnr" one of the two tries. + n = 0; + wordcount = 0; + for (wlen = 0; wlen < MAXWLEN - 3; wlen++) { + i = 1; + if (wordcount == orgnr && byts[n + 1] == NUL) { + break; // found end of word + } + if (byts[n + 1] == NUL) { + wordcount++; + } + + // skip over the NUL bytes + for (; byts[n + i] == NUL; i++) { + if (i > byts[n]) { // safety check + STRCPY(theword + wlen, "BAD"); + wlen += 3; + goto badword; + } + } + + // One of the siblings must have the word. + for (; i < byts[n]; i++) { + wc = idxs[idxs[n + i]]; // nr of words under this byte + if (wordcount + wc > orgnr) { + break; + } + wordcount += wc; + } + + theword[wlen] = byts[n + i]; + n = idxs[n + i]; + } +badword: + theword[wlen] = NUL; + + // Go over the possible flags and regions. + for (; i <= byts[n] && byts[n + i] == NUL; i++) { + char_u cword[MAXWLEN]; + char_u *p; + int flags = (int)idxs[n + i]; + + // Skip words with the NOSUGGEST flag + if (flags & WF_NOSUGGEST) { + continue; + } + + if (flags & WF_KEEPCAP) { + // Must find the word in the keep-case tree. + find_keepcap_word(slang, theword, cword); + p = cword; + } else { + flags |= su->su_badflags; + if ((flags & WF_CAPMASK) != 0) { + // Need to fix case according to "flags". + make_case_word(theword, cword, flags); + p = cword; + } else { + p = theword; + } + } + + // Add the suggestion. + if (sps_flags & SPS_DOUBLE) { + // Add the suggestion if the score isn't too bad. + if (score <= su->su_maxscore) { + add_suggestion(su, &su->su_sga, p, su->su_badlen, + score, 0, false, slang, false); + } + } else { + // Add a penalty for words in another region. + if ((flags & WF_REGION) + && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) { + goodscore = SCORE_REGION; + } else { + goodscore = 0; + } + + // Add a small penalty for changing the first letter from + // lower to upper case. Helps for "tath" -> "Kath", which is + // less common than "tath" -> "path". Don't do it when the + // letter is the same, that has already been counted. + gc = utf_ptr2char((char *)p); + if (SPELL_ISUPPER(gc)) { + bc = utf_ptr2char((char *)su->su_badword); + if (!SPELL_ISUPPER(bc) + && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) { + goodscore += SCORE_ICASE / 2; + } + } + + // Compute the score for the good word. This only does letter + // insert/delete/swap/replace. REP items are not considered, + // which may make the score a bit higher. + // Use a limit for the score to make it work faster. Use + // MAXSCORE(), because RESCORE() will change the score. + // If the limit is very high then the iterative method is + // inefficient, using an array is quicker. + limit = MAXSCORE(su->su_sfmaxscore - goodscore, score); + if (limit > SCORE_LIMITMAX) { + goodscore += spell_edit_score(slang, su->su_badword, p); + } else { + goodscore += spell_edit_score_limit(slang, su->su_badword, + p, limit); + } + + // When going over the limit don't bother to do the rest. + if (goodscore < SCORE_MAXMAX) { + // Give a bonus to words seen before. + goodscore = score_wordcount_adj(slang, goodscore, p, false); + + // Add the suggestion if the score isn't too bad. + goodscore = RESCORE(goodscore, score); + if (goodscore <= su->su_sfmaxscore) { + add_suggestion(su, &su->su_ga, p, su->su_badlen, + goodscore, score, true, slang, true); + } + } + } + } + } +} + +/// Find word "word" in fold-case tree for "slang" and return the word number. +static int soundfold_find(slang_T *slang, char_u *word) +{ + idx_T arridx = 0; + int len; + int wlen = 0; + int c; + char_u *ptr = word; + char_u *byts; + idx_T *idxs; + int wordnr = 0; + + byts = slang->sl_sbyts; + idxs = slang->sl_sidxs; + + for (;;) { + // First byte is the number of possible bytes. + len = byts[arridx++]; + + // If the first possible byte is a zero the word could end here. + // If the word ends we found the word. If not skip the NUL bytes. + c = ptr[wlen]; + if (byts[arridx] == NUL) { + if (c == NUL) { + break; + } + + // Skip over the zeros, there can be several. + while (len > 0 && byts[arridx] == NUL) { + arridx++; + len--; + } + if (len == 0) { + return -1; // no children, word should have ended here + } + wordnr++; + } + + // If the word ends we didn't find it. + if (c == NUL) { + return -1; + } + + // Perform a binary search in the list of accepted bytes. + if (c == TAB) { // <Tab> is handled like <Space> + c = ' '; + } + while (byts[arridx] < c) { + // The word count is in the first idxs[] entry of the child. + wordnr += idxs[idxs[arridx]]; + arridx++; + if (--len == 0) { // end of the bytes, didn't find it + return -1; + } + } + if (byts[arridx] != c) { // didn't find the byte + return -1; + } + + // Continue at the child (if there is one). + arridx = idxs[arridx]; + wlen++; + + // One space in the good word may stand for several spaces in the + // checked word. + if (c == ' ') { + while (ptr[wlen] == ' ' || ptr[wlen] == TAB) { + wlen++; + } + } + } + + return wordnr; +} + +/// Returns true if "c1" and "c2" are similar characters according to the MAP +/// lines in the .aff file. +static bool similar_chars(slang_T *slang, int c1, int c2) +{ + int m1, m2; + char buf[MB_MAXBYTES + 1]; + hashitem_T *hi; + + if (c1 >= 256) { + buf[utf_char2bytes(c1, (char *)buf)] = 0; + hi = hash_find(&slang->sl_map_hash, buf); + if (HASHITEM_EMPTY(hi)) { + m1 = 0; + } else { + m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + } + } else { + m1 = slang->sl_map_array[c1]; + } + if (m1 == 0) { + return false; + } + + if (c2 >= 256) { + buf[utf_char2bytes(c2, (char *)buf)] = 0; + hi = hash_find(&slang->sl_map_hash, buf); + if (HASHITEM_EMPTY(hi)) { + m2 = 0; + } else { + m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + } + } else { + m2 = slang->sl_map_array[c2]; + } + + return m1 == m2; +} + +/// Adds a suggestion to the list of suggestions. +/// For a suggestion that is already in the list the lowest score is remembered. +/// +/// @param gap either su_ga or su_sga +/// @param badlenarg len of bad word replaced with "goodword" +/// @param had_bonus value for st_had_bonus +/// @param slang language for sound folding +/// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score. +static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg, + int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf) +{ + int goodlen; // len of goodword changed + int badlen; // len of bad word changed + suggest_T *stp; + suggest_T new_sug; + + // Minimize "badlen" for consistency. Avoids that changing "the the" to + // "thee the" is added next to changing the first "the" the "thee". + const char_u *pgood = goodword + STRLEN(goodword); + char_u *pbad = su->su_badptr + badlenarg; + for (;;) { + goodlen = (int)(pgood - goodword); + badlen = (int)(pbad - su->su_badptr); + if (goodlen <= 0 || badlen <= 0) { + break; + } + MB_PTR_BACK(goodword, pgood); + MB_PTR_BACK(su->su_badptr, pbad); + if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) { + break; + } + } + + if (badlen == 0 && goodlen == 0) { + // goodword doesn't change anything; may happen for "the the" changing + // the first "the" to itself. + return; + } + + int i; + if (GA_EMPTY(gap)) { + i = -1; + } else { + // Check if the word is already there. Also check the length that is + // being replaced "thes," -> "these" is a different suggestion from + // "thes" -> "these". + stp = &SUG(*gap, 0); + for (i = gap->ga_len; --i >= 0; ++stp) { + if (stp->st_wordlen == goodlen + && stp->st_orglen == badlen + && STRNCMP(stp->st_word, goodword, goodlen) == 0) { + // Found it. Remember the word with the lowest score. + if (stp->st_slang == NULL) { + stp->st_slang = slang; + } + + new_sug.st_score = score; + new_sug.st_altscore = altscore; + new_sug.st_had_bonus = had_bonus; + + if (stp->st_had_bonus != had_bonus) { + // Only one of the two had the soundalike score computed. + // Need to do that for the other one now, otherwise the + // scores can't be compared. This happens because + // suggest_try_change() doesn't compute the soundalike + // word to keep it fast, while some special methods set + // the soundalike score to zero. + if (had_bonus) { + rescore_one(su, stp); + } else { + new_sug.st_word = stp->st_word; + new_sug.st_wordlen = stp->st_wordlen; + new_sug.st_slang = stp->st_slang; + new_sug.st_orglen = badlen; + rescore_one(su, &new_sug); + } + } + + if (stp->st_score > new_sug.st_score) { + stp->st_score = new_sug.st_score; + stp->st_altscore = new_sug.st_altscore; + stp->st_had_bonus = new_sug.st_had_bonus; + } + break; + } + } + } + + if (i < 0) { + // Add a suggestion. + stp = GA_APPEND_VIA_PTR(suggest_T, gap); + stp->st_word = vim_strnsave(goodword, (size_t)goodlen); + stp->st_wordlen = goodlen; + stp->st_score = score; + stp->st_altscore = altscore; + stp->st_had_bonus = had_bonus; + stp->st_orglen = badlen; + stp->st_slang = slang; + + // If we have too many suggestions now, sort the list and keep + // the best suggestions. + if (gap->ga_len > SUG_MAX_COUNT(su)) { + if (maxsf) { + su->su_sfmaxscore = cleanup_suggestions(gap, + su->su_sfmaxscore, SUG_CLEAN_COUNT(su)); + } else { + su->su_maxscore = cleanup_suggestions(gap, + su->su_maxscore, SUG_CLEAN_COUNT(su)); + } + } + } +} + +/// Suggestions may in fact be flagged as errors. Esp. for banned words and +/// for split words, such as "the the". Remove these from the list here. +/// +/// @param gap either su_ga or su_sga +static void check_suggestions(suginfo_T *su, garray_T *gap) +{ + suggest_T *stp; + char_u longword[MAXWLEN + 1]; + int len; + hlf_T attr; + + if (gap->ga_len == 0) { + return; + } + stp = &SUG(*gap, 0); + for (int i = gap->ga_len - 1; i >= 0; i--) { + // Need to append what follows to check for "the the". + STRLCPY(longword, stp[i].st_word, MAXWLEN + 1); + len = stp[i].st_wordlen; + STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen, + MAXWLEN - len + 1); + attr = HLF_COUNT; + (void)spell_check(curwin, longword, &attr, NULL, false); + if (attr != HLF_COUNT) { + // Remove this entry. + xfree(stp[i].st_word); + gap->ga_len--; + if (i < gap->ga_len) { + memmove(stp + i, stp + i + 1, sizeof(suggest_T) * (size_t)(gap->ga_len - i)); + } + } + } +} + +/// Add a word to be banned. +static void add_banned(suginfo_T *su, char_u *word) +{ + char_u *s; + hash_T hash; + hashitem_T *hi; + + hash = hash_hash(word); + const size_t word_len = STRLEN(word); + hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash); + if (HASHITEM_EMPTY(hi)) { + s = xmemdupz(word, word_len); + hash_add_item(&su->su_banned, hi, s, hash); + } +} + +/// Recompute the score for all suggestions if sound-folding is possible. This +/// is slow, thus only done for the final results. +static void rescore_suggestions(suginfo_T *su) +{ + if (su->su_sallang != NULL) { + for (int i = 0; i < su->su_ga.ga_len; i++) { + rescore_one(su, &SUG(su->su_ga, i)); + } + } +} + +/// Recompute the score for one suggestion if sound-folding is possible. +static void rescore_one(suginfo_T *su, suggest_T *stp) +{ + slang_T *slang = stp->st_slang; + char_u sal_badword[MAXWLEN]; + char_u *p; + + // Only rescore suggestions that have no sal score yet and do have a + // language. + if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) { + if (slang == su->su_sallang) { + p = su->su_sal_badword; + } else { + spell_soundfold(slang, su->su_fbadword, true, sal_badword); + p = sal_badword; + } + + stp->st_altscore = stp_sal_score(stp, su, slang, p); + if (stp->st_altscore == SCORE_MAXMAX) { + stp->st_altscore = SCORE_BIG; + } + stp->st_score = RESCORE(stp->st_score, stp->st_altscore); + stp->st_had_bonus = true; + } +} + +/// Function given to qsort() to sort the suggestions on st_score. +/// First on "st_score", then "st_altscore" then alphabetically. +static int sug_compare(const void *s1, const void *s2) +{ + suggest_T *p1 = (suggest_T *)s1; + suggest_T *p2 = (suggest_T *)s2; + int n = p1->st_score - p2->st_score; + + if (n == 0) { + n = p1->st_altscore - p2->st_altscore; + if (n == 0) { + n = STRICMP(p1->st_word, p2->st_word); + } + } + return n; +} + +/// Cleanup the suggestions: +/// - Sort on score. +/// - Remove words that won't be displayed. +/// +/// @param keep nr of suggestions to keep +/// +/// @return the maximum score in the list or "maxscore" unmodified. +static int cleanup_suggestions(garray_T *gap, int maxscore, int keep) + FUNC_ATTR_NONNULL_ALL +{ + if (gap->ga_len > 0) { + // Sort the list. + qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); + + // Truncate the list to the number of suggestions that will be displayed. + if (gap->ga_len > keep) { + suggest_T *const stp = &SUG(*gap, 0); + + for (int i = keep; i < gap->ga_len; i++) { + xfree(stp[i].st_word); + } + gap->ga_len = keep; + if (keep >= 1) { + return stp[keep - 1].st_score; + } + } + } + return maxscore; +} + +/// Compute a score for two sound-a-like words. +/// This permits up to two inserts/deletes/swaps/etc. to keep things fast. +/// Instead of a generic loop we write out the code. That keeps it fast by +/// avoiding checks that will not be possible. +/// +/// @param goodstart sound-folded good word +/// @param badstart sound-folded bad word +static int soundalike_score(char_u *goodstart, char_u *badstart) +{ + char_u *goodsound = goodstart; + char_u *badsound = badstart; + int goodlen; + int badlen; + int n; + char_u *pl, *ps; + char_u *pl2, *ps2; + int score = 0; + + // Adding/inserting "*" at the start (word starts with vowel) shouldn't be + // counted so much, vowels in the middle of the word aren't counted at all. + if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) { + if ((badsound[0] == NUL && goodsound[1] == NUL) + || (goodsound[0] == NUL && badsound[1] == NUL)) { + // changing word with vowel to word without a sound + return SCORE_DEL; + } + if (badsound[0] == NUL || goodsound[0] == NUL) { + // more than two changes + return SCORE_MAXMAX; + } + + if (badsound[1] == goodsound[1] + || (badsound[1] != NUL + && goodsound[1] != NUL + && badsound[2] == goodsound[2])) { + // handle like a substitute + } else { + score = 2 * SCORE_DEL / 3; + if (*badsound == '*') { + badsound++; + } else { + goodsound++; + } + } + } + + goodlen = (int)STRLEN(goodsound); + badlen = (int)STRLEN(badsound); + + // Return quickly if the lengths are too different to be fixed by two + // changes. + n = goodlen - badlen; + if (n < -2 || n > 2) { + return SCORE_MAXMAX; + } + + if (n > 0) { + pl = goodsound; // goodsound is longest + ps = badsound; + } else { + pl = badsound; // badsound is longest + ps = goodsound; + } + + // Skip over the identical part. + while (*pl == *ps && *pl != NUL) { + pl++; + ps++; + } + + switch (n) { + case -2: + case 2: + // Must delete two characters from "pl". + pl++; // first delete + while (*pl == *ps) { + pl++; + ps++; + } + // strings must be equal after second delete + if (STRCMP(pl + 1, ps) == 0) { + return score + SCORE_DEL * 2; + } + + // Failed to compare. + break; + + case -1: + case 1: + // Minimal one delete from "pl" required. + + // 1: delete + pl2 = pl + 1; + ps2 = ps; + while (*pl2 == *ps2) { + if (*pl2 == NUL) { // reached the end + return score + SCORE_DEL; + } + pl2++; + ps2++; + } + + // 2: delete then swap, then rest must be equal + if (pl2[0] == ps2[1] && pl2[1] == ps2[0] + && STRCMP(pl2 + 2, ps2 + 2) == 0) { + return score + SCORE_DEL + SCORE_SWAP; + } + + // 3: delete then substitute, then the rest must be equal + if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + return score + SCORE_DEL + SCORE_SUBST; + } + + // 4: first swap then delete + if (pl[0] == ps[1] && pl[1] == ps[0]) { + pl2 = pl + 2; // swap, skip two chars + ps2 = ps + 2; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + // delete a char and then strings must be equal + if (STRCMP(pl2 + 1, ps2) == 0) { + return score + SCORE_SWAP + SCORE_DEL; + } + } + + // 5: first substitute then delete + pl2 = pl + 1; // substitute, skip one char + ps2 = ps + 1; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + // delete a char and then strings must be equal + if (STRCMP(pl2 + 1, ps2) == 0) { + return score + SCORE_SUBST + SCORE_DEL; + } + + // Failed to compare. + break; + + case 0: + // Lengths are equal, thus changes must result in same length: An + // insert is only possible in combination with a delete. + // 1: check if for identical strings + if (*pl == NUL) { + return score; + } + + // 2: swap + if (pl[0] == ps[1] && pl[1] == ps[0]) { + pl2 = pl + 2; // swap, skip two chars + ps2 = ps + 2; + while (*pl2 == *ps2) { + if (*pl2 == NUL) { // reached the end + return score + SCORE_SWAP; + } + pl2++; + ps2++; + } + // 3: swap and swap again + if (pl2[0] == ps2[1] && pl2[1] == ps2[0] + && STRCMP(pl2 + 2, ps2 + 2) == 0) { + return score + SCORE_SWAP + SCORE_SWAP; + } + + // 4: swap and substitute + if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + return score + SCORE_SWAP + SCORE_SUBST; + } + } + + // 5: substitute + pl2 = pl + 1; + ps2 = ps + 1; + while (*pl2 == *ps2) { + if (*pl2 == NUL) { // reached the end + return score + SCORE_SUBST; + } + pl2++; + ps2++; + } + + // 6: substitute and swap + if (pl2[0] == ps2[1] && pl2[1] == ps2[0] + && STRCMP(pl2 + 2, ps2 + 2) == 0) { + return score + SCORE_SUBST + SCORE_SWAP; + } + + // 7: substitute and substitute + if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + return score + SCORE_SUBST + SCORE_SUBST; + } + + // 8: insert then delete + pl2 = pl; + ps2 = ps + 1; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + if (STRCMP(pl2 + 1, ps2) == 0) { + return score + SCORE_INS + SCORE_DEL; + } + + // 9: delete then insert + pl2 = pl + 1; + ps2 = ps; + while (*pl2 == *ps2) { + pl2++; + ps2++; + } + if (STRCMP(pl2, ps2 + 1) == 0) { + return score + SCORE_INS + SCORE_DEL; + } + + // Failed to compare. + break; + } + + return SCORE_MAXMAX; +} + +/// Compute the "edit distance" to turn "badword" into "goodword". The less +/// deletes/inserts/substitutes/swaps are required the lower the score. +/// +/// The algorithm is described by Du and Chang, 1992. +/// The implementation of the algorithm comes from Aspell editdist.cpp, +/// edit_distance(). It has been converted from C++ to C and modified to +/// support multi-byte characters. +static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) +{ + int *cnt; + int j, i; + int t; + int bc, gc; + int pbc, pgc; + int wbadword[MAXWLEN]; + int wgoodword[MAXWLEN]; + + // Lengths with NUL. + int badlen; + int goodlen; + { + // Get the characters from the multi-byte strings and put them in an + // int array for easy access. + badlen = 0; + for (const char_u *p = badword; *p != NUL;) { + wbadword[badlen++] = mb_cptr2char_adv(&p); + } + wbadword[badlen++] = 0; + goodlen = 0; + for (const char_u *p = goodword; *p != NUL;) { + wgoodword[goodlen++] = mb_cptr2char_adv(&p); + } + wgoodword[goodlen++] = 0; + } + + // We use "cnt" as an array: CNT(badword_idx, goodword_idx). +#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)] + cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1)); + + CNT(0, 0) = 0; + for (j = 1; j <= goodlen; j++) { + CNT(0, j) = CNT(0, j - 1) + SCORE_INS; + } + + for (i = 1; i <= badlen; i++) { + CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL; + for (j = 1; j <= goodlen; j++) { + bc = wbadword[i - 1]; + gc = wgoodword[j - 1]; + if (bc == gc) { + CNT(i, j) = CNT(i - 1, j - 1); + } else { + // Use a better score when there is only a case difference. + if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { + CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1); + } else { + // For a similar character use SCORE_SIMILAR. + if (slang != NULL + && slang->sl_has_map + && similar_chars(slang, gc, bc)) { + CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1); + } else { + CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1); + } + } + + if (i > 1 && j > 1) { + pbc = wbadword[i - 2]; + pgc = wgoodword[j - 2]; + if (bc == pgc && pbc == gc) { + t = SCORE_SWAP + CNT(i - 2, j - 2); + if (t < CNT(i, j)) { + CNT(i, j) = t; + } + } + } + t = SCORE_DEL + CNT(i - 1, j); + if (t < CNT(i, j)) { + CNT(i, j) = t; + } + t = SCORE_INS + CNT(i, j - 1); + if (t < CNT(i, j)) { + CNT(i, j) = t; + } + } + } + } + + i = CNT(badlen - 1, goodlen - 1); + xfree(cnt); + return i; +} + +typedef struct { + int badi; + int goodi; + int score; +} limitscore_T; + +/// Like spell_edit_score(), but with a limit on the score to make it faster. +/// May return SCORE_MAXMAX when the score is higher than "limit". +/// +/// This uses a stack for the edits still to be tried. +/// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support +/// for multi-byte characters. +static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit) +{ + return spell_edit_score_limit_w(slang, badword, goodword, limit); +} + +/// Multi-byte version of spell_edit_score_limit(). +/// Keep it in sync with the above! +static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit) +{ + limitscore_T stack[10]; // allow for over 3 * 2 edits + int stackidx; + int bi, gi; + int bi2, gi2; + int bc, gc; + int score; + int score_off; + int minscore; + int round; + int wbadword[MAXWLEN]; + int wgoodword[MAXWLEN]; + + // Get the characters from the multi-byte strings and put them in an + // int array for easy access. + bi = 0; + for (const char_u *p = badword; *p != NUL;) { + wbadword[bi++] = mb_cptr2char_adv(&p); + } + wbadword[bi++] = 0; + gi = 0; + for (const char_u *p = goodword; *p != NUL;) { + wgoodword[gi++] = mb_cptr2char_adv(&p); + } + wgoodword[gi++] = 0; + + // The idea is to go from start to end over the words. So long as + // characters are equal just continue, this always gives the lowest score. + // When there is a difference try several alternatives. Each alternative + // increases "score" for the edit distance. Some of the alternatives are + // pushed unto a stack and tried later, some are tried right away. At the + // end of the word the score for one alternative is known. The lowest + // possible score is stored in "minscore". + stackidx = 0; + bi = 0; + gi = 0; + score = 0; + minscore = limit + 1; + + for (;;) { + // Skip over an equal part, score remains the same. + for (;;) { + bc = wbadword[bi]; + gc = wgoodword[gi]; + + if (bc != gc) { // stop at a char that's different + break; + } + if (bc == NUL) { // both words end + if (score < minscore) { + minscore = score; + } + goto pop; // do next alternative + } + bi++; + gi++; + } + + if (gc == NUL) { // goodword ends, delete badword chars + do { + if ((score += SCORE_DEL) >= minscore) { + goto pop; // do next alternative + } + } while (wbadword[++bi] != NUL); + minscore = score; + } else if (bc == NUL) { // badword ends, insert badword chars + do { + if ((score += SCORE_INS) >= minscore) { + goto pop; // do next alternative + } + } while (wgoodword[++gi] != NUL); + minscore = score; + } else { // both words continue + // If not close to the limit, perform a change. Only try changes + // that may lead to a lower score than "minscore". + // round 0: try deleting a char from badword + // round 1: try inserting a char in badword + for (round = 0; round <= 1; round++) { + score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS); + if (score_off < minscore) { + if (score_off + SCORE_EDIT_MIN >= minscore) { + // Near the limit, rest of the words must match. We + // can check that right now, no need to push an item + // onto the stack. + bi2 = bi + 1 - round; + gi2 = gi + round; + while (wgoodword[gi2] == wbadword[bi2]) { + if (wgoodword[gi2] == NUL) { + minscore = score_off; + break; + } + bi2++; + gi2++; + } + } else { + // try deleting a character from badword later + stack[stackidx].badi = bi + 1 - round; + stack[stackidx].goodi = gi + round; + stack[stackidx].score = score_off; + stackidx++; + } + } + } + + if (score + SCORE_SWAP < minscore) { + // If swapping two characters makes a match then the + // substitution is more expensive, thus there is no need to + // try both. + if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) { + // Swap two characters, that is: skip them. + gi += 2; + bi += 2; + score += SCORE_SWAP; + continue; + } + } + + // Substitute one character for another which is the same + // thing as deleting a character from both goodword and badword. + // Use a better score when there is only a case difference. + if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) { + score += SCORE_ICASE; + } else { + // For a similar character use SCORE_SIMILAR. + if (slang != NULL + && slang->sl_has_map + && similar_chars(slang, gc, bc)) { + score += SCORE_SIMILAR; + } else { + score += SCORE_SUBST; + } + } + + if (score < minscore) { + // Do the substitution. + gi++; + bi++; + continue; + } + } +pop: + // Get here to try the next alternative, pop it from the stack. + if (stackidx == 0) { // stack is empty, finished + break; + } + + // pop an item from the stack + stackidx--; + gi = stack[stackidx].goodi; + bi = stack[stackidx].badi; + score = stack[stackidx].score; + } + + // When the score goes over "limit" it may actually be much higher. + // Return a very large number to avoid going below the limit when giving a + // bonus. + if (minscore > limit) { + return SCORE_MAXMAX; + } + return minscore; +} diff --git a/src/nvim/spellsuggest.h b/src/nvim/spellsuggest.h new file mode 100644 index 0000000000..8813a5b3f1 --- /dev/null +++ b/src/nvim/spellsuggest.h @@ -0,0 +1,9 @@ +#ifndef NVIM_SPELLSUGGEST_H +#define NVIM_SPELLSUGGEST_H + +#include "nvim/garray.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "spellsuggest.h.generated.h" +#endif +#endif // NVIM_SPELLSUGGEST_H diff --git a/src/nvim/state.c b/src/nvim/state.c index d6cca71ad8..61740800a1 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -5,6 +5,7 @@ #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" @@ -15,7 +16,6 @@ #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/os/input.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/ui.h" #include "nvim/vim.h" diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 22effaade0..78312c738c 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -191,7 +191,7 @@ char *vim_strnsave_unquoted(const char *const string, const size_t length) char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_newline) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char_u *d; + char *d; char_u *escaped_string; size_t l; int csh_like; @@ -238,7 +238,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n // Allocate memory for the result and fill it. escaped_string = xmalloc(length); - d = escaped_string; + d = (char *)escaped_string; // add opening quote #ifdef WIN32 @@ -248,7 +248,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n #endif *d++ = '\''; - for (const char_u *p = string; *p != NUL;) { + for (const char *p = (char *)string; *p != NUL;) { #ifdef WIN32 if (!p_ssl) { if (*p == '"') { @@ -264,7 +264,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n *d++ = '\\'; *d++ = '\''; *d++ = '\''; - ++p; + p++; continue; } if ((*p == '\n' && (csh_like || do_newline)) @@ -276,7 +276,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n *d++ = *p++; continue; } - if (do_special && find_cmdline_var(p, &l) >= 0) { + if (do_special && find_cmdline_var((char_u *)p, &l) >= 0) { *d++ = '\\'; // insert backslash while (--l != SIZE_MAX) { // copy the var *d++ = *p++; @@ -431,8 +431,8 @@ int vim_stricmp(const char *s1, const char *s2) if (*s1 == NUL) { break; // strings match until NUL } - ++s1; - ++s2; + s1++; + s2++; } return 0; // strings match } @@ -457,9 +457,9 @@ int vim_strnicmp(const char *s1, const char *s2, size_t len) if (*s1 == NUL) { break; // strings match until NUL } - ++s1; - ++s2; - --len; + s1++; + s2++; + len--; } return 0; // strings match } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 4ec4a57d68..47b5647a08 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -14,12 +14,13 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/vars.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/fold.h" @@ -41,8 +42,8 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -378,7 +379,7 @@ void syntax_start(win_T *wp, linenr_T lnum) && current_lnum < syn_buf->b_ml.ml_line_count) { (void)syn_finish_line(false); if (!current_state_stored) { - ++current_lnum; + current_lnum++; (void)store_current_state(); } @@ -723,7 +724,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) } else if (found_m_endpos.col > current_col) { current_col = found_m_endpos.col; } else { - ++current_col; + current_col++; } // syn_current_attr() will have skipped the check for @@ -731,7 +732,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) // careful not to go past the NUL. prev_current_col = current_col; if (syn_getcurline()[current_col] != NUL) { - ++current_col; + current_col++; } check_state_ends(); current_col = prev_current_col; @@ -1029,7 +1030,7 @@ static void syn_stack_alloc(void) // Move the states from the old array to the new one. for (from = syn_block->b_sst_first; from != NULL; from = from->sst_next) { - ++to; + to++; *to = *from; to->sst_next = to + 1; } @@ -1500,7 +1501,7 @@ bool syntax_check_changed(linenr_T lnum) /* * Store the current state in b_sst_array[] for later use. */ - ++current_lnum; + current_lnum++; (void)store_current_state(); } } @@ -2095,9 +2096,9 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con check_state_ends(); if (!GA_EMPTY(¤t_state) && syn_getcurline()[current_col] != NUL) { - ++current_col; + current_col++; check_state_ends(); - --current_col; + current_col--; } } } else if (can_spell != NULL) { @@ -2506,7 +2507,7 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) static void push_current_state(int idx) { stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, ¤t_state); - memset(p, 0, sizeof(*p)); + CLEAR_POINTER(p); p->si_idx = idx; } @@ -2582,7 +2583,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ if (spp->sp_type != SPTYPE_START) { break; } - ++idx; + idx++; } /* @@ -2590,7 +2591,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ */ if (spp->sp_type == SPTYPE_SKIP) { spp_skip = spp; - ++idx; + idx++; } else { spp_skip = NULL; } @@ -3653,7 +3654,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END) { put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr); } - --idx; + idx--; msg_putchar(' '); } syn_list_flags(namelist1, spp->sp_flags, attr); @@ -3927,7 +3928,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht) if (HASHITEM_EMPTY(hi)) { continue; } - --todo; + todo--; kp_prev = NULL; for (kp = HI2KE(hi); kp != NULL;) { if (kp->k_syn.id == id) { @@ -3967,7 +3968,7 @@ static void clear_keywtab(hashtab_T *ht) todo = (int)ht->ht_used; for (hi = ht->ht_array; todo > 0; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; for (kp = HI2KE(hi); kp != NULL; kp = kp_next) { kp_next = kp->ke_next; xfree(kp->next_list); @@ -4257,7 +4258,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing) } if (arg[0] == '@') { - ++arg; + arg++; rest = get_group_name(arg, &group_name_end); if (rest == NULL) { emsg(_("E397: Filename required")); @@ -4453,7 +4454,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) // get the pattern. init_syn_patterns(); - memset(&item, 0, sizeof(item)); + CLEAR_FIELD(item); rest = get_syn_pattern(rest, &item); if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; @@ -4583,7 +4584,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) // must be a pattern or matchgroup then key_end = rest; while (*key_end && !ascii_iswhite(*key_end) && *key_end != '=') { - ++key_end; + key_end++; } xfree(key); key = vim_strnsave_up(rest, (size_t)(key_end - rest)); @@ -4708,8 +4709,8 @@ static void syn_cmd_region(exarg_T *eap, int syncing) SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list; } - ++curwin->w_s->b_syn_patterns.ga_len; - ++idx; + curwin->w_s->b_syn_patterns.ga_len++; + idx++; if (syn_opt_arg.flags & HL_FOLD) { ++curwin->w_s->b_syn_folditems; } @@ -4945,7 +4946,7 @@ static int syn_add_cluster(char_u *name) syn_cluster_T *scp = GA_APPEND_VIA_PTR(syn_cluster_T, &curwin->w_s->b_syn_clusters); - memset(scp, 0, sizeof(*scp)); + CLEAR_POINTER(scp); scp->scl_name = name; scp->scl_name_u = vim_strsave_up(name); scp->scl_list = NULL; @@ -5048,7 +5049,7 @@ static void init_syn_patterns(void) */ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) { - char_u *end; + char *end; int *p; int idx; char *cpo_save; @@ -5058,13 +5059,13 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) return NULL; } - end = skip_regexp(arg + 1, *arg, TRUE, NULL); - if (*end != *arg) { // end delimiter not found + end = (char *)skip_regexp(arg + 1, *arg, true, NULL); + if (*end != (char)(*arg)) { // end delimiter not found semsg(_("E401: Pattern delimiter not found: %s"), arg); return NULL; } // store the pattern and compiled regexp program - ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - arg) - 1); + ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - (char *)arg) - 1); // Make 'cpoptions' empty, to avoid the 'l' flag cpo_save = p_cpo; @@ -5081,7 +5082,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) /* * Check for a match, highlight or region offset. */ - ++end; + end++; do { for (idx = SPO_COUNT; --idx >= 0;) { if (STRNCMP(end, spo_name_tab[idx], 3) == 0) { @@ -5106,7 +5107,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) ci->sp_off_flags |= (int16_t)(1 << idx); if (idx == SPO_LC_OFF) { // lc=99 end += 3; - *p = getdigits_int((char **)&end, true, 0); + *p = getdigits_int(&end, true, 0); // "lc=" offset automatically sets "ms=" offset if (!(ci->sp_off_flags & (1 << SPO_MS_OFF))) { @@ -5117,16 +5118,16 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) end += 4; if (*end == '+') { end++; - *p = getdigits_int((char **)&end, true, 0); // positive offset + *p = getdigits_int(&end, true, 0); // positive offset } else if (*end == '-') { end++; - *p = -getdigits_int((char **)&end, true, 0); // negative offset + *p = -getdigits_int(&end, true, 0); // negative offset } } if (*end != ',') { break; } - ++end; + end++; } } } while (idx >= 0); @@ -5135,7 +5136,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) semsg(_("E402: Garbage after pattern: %s"), arg); return NULL; } - return (char_u *)skipwhite((char *)end); + return (char_u *)skipwhite(end); } /* @@ -5144,7 +5145,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) static void syn_cmd_sync(exarg_T *eap, int syncing) { char_u *arg_start = (char_u *)eap->arg; - char_u *arg_end; + char *arg_end; char_u *key = NULL; char_u *next_arg; int illegal = false; @@ -5157,21 +5158,21 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) } while (!ends_excmd(*arg_start)) { - arg_end = skiptowhite(arg_start); - next_arg = (char_u *)skipwhite((char *)arg_end); + arg_end = (char *)skiptowhite(arg_start); + next_arg = (char_u *)skipwhite(arg_end); xfree(key); - key = vim_strnsave_up(arg_start, (size_t)(arg_end - arg_start)); + key = vim_strnsave_up(arg_start, (size_t)(arg_end - (char *)arg_start)); if (STRCMP(key, "CCOMMENT") == 0) { if (!eap->skip) { curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT; } if (!ends_excmd(*next_arg)) { - arg_end = skiptowhite(next_arg); + arg_end = (char *)skiptowhite(next_arg); if (!eap->skip) { curwin->w_s->b_syn_sync_id = - (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - next_arg)); + (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - (char *)next_arg)); } - next_arg = (char_u *)skipwhite((char *)arg_end); + next_arg = (char_u *)skipwhite(arg_end); } else if (!eap->skip) { curwin->w_s->b_syn_sync_id = (int16_t)syn_name2id("Comment"); } @@ -5180,17 +5181,17 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) || STRNCMP(key, "MAXLINES", 8) == 0 || STRNCMP(key, "LINEBREAKS", 10) == 0) { if (key[4] == 'S') { - arg_end = key + 6; + arg_end = (char *)key + 6; } else if (key[0] == 'L') { - arg_end = key + 11; + arg_end = (char *)key + 11; } else { - arg_end = key + 9; + arg_end = (char *)key + 9; } if (arg_end[-1] != '=' || !ascii_isdigit(*arg_end)) { illegal = TRUE; break; } - linenr_T n = getdigits_int32((char **)&arg_end, false, 0); + linenr_T n = getdigits_int32(&arg_end, false, 0); if (!eap->skip) { if (key[4] == 'B') { curwin->w_s->b_syn_sync_linebreaks = n; @@ -5215,16 +5216,16 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) finished = TRUE; break; } - arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL); - if (*arg_end != *next_arg) { // end delimiter not found - illegal = TRUE; + arg_end = (char *)skip_regexp(next_arg + 1, *next_arg, true, NULL); + if (*arg_end != (char)(*next_arg)) { // end delimiter not found + illegal = true; break; } if (!eap->skip) { // store the pattern and compiled regexp program curwin->w_s->b_syn_linecont_pat = - vim_strnsave(next_arg + 1, (size_t)(arg_end - next_arg) - 1); + vim_strnsave(next_arg + 1, (size_t)(arg_end - (char *)next_arg) - 1); curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic; // Make 'cpoptions' empty, to avoid the 'l' flag @@ -5241,7 +5242,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) break; } } - next_arg = (char_u *)skipwhite((char *)arg_end + 1); + next_arg = (char_u *)skipwhite(arg_end + 1); } else { eap->arg = (char *)next_arg; if (STRCMP(key, "MATCH") == 0) { @@ -5401,7 +5402,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis retval[count] = (int16_t)id; } } - ++count; + count++; } p = skipwhite(end); if (*p != ',') { @@ -5477,7 +5478,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in // that we don't go back past the first one. while ((cur_si->si_flags & HL_TRANS_CONT) && cur_si > (stateitem_T *)(current_state.ga_data)) { - --cur_si; + cur_si--; } // cur_si->si_idx is -1 for keywords, these never contain anything. if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list, @@ -5541,9 +5542,9 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in // restrict recursiveness to 30 to avoid an endless loop for a // cluster that includes itself (indirectly) if (scl_list != NULL && depth < 30) { - ++depth; + depth++; r = in_id_list(NULL, scl_list, ssp, contained); - --depth; + depth--; if (r) { return retval; } @@ -5614,7 +5615,7 @@ void ex_syntax(exarg_T *eap) } xfree(subcmd_name); if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -5624,8 +5625,7 @@ void ex_ownsyntax(exarg_T *eap) char_u *new_value; if (curwin->w_s == &curwin->w_buffer->b_s) { - curwin->w_s = xmalloc(sizeof(synblock_T)); - memset(curwin->w_s, 0, sizeof(synblock_T)); + curwin->w_s = xcalloc(1, sizeof(synblock_T)); hash_init(&curwin->w_s->b_keywtab); hash_init(&curwin->w_s->b_keywtab_ic); // TODO: Keep the spell checking as it was. NOLINT(readability/todo) diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5b799be381..f212aefbfc 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -14,16 +14,17 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/help.h" #include "nvim/if_cscope.h" #include "nvim/input.h" #include "nvim/insexpand.h" @@ -40,7 +41,7 @@ #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/tag.h" @@ -465,7 +466,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // when the argument starts with '/', use it as a regexp if (!no_regexp && *name == '/') { flags = TAG_REGEXP; - ++name; + name++; } else { flags = TAG_NOIC; } @@ -653,13 +654,13 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) || cur_match < num_matches - 1))) { error_cur_match = cur_match; if (use_tagstack) { - --tagstackidx; + tagstackidx--; } if (type == DT_PREV) { - --cur_match; + cur_match--; } else { type = DT_NEXT; - ++cur_match; + cur_match++; } continue; } @@ -1076,9 +1077,9 @@ static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) if (*s1 == NUL) { break; // strings match until NUL } - ++s1; - ++s2; - --len; + s1++; + s2++; + len--; } return 0; // strings match } @@ -1320,7 +1321,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl // Add all matches because tagfunc should do filtering. ga_grow(ga, 1); - ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp; + ((char **)(ga->ga_data))[ga->ga_len++] = (char *)mfp; ntags++; result = OK; }); @@ -1411,7 +1412,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi int matchoff = 0; int save_emsg_off; - char_u *mfp; + char *mfp; garray_T ga_match[MT_COUNT]; // stores matches in sequence hashtab_T ht_match[MT_COUNT]; // stores matches by key hash_T hash = 0; @@ -1476,7 +1477,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi lbuf = xmalloc((size_t)lbuf_size); tag_fname = xmalloc(MAXPATHL + 1); for (mtt = 0; mtt < MT_COUNT; mtt++) { - ga_init(&ga_match[mtt], sizeof(char_u *), 100); + ga_init(&ga_match[mtt], sizeof(char *), 100); hash_init(&ht_match[mtt]); } @@ -1520,7 +1521,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi // This is only to avoid a compiler warning for using search_info // uninitialised. - memset(&search_info, 0, 1); // -V512 + CLEAR_FIELD(search_info); if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { tfu_in_use = true; @@ -1612,7 +1613,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi // unless found already. help_pri++; if (STRICMP(help_lang, "en") != 0) { - ++help_pri; + help_pri++; } } } @@ -1860,7 +1861,7 @@ parse_line: // For "normal" tags: Do a quick check if the tag matches. // This speeds up tag searching a lot! if (orgpat.headlen) { - memset(&tagp, 0, sizeof(tagp)); + CLEAR_FIELD(tagp); tagp.tagname = lbuf; tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB); if (tagp.tagname_end == NULL) { @@ -2088,9 +2089,9 @@ parse_line: // The format is {tagname}@{lang}NUL{heuristic}NUL *tagp.tagname_end = NUL; len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); + mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); - p = mfp; + p = (char_u *)mfp; STRCPY(p, tagp.tagname); p[len] = '@'; STRCPY(p + len + 1, help_lang); @@ -2122,7 +2123,7 @@ parse_line: get_it_again = false; } else { len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char_u) + len + 1); + mfp = xmalloc(sizeof(char) + len + 1); STRLCPY(mfp, tagp.tagname, len + 1); // if wanted, re-read line to get long form too @@ -2140,8 +2141,8 @@ parse_line: // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> // Here <mtt> is the "mtt" value plus 1 to avoid NUL. len = tag_fname_len + STRLEN(lbuf) + 3; - mfp = xmalloc(sizeof(char_u) + len + 1); - p = mfp; + mfp = xmalloc(sizeof(char) + len + 1); + p = (char_u *)mfp; p[0] = (char_u)(mtt + 1); STRCPY(p + 1, tag_fname); #ifdef BACKSLASH_IN_FILENAME @@ -2166,15 +2167,14 @@ parse_line: if (use_cscope) { hash++; } else { - hash = hash_hash(mfp); + hash = hash_hash((char_u *)mfp); } hi = hash_lookup(&ht_match[mtt], (const char *)mfp, STRLEN(mfp), hash); if (HASHITEM_EMPTY(hi)) { - hash_add_item(&ht_match[mtt], hi, mfp, hash); + hash_add_item(&ht_match[mtt], hi, (char_u *)mfp, hash); ga_grow(&ga_match[mtt], 1); - ((char_u **)(ga_match[mtt].ga_data)) - [ga_match[mtt].ga_len++] = mfp; + ((char **)(ga_match[mtt].ga_data))[ga_match[mtt].ga_len++] = mfp; match_count++; } else { // duplicate tag, drop it @@ -2258,29 +2258,29 @@ findtag_end: } if (match_count > 0) { - matches = xmalloc((size_t)match_count * sizeof(char_u *)); + matches = xmalloc((size_t)match_count * sizeof(char *)); } else { matches = NULL; } match_count = 0; for (mtt = 0; mtt < MT_COUNT; mtt++) { for (i = 0; i < ga_match[mtt].ga_len; i++) { - mfp = ((char_u **)(ga_match[mtt].ga_data))[i]; + mfp = ((char **)(ga_match[mtt].ga_data))[i]; if (matches == NULL) { xfree(mfp); } else { if (!name_only) { // Change mtt back to zero-based. - *mfp = (char_u)(*mfp - 1); + *mfp = (char)(*mfp - 1); // change the TAG_SEP back to NUL - for (p = mfp + 1; *p != NUL; p++) { + for (p = (char_u *)mfp + 1; *p != NUL; p++) { if (*p == TAG_SEP) { *p = NUL; } } } - matches[match_count++] = (char *)mfp; + matches[match_count++] = mfp; } } @@ -2342,7 +2342,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) char_u *r_ptr; if (first) { - memset(tnp, 0, sizeof(tagname_T)); + CLEAR_POINTER(tnp); } if (curbuf->b_help) { @@ -2353,7 +2353,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) */ if (first) { ga_clear_strings(&tag_fnames); - ga_init(&tag_fnames, (int)sizeof(char_u *), 10); + ga_init(&tag_fnames, (int)sizeof(char *), 10); do_in_runtimepath("doc/tags doc/tags-??", DIP_ALL, found_tagfile_cb, NULL); } @@ -2373,13 +2373,12 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) simplify_filename(buf); for (int i = 0; i < tag_fnames.ga_len; i++) { - if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0) { + if (STRCMP(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { return FAIL; // avoid duplicate file names } } } else { - STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], - MAXPATHL); + STRLCPY(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); } return OK; } @@ -2387,9 +2386,8 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) if (first) { // Init. We make a copy of 'tags', because autocommands may change // the value without notifying us. - tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) - ? curbuf->b_p_tags : p_tags); - tnp->tn_np = tnp->tn_tags; + tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : p_tags); + tnp->tn_np = (char *)tnp->tn_tags; } /* @@ -2420,7 +2418,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) * Copy next file name into buf. */ buf[0] = NUL; - (void)copy_option_part((char **)&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,"); + (void)copy_option_part(&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,"); r_ptr = vim_findfile_stopdir(buf); // move the filename one char forward and truncate the @@ -2478,7 +2476,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) // Isolate file name, from first to second white space if (*p != NUL) { - ++p; + p++; } tagp->fname = p; p = (char_u *)vim_strchr((char *)p, TAB); @@ -2489,7 +2487,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) // find start of search command, after second white space if (*p != NUL) { - ++p; + p++; } if (*p == NUL) { return FAIL; @@ -2720,7 +2718,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) goto erret; } - ++RedrawingDisabled; + RedrawingDisabled++; if (l_g_do_tagpreview != 0) { postponed_split = 0; // don't split again below @@ -3171,7 +3169,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char_u *sta if (end == NULL) { end = start + STRLEN(start); while (end > start && (end[-1] == '\r' || end[-1] == '\n')) { - --end; + end--; } } len = (int)(end - start); @@ -3250,13 +3248,13 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) // separated by Tabs. n = p; while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':') { - ++p; + p++; } len = (int)(p - n); if (*p == ':' && len > 0) { s = ++p; while (*p != NUL && *p >= ' ') { - ++p; + p++; } n[len] = NUL; if (add_tag_field(dict, (char *)n, s, p) == FAIL) { @@ -3266,7 +3264,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) } else { // Skip field without colon. while (*p != NUL && *p >= ' ') { - ++p; + p++; } } if (*p == NUL) { diff --git a/src/nvim/tag.h b/src/nvim/tag.h index c8051e1dcc..0b4039afb6 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -35,7 +35,7 @@ // Structure used for get_tagfname(). typedef struct { char_u *tn_tags; // value of 'tags' when starting - char_u *tn_np; // current position in tn_tags + char *tn_np; // current position in tn_tags int tn_did_filefind_init; int tn_hf_idx; void *tn_search_ctx; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index eb7c83d317..844a79b33d 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -44,15 +44,16 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/edit.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" @@ -69,7 +70,6 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/terminal.h" #include "nvim/ui.h" @@ -681,7 +681,7 @@ static bool is_filter_char(int c) return !!(tpf_flags & flag); } -void terminal_paste(long count, char_u **y_array, size_t y_size) +void terminal_paste(long count, char **y_array, size_t y_size) { if (y_size == 0) { return; @@ -702,7 +702,7 @@ void terminal_paste(long count, char_u **y_array, size_t y_size) buff_len = len; } char_u *dst = buff; - char_u *src = y_array[j]; + char_u *src = (char_u *)y_array[j]; while (*src != '\0') { len = (size_t)utf_ptr2len((char *)src); int c = utf_ptr2char((char *)src); diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6b16e888a9..fcd3d5724c 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -257,6 +257,7 @@ endfunc func EarlyExit(test) " It's OK for the test we use to test the quit detection. if a:test != 'Test_zz_quit_detected()' + call add(v:errors, v:errmsg) call add(v:errors, 'Test caused Vim to exit: ' . a:test) endif diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index ca7c8574cb..521c3fcd57 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -87,6 +87,10 @@ func Test_argadd() new arga call assert_equal(0, len(argv())) + + if has('unix') + call assert_fails('argadd `Xdoes_not_exist`', 'E479:') + endif endfunc func Test_argadd_empty_curbuf() @@ -408,6 +412,35 @@ func Test_argedit() bw! x endfunc +" Test for the :argdedupe command +func Test_argdedupe() + call Reset_arglist() + argdedupe + call assert_equal([], argv()) + args a a a aa b b a b aa + argdedupe + call assert_equal(['a', 'aa', 'b'], argv()) + args a b c + argdedupe + call assert_equal(['a', 'b', 'c'], argv()) + args a + argdedupe + call assert_equal(['a'], argv()) + args a A b B + argdedupe + if has('fname_case') + call assert_equal(['a', 'A', 'b', 'B'], argv()) + else + call assert_equal(['a', 'b'], argv()) + endif + args a b a c a b + last + argdedupe + next + call assert_equal('c', expand('%:t')) + %argd +endfunc + " Test for the :argdelete command func Test_argdelete() call Reset_arglist() @@ -420,6 +453,8 @@ func Test_argdelete() call assert_equal(['b'], argv()) call assert_fails('argdelete', 'E610:') call assert_fails('1,100argdelete', 'E16:') + call assert_fails('argdel /\)/', 'E55:') + call assert_fails('1argdel 1', 'E474:') call Reset_arglist() args a b c d @@ -427,6 +462,8 @@ func Test_argdelete() argdel call Assert_argc(['a', 'c', 'd']) %argdel + + call assert_fails('argdel does_not_exist', 'E480:') endfunc func Test_argdelete_completion() @@ -472,13 +509,16 @@ func Test_arglist_autocmd() new " redefine arglist; go to Xxx1 next! Xxx1 Xxx2 Xxx3 - " open window for all args + " open window for all args; Reading Xxx2 will change the arglist and the + " third window will get Xxx1: + " win 1: Xxx1 + " win 2: Xxx2 + " win 3: Xxx1 all call assert_equal('test file Xxx1', getline(1)) wincmd w wincmd w call assert_equal('test file Xxx1', getline(1)) - " should now be in Xxx2 rewind call assert_equal('test file Xxx2', getline(1)) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 1c2f86a584..716511210d 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -2614,6 +2614,28 @@ func Test_BufWrite_lockmarks() call delete('Xtest2') endfunc +func Test_FileType_spell() + if !isdirectory('/tmp') + throw "Skipped: requires /tmp directory" + endif + + " this was crashing with an invalid free() + setglobal spellfile=/tmp/en.utf-8.add + augroup crash + autocmd! + autocmd BufNewFile,BufReadPost crashfile setf somefiletype + autocmd BufNewFile,BufReadPost crashfile set ft=anotherfiletype + autocmd FileType anotherfiletype setlocal spell + augroup END + func! NoCrash() abort + edit /tmp/crashfile + endfunc + call NoCrash() + + au! crash + setglobal spellfile= +endfunc + " Test closing a window or editing another buffer from a FileChangedRO handler " in a readonly buffer func Test_FileChangedRO_winclose() @@ -2715,6 +2737,59 @@ func Test_autocmd_sigusr1() unlet g:sigusr1_passed endfunc +" Test for BufReadPre autocmd deleting the file +func Test_BufReadPre_delfile() + augroup TestAuCmd + au! + autocmd BufReadPre Xfile call delete('Xfile') + augroup END + call writefile([], 'Xfile') + call assert_fails('new Xfile', 'E200:') + call assert_equal('Xfile', @%) + call assert_equal(1, &readonly) + call delete('Xfile') + augroup TestAuCmd + au! + augroup END + close! +endfunc + +" Test for BufReadPre autocmd changing the current buffer +func Test_BufReadPre_changebuf() + augroup TestAuCmd + au! + autocmd BufReadPre Xfile edit Xsomeotherfile + augroup END + call writefile([], 'Xfile') + call assert_fails('new Xfile', 'E201:') + call assert_equal('Xsomeotherfile', @%) + call assert_equal(1, &readonly) + call delete('Xfile') + augroup TestAuCmd + au! + augroup END + close! +endfunc + +" Test for BufWipeouti autocmd changing the current buffer when reading a file +" in an empty buffer with 'f' flag in 'cpo' +func Test_BufDelete_changebuf() + new + augroup TestAuCmd + au! + autocmd BufWipeout * let bufnr = bufadd('somefile') | exe "b " .. bufnr + augroup END + let save_cpo = &cpo + set cpo+=f + call assert_fails('r Xfile', 'E484:') + call assert_equal('somefile', @%) + let &cpo = save_cpo + augroup TestAuCmd + au! + augroup END + close! +endfunc + " Test for the temporary internal window used to execute autocmds func Test_autocmd_window() %bw! diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index ffb8e3facd..579d3a5eb5 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -19,7 +19,7 @@ func Test_setbufline_getbufline() let b = bufnr('%') wincmd w call assert_equal(1, setbufline(b, 5, ['x'])) - call assert_equal(1, setbufline(1234, 1, ['x'])) + call assert_equal(1, setbufline(bufnr('$') + 1, 1, ['x'])) call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) call assert_equal(['d'], getbufline(b, 4)) diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 7aac731709..b9f027afb2 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -126,6 +126,40 @@ func Test_wildmenu_screendump() call delete('XTest_wildmenu') endfunc +func Test_changing_cmdheight() + CheckScreendump + + let lines =<< trim END + set cmdheight=1 laststatus=2 + END + call writefile(lines, 'XTest_cmdheight') + + let buf = RunVimInTerminal('-S XTest_cmdheight', {'rows': 8}) + call term_sendkeys(buf, ":resize -3\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {}) + + " using the space available doesn't change the status line + call term_sendkeys(buf, ":set cmdheight+=3\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {}) + + " using more space moves the status line up + call term_sendkeys(buf, ":set cmdheight+=1\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {}) + + " reducing cmdheight moves status line down + call term_sendkeys(buf, ":set cmdheight-=2\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {}) + + " reducing window size and then setting cmdheight + call term_sendkeys(buf, ":resize -1\<CR>") + call term_sendkeys(buf, ":set cmdheight=1\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_cmdheight') +endfunc + func Test_map_completion() if !has('cmdline_compl') return @@ -912,12 +946,26 @@ func Test_cmdline_complete_various() call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:) + " completion of autocmd group after comma + call feedkeys(":doautocmd BufNew,BufEn\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"doautocmd BufNew,BufEnter", @:) + + " completion of file name in :doautocmd + call writefile([], 'Xfile1') + call writefile([], 'Xfile2') + call feedkeys(":doautocmd BufEnter Xfi\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"doautocmd BufEnter Xfile1 Xfile2", @:) + call delete('Xfile1') + call delete('Xfile2') + " completion for the :augroup command - augroup XTest + augroup XTest.test augroup END call feedkeys(":augroup X\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"augroup XTest", @:) - augroup! XTest + call assert_equal("\"augroup XTest.test", @:) + call feedkeys(":au X\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"au XTest.test", @:) + augroup! XTest.test " completion for the :unlet command call feedkeys(":unlet one two\<C-A>\<C-B>\"\<CR>", 'xt') @@ -1392,14 +1440,6 @@ func Test_cmdwin_jump_to_win() call assert_equal(1, winnr('$')) endfunc -" Test for backtick expression in the command line -func Test_cmd_backtick() - %argd - argadd `=['a', 'b', 'c']` - call assert_equal(['a', 'b', 'c'], argv()) - %argd -endfunc - func Test_cmdwin_tabpage() tabedit " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. @@ -1412,11 +1452,22 @@ func Test_cmdwin_tabpage() tabclose! endfunc +" Test for backtick expression in the command line +func Test_cmd_backtick() + CheckNotMSWindows " FIXME: see #19297 + %argd + argadd `=['a', 'b', 'c']` + call assert_equal(['a', 'b', 'c'], argv()) + %argd + + argadd `echo abc def` + call assert_equal(['abc def'], argv()) + %argd +endfunc + " Test for the :! command func Test_cmd_bang() - if !has('unix') - return - endif + CheckUnix let lines =<< trim [SCRIPT] " Test for no previous command diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 1dbbe578c5..ea453b7174 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -137,7 +137,7 @@ func Common_vert_split() " Test diffoff diffoff! - 1wincmd 2 + 1wincmd w let &diff = 1 let &fdm = diff_fdm let &fdc = diff_fdc diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index a09346a595..e26bbdc5be 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -262,22 +262,6 @@ func Test_edit_09() bw! endfunc -func Test_edit_10() - " Test for starting selectmode - new - set selectmode=key keymodel=startsel - call setline(1, ['abc', 'def', 'ghi']) - call cursor(1, 4) - call feedkeys("A\<s-home>start\<esc>", 'txin') - call assert_equal(['startdef', 'ghi'], getline(1, '$')) - " start select mode again with gv - set selectmode=cmd - call feedkeys('gvabc', 'xt') - call assert_equal('abctdef', getline(1)) - set selectmode= keymodel= - bw! -endfunc - func Test_edit_11() " Test that indenting kicks in new @@ -1620,6 +1604,7 @@ func Test_edit_InsertLeave_undo() bwipe! au! InsertLeave call delete('XtestUndo') + call delete(undofile('XtestUndo')) set undofile& endfunc @@ -1687,11 +1672,11 @@ func Test_edit_noesckeys() endfunc " Test for running an invalid ex command in insert mode using CTRL-O -" Note that vim has a hard-coded sleep of 3 seconds. So this test will take -" more than 3 seconds to complete. func Test_edit_ctrl_o_invalid_cmd() new set showmode showcmd + " Avoid a sleep of 3 seconds. Zero might have side effects. + " call test_override('ui_delay', 50) let caught_e492 = 0 try call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt") @@ -1701,6 +1686,18 @@ func Test_edit_ctrl_o_invalid_cmd() call assert_equal(1, caught_e492) call assert_equal('abc', getline(1)) set showmode& showcmd& + " call test_override('ui_delay', 0) + close! +endfunc + +" Test for editing a file with a very long name +func Test_edit_illegal_filename() + CheckEnglish + new + redir => msg + exe 'edit ' . repeat('f', 5000) + redir END + call assert_match("Illegal file name$", split(msg, "\n")[0]) close! endfunc @@ -1763,6 +1760,102 @@ func Test_edit_is_a_directory() call delete(dirname, 'rf') endfunc +" Test for editing a file using invalid file encoding +func Test_edit_invalid_encoding() + CheckEnglish + call writefile([], 'Xfile') + redir => msg + new ++enc=axbyc Xfile + redir END + call assert_match('\[NOT converted\]', msg) + call delete('Xfile') + close! +endfunc + +" Test for the "charconvert" option +func Test_edit_charconvert() + CheckEnglish + call writefile(['one', 'two'], 'Xfile') + + " set 'charconvert' to a non-existing function + set charconvert=NonExitingFunc() + new + let caught_e117 = v:false + try + redir => msg + edit ++enc=axbyc Xfile + catch /E117:/ + let caught_e117 = v:true + finally + redir END + endtry + call assert_true(caught_e117) + call assert_equal(['one', 'two'], getline(1, '$')) + call assert_match("Conversion with 'charconvert' failed", msg) + close! + set charconvert& + + " 'charconvert' function doesn't create a output file + func Cconv1() + endfunc + set charconvert=Cconv1() + new + redir => msg + edit ++enc=axbyc Xfile + redir END + call assert_equal(['one', 'two'], getline(1, '$')) + call assert_match("can't read output of 'charconvert'", msg) + close! + delfunc Cconv1 + set charconvert& + + " 'charconvert' function to convert to upper case + func Cconv2() + let data = readfile(v:fname_in) + call map(data, 'toupper(v:val)') + call writefile(data, v:fname_out) + endfunc + set charconvert=Cconv2() + new Xfile + write ++enc=ucase Xfile1 + call assert_equal(['ONE', 'TWO'], readfile('Xfile1')) + call delete('Xfile1') + close! + delfunc Cconv2 + set charconvert& + + " 'charconvert' function removes the input file + func Cconv3() + call delete(v:fname_in) + endfunc + set charconvert=Cconv3() + new + call assert_fails('edit ++enc=lcase Xfile', 'E202:') + call assert_equal([''], getline(1, '$')) + close! + delfunc Cconv3 + set charconvert& + + call delete('Xfile') +endfunc + +" Test for editing a file without read permission +func Test_edit_file_no_read_perm() + CheckUnix + CheckNotBSD + call writefile(['one', 'two'], 'Xfile') + call setfperm('Xfile', '-w-------') + new + redir => msg + edit Xfile + redir END + call assert_equal(1, &readonly) + call assert_equal([''], getline(1, '$')) + call assert_match('\[Permission Denied\]', msg) + close! + call delete('Xfile') +endfunc + " Using :edit without leaving 'insertmode' should not cause Insert mode to be " re-entered immediately after <C-L> func Test_edit_insertmode_ex_edit() diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 811c6c946d..eff1376d3c 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -75,6 +75,18 @@ func Test_for_invalid() redraw endfunc +func Test_for_over_null_string() + let save_enc = &enc + " set enc=iso8859 + let cnt = 0 + for c in v:_null_string + let cnt += 1 + endfor + call assert_equal(0, cnt) + + let &enc = save_enc +endfunc + func Test_readfile_binary() new call setline(1, ['one', 'two', 'three']) diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index befcaec2b2..37be293950 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -95,7 +95,7 @@ func Test_exit_code() [CODE] if RunVim(before, ['quit'], '') - call assert_equal(['qp = null', 'ep = null', 'lp = 0', 'l = 0'], readfile('Xtestout')) + call assert_equal(['qp = v:null', 'ep = v:null', 'lp = 0', 'l = 0'], readfile('Xtestout')) endif call delete('Xtestout') diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index b48c2e8a19..df01d84f19 100644 --- a/src/nvim/testdir/test_expand_func.vim +++ b/src/nvim/testdir/test_expand_func.vim @@ -37,17 +37,54 @@ func Test_expand_sflnum() delcommand Flnum endfunc -func Test_expand_sfile() +func Test_expand_sfile_and_stack() call assert_match('test_expand_func\.vim$', s:sfile) - call assert_match('^function .*\.\.Test_expand_sfile$', expand('<sfile>')) + let expected = 'script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack' + call assert_match(expected .. '$', expand('<sfile>')) + call assert_match(expected .. '\[4\]$' , expand('<stack>')) " Call in script-local function - call assert_match('^function .*\.\.Test_expand_sfile\[5\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile()) + call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack\[7\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile()) " Call in command command Sfile echo expand('<sfile>') - call assert_match('^function .*\.\.Test_expand_sfile$', trim(execute('Sfile'))) + call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack$', trim(execute('Sfile'))) delcommand Sfile + + " Use <stack> from sourced script. + let lines =<< trim END + " comment here + let g:stack_value = expand('<stack>') + END + call writefile(lines, 'Xstack') + source Xstack + call assert_match('\<Xstack\[2\]$', g:stack_value) + unlet g:stack_value + call delete('Xstack') + + if exists('+shellslash') + call mkdir('Xshellslash') + let lines =<< trim END + let g:stack1 = expand('<stack>') + set noshellslash + let g:stack2 = expand('<stack>') + set shellslash + let g:stack3 = expand('<stack>') + END + call writefile(lines, 'Xshellslash/Xstack') + " Test that changing 'shellslash' always affects the result of expand() + " when sourcing a script multiple times. + for i in range(2) + source Xshellslash/Xstack + call assert_match('\<Xshellslash/Xstack\[1\]$', g:stack1) + call assert_match('\<Xshellslash\\Xstack\[3\]$', g:stack2) + call assert_match('\<Xshellslash/Xstack\[5\]$', g:stack3) + unlet g:stack1 + unlet g:stack2 + unlet g:stack3 + endfor + call delete('Xshellslash', 'rf') + endif endfunc func Test_expand_slnum() diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index c6e781a1ef..b77f02afd1 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -242,6 +242,15 @@ func Test_file_changed_dialog() call assert_equal(1, line('$')) call assert_equal('new line', getline(1)) + " File created after starting to edit it + call delete('Xchanged_d') + new Xchanged_d + call writefile(['one'], 'Xchanged_d') + call feedkeys('L', 'L') + checktime Xchanged_d + call assert_equal(['one'], getline(1, '$')) + close! + bwipe! call delete('Xchanged_d') endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index eedad15e9e..e3a8370661 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -71,6 +71,7 @@ let s:filename_checks = { \ 'asciidoc': ['file.asciidoc', 'file.adoc'], \ 'asn': ['file.asn', 'file.asn1'], \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'], + \ 'astro': ['file.astro'], \ 'atlas': ['file.atl', 'file.as'], \ 'autohotkey': ['file.ahk'], \ 'autoit': ['file.au3'], @@ -360,7 +361,7 @@ let s:filename_checks = { \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], \ 'moo': ['file.moo'], \ 'moonscript': ['file.moon'], - \ 'mp': ['file.mp'], + \ 'mp': ['file.mp', 'file.mpxl', 'file.mpiv', 'file.mpvi'], \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'], \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'], \ 'msidl': ['file.odl', 'file.mof'], @@ -441,6 +442,7 @@ let s:filename_checks = { \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], \ 'ql': ['file.ql', 'file.qll'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'], + \ 'quarto': ['file.qmd'], \ 'r': ['file.r'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index c11e7b4fea..44b6f0373e 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1769,6 +1769,15 @@ func Test_char2nr() call assert_equal(12354, char2nr('あ', 1)) endfunc +func Test_charclass() + call assert_equal(0, charclass(' ')) + call assert_equal(1, charclass('.')) + call assert_equal(2, charclass('x')) + call assert_equal(3, charclass("\u203c")) + " this used to crash vim + call assert_equal(0, "xxx"[-1]->charclass()) +endfunc + func Test_eventhandler() call assert_equal(0, eventhandler()) endfunc diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index feae44e5ee..b2bb189688 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -191,6 +191,22 @@ func Test_gf_error() au! InsertCharPre bwipe! + + " gf is not allowed when buffer is locked + new + augroup Test_gf + au! + au OptionSet diff norm! gf + augroup END + call setline(1, ['Xfile1', 'line2', 'line3', 'line4']) + " Nvim does not support test_override() + " call test_override('starting', 1) + " call assert_fails('diffthis', 'E788:') + " call test_override('starting', 0) + augroup Test_gf + au! + augroup END + bw! endfunc " If a file is not found by 'gf', then 'includeexpr' should be used to locate diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index 947f7efc7c..cb6851250c 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -9,7 +9,10 @@ func Test_yank_put_clipboard() set clipboard=unnamed g/^/normal yyp call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6)) - + set clipboard=unnamed,unnamedplus + call setline(1, ['a', 'b', 'c']) + g/^/normal yyp + call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6)) set clipboard& bwipe! endfunc diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index 49095400ef..6d029ffda2 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -122,6 +122,24 @@ func Test_gd() call XTest_goto_decl('gd', lines, 3, 14) endfunc +" Using gd to jump to a declaration in a fold +func Test_gd_with_fold() + new + let lines =<< trim END + #define ONE 1 + #define TWO 2 + #define THREE 3 + + TWO + END + call setline(1, lines) + 1,3fold + call feedkeys('Ggd', 'xt') + call assert_equal(2, line('.')) + call assert_equal(-1, foldclosedend(2)) + bw! +endfunc + func Test_gd_not_local() let lines =<< trim [CODE] int func1(void) diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index aa66d86af1..2f4e1db4a1 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -649,6 +649,8 @@ func Test_reduce() call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:') call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:') call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:') + call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:') + call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:') let g:lut = [1, 2, 3, 4] func EvilRemove() diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index a02d23b409..3a607ff533 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -95,6 +95,65 @@ func Test_echoerr() call test_ignore_error('RESET') endfunc +func Test_mode_message_at_leaving_insert_by_ctrl_c() + if !has('terminal') || has('gui_running') + return + endif + + " Set custom statusline built by user-defined function. + let testfile = 'Xtest.vim' + call writefile([ + \ 'func StatusLine() abort', + \ ' return ""', + \ 'endfunc', + \ 'set statusline=%!StatusLine()', + \ 'set laststatus=2', + \ ], testfile) + + let rows = 10 + let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows}) + call term_wait(buf, 200) + call assert_equal('run', job_status(term_getjob(buf))) + + call term_sendkeys(buf, "i") + call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))}) + call term_sendkeys(buf, "\<C-C>") + call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))}) + + call term_sendkeys(buf, ":qall!\<CR>") + call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))}) + exe buf . 'bwipe!' + call delete(testfile) +endfunc + +func Test_mode_message_at_leaving_insert_with_esc_mapped() + if !has('terminal') || has('gui_running') + return + endif + + " Set custom statusline built by user-defined function. + let testfile = 'Xtest.vim' + call writefile([ + \ 'set laststatus=2', + \ 'inoremap <Esc> <Esc>00', + \ ], testfile) + + let rows = 10 + let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows}) + call term_wait(buf, 200) + call assert_equal('run', job_status(term_getjob(buf))) + + call term_sendkeys(buf, "i") + call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))}) + call term_sendkeys(buf, "\<Esc>") + call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))}) + + call term_sendkeys(buf, ":qall!\<CR>") + call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))}) + exe buf . 'bwipe!' + call delete(testfile) +endfunc + func Test_echospace() set noruler noshowcmd laststatus=1 call assert_equal(&columns - 1, v:echospace) @@ -317,6 +376,7 @@ func Test_fileinfo_after_echo() endfunc func Test_cmdheight_zero() + enew set cmdheight=0 set showcmd redraw! @@ -366,10 +426,13 @@ func Test_cmdheight_zero() 7 call feedkeys(":\"\<C-R>=line('w0')\<CR>\<CR>", "xt") call assert_equal('"1', @:) - bwipe! + bwipe! + bwipe! set cmdheight& set showcmd& + tabnew + tabonly endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 7cb70aa2af..347404a579 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -3,6 +3,7 @@ source shared.vim source check.vim source view_util.vim +source screendump.vim func Setup_NewWindow() 10new @@ -123,31 +124,6 @@ func Test_normal01_keymodel() bw! endfunc -" Test for select mode -func Test_normal02_selectmode() - call Setup_NewWindow() - 50 - norm! gHy - call assert_equal('y51', getline('.')) - call setline(1, range(1,100)) - 50 - exe ":norm! V9jo\<c-g>y" - call assert_equal('y60', getline('.')) - " clean up - bw! -endfunc - -func Test_normal02_selectmode2() - " some basic select mode tests - call Setup_NewWindow() - 50 - " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx') - call feedkeys("i\<c-o>gHc\<esc>", 'tx') - call assert_equal('c51', getline('.')) - " clean up - bw! -endfunc - func Test_normal03_join() " basic join test call Setup_NewWindow() @@ -491,6 +467,18 @@ func Test_normal11_showcmd() call assert_equal(3, line('$')) exe "norm! 0d3\<del>2l" call assert_equal('obar2foobar3', getline('.')) + " test for the visual block size displayed in the status line + call setline(1, ['aaaaa', 'bbbbb', 'ccccc']) + call feedkeys("ggl\<C-V>lljj", 'xt') + redraw! + call assert_match('3x3$', Screenline(&lines)) + call feedkeys("\<C-V>", 'xt') + " test for visually selecting a multi-byte character + call setline(1, ["\U2206"]) + call feedkeys("ggv", 'xt') + redraw! + call assert_match('1-3$', Screenline(&lines)) + call feedkeys("v", 'xt') bw! endfunc @@ -654,6 +642,19 @@ func Test_normal15_z_scroll_vert() call assert_equal(21, winsaveview()['topline']) call assert_equal([0, 21, 2, 0, 9], getcurpos()) + " Test for z+ with [count] greater than buffer size + 1 + norm! 1000z+ + call assert_equal(' 100', getline('.')) + call assert_equal(100, winsaveview()['topline']) + call assert_equal([0, 100, 2, 0, 9], getcurpos()) + + " Test for z+ from the last buffer line + norm! Gz.z+ + call assert_equal(' 100', getline('.')) + call assert_equal(100, winsaveview()['topline']) + call assert_equal([0, 100, 2, 0, 9], getcurpos()) + " Test for z^ norm! 22z+0 norm! z^ @@ -661,6 +662,12 @@ func Test_normal15_z_scroll_vert() call assert_equal(12, winsaveview()['topline']) call assert_equal([0, 21, 2, 0, 9], getcurpos()) + " Test for z^ from first buffer line + norm! ggz^ + call assert_equal('1', getline('.')) + call assert_equal(1, winsaveview()['topline']) + call assert_equal([0, 1, 1, 0, 1], getcurpos()) + " Test for [count]z^ 1 norm! 30z^ @@ -740,6 +747,19 @@ func Test_normal16_z_scroll_hor() norm! yl call assert_equal('z', @0) + " Test for zs and ze with folds + %fold + norm! $zs + call assert_equal(26, col('.')) + call assert_equal(0, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + norm! ze + call assert_equal(26, col('.')) + call assert_equal(0, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + " cleanup set wrap listchars=eol:$ bw! @@ -833,6 +853,19 @@ func Test_vert_scroll_cmds() normal! 4H call assert_equal(33, line('.')) + " Test for using a large count value + %d + call setline(1, range(1, 4)) + norm! 6H + call assert_equal(4, line('.')) + + " Test for 'M' with folded lines + %d + call setline(1, range(1, 20)) + 1,5fold + norm! LM + call assert_equal(12, line('.')) + " Test for the CTRL-E and CTRL-Y commands with folds %d call setline(1, range(1, 10)) @@ -851,6 +884,18 @@ func Test_vert_scroll_cmds() exe "normal \<C-Y>\<C-Y>" call assert_equal(h + 1, line('w$')) + " Test for CTRL-Y from the first line and CTRL-E from the last line + %d + set scrolloff=2 + call setline(1, range(1, 4)) + exe "normal gg\<C-Y>" + call assert_equal(1, line('w0')) + call assert_equal(1, line('.')) + exe "normal G4\<C-E>\<C-E>" + call assert_equal(4, line('w$')) + call assert_equal(4, line('.')) + set scrolloff& + " Using <PageUp> and <PageDown> in an empty buffer should beep %d call assert_beeps('exe "normal \<PageUp>"') @@ -899,6 +944,18 @@ func Test_vert_scroll_cmds() exe "normal \<C-D>" call assert_equal(50, line('w0')) + " Test for <S-CR>. Page down. + %d + call setline(1, range(1, 100)) + call feedkeys("\<S-CR>", 'xt') + call assert_equal(14, line('w0')) + call assert_equal(28, line('w$')) + + " Test for <S-->. Page up. + call feedkeys("\<S-->", 'xt') + call assert_equal(1, line('w0')) + call assert_equal(15, line('w$')) + set foldenable& close! endfunc @@ -1213,6 +1270,13 @@ func Test_normal18_z_fold() norm! j call assert_equal('55', getline('.')) + " Test for zm with a count + 50 + set foldlevel=2 + norm! 3zm + call assert_equal(0, &foldlevel) + call assert_equal(49, foldclosed(line('.'))) + " Test for zM 48 set nofoldenable foldlevel=99 @@ -1420,6 +1484,15 @@ func Test_normal23_K() set iskeyword-=% set iskeyword-=\| + " Currently doesn't work in Nvim, see #19436 + " Test for specifying a count to K + " 1 + " com! -nargs=* Kprog let g:Kprog_Args = <q-args> + " set keywordprg=:Kprog + " norm! 3K + " call assert_equal('3 version8', g:Kprog_Args) + " delcom Kprog + " Only expect "man" to work on Unix if !has("unix") || has('nvim') " Nvim K uses :terminal. #15398 let &keywordprg = k @@ -1867,7 +1940,31 @@ func Test_normal29_brace() bw! endfunc -" Test for ~ command +" Test for section movements +func Test_normal_section() + new + let lines =<< trim [END] + int foo() + { + if (1) + { + a = 1; + } + } + [END] + call setline(1, lines) + + " jumping to a folded line using [[ should open the fold + 2,3fold + call cursor(5, 1) + call feedkeys("[[", 'xt') + call assert_equal(2, line('.')) + call assert_equal(-1, foldclosedend(line('.'))) + + close! +endfunc + +" Test for changing case using u, U, gu, gU and ~ (tilde) commands func Test_normal30_changecase() new call append(0, 'This is a simple test: äüöß') @@ -1887,6 +1984,9 @@ func Test_normal30_changecase() call assert_equal('this is a SIMPLE TEST: ÄÜÖSS', getline('.')) norm! V~ call assert_equal('THIS IS A simple test: äüöss', getline('.')) + call assert_beeps('norm! c~') + %d + call assert_beeps('norm! ~') " Test for changing case across lines using 'whichwrap' call setline(1, ['aaaaaa', 'aaaaaa']) @@ -2038,9 +2138,9 @@ func Test_normal33_g_cmd2() call assert_equal(2, line('.')) call assert_fails(':norm! g;', 'E662') call assert_fails(':norm! g,', 'E663') - let &ul=&ul + let &ul = &ul call append('$', ['a', 'b', 'c', 'd']) - let &ul=&ul + let &ul = &ul call append('$', ['Z', 'Y', 'X', 'W']) let a = execute(':changes') call assert_match('2\s\+0\s\+2', a) @@ -2889,6 +2989,20 @@ func Test_message_when_using_ctrl_c() bwipe! endfunc +func Test_mode_updated_after_ctrl_c() + CheckScreendump + + let buf = RunVimInTerminal('', {'rows': 5}) + call term_sendkeys(buf, "i") + call term_sendkeys(buf, "\<C-O>") + " wait a moment so that the "-- (insert) --" message is displayed + call TermWait(buf, 50) + call term_sendkeys(buf, "\<C-C>") + call VerifyScreenDump(buf, 'Test_mode_updated_1', {}) + + call StopVimInTerminal(buf) +endfunc + " Test for '[m', ']m', '[M' and ']M' " Jumping to beginning and end of methods in Java-like languages func Test_java_motion() @@ -2897,25 +3011,26 @@ func Test_java_motion() call assert_beeps('normal! ]m') call assert_beeps('normal! [M') call assert_beeps('normal! ]M') - a -Piece of Java -{ - tt m1 { - t1; - } e1 - - tt m2 { - t2; - } e2 - - tt m3 { - if (x) - { - t3; - } - } e3 -} -. + let lines =<< trim [CODE] + Piece of Java + { + tt m1 { + t1; + } e1 + + tt m2 { + t2; + } e2 + + tt m3 { + if (x) + { + t3; + } + } e3 + } + [CODE] + call setline(1, lines) normal gg @@ -2968,14 +3083,21 @@ Piece of Java call assert_equal("{LF", getline('.')) call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')]) + call cursor(2, 1) + call assert_beeps('norm! 5]m') + + " jumping to a method in a fold should open the fold + 6,10fold + call feedkeys("gg3]m", 'xt') + call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')]) + call assert_equal(-1, foldclosedend(7)) + close! endfunc +" Tests for g cmds func Test_normal_gdollar_cmd() - if !has("jumplist") - return - endif - " Tests for g cmds + CheckFeature jumplist call Setup_NewWindow() " Make long lines that will wrap %s/$/\=repeat(' foobar', 10)/ @@ -3183,6 +3305,27 @@ func Test_normal_colon_op() close! endfunc +" Test for deleting or changing characters across lines with 'whichwrap' +" containing 's'. Should count <EOL> as one character. +func Test_normal_op_across_lines() + new + set whichwrap& + call setline(1, ['one two', 'three four']) + exe "norm! $3d\<Space>" + call assert_equal(['one twhree four'], getline(1, '$')) + + call setline(1, ['one two', 'three four']) + exe "norm! $3c\<Space>x" + call assert_equal(['one twxhree four'], getline(1, '$')) + + set whichwrap+=l + call setline(1, ['one two', 'three four']) + exe "norm! $3x" + call assert_equal(['one twhree four'], getline(1, '$')) + close! + set whichwrap& +endfunc + " Test for 'w' and 'b' commands func Test_normal_word_move() new @@ -3256,6 +3399,19 @@ func Test_normal_vert_scroll_longline() close! endfunc +" Test for jumping in a file using % +func Test_normal_percent_jump() + new + call setline(1, range(1, 100)) + + " jumping to a folded line should open the fold + 25,75fold + call feedkeys('50%', 'xt') + call assert_equal(50, line('.')) + call assert_equal(-1, foldclosedend(50)) + close! +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index b10f0f5030..fdfc1c0f89 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -368,9 +368,17 @@ func Test_set_errors() call assert_fails('set sessionoptions=curdir,sesdir', 'E474:') call assert_fails('set foldmarker={{{,', 'E474:') call assert_fails('set sessionoptions=sesdir,curdir', 'E474:') - call assert_fails('set listchars=trail:· ambiwidth=double', 'E834:') + setlocal listchars=trail:· + call assert_fails('set ambiwidth=double', 'E834:') + setlocal listchars=trail:- + setglobal listchars=trail:· + call assert_fails('set ambiwidth=double', 'E834:') set listchars& - call assert_fails('set fillchars=stl:· ambiwidth=double', 'E835:') + setlocal fillchars=stl:· + call assert_fails('set ambiwidth=double', 'E835:') + setlocal fillchars=stl:- + setglobal fillchars=stl:· + call assert_fails('set ambiwidth=double', 'E835:') set fillchars& call assert_fails('set fileencoding=latin1,utf-8', 'E474:') set nomodifiable @@ -812,11 +820,16 @@ func Test_rightleftcmd() set rightleft& endfunc -" Test for the "debug" option +" Test for the 'debug' option func Test_debug_option() + " redraw to avoid matching previous messages + redraw set debug=beep exe "normal \<C-c>" call assert_equal('Beep!', Screenline(&lines)) + call assert_equal('line 4:', Screenline(&lines - 1)) + " only match the final colon in the line that shows the source + call assert_match(':$', Screenline(&lines - 2)) set debug& endfunc diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index 191cd948ac..14b9724d67 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -522,8 +522,8 @@ endfunc func Test_search_with_end_offset() new call setline(1, ['', 'dog(a', 'cat(']) - exe "normal /(/e+" .. "\<CR>" - normal "ayn + exe "normal /(/e+\<CR>" + normal n"ayn call assert_equal("a\ncat(", @a) close! endfunc diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 52e745438d..11dd3badb6 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -279,7 +279,12 @@ func Test_get_register() call feedkeys(":\<C-R>r\<Esc>", 'xt') call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + call assert_fails('let r = getreg("=", [])', 'E745:') + call assert_fails('let r = getreg("=", 1, [])', 'E745:') enew! + + " Using a register in operator-pending mode should fail + call assert_beeps('norm! c"') endfunc func Test_set_register() @@ -426,6 +431,12 @@ func Test_execute_register() @ call assert_equal(3, i) + " try to execute expression register and use a backspace to cancel it + new + call feedkeys("@=\<BS>ax\<CR>y", 'xt') + call assert_equal(['x', 'y'], getline(1, '$')) + close! + " cannot execute a register in operator pending mode call assert_beeps('normal! c@r') endfunc diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim index b483841060..f2cab45450 100644 --- a/src/nvim/testdir/test_selectmode.vim +++ b/src/nvim/testdir/test_selectmode.vim @@ -2,6 +2,156 @@ source shared.vim +" Test for select mode +func Test_selectmode_basic() + new + call setline(1, range(1,100)) + 50 + norm! gHy + call assert_equal('y51', getline('.')) + call setline(1, range(1,100)) + 50 + exe ":norm! V9jo\<c-g>y" + call assert_equal('y60', getline('.')) + call setline(1, range(1,100)) + 50 + " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx') + call feedkeys("i\<c-o>gHc\<esc>", 'tx') + call assert_equal('c51', getline('.')) + " clean up + bw! +endfunc + +" Test for starting selectmode +func Test_selectmode_start() + new + set selectmode=key keymodel=startsel + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 4) + call feedkeys("A\<s-home>start\<esc>", 'txin') + call assert_equal(['startdef', 'ghi'], getline(1, '$')) + " start select mode again with gv + set selectmode=cmd + call feedkeys('gvabc', 'xt') + call assert_equal('abctdef', getline(1)) + set selectmode= keymodel= + bw! +endfunc + +" Test for characterwise select mode +func Test_characterwise_select_mode() + new + + " Select mode maps + snoremap <lt>End> <End> + snoremap <lt>Down> <Down> + snoremap <lt>Del> <Del> + + " characterwise select mode: delete middle line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkkgh\<End>\<Del>" + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + " characterwise select mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkkgh\<Down>\<End>\<Del>" + call assert_equal(['', 'c'], getline(1, '$')) + + " characterwise select mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Ggh\<End>\<Del>" + call assert_equal(['', 'a', 'b', ''], getline(1, '$')) + + " characterwise select mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal Gkgh\<Down>\<End>\<Del>" + call assert_equal(['', 'a', ''], getline(1, '$')) + + " CTRL-H in select mode behaves like 'x' + call setline(1, 'abcdef') + exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>" + call assert_equal('ef', getline(1)) + + " CTRL-O in select mode switches to visual mode for one command + call setline(1, 'abcdef') + exe "normal! gggh\<C-O>3lm" + call assert_equal('mef', getline(1)) + + sunmap <lt>End> + sunmap <lt>Down> + sunmap <lt>Del> + bwipe! +endfunc + +" Test for linewise select mode +func Test_linewise_select_mode() + new + + " linewise select mode: delete middle line + call append('$', ['a', 'b', 'c']) + exe "normal GkkgH\<Del>" + call assert_equal(['', 'b', 'c'], getline(1, '$')) + + " linewise select mode: delete middle two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GkkgH\<Down>\<Del>" + call assert_equal(['', 'c'], getline(1, '$')) + + " linewise select mode: delete last line + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GgH\<Del>" + call assert_equal(['', 'a', 'b'], getline(1, '$')) + + " linewise select mode: delete last two lines + call deletebufline('', 1, '$') + call append('$', ['a', 'b', 'c']) + exe "normal GkgH\<Down>\<Del>" + call assert_equal(['', 'a'], getline(1, '$')) + + bwipe! +endfunc + +" Test for blockwise select mode (g CTRL-H) +func Test_blockwise_select_mode() + new + call setline(1, ['foo', 'bar']) + call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') + call assert_equal(['mmo', 'mmr'], getline(1, '$')) + close! +endfunc + +" Test for using visual mode maps in select mode +func Test_select_mode_map() + new + vmap <buffer> <F2> 3l + call setline(1, 'Test line') + call feedkeys("gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + + vmap <buffer> <F2> ygV + call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') + call assert_equal('abc line', getline(1)) + + vmap <buffer> <F2> :<C-U>let v=100<CR> + call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') + call assert_equal('foo line', getline(1)) + + " reselect the select mode using gv from a visual mode map + vmap <buffer> <F2> gv + set selectmode=cmd + call feedkeys("0gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + set selectmode& + + close! +endfunc + " Test for selecting a register with CTRL-R func Test_selectmode_register() new diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 7744c5bcca..8ab8204b10 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -474,6 +474,16 @@ func Test_spellsuggest_option_expr() bwipe! endfunc +func Test_spellsuggest_timeout() + set spellsuggest=timeout:30 + set spellsuggest=timeout:-123 + set spellsuggest=timeout:999999 + call assert_fails('set spellsuggest=timeout', 'E474:') + call assert_fails('set spellsuggest=timeout:x', 'E474:') + call assert_fails('set spellsuggest=timeout:-x', 'E474:') + call assert_fails('set spellsuggest=timeout:--9', 'E474:') +endfunc + func Test_spellinfo() throw 'skipped: Nvim does not support enc=latin1' new diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim index b028e9d969..dbffbafed9 100644 --- a/src/nvim/testdir/test_spellfile.vim +++ b/src/nvim/testdir/test_spellfile.vim @@ -25,6 +25,18 @@ func Test_spell_normal() let cnt=readfile('./Xspellfile.add') call assert_equal('goood', cnt[0]) + " zg should fail in operator-pending mode + call assert_beeps('norm! czg') + + " zg fails in visual mode when not able to get the visual text + call assert_beeps('norm! ggVjzg') + norm! V + + " zg fails for a non-identifier word + call append(line('$'), '###') + call assert_fails('norm! Gzg', 'E349:') + $d + " Test for zw 2 norm! $zw @@ -907,6 +919,33 @@ func Test_spellfile_COMMON() call delete('XtestCOMMON-utf8.spl') endfunc +" Test NOSUGGEST (see :help spell-COMMON) +func Test_spellfile_NOSUGGEST() + call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic') + call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff') + + mkspell! XtestNOSUGGEST-utf8.spl XtestNOSUGGEST + set spell spelllang=XtestNOSUGGEST-utf8.spl + + for goodword in ['foo', 'Foo', 'FOO', 'fog', 'Fog', 'FOG'] + call assert_equal(['', ''], spellbadword(goodword), goodword) + endfor + for badword in ['foO', 'fOO', 'fooo', 'foog', 'foofog', 'fogfoo'] + call assert_equal([badword, 'bad'], spellbadword(badword)) + endfor + + call assert_equal(['fog'], spellsuggest('fooo', 1)) + call assert_equal(['fog'], spellsuggest('fOo', 1)) + call assert_equal(['fog'], spellsuggest('foG', 1)) + call assert_equal(['fog'], spellsuggest('fogg', 1)) + + set spell& spelllang& + call delete('XtestNOSUGGEST.dic') + call delete('XtestNOSUGGEST.aff') + call delete('XtestNOSUGGEST-utf8.spl') +endfunc + + " Test CIRCUMFIX (see: :help spell-CIRCUMFIX) func Test_spellfile_CIRCUMFIX() " Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index f795d1c0cf..b3a80072d9 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -858,6 +858,44 @@ func Test_substitute_skipped_range() bwipe! endfunc +" Test using the 'gdefault' option (when on, flag 'g' is default on). +func Test_substitute_gdefault() + new + + " First check without 'gdefault' + call setline(1, 'foo bar foo') + s/foo/FOO/ + call assert_equal('FOO bar foo', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/g + call assert_equal('FOO bar FOO', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/gg + call assert_equal('FOO bar foo', getline(1)) + + " Then check with 'gdefault' + set gdefault + call setline(1, 'foo bar foo') + s/foo/FOO/ + call assert_equal('FOO bar FOO', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/g + call assert_equal('FOO bar foo', getline(1)) + call setline(1, 'foo bar foo') + s/foo/FOO/gg + call assert_equal('FOO bar FOO', getline(1)) + + " Setting 'compatible' should reset 'gdefault' + call assert_equal(1, &gdefault) + " set compatible + set nogdefault + call assert_equal(0, &gdefault) + set nocompatible + call assert_equal(0, &gdefault) + + bw! +endfunc + " This was using "old_sub" after it was freed. func Test_using_old_sub() " set compatible maxfuncdepth=10 diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 07f35ddb4f..6d468ec9de 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -670,15 +670,19 @@ func Test_tabline_tabmenu() call assert_equal(3, tabpagenr('$')) " go to tab page 2 in operator-pending mode (should beep) - call assert_beeps('call feedkeys("f" .. TabLineSelectPageCode(2), "Lx!")') + call assert_beeps('call feedkeys("c" .. TabLineSelectPageCode(2), "Lx!")') + call assert_equal(2, tabpagenr()) + call assert_equal(3, tabpagenr('$')) " open new tab page before tab page 1 in operator-pending mode (should beep) - call assert_beeps('call feedkeys("f" .. TabMenuNewItemCode(1), "Lx!")') + call assert_beeps('call feedkeys("c" .. TabMenuNewItemCode(1), "Lx!")') + call assert_equal(1, tabpagenr()) + call assert_equal(4, tabpagenr('$')) " open new tab page after tab page 3 in normal mode call feedkeys(TabMenuNewItemCode(4), "Lx!") call assert_equal(4, tabpagenr()) - call assert_equal(4, tabpagenr('$')) + call assert_equal(5, tabpagenr('$')) " go to tab page 2 in insert mode call feedkeys("i" .. TabLineSelectPageCode(2) .. "\<C-C>", "Lx!") @@ -686,17 +690,17 @@ func Test_tabline_tabmenu() " close tab page 2 in insert mode call feedkeys("i" .. TabMenuCloseItemCode(2) .. "\<C-C>", "Lx!") - call assert_equal(3, tabpagenr('$')) + call assert_equal(4, tabpagenr('$')) " open new tab page before tab page 3 in insert mode call feedkeys("i" .. TabMenuNewItemCode(3) .. "\<C-C>", "Lx!") call assert_equal(3, tabpagenr()) - call assert_equal(4, tabpagenr('$')) + call assert_equal(5, tabpagenr('$')) " open new tab page after tab page 4 in insert mode call feedkeys("i" .. TabMenuNewItemCode(5) .. "\<C-C>", "Lx!") call assert_equal(5, tabpagenr()) - call assert_equal(5, tabpagenr('$')) + call assert_equal(6, tabpagenr('$')) %bw! endfunc diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 1dd656ece5..04c0218f74 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -1133,7 +1133,7 @@ endfunc " Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands func Test_inc_search() new - call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo']) + call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '===']) call cursor(3, 1) " Test for [i and ]i @@ -1143,6 +1143,9 @@ func Test_inc_search() call assert_equal('3:foo', execute('normal ]i')) call assert_equal('4:foo', execute('normal 2]i')) call assert_fails('normal 3]i', 'E389:') + call assert_fails('normal G]i', 'E349:') + call assert_fails('normal [i', 'E349:') + call cursor(3, 1) " Test for :isearch call assert_equal('1:foo', execute('isearch foo')) @@ -1163,6 +1166,9 @@ func Test_inc_search() call assert_equal([ \ ' 1: 4 3:foo', \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n")) + call assert_fails('normal G]I', 'E349:') + call assert_fails('normal [I', 'E349:') + call cursor(3, 1) " Test for :ilist call assert_equal([ @@ -1188,6 +1194,9 @@ func Test_inc_search() exe "normal k2]\t" call assert_equal([5, 3], [line('.'), col('.')]) call assert_fails("normal 2k3]\t", 'E389:') + call assert_fails("normal G[\t", 'E349:') + call assert_fails("normal ]\t", 'E349:') + call cursor(3, 1) " Test for :ijump call cursor(3, 1) @@ -1212,6 +1221,8 @@ func Test_inc_search() close call assert_fails('3wincmd i', 'E387:') call assert_fails('6wincmd i', 'E389:') + call assert_fails("normal G\<C-W>i", 'E349:') + call cursor(3, 1) " Test for :isplit isplit foo diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index eeb2946a8b..f21d6fcb99 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -1,7 +1,6 @@ " Test for textobjects source check.vim -CheckFeature textobjects func CpoM(line, useM, expected) new diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index a9ec405aa4..eb47af08d7 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -336,7 +336,7 @@ func Test_undofile_earlier() " create undofile with timestamps older than Vim startup time. let t0 = localtime() - 43200 call test_settime(t0) - new Xfile + new XfileEarlier call feedkeys("ione\<Esc>", 'xt') set ul=100 call test_settime(t0 + 1) @@ -350,12 +350,12 @@ func Test_undofile_earlier() bwipe! " restore normal timestamps. call test_settime(0) - new Xfile + new XfileEarlier rundo Xundofile earlier 1d call assert_equal('', getline(1)) bwipe! - call delete('Xfile') + call delete('XfileEarlier') call delete('Xundofile') endfunc diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index 9b010a5dbc..ab3503c282 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -140,6 +140,51 @@ func Test_list2str_str2list_latin1() call assert_equal(s, sres) endfunc +func Test_setcellwidths() + call setcellwidths([ + \ [0x1330, 0x1330, 2], + \ [9999, 10000, 1], + \ [0x1337, 0x1339, 2], + \]) + + call assert_equal(2, strwidth("\u1330")) + call assert_equal(1, strwidth("\u1336")) + call assert_equal(2, strwidth("\u1337")) + call assert_equal(2, strwidth("\u1339")) + call assert_equal(1, strwidth("\u133a")) + + call setcellwidths([]) + + call assert_fails('call setcellwidths(1)', 'E714:') + + call assert_fails('call setcellwidths([1, 2, 0])', 'E1109:') + + call assert_fails('call setcellwidths([[0x101]])', 'E1110:') + call assert_fails('call setcellwidths([[0x101, 0x102]])', 'E1110:') + call assert_fails('call setcellwidths([[0x101, 0x102, 1, 4]])', 'E1110:') + call assert_fails('call setcellwidths([["a"]])', 'E1110:') + + call assert_fails('call setcellwidths([[0x102, 0x101, 1]])', 'E1111:') + + call assert_fails('call setcellwidths([[0x101, 0x102, 0]])', 'E1112:') + call assert_fails('call setcellwidths([[0x101, 0x102, 3]])', 'E1112:') + + call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x115, 0x116, 2]])', 'E1113:') + call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x122, 0x123, 2]])', 'E1113:') + + call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:') + + set listchars=tab:--\\u2192 + call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:') + + set fillchars=stl:\\u2501 + call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:') + + set listchars& + set fillchars& + call setcellwidths([]) +endfunc + func Test_print_overlong() " Text with more composing characters than MB_MAXBYTES. new diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index de4629451b..0f204cdd0c 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1165,10 +1165,10 @@ func Test_type() " call assert_equal(0, 0 + v:none) call assert_equal(0, 0 + v:null) - call assert_equal('false', '' . v:false) - call assert_equal('true', '' . v:true) - " call assert_equal('none', '' . v:none) - call assert_equal('null', '' . v:null) + call assert_equal('v:false', '' . v:false) + call assert_equal('v:true', '' . v:true) + " call assert_equal('v:none', '' . v:none) + call assert_equal('v:null', '' . v:null) call assert_true(v:false == 0) call assert_false(v:false != 0) @@ -1573,6 +1573,23 @@ func Test_script_local_func() enew! | close endfunc +func Test_script_expand_sfile() + let lines =<< trim END + func s:snr() + return expand('<sfile>') + endfunc + let g:result = s:snr() + END + call writefile(lines, 'Xexpand') + source Xexpand + call assert_match('<SNR>\d\+_snr', g:result) + source Xexpand + call assert_match('<SNR>\d\+_snr', g:result) + + call delete('Xexpand') + unlet g:result +endfunc + func Test_compound_assignment_operators() " Test for number let x = 1 diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index f9ac0e0884..9c1ad0c099 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -378,14 +378,17 @@ endfunc func Test_Visual_paragraph_textobject() new - call setline(1, ['First line.', - \ '', - \ 'Second line.', - \ 'Third line.', - \ 'Fourth line.', - \ 'Fifth line.', - \ '', - \ 'Sixth line.']) + let lines =<< trim [END] + First line. + + Second line. + Third line. + Fourth line. + Fifth line. + + Sixth line. + [END] + call setline(1, lines) " When start and end of visual area are identical, 'ap' or 'ip' select " the whole paragraph. @@ -639,6 +642,14 @@ func Test_characterwise_visual_mode() normal Gkvj$d call assert_equal(['', 'a', ''], getline(1, '$')) + " characterwise visual mode: use a count with the visual mode from the last + " line in the buffer + %d _ + call setline(1, ['one', 'two', 'three', 'four']) + norm! vj$y + norm! G1vy + call assert_equal('four', @") + " characterwise visual mode: replace a single character line and the eol %d _ call setline(1, "a") @@ -653,92 +664,6 @@ func Test_characterwise_visual_mode() bwipe! endfunc -func Test_characterwise_select_mode() - new - - " Select mode maps - snoremap <lt>End> <End> - snoremap <lt>Down> <Down> - snoremap <lt>Del> <Del> - - " characterwise select mode: delete middle line - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Gkkgh\<End>\<Del>" - call assert_equal(['', 'b', 'c'], getline(1, '$')) - - " characterwise select mode: delete middle two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Gkkgh\<Down>\<End>\<Del>" - call assert_equal(['', 'c'], getline(1, '$')) - - " characterwise select mode: delete last line - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Ggh\<End>\<Del>" - call assert_equal(['', 'a', 'b', ''], getline(1, '$')) - - " characterwise select mode: delete last two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal Gkgh\<Down>\<End>\<Del>" - call assert_equal(['', 'a', ''], getline(1, '$')) - - " CTRL-H in select mode behaves like 'x' - call setline(1, 'abcdef') - exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>" - call assert_equal('ef', getline(1)) - - " CTRL-O in select mode switches to visual mode for one command - call setline(1, 'abcdef') - exe "normal! gggh\<C-O>3lm" - call assert_equal('mef', getline(1)) - - sunmap <lt>End> - sunmap <lt>Down> - sunmap <lt>Del> - bwipe! -endfunc - -func Test_linewise_select_mode() - new - - " linewise select mode: delete middle line - call append('$', ['a', 'b', 'c']) - exe "normal GkkgH\<Del>" - call assert_equal(['', 'b', 'c'], getline(1, '$')) - - " linewise select mode: delete middle two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal GkkgH\<Down>\<Del>" - call assert_equal(['', 'c'], getline(1, '$')) - - " linewise select mode: delete last line - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal GgH\<Del>" - call assert_equal(['', 'a', 'b'], getline(1, '$')) - - " linewise select mode: delete last two lines - call deletebufline('', 1, '$') - call append('$', ['a', 'b', 'c']) - exe "normal GkgH\<Down>\<Del>" - call assert_equal(['', 'a'], getline(1, '$')) - - bwipe! -endfunc - -" Test for blockwise select mode (g CTRL-H) -func Test_blockwise_select_mode() - new - call setline(1, ['foo', 'bar']) - call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') - call assert_equal(['mmo', 'mmr'], getline(1, '$')) - close! -endfunc - func Test_visual_mode_put() new @@ -778,16 +703,16 @@ func Test_visual_mode_put() bwipe! endfunc -func Test_select_mode_gv() +func Test_gv_with_exclusive_selection() new - " gv in exclusive select mode after operation + " gv with exclusive selection after an operation call append('$', ['zzz ', 'äà ']) set selection=exclusive normal Gkv3lyjv3lpgvcxxx call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$')) - " gv in exclusive select mode without operation + " gv with exclusive selection without an operation call deletebufline('', 1, '$') call append('$', 'zzz ') set selection=exclusive @@ -1136,32 +1061,6 @@ func Test_star_register() close! endfunc -" Test for using visual mode maps in select mode -func Test_select_mode_map() - new - vmap <buffer> <F2> 3l - call setline(1, 'Test line') - call feedkeys("gh\<F2>map", 'xt') - call assert_equal('map line', getline(1)) - - vmap <buffer> <F2> ygV - call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') - call assert_equal('abc line', getline(1)) - - vmap <buffer> <F2> :<C-U>let v=100<CR> - call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') - call assert_equal('foo line', getline(1)) - - " reselect the select mode using gv from a visual mode map - vmap <buffer> <F2> gv - set selectmode=cmd - call feedkeys("0gh\<F2>map", 'xt') - call assert_equal('map line', getline(1)) - set selectmode& - - close! -endfunc - " Test for changing text in visual mode with 'exclusive' selection func Test_exclusive_selection() new @@ -1178,15 +1077,38 @@ func Test_exclusive_selection() close! endfunc -" Test for starting visual mode with a count. -" This test should be run without any previous visual modes. So this should be -" run as a first test. -func Test_AAA_start_visual_mode_with_count() - new - call setline(1, ['aaaaaaa', 'aaaaaaa', 'aaaaaaa', 'aaaaaaa']) - normal! gg2Vy - call assert_equal("aaaaaaa\naaaaaaa\n", @") - close! +" Test for starting linewise visual with a count. +" This test needs to be run without any previous visual mode. Otherwise the +" count will use the count from the previous visual mode. +func Test_linewise_visual_with_count() + let after =<< trim [CODE] + call setline(1, ['one', 'two', 'three', 'four']) + norm! 3Vy + call assert_equal("one\ntwo\nthree\n", @") + call writefile(v:errors, 'Xtestout') + qall! + [CODE] + if RunVim([], after, '') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif +endfunc + +" Test for starting characterwise visual with a count. +" This test needs to be run without any previous visual mode. Otherwise the +" count will use the count from the previous visual mode. +func Test_characterwise_visual_with_count() + let after =<< trim [CODE] + call setline(1, ['one two', 'three']) + norm! l5vy + call assert_equal("ne tw", @") + call writefile(v:errors, 'Xtestout') + qall! + [CODE] + if RunVim([], after, '') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif endfunc " Test for visually selecting an inner block (iB) @@ -1550,5 +1472,25 @@ func Test_visual_area_adjusted_when_hiding() bwipe! endfunc +func Test_switch_buffer_ends_visual_mode() + enew + call setline(1, 'foo') + set hidden + set virtualedit=all + let buf1 = bufnr() + enew + let buf2 = bufnr() + call setline(1, ['', '', '', '']) + call cursor(4, 5) + call feedkeys("\<C-V>3k4h", 'xt') + exe 'buffer' buf1 + call assert_equal('n', mode()) + + set nohidden + set virtualedit= + bwipe! + exe 'bwipe!' buf2 +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index bfbba1f793..a8735bcaf1 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -128,6 +128,25 @@ func Test_nowrite_quit_split() bwipe Xfile endfunc +func Test_writefile_sync_arg() + " This doesn't check if fsync() works, only that the argument is accepted. + call writefile(['one'], 'Xtest', 's') + call writefile(['two'], 'Xtest', 'S') + call delete('Xtest') +endfunc + +func Test_writefile_sync_dev_stdout() + if !has('unix') + return + endif + if filewritable('/dev/stdout') + " Just check that this doesn't cause an error. + call writefile(['one'], '/dev/stdout', 's') + else + throw 'Skipped: /dev/stdout is not writable' + endif +endfunc + func Test_writefile_autowrite() set autowrite new @@ -237,29 +256,18 @@ func Test_write_errors() call delete('Xfile') endfunc -func Test_writefile_sync_dev_stdout() - if !has('unix') - return - endif - if filewritable('/dev/stdout') - " Just check that this doesn't cause an error. - call writefile(['one'], '/dev/stdout', 's') - else - throw 'Skipped: /dev/stdout is not writable' - endif -endfunc - -func Test_writefile_sync_arg() - " This doesn't check if fsync() works, only that the argument is accepted. - call writefile(['one'], 'Xtest', 's') - call writefile(['two'], 'Xtest', 'S') - call delete('Xtest') +" Test for writing a file using invalid file encoding +func Test_write_invalid_encoding() + new + call setline(1, 'abc') + call assert_fails('write ++enc=axbyc Xfile', 'E213:') + close! endfunc " Tests for reading and writing files with conversion for Win32. func Test_write_file_encoding() - CheckMSWindows throw 'skipped: Nvim does not support :w ++enc=cp1251' + CheckMSWindows let save_encoding = &encoding let save_fileencodings = &fileencodings set encoding& fileencodings& diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 69b687e44f..e70e9f2cbd 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -7,6 +7,7 @@ #include "nvim/eval/encode.h" #include "nvim/ex_docmd.h" #include "nvim/os/os.h" +#include "nvim/runtime.h" #include "nvim/testing.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -17,21 +18,23 @@ static void prepare_assert_error(garray_T *gap) { char buf[NUMBUFLEN]; + char *sname = estack_sfile(ESTACK_NONE); ga_init(gap, 1, 100); - if (sourcing_name != NULL) { - ga_concat(gap, (char *)sourcing_name); - if (sourcing_lnum > 0) { + if (sname != NULL) { + ga_concat(gap, sname); + if (SOURCING_LNUM > 0) { ga_concat(gap, " "); } } - if (sourcing_lnum > 0) { - vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); + if (SOURCING_LNUM > 0) { + vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)SOURCING_LNUM); ga_concat(gap, buf); } - if (sourcing_name != NULL || sourcing_lnum > 0) { + if (sname != NULL || SOURCING_LNUM > 0) { ga_concat(gap, ": "); } + xfree(sname); } /// Append "p[clen]" to "gap", escaping unprintable characters. diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e2289eb9ce..38e8c15762 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -171,7 +171,7 @@ UI *tui_start(void) ui->option_set = tui_option_set; ui->raw_line = tui_raw_line; - memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + CLEAR_FIELD(ui->ui_ext); ui->ui_ext[kUILinegrid] = true; ui->ui_ext[kUITermColors] = true; @@ -875,6 +875,53 @@ safe_move: ugrid_goto(grid, row, col); } +static void print_spaces(UI *ui, int width) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + out(ui, data->space_buf, (size_t)width); + grid->col += width; + if (data->immediate_wrap_after_last_column) { + // Printing at the right margin immediately advances the cursor. + final_column_wrap(ui); + } +} + +/// Move cursor to the position given by `row` and `col` and print the character in `cell`. +/// This allows the grid and the host terminal to assume different widths of ambiguous-width chars. +/// +/// @param is_doublewidth whether the character is double-width on the grid. +/// If true and the character is ambiguous-width, clear two cells. +static void print_cell_at_pos(UI *ui, int row, int col, UCell *cell, bool is_doublewidth) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + if (grid->row == -1 && cell->data[0] == NUL) { + // If cursor needs to repositioned and there is nothing to print, don't move cursor. + return; + } + + cursor_goto(ui, row, col); + + bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data)); + 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. + update_attrs(ui, cell->attr); + print_spaces(ui, 2); + cursor_goto(ui, row, col); + } + + print_cell(ui, cell); + + if (is_ambiwidth) { + // Force repositioning cursor after printing an ambiguous-width character. + grid->row = -1; + } +} + static void clear_region(UI *ui, int top, int bot, int left, int right, int attr_id) { TUIData *data = ui->data; @@ -888,7 +935,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right, int attr && left == 0 && right == ui->width && bot == ui->height) { if (top == 0) { unibi_out(ui, unibi_clear_screen); - ugrid_goto(&data->grid, top, left); + ugrid_goto(grid, top, left); } else { cursor_goto(ui, top, 0); unibi_out(ui, unibi_clr_eos); @@ -905,12 +952,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right, int attr UNIBI_SET_NUM_VAR(data->params[0], width); unibi_out(ui, unibi_erase_chars); } else { - out(ui, data->space_buf, (size_t)width); - grid->col += width; - if (data->immediate_wrap_after_last_column) { - // Printing at the right margin immediately advances the cursor. - final_column_wrap(ui); - } + print_spaces(ui, width); } } } @@ -1302,8 +1344,8 @@ static void tui_flush(UI *ui) } UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { - cursor_goto(ui, row, curcol); - print_cell(ui, cell); + print_cell_at_pos(ui, row, curcol, cell, + curcol < clear_col - 1 && (cell + 1)->data[0] == NUL); }); if (clear_col < r.right) { clear_region(ui, row, row + 1, clear_col, r.right, clear_attr); @@ -1439,8 +1481,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { - cursor_goto(ui, (int)linerow, curcol); - print_cell(ui, cell); + print_cell_at_pos(ui, (int)linerow, curcol, cell, + curcol < endcol - 1 && (cell + 1)->data[0] == NUL); }); if (clearcol > endcol) { @@ -1458,8 +1500,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I 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; - cursor_goto(ui, (int)linerow, grid->width - size); - print_cell(ui, &grid->cells[linerow][grid->width - size]); + print_cell_at_pos(ui, (int)linerow, grid->width - size, + &grid->cells[linerow][grid->width - size], size == 2); } // Wrap the cursor over to the next line. The next line will be diff --git a/src/nvim/types.h b/src/nvim/types.h index 00b9e6fc09..477102276c 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -28,6 +28,8 @@ typedef handle_T NS; typedef struct expand expand_T; +typedef uint64_t proftime_T; + typedef enum { kNone = -1, kFalse = 0, diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 4fcfee1192..da671a3ad1 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -13,11 +13,12 @@ #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/event/loop.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/log.h" #include "nvim/main.h" @@ -31,8 +32,7 @@ #include "nvim/os/signal.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -663,6 +663,6 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) // non-positive indicates no request wp->w_height_request = MAX(height, 0); wp->w_width_request = MAX(width, 0); - win_set_inner_size(wp); + win_set_inner_size(wp, true); } } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 5df70d0d8e..2216e25db9 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -13,6 +13,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" @@ -22,8 +23,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/os.h" -#include "nvim/popupmnu.h" -#include "nvim/screen.h" +#include "nvim/popupmenu.h" #include "nvim/ugrid.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 45c083b034..75a09b244c 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1,73 +1,71 @@ // 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 -/* - * undo.c: multi level undo facility - * - * The saved lines are stored in a list of lists (one for each buffer): - * - * b_u_oldhead------------------------------------------------+ - * | - * V - * +--------------+ +--------------+ +--------------+ - * b_u_newhead--->| u_header | | u_header | | u_header | - * | uh_next------>| uh_next------>| uh_next---->NULL - * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | - * | uh_entry | | uh_entry | | uh_entry | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ +--------------+ +--------------+ - * | u_entry | | u_entry | | u_entry | - * | ue_next | | ue_next | | ue_next | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ NULL NULL - * | u_entry | - * | ue_next | - * +--------|-----+ - * | - * V - * etc. - * - * Each u_entry list contains the information for one undo or redo. - * curbuf->b_u_curhead points to the header of the last undo (the next redo), - * or is NULL if nothing has been undone (end of the branch). - * - * For keeping alternate undo/redo branches the uh_alt field is used. Thus at - * each point in the list a branch may appear for an alternate to redo. The - * uh_seq field is numbered sequentially to be able to find a newer or older - * branch. - * - * +---------------+ +---------------+ - * b_u_oldhead --->| u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next ----> NULL - * NULL <----- uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next | | uh_alt_next | - * b_u_newhead --->| uh_alt_prev | | uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * NULL +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next | - * | uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * etc. etc. - * - * - * All data is allocated and will all be freed when the buffer is unloaded. - */ +// undo.c: multi level undo facility + +// The saved lines are stored in a list of lists (one for each buffer): +// +// b_u_oldhead------------------------------------------------+ +// | +// V +// +--------------+ +--------------+ +--------------+ +// b_u_newhead--->| u_header | | u_header | | u_header | +// | uh_next------>| uh_next------>| uh_next---->NULL +// NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | +// | uh_entry | | uh_entry | | uh_entry | +// +--------|-----+ +--------|-----+ +--------|-----+ +// | | | +// V V V +// +--------------+ +--------------+ +--------------+ +// | u_entry | | u_entry | | u_entry | +// | ue_next | | ue_next | | ue_next | +// +--------|-----+ +--------|-----+ +--------|-----+ +// | | | +// V V V +// +--------------+ NULL NULL +// | u_entry | +// | ue_next | +// +--------|-----+ +// | +// V +// etc. +// +// Each u_entry list contains the information for one undo or redo. +// curbuf->b_u_curhead points to the header of the last undo (the next redo), +// or is NULL if nothing has been undone (end of the branch). +// +// For keeping alternate undo/redo branches the uh_alt field is used. Thus at +// each point in the list a branch may appear for an alternate to redo. The +// uh_seq field is numbered sequentially to be able to find a newer or older +// branch. +// +// +---------------+ +---------------+ +// b_u_oldhead --->| u_header | | u_header | +// | uh_alt_next ---->| uh_alt_next ----> NULL +// NULL <----- uh_alt_prev |<------ uh_alt_prev | +// | uh_prev | | uh_prev | +// +-----|---------+ +-----|---------+ +// | | +// V V +// +---------------+ +---------------+ +// | u_header | | u_header | +// | uh_alt_next | | uh_alt_next | +// b_u_newhead --->| uh_alt_prev | | uh_alt_prev | +// | uh_prev | | uh_prev | +// +-----|---------+ +-----|---------+ +// | | +// V V +// NULL +---------------+ +---------------+ +// | u_header | | u_header | +// | uh_alt_next ---->| uh_alt_next | +// | uh_alt_prev |<------ uh_alt_prev | +// | uh_prev | | uh_prev | +// +-----|---------+ +-----|---------+ +// | | +// etc. etc. +// +// +// All data is allocated and will all be freed when the buffer is unloaded. // Uncomment the next line for including the u_check() function. This warns // for errors in the debug information. @@ -88,6 +86,7 @@ #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" @@ -113,6 +112,12 @@ #include "nvim/types.h" #include "nvim/undo.h" +/// Structure passed around between undofile functions. +typedef struct { + buf_T *bi_buf; + FILE *bi_fp; +} bufinfo_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.c.generated.h" #endif @@ -120,31 +125,25 @@ // used in undo_end() to report number of added and deleted lines static long u_newcount, u_oldcount; -/* - * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember - * the action that "u" should do. - */ +// When 'u' flag included in 'cpoptions', we behave like vi. Need to remember +// the action that "u" should do. static bool undo_undoes = false; static int lastmark = 0; #if defined(U_DEBUG) -/* - * Check the undo structures for being valid. Print a warning when something - * looks wrong. - */ +// Check the undo structures for being valid. Print a warning when something +// looks wrong. static int seen_b_u_curhead; static int seen_b_u_newhead; static int header_count; static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *exp_uh_alt_prev) { - u_entry_T *uep; - if (uhp == NULL) { return; } - ++header_count; + header_count++; if (uhp == curbuf->b_u_curhead && ++seen_b_u_curhead > 1) { emsg("b_u_curhead found twice (looping?)"); return; @@ -170,7 +169,7 @@ static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *e } // Check the undo tree at this header. - for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { + for (u_entry_T *uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { if (uep->ue_magic != UE_MAGIC) { emsg("ue_magic wrong (may be using freed memory)"); break; @@ -209,11 +208,9 @@ static void u_check(int newhead_may_be_NULL) #endif -/* - * Save the current line for both the "u" and "U" command. - * Careful: may trigger autocommands that reload the buffer. - * Returns OK or FAIL. - */ +/// Save the current line for both the "u" and "U" command. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns OK or FAIL. int u_save_cursor(void) { linenr_T cur = curwin->w_cursor.lnum; @@ -223,12 +220,10 @@ int u_save_cursor(void) return u_save(top, bot); } -/* - * Save the lines between "top" and "bot" for both the "u" and "U" command. - * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Save the lines between "top" and "bot" for both the "u" and "U" command. +/// "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_save(linenr_T top, linenr_T bot) { if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) { @@ -242,35 +237,29 @@ int u_save(linenr_T top, linenr_T bot) return u_savecommon(curbuf, top, bot, (linenr_T)0, false); } -/* - * Save the line "lnum" (used by ":s" and "~" command). - * The line is replaced, so the new bottom line is lnum + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Save the line "lnum" (used by ":s" and "~" command). +/// The line is replaced, so the new bottom line is lnum + 1. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_savesub(linenr_T lnum) { return u_savecommon(curbuf, lnum - 1, lnum + 1, lnum + 1, false); } -/* - * A new line is inserted before line "lnum" (used by :s command). - * The line is inserted, so the new bottom line is lnum + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// A new line is inserted before line "lnum" (used by :s command). +/// The line is inserted, so the new bottom line is lnum + 1. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_inssub(linenr_T lnum) { return u_savecommon(curbuf, lnum - 1, lnum, lnum + 1, false); } -/* - * Save the lines "lnum" - "lnum" + nlines (used by delete command). - * The lines are deleted, so the new bottom line is lnum, unless the buffer - * becomes empty. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Save the lines "lnum" - "lnum" + nlines (used by delete command). +/// The lines are deleted, so the new bottom line is lnum, unless the buffer +/// becomes empty. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_savedel(linenr_T lnum, long nlines) { return u_savecommon(curbuf, lnum - 1, lnum + (linenr_T)nlines, @@ -322,25 +311,15 @@ static inline void zero_fmark_additional_data(fmark_T *fmarks) } } -/* - * Common code for various ways to save text before a change. - * "top" is the line above the first changed line. - * "bot" is the line below the last changed line. - * "newbot" is the new bottom line. Use zero when not known. - * "reload" is TRUE when saving for a buffer reload. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ +/// Common code for various ways to save text before a change. +/// "top" is the line above the first changed line. +/// "bot" is the line below the last changed line. +/// "newbot" is the new bottom line. Use zero when not known. +/// "reload" is true when saving for a buffer reload. +/// Careful: may trigger autocommands that reload the buffer. +/// Returns FAIL when lines could not be saved, OK otherwise. int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int reload) { - linenr_T lnum; - long i; - u_header_T *uhp; - u_header_T *old_curhead; - u_entry_T *uep; - u_entry_T *prev_uep; - long size; - if (!reload) { // When making changes is not allowed return FAIL. It's a crude way // to make all change commands fail. @@ -365,16 +344,19 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re } #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif - size = bot - top - 1; + u_entry_T *uep; + u_entry_T *prev_uep; + long size = bot - top - 1; // If curbuf->b_u_synced == true make a new header. if (buf->b_u_synced) { // Need to create new entry in b_changelist. buf->b_new_change = true; + u_header_T *uhp; if (get_undolevel(buf) >= 0) { // Make a new header entry. Do this first so that we don't mess // up the undo info when out of memory. @@ -387,19 +369,15 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re uhp = NULL; } - /* - * If we undid more than we redid, move the entry lists before and - * including curbuf->b_u_curhead to an alternate branch. - */ - old_curhead = buf->b_u_curhead; + // If we undid more than we redid, move the entry lists before and + // including curbuf->b_u_curhead to an alternate branch. + u_header_T *old_curhead = buf->b_u_curhead; if (old_curhead != NULL) { buf->b_u_newhead = old_curhead->uh_next.ptr; buf->b_u_curhead = NULL; } - /* - * free headers to keep the size right - */ + // free headers to keep the size right while (buf->b_u_numhead > get_undolevel(buf) && buf->b_u_oldhead != NULL) { u_header_T *uhfree = buf->b_u_oldhead; @@ -418,7 +396,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re u_freebranch(buf, uhfree, &old_curhead); } #ifdef U_DEBUG - u_check(TRUE); + u_check(true); #endif } @@ -490,19 +468,17 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re return OK; } - /* - * When saving a single line, and it has been saved just before, it - * doesn't make sense saving it again. Saves a lot of memory when - * making lots of changes inside the same line. - * This is only possible if the previous change didn't increase or - * decrease the number of lines. - * Check the ten last changes. More doesn't make sense and takes too - * long. - */ + // When saving a single line, and it has been saved just before, it + // doesn't make sense saving it again. Saves a lot of memory when + // making lots of changes inside the same line. + // This is only possible if the previous change didn't increase or + // decrease the number of lines. + // Check the ten last changes. More doesn't make sense and takes too + // long. if (size == 1) { uep = u_get_headentry(buf); prev_uep = NULL; - for (i = 0; i < 10; ++i) { + for (long i = 0; i < 10; i++) { if (uep == NULL) { break; } @@ -514,7 +490,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re != (uep->ue_bot == 0 ? buf->b_ml.ml_line_count + 1 : uep->ue_bot)) - : uep->ue_lcount != buf->b_ml.ml_line_count) + : uep->ue_lcount != buf->b_ml.ml_line_count) || (uep->ue_size > 1 && top >= uep->ue_top && top + 2 <= uep->ue_top + uep->ue_size + 1)) { @@ -561,11 +537,9 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re u_getbot(buf); } - /* - * add lines in front of entry list - */ + // add lines in front of entry list uep = xmalloc(sizeof(u_entry_T)); - memset(uep, 0, sizeof(u_entry_T)); + CLEAR_POINTER(uep); #ifdef U_DEBUG uep->ue_magic = UE_MAGIC; #endif @@ -585,7 +559,9 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re if (size > 0) { uep->ue_array = xmalloc(sizeof(char_u *) * (size_t)size); - for (i = 0, lnum = top + 1; i < size; ++i) { + linenr_T lnum; + long i; + for (i = 0, lnum = top + 1; i < size; i++) { fast_breakcheck(); if (got_int) { u_freeentry(uep, i); @@ -607,7 +583,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re undo_undoes = false; #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif return OK; } @@ -643,12 +619,9 @@ static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); void u_compute_hash(buf_T *buf, char_u *hash) { context_sha256_T ctx; - linenr_T lnum; - char_u *p; - sha256_start(&ctx); - for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - p = ml_get_buf(buf, lnum, false); + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + char_u *p = ml_get_buf(buf, lnum, false); sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1)); } sha256_finish(&ctx, hash); @@ -668,21 +641,14 @@ void u_compute_hash(buf_T *buf, char_u *hash) char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) FUNC_ATTR_WARN_UNUSED_RESULT { - char *dirp; - char dir_name[MAXPATHL + 1]; - char *munged_name = NULL; - char *undo_file_name = NULL; const char *ffname = buf_ffname; -#ifdef HAVE_READLINK - char fname_buf[MAXPATHL]; -#endif - char *p; if (ffname == NULL) { return NULL; } #ifdef HAVE_READLINK + char fname_buf[MAXPATHL]; // Expand symlink in the file name, so that we put the undo file with the // actual file instead of with the symlink. if (resolve_symlink((const char_u *)ffname, (char_u *)fname_buf) == OK) { @@ -690,9 +656,13 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) } #endif + char dir_name[MAXPATHL + 1]; + char *munged_name = NULL; + char *undo_file_name = NULL; + // Loop over 'undodir'. When reading find the first file that exists. // When not reading use the first directory that exists or ".". - dirp = (char *)p_udir; + char *dirp = (char *)p_udir; while (*dirp != NUL) { size_t dir_len = copy_option_part(&dirp, dir_name, MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { @@ -710,7 +680,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) dir_name[dir_len] = NUL; // Remove trailing pathseps from directory name - p = &dir_name[dir_len - 1]; + char *p = &dir_name[dir_len - 1]; while (vim_ispathsep(*p)) { *p-- = NUL; } @@ -731,9 +701,9 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) if (has_directory) { if (munged_name == NULL) { munged_name = xstrdup(ffname); - for (p = munged_name; *p != NUL; MB_PTR_ADV(p)) { - if (vim_ispathsep(*p)) { - *p = '%'; + for (char *c = munged_name; *c != NUL; MB_PTR_ADV(c)) { + if (vim_ispathsep(*c)) { + *c = '%'; } } } @@ -765,12 +735,9 @@ static void corruption_error(const char *const mesg, const char *const file_name static void u_free_uhp(u_header_T *uhp) { - u_entry_T *nuep; - u_entry_T *uep; - - uep = uhp->uh_entry; + u_entry_T *uep = uhp->uh_entry; while (uep != NULL) { - nuep = uep->ue_next; + u_entry_T *nuep = uep->ue_next; u_freeentry(uep, uep->ue_size); uep = nuep; } @@ -895,7 +862,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) static u_header_T *unserialize_uhp(bufinfo_T *bi, const char *file_name) { u_header_T *uhp = xmalloc(sizeof(u_header_T)); - memset(uhp, 0, sizeof(u_header_T)); + CLEAR_POINTER(uhp); #ifdef U_DEBUG uhp->uh_magic = UH_MAGIC; #endif @@ -1016,23 +983,21 @@ static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup) static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error, const char *filename) { - UndoObjectType type; uint8_t *buf = NULL; - size_t n_elems; ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject)); - type = (UndoObjectType)undo_read_4c(bi); + UndoObjectType type = (UndoObjectType)undo_read_4c(bi); extup->type = type; if (type == kExtmarkSplice) { - n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); + size_t n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); buf = xcalloc(n_elems, sizeof(uint8_t)); if (!undo_read(bi, buf, n_elems)) { goto error; } extup->data.splice = *(ExtmarkSplice *)buf; } else if (type == kExtmarkMove) { - n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); + size_t n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); buf = xcalloc(n_elems, sizeof(uint8_t)); if (!undo_read(bi, buf, n_elems)) { goto error; @@ -1083,7 +1048,7 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_name) { u_entry_T *uep = xmalloc(sizeof(u_entry_T)); - memset(uep, 0, sizeof(u_entry_T)); + CLEAR_POINTER(uep); #ifdef U_DEBUG uep->ue_magic = UE_MAGIC; #endif @@ -1173,17 +1138,12 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, char_u *const hash) FUNC_ATTR_NONNULL_ARG(3, 4) { - u_header_T *uhp; char *file_name; - int mark; #ifdef U_DEBUG int headers_written = 0; #endif - int fd; FILE *fp = NULL; - int perm; bool write_ok = false; - bufinfo_T bi; if (name == NULL) { file_name = u_get_undo_file_name(buf->b_ffname, false); @@ -1199,12 +1159,10 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, file_name = (char *)name; } - /* - * Decide about the permission to use for the undo file. If the buffer - * has a name use the permission of the original file. Otherwise only - * allow the user to access the undo file. - */ - perm = 0600; + // Decide about the permission to use for the undo file. If the buffer + // has a name use the permission of the original file. Otherwise only + // allow the user to access the undo file. + int perm = 0600; if (buf->b_ffname != NULL) { perm = os_getperm((const char *)buf->b_ffname); if (perm < 0) { @@ -1215,6 +1173,8 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, // Strip any sticky and executable bits. perm = perm & 0666; + int fd; + // If the undo file already exists, verify that it actually is an undo // file, and delete it. if (os_path_exists((char_u *)file_name)) { @@ -1279,15 +1239,13 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, #ifdef U_DEBUG // Check there is no problem in undo info before writing. - u_check(FALSE); + u_check(false); #endif #ifdef UNIX - /* - * Try to set the group of the undo file same as the original file. If - * this fails, set the protection bits for the group same as the - * protection bits for others. - */ + // Try to set the group of the undo file same as the original file. If + // this fails, set the protection bits for the group same as the + // protection bits for others. FileInfo file_info_old; FileInfo file_info_new; if (buf->b_ffname != NULL @@ -1310,26 +1268,24 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, // Undo must be synced. u_sync(true); - /* - * Write the header. - */ - bi.bi_buf = buf; - bi.bi_fp = fp; + // Write the header. + bufinfo_T bi = { + .bi_buf = buf, + .bi_fp = fp, + }; if (!serialize_header(&bi, hash)) { goto write_error; } - /* - * Iteratively serialize UHPs and their UEPs from the top down. - */ - mark = ++lastmark; - uhp = buf->b_u_oldhead; + // Iteratively serialize UHPs and their UEPs from the top down. + int mark = ++lastmark; + u_header_T *uhp = buf->b_u_oldhead; while (uhp != NULL) { // Serialize current UHP if we haven't seen it if (uhp->uh_walk != mark) { uhp->uh_walk = mark; #ifdef U_DEBUG - ++headers_written; + headers_written++; #endif if (!serialize_uhp(&bi, uhp)) { goto write_error; @@ -1437,9 +1393,10 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT goto error; } - bufinfo_T bi; - bi.bi_buf = curbuf; - bi.bi_fp = fp; + bufinfo_T bi = { + .bi_buf = curbuf, + .bi_fp = fp, + }; // Read the undo file header. char_u magic_buf[UF_START_MAGIC_LEN]; @@ -1568,7 +1525,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT // We have put all of the headers into a table. Now we iterate through the // table and swizzle each sequence number we have stored in uh_*_seq into // a pointer corresponding to the header with that sequence number. - short old_idx = -1, new_idx = -1, cur_idx = -1; + int16_t old_idx = -1, new_idx = -1, cur_idx = -1; for (int i = 0; i < num_head; i++) { u_header_T *uhp = uhp_table[i]; if (uhp == NULL) { @@ -1614,18 +1571,18 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT } } if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) { - assert(i <= SHRT_MAX); - old_idx = (short)i; + assert(i <= INT16_MAX); + old_idx = (int16_t)i; SET_FLAG(i); } if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq) { - assert(i <= SHRT_MAX); - new_idx = (short)i; + assert(i <= INT16_MAX); + new_idx = (int16_t)i; SET_FLAG(i); } if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq) { - assert(i <= SHRT_MAX); - cur_idx = (short)i; + assert(i <= INT16_MAX); + cur_idx = (int16_t)i; SET_FLAG(i); } } @@ -1656,7 +1613,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT } } xfree(uhp_table_used); - u_check(TRUE); + u_check(true); #endif if (name != NULL) { @@ -1781,17 +1738,13 @@ static uint8_t *undo_read_string(bufinfo_T *bi, size_t len) return ptr; } -/* - * If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). - * If 'cpoptions' does not contain 'u': Always undo. - */ +/// If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). +/// If 'cpoptions' does not contain 'u': Always undo. void u_undo(int count) { - /* - * If we get an undo command while executing a macro, we behave like the - * original vi. If this happens twice in one macro the result will not - * be compatible. - */ + // If we get an undo command while executing a macro, we behave like the + // original vi. If this happens twice in one macro the result will not + // be compatible. if (curbuf->b_u_synced == false) { u_sync(true); count = 1; @@ -1805,10 +1758,8 @@ void u_undo(int count) u_doit(count, false, true); } -/* - * If 'cpoptions' contains 'u': Repeat the previous undo or redo. - * If 'cpoptions' does not contain 'u': Always redo. - */ +/// If 'cpoptions' contains 'u': Repeat the previous undo or redo. +/// If 'cpoptions' does not contain 'u': Always redo. void u_redo(int count) { if (vim_strchr(p_cpo, CPO_UNDO) == NULL) { @@ -1870,8 +1821,6 @@ bool u_undo_and_forget(int count) /// @param do_buf_event If `true`, send the changedtick with the buffer updates static void u_doit(int startcount, bool quiet, bool do_buf_event) { - int count = startcount; - if (!undo_allowed(curbuf)) { return; } @@ -1881,6 +1830,8 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event) if (curbuf->b_ml.ml_flags & ML_EMPTY) { u_oldcount = -1; } + + int count = startcount; while (count--) { // Do the change warning now, so that it triggers FileChangedRO when // needed. This may cause the file to be reloaded, that must happen @@ -1940,21 +1891,6 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event) // "sec" must be false then. void undo_time(long step, bool sec, bool file, bool absolute) { - long target; - long closest; - long closest_start; - long closest_seq = 0; - long val; - u_header_T *uhp = NULL; - u_header_T *last; - int mark; - int nomark = 0; // shut up compiler - int round; - bool dosec = sec; - bool dofile = file; - bool above = false; - bool did_undo = true; - if (text_locked()) { text_locked_msg(); return; @@ -1971,6 +1907,14 @@ void undo_time(long step, bool sec, bool file, bool absolute) u_oldcount = -1; } + long target; + long closest; + u_header_T *uhp = NULL; + bool dosec = sec; + bool dofile = file; + bool above = false; + bool did_undo = true; + // "target" is the node below which we want to be. // Init "closest" to a value we can't reach. if (absolute) { @@ -2034,8 +1978,10 @@ void undo_time(long step, bool sec, bool file, bool absolute) } } } - closest_start = closest; - closest_seq = curbuf->b_u_seq_cur; + long closest_start = closest; + long closest_seq = curbuf->b_u_seq_cur; + int mark; + int nomark = 0; // shut up compiler // When "target" is 0; Back to origin. if (target == 0) { @@ -2043,15 +1989,13 @@ void undo_time(long step, bool sec, bool file, bool absolute) goto target_zero; } - /* - * May do this twice: - * 1. Search for "target", update "closest" to the best match found. - * 2. If "target" not found search for "closest". - * - * When using the closest time we use the sequence number in the second - * round, because there may be several entries with the same time. - */ - for (round = 1; round <= 2; round++) { + // May do this twice: + // 1. Search for "target", update "closest" to the best match found. + // 2. If "target" not found search for "closest". + // + // When using the closest time we use the sequence number in the second + // round, because there may be several entries with the same time. + for (int round = 1; round <= 2; round++) { // Find the path from the current state to where we want to go. The // desired state can be anywhere in the undo tree, need to go all over // it. We put "nomark" in uh_walk where we have been without success, @@ -2067,13 +2011,9 @@ void undo_time(long step, bool sec, bool file, bool absolute) while (uhp != NULL) { uhp->uh_walk = mark; - if (dosec) { - val = (long)(uhp->uh_time); - } else if (dofile) { - val = uhp->uh_save_nr; - } else { - val = uhp->uh_seq; - } + long val = dosec ? (long)(uhp->uh_time) : + dofile ? uhp->uh_save_nr + : uhp->uh_seq; if (round == 1 && !(dofile && val == 0)) { // Remember the header that is closest to the target. @@ -2081,17 +2021,17 @@ void undo_time(long step, bool sec, bool file, bool absolute) // "b_u_seq_cur"). When the timestamp is equal find the // highest/lowest sequence number. if ((step < 0 ? uhp->uh_seq <= curbuf->b_u_seq_cur - : uhp->uh_seq > curbuf->b_u_seq_cur) + : uhp->uh_seq > curbuf->b_u_seq_cur) && ((dosec && val == closest) ? (step < 0 ? uhp->uh_seq < closest_seq : uhp->uh_seq > closest_seq) - : closest == closest_start + : closest == closest_start || (val > target ? (closest > target ? val - target <= closest - target : val - target <= target - closest) - : (closest > target + : (closest > target ? target - val <= closest - target : target - val <= target - closest)))) { closest = val; @@ -2110,11 +2050,10 @@ void undo_time(long step, bool sec, bool file, bool absolute) if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark && uhp->uh_prev.ptr->uh_walk != mark) { uhp = uhp->uh_prev.ptr; - } - // go to alternate branch if we haven't been there - else if (uhp->uh_alt_next.ptr != NULL - && uhp->uh_alt_next.ptr->uh_walk != nomark - && uhp->uh_alt_next.ptr->uh_walk != mark) { + } else if (uhp->uh_alt_next.ptr != NULL + && uhp->uh_alt_next.ptr->uh_walk != nomark + && uhp->uh_alt_next.ptr->uh_walk != mark) { + // go to alternate branch if we haven't been there uhp = uhp->uh_alt_next.ptr; } else if (uhp->uh_next.ptr != NULL && uhp->uh_alt_prev.ptr == NULL // go up in the tree if we haven't been there and we are at the @@ -2208,7 +2147,7 @@ target_zero: } // Find the last branch with a mark, that's the one. - last = uhp; + u_header_T *last = uhp; while (last->uh_alt_next.ptr != NULL && last->uh_alt_next.ptr->uh_walk == mark) { last = last->uh_alt_next.ptr; @@ -2285,19 +2224,10 @@ target_zero: static void u_undoredo(int undo, bool do_buf_event) { char_u **newarray = NULL; - linenr_T oldsize; - linenr_T newsize; - linenr_T top, bot; - linenr_T lnum; linenr_T newlnum = MAXLNUM; - long i; - u_entry_T *uep, *nuep; + u_entry_T *nuep; u_entry_T *newlist = NULL; - int old_flags; - int new_flags; fmark_T namedm[NMARKS]; - visualinfo_T visualinfo; - bool empty_buffer; // buffer became empty u_header_T *curhead = curbuf->b_u_curhead; // Don't want autocommands using the undo structures here, they are @@ -2305,28 +2235,26 @@ static void u_undoredo(int undo, bool do_buf_event) block_autocmds(); #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif - old_flags = curhead->uh_flags; - new_flags = (curbuf->b_changed ? UH_CHANGED : 0) - | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0) - | (old_flags & UH_RELOAD); + int old_flags = curhead->uh_flags; + int new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0) + | (old_flags & UH_RELOAD); setpcmark(); - /* - * save marks before undo/redo - */ + // save marks before undo/redo zero_fmark_additional_data(curbuf->b_namedm); memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS); - visualinfo = curbuf->b_visual; + visualinfo_T visualinfo = curbuf->b_visual; curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count; curbuf->b_op_start.col = 0; curbuf->b_op_end.lnum = 0; curbuf->b_op_end.col = 0; - for (uep = curhead->uh_entry; uep != NULL; uep = nuep) { - top = uep->ue_top; - bot = uep->ue_bot; + for (u_entry_T *uep = curhead->uh_entry; uep != NULL; uep = nuep) { + linenr_T top = uep->ue_top; + linenr_T bot = uep->ue_bot; if (bot == 0) { bot = curbuf->b_ml.ml_line_count + 1; } @@ -2338,21 +2266,22 @@ static void u_undoredo(int undo, bool do_buf_event) return; } - oldsize = bot - top - 1; // number of lines before undo - newsize = (linenr_T)uep->ue_size; // number of lines after undo + linenr_T oldsize = bot - top - 1; // number of lines before undo + linenr_T newsize = (linenr_T)uep->ue_size; // number of lines after undo if (top < newlnum) { - /* If the saved cursor is somewhere in this undo block, move it to - * the remembered position. Makes "gwap" put the cursor back - * where it was. */ - lnum = curhead->uh_cursor.lnum; + // If the saved cursor is somewhere in this undo block, move it to + // the remembered position. Makes "gwap" put the cursor back + // where it was. + linenr_T lnum = curhead->uh_cursor.lnum; if (lnum >= top && lnum <= top + newsize + 1) { curwin->w_cursor = curhead->uh_cursor; newlnum = curwin->w_cursor.lnum - 1; } else { - /* Use the first line that actually changed. Avoids that - * undoing auto-formatting puts the cursor in the previous - * line. */ + // Use the first line that actually changed. Avoids that + // undoing auto-formatting puts the cursor in the previous + // line. + long i; for (i = 0; i < newsize && i < oldsize; i++) { if (STRCMP(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) { break; @@ -2368,17 +2297,19 @@ static void u_undoredo(int undo, bool do_buf_event) } } - empty_buffer = false; + bool empty_buffer = false; // delete the lines between top and bot and save them in newarray if (oldsize > 0) { newarray = xmalloc(sizeof(char_u *) * (size_t)oldsize); // delete backwards, it goes faster in most cases + long i; + linenr_T lnum; for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum) { // what can we do when we run out of memory? newarray[i] = u_save_line(lnum); - /* remember we deleted the last line in the buffer, and a - * dummy empty line will be inserted */ + // remember we deleted the last line in the buffer, and a + // dummy empty line will be inserted if (curbuf->b_ml.ml_line_count == 1) { empty_buffer = true; } @@ -2390,11 +2321,11 @@ static void u_undoredo(int undo, bool do_buf_event) // insert the lines in u_array between top and bot if (newsize) { - for (lnum = top, i = 0; i < newsize; ++i, ++lnum) { - /* - * If the file is empty, there is an empty line 1 that we - * should get rid of, by replacing it with the new line - */ + long i; + linenr_T lnum; + for (lnum = top, i = 0; i < newsize; i++, lnum++) { + // If the file is empty, there is an empty line 1 that we + // should get rid of, by replacing it with the new line if (empty_buffer && lnum == 0) { ml_replace((linenr_T)1, (char *)uep->ue_array[i], true); } else { @@ -2435,9 +2366,7 @@ static void u_undoredo(int undo, bool do_buf_event) uep->ue_array = newarray; uep->ue_bot = top + newsize + 1; - /* - * insert this entry in front of the new entry list - */ + // insert this entry in front of the new entry list nuep = uep->ue_next; uep->ue_next = newlist; newlist = uep; @@ -2454,13 +2383,13 @@ static void u_undoredo(int undo, bool do_buf_event) // Adjust Extmarks ExtmarkUndoObject undo_info; if (undo) { - for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) { + for (long i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) { undo_info = kv_A(curhead->uh_extmark, i); extmark_apply_undo(undo_info, undo); } // redo } else { - for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) { + for (long i = 0; i < (int)kv_size(curhead->uh_extmark); i++) { undo_info = kv_A(curhead->uh_extmark, i); extmark_apply_undo(undo_info, undo); } @@ -2490,10 +2419,8 @@ static void u_undoredo(int undo, bool do_buf_event) buf_updates_changedtick(curbuf); } - /* - * restore marks from before undo/redo - */ - for (i = 0; i < NMARKS; ++i) { + // restore marks from before undo/redo + for (long i = 0; i < NMARKS; i++) { if (curhead->uh_namedm[i].mark.lnum != 0) { free_fmark(curbuf->b_namedm[i]); curbuf->b_namedm[i] = curhead->uh_namedm[i]; @@ -2509,14 +2436,12 @@ static void u_undoredo(int undo, bool do_buf_event) curhead->uh_visual = visualinfo; } - /* - * If the cursor is only off by one line, put it at the same position as - * before starting the change (for the "o" command). - * Otherwise the cursor should go to the first undone line. - */ + // If the cursor is only off by one line, put it at the same position as + // before starting the change (for the "o" command). + // Otherwise the cursor should go to the first undone line. if (curhead->uh_cursor.lnum + 1 == curwin->w_cursor.lnum && curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; } if (curwin->w_cursor.lnum <= curbuf->b_ml.ml_line_count) { if (curhead->uh_cursor.lnum == curwin->w_cursor.lnum) { @@ -2565,7 +2490,7 @@ static void u_undoredo(int undo, bool do_buf_event) unblock_autocmds(); #ifdef U_DEBUG - u_check(FALSE); + u_check(false); #endif } @@ -2577,10 +2502,6 @@ static void u_undoredo(int undo, bool do_buf_event) /// @param absolute used ":undo N" static void u_undo_end(bool did_undo, bool absolute, bool quiet) { - char *msgstr; - u_header_T *uhp; - char_u msgbuf[80]; - if ((fdo_flags & FDO_UNDO) && KeyTyped) { foldOpenCursor(); } @@ -2592,10 +2513,11 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } if (curbuf->b_ml.ml_flags & ML_EMPTY) { - --u_newcount; + u_newcount--; } u_oldcount -= u_newcount; + char *msgstr; if (u_oldcount == -1) { msgstr = N_("more line"); } else if (u_oldcount < 0) { @@ -2613,6 +2535,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } } + u_header_T *uhp; if (curbuf->b_u_curhead != NULL) { // For ":undo N" we prefer a "after #N" message. if (absolute && curbuf->b_u_curhead->uh_next.ptr != NULL) { @@ -2627,6 +2550,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) uhp = curbuf->b_u_newhead; } + char_u msgbuf[80]; if (uhp == NULL) { *msgbuf = NUL; } else { @@ -2657,9 +2581,8 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) /// Put the timestamp of an undo header in "buf[buflen]" in a nice format. void undo_fmt_time(char_u *buf, size_t buflen, time_t tt) { - struct tm curtime; - if (time(NULL) - tt >= 100) { + struct tm curtime; os_localtime_r(&tt, &curtime); if (time(NULL) - tt < (60L * 60L * 12L)) { // within 12 hours @@ -2695,27 +2618,20 @@ void u_sync(bool force) } } -/* - * ":undolist": List the leafs of the undo tree - */ +/// ":undolist": List the leafs of the undo tree void ex_undolist(exarg_T *eap) { - garray_T ga; - u_header_T *uhp; - int mark; - int nomark; int changes = 1; - /* - * 1: walk the tree to find all leafs, put the info in "ga". - * 2: sort the lines - * 3: display the list - */ - mark = ++lastmark; - nomark = ++lastmark; + // 1: walk the tree to find all leafs, put the info in "ga". + // 2: sort the lines + // 3: display the list + int mark = ++lastmark; + int nomark = ++lastmark; + garray_T ga; ga_init(&ga, (int)sizeof(char *), 20); - uhp = curbuf->b_u_oldhead; + u_header_T *uhp = curbuf->b_u_oldhead; while (uhp != NULL) { if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark && uhp->uh_walk != mark) { @@ -2736,12 +2652,11 @@ void ex_undolist(exarg_T *eap) if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark && uhp->uh_prev.ptr->uh_walk != mark) { uhp = uhp->uh_prev.ptr; - ++changes; - } - // go to alternate branch if we haven't been there - else if (uhp->uh_alt_next.ptr != NULL - && uhp->uh_alt_next.ptr->uh_walk != nomark - && uhp->uh_alt_next.ptr->uh_walk != mark) { + changes++; + } else if (uhp->uh_alt_next.ptr != NULL + && uhp->uh_alt_next.ptr->uh_walk != nomark + && uhp->uh_alt_next.ptr->uh_walk != mark) { + // go to alternate branch if we haven't been there uhp = uhp->uh_alt_next.ptr; } else if (uhp->uh_next.ptr != NULL && uhp->uh_alt_prev.ptr == NULL // go up in the tree if we haven't been there and we are at the @@ -2749,7 +2664,7 @@ void ex_undolist(exarg_T *eap) && uhp->uh_next.ptr->uh_walk != nomark && uhp->uh_next.ptr->uh_walk != mark) { uhp = uhp->uh_next.ptr; - --changes; + changes--; } else { // need to backtrack; mark this node as done uhp->uh_walk = nomark; @@ -2757,7 +2672,7 @@ void ex_undolist(exarg_T *eap) uhp = uhp->uh_alt_prev.ptr; } else { uhp = uhp->uh_next.ptr; - --changes; + changes--; } } } @@ -2783,9 +2698,7 @@ void ex_undolist(exarg_T *eap) } } -/* - * ":undojoin": continue adding to the last entry list - */ +/// ":undojoin": continue adding to the last entry list void ex_undojoin(exarg_T *eap) { if (curbuf->b_u_newhead == NULL) { @@ -2805,35 +2718,30 @@ void ex_undojoin(exarg_T *eap) } } -/* - * Called after writing or reloading the file and setting b_changed to FALSE. - * Now an undo means that the buffer is modified. - */ +/// Called after writing or reloading the file and setting b_changed to false. +/// Now an undo means that the buffer is modified. void u_unchanged(buf_T *buf) { u_unch_branch(buf->b_u_oldhead); buf->b_did_warn = false; } -/* - * After reloading a buffer which was saved for 'undoreload': Find the first - * line that was changed and set the cursor there. - */ +/// After reloading a buffer which was saved for 'undoreload': Find the first +/// line that was changed and set the cursor there. void u_find_first_changed(void) { u_header_T *uhp = curbuf->b_u_newhead; - u_entry_T *uep; - linenr_T lnum; if (curbuf->b_u_curhead != NULL || uhp == NULL) { return; // undid something in an autocmd? } // Check that the last undo block was for the whole file. - uep = uhp->uh_entry; + u_entry_T *uep = uhp->uh_entry; if (uep->ue_top != 0 || uep->ue_bot != 0) { return; } + linenr_T lnum; for (lnum = 1; lnum < curbuf->b_ml.ml_line_count && lnum <= uep->ue_size; lnum++) { if (STRCMP(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) { @@ -2849,17 +2757,13 @@ void u_find_first_changed(void) } } -/* - * Increase the write count, store it in the last undo header, what would be - * used for "u". - */ +/// Increase the write count, store it in the last undo header, what would be +/// used for "u". void u_update_save_nr(buf_T *buf) { - u_header_T *uhp; - - ++buf->b_u_save_nr_last; + buf->b_u_save_nr_last++; buf->b_u_save_nr_cur = buf->b_u_save_nr_last; - uhp = buf->b_u_curhead; + u_header_T *uhp = buf->b_u_curhead; if (uhp != NULL) { uhp = uhp->uh_next.ptr; } else { @@ -2872,9 +2776,7 @@ void u_update_save_nr(buf_T *buf) static void u_unch_branch(u_header_T *uhp) { - u_header_T *uh; - - for (uh = uhp; uh != NULL; uh = uh->uh_prev.ptr) { + for (u_header_T *uh = uhp; uh != NULL; uh = uh->uh_prev.ptr) { uh->uh_flags |= UH_CHANGED; if (uh->uh_alt_next.ptr != NULL) { u_unch_branch(uh->uh_alt_next.ptr); // recursive @@ -2882,10 +2784,8 @@ static void u_unch_branch(u_header_T *uhp) } } -/* - * Get pointer to last added entry. - * If it's not valid, give an error message and return NULL. - */ +/// Get pointer to last added entry. +/// If it's not valid, give an error message and return NULL. static u_entry_T *u_get_headentry(buf_T *buf) { if (buf->b_u_newhead == NULL || buf->b_u_newhead->uh_entry == NULL) { @@ -2895,28 +2795,21 @@ static u_entry_T *u_get_headentry(buf_T *buf) return buf->b_u_newhead->uh_entry; } -/* - * u_getbot(): compute the line number of the previous u_save - * It is called only when b_u_synced is false. - */ +/// u_getbot(): compute the line number of the previous u_save +/// It is called only when b_u_synced is false. static void u_getbot(buf_T *buf) { - u_entry_T *uep; - linenr_T extra; - - uep = u_get_headentry(buf); // check for corrupt undo list + u_entry_T *uep = u_get_headentry(buf); // check for corrupt undo list if (uep == NULL) { return; } uep = buf->b_u_newhead->uh_getbot_entry; if (uep != NULL) { - /* - * the new ue_bot is computed from the number of lines that has been - * inserted (0 - deleted) since calling u_save. This is equal to the - * old line count subtracted from the current line count. - */ - extra = buf->b_ml.ml_line_count - uep->ue_lcount; + // the new ue_bot is computed from the number of lines that has been + // inserted (0 - deleted) since calling u_save. This is equal to the + // old line count subtracted from the current line count. + linenr_T extra = buf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + (linenr_T)uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > buf->b_ml.ml_line_count) { iemsg(_("E440: undo line missing")); @@ -2937,8 +2830,6 @@ static void u_getbot(buf_T *buf) /// @param uhpp if not NULL reset when freeing this header static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) { - u_header_T *uhap; - // When there is an alternate redo list free that branch completely, // because we can never go there. if (uhp->uh_alt_next.ptr != NULL) { @@ -2959,7 +2850,7 @@ static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) if (uhp->uh_prev.ptr == NULL) { buf->b_u_newhead = uhp->uh_next.ptr; } else { - for (uhap = uhp->uh_prev.ptr; uhap != NULL; + for (u_header_T *uhap = uhp->uh_prev.ptr; uhap != NULL; uhap = uhap->uh_alt_next.ptr) { uhap->uh_next.ptr = uhp->uh_next.ptr; } @@ -2973,8 +2864,6 @@ static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) /// @param uhpp if not NULL reset when freeing this header static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) { - u_header_T *tofree, *next; - // If this is the top branch we may need to use u_freeheader() to update // all the pointers. if (uhp == buf->b_u_oldhead) { @@ -2988,9 +2877,9 @@ static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) uhp->uh_alt_prev.ptr->uh_alt_next.ptr = NULL; } - next = uhp; + u_header_T *next = uhp; while (next != NULL) { - tofree = next; + u_header_T *tofree = next; if (tofree->uh_alt_next.ptr != NULL) { u_freebranch(buf, tofree->uh_alt_next.ptr, uhpp); // recursive } @@ -3029,12 +2918,10 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) uhp->uh_magic = 0; #endif xfree((char_u *)uhp); - --buf->b_u_numhead; + buf->b_u_numhead--; } -/* - * free entry 'uep' and 'n' lines in uep->ue_array[] - */ +/// free entry 'uep' and 'n' lines in uep->ue_array[] static void u_freeentry(u_entry_T *uep, long n) { while (n > 0) { @@ -3047,9 +2934,7 @@ static void u_freeentry(u_entry_T *uep, long n) xfree((char_u *)uep); } -/* - * invalidate the undo buffer; called when storage has already been released - */ +/// invalidate the undo buffer; called when storage has already been released void u_clearall(buf_T *buf) { buf->b_u_newhead = buf->b_u_oldhead = buf->b_u_curhead = NULL; @@ -3059,9 +2944,7 @@ void u_clearall(buf_T *buf) buf->b_u_line_lnum = 0; } -/* - * save the line "lnum" for the "U" command - */ +/// save the line "lnum" for the "U" command void u_saveline(linenr_T lnum) { if (lnum == curbuf->b_u_line_lnum) { // line is already saved @@ -3080,10 +2963,8 @@ void u_saveline(linenr_T lnum) curbuf->b_u_line_ptr = u_save_line(lnum); } -/* - * clear the line saved for the "U" command - * (this is used externally for crossing a line while in insert mode) - */ +/// clear the line saved for the "U" command +/// (this is used externally for crossing a line while in insert mode) void u_clearline(void) { if (curbuf->b_u_line_ptr != NULL) { @@ -3092,17 +2973,12 @@ void u_clearline(void) } } -/* - * Implementation of the "U" command. - * Differentiation from vi: "U" can be undone with the next "U". - * We also allow the cursor to be in another line. - * Careful: may trigger autocommands that reload the buffer. - */ +/// Implementation of the "U" command. +/// Differentiation from vi: "U" can be undone with the next "U". +/// We also allow the cursor to be in another line. +/// Careful: may trigger autocommands that reload the buffer. void u_undoline(void) { - colnr_T t; - char_u *oldp; - if (curbuf->b_u_line_ptr == NULL || curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) { beep_flush(); @@ -3115,7 +2991,7 @@ void u_undoline(void) return; } - oldp = u_save_line(curbuf->b_u_line_lnum); + char_u *oldp = u_save_line(curbuf->b_u_line_lnum); ml_replace(curbuf->b_u_line_lnum, (char *)curbuf->b_u_line_ptr, true); changed_bytes(curbuf->b_u_line_lnum, 0); extmark_splice_cols(curbuf, (int)curbuf->b_u_line_lnum - 1, 0, (colnr_T)STRLEN(oldp), @@ -3123,7 +2999,7 @@ void u_undoline(void) xfree(curbuf->b_u_line_ptr); curbuf->b_u_line_ptr = oldp; - t = curbuf->b_u_line_colnr; + colnr_T t = curbuf->b_u_line_colnr; if (curwin->w_cursor.lnum == curbuf->b_u_line_lnum) { curbuf->b_u_line_colnr = curwin->w_cursor.col; } @@ -3132,9 +3008,7 @@ void u_undoline(void) check_cursor_col(); } -/* - * Free all allocated memory blocks for the buffer 'buf'. - */ +/// Free all allocated memory blocks for the buffer 'buf'. void u_blockfree(buf_T *buf) { while (buf->b_u_oldhead != NULL) { diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index d8470b07b1..4b64f97919 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -74,10 +74,4 @@ struct u_header { #define UH_EMPTYBUF 0x02 // buffer was empty #define UH_RELOAD 0x04 // buffer was reloaded -/// Structure passed around between undofile functions. -typedef struct { - buf_T *bi_buf; - FILE *bi_fp; -} bufinfo_T; - #endif // NVIM_UNDO_DEFS_H diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 15197dc504..59b8d10200 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -12,10 +12,12 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/garray.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" +#include "nvim/runtime.h" #include "nvim/usercmd.h" #include "nvim/window.h" @@ -167,7 +169,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) xp->xp_luaref = uc->uc_compl_luaref; xp->xp_arg = (char *)uc->uc_compl_arg; xp->xp_script_ctx = uc->uc_script_ctx; - xp->xp_script_ctx.sc_lnum += sourcing_lnum; + xp->xp_script_ctx.sc_lnum += SOURCING_LNUM; } // Do not search for further abbreviations // if this is an exact match. @@ -889,7 +891,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, cmd->uc_def = def; cmd->uc_compl = compl; cmd->uc_script_ctx = current_sctx; - cmd->uc_script_ctx.sc_lnum += sourcing_lnum; + cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&cmd->uc_script_ctx); cmd->uc_compl_arg = (char_u *)compl_arg; cmd->uc_compl_luaref = compl_luaref; @@ -1162,7 +1164,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, *q++ = ' '; *q++ = '"'; } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); + mb_copy_char((const char **)&p, &q); } } } else { @@ -1175,7 +1177,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, *q++ = '\\'; *q++ = *p++; } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); + mb_copy_char((const char **)&p, &q); } } if (i != argc - 1) { diff --git a/src/nvim/version.c b/src/nvim/version.c index 3ffae6592c..0667243bc3 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -14,12 +14,13 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/drawscreen.h" +#include "nvim/grid.h" #include "nvim/iconv.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -2063,7 +2064,7 @@ static void list_features(void) /// List string items nicely aligned in columns. /// When "size" is < 0 then the last entry is marked with NULL. /// The entry with index "current" is inclosed in []. -void list_in_columns(char_u **items, int size, int current) +void list_in_columns(char **items, int size, int current) { int item_count = 0; int width = 0; diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 31ac5a67ff..09b949bb20 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -199,6 +199,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // Size in bytes of the hash used in the undo file. #define UNDO_HASH_SIZE 32 +#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field)) #define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr))) // defines to avoid typecasts from (char_u *) to (char *) and back @@ -274,8 +275,8 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() (const char *)(y), \ (size_t)(n)) -// Enums need a typecast to be used as array index (for Ultrix). -#define HL_ATTR(n) highlight_attr[(int)(n)] +// Enums need a typecast to be used as array index. +#define HL_ATTR(n) hl_attr_active[(int)(n)] /// Maximum number of bytes in a multi-byte character. It can be one 32-bit /// character of up to 6 bytes, or one 16-bit character of up to three bytes diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index fd7dc17ee3..387b9d61f2 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1828,13 +1828,13 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no v_p += special_len; } else { is_unknown = true; - mb_copy_char((const char_u **)&p, (char_u **)&v_p); + mb_copy_char(&p, &v_p); } break; } default: is_unknown = true; - mb_copy_char((const char_u **)&p, (char_u **)&v_p); + mb_copy_char(&p, &v_p); break; } if (pstate->colors) { diff --git a/src/nvim/window.c b/src/nvim/window.c index 39346faa14..2d995af00d 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6,11 +6,14 @@ #include <stdbool.h> #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" +#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/vars.h" @@ -25,7 +28,9 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/globals.h" +#include "nvim/grid.h" #include "nvim/hashtab.h" +#include "nvim/highlight.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mark.h" @@ -43,7 +48,6 @@ #include "nvim/plines.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -122,7 +126,7 @@ void do_window(int nchar, long Prenum, int xchar) { long Prenum1; win_T *wp; - char_u *ptr; + char *ptr; linenr_T lnum = -1; int type = FIND_DEFINE; size_t len; @@ -483,14 +487,14 @@ newwindow: wingotofile: CHECK_CMDWIN; - ptr = grab_file_name(Prenum1, &lnum); + ptr = (char *)grab_file_name(Prenum1, &lnum); if (ptr != NULL) { tabpage_T *oldtab = curtab; win_T *oldwin = curwin; setpcmark(); if (win_split(0, 0) == OK) { RESET_BINDING(curwin); - if (do_ecmd(0, (char *)ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) { + if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) { // Failed to open the file, close the window opened for it. win_close(curwin, false, false); goto_tabpage_win(oldtab, oldwin); @@ -518,9 +522,9 @@ wingotofile: } // Make a copy, if the line was changed it will be freed. - ptr = vim_strnsave(ptr, len); + ptr = xstrnsave(ptr, len); - find_pattern_in_path(ptr, 0, len, true, Prenum == 0, + find_pattern_in_path((char_u *)ptr, 0, len, true, Prenum == 0, type, Prenum1, ACTION_SPLIT, 1, MAXLNUM); xfree(ptr); curwin->w_set_curswant = true; @@ -698,14 +702,14 @@ win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err) win_remove(wp, NULL); win_append(lastwin_nofloating(), wp); } - wp->w_floating = 1; + wp->w_floating = true; wp->w_status_height = 0; wp->w_winbar_height = 0; wp->w_hsep_height = 0; wp->w_vsep_width = 0; win_config_float(wp, fconfig); - win_set_inner_size(wp); + win_set_inner_size(wp, true); wp->w_pos_changed = true; redraw_later(wp, VALID); return wp; @@ -728,13 +732,15 @@ void win_set_minimal_style(win_T *wp) : concat_str(old, (char_u *)",eob: ")); free_string_option(old); } - if (wp->w_hl_ids[HLF_EOB] != -1) { - char_u *old = wp->w_p_winhl; - wp->w_p_winhl = ((*old == NUL) - ? (char_u *)xstrdup("EndOfBuffer:") - : concat_str(old, (char_u *)",EndOfBuffer:")); - free_string_option(old); - } + + // TODO(bfredl): this could use a highlight namespace directly, + // and avoid pecularities around window options + char_u *old = wp->w_p_winhl; + wp->w_p_winhl = ((*old == NUL) + ? (char_u *)xstrdup("EndOfBuffer:") + : concat_str(old, (char_u *)",EndOfBuffer:")); + free_string_option(old); + parse_winhl_opt(wp); // signcolumn: use 'auto' if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) { @@ -789,7 +795,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp)); } - win_set_inner_size(wp); + win_set_inner_size(wp, true); must_redraw = MAX(must_redraw, VALID); wp->w_pos_changed = true; @@ -1270,7 +1276,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) wp->w_floating = false; // non-floating window doesn't store float config or have a border. wp->w_float_config = FLOAT_CONFIG_INIT; - memset(wp->w_border_adj, 0, sizeof(wp->w_border_adj)); + CLEAR_FIELD(wp->w_border_adj); } /* @@ -1670,7 +1676,7 @@ int win_count(void) int count = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - ++count; + count++; } return count; } @@ -2459,7 +2465,7 @@ void close_windows(buf_T *buf, bool keep_curwin) tabpage_T *tp, *nexttp; int h = tabline_height(); - ++RedrawingDisabled; + RedrawingDisabled++; // Start from lastwin to close floating windows with the same buffer first. // When the autocommand window is involved win_close() may need to print an error message. @@ -2496,7 +2502,7 @@ void close_windows(buf_T *buf, bool keep_curwin) } } - --RedrawingDisabled; + RedrawingDisabled--; redraw_tabline = true; if (h != tabline_height()) { @@ -3825,7 +3831,7 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) m = (int)p_wmw + topfrp->fr_win->w_vsep_width; // Current window is minimal one column wide if (p_wmw == 0 && topfrp->fr_win == curwin && next_curwin == NULL) { - ++m; + m++; } } } else if (topfrp->fr_layout == FR_COL) { @@ -4098,7 +4104,7 @@ int win_new_tabpage(int after, char_u *filename) n = 2; for (tp = first_tabpage; tp->tp_next != NULL && n < after; tp = tp->tp_next) { - ++n; + n++; } } newtp->tp_next = tp->tp_next; @@ -4244,7 +4250,7 @@ tabpage_T *find_tabpage(int n) int i = 1; for (tp = first_tabpage; tp != NULL && i != n; tp = tp->tp_next) { - ++i; + i++; } return tp; } @@ -4259,7 +4265,7 @@ int tabpage_index(tabpage_T *ftp) tabpage_T *tp; for (tp = first_tabpage; tp != NULL && tp != ftp; tp = tp->tp_next) { - ++i; + i++; } return i; } @@ -4521,7 +4527,7 @@ void tabpage_move(int nr) } for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) { - ++n; + n++; } if (tp == curtab || (nr > 0 && tp->tp_next != NULL @@ -4868,10 +4874,17 @@ static void win_enter_ext(win_T *const wp, const int flags) redraw_later(curwin, VALID); // causes status line redraw } - if (HL_ATTR(HLF_INACTIVE) - || (prevwin && prevwin->w_hl_ids[HLF_INACTIVE]) - || curwin->w_hl_ids[HLF_INACTIVE]) { - redraw_all_later(NOT_VALID); + // change background color according to NormalNC, + // but only if actually defined (otherwise no extra redraw) + if (curwin->w_hl_attr_normal != curwin->w_hl_attr_normalnc) { + // TODO(bfredl): eventually we should be smart enough + // to only recompose the window, not redraw it. + redraw_later(curwin, NOT_VALID); + } + if (prevwin) { + if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) { + redraw_later(prevwin, NOT_VALID); + } } // set window height to desired minimal value @@ -5040,6 +5053,8 @@ static win_T *win_alloc(win_T *after, bool hidden) new_wp->w_float_config = FLOAT_CONFIG_INIT; new_wp->w_viewport_invalid = true; + new_wp->w_ns_hl = -1; + // use global option for global-local options new_wp->w_p_so = -1; new_wp->w_p_siso = -1; @@ -5180,7 +5195,7 @@ void win_free_grid(win_T *wp, bool reinit) grid_free(&wp->w_grid_alloc); if (reinit) { // if a float is turned into a split, the grid data structure will be reused - memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc)); + CLEAR_FIELD(wp->w_grid_alloc); } } @@ -5556,7 +5571,7 @@ static void frame_setheight(frame_T *curfrp, int height) } if (curfrp->fr_parent == NULL) { - // topframe: can only change the command line + // topframe: can only change the command line height if (height > ROWS_AVAIL) { // If height is greater than the available space, try to create space for // the frame by reducing 'cmdheight' if possible, while making sure @@ -5919,6 +5934,13 @@ void win_drag_status_line(win_T *dragwin, int offset) int row; bool up; // if true, drag status line up, otherwise down int n; + static bool p_ch_was_zero = false; + + // If the user explicitly set 'cmdheight' to zero, then allow for dragging + // the status line making it zero again. + if (p_ch == 0) { + p_ch_was_zero = true; + } fr = dragwin->w_frame; curfr = fr; @@ -5969,6 +5991,8 @@ void win_drag_status_line(win_T *dragwin, int offset) room = Rows - cmdline_row; if (curfr->fr_next != NULL) { room -= (int)p_ch + global_stl_height(); + } else if (!p_ch_was_zero) { + room--; } if (room < 0) { room = 0; @@ -6024,7 +6048,7 @@ void win_drag_status_line(win_T *dragwin, int offset) clear_cmdline = true; } cmdline_row = row; - p_ch = MAX(Rows - cmdline_row, 0); + p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1); curtab->tp_ch_used = p_ch; redraw_all_later(SOME_VALID); showmode(); @@ -6167,7 +6191,7 @@ void win_new_height(win_T *wp, int height) wp->w_height = height; wp->w_pos_changed = true; - win_set_inner_size(wp); + win_set_inner_size(wp, true); } void scroll_to_fraction(win_T *wp, int prev_height) @@ -6230,7 +6254,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) if (lnum == 1) { // first line in buffer is folded line_size = 1; - --sline; + sline--; break; } lnum--; @@ -6276,7 +6300,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) invalidate_botline_win(wp); } -void win_set_inner_size(win_T *wp) +void win_set_inner_size(win_T *wp, bool valid_cursor) { int width = wp->w_width_request; if (width == 0) { @@ -6290,7 +6314,7 @@ void win_set_inner_size(win_T *wp) } if (height != prev_height) { - if (height > 0) { + if (height > 0 && valid_cursor) { if (wp == curwin) { // w_wrow needs to be valid. When setting 'laststatus' this may // call win_new_height() recursively. @@ -6309,7 +6333,7 @@ void win_set_inner_size(win_T *wp) // There is no point in adjusting the scroll position when exiting. Some // values might be invalid. // Skip scroll_to_fraction() when 'cmdheight' was set to one from zero. - if (!exiting && !made_cmdheight_nonzero) { + if (!exiting && !made_cmdheight_nonzero && valid_cursor) { scroll_to_fraction(wp, prev_height); } redraw_later(wp, NOT_VALID); // SOME_VALID?? @@ -6318,11 +6342,13 @@ void win_set_inner_size(win_T *wp) if (width != wp->w_width_inner) { wp->w_width_inner = width; wp->w_lines_valid = 0; - changed_line_abv_curs_win(wp); - invalidate_botline_win(wp); - if (wp == curwin) { - update_topline(wp); - curs_columns(wp, true); // validate w_wrow + if (valid_cursor) { + changed_line_abv_curs_win(wp); + invalidate_botline_win(wp); + if (wp == curwin) { + update_topline(wp); + curs_columns(wp, true); // validate w_wrow + } } redraw_later(wp, NOT_VALID); } @@ -6351,7 +6377,7 @@ static int win_border_width(win_T *wp) void win_new_width(win_T *wp, int width) { wp->w_width = width; - win_set_inner_size(wp); + win_set_inner_size(wp, true); wp->w_redr_status = true; wp->w_pos_changed = true; @@ -6386,6 +6412,19 @@ void command_height(void) // p_ch was changed in another tab page. curtab->tp_ch_used = p_ch; + // If the space for the command line is already more than 'cmdheight' there + // is nothing to do (window size must have decreased). + if (p_ch > old_p_ch && cmdline_row <= Rows - p_ch) { + return; + } + + // If cmdline_row is smaller than what it is supposed to be for 'cmdheight' + // then set old_p_ch to what it would be, so that the windows get resized + // properly for the new value. + if (cmdline_row < Rows - p_ch) { + old_p_ch = Rows - cmdline_row; + } + // Find bottom frame with width of screen. frp = lastwin_nofloating()->w_frame; while (frp->fr_width != Columns && frp->fr_parent != NULL) { @@ -6470,17 +6509,17 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC; if (VIsual_active) { size_t len; - char_u *ptr; + char *ptr; if (get_visual_text(NULL, &ptr, &len) == FAIL) { return NULL; } // Only recognize ":123" here if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) { - char *p = (char *)ptr + len + 1; + char *p = ptr + len + 1; *file_lnum = (linenr_T)getdigits_long(&p, false, 0); } - return find_file_name_in_path(ptr, len, options, count, (char_u *)curbuf->b_ffname); + return find_file_name_in_path((char_u *)ptr, len, options, count, (char_u *)curbuf->b_ffname); } return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); } @@ -6566,7 +6605,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u if (ptr[len] == '\\' && ptr[len + 1] == ' ') { // Skip over the "\" in "\ ". - ++len; + len++; } len += (size_t)(utfc_ptr2len(ptr + len)); } @@ -6577,7 +6616,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u */ if (len > 2 && vim_strchr(".,:;!", ptr[len - 1]) != NULL && ptr[len - 2] != '.') { - --len; + len--; } if (file_lnum != NULL) { @@ -6743,9 +6782,11 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) /// Add or remove window bar from window "wp". /// /// @param make_room Whether to resize frames to make room for winbar. +/// @param valid_cursor Whether the cursor is valid and should be used while +/// resizing. /// /// @return Success status. -int set_winbar_win(win_T *wp, bool make_room) +int set_winbar_win(win_T *wp, bool make_room, bool valid_cursor) { // Require the local value to be set in order to show winbar on a floating window. int winbar_height = wp->w_floating ? ((*wp->w_p_wbr != NUL) ? 1 : 0) @@ -6761,7 +6802,7 @@ int set_winbar_win(win_T *wp, bool make_room) } } wp->w_winbar_height = winbar_height; - win_set_inner_size(wp); + win_set_inner_size(wp, valid_cursor); wp->w_redr_status = wp->w_redr_status || winbar_height; if (winbar_height == 0) { @@ -6782,7 +6823,7 @@ int set_winbar_win(win_T *wp, bool make_room) void set_winbar(bool make_room) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (set_winbar_win(wp, make_room) == FAIL) { + if (set_winbar_win(wp, make_room, true) == FAIL) { break; } } @@ -7100,7 +7141,7 @@ int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_displa // As switch_win() but without blocking autocommands. int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - memset(switchwin, 0, sizeof(switchwin_T)); + CLEAR_POINTER(switchwin); switchwin->sw_curwin = curwin; if (win == curwin) { switchwin->sw_same_win = true; @@ -7233,6 +7274,88 @@ static bool frame_check_width(const frame_T *topfrp, int width) return true; } +/// Simple int comparison function for use with qsort() +static int int_cmp(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +/// Handle setting 'colorcolumn' or 'textwidth' in window "wp". +/// +/// @return error message, NULL if it's OK. +char *check_colorcolumn(win_T *wp) +{ + char *s; + int col; + unsigned int count = 0; + int color_cols[256]; + int j = 0; + + if (wp->w_buffer == NULL) { + return NULL; // buffer was closed + } + + for (s = (char *)wp->w_p_cc; *s != NUL && count < 255;) { + if (*s == '-' || *s == '+') { + // -N and +N: add to 'textwidth' + col = (*s == '-') ? -1 : 1; + s++; + if (!ascii_isdigit(*s)) { + return e_invarg; + } + col = col * getdigits_int(&s, true, 0); + if (wp->w_buffer->b_p_tw == 0) { + goto skip; // 'textwidth' not set, skip this item + } + assert((col >= 0 + && wp->w_buffer->b_p_tw <= INT_MAX - col + && wp->w_buffer->b_p_tw + col >= INT_MIN) + || (col < 0 + && wp->w_buffer->b_p_tw >= INT_MIN - col + && wp->w_buffer->b_p_tw + col <= INT_MAX)); + col += (int)wp->w_buffer->b_p_tw; + if (col < 0) { + goto skip; + } + } else if (ascii_isdigit(*s)) { + col = getdigits_int(&s, true, 0); + } else { + return e_invarg; + } + color_cols[count++] = col - 1; // 1-based to 0-based +skip: + if (*s == NUL) { + break; + } + if (*s != ',') { + return e_invarg; + } + if (*++s == NUL) { + return e_invarg; // illegal trailing comma as in "set cc=80," + } + } + + xfree(wp->w_p_cc_cols); + if (count == 0) { + wp->w_p_cc_cols = NULL; + } else { + wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1)); + // sort the columns for faster usage on screen redraw inside + // win_line() + qsort(color_cols, count, sizeof(int), int_cmp); + + for (unsigned int i = 0; i < count; i++) { + // skip duplicates + if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { + wp->w_p_cc_cols[j++] = color_cols[i]; + } + } + wp->w_p_cc_cols[j] = -1; // end marker + } + + return NULL; // no error +} + int win_getid(typval_T *argvars) { if (argvars[0].v_type == VAR_UNKNOWN) { |