diff options
Diffstat (limited to 'src')
296 files changed, 39544 insertions, 35234 deletions
diff --git a/src/mpack/mpack_core.h b/src/mpack/mpack_core.h index 1d601bc82d..336b778931 100644 --- a/src/mpack/mpack_core.h +++ b/src/mpack/mpack_core.h @@ -8,6 +8,7 @@ #include <assert.h> #include <limits.h> #include <stddef.h> +#include <stdbool.h> #ifdef __GNUC__ # define FPURE __attribute__((const)) 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..1cf0211f43 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 @@ -887,12 +887,12 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { - ADD(*array, copy_object(*v)); + ADD(*array, copy_object(*v, NULL)); } else if (v->type == kObjectTypeArray) { if (!check_autocmd_string_array(v->data.array, k, err)) { return false; } - *array = copy_array(v->data.array); + *array = copy_array(v->data.array, NULL); } else { if (required) { api_set_error(err, diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index d3895d31cf..199650fc55 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" @@ -1020,7 +1021,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Buffer name -String nvim_buf_get_name(Buffer buffer, Error *err) +String nvim_buf_get_name(Buffer buffer, Arena *arena, Error *err) FUNC_API_SINCE(1) { String rv = STRING_INIT; @@ -1030,7 +1031,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err) return rv; } - return cstr_to_string((char *)buf->b_ffname); + return cstr_as_string(buf->b_ffname); } /// Sets the full file name for a buffer 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..09b004637f 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" @@ -51,7 +51,7 @@ Integer nvim_create_namespace(String name) } id = next_namespace_id++; if (name.size > 0) { - String name_alloc = copy_string(name); + String name_alloc = copy_string(name, NULL); map_put(String, handle_T)(&namespace_ids, name_alloc, id); } return (Integer)id; @@ -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. @@ -992,7 +993,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw - redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? + redraw_all_later(UPD_NOT_VALID); // TODO(bfredl): too soon? struct { const char *name; @@ -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 4f4ac40ce9..6fad52ba75 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -104,7 +104,6 @@ return { "reverse"; "nocombine"; "default"; - "global"; "cterm"; "foreground"; "fg"; "background"; "bg"; @@ -112,9 +111,9 @@ return { "ctermbg"; "special"; "sp"; "link"; + "global_link"; "fallback"; "blend"; - "temp"; }; highlight_cterm = { "bold"; diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index 4b7c394944..f92b205531 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -5,18 +5,23 @@ typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, + Arena *arena, Error *error); /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// functions of this type. -typedef struct { +struct MsgpackRpcRequestHandler { const char *name; ApiDispatchWrapper fn; bool fast; // Function is safe to be executed immediately while running the // uv loop (the loop is run very frequently due to breakcheck). // If "fast" is false, the function is deferred, i e the call will // be put in the event queue, for safe handling later. -} MsgpackRpcRequestHandler; + bool arena_return; // return value is allocated in the arena (or statically) + // and should not be freed as such. +}; + +extern const MsgpackRpcRequestHandler method_handlers[]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch.h.generated.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fad75d55be..ebcf6cca6d 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,10 +54,11 @@ 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, + .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -65,6 +66,7 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; + did_throw = false; need_rethrow = false; did_emsg = false; } @@ -85,14 +87,16 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); + assert(!did_throw); assert(!did_emsg); 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; + did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -127,7 +131,7 @@ bool try_end(Error *err) force_abort = false; if (got_int) { - if (current_exception) { + if (did_throw) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -146,7 +150,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (current_exception) { + } else if (did_throw) { api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } @@ -618,6 +622,7 @@ void api_clear_error(Error *value) value->type = kErrorTypeNone; } +/// @returns a shared value. caller must not modify it! Dictionary api_metadata(void) { static Dictionary metadata = ARRAY_DICT_INIT; @@ -630,7 +635,7 @@ Dictionary api_metadata(void) init_type_metadata(&metadata); } - return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; + return metadata; } static void init_function_metadata(Dictionary *metadata) @@ -715,36 +720,40 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } -String copy_string(String str) +// all the copy_[object] functions allow arena=NULL, +// then global allocations are used, and the resulting object +// should be freed with an api_free_[object] function + +String copy_string(String str, Arena *arena) { if (str.data != NULL) { - return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + return (String){ .data = arena_memdupz(arena, str.data, str.size), .size = str.size }; } else { return (String)STRING_INIT; } } -Array copy_array(Array array) +Array copy_array(Array array, Arena *arena) { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, array.size); for (size_t i = 0; i < array.size; i++) { - ADD(rv, copy_object(array.items[i])); + ADD(rv, copy_object(array.items[i], arena)); } return rv; } -Dictionary copy_dictionary(Dictionary dict) +Dictionary copy_dictionary(Dictionary dict, Arena *arena) { - Dictionary rv = ARRAY_DICT_INIT; + Dictionary rv = arena_dict(arena, dict.size); for (size_t i = 0; i < dict.size; i++) { KeyValuePair item = dict.items[i]; - PUT(rv, item.key.data, copy_object(item.value)); + PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena)); } return rv; } /// Creates a deep clone of an object -Object copy_object(Object obj) +Object copy_object(Object obj, Arena *arena) { switch (obj.type) { case kObjectTypeBuffer: @@ -757,13 +766,13 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(copy_string(obj.data.string)); + return STRING_OBJ(copy_string(obj.data.string, arena)); case kObjectTypeArray: - return ARRAY_OBJ(copy_array(obj.data.array)); + return ARRAY_OBJ(copy_array(obj.data.array, arena)); case kObjectTypeDictionary: - return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); + return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary, arena)); case kObjectTypeLuaRef: return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); @@ -844,7 +853,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) goto free_exit; } - String str = copy_string(chunk.items[0].data.string); + String str = copy_string(chunk.items[0].data.string, NULL); int attr = 0; if (chunk.size == 2) { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 1441da853c..2157ad0ec2 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,10 +130,11 @@ 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; + bool did_throw; int need_rethrow; int did_emsg; } TryState; @@ -144,8 +145,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..e34dcbdb46 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); @@ -965,7 +965,7 @@ static Array translate_contents(UI *ui, Array contents) } else { ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); } - ADD(new_item, copy_object(item.items[1])); + ADD(new_item, copy_object(item.items[1], NULL)); ADD(new_contents, ARRAY_OBJ(new_item)); } return new_contents; @@ -978,7 +978,7 @@ static Array translate_firstarg(UI *ui, Array args) ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents))); for (size_t i = 1; i < args.size; i++) { - ADD(new_args, copy_object(args.items[i])); + ADD(new_args, copy_object(args.items[i], NULL)); } return new_args; } @@ -1024,7 +1024,7 @@ static void remote_ui_event(UI *ui, char *name, Array args) Array items = args.items[0].data.array; Array new_items = ARRAY_DICT_INIT; for (size_t i = 0; i < items.size; i++) { - ADD(new_items, copy_object(items.items[i].data.array.items[0])); + ADD(new_items, copy_object(items.items[i].data.array.items[0], NULL)); } ADD_C(new_args, ARRAY_OBJ(new_items)); push_call(ui, "wildmenu_show", new_args); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e2f58dba62..b164106cef 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" @@ -50,11 +52,13 @@ #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.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/statusline.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -91,7 +95,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 +183,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(UPD_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 +484,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`. @@ -1473,14 +1479,14 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) /// 1 is the |api-metadata| map (Dictionary). /// /// @returns 2-tuple [{channel-id}, {api-metadata}] -Array nvim_get_api_info(uint64_t channel_id) +Array nvim_get_api_info(uint64_t channel_id, Arena *arena) FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); assert(channel_id <= INT64_MAX); - ADD(rv, INTEGER_OBJ((int64_t)channel_id)); - ADD(rv, DICTIONARY_OBJ(api_metadata())); + ADD_C(rv, INTEGER_OBJ((int64_t)channel_id)); + ADD_C(rv, DICTIONARY_OBJ(api_metadata())); return rv; } @@ -1539,9 +1545,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY { Dictionary info = ARRAY_DICT_INIT; - PUT(info, "name", copy_object(STRING_OBJ(name))); + PUT(info, "name", copy_object(STRING_OBJ(name), NULL)); - version = copy_dictionary(version); + version = copy_dictionary(version, NULL); bool has_major = false; for (size_t i = 0; i < version.size; i++) { if (strequal(version.items[i].key.data, "major")) { @@ -1554,9 +1560,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, } PUT(info, "version", DICTIONARY_OBJ(version)); - PUT(info, "type", copy_object(STRING_OBJ(type))); - PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods))); - PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes))); + PUT(info, "type", copy_object(STRING_OBJ(type), NULL)); + PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods, NULL))); + PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes, NULL))); rpc_set_client_info(channel_id, info); } @@ -1623,11 +1629,11 @@ Array nvim_list_chans(void) /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an /// error occurred, the values from all preceding calls will still be returned. -Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) +Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; - Array results = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); + Array results = arena_array(arena, calls.size); Error nested_error = ERROR_INIT; size_t i; // also used for freeing the variables @@ -1636,21 +1642,21 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays"); - goto validation_error; + goto theend; } Array call = calls.items[i].data.array; if (call.size != 2) { api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays of size 2"); - goto validation_error; + goto theend; } if (call.items[0].type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "Name must be String"); - goto validation_error; + goto theend; } String name = call.items[0].data.string; @@ -1658,7 +1664,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Args must be Array"); - goto validation_error; + goto theend; } Array args = call.items[1].data.array; @@ -1670,29 +1676,32 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) if (ERROR_SET(&nested_error)) { break; } - Object result = handler.fn(channel_id, args, &nested_error); + + Object result = handler.fn(channel_id, args, arena, &nested_error); if (ERROR_SET(&nested_error)) { // error handled after loop break; } - - ADD(results, result); + // TODO(bfredl): wastefull copy. It could be avoided to encoding to msgpack + // directly here. But `result` might become invalid when next api function + // is called in the loop. + ADD_C(results, copy_object(result, arena)); + if (!handler.arena_return) { + api_free_object(result); + } } - ADD(rv, ARRAY_OBJ(results)); + ADD_C(rv, ARRAY_OBJ(results)); if (ERROR_SET(&nested_error)) { - Array errval = ARRAY_DICT_INIT; - ADD(errval, INTEGER_OBJ((Integer)i)); - ADD(errval, INTEGER_OBJ(nested_error.type)); - ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); - ADD(rv, ARRAY_OBJ(errval)); + Array errval = arena_array(arena, 3); + ADD_C(errval, INTEGER_OBJ((Integer)i)); + ADD_C(errval, INTEGER_OBJ(nested_error.type)); + ADD_C(errval, STRING_OBJ(copy_string(cstr_as_string(nested_error.msg), arena))); + ADD_C(rv, ARRAY_OBJ(errval)); } else { - ADD(rv, NIL); + ADD_C(rv, NIL); } - goto theend; -validation_error: - api_free_array(results); theend: api_clear_error(&nested_error); return rv; @@ -1745,7 +1754,7 @@ static void write_msg(String message, bool to_err) /// @return its argument. Object nvim__id(Object obj) { - return copy_object(obj); + return copy_object(obj, NULL); } /// Returns array given as argument. @@ -1758,7 +1767,7 @@ Object nvim__id(Object obj) /// @return its argument. Array nvim__id_array(Array arr) { - return copy_object(ARRAY_OBJ(arr)).data.array; + return copy_array(arr, NULL); } /// Returns dictionary given as argument. @@ -1771,7 +1780,7 @@ Array nvim__id_array(Array arr) /// @return its argument. Dictionary nvim__id_dictionary(Dictionary dct) { - return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; + return copy_dictionary(dct, NULL); } /// Returns floating-point value given as argument. @@ -1797,6 +1806,7 @@ Dictionary nvim__stats(void) PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip)); PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count)); return rv; } @@ -1833,11 +1843,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 +1886,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) { @@ -2039,7 +2044,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) // Marks are from an open buffer it fnum is non zero if (mark->fmark.fnum != 0) { bufnr = mark->fmark.fnum; - filename = (char *)buflist_nr2name(bufnr, true, true); + filename = buflist_nr2name(bufnr, true, true); allocated = true; // Marks comes from shada } else { @@ -2087,7 +2092,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_winbar: (boolean) Evaluate winbar instead of statusline. -/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} +/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid} /// is ignored. Mutually exclusive with {use_winbar}. /// /// @param[out] err Error details, if any. @@ -2095,7 +2100,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - str: (string) Characters that will be displayed on the statusline. /// - width: (number) Display width of the statusline. /// - highlights: Array containing highlight information of the statusline. Only included when -/// the "highlights" key in {opts} is |TRUE|. Each element of the array is a +/// the "highlights" key in {opts} is true. Each element of the array is a /// |Dictionary| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. /// - group: (string) Name of highlight group. @@ -2227,7 +2232,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list - if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) { + if (hltab->start == NULL || (hltab->start - buf) != 0) { Dictionary hl_info = ARRAY_DICT_INIT; grpname = get_default_stl_hl(wp, use_winbar); diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 478e146781..f6d0e39327 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; @@ -132,7 +137,7 @@ Object nvim_eval(String expr, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } @@ -191,7 +196,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 969643eeef..96c560efa1 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; } @@ -202,14 +202,14 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) if (!win_new_float(win, false, fconfig, err)) { return; } - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } else { win_config_float(win, fconfig); win->w_pos_changed = true; } 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..5203003369 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" @@ -118,7 +118,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // make sure cursor is in visible range even if win != curwin update_topline_win(win); - redraw_later(win, VALID); + redraw_later(win, UPD_VALID); win->w_redr_status = true; } @@ -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, UPD_NOT_VALID); +} diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 06536e6e2b..b57019286f 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -320,22 +320,22 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c) int backward_combine = !prev_laa && can_join(prev_c, c); int forward_combine = can_join(c, next_c); - if (backward_combine && forward_combine) { - curr_c = (int)curr_a->medial; - } - if (backward_combine && !forward_combine) { - curr_c = (int)curr_a->final; - } - if (!backward_combine && forward_combine) { - curr_c = (int)curr_a->initial; - } - if (!backward_combine && !forward_combine) { - curr_c = (int)curr_a->isolated; + if (backward_combine) { + if (forward_combine) { + curr_c = (int)curr_a->medial; + } else { + curr_c = (int)curr_a->final; + } + } else { + if (forward_combine) { + curr_c = (int)curr_a->initial; + } else { + curr_c = (int)curr_a->isolated; + } } } - // Sanity check -- curr_c should, in the future, never be 0. - // We should, in the future, insert a fatal error here. + // Character missing from the table means using original character. if (curr_c == NUL) { curr_c = c; } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c new file mode 100644 index 0000000000..70b2e71949 --- /dev/null +++ b/src/nvim/arglist.c @@ -0,0 +1,1156 @@ +// 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_cmds.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 = 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(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 *), 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 *str, int *fcountp, char ***fnamesp, bool wig) +{ + garray_T ga; + int i; + + get_arglist(&ga, 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 = 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 **)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 *) * (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 = xstrdup(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 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, EvalFuncData 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, EvalFuncData fptr) +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/// "arglistid()" function +void f_arglistid(typval_T *argvars, typval_T *rettv, EvalFuncData 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, EvalFuncData 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..1c1de214cd 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/optionstr.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" @@ -681,7 +686,7 @@ const char *event_nr2name(event_T event) static bool event_ignored(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char *p = (char *)p_ei; + char *p = p_ei; while (*p != NUL) { if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { @@ -698,7 +703,7 @@ static bool event_ignored(event_T event) // Return OK when the contents of p_ei is valid, FAIL otherwise. int check_ei(void) { - char *p = (char *)p_ei; + char *p = p_ei; while (*p) { if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { @@ -719,8 +724,8 @@ int check_ei(void) // Returns the old value of 'eventignore' in allocated memory. char *au_event_disable(char *what) { - char *save_ei = (char *)vim_strsave(p_ei); - char *new_ei = (char *)vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + char *save_ei = xstrdup(p_ei); + char *new_ei = xstrnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); if (*what == ',' && *p_ei == NUL) { STRCPY(new_ei, what + 1); } else { @@ -1114,6 +1119,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) { curwin->w_last_topline = curwin->w_topline; curwin->w_last_leftcol = curwin->w_leftcol; + curwin->w_last_skipcol = curwin->w_skipcol; curwin->w_last_width = curwin->w_width; curwin->w_last_height = curwin->w_height; } @@ -1141,7 +1147,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; @@ -1715,9 +1721,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force fname = NULL; } else { if (event == EVENT_SYNTAX) { - fname = (char *)buf->b_p_syn; + fname = buf->b_p_syn; } else if (event == EVENT_FILETYPE) { - fname = (char *)buf->b_p_ft; + fname = buf->b_p_ft; } else { if (buf->b_sfname != NULL) { sfname = xstrdup(buf->b_sfname); @@ -1769,10 +1775,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; @@ -1807,16 +1812,15 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force char *tail = path_tail(fname); // Find first autocommand that matches - AutoPatCmd patcmd; - patcmd.curpat = first_autopat[(int)event]; - patcmd.nextcmd = NULL; - patcmd.group = group; - patcmd.fname = fname; - patcmd.sfname = sfname; - patcmd.tail = tail; - patcmd.event = event; - patcmd.arg_bufnr = autocmd_bufnr; - patcmd.next = NULL; + AutoPatCmd patcmd = { + .curpat = first_autopat[(int)event], + .group = group, + .fname = fname, + .sfname = sfname, + .tail = tail, + .event = event, + .arg_bufnr = autocmd_bufnr, + }; auto_next_pat(&patcmd, false); // found one, start executing the autocommands @@ -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; @@ -1982,7 +1985,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) AutoCmd *cp; char *s; - XFREE_CLEAR(sourcing_name); + estack_T *const entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + + // Clear the exestack entry for this ETYPE_AUCMD entry. + XFREE_CLEAR(entry->es_name); + entry->es_info.aucmd = NULL; for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { apc->curpat = NULL; @@ -2007,14 +2014,18 @@ 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); + char *const namep = xmalloc(sourcing_name_len); + snprintf(namep, sourcing_name_len, s, name, ap->pat); if (p_verbose >= 8) { verbose_enter(); - smsg(_("Executing %s"), sourcing_name); + smsg(_("Executing %s"), namep); verbose_leave(); } + // Update the exestack entry for this autocmd. + entry->es_name = namep; + entry->es_info.aucmd = apc; + apc->curpat = ap; apc->nextcmd = ap->cmds; // mark last command @@ -2047,7 +2058,7 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); if (apc->data) { - PUT(data, "data", copy_object(*apc->data)); + PUT(data, "data", copy_object(*apc->data, NULL)); } int group = apc->curpat->group; @@ -2146,6 +2157,7 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) // lua code, so that it works properly autocmd_nested = ac->nested; current_sctx = ac->script_ctx; + acp->script_ctx = current_sctx; if (ac->exec.type == CALLABLE_CB) { if (call_autocmd_callback(ac, acp)) { diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index a085a03455..75a8a7aaa1 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -22,19 +22,21 @@ 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 bool last; // last command in list int64_t id; // ID used for uniquely tracking an autocmd. - sctx_T script_ctx; // script context where defined + sctx_T script_ctx; // script context where it is 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 @@ -56,10 +59,11 @@ typedef struct AutoPatCmd { char *sfname; // sfname to match with char *tail; // tail of fname event_T event; // current event + sctx_T script_ctx; // script context where it is defined 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..514be4c56b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -25,17 +25,21 @@ #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" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/cursor.h" #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 +54,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" @@ -62,6 +67,7 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -69,9 +75,10 @@ #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/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -85,11 +92,6 @@ # include "buffer.c.generated.h" #endif -// Determines how deeply nested %{} blocks will be evaluated in statusline. -#define MAX_STL_EVAL_DEPTH 100 - -static char *msg_loclist = N_("[Location List]"); -static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); @@ -163,11 +165,12 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags) /// /// @param read_stdin read file from stdin /// @param eap for forced 'ff' and 'fenc' or NULL -/// @param flags extra flags for readfile() +/// @param flags_arg extra flags for readfile() /// /// @return FAIL for failure, OK otherwise. -int open_buffer(int read_stdin, exarg_T *eap, int flags) +int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) { + int flags = flags_arg; int retval = OK; bufref_T old_curbuf; long old_tw = curbuf->b_p_tw; @@ -222,6 +225,13 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) // mark cursor position as being invalid curwin->w_valid = 0; + // A buffer without an actual file should not use the buffer name to read a + // file. + if (bt_nofileread(curbuf)) { + flags |= READ_NOFILE; + } + + // Read the file if there is one. if (curbuf->b_ffname != NULL) { #ifdef UNIX int save_bin = curbuf->b_p_bin; @@ -793,8 +803,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 { @@ -802,6 +812,18 @@ static void free_buffer(buf_T *buf) } } +/// Free the b_wininfo list for buffer "buf". +static void clear_wininfo(buf_T *buf) +{ + wininfo_T *wip; + + while (buf->b_wininfo != NULL) { + wip = buf->b_wininfo; + buf->b_wininfo = wip->wi_next; + free_wininfo(wip, buf); + } +} + /// Free stuff in the buffer for ":bdel" and when wiping out the buffer. /// /// @param buf Buffer pointer @@ -836,18 +858,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_updates_unload(buf, false); } -/// Free the b_wininfo list for buffer "buf". -static void clear_wininfo(buf_T *buf) -{ - wininfo_T *wip; - - while (buf->b_wininfo != NULL) { - wip = buf->b_wininfo; - buf->b_wininfo = wip->wi_next; - free_wininfo(wip, buf); - } -} - /// Go to another buffer. Handles the result of the ATTENTION dialog. void goto_buffer(exarg_T *eap, int start, int dir, int count) { @@ -1532,6 +1542,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; @@ -1612,7 +1631,7 @@ void enter_buffer(buf_T *buf) } curbuf->b_last_used = time(NULL); - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Change to the directory of the current buffer. @@ -2595,7 +2614,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 +2622,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. @@ -3155,14 +3174,14 @@ void maketitle(void) use_sandbox = was_set_insecurely(curwin, "titlestring", 0); build_stl_str_hl(curwin, buf, sizeof(buf), - (char *)p_titlestring, use_sandbox, + p_titlestring, use_sandbox, 0, maxlen, NULL, NULL); title_str = buf; if (called_emsg > called_emsg_before) { set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR); } } else { - title_str = (char *)p_titlestring; + title_str = p_titlestring; } } else { // Format: "fname + (path) (1 of 2) - VIM". @@ -3269,13 +3288,13 @@ void maketitle(void) use_sandbox = was_set_insecurely(curwin, "iconstring", 0); build_stl_str_hl(curwin, icon_str, sizeof(buf), - (char *)p_iconstring, use_sandbox, + p_iconstring, use_sandbox, 0, 0, NULL, NULL); if (called_emsg > called_emsg_before) { set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR); } } else { - icon_str = (char *)p_iconstring; + icon_str = p_iconstring; } } else { char *buf_p; @@ -3346,1192 +3365,6 @@ void free_titles(void) #endif -/// Enumeration specifying the valid numeric bases that can -/// be used when printing numbers in the status line. -typedef enum { - kNumBaseDecimal = 10, - kNumBaseHexadecimal = 16, -} NumberBase; - -/// Build a string from the status line items in "fmt". -/// Return length of string in screen cells. -/// -/// Normally works for window "wp", except when working for 'tabline' then it -/// is "curwin". -/// -/// Items are drawn interspersed with the text that surrounds it -/// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation -/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional -/// -/// If maxwidth is not zero, the string will be filled at any middle marker -/// or truncated if too long, fillchar is used for all whitespace. -/// -/// @param wp The window to build a statusline for -/// @param out The output buffer to write the statusline to -/// Note: This should not be NameBuff -/// @param outlen The length of the output buffer -/// @param fmt The statusline format string -/// @param use_sandbox Use a sandboxed environment when evaluating fmt -/// @param fillchar Character to use when filling empty space in the statusline -/// @param maxwidth The maximum width to make the statusline -/// @param hltab HL attributes (can be NULL) -/// @param tabtab Tab clicks definition (can be NULL). -/// -/// @return The final width of the statusline -int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar, - int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) -{ - static size_t stl_items_len = 20; // Initial value, grows as needed. - static stl_item_t *stl_items = NULL; - static int *stl_groupitems = NULL; - static stl_hlrec_t *stl_hltab = NULL; - static StlClickRecord *stl_tabtab = NULL; - static int *stl_separator_locations = NULL; - -#define TMPLEN 70 - char buf_tmp[TMPLEN]; - char win_tmp[TMPLEN]; - char *usefmt = fmt; - const int save_must_redraw = must_redraw; - const int save_redr_type = curwin->w_redr_type; - - if (stl_items == NULL) { - stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); - stl_groupitems = xmalloc(sizeof(int) * stl_items_len); - - // Allocate one more, because the last element is used to indicate the - // end of the list. - stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); - stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); - - stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); - } - - // When the format starts with "%!" then evaluate it as an expression and - // use the result as the actual format string. - if (fmt[0] == '%' && fmt[1] == '!') { - typval_T tv = { - .v_type = VAR_NUMBER, - .vval.v_number = wp->handle, - }; - set_var(S_LEN("g:statusline_winid"), &tv, false); - - usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); - if (usefmt == NULL) { - usefmt = fmt; - } - - do_unlet(S_LEN("g:statusline_winid"), true); - } - - if (fillchar == 0) { - fillchar = ' '; - } - - // The cursor in windows other than the current one isn't always - // up-to-date, esp. because of autocommands and timers. - linenr_T lnum = wp->w_cursor.lnum; - if (lnum > wp->w_buffer->b_ml.ml_line_count) { - lnum = wp->w_buffer->b_ml.ml_line_count; - wp->w_cursor.lnum = lnum; - } - - // Get line & check if empty (cursorpos will show "0-1"). - const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false); - bool empty_line = (*line_ptr == NUL); - - // Get the byte value now, in case we need it below. This is more - // efficient than making a copy of the line. - int byteval; - const size_t len = STRLEN(line_ptr); - if (wp->w_cursor.col > (colnr_T)len) { - // Line may have changed since checking the cursor column, or the lnum - // was adjusted above. - wp->w_cursor.col = (colnr_T)len; - wp->w_cursor.coladd = 0; - byteval = 0; - } else { - byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); - } - - int groupdepth = 0; - int evaldepth = 0; - - int curitem = 0; - bool prevchar_isflag = true; - bool prevchar_isitem = false; - - // out_p is the current position in the output buffer - char *out_p = out; - - // out_end_p is the last valid character in the output buffer - // Note: The null termination character must occur here or earlier, - // so any user-visible characters must occur before here. - char *out_end_p = (out + outlen) - 1; - - // Proceed character by character through the statusline format string - // fmt_p is the current position in the input buffer - for (char *fmt_p = usefmt; *fmt_p != NUL;) { - if (curitem == (int)stl_items_len) { - size_t new_len = stl_items_len * 3 / 2; - - stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); - stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); - stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); - stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); - stl_separator_locations = - xrealloc(stl_separator_locations, sizeof(int) * new_len); - - stl_items_len = new_len; - } - - if (*fmt_p != '%') { - prevchar_isflag = prevchar_isitem = false; - } - - // Copy the formatting verbatim until we reach the end of the string - // or find a formatting item (denoted by `%`) - // or run out of room in our output buffer. - while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) { - *out_p++ = *fmt_p++; - } - - // If we have processed the entire format string or run out of - // room in our output buffer, exit the loop. - if (*fmt_p == NUL || out_p >= out_end_p) { - break; - } - - // The rest of this loop will handle a single `%` item. - // Note: We increment here to skip over the `%` character we are currently - // on so we can process the item's contents. - fmt_p++; - - // Ignore `%` at the end of the format string - if (*fmt_p == NUL) { - break; - } - - // Two `%` in a row is the escape sequence to print a - // single `%` in the output buffer. - if (*fmt_p == '%') { - *out_p++ = *fmt_p++; - prevchar_isflag = prevchar_isitem = false; - continue; - } - - // STL_SEPARATE: Separation place between left and right aligned items. - if (*fmt_p == STL_SEPARATE) { - fmt_p++; - // Ignored when we are inside of a grouping - if (groupdepth > 0) { - continue; - } - stl_items[curitem].type = Separate; - stl_items[curitem++].start = out_p; - continue; - } - - // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. - if (*fmt_p == STL_TRUNCMARK) { - fmt_p++; - stl_items[curitem].type = Trunc; - stl_items[curitem++].start = out_p; - continue; - } - - // The end of a grouping - if (*fmt_p == ')') { - fmt_p++; - // Ignore if we are not actually inside a group currently - if (groupdepth < 1) { - continue; - } - groupdepth--; - - // Determine how long the group is. - // Note: We set the current output position to null - // so `vim_strsize` will work. - char *t = stl_items[stl_groupitems[groupdepth]].start; - *out_p = NUL; - long group_len = vim_strsize(t); - - // If the group contained internal items - // and the group did not have a minimum width, - // and if there were no normal items in the group, - // move the output pointer back to where the group started. - // Note: This erases any non-item characters that were in the group. - // Otherwise there would be no reason to do this step. - if (curitem > stl_groupitems[groupdepth] + 1 - && stl_items[stl_groupitems[groupdepth]].minwid == 0) { - // remove group if all items are empty and highlight group - // doesn't change - int group_start_userhl = 0; - int group_end_userhl = 0; - int n; - for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { - if (stl_items[n].type == Highlight) { - group_start_userhl = group_end_userhl = stl_items[n].minwid; - break; - } - } - for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { - if (stl_items[n].type == Normal) { - break; - } - if (stl_items[n].type == Highlight) { - group_end_userhl = stl_items[n].minwid; - } - } - if (n == curitem && group_start_userhl == group_end_userhl) { - // empty group - out_p = t; - group_len = 0; - for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { - // do not use the highlighting from the removed group - if (stl_items[n].type == Highlight) { - stl_items[n].type = Empty; - } - // adjust the start position of TabPage to the next - // item position - if (stl_items[n].type == TabPage) { - stl_items[n].start = out_p; - } - } - } - } - - // If the group is longer than it is allowed to be - // truncate by removing bytes from the start of the group text. - if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { - // { Determine the number of bytes to remove - - // Find the first character that should be included. - long n = 0; - while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { - group_len -= ptr2cells(t + n); - n += utfc_ptr2len(t + n); - } - // } - - // Prepend the `<` to indicate that the output was truncated. - *t = '<'; - - // { Move the truncated output - memmove(t + 1, t + n, (size_t)(out_p - (t + n))); - out_p = out_p - n + 1; - // Fill up space left over by half a double-wide char. - while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { - MB_CHAR2BYTES(fillchar, out_p); - } - // } - - // correct the start of the items for the truncation - for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { - // Shift everything back by the number of removed bytes - // Minus one for the leading '<' added above. - stl_items[idx].start -= n - 1; - - // If the item was partially or completely truncated, set its - // start to the start of the group - if (stl_items[idx].start < t) { - stl_items[idx].start = t; - } - } - // If the group is shorter than the minimum width, add padding characters. - } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { - long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; - // If the group is left-aligned, add characters to the right. - if (min_group_width < 0) { - min_group_width = 0 - min_group_width; - while (group_len++ < min_group_width && out_p < out_end_p) { - MB_CHAR2BYTES(fillchar, out_p); - } - // If the group is right-aligned, shift everything to the right and - // prepend with filler characters. - } else { - // { Move the group to the right - group_len = (min_group_width - group_len) * utf_char2len(fillchar); - memmove(t + group_len, t, (size_t)(out_p - t)); - if (out_p + group_len >= (out_end_p + 1)) { - group_len = (long)(out_end_p - out_p); - } - out_p += group_len; - // } - - // Adjust item start positions - for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { - stl_items[n].start += group_len; - } - - // Prepend the fill characters - for (; group_len > 0; group_len--) { - MB_CHAR2BYTES(fillchar, t); - } - } - } - continue; - } - int minwid = 0; - int maxwid = 9999; - bool left_align = false; - - // Denotes that numbers should be left-padded with zeros - bool zeropad = (*fmt_p == '0'); - if (zeropad) { - fmt_p++; - } - - // Denotes that the item should be left-aligned. - // This is tracked by using a negative length. - if (*fmt_p == '-') { - fmt_p++; - left_align = true; - } - - // The first digit group is the item's min width - if (ascii_isdigit(*fmt_p)) { - minwid = getdigits_int(&fmt_p, false, 0); - } - - // User highlight groups override the min width field - // to denote the styling to use. - if (*fmt_p == STL_USER_HL) { - stl_items[curitem].type = Highlight; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; - fmt_p++; - curitem++; - continue; - } - - // TABPAGE pairs are used to denote a region that when clicked will - // either switch to or close a tab. - // - // Ex: tabline=%0Ttab\ zero%X - // This tabline has a TABPAGENR item with minwid `0`, - // which is then closed with a TABCLOSENR item. - // Clicking on this region with mouse enabled will switch to tab 0. - // Setting the minwid to a different value will switch - // to that tab, if it exists - // - // Ex: tabline=%1Xtab\ one%X - // This tabline has a TABCLOSENR item with minwid `1`, - // which is then closed with a TABCLOSENR item. - // Clicking on this region with mouse enabled will close tab 0. - // This is determined by the following formula: - // tab to close = (1 - minwid) - // This is because for TABPAGENR we use `minwid` = `tab number`. - // For TABCLOSENR we store the tab number as a negative value. - // Because 0 is a valid TABPAGENR value, we have to - // start our numbering at `-1`. - // So, `-1` corresponds to us wanting to close tab `0` - // - // Note: These options are only valid when creating a tabline. - if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { - if (*fmt_p == STL_TABCLOSENR) { - if (minwid == 0) { - // %X ends the close label, go back to the previous tab label nr. - for (long n = curitem - 1; n >= 0; n--) { - if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { - minwid = stl_items[n].minwid; - break; - } - } - } else { - // close nrs are stored as negative values - minwid = -minwid; - } - } - stl_items[curitem].type = TabPage; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = minwid; - fmt_p++; - curitem++; - continue; - } - - if (*fmt_p == STL_CLICK_FUNC) { - fmt_p++; - char *t = fmt_p; - while (*fmt_p != STL_CLICK_FUNC && *fmt_p) { - fmt_p++; - } - if (*fmt_p != STL_CLICK_FUNC) { - break; - } - stl_items[curitem].type = ClickFunc; - stl_items[curitem].start = out_p; - stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t)); - stl_items[curitem].minwid = minwid; - fmt_p++; - curitem++; - continue; - } - - // Denotes the end of the minwid - // the maxwid may follow immediately after - if (*fmt_p == '.') { - fmt_p++; - if (ascii_isdigit(*fmt_p)) { - maxwid = getdigits_int(&fmt_p, false, 50); - } - } - - // Bound the minimum width at 50. - // Make the number negative to denote left alignment of the item - minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1); - - // Denotes the start of a new group - if (*fmt_p == '(') { - stl_groupitems[groupdepth++] = curitem; - stl_items[curitem].type = Group; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = minwid; - stl_items[curitem].maxwid = maxwid; - fmt_p++; - curitem++; - continue; - } - - // Denotes end of expanded %{} block - if (*fmt_p == '}' && evaldepth > 0) { - fmt_p++; - evaldepth--; - continue; - } - - // An invalid item was specified. - // Continue processing on the next character of the format string. - if (vim_strchr(STL_ALL, *fmt_p) == NULL) { - fmt_p++; - continue; - } - - // The status line item type - char opt = *fmt_p++; - - // OK - now for the real work - NumberBase base = kNumBaseDecimal; - bool itemisflag = false; - bool fillable = true; - long num = -1; - char *str = NULL; - switch (opt) { - case STL_FILEPATH: - case STL_FULLPATH: - case STL_FILENAME: - // Set fillable to false so that ' ' in the filename will not - // get replaced with the fillchar - fillable = false; - if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); - } else { - char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname - : wp->w_buffer->b_fname; - home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true); - } - trans_characters((char *)NameBuff, MAXPATHL); - if (opt != STL_FILENAME) { - str = (char *)NameBuff; - } else { - str = path_tail((char *)NameBuff); - } - break; - case STL_VIM_EXPR: // '{' - { - char *block_start = fmt_p - 1; - int reevaluate = (*fmt_p == '%'); - itemisflag = true; - - if (reevaluate) { - fmt_p++; - } - - // Attempt to copy the expression to evaluate into - // the output buffer as a null-terminated string. - char *t = out_p; - while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) - && *fmt_p != NUL && out_p < out_end_p) { - *out_p++ = *fmt_p++; - } - if (*fmt_p != '}') { // missing '}' or out of space - break; - } - fmt_p++; - if (reevaluate) { - out_p[-1] = 0; // remove the % at the end of %{% expr %} - } else { - *out_p = 0; - } - - // Move our position in the output buffer - // to the beginning of the expression - out_p = t; - - // { Evaluate the expression - - // Store the current buffer number as a string variable - vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); - set_internal_string_var("g:actual_curbuf", buf_tmp); - vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); - set_internal_string_var("g:actual_curwin", (char *)win_tmp); - - buf_T *const save_curbuf = curbuf; - win_T *const save_curwin = curwin; - const int save_VIsual_active = VIsual_active; - curwin = wp; - curbuf = wp->w_buffer; - // Visual mode is only valid in the current window. - if (curwin != save_curwin) { - VIsual_active = false; - } - - // Note: The result stored in `t` is unused. - str = eval_to_string_safe(out_p, &t, use_sandbox); - - curwin = save_curwin; - curbuf = save_curbuf; - VIsual_active = save_VIsual_active; - - // Remove the variable we just stored - do_unlet(S_LEN("g:actual_curbuf"), true); - do_unlet(S_LEN("g:actual_curwin"), true); - - // } - - // Check if the evaluated result is a number. - // If so, convert the number to an int and free the string. - if (str != NULL && *str != 0) { - if (*skipdigits(str) == NUL) { - num = atoi(str); - XFREE_CLEAR(str); - itemisflag = false; - } - } - - // If the output of the expression needs to be evaluated - // replace the %{} block with the result of evaluation - if (reevaluate && str != NULL && *str != 0 - && strchr((const char *)str, '%') != NULL - && evaldepth < MAX_STL_EVAL_DEPTH) { - size_t parsed_usefmt = (size_t)(block_start - usefmt); - size_t str_length = STRLEN(str); - size_t fmt_length = STRLEN(fmt_p); - size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; - char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); - char *new_fmt_p = new_fmt; - - new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt; - new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length; - new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2; - new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length; - *new_fmt_p = 0; - new_fmt_p = NULL; - - if (usefmt != fmt) { - xfree(usefmt); - } - XFREE_CLEAR(str); - usefmt = new_fmt; - fmt_p = usefmt + parsed_usefmt; - evaldepth++; - continue; - } - break; - } - - case STL_LINE: - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) - ? 0L : (long)(wp->w_cursor.lnum); - break; - - case STL_NUMLINES: - num = wp->w_buffer->b_ml.ml_line_count; - break; - - case STL_COLUMN: - num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1; - break; - - case STL_VIRTCOL: - case STL_VIRTCOL_ALT: { - colnr_T virtcol = wp->w_virtcol + 1; - // Don't display %V if it's the same as %c. - if (opt == STL_VIRTCOL_ALT - && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line - ? 0 : (int)wp->w_cursor.col + 1))) { - break; - } - num = (long)virtcol; - break; - } - - case STL_PERCENTAGE: - num = (int)(((long)wp->w_cursor.lnum * 100L) / - (long)wp->w_buffer->b_ml.ml_line_count); - break; - - case STL_ALTPERCENT: - // Store the position percentage in our temporary buffer. - // Note: We cannot store the value in `num` because - // `get_rel_pos` can return a named position. Ex: "Top" - get_rel_pos(wp, buf_tmp, TMPLEN); - str = buf_tmp; - break; - - case STL_ARGLISTSTAT: - fillable = false; - - // Note: This is important because `append_arg_number` starts appending - // at the end of the null-terminated string. - // Setting the first byte to null means it will place the argument - // number string at the beginning of the buffer. - buf_tmp[0] = 0; - - // Note: The call will only return true if it actually - // appended data to the `buf_tmp` buffer. - if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { - str = buf_tmp; - } - break; - - case STL_KEYMAP: - fillable = false; - if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) { - str = buf_tmp; - } - break; - case STL_PAGENUM: - num = printer_page_num; - break; - - case STL_BUFNO: - num = wp->w_buffer->b_fnum; - break; - - case STL_OFFSET_X: - base = kNumBaseHexadecimal; - FALLTHROUGH; - case STL_OFFSET: { - long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, - false); - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? - 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ? - 0 : (int)wp->w_cursor.col); - break; - } - case STL_BYTEVAL_X: - base = kNumBaseHexadecimal; - FALLTHROUGH; - case STL_BYTEVAL: - num = byteval; - if (num == NL) { - num = 0; - } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { - num = NL; - } - break; - - case STL_ROFLAG: - case STL_ROFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_p_ro) { - str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); - } - break; - - case STL_HELPFLAG: - case STL_HELPFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_help) { - str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]"); - } - break; - - case STL_FILETYPE: - // Copy the filetype if it is not null and the formatted string will fit - // in the temporary buffer - // (including the brackets and null terminating character) - if (*wp->w_buffer->b_p_ft != NUL - && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { - vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", - wp->w_buffer->b_p_ft); - str = buf_tmp; - } - break; - - case STL_FILETYPE_ALT: - itemisflag = true; - // Copy the filetype if it is not null and the formatted string will fit - // in the temporary buffer - // (including the comma and null terminating character) - if (*wp->w_buffer->b_p_ft != NUL - && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { - vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft); - // Uppercase the file extension - for (char *t = buf_tmp; *t != 0; t++) { - *t = (char)TOUPPER_LOC(*t); - } - str = buf_tmp; - } - break; - case STL_PREVIEWFLAG: - case STL_PREVIEWFLAG_ALT: - itemisflag = true; - if (wp->w_p_pvw) { - str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]"); - } - break; - - case STL_QUICKFIX: - if (bt_quickfix(wp->w_buffer)) { - str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist); - } - break; - - case STL_MODIFIED: - case STL_MODIFIED_ALT: - itemisflag = true; - switch ((opt == STL_MODIFIED_ALT) - + bufIsChanged(wp->w_buffer) * 2 - + (!MODIFIABLE(wp->w_buffer)) * 4) { - case 2: - str = "[+]"; break; - case 3: - str = ",+"; break; - case 4: - str = "[-]"; break; - case 5: - str = ",-"; break; - case 6: - str = "[+-]"; break; - case 7: - str = ",+-"; break; - } - break; - - case STL_HIGHLIGHT: { - // { The name of the highlight is surrounded by `#` - char *t = fmt_p; - while (*fmt_p != '#' && *fmt_p != NUL) { - fmt_p++; - } - // } - - // Create a highlight item based on the name - if (*fmt_p == '#') { - stl_items[curitem].type = Highlight; - stl_items[curitem].start = out_p; - stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t)); - curitem++; - fmt_p++; - } - continue; - } - } - - // If we made it this far, the item is normal and starts at - // our current position in the output buffer. - // Non-normal items would have `continued`. - stl_items[curitem].start = out_p; - stl_items[curitem].type = Normal; - - // Copy the item string into the output buffer - if (str != NULL && *str) { - // { Skip the leading `,` or ` ` if the item is a flag - // and the proper conditions are met - char *t = str; - if (itemisflag) { - if ((t[0] && t[1]) - && ((!prevchar_isitem && *t == ',') - || (prevchar_isflag && *t == ' '))) { - t++; - } - prevchar_isflag = true; - } - // } - - long l = vim_strsize(t); - - // If this item is non-empty, record that the last thing - // we put in the output buffer was an item - if (l > 0) { - prevchar_isitem = true; - } - - // If the item is too wide, truncate it from the beginning - if (l > maxwid) { - while (l >= maxwid) { - l -= ptr2cells(t); - t += utfc_ptr2len(t); - } - - // Early out if there isn't enough room for the truncation marker - if (out_p >= out_end_p) { - break; - } - - // Add the truncation marker - *out_p++ = '<'; - } - - // If the item is right aligned and not wide enough, - // pad with fill characters. - if (minwid > 0) { - for (; l < minwid && out_p < out_end_p; l++) { - // Don't put a "-" in front of a digit. - if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { - *out_p++ = ' '; - } else { - MB_CHAR2BYTES(fillchar, out_p); - } - } - minwid = 0; - } else { - // Note: The negative value denotes a left aligned item. - // Here we switch the minimum width back to a positive value. - minwid *= -1; - } - - // { Copy the string text into the output buffer - for (; *t && out_p < out_end_p; t++) { - // Change a space by fillchar, unless fillchar is '-' and a - // digit follows. - if (fillable && *t == ' ' - && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { - MB_CHAR2BYTES(fillchar, out_p); - } else { - *out_p++ = *t; - } - } - // } - - // For left-aligned items, fill any remaining space with the fillchar - for (; l < minwid && out_p < out_end_p; l++) { - MB_CHAR2BYTES(fillchar, out_p); - } - - // Otherwise if the item is a number, copy that to the output buffer. - } else if (num >= 0) { - if (out_p + 20 > out_end_p) { - break; // not sufficient space - } - prevchar_isitem = true; - - // { Build the formatting string - char nstr[20]; - char *t = nstr; - if (opt == STL_VIRTCOL_ALT) { - *t++ = '-'; - minwid--; - } - *t++ = '%'; - if (zeropad) { - *t++ = '0'; - } - - // Note: The `*` means we take the width as one of the arguments - *t++ = '*'; - *t++ = base == kNumBaseHexadecimal ? 'X' : 'd'; - *t = 0; - // } - - // { Determine how many characters the number will take up when printed - // Note: We have to cast the base because the compiler uses - // unsigned ints for the enum values. - long num_chars = 1; - for (long n = num; n >= (int)base; n /= (int)base) { - num_chars++; - } - - // VIRTCOL_ALT takes up an extra character because - // of the `-` we added above. - if (opt == STL_VIRTCOL_ALT) { - num_chars++; - } - // } - - assert(out_end_p >= out_p); - size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; - - // If the number is going to take up too much room - // Figure out the approximate number in "scientific" type notation. - // Ex: 14532 with maxwid of 4 -> '14>3' - if (num_chars > maxwid) { - // Add two to the width because the power piece will take - // two extra characters - num_chars += 2; - - // How many extra characters there are - long n = num_chars - maxwid; - - // { Reduce the number by base^n - while (num_chars-- > maxwid) { - num /= (long)base; - } - // } - - // { Add the format string for the exponent bit - *t++ = '>'; - *t++ = '%'; - // Use the same base as the first number - *t = t[-3]; - *++t = 0; - // } - - vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n); - } else { - vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num); - } - - // Advance the output buffer position to the end of the - // number we just printed - out_p += STRLEN(out_p); - - // Otherwise, there was nothing to print so mark the item as empty - } else { - stl_items[curitem].type = Empty; - } - - // Only free the string buffer if we allocated it. - // Note: This is not needed if `str` is pointing at `tmp` - if (opt == STL_VIM_EXPR) { - XFREE_CLEAR(str); - } - - if (num >= 0 || (!itemisflag && str && *str)) { - prevchar_isflag = false; // Item not NULL, but not a flag - } - - // Item processed, move to the next - curitem++; - } - - *out_p = NUL; - int itemcnt = curitem; - - // Free the format buffer if we allocated it internally - if (usefmt != fmt) { - xfree(usefmt); - } - - // We have now processed the entire statusline format string. - // What follows is post-processing to handle alignment and highlighting. - - int width = vim_strsize(out); - if (maxwidth > 0 && width > maxwidth) { - // Result is too long, must truncate somewhere. - int item_idx = 0; - char *trunc_p; - - // If there are no items, truncate from beginning - if (itemcnt == 0) { - trunc_p = out; - - // Otherwise, look for the truncation item - } else { - // Default to truncating at the first item - trunc_p = stl_items[0].start; - item_idx = 0; - - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].type == Trunc) { - // Truncate at %< stl_items. - trunc_p = stl_items[i].start; - item_idx = i; - break; - } - } - } - - // If the truncation point we found is beyond the maximum - // length of the string, truncate the end of the string. - if (width - vim_strsize(trunc_p) >= maxwidth) { - // Walk from the beginning of the - // string to find the last character that will fit. - trunc_p = out; - width = 0; - for (;;) { - width += ptr2cells(trunc_p); - if (width >= maxwidth) { - break; - } - - // Note: Only advance the pointer if the next - // character will fit in the available output space - trunc_p += utfc_ptr2len(trunc_p); - } - - // Ignore any items in the statusline that occur after - // the truncation point - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].start > trunc_p) { - itemcnt = i; - break; - } - } - - // Truncate the output - *trunc_p++ = '>'; - *trunc_p = 0; - - // Truncate at the truncation point we found - } else { - // { Determine how many bytes to remove - long trunc_len = 0; - while (width >= maxwidth) { - width -= ptr2cells(trunc_p + trunc_len); - trunc_len += utfc_ptr2len(trunc_p + trunc_len); - } - // } - - // { Truncate the string - char *trunc_end_p = trunc_p + trunc_len; - STRMOVE(trunc_p + 1, trunc_end_p); - - // Put a `<` to mark where we truncated at - *trunc_p = '<'; - - if (width + 1 < maxwidth) { - // Advance the pointer to the end of the string - trunc_p = trunc_p + STRLEN(trunc_p); - } - - // Fill up for half a double-wide character. - while (++width < maxwidth) { - MB_CHAR2BYTES(fillchar, trunc_p); - *trunc_p = NUL; - } - // } - - // { Change the start point for items based on - // their position relative to our truncation point - - // Note: The offset is one less than the truncation length because - // the truncation marker `<` is not counted. - long item_offset = trunc_len - 1; - - for (int i = item_idx; i < itemcnt; i++) { - // Items starting at or after the end of the truncated section need - // to be moved backwards. - if (stl_items[i].start >= trunc_end_p) { - stl_items[i].start -= item_offset; - // Anything inside the truncated area is set to start - // at the `<` truncation character. - } else { - stl_items[i].start = trunc_p; - } - } - // } - } - width = maxwidth; - - // If there is room left in our statusline, and room left in our buffer, - // add characters at the separate marker (if there is one) to - // fill up the available space. - } else if (width < maxwidth - && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) { - // Find how many separators there are, which we will use when - // figuring out how many groups there are. - int num_separators = 0; - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].type == Separate) { - // Create an array of the start location for each - // separator mark. - stl_separator_locations[num_separators] = i; - num_separators++; - } - } - - // If we have separated groups, then we deal with it now - if (num_separators) { - int standard_spaces = (maxwidth - width) / num_separators; - int final_spaces = (maxwidth - width) - - standard_spaces * (num_separators - 1); - - for (int i = 0; i < num_separators; i++) { - int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; - dislocation *= utf_char2len(fillchar); - char *start = stl_items[stl_separator_locations[i]].start; - char *seploc = start + dislocation; - STRMOVE(seploc, start); - for (char *s = start; s < seploc;) { - MB_CHAR2BYTES(fillchar, s); - } - - for (int item_idx = stl_separator_locations[i] + 1; - item_idx < itemcnt; - item_idx++) { - stl_items[item_idx].start += dislocation; - } - } - - width = maxwidth; - } - } - - // Store the info about highlighting. - if (hltab != NULL) { - *hltab = stl_hltab; - stl_hlrec_t *sp = stl_hltab; - for (long l = 0; l < itemcnt; l++) { - if (stl_items[l].type == Highlight) { - sp->start = stl_items[l].start; - sp->userhl = stl_items[l].minwid; - sp++; - } - } - sp->start = NULL; - sp->userhl = 0; - } - - // Store the info about tab pages labels. - if (tabtab != NULL) { - *tabtab = stl_tabtab; - StlClickRecord *cur_tab_rec = stl_tabtab; - for (long l = 0; l < itemcnt; l++) { - if (stl_items[l].type == TabPage) { - cur_tab_rec->start = stl_items[l].start; - if (stl_items[l].minwid == 0) { - cur_tab_rec->def.type = kStlClickDisabled; - cur_tab_rec->def.tabnr = 0; - } else { - int tabnr = stl_items[l].minwid; - if (stl_items[l].minwid > 0) { - cur_tab_rec->def.type = kStlClickTabSwitch; - } else { - cur_tab_rec->def.type = kStlClickTabClose; - tabnr = -tabnr; - } - cur_tab_rec->def.tabnr = tabnr; - } - cur_tab_rec->def.func = NULL; - cur_tab_rec++; - } else if (stl_items[l].type == ClickFunc) { - cur_tab_rec->start = stl_items[l].start; - cur_tab_rec->def.type = kStlClickFuncRun; - cur_tab_rec->def.tabnr = stl_items[l].minwid; - cur_tab_rec->def.func = stl_items[l].cmd; - cur_tab_rec++; - } - } - cur_tab_rec->start = NULL; - cur_tab_rec->def.type = kStlClickDisabled; - cur_tab_rec->def.tabnr = 0; - cur_tab_rec->def.func = NULL; - } - - // When inside update_screen we do not want redrawing a statusline, ruler, - // title, etc. to trigger another redraw, it may cause an endless loop. - if (updating_screen) { - must_redraw = save_must_redraw; - curwin->w_redr_type = save_redr_type; - } - - return width; -} - /// Get relative cursor position in window into "buf[buflen]", in the form 99%, /// using "Top", "Bot" or "All" when appropriate. void get_rel_pos(win_T *wp, char *buf, int buflen) @@ -4571,7 +3404,7 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) /// @param add_file if true, add "file" before the arg number /// /// @return true if it was appended. -static bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) +bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) FUNC_ATTR_NONNULL_ALL { // Nothing to do @@ -4628,279 +3461,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 +3699,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 +3743,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 +3784,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 +3799,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; @@ -5280,7 +3834,8 @@ bool bt_terminal(const buf_T *const buf) } /// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" -/// buffer. This means the buffer name is not a file name. +/// buffer. This means the buffer name may not be a file name, +/// at least not for writing the buffer. bool bt_nofilename(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5290,6 +3845,17 @@ bool bt_nofilename(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } +/// @return true if "buf" is a "nofile", "quickfix", "terminal" or "prompt" +/// buffer. This means the buffer is not to be read from a file. +static bool bt_nofileread(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'q' + || buf->b_p_bt[0] == 'p'); +} + /// @return true if "buf" has 'buftype' set to "nofile". bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -5488,7 +4054,7 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) buf->b_signcols.max++; } buf->b_signcols.size++; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); return; } @@ -5509,7 +4075,7 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) buf->b_signcols.size = linesum; buf->b_signcols.max = linesum; buf->b_signcols.sentinel = added->se_lnum; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } } @@ -5528,7 +4094,7 @@ int buf_signcols(buf_T *buf, int maximum) if (signcols != buf->b_signcols.size) { buf->b_signcols.size = signcols; buf->b_signcols.max = maximum; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } buf->b_signcols.valid = true; @@ -5637,14 +4203,19 @@ void wipe_buffer(buf_T *buf, bool aucmd) /// @param bufnr Buffer to switch to, or 0 to create a new buffer. /// /// @see curbufIsChanged() -void buf_open_scratch(handle_T bufnr, char *bufname) +/// +/// @return FAIL for failure, OK otherwise +int buf_open_scratch(handle_T bufnr, char *bufname) { - (void)do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + if (do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL) == FAIL) { + return FAIL; + } apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); (void)setfname(curbuf, bufname, NULL, true); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); - set_option_value("bh", 0L, "hide", OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL); + set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); RESET_BINDING(curwin); + return OK; } diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index b452eb227e..d56a70dc7e 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 { @@ -61,6 +62,9 @@ enum bfa_values { BFA_IGNORE_ABORT = 8, // do not abort for aborting() }; +EXTERN char *msg_loclist INIT(= N_("[Location List]")); +EXTERN char *msg_qflist INIT(= N_("[Quickfix List]")); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer.h.generated.h" #endif diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 9457051947..748e94f0a1 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) @@ -118,11 +116,11 @@ typedef uint64_t disptick_T; // display tick type * The taggy struct is used to store the information about a :tag command. */ typedef struct taggy { - char_u *tagname; // tag name + char *tagname; // tag name fmark_T fmark; // cursor position BEFORE ":tag" int cur_match; // match number int cur_fnum; // buffer number used for cur_match - char_u *user_data; // used with tagfunc + char *user_data; // used with tagfunc } taggy_T; typedef struct buffblock buffblock_T; @@ -133,7 +131,7 @@ typedef struct buffheader buffheader_T; */ struct buffblock { buffblock_T *b_next; // pointer to next buffblock - char_u b_str[1]; // contents (actually longer) + char b_str[1]; // contents (actually longer) }; /* @@ -161,39 +159,39 @@ typedef struct { #define w_p_arab w_onebuf_opt.wo_arab // 'arabic' int wo_bri; #define w_p_bri w_onebuf_opt.wo_bri // 'breakindent' - char_u *wo_briopt; + char *wo_briopt; #define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt' int wo_diff; #define w_p_diff w_onebuf_opt.wo_diff // 'diff' - char_u *wo_fdc; + char *wo_fdc; #define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn' - char_u *wo_fdc_save; + char *wo_fdc_save; #define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode int wo_fen; #define w_p_fen w_onebuf_opt.wo_fen // 'foldenable' int wo_fen_save; // 'foldenable' saved for diff mode #define w_p_fen_save w_onebuf_opt.wo_fen_save - char_u *wo_fdi; + char *wo_fdi; #define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore' long wo_fdl; #define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel' long wo_fdl_save; // 'foldlevel' state saved for diff mode #define w_p_fdl_save w_onebuf_opt.wo_fdl_save - char_u *wo_fdm; + char *wo_fdm; #define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod' - char_u *wo_fdm_save; + char *wo_fdm_save; #define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode long wo_fml; #define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines' long wo_fdn; #define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax' - char_u *wo_fde; + char *wo_fde; #define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr' - char_u *wo_fdt; + char *wo_fdt; #define w_p_fdt w_onebuf_opt.wo_fdt // 'foldtext' - char_u *wo_fmr; + char *wo_fmr; #define w_p_fmr w_onebuf_opt.wo_fmr // 'foldmarker' int wo_lbr; #define w_p_lbr w_onebuf_opt.wo_lbr // 'linebreak' @@ -203,7 +201,7 @@ typedef struct { #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; #define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' - char_u *wo_ve; + char *wo_ve; #define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit' unsigned wo_ve_flags; #define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' @@ -217,7 +215,7 @@ typedef struct { #define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow' int wo_rl; #define w_p_rl w_onebuf_opt.wo_rl // 'rightleft' - char_u *wo_rlc; + char *wo_rlc; #define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd' long wo_scr; #define w_p_scr w_onebuf_opt.wo_scr // 'scroll' @@ -227,13 +225,13 @@ typedef struct { #define w_p_cuc w_onebuf_opt.wo_cuc // 'cursorcolumn' int wo_cul; #define w_p_cul w_onebuf_opt.wo_cul // 'cursorline' - char_u *wo_culopt; + char *wo_culopt; #define w_p_culopt w_onebuf_opt.wo_culopt // 'cursorlineopt' - char_u *wo_cc; + char *wo_cc; #define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' - char_u *wo_sbr; + char *wo_sbr; #define w_p_sbr w_onebuf_opt.wo_sbr // 'showbreak' - char_u *wo_stl; + char *wo_stl; #define w_p_stl w_onebuf_opt.wo_stl // 'statusline' char *wo_wbr; #define w_p_wbr w_onebuf_opt.wo_wbr // 'winbar' @@ -247,7 +245,7 @@ typedef struct { #define w_p_wrap w_onebuf_opt.wo_wrap // 'wrap' int wo_wrap_save; // 'wrap' state saved for diff mode #define w_p_wrap_save w_onebuf_opt.wo_wrap_save - char_u *wo_cocu; // 'concealcursor' + char *wo_cocu; // 'concealcursor' #define w_p_cocu w_onebuf_opt.wo_cocu long wo_cole; // 'conceallevel' #define w_p_cole w_onebuf_opt.wo_cole @@ -255,14 +253,14 @@ typedef struct { #define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind' int wo_crb_save; // 'cursorbind' state saved for diff mode #define w_p_crb_save w_onebuf_opt.wo_crb_save - char_u *wo_scl; + char *wo_scl; #define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn' - char_u *wo_winhl; + char *wo_winhl; #define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight' - char_u *wo_fcs; -#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' - char_u *wo_lcs; + char *wo_lcs; #define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars' + char *wo_fcs; +#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' long wo_winbl; #define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend' @@ -310,8 +308,8 @@ typedef struct arglist { // // TODO(Felipe): move aentry_T to another header typedef struct argentry { - char_u *ae_fname; // file name as specified - int ae_fnum; // buffer number with expanded file name + char *ae_fname; // file name as specified + int ae_fnum; // buffer number with expanded file name } aentry_T; #define ALIST(win) (win)->w_alist @@ -327,8 +325,8 @@ typedef struct argentry { * Used for the typeahead buffer: typebuf. */ typedef struct { - char_u *tb_buf; // buffer for typed characters - char_u *tb_noremap; // mapping flags for characters in tb_buf[] + uint8_t *tb_buf; // buffer for typed characters + uint8_t *tb_noremap; // mapping flags for characters in tb_buf[] int tb_buflen; // size of tb_buf[] int tb_off; // current position in tb_buf[] int tb_len; // number of valid bytes in tb_buf[] @@ -354,11 +352,11 @@ typedef struct { */ typedef struct mapblock mapblock_T; struct mapblock { - mapblock_T *m_next; // next mapblock in list - char_u *m_keys; // mapped from, lhs - char_u *m_str; // mapped to, rhs - char_u *m_orig_str; // rhs as entered by the user - LuaRef m_luaref; // lua function reference as rhs + mapblock_T *m_next; // next mapblock in list + uint8_t *m_keys; // mapped from, lhs + char *m_str; // mapped to, rhs + char *m_orig_str; // rhs as entered by the user + LuaRef m_luaref; // lua function reference as rhs int m_keylen; // strlen(m_keys) int m_mode; // valid mode int m_simplified; // m_keys was simplified, do no use this map @@ -368,8 +366,8 @@ struct mapblock { char m_nowait; // <nowait> used char m_expr; // <expr> used, m_str is an expression sctx_T m_script_ctx; // SCTX where map was defined - char *m_desc; // description of keymap - bool m_replace_keycodes; // replace termcodes in lua function + char *m_desc; // description of mapping + bool m_replace_keycodes; // replace keycodes in result of expression }; /// Used for highlighting in the status line. @@ -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 @@ -442,14 +440,14 @@ typedef struct { garray_T b_syn_clusters; // table for syntax clusters int b_spell_cluster_id; // @Spell cluster ID or 0 int b_nospell_cluster_id; // @NoSpell cluster ID or 0 - int b_syn_containedin; // TRUE when there is an item with a + int b_syn_containedin; // true when there is an item with a // "containedin" argument int b_syn_sync_flags; // flags about how to sync int16_t b_syn_sync_id; // group to sync on linenr_T b_syn_sync_minlines; // minimal sync lines offset linenr_T b_syn_sync_maxlines; // maximal sync lines offset linenr_T b_syn_sync_linebreaks; // offset for multi-line pattern - char_u *b_syn_linecont_pat; // line continuation pattern + char *b_syn_linecont_pat; // line continuation pattern regprog_T *b_syn_linecont_prog; // line continuation program syn_time_T b_syn_linecont_time; int b_syn_linecont_ic; // ignore-case flag for above @@ -478,17 +476,17 @@ typedef struct { disptick_T b_sst_lasttick; // last display tick // for spell checking - garray_T b_langp; // list of pointers to slang_T, see spell.c - bool b_spell_ismw[256]; // flags: is midword char - char_u *b_spell_ismw_mb; // multi-byte midword chars - char_u *b_p_spc; // 'spellcapcheck' + garray_T b_langp; // list of pointers to slang_T, see spell.c + bool b_spell_ismw[256]; // flags: is midword char + char *b_spell_ismw_mb; // multi-byte midword chars + char *b_p_spc; // 'spellcapcheck' regprog_T *b_cap_prog; // program for 'spellcapcheck' - char_u *b_p_spf; // 'spellfile' - char_u *b_p_spl; // 'spelllang' - char_u *b_p_spo; // 'spelloptions' - int b_cjk; // all CJK letters as OK - char_u b_syn_chartab[32]; // syntax iskeyword option - char_u *b_syn_isk; // iskeyword option + char *b_p_spf; // 'spellfile' + char *b_p_spl; // 'spelllang' + char *b_p_spo; // 'spelloptions' + int b_cjk; // all CJK letters as OK + uint8_t b_syn_chartab[32]; // syntax iskeyword option + char *b_syn_isk; // iskeyword option } synblock_T; /// Type used for changedtick_di member in buf_T @@ -602,7 +600,7 @@ struct file_buffer { fmark_T b_namedm[NMARKS]; // current named marks (mark.c) - // These variables are set when VIsual_active becomes FALSE + // These variables are set when VIsual_active becomes false visualinfo_T b_visual; int b_visual_mode_eval; // b_visual.vi_mode for visualmode() @@ -655,10 +653,8 @@ struct file_buffer { time_t b_u_time_cur; // uh_time of header below which we are now long b_u_save_nr_cur; // file write nr after which we are now - /* - * variables for "U" command in undo.c - */ - char_u *b_u_line_ptr; // saved line for "U" command + // variables for "U" command in undo.c + char *b_u_line_ptr; // saved line for "U" command linenr_T b_u_line_lnum; // line number of line in u_line colnr_T b_u_line_colnr; // optional column number @@ -688,73 +684,73 @@ struct file_buffer { int b_p_ai; ///< 'autoindent' int b_p_ai_nopaste; ///< b_p_ai saved for paste mode - char_u *b_p_bkc; ///< 'backupco + char *b_p_bkc; ///< 'backupco unsigned int b_bkc_flags; ///< flags for 'backupco int b_p_ci; ///< 'copyindent' int b_p_bin; ///< 'binary' int b_p_bomb; ///< 'bomb' - char_u *b_p_bh; ///< 'bufhidden' - char_u *b_p_bt; ///< 'buftype' + char *b_p_bh; ///< 'bufhidden' + char *b_p_bt; ///< 'buftype' int b_has_qf_entry; ///< quickfix exists for buffer int b_p_bl; ///< 'buflisted' long b_p_channel; ///< 'channel' int b_p_cin; ///< 'cindent' - char_u *b_p_cino; ///< 'cinoptions' - char_u *b_p_cink; ///< 'cinkeys' - char_u *b_p_cinw; ///< 'cinwords' - char_u *b_p_cinsd; ///< 'cinscopedecls' - char_u *b_p_com; ///< 'comments' - char_u *b_p_cms; ///< 'commentstring' - char_u *b_p_cpt; ///< 'complete' + char *b_p_cino; ///< 'cinoptions' + char *b_p_cink; ///< 'cinkeys' + char *b_p_cinw; ///< 'cinwords' + char *b_p_cinsd; ///< 'cinscopedecls' + char *b_p_com; ///< 'comments' + char *b_p_cms; ///< 'commentstring' + char *b_p_cpt; ///< 'complete' #ifdef BACKSLASH_IN_FILENAME - char_u *b_p_csl; ///< 'completeslash' + char *b_p_csl; ///< 'completeslash' #endif - char_u *b_p_cfu; ///< 'completefunc' - char_u *b_p_ofu; ///< 'omnifunc' - char_u *b_p_tfu; ///< 'tagfunc' - char_u *b_p_umf; ///< 'usermarkfunc' + char *b_p_cfu; ///< 'completefunc' + char *b_p_ofu; ///< 'omnifunc' + char *b_p_tfu; ///< 'tagfunc' + char *b_p_umf; ///< 'usermarkfunc' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' int b_p_et_nobin; ///< b_p_et saved for binary mode int b_p_et_nopaste; ///< b_p_et saved for paste mode - char_u *b_p_fenc; ///< 'fileencoding' - char_u *b_p_ff; ///< 'fileformat' - char_u *b_p_ft; ///< 'filetype' - char_u *b_p_fo; ///< 'formatoptions' - char_u *b_p_flp; ///< 'formatlistpat' + char *b_p_fenc; ///< 'fileencoding' + char *b_p_ff; ///< 'fileformat' + char *b_p_ft; ///< 'filetype' + char *b_p_fo; ///< 'formatoptions' + char *b_p_flp; ///< 'formatlistpat' int b_p_inf; ///< 'infercase' - char_u *b_p_isk; ///< 'iskeyword' - char_u *b_p_def; ///< 'define' local value - char_u *b_p_inc; ///< 'include' - char_u *b_p_inex; ///< 'includeexpr' + char *b_p_isk; ///< 'iskeyword' + char *b_p_def; ///< 'define' local value + char *b_p_inc; ///< 'include' + char *b_p_inex; ///< 'includeexpr' uint32_t b_p_inex_flags; ///< flags for 'includeexpr' - char_u *b_p_inde; ///< 'indentexpr' + char *b_p_inde; ///< 'indentexpr' uint32_t b_p_inde_flags; ///< flags for 'indentexpr' - char_u *b_p_indk; ///< 'indentkeys' - char_u *b_p_fp; ///< 'formatprg' - char_u *b_p_fex; ///< 'formatexpr' + char *b_p_indk; ///< 'indentkeys' + char *b_p_fp; ///< 'formatprg' + char *b_p_fex; ///< 'formatexpr' uint32_t b_p_fex_flags; ///< flags for 'formatexpr' - char_u *b_p_kp; ///< 'keywordprg' + char *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' - char_u *b_p_menc; ///< 'makeencoding' - char_u *b_p_mps; ///< 'matchpairs' + char *b_p_menc; ///< 'makeencoding' + char *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode int b_p_ma; ///< 'modifiable' - char_u *b_p_nf; ///< 'nrformats' + char *b_p_nf; ///< 'nrformats' int b_p_pi; ///< 'preserveindent' - char_u *b_p_qe; ///< 'quoteescape' + char *b_p_qe; ///< 'quoteescape' int b_p_ro; ///< 'readonly' long b_p_sw; ///< 'shiftwidth' long b_p_scbk; ///< 'scrollback' int b_p_si; ///< 'smartindent' long b_p_sts; ///< 'softtabstop' long b_p_sts_nopaste; ///< b_p_sts saved for paste mode - char_u *b_p_sua; ///< 'suffixesadd' + char *b_p_sua; ///< 'suffixesadd' int b_p_swf; ///< 'swapfile' long b_p_smc; ///< 'synmaxcol' - char_u *b_p_syn; ///< 'syntax' + char *b_p_syn; ///< 'syntax' long b_p_ts; ///< 'tabstop' long b_p_tw; ///< 'textwidth' long b_p_tw_nobin; ///< b_p_tw saved for binary mode @@ -762,29 +758,29 @@ struct file_buffer { long b_p_wm; ///< 'wrapmargin' long b_p_wm_nobin; ///< b_p_wm saved for binary mode long b_p_wm_nopaste; ///< b_p_wm saved for paste mode - char_u *b_p_vsts; ///< 'varsofttabstop' - long *b_p_vsts_array; ///< 'varsofttabstop' in internal format - char_u *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode - char_u *b_p_vts; ///< 'vartabstop' - long *b_p_vts_array; ///< 'vartabstop' in internal format - char_u *b_p_keymap; ///< 'keymap' + char *b_p_vsts; ///< 'varsofttabstop' + long *b_p_vsts_array; ///< 'varsofttabstop' in internal format + char *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode + char *b_p_vts; ///< 'vartabstop' + long *b_p_vts_array; ///< 'vartabstop' in internal format + char *b_p_keymap; ///< 'keymap' // local values for options which are normally global - char_u *b_p_gp; ///< 'grepprg' local value - char_u *b_p_mp; ///< 'makeprg' local value - char_u *b_p_efm; ///< 'errorformat' local value - char_u *b_p_ep; ///< 'equalprg' local value - char_u *b_p_path; ///< 'path' local value + char *b_p_gp; ///< 'grepprg' local value + char *b_p_mp; ///< 'makeprg' local value + char *b_p_efm; ///< 'errorformat' local value + char *b_p_ep; ///< 'equalprg' local value + char *b_p_path; ///< 'path' local value int b_p_ar; ///< 'autoread' local value - char_u *b_p_tags; ///< 'tags' local value - char_u *b_p_tc; ///< 'tagcase' local value + char *b_p_tags; ///< 'tags' local value + char *b_p_tc; ///< 'tagcase' local value unsigned b_tc_flags; ///< flags for 'tagcase' - char_u *b_p_dict; ///< 'dictionary' local value - char_u *b_p_tsr; ///< 'thesaurus' local value - char_u *b_p_tsrfu; ///< 'thesaurusfunc' local value + char *b_p_dict; ///< 'dictionary' local value + char *b_p_tsr; ///< 'thesaurus' local value + char *b_p_tsrfu; ///< 'thesaurusfunc' local value long b_p_ul; ///< 'undolevels' local value int b_p_udf; ///< 'undofile' - char_u *b_p_lw; ///< 'lispwords' local value + char *b_p_lw; ///< 'lispwords' local value // end of buffer options @@ -852,7 +848,7 @@ struct file_buffer { * spell buffer - used for spell info, never displayed and doesn't have a * file name. */ - bool b_help; // TRUE for help file buffer (when set b_p_bt + bool b_help; // true for help file buffer (when set b_p_bt // is "help") bool b_spell; // True for a spell file buffer, most fields // are not used! Use the B_SPELL macro to @@ -977,15 +973,15 @@ struct tabpage_S { * When the display is changed (e.g., when clearing the screen) w_lines_valid * is changed to exclude invalid entries. * When making changes to the buffer, wl_valid is reset to indicate wl_size - * may not reflect what is actually in the buffer. When wl_valid is FALSE, + * may not reflect what is actually in the buffer. When wl_valid is false, * the entries can only be used to count the number of displayed lines used. * wl_lnum and wl_lastlnum are invalid too. */ typedef struct w_line { linenr_T wl_lnum; // buffer line number for logical line uint16_t wl_size; // height in screen lines - char wl_valid; // TRUE values are valid for text in buffer - char wl_folded; // TRUE when this is a range of folded lines + char wl_valid; // true values are valid for text in buffer + char wl_folded; // true when this is a range of folded lines linenr_T wl_lastlnum; // last buffer line number for logical line } wline_T; @@ -1135,43 +1131,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. @@ -1183,11 +1142,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 @@ -1266,8 +1228,8 @@ struct window_S { */ linenr_T w_topline; /* buffer line number of the line at the top of the window */ - char w_topline_was_set; /* flag set to TRUE when topline is set, - e.g. by winrestview() */ + char w_topline_was_set; // flag set to true when topline is set, + // e.g. by winrestview() int w_topfill; // number of filler lines above w_topline int w_old_topfill; // w_topfill at last redraw bool w_botfill; // true when filler lines are actually @@ -1279,9 +1241,10 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window - // four fields that are only used when there is a WinScrolled autocommand + // five fields that are only used when there is a WinScrolled autocommand linenr_T w_last_topline; ///< last known value for w_topline colnr_T w_last_leftcol; ///< last known value for w_leftcol + colnr_T w_last_skipcol; ///< last known value for w_skipcol int w_last_width; ///< last known value for w_width int w_last_height; ///< last known value for w_height @@ -1390,7 +1353,7 @@ struct window_S { int w_redr_type; // type of redraw to be performed on win int w_upd_rows; // number of window lines to update when - // w_redr_type is REDRAW_TOP + // w_redr_type is UPD_REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw bool w_redr_status; // if true statusline/winbar must be redrawn @@ -1402,7 +1365,7 @@ struct window_S { linenr_T w_ru_topline; // topline shown in ruler linenr_T w_ru_line_count; // line count used for ruler int w_ru_topfill; // topfill shown in ruler - char w_ru_empty; // TRUE if ruler shows 0-1 (empty line) + char w_ru_empty; // true if ruler shows 0-1 (empty line) int w_alt_fnum; // alternate file (for # and CTRL-^) @@ -1509,11 +1472,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..85ab92e49b 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,9 +24,9 @@ #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/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" @@ -96,8 +97,7 @@ void changed(void) // Create a swap file if that is wanted. // Don't do this for "nofile" and "nowrite" buffer types. - if (curbuf->b_may_swap - && !bt_dontwrite(curbuf)) { + if (curbuf->b_may_swap && !bt_dontwrite(curbuf)) { bool save_need_wait_return = need_wait_return; need_wait_return = false; @@ -223,8 +223,8 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == curbuf) { // Mark this window to be redrawn later. - if (wp->w_redr_type < VALID) { - wp->w_redr_type = VALID; + if (wp->w_redr_type < UPD_VALID) { + wp->w_redr_type = UPD_VALID; } // Check if a change in the buffer has invalidated the cached @@ -301,17 +301,17 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // requires a redraw. if (wp->w_p_rnu && xtra != 0) { wp->w_last_cursor_lnum_rnu = 0; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } // Cursor line highlighting probably need to be updated with - // "VALID" if it's below the change. + // "UPD_VALID" if it's below the change. // If the cursor line is inside the change we need to redraw more. if (wp->w_p_cul) { if (xtra == 0) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } else if (lnum <= wp->w_last_cursorline) { - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); } } } @@ -319,8 +319,8 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // Call update_screen() later, which checks out what needs to be redrawn, // since it notices b_mod_set and then uses b_mod_*. - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } // when the cursor line is changed always trigger CursorMoved @@ -364,7 +364,7 @@ void changed_bytes(linenr_T lnum, colnr_T col) if (curwin->w_p_diff) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); linenr_T wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); @@ -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); } @@ -493,7 +493,7 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bo FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changed_lines_buf(wp->w_buffer, wlnum, @@ -534,21 +534,72 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) } } +/// Save the current values of 'fileformat' and 'fileencoding', so that we know +/// the file must be considered changed when the value is different. +void save_file_ff(buf_T *buf) +{ + buf->b_start_ffc = (unsigned char)(*buf->b_p_ff); + buf->b_start_eol = buf->b_p_eol; + buf->b_start_bomb = buf->b_p_bomb; + + // Only use free/alloc when necessary, they take time. + if (buf->b_start_fenc == NULL + || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { + xfree(buf->b_start_fenc); + buf->b_start_fenc = xstrdup(buf->b_p_fenc); + } +} + +/// Return true if 'fileformat' and/or 'fileencoding' has a different value +/// from when editing started (save_file_ff() called). +/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was +/// changed and 'binary' is not set. +/// Also when 'endofline' was changed and 'fixeol' is not set. +/// When "ignore_empty" is true don't consider a new, empty buffer to be +/// changed. +bool file_ff_differs(buf_T *buf, bool ignore_empty) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + // In a buffer that was never loaded the options are not valid. + if (buf->b_flags & BF_NEVERLOADED) { + return false; + } + if (ignore_empty + && (buf->b_flags & BF_NEW) + && buf->b_ml.ml_line_count == 1 + && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { + return false; + } + if (buf->b_start_ffc != *buf->b_p_ff) { + return true; + } + if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) { + return true; + } + if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { + return true; + } + if (buf->b_start_fenc == NULL) { + return *buf->b_p_fenc != NUL; + } + return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; +} + /// Insert string "p" at the cursor position. Stops at a NUL byte. /// Handles Replace mode and multi-byte characters. -void ins_bytes(char_u *p) +void ins_bytes(char *p) { ins_bytes_len(p, STRLEN(p)); } /// Insert string "p" with length "len" at the cursor position. /// Handles Replace mode and multi-byte characters. -void ins_bytes_len(char_u *p, size_t len) +void ins_bytes_len(char *p, size_t len) { size_t n; for (size_t i = 0; i < len; i += n) { // avoid reading past p[len] - n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i)); + n = (size_t)utfc_ptr2len_len((char_u *)p + i, (int)(len - i)); ins_char_bytes(p + i, n); } } @@ -560,18 +611,18 @@ void ins_bytes_len(char_u *p, size_t len) /// convert bytes to a character. void ins_char(int c) { - char_u buf[MB_MAXBYTES + 1]; - size_t n = (size_t)utf_char2bytes(c, (char *)buf); + char buf[MB_MAXBYTES + 1]; + size_t n = (size_t)utf_char2bytes(c, buf); // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. // Happens for CTRL-Vu9900. if (buf[0] == 0) { buf[0] = '\n'; } - ins_char_bytes((char_u *)buf, n); + ins_char_bytes(buf, n); } -void ins_char_bytes(char_u *buf, size_t charlen) +void ins_char_bytes(char *buf, size_t charlen) { // Break tabs if needed. if (virtual_active() && curwin->w_cursor.coladd > 0) { @@ -580,7 +631,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) size_t col = (size_t)curwin->w_cursor.col; linenr_T lnum = curwin->w_cursor.lnum; - char_u *oldp = ml_get(lnum); + char *oldp = (char *)ml_get(lnum); size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL // The lengths default to the values for when not replacing. @@ -610,7 +661,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) if (vcol > new_vcol && oldp[col + oldlen] == TAB) { break; } - oldlen += (size_t)utfc_ptr2len((char *)oldp + col + oldlen); + oldlen += (size_t)utfc_ptr2len(oldp + col + oldlen); // Deleted a bit too much, insert spaces. if (vcol > new_vcol) { newlen += (size_t)(vcol - new_vcol); @@ -619,7 +670,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) curwin->w_p_list = old_list; } else if (oldp[col] != NUL) { // normal replace - oldlen = (size_t)utfc_ptr2len((char *)oldp + col); + oldlen = (size_t)utfc_ptr2len(oldp + col); } // Push the replaced bytes onto the replace stack, so that they can be @@ -632,7 +683,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = xmalloc(linelen + newlen - oldlen); + char *newp = xmalloc(linelen + newlen - oldlen); // Copy bytes before the cursor. if (col > 0) { @@ -640,7 +691,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } // Copy bytes after the changed character(s). - char_u *p = newp + col; + char *p = newp + col; if (linelen > col + oldlen) { memmove(p + newlen, oldp + col + oldlen, (size_t)(linelen - col - oldlen)); @@ -655,7 +706,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } // Replace the line in the buffer. - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen); @@ -665,7 +716,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) if (p_sm && (State & MODE_INSERT) && msg_silent == 0 && !ins_compl_active()) { - showmatch(utf_ptr2char((char *)buf)); + showmatch(utf_ptr2char(buf)); } if (!p_ri || (State & REPLACE_FLAG)) { @@ -678,7 +729,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) /// Insert a string at the cursor position. /// Note: Does NOT handle Replace mode. /// Caller must have prepared for undo. -void ins_str(char_u *s) +void ins_str(char *s) { int newlen = (int)STRLEN(s); linenr_T lnum = curwin->w_cursor.lnum; @@ -688,10 +739,10 @@ void ins_str(char_u *s) } colnr_T col = curwin->w_cursor.col; - char_u *oldp = ml_get(lnum); + char *oldp = (char *)ml_get(lnum); int oldlen = (int)STRLEN(oldp); - char_u *newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); + char *newp = xmalloc((size_t)oldlen + (size_t)newlen + 1); if (col > 0) { memmove(newp, oldp, (size_t)col); } @@ -699,7 +750,7 @@ void ins_str(char_u *s) int bytes = oldlen - col + 1; assert(bytes >= 0); memmove(newp + col + newlen, oldp + col, (size_t)bytes); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); inserted_bytes(lnum, col, 0, newlen); curwin->w_cursor.col += newlen; } @@ -723,9 +774,9 @@ int del_char(bool fixpos) int del_chars(long count, int fixpos) { int bytes = 0; - char_u *p = get_cursor_pos_ptr(); + char *p = (char *)get_cursor_pos_ptr(); for (long i = 0; i < count && *p != NUL; i++) { - int l = utfc_ptr2len((char *)p); + int l = utfc_ptr2len(p); bytes += l; p += l; } @@ -746,7 +797,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; bool fixpos = fixpos_arg; - char_u *oldp = ml_get(lnum); + char *oldp = (char *)ml_get(lnum); colnr_T oldlen = (colnr_T)STRLEN(oldp); // Can't do anything when the cursor is on the NUL after the line. @@ -766,7 +817,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // If 'delcombine' is set and deleting (less than) one character, only // delete the last combining character. if (p_deco && use_delcombine - && utfc_ptr2len((char *)oldp + col) >= count) { + && utfc_ptr2len(oldp + col) >= count) { int cc[MAX_MCO]; (void)utfc_ptr2char(oldp + col, cc); @@ -775,9 +826,9 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) int n = col; do { col = n; - count = utf_ptr2len((char *)oldp + n); + count = utf_ptr2len(oldp + n); n += count; - } while (utf_composinglike(oldp + col, oldp + n)); + } while (utf_composinglike((char_u *)oldp + col, (char_u *)oldp + n)); fixpos = false; } } @@ -792,7 +843,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; - curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); + curwin->w_cursor.col -= utf_head_off((char_u *)oldp, (char_u *)oldp + curwin->w_cursor.col); } count = oldlen - col; movelen = 1; @@ -801,7 +852,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // If the old line has been allocated the deletion can be done in the // existing line. Otherwise a new line has to be allocated. bool was_alloced = ml_line_alloced(); // check if oldp was allocated - char_u *newp; + char *newp; if (was_alloced) { ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory @@ -811,7 +862,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } memmove(newp + col, oldp + col + count, (size_t)movelen); if (!was_alloced) { - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); } // mark the buffer as changed and prepare for displaying @@ -823,10 +874,10 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) /// Copy the indent from ptr to the current line (and fill to size). /// Leaves the cursor on the first non-blank in the line. /// @return true if the line was changed. -int copy_indent(int size, char_u *src) +int copy_indent(int size, char *src) { - char_u *p = NULL; - char_u *line = NULL; + char *p = NULL; + char *line = NULL; int ind_len; int line_len = 0; int tab_pad; @@ -838,7 +889,7 @@ int copy_indent(int size, char_u *src) ind_len = 0; int ind_done = 0; int ind_col = 0; - char_u *s = src; + char *s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { @@ -924,7 +975,7 @@ int copy_indent(int size, char_u *src) memmove(p, get_cursor_line_ptr(), (size_t)line_len); // Replace the line - ml_replace(curwin->w_cursor.lnum, (char *)line, false); + ml_replace(curwin->w_cursor.lnum, line, false); // Put the cursor after the indent. curwin->w_cursor.col = ind_len; @@ -955,8 +1006,8 @@ int copy_indent(int size, char_u *src) /// @return true on success, false on failure int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) { - char_u *next_line = NULL; // copy of the next line - char_u *p_extra = NULL; // what goes to next line + char *next_line = NULL; // copy of the next line + char *p_extra = NULL; // what goes to next line colnr_T less_cols = 0; // less columns for mark in new line colnr_T less_cols_off = 0; // columns to skip for mark adjust pos_T old_cursor; // old cursor position @@ -969,9 +1020,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) int comment_start = 0; // start index of the comment leader char *lead_flags; // position in 'comments' for comment leader char *leader = NULL; // copy of comment leader - char_u *allocated = NULL; // allocated memory + char *allocated = NULL; // allocated memory char *p; - char_u saved_char = NUL; // init for GCC + char saved_char = NUL; // init for GCC pos_T *pos; bool do_si = may_do_si(); bool do_cindent; @@ -985,7 +1036,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) colnr_T mincol = curwin->w_cursor.col + 1; // make a copy of the current line so we can mess with it - char_u *saved_line = vim_strsave(get_cursor_line_ptr()); + char *saved_line = (char *)vim_strsave(get_cursor_line_ptr()); if (State & VREPLACE_FLAG) { // With MODE_VREPLACE we make a copy of the next line, which we will be @@ -996,9 +1047,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // the line, replacing what was there before and pushing the right // stuff onto the replace stack. -- webb. if (curwin->w_cursor.lnum < orig_line_count) { - next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); + next_line = (char *)vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); } else { - next_line = vim_strsave((char_u *)""); + next_line = xstrdup(""); } // In MODE_VREPLACE state, a NL replaces the rest of the line, and @@ -1008,9 +1059,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // autoindent etc) a bit later. replace_push(NUL); // Call twice because BS over NL expects it replace_push(NUL); - p = (char *)saved_line + curwin->w_cursor.col; + p = saved_line + curwin->w_cursor.col; while (*p != NUL) { - p += replace_push_mb((char_u *)p); + p += replace_push_mb(p); } saved_line[curwin->w_cursor.col] = NUL; } @@ -1018,7 +1069,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if ((State & MODE_INSERT) && (State & VREPLACE_FLAG) == 0) { p_extra = saved_line + curwin->w_cursor.col; if (do_si) { // need first char after new line break - p = skipwhite((char *)p_extra); + p = skipwhite(p_extra); first_char = (unsigned char)(*p); } extra_len = (int)STRLEN(p_extra); @@ -1058,7 +1109,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) char *ptr; old_cursor = curwin->w_cursor; - ptr = (char *)saved_line; + ptr = saved_line; if (flags & OPENLINE_DO_COM) { lead_len = get_leader_len(ptr, NULL, false, true); } else { @@ -1142,7 +1193,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Don't do this if the previous line ended in ';' or // '}'. } else if (last_char != ';' && last_char != '}' - && cin_is_cinword((char_u *)ptr)) { + && cin_is_cinword(ptr)) { did_si = true; } } @@ -1192,13 +1243,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // This may then be inserted in front of the new line. end_comment_pending = NUL; if (flags & OPENLINE_DO_COM) { - lead_len = get_leader_len((char *)saved_line, &lead_flags, dir == BACKWARD, true); + lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD && (!has_format_option(FO_NO_OPEN_COMS) || (flags & OPENLINE_FORMAT))) { // Check for a line comment after code. comment_start = check_linecomment(saved_line); if (comment_start != MAXCOL) { - lead_len = get_leader_len((char *)saved_line + comment_start, &lead_flags, false, true); + lead_len = get_leader_len(saved_line + comment_start, &lead_flags, false, true); if (lead_len != 0) { lead_len += comment_start; if (did_do_comment != NULL) { @@ -1213,13 +1264,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (lead_len > 0) { char *lead_repl = NULL; // replaces comment leader int lead_repl_len = 0; // length of *lead_repl - char_u lead_middle[COM_MAX_LEN]; // middle-comment string - char_u lead_end[COM_MAX_LEN]; // end-comment string - char_u *comment_end = NULL; // where lead_end has been found + char lead_middle[COM_MAX_LEN]; // middle-comment string + char lead_end[COM_MAX_LEN]; // end-comment string + char *comment_end = NULL; // where lead_end has been found int extra_space = false; // append extra space int current_flag; int require_blank = false; // requires blank after middle - char_u *p2; + char *p2; // If the comment leader has the start, middle or end flag, it may not // be used or may be replaced with the middle leader. @@ -1261,15 +1312,15 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) size_t n = copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); if (end_comment_pending == -1) { // we can set it now - end_comment_pending = lead_end[n - 1]; + end_comment_pending = (unsigned char)lead_end[n - 1]; } // If the end of the comment is in the same line, don't use // the comment leader. if (dir == FORWARD) { - for (p = (char *)saved_line + lead_len; *p; p++) { + for (p = saved_line + lead_len; *p; p++) { if (STRNCMP(p, lead_end, n) == 0) { - comment_end = (char_u *)p; + comment_end = p; lead_len = 0; break; } @@ -1302,17 +1353,17 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Remember where the end is, might want to use it to find the // start (for C-comments). if (dir == FORWARD) { - comment_end = (char_u *)skipwhite((char *)saved_line); + comment_end = skipwhite(saved_line); lead_len = 0; break; } // Doing "O" on the end of a comment inserts the middle leader. // Find the string for the middle leader, searching backwards. - while (p > (char *)curbuf->b_p_com && *p != ',') { + while (p > curbuf->b_p_com && *p != ',') { p--; } - for (lead_repl = p; lead_repl > (char *)curbuf->b_p_com + for (lead_repl = p; lead_repl > curbuf->b_p_com && lead_repl[-1] != ':'; lead_repl--) {} lead_repl_len = (int)(p - lead_repl); @@ -1321,7 +1372,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) extra_space = true; // Check whether we allow automatic ending of comments - for (p2 = (char_u *)p; *p2 && *p2 != ':'; p2++) { + for (p2 = p; *p2 && *p2 != ':'; p2++) { if (*p2 == COM_AUTO_END) { end_comment_pending = -1; // means we want to set it } @@ -1331,7 +1382,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) while (*p2 && *p2 != ',') { p2++; } - end_comment_pending = p2[-1]; + end_comment_pending = (unsigned char)p2[-1]; } break; } @@ -1357,7 +1408,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) + 1; assert(bytes >= 0); leader = xmalloc((size_t)bytes); - allocated = (char_u *)leader; // remember to free it later + allocated = leader; // remember to free it later STRLCPY(leader, saved_line, lead_len + 1); @@ -1391,7 +1442,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Compute the length of the replaced characters in // screen characters, not bytes. { - int repl_size = vim_strnsize((char_u *)lead_repl, lead_repl_len); + int repl_size = vim_strnsize(lead_repl, lead_repl_len); int old_size = 0; char *endp = p; int l; @@ -1436,13 +1487,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // screen characters, not bytes. Move the part that is // not to be overwritten. { - int repl_size = vim_strnsize((char_u *)lead_repl, lead_repl_len); + int repl_size = vim_strnsize(lead_repl, lead_repl_len); int i; int l; for (i = 0; i < lead_len && p[i] != NUL; i += l) { l = utfc_ptr2len(p + i); - if (vim_strnsize((char_u *)p, i + l) > repl_size) { + if (vim_strnsize(p, i + l) > repl_size) { break; } } @@ -1485,7 +1536,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Recompute the indent, it may have changed. if (curbuf->b_p_ai || do_si) { - newindent = get_indent_str_vtab((char_u *)leader, + newindent = get_indent_str_vtab(leader, curbuf->b_p_ts, curbuf->b_p_vts_array, false); } @@ -1568,7 +1619,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) { while ((*p_extra == ' ' || *p_extra == '\t') - && !utf_iscomposing(utf_ptr2char((char *)p_extra + 1))) { + && !utf_iscomposing(utf_ptr2char(p_extra + 1))) { if (REPLACE_NORMAL(State)) { replace_push(*p_extra); } @@ -1582,7 +1633,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (p_extra == NULL) { - p_extra = (char_u *)""; // append empty line + p_extra = ""; // append empty line } // concatenate leader and p_extra, if there is a leader @@ -1602,7 +1653,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } } STRCAT(leader, p_extra); - p_extra = (char_u *)leader; + p_extra = leader; did_ai = true; // So truncating blanks works with comments less_cols -= lead_len; } else { @@ -1615,7 +1666,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) curwin->w_cursor.lnum--; } if ((State & VREPLACE_FLAG) == 0 || old_cursor.lnum >= orig_line_count) { - if (ml_append(curwin->w_cursor.lnum, (char *)p_extra, (colnr_T)0, false) == FAIL) { + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) { goto theend; } // Postpone calling changed_lines(), because it would mess up folding @@ -1637,7 +1688,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) (void)u_save_cursor(); // errors are ignored! vr_lines_changed++; } - ml_replace(curwin->w_cursor.lnum, (char *)p_extra, true); + ml_replace(curwin->w_cursor.lnum, p_extra, true); changed_bytes(curwin->w_cursor.lnum, 0); // TODO(vigoux): extmark_splice_cols here?? curwin->w_cursor.lnum--; @@ -1702,7 +1753,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) { truncate_spaces(saved_line); } - ml_replace(curwin->w_cursor.lnum, (char *)saved_line, false); + ml_replace(curwin->w_cursor.lnum, saved_line, false); int new_len = (int)STRLEN(saved_line); @@ -1787,10 +1838,10 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // stuff onto the replace stack (via ins_char()). if (State & VREPLACE_FLAG) { // Put new line in p_extra - p_extra = vim_strsave(get_cursor_line_ptr()); + p_extra = (char *)vim_strsave(get_cursor_line_ptr()); // Put back original line - ml_replace(curwin->w_cursor.lnum, (char *)next_line, false); + ml_replace(curwin->w_cursor.lnum, next_line, false); // Insert new stuff into line again curwin->w_cursor.col = 0; @@ -1814,16 +1865,16 @@ theend: /// If "fixpos" is true fix the cursor position when done. void truncate_line(int fixpos) { - char_u *newp; + char *newp; linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; if (col == 0) { - newp = vim_strsave((char_u *)""); + newp = xstrdup(""); } else { - newp = vim_strnsave(ml_get(lnum), (size_t)col); + newp = (char *)vim_strnsave(ml_get(lnum), (size_t)col); } - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying changed_bytes(lnum, curwin->w_cursor.col); @@ -1900,7 +1951,7 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space) while (line[i] != NUL) { // scan through the 'comments' option for a match int found_one = false; - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { // Get one option part into part_buf[]. Advance "list" to next // one. Put "string" at start of string. if (!got_com && flags != NULL) { @@ -2037,7 +2088,7 @@ int get_last_leader_offset(char *line, char **flags) while (--i >= lower_check_bound) { // scan through the 'comments' option for a match int found_one = false; - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { char *flags_save = list; // Get one option part into part_buf[]. Advance list to next one. @@ -2122,7 +2173,7 @@ int get_last_leader_offset(char *line, char **flags) } len1 = (int)STRLEN(com_leader); - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { char *flags_save = list; (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); 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..4efd8a4ad1 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) { @@ -140,16 +140,16 @@ int buf_init_chartab(buf_T *buf, int global) const char_u *p; if (i == 0) { // first round: 'isident' - p = p_isi; + p = (char_u *)p_isi; } else if (i == 1) { // second round: 'isprint' - p = p_isp; + p = (char_u *)p_isp; } else if (i == 2) { // third round: 'isfname' - p = p_isf; + p = (char_u *)p_isf; } else { // i == 3 // fourth round: 'iskeyword' - p = buf->b_p_isk; + p = (char_u *)buf->b_p_isk; } while (*p) { @@ -246,7 +246,7 @@ int buf_init_chartab(buf_T *buf, int global) } c = *p; - p = skip_to_option_part(p); + p = (char_u *)skip_to_option_part((char *)p); if ((c == ',') && (*p == NUL)) { // Trailing comma is not allowed. @@ -313,7 +313,7 @@ size_t transstr_len(const char *const s, bool untab) const size_t l = (size_t)utfc_ptr2len(p); if (l > 1) { int pcc[MAX_MCO + 1]; - pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); + pcc[0] = utfc_ptr2char(p, &pcc[1]); if (vim_isprintc(pcc[0])) { len += l; @@ -359,7 +359,7 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool break; // Exceeded `buf` size. } int pcc[MAX_MCO + 1]; - pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); + pcc[0] = utfc_ptr2char(p, &pcc[1]); if (vim_isprintc(pcc[0])) { memmove(buf_p, p, l); @@ -709,7 +709,7 @@ int ptr2cells(const char *p_in) /// @return number of character cells. int vim_strsize(char *s) { - return vim_strnsize((char_u *)s, MAXCOL); + return vim_strnsize(s, MAXCOL); } /// Return the number of character cells string "s[len]" will take on the @@ -721,13 +721,13 @@ int vim_strsize(char *s) /// @param len /// /// @return Number of character cells. -int vim_strnsize(char_u *s, int len) +int vim_strnsize(char *s, int len) { assert(s != NULL); int size = 0; while (*s != NUL && --len >= 0) { - int l = utfc_ptr2len((char *)s); - size += ptr2cells((char *)s); + int l = utfc_ptr2len(s); + size += ptr2cells(s); s += l; len -= l - 1; } @@ -930,14 +930,18 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en posptr -= utf_head_off(line, posptr); } + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); + // This function is used very often, do some speed optimizations. // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set - // use a simple loop. + // and there are no virtual text use a simple loop. // Also use this when 'list' is set but tabs take their normal size. if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL - && !wp->w_p_bri) { + && !wp->w_p_bri + && !cts.cts_has_virt_text) { for (;;) { head = 0; int c = *ptr; @@ -984,25 +988,29 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } else { for (;;) { // A tab gets expanded, depending on the current column + // Other things also take up space. head = 0; - incr = win_lbr_chartabsize(wp, line, ptr, vcol, &head); + incr = win_lbr_chartabsize(&cts, &head); // make sure we don't go past the end of the line - if (*ptr == NUL) { + if (*cts.cts_ptr == NUL) { // NUL at end of line only takes one column incr = 1; break; } - if ((posptr != NULL) && (ptr >= posptr)) { + if ((posptr != NULL) && ((char_u *)cts.cts_ptr >= posptr)) { // character at pos->col break; } - vcol += incr; - MB_PTR_ADV(ptr); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + vcol = cts.cts_vcol; + ptr = (char_u *)cts.cts_ptr; } + clear_chartabsize_arg(&cts); if (start != NULL) { *start = vcol + head; @@ -1013,6 +1021,8 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } if (cursor != NULL) { + // cursor is after inserted text + vcol += cts.cts_cur_text_width; if ((*ptr == TAB) && (State & MODE_NORMAL) && !wp->w_p_list @@ -1147,7 +1157,7 @@ char *skipwhite(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - return (char *)skipwhite_len((char_u *)p, STRLEN(p)); + return skipwhite_len(p, STRLEN(p)); } /// Like `skipwhite`, but skip up to `len` characters. @@ -1158,27 +1168,27 @@ char *skipwhite(const char *const p) /// /// @return Pointer to character after the skipped whitespace, or the `len`-th /// character in the string. -char_u *skipwhite_len(const char_u *p, size_t len) +char *skipwhite_len(const char *p, size_t len) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { for (; len > 0 && ascii_iswhite(*p); len--) { p++; } - return (char_u *)p; + return (char *)p; } // getwhitecols: return the number of whitespace // columns (bytes) at the start of a given line intptr_t getwhitecols_curline(void) { - return getwhitecols(get_cursor_line_ptr()); + return getwhitecols((char *)get_cursor_line_ptr()); } -intptr_t getwhitecols(const char_u *p) +intptr_t getwhitecols(const char *p) FUNC_ATTR_PURE { - return (char_u *)skipwhite((char *)p) - p; + return skipwhite(p) - p; } /// Skip over digits @@ -1222,10 +1232,10 @@ const char *skipbin(const char *q) /// /// @return Pointer to the character after the skipped digits and hex /// characters. -char_u *skiphex(char_u *q) +char *skiphex(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (ascii_isxdigit(*p)) { // skip to next non-digit p++; @@ -1238,10 +1248,10 @@ char_u *skiphex(char_u *q) /// @param q /// /// @return Pointer to the digit or (NUL after the string). -char_u *skiptodigit(char_u *q) +char *skiptodigit(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (*p != NUL && !ascii_isdigit(*p)) { // skip to next digit p++; @@ -1272,10 +1282,10 @@ const char *skiptobin(const char *q) /// @param q /// /// @return Pointer to the hex character or (NUL after the string). -char_u *skiptohex(char_u *q) +char *skiptohex(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (*p != NUL && !ascii_isxdigit(*p)) { // skip to next digit p++; @@ -1288,13 +1298,13 @@ char_u *skiptohex(char_u *q) /// @param[in] p Text to skip over. /// /// @return Pointer to the next whitespace or NUL character. -char_u *skiptowhite(const char_u *p) +char *skiptowhite(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { while (*p != ' ' && *p != '\t' && *p != NUL) { p++; } - return (char_u *)p; + return (char *)p; } /// skiptowhite_esc: Like skiptowhite(), but also skip escaped chars @@ -1319,11 +1329,11 @@ char *skiptowhite_esc(char *p) /// @param[in] p Text to skip over. /// /// @return Pointer to the next '\n' or NUL character. -char_u *skip_to_newline(const char_u *const p) +char *skip_to_newline(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - return (char_u *)xstrchrnul((const char *)p, NL); + return xstrchrnul(p, NL); } /// Gets a number from a string and skips over it, signalling overflow. @@ -1412,10 +1422,10 @@ int32_t getdigits_int32(char **pp, bool strict, long def) /// Check that "lbuf" is empty or only contains blanks. /// /// @param lbuf line buffer to check -bool vim_isblankline(char_u *lbuf) +bool vim_isblankline(char *lbuf) FUNC_ATTR_PURE { - char_u *p = (char_u *)skipwhite((char *)lbuf); + char *p = skipwhite(lbuf); return *p == NUL || *p == '\r' || *p == '\n'; } @@ -1654,8 +1664,9 @@ int hex2nr(int c) } /// Convert two hex characters to a byte. -/// Return -1 if one of the characters is not hex. -int hexhex2nr(char_u *p) +/// +/// @return -1 if one of the characters is not hex. +int hexhex2nr(const char *p) FUNC_ATTR_PURE { if (!ascii_isxdigit(p[0]) || !ascii_isxdigit(p[1])) { @@ -1677,12 +1688,12 @@ int hexhex2nr(char_u *p) /// characters. /// /// @param str file path string to check -bool rem_backslash(const char_u *str) +bool rem_backslash(const char *str) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { #ifdef BACKSLASH_IN_FILENAME return str[0] == '\\' - && str[1] < 0x80 + && (uint8_t)str[1] < 0x80 && (str[1] == ' ' || (str[1] != NUL && str[1] != '*' @@ -1697,7 +1708,7 @@ bool rem_backslash(const char_u *str) /// Halve the number of backslashes in a file name argument. /// /// @param p -void backslash_halve(char_u *p) +void backslash_halve(char *p) { for (; *p; p++) { if (rem_backslash(p)) { @@ -1711,11 +1722,11 @@ void backslash_halve(char_u *p) /// @param p /// /// @return String with the number of backslashes halved. -char_u *backslash_halve_save(const char_u *p) +char *backslash_halve_save(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { // TODO(philix): simplify and improve backslash_halve_save algorithm - char_u *res = vim_strsave(p); + char *res = xstrdup(p); backslash_halve(res); return res; } diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c new file mode 100644 index 0000000000..5c54404aab --- /dev/null +++ b/src/nvim/cmdexpand.c @@ -0,0 +1,2908 @@ +// 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 + +// cmdexpand.c: functions for command-line completion + +#include "nvim/api/private/helpers.h" +#include "nvim/arglist.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdhist.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/garray.h" +#include "nvim/getchar.h" +#include "nvim/help.h" +#include "nvim/highlight_group.h" +#include "nvim/if_cscope.h" +#include "nvim/locale.h" +#include "nvim/lua/executor.h" +#include "nvim/mapping.h" +#include "nvim/menu.h" +#include "nvim/option.h" +#include "nvim/os/os.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/strings.h" +#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/usercmd.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +/// Type used by ExpandGeneric() +typedef char *(*CompleteListItemGetter)(expand_T *, int); + +/// Type used by call_user_expand_func +typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdexpand.c.generated.h" +#endif + +static int cmd_showtail; ///< Only show path tail in lists ? + +/// "compl_match_array" points the currently displayed list of entries in the +/// popup menu. It is NULL when there is no popup menu. +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; +/// First column in cmdline of the matched item for completion. +static int compl_startcol; +static int compl_selected; + +static int sort_func_compare(const void *s1, const void *s2) +{ + char_u *p1 = *(char_u **)s1; + char_u *p2 = *(char_u **)s2; + + if (*p1 != '<' && *p2 == '<') { + return -1; + } + if (*p1 == '<' && *p2 != '<') { + return 1; + } + return STRCMP(p1, p2); +} + +static void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char **files, int options) +{ + int i; + char_u *p; + const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; + + // May change home directory back to "~" + if (options & WILD_HOME_REPLACE) { + tilde_replace(str, numfiles, files); + } + + if (options & WILD_ESCAPE) { + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) { + // Insert a backslash into a file name before a space, \, %, # + // and wildmatch characters, except '~'. + for (i = 0; i < numfiles; i++) { + // for ":set path=" we need to escape spaces twice + if (xp->xp_backslash == XP_BS_THREE) { + p = vim_strsave_escaped((char_u *)files[i], (char_u *)" "); + xfree(files[i]); + files[i] = (char *)p; +#if defined(BACKSLASH_IN_FILENAME) + p = vim_strsave_escaped(files[i], (char_u *)" "); + xfree(files[i]); + files[i] = p; +#endif + } +#ifdef BACKSLASH_IN_FILENAME + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); +#else + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], + xp->xp_shell ? VSE_SHELL : vse_what); +#endif + xfree(files[i]); + files[i] = (char *)p; + + // If 'str' starts with "\~", replace "~" at start of + // files[i] with "\~". + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { + escape_fname(&files[i]); + } + } + xp->xp_backslash = XP_BS_NONE; + + // If the first file starts with a '+' escape it. Otherwise it + // could be seen as "+cmd". + if (*files[0] == '+') { + escape_fname(&files[0]); + } + } else if (xp->xp_context == EXPAND_TAGS) { + // Insert a backslash before characters in a tag name that + // would terminate the ":tag" command. + for (i = 0; i < numfiles; i++) { + p = vim_strsave_escaped((char_u *)files[i], (char_u *)"\\|\""); + xfree(files[i]); + files[i] = (char *)p; + } + } + } +} + +/// Return FAIL if this is not an appropriate context in which to do +/// completion of anything, return OK if it is (even if there are no matches). +/// For the caller, this means that the character is just passed through like a +/// normal character (instead of being expanded). This allows :s/^I^D etc. +/// +/// @param options extra options for ExpandOne() +/// @param escape if true, escape the returned matches +int nextwild(expand_T *xp, int type, int options, bool escape) +{ + CmdlineInfo *const ccline = get_cmdline_info(); + int i, j; + char_u *p1; + char_u *p2; + int difflen; + + if (xp->xp_numfiles == -1) { + set_expand_context(xp); + cmd_showtail = expand_showtail(xp); + } + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) { + beep_flush(); + return OK; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) { + // Caller can use the character as a normal char instead + return FAIL; + } + + if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { + msg_puts("..."); // show that we are busy + ui_flush(); + } + + i = (int)((char_u *)xp->xp_pattern - ccline->cmdbuff); + assert(ccline->cmdpos >= i); + xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; + + if (type == WILD_NEXT || type == WILD_PREV) { + // Get next/previous match for a previous expanded pattern. + p2 = ExpandOne(xp, NULL, NULL, 0, type); + } else { + // Translate string into pattern and expand it. + p1 = addstar((char_u *)xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + const int use_options = (options + | WILD_HOME_REPLACE + | WILD_ADD_SLASH + | WILD_SILENT + | (escape ? WILD_ESCAPE : 0) + | (p_wic ? WILD_ICASE : 0)); + p2 = ExpandOne(xp, p1, vim_strnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), + use_options, type); + xfree(p1); + + // xp->xp_pattern might have been modified by ExpandOne (for example, + // in lua completion), so recompute the pattern index and length + i = (int)((char_u *)xp->xp_pattern - ccline->cmdbuff); + xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; + + // Longest match: make sure it is not shorter, happens with :help. + if (p2 != NULL && type == WILD_LONGEST) { + for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { + if (ccline->cmdbuff[i + j] == '*' + || ccline->cmdbuff[i + j] == '?') { + break; + } + } + if ((int)STRLEN(p2) < j) { + XFREE_CLEAR(p2); + } + } + } + + if (p2 != NULL && !got_int) { + difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len); + if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) { + realloc_cmdbuff(ccline->cmdlen + difflen + 4); + xp->xp_pattern = (char *)ccline->cmdbuff + i; + } + assert(ccline->cmdpos <= ccline->cmdlen); + memmove(&ccline->cmdbuff[ccline->cmdpos + difflen], + &ccline->cmdbuff[ccline->cmdpos], + (size_t)ccline->cmdlen - (size_t)ccline->cmdpos + 1); + memmove(&ccline->cmdbuff[i], p2, STRLEN(p2)); + ccline->cmdlen += difflen; + ccline->cmdpos += difflen; + } + xfree(p2); + + redrawcmd(); + cursorcmd(); + + // When expanding a ":map" command and no matches are found, assume that + // the key is supposed to be inserted literally + if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) { + return FAIL; + } + + if (xp->xp_numfiles <= 0 && p2 == NULL) { + beep_flush(); + } else if (xp->xp_numfiles == 1) { + // free expanded pattern + (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); + } + + return OK; +} + +void cmdline_pum_display(bool changed_array) +{ + pum_display(compl_match_array, compl_match_arraysize, compl_selected, + changed_array, compl_startcol); +} + +bool cmdline_pum_active(void) +{ + // compl_match_array != NULL should already imply pum_visible() in Nvim. + return compl_match_array != NULL; +} + +/// Remove the cmdline completion popup menu +void cmdline_pum_remove(void) +{ + pum_undisplay(true); + XFREE_CLEAR(compl_match_array); +} + +void cmdline_pum_cleanup(CmdlineInfo *cclp) +{ + cmdline_pum_remove(); + wildmenu_cleanup(cclp); +} + +/// Get the next or prev cmdline completion match. The index of the match is set +/// in "p_findex" +static char_u *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char_u *orig_save) +{ + if (xp->xp_numfiles <= 0) { + return NULL; + } + + int findex = *p_findex; + + if (mode == WILD_PREV) { + if (findex == -1) { + findex = xp->xp_numfiles; + } + findex--; + } else { // mode == WILD_NEXT + findex++; + } + + // When wrapping around, return the original string, set findex to -1. + if (findex < 0) { + if (orig_save == NULL) { + findex = xp->xp_numfiles - 1; + } else { + findex = -1; + } + } + if (findex >= xp->xp_numfiles) { + if (orig_save == NULL) { + findex = 0; + } else { + findex = -1; + } + } + if (compl_match_array) { + compl_selected = findex; + cmdline_pum_display(false); + } else if (p_wmnu) { + redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); + } + *p_findex = findex; + + return vim_strsave(findex == -1 ? orig_save : (char_u *)xp->xp_files[findex]); +} + +/// Start the command-line expansion and get the matches. +static char_u *ExpandOne_start(int mode, expand_T *xp, char_u *str, int options) +{ + int non_suf_match; // number without matching suffix + char_u *ss = NULL; + + // Do the expansion. + if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, options) == FAIL) { +#ifdef FNAME_ILLEGAL + // Illegal file name has been silently skipped. But when there + // are wildcards, the real problem is that there was no match, + // causing the pattern to be added, which has illegal characters. + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { + semsg(_(e_nomatch2), str); + } +#endif + } else if (xp->xp_numfiles == 0) { + if (!(options & WILD_SILENT)) { + semsg(_(e_nomatch2), str); + } + } else { + // Escape the matches for use on the command line. + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); + + // Check for matching suffixes in file names. + if (mode != WILD_ALL && mode != WILD_ALL_KEEP + && mode != WILD_LONGEST) { + if (xp->xp_numfiles) { + non_suf_match = xp->xp_numfiles; + } else { + non_suf_match = 1; + } + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) { + // More than one match; check suffix. + // The files will have been sorted on matching suffix in + // expand_wildcards, only need to check the first two. + non_suf_match = 0; + for (int i = 0; i < 2; i++) { + if (match_suffix((char_u *)xp->xp_files[i])) { + non_suf_match++; + } + } + } + if (non_suf_match != 1) { + // Can we ever get here unless it's while expanding + // interactively? If not, we can get rid of this all + // together. Don't really want to wait for this message + // (and possibly have to hit return to continue!). + if (!(options & WILD_SILENT)) { + emsg(_(e_toomany)); + } else if (!(options & WILD_NO_BEEP)) { + beep_flush(); + } + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { + ss = vim_strsave((char_u *)xp->xp_files[0]); + } + } + } + + return ss; +} + +/// Return the longest common part in the list of cmdline completion matches. +static char *find_longest_match(expand_T *xp, int options) +{ + size_t len = 0; + + for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { + mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); + int c0 = utf_ptr2char(&xp->xp_files[0][len]); + int i; + for (i = 1; i < xp->xp_numfiles; i++) { + int ci = utf_ptr2char(&xp->xp_files[i][len]); + + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) { + if (mb_tolower(c0) != mb_tolower(ci)) { + break; + } + } else if (c0 != ci) { + break; + } + } + if (i < xp->xp_numfiles) { + if (!(options & WILD_NO_BEEP)) { + vim_beep(BO_WILD); + } + break; + } + } + + return xstrndup(xp->xp_files[0], len); +} + +/// Do wildcard expansion on the string 'str'. +/// Chars that should not be expanded must be preceded with a backslash. +/// Return a pointer to allocated memory containing the new string. +/// Return NULL for failure. +/// +/// "orig" is the originally expanded string, copied to allocated memory. It +/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or +/// WILD_PREV "orig" should be NULL. +/// +/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" +/// is WILD_EXPAND_FREE or WILD_ALL. +/// +/// mode = WILD_FREE: just free previously expanded matches +/// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches +/// mode = WILD_EXPAND_KEEP: normal expansion, keep matches +/// mode = WILD_NEXT: use next match in multiple match, wrap to first +/// mode = WILD_PREV: use previous match in multiple match, wrap to first +/// mode = WILD_ALL: return all matches concatenated +/// mode = WILD_LONGEST: return longest matched part +/// mode = WILD_ALL_KEEP: get all matches, keep matches +/// mode = WILD_APPLY: apply the item selected in the cmdline completion +/// popup menu and close the menu. +/// mode = WILD_CANCEL: cancel and close the cmdline completion popup and +/// use the original text. +/// +/// options = WILD_LIST_NOTFOUND: list entries without a match +/// options = WILD_HOME_REPLACE: do home_replace() for buffer names +/// options = WILD_USE_NL: Use '\n' for WILD_ALL +/// options = WILD_NO_BEEP: Don't beep for multiple matches +/// options = WILD_ADD_SLASH: add a slash after directory names +/// options = WILD_KEEP_ALL: don't remove 'wildignore' entries +/// options = WILD_SILENT: don't print warning messages +/// options = WILD_ESCAPE: put backslash before special chars +/// options = WILD_ICASE: ignore case for files +/// +/// The variables xp->xp_context and xp->xp_backslash must have been set! +/// +/// @param orig allocated copy of original of expanded string +char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode) +{ + char_u *ss = NULL; + static int findex; + static char_u *orig_save = NULL; // kept value of orig + int orig_saved = false; + int i; + + // first handle the case of using an old match + if (mode == WILD_NEXT || mode == WILD_PREV) { + return get_next_or_prev_match(mode, xp, &findex, orig_save); + } + + if (mode == WILD_CANCEL) { + ss = vim_strsave(orig_save ? orig_save : (char_u *)""); + } else if (mode == WILD_APPLY) { + ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") + : (char_u *)xp->xp_files[findex]); + } + + // free old names + if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + XFREE_CLEAR(orig_save); + } + findex = 0; + + if (mode == WILD_FREE) { // only release file name + return NULL; + } + + if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { + xfree(orig_save); + orig_save = orig; + orig_saved = true; + + ss = ExpandOne_start(mode, xp, str, options); + } + + // Find longest common part + if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { + ss = (char_u *)find_longest_match(xp, options); + findex = -1; // next p_wc gets first one + } + + // Concatenate all matching names. Unless interrupted, this can be slow + // and the result probably won't be used. + // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne) + if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) { + size_t len = 0; + for (i = 0; i < xp->xp_numfiles; i++) { + len += STRLEN(xp->xp_files[i]) + 1; + } + ss = xmalloc(len); + *ss = NUL; + for (i = 0; i < xp->xp_numfiles; i++) { + STRCAT(ss, xp->xp_files[i]); + if (i != xp->xp_numfiles - 1) { + STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); + } + } + } + + if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) { + ExpandCleanup(xp); + } + + // Free "orig" if it wasn't stored in "orig_save". + if (!orig_saved) { + xfree(orig); + } + + return ss; +} + +/// Prepare an expand structure for use. +void ExpandInit(expand_T *xp) + FUNC_ATTR_NONNULL_ALL +{ + CLEAR_POINTER(xp); + xp->xp_backslash = XP_BS_NONE; + xp->xp_numfiles = -1; +} + +/// Cleanup an expand structure after use. +void ExpandCleanup(expand_T *xp) +{ + if (xp->xp_numfiles >= 0) { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + } +} + +/// Show all matches for completion on the command line. +/// Returns EXPAND_NOTHING when the character that triggered expansion should +/// be inserted like a normal character. +int showmatches(expand_T *xp, int wildmenu) +{ + CmdlineInfo *const ccline = get_cmdline_info(); +#define L_SHOWFILE(m) (showtail \ + ? sm_gettail(files_found[m], false) : files_found[m]) + int num_files; + char **files_found; + int i, j, k; + int maxlen; + int lines; + int columns; + char_u *p; + int lastlen; + int attr; + int showtail; + + if (xp->xp_numfiles == -1) { + set_expand_context(xp); + i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, + &num_files, &files_found); + showtail = expand_showtail(xp); + if (i != EXPAND_OK) { + return i; + } + } else { + num_files = xp->xp_numfiles; + files_found = xp->xp_files; + showtail = cmd_showtail; + } + + bool compl_use_pum = (ui_has(kUICmdline) + ? ui_has(kUIPopupmenu) + : wildmenu && (wop_flags & WOP_PUM)) + || ui_has(kUIWildmenu); + + if (compl_use_pum) { + assert(num_files >= 0); + compl_match_arraysize = num_files; + compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)compl_match_arraysize); + for (i = 0; i < num_files; i++) { + compl_match_array[i] = (pumitem_T){ + .pum_text = (char_u *)L_SHOWFILE(i), + .pum_info = NULL, + .pum_extra = NULL, + .pum_kind = NULL, + }; + } + char_u *endpos = (char_u *)(showtail ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern); + if (ui_has(kUICmdline)) { + compl_startcol = (int)(endpos - ccline->cmdbuff); + } else { + compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); + } + compl_selected = -1; + cmdline_pum_display(true); + return EXPAND_OK; + } + + if (!wildmenu) { + msg_didany = false; // lines_left will be set + msg_start(); // prepare for paging + msg_putchar('\n'); + ui_flush(); + cmdline_row = msg_row; + msg_didany = false; // lines_left will be set again + msg_start(); // prepare for paging + } + + if (got_int) { + got_int = false; // only int. the completion, not the cmd line + } else if (wildmenu) { + redraw_wildmenu(xp, num_files, files_found, -1, showtail); + } else { + // find the length of the longest file name + maxlen = 0; + for (i = 0; i < num_files; i++) { + if (!showtail && (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) { + home_replace(NULL, files_found[i], (char *)NameBuff, MAXPATHL, true); + j = vim_strsize((char *)NameBuff); + } else { + j = vim_strsize(L_SHOWFILE(i)); + } + if (j > maxlen) { + maxlen = j; + } + } + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + lines = num_files; + } else { + // compute the number of columns and lines for the listing + maxlen += 2; // two spaces between file names + columns = (Columns + 2) / maxlen; + if (columns < 1) { + columns = 1; + } + lines = (num_files + columns - 1) / columns; + } + + attr = HL_ATTR(HLF_D); // find out highlighting for directories + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); + msg_clr_eos(); + msg_advance(maxlen - 3); + msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); + } + + // list the files line by line + for (i = 0; i < lines; i++) { + lastlen = 999; + for (k = i; k < num_files; k += lines) { + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D)); + p = (char_u *)files_found[k] + STRLEN(files_found[k]) + 1; + msg_advance(maxlen + 1); + msg_puts((const char *)p); + msg_advance(maxlen + 3); + msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); + break; + } + for (j = maxlen - lastlen; --j >= 0;) { + msg_putchar(' '); + } + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS) { + // highlight directories + if (xp->xp_numfiles != -1) { + // Expansion was done before and special characters + // were escaped, need to halve backslashes. Also + // $HOME has been replaced with ~/. + char_u *exp_path = expand_env_save_opt((char_u *)files_found[k], true); + char_u *path = exp_path != NULL ? exp_path : (char_u *)files_found[k]; + char_u *halved_slash = (char_u *)backslash_halve_save((char *)path); + j = os_isdir((char *)halved_slash); + xfree(exp_path); + if (halved_slash != path) { + xfree(halved_slash); + } + } else { + // Expansion was done here, file names are literal. + j = os_isdir(files_found[k]); + } + if (showtail) { + p = (char_u *)L_SHOWFILE(k); + } else { + home_replace(NULL, files_found[k], (char *)NameBuff, MAXPATHL, true); + p = (char_u *)NameBuff; + } + } else { + j = false; + p = (char_u *)L_SHOWFILE(k); + } + lastlen = msg_outtrans_attr((char *)p, j ? attr : 0); + } + if (msg_col > 0) { // when not wrapped around + msg_clr_eos(); + msg_putchar('\n'); + } + ui_flush(); // show one line at a time + if (got_int) { + got_int = false; + break; + } + } + + // we redraw the command below the lines that we have just listed + // This is a bit tricky, but it saves a lot of screen updating. + cmdline_row = msg_row; // will put it back later + } + + if (xp->xp_numfiles == -1) { + FreeWild(num_files, files_found); + } + + return EXPAND_OK; +} + +/// Private path_tail for showmatches() (and redraw_wildmenu()): +/// Find tail of file name path, but ignore trailing "/". +char *sm_gettail(char *s, bool eager) +{ + char_u *p; + char_u *t = (char_u *)s; + bool had_sep = false; + + for (p = (char_u *)s; *p != NUL;) { + if (vim_ispathsep(*p) +#ifdef BACKSLASH_IN_FILENAME + && !rem_backslash(p) +#endif + ) { + if (eager) { + t = p + 1; + } else { + had_sep = true; + } + } else if (had_sep) { + t = p; + had_sep = false; + } + MB_PTR_ADV(p); + } + return (char *)t; +} + +/// Return true if we only need to show the tail of completion matches. +/// When not completing file names or there is a wildcard in the path false is +/// returned. +static bool expand_showtail(expand_T *xp) +{ + char_u *s; + char_u *end; + + // When not completing file names a "/" may mean something different. + if (xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_DIRECTORIES) { + return false; + } + + end = (char_u *)path_tail(xp->xp_pattern); + if (end == (char_u *)xp->xp_pattern) { // there is no path separator + return false; + } + + for (s = (char_u *)xp->xp_pattern; s < end; s++) { + // Skip escaped wildcards. Only when the backslash is not a path + // separator, on DOS the '*' "path\*\file" must not be skipped. + if (rem_backslash((char *)s)) { + s++; + } else if (vim_strchr("*?[", *s) != NULL) { + return false; + } + } + return true; +} + +/// Prepare a string for expansion. +/// +/// When expanding file names: The string will be used with expand_wildcards(). +/// Copy "fname[len]" into allocated memory and add a '*' at the end. +/// When expanding other names: The string will be used with regcomp(). Copy +/// the name into allocated memory and prepend "^". +/// +/// @param context EXPAND_FILES etc. +char_u *addstar(char_u *fname, size_t len, int context) + FUNC_ATTR_NONNULL_RET +{ + char_u *retval; + size_t i, j; + size_t new_len; + char_u *tail; + int ends_in_star; + + if (context != EXPAND_FILES + && context != EXPAND_FILES_IN_PATH + && context != EXPAND_SHELLCMD + && context != EXPAND_DIRECTORIES) { + // Matching will be done internally (on something other than files). + // So we convert the file-matching-type wildcards into our kind for + // use with vim_regcomp(). First work out how long it will be: + + // For help tags the translation is done in find_help_tags(). + // For a tag pattern starting with "/" no translation is needed. + if (context == EXPAND_HELP + || context == EXPAND_CHECKHEALTH + || context == EXPAND_COLORS + || context == EXPAND_COMPILER + || context == EXPAND_OWNSYNTAX + || context == EXPAND_FILETYPE + || context == EXPAND_PACKADD + || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) + && fname[0] == '/')) { + retval = vim_strnsave(fname, len); + } else { + new_len = len + 2; // +2 for '^' at start, NUL at end + for (i = 0; i < len; i++) { + if (fname[i] == '*' || fname[i] == '~') { + new_len++; // '*' needs to be replaced by ".*" + // '~' needs to be replaced by "\~" + } + // Buffer names are like file names. "." should be literal + if (context == EXPAND_BUFFERS && fname[i] == '.') { + new_len++; // "." becomes "\." + } + // Custom expansion takes care of special things, match + // backslashes literally (perhaps also for other types?) + if ((context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) && fname[i] == '\\') { + new_len++; // '\' becomes "\\" + } + } + retval = xmalloc(new_len); + { + retval[0] = '^'; + j = 1; + for (i = 0; i < len; i++, j++) { + // Skip backslash. But why? At least keep it for custom + // expansion. + if (context != EXPAND_USER_DEFINED + && context != EXPAND_USER_LIST + && fname[i] == '\\' + && ++i == len) { + break; + } + + switch (fname[i]) { + case '*': + retval[j++] = '.'; + break; + case '~': + retval[j++] = '\\'; + break; + case '?': + retval[j] = '.'; + continue; + case '.': + if (context == EXPAND_BUFFERS) { + retval[j++] = '\\'; + } + break; + case '\\': + if (context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) { + retval[j++] = '\\'; + } + break; + } + retval[j] = fname[i]; + } + retval[j] = NUL; + } + } + } else { + retval = xmalloc(len + 4); + STRLCPY(retval, fname, len + 1); + + // Don't add a star to *, ~, ~user, $var or `cmd`. + // * would become **, which walks the whole tree. + // ~ would be at the start of the file name, but not the tail. + // $ could be anywhere in the tail. + // ` could be anywhere in the file name. + // When the name ends in '$' don't add a star, remove the '$'. + tail = (char_u *)path_tail((char *)retval); + ends_in_star = (len > 0 && retval[len - 1] == '*'); +#ifndef BACKSLASH_IN_FILENAME + for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { + if (retval[k] != '\\') { + break; + } + ends_in_star = !ends_in_star; + } +#endif + if ((*retval != '~' || tail != retval) + && !ends_in_star + && vim_strchr((char *)tail, '$') == NULL + && vim_strchr((char *)retval, '`') == NULL) { + retval[len++] = '*'; + } else if (len > 0 && retval[len - 1] == '$') { + len--; + } + retval[len] = NUL; + } + return retval; +} + +/// Must parse the command line so far to work out what context we are in. +/// Completion can then be done based on that context. +/// This routine sets the variables: +/// xp->xp_pattern The start of the pattern to be expanded within +/// the command line (ends at the cursor). +/// xp->xp_context The type of thing to expand. Will be one of: +/// +/// EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on +/// the command line, like an unknown command. Caller +/// should beep. +/// EXPAND_NOTHING Unrecognised context for completion, use char like +/// a normal char, rather than for completion. eg +/// :s/^I/ +/// EXPAND_COMMANDS Cursor is still touching the command, so complete +/// it. +/// EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. +/// EXPAND_FILES After command with EX_XFILE set, or after setting +/// with P_EXPAND set. eg :e ^I, :w>>^I +/// EXPAND_DIRECTORIES In some cases this is used instead of the latter +/// when we know only directories are of interest. eg +/// :set dir=^I +/// EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". +/// EXPAND_SETTINGS Complete variable names. eg :set d^I +/// EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I +/// EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I +/// EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect +/// EXPAND_HELP Complete tags from the file 'helpfile'/tags +/// EXPAND_EVENTS Complete event names +/// EXPAND_SYNTAX Complete :syntax command arguments +/// EXPAND_HIGHLIGHT Complete highlight (syntax) group names +/// EXPAND_AUGROUP Complete autocommand group names +/// EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I +/// EXPAND_MAPPINGS Complete mapping and abbreviation names, +/// eg :unmap a^I , :cunab x^I +/// EXPAND_FUNCTIONS Complete internal or user defined function names, +/// eg :call sub^I +/// EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I +/// EXPAND_EXPRESSION Complete internal or user defined function/variable +/// names in expressions, eg :while s^I +/// EXPAND_ENV_VARS Complete environment variable names +/// EXPAND_USER Complete user names +void set_expand_context(expand_T *xp) +{ + CmdlineInfo *const ccline = get_cmdline_info(); + + // only expansion for ':', '>' and '=' command-lines + if (ccline->cmdfirstc != ':' + && ccline->cmdfirstc != '>' && ccline->cmdfirstc != '=' + && !ccline->input_fn) { + xp->xp_context = EXPAND_NOTHING; + return; + } + set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true); +} + +/// Sets the index of a built-in or user defined command "cmd" in eap->cmdidx. +/// For user defined commands, the completion context is set in "xp" and the +/// completion flags in "complp". +/// +/// @return a pointer to the text after the command or NULL for failure. +static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp) +{ + const char *p = NULL; + size_t len = 0; + + // 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' + if (*cmd == 'k' && cmd[1] != 'e') { + eap->cmdidx = CMD_k; + p = cmd + 1; + } else { + p = cmd; + while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card + p++; + } + // a user command may contain digits + if (ASCII_ISUPPER(cmd[0])) { + while (ASCII_ISALNUM(*p) || *p == '*') { + p++; + } + } + // for python 3.x: ":py3*" commands completion + if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { + p++; + while (ASCII_ISALPHA(*p) || *p == '*') { + p++; + } + } + // check for non-alpha command + if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { + p++; + } + len = (size_t)(p - cmd); + + if (len == 0) { + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + eap->cmdidx = excmd_get_cmdidx(cmd, len); + + if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card + p++; + } + } + } + + // If the cursor is touching the command, and it ends in an alphanumeric + // character, complete the command name. + if (*p == NUL && ASCII_ISALNUM(p[-1])) { + return NULL; + } + + if (eap->cmdidx == CMD_SIZE) { + if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { + eap->cmdidx = CMD_substitute; + p = cmd + 1; + } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + eap->cmd = (char *)cmd; + p = (const char *)find_ucmd(eap, (char *)p, NULL, xp, complp); + if (p == NULL) { + eap->cmdidx = CMD_SIZE; // Ambiguous user command. + } + } + } + if (eap->cmdidx == CMD_SIZE) { + // Not still touching the command and it was an illegal one + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + return p; +} + +/// Set the completion context for a command argument with wild card characters. +static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool usefilter, + expand_T *xp, int *complp) +{ + bool in_quote = false; + const char *bow = NULL; // Beginning of word. + size_t len = 0; + + // Allow spaces within back-quotes to count as part of the argument + // being expanded. + xp->xp_pattern = skipwhite(arg); + const char *p = (const char *)xp->xp_pattern; + while (*p != NUL) { + int c = utf_ptr2char(p); + if (c == '\\' && p[1] != NUL) { + p++; + } else if (c == '`') { + if (!in_quote) { + xp->xp_pattern = (char *)p; + 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)) { + len = 0; // avoid getting stuck when space is in 'isfname' + while (*p != NUL) { + c = utf_ptr2char(p); + if (c == '`' || vim_isfilec_or_wc(c)) { + break; + } + len = (size_t)utfc_ptr2len(p); + MB_PTR_ADV(p); + } + if (in_quote) { + bow = p; + } else { + xp->xp_pattern = (char *)p; + } + p -= len; + } + MB_PTR_ADV(p); + } + + // 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; + } + xp->xp_context = EXPAND_FILES; + + // For a shell command more chars need to be escaped. + if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal) { +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = true; +#endif + // When still after the command name expand executables. + if (xp->xp_pattern == skipwhite(arg)) { + xp->xp_context = EXPAND_SHELLCMD; + } + } + + // Check for environment variable. + if (*xp->xp_pattern == '$') { + for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { + if (!vim_isIDc((uint8_t)(*p))) { + break; + } + } + if (*p == NUL) { + xp->xp_context = EXPAND_ENV_VARS; + xp->xp_pattern++; + // Avoid that the assignment uses EXPAND_FILES again. + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) { + *complp = EXPAND_ENV_VARS; + } + } + } + // Check for user names. + if (*xp->xp_pattern == '~') { + for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} + // Complete ~user only if it partially matches a user name. + // A full match ~user<Tab> will be replaced by user's home + // directory i.e. something like ~user<Tab> -> /home/user/ + 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++; + } + } +} + +/// Set the completion context in "xp" for command "cmd" with index "cmdidx". +/// The argument to the command is "arg" and the argument flags is "argt". +/// For user-defined commands and for environment variables, "context" has the +/// completion type. +/// +/// @return a pointer to the next command, or NULL if there is no next command. +static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, const char *arg, + uint32_t argt, int context, expand_T *xp, bool forceit) +{ + const char *p; + + switch (cmdidx) { + case CMD_find: + case CMD_sfind: + case CMD_tabfind: + if (xp->xp_context == EXPAND_FILES) { + xp->xp_context = EXPAND_FILES_IN_PATH; + } + break; + case CMD_cd: + case CMD_chdir: + case CMD_lcd: + case CMD_lchdir: + case CMD_tcd: + case CMD_tchdir: + if (xp->xp_context == EXPAND_FILES) { + xp->xp_context = EXPAND_DIRECTORIES; + } + break; + case CMD_help: + xp->xp_context = EXPAND_HELP; + xp->xp_pattern = (char *)arg; + break; + + // Command modifiers: return the argument. + // Also for commands with an argument that is a command. + case CMD_aboveleft: + case CMD_argdo: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_bufdo: + case CMD_cdo: + case CMD_cfdo: + case CMD_confirm: + case CMD_debug: + case CMD_folddoclosed: + case CMD_folddoopen: + case CMD_hide: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_ldo: + case CMD_leftabove: + case CMD_lfdo: + case CMD_lockmarks: + case CMD_noautocmd: + case CMD_noswapfile: + case CMD_rightbelow: + case CMD_sandbox: + case CMD_silent: + case CMD_tab: + case CMD_tabdo: + case CMD_topleft: + case CMD_verbose: + case CMD_vertical: + case CMD_windo: + return arg; + + case CMD_filter: + if (*arg != NUL) { + arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL); + } + if (arg == NULL || *arg == NUL) { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + return (const char *)skipwhite(arg); + + case CMD_match: + if (*arg == NUL || !ends_excmd(*arg)) { + // also complete "None" + set_context_in_echohl_cmd(xp, arg); + arg = (const char *)skipwhite(skiptowhite(arg)); + if (*arg != NUL) { + xp->xp_context = EXPAND_NOTHING; + arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), p_magic, NULL); + } + } + return (const char *)find_nextcmd(arg); + + // All completion for the +cmdline_compl feature goes here. + + case CMD_command: + return set_context_in_user_cmd(xp, arg); + + case CMD_delcommand: + xp->xp_context = EXPAND_USER_COMMANDS; + xp->xp_pattern = (char *)arg; + break; + + case CMD_global: + case CMD_vglobal: { + const int delim = (uint8_t)(*arg); // Get the delimiter. + if (delim) { + arg++; // Skip delimiter if there is one. + } + + while (arg[0] != NUL && (uint8_t)arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) { + arg++; + } + arg++; + } + if (arg[0] != NUL) { + return arg + 1; + } + break; + } + case CMD_and: + case CMD_substitute: { + const int delim = (uint8_t)(*arg); + if (delim) { + // Skip "from" part. + arg++; + arg = (const char *)skip_regexp((char *)arg, delim, p_magic, NULL); + } + // Skip "to" part. + while (arg[0] != NUL && (uint8_t)arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) { + arg++; + } + arg++; + } + if (arg[0] != NUL) { // Skip delimiter. + arg++; + } + while (arg[0] && strchr("|\"#", arg[0]) == NULL) { + arg++; + } + if (arg[0] != NUL) { + return arg; + } + break; + } + case CMD_isearch: + case CMD_dsearch: + case CMD_ilist: + case CMD_dlist: + case CMD_ijump: + case CMD_psearch: + case CMD_djump: + case CMD_isplit: + case CMD_dsplit: + // Skip count. + arg = (const char *)skipwhite(skipdigits(arg)); + if (*arg == '/') { // Match regexp, not just whole words. + for (++arg; *arg && *arg != '/'; arg++) { + if (*arg == '\\' && arg[1] != NUL) { + arg++; + } + } + if (*arg) { + arg = (const char *)skipwhite(arg + 1); + + // Check for trailing illegal characters. + if (*arg && strchr("|\"\n", *arg) == NULL) { + xp->xp_context = EXPAND_NOTHING; + } else { + return arg; + } + } + } + break; + case CMD_autocmd: + return (const char *)set_context_in_autocmd(xp, (char *)arg, false); + + case CMD_doautocmd: + case CMD_doautoall: + return (const char *)set_context_in_autocmd(xp, (char *)arg, true); + case CMD_set: + set_context_in_set_cmd(xp, (char_u *)arg, 0); + break; + case CMD_setglobal: + set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); + break; + case CMD_setlocal: + set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); + break; + case CMD_tag: + case CMD_stag: + case CMD_ptag: + case CMD_ltag: + case CMD_tselect: + case CMD_stselect: + case CMD_ptselect: + case CMD_tjump: + case CMD_stjump: + case CMD_ptjump: + if (wop_flags & WOP_TAGFILE) { + xp->xp_context = EXPAND_TAGS_LISTFILES; + } else { + xp->xp_context = EXPAND_TAGS; + } + xp->xp_pattern = (char *)arg; + break; + case CMD_augroup: + xp->xp_context = EXPAND_AUGROUP; + xp->xp_pattern = (char *)arg; + break; + case CMD_syntax: + set_context_in_syntax_cmd(xp, arg); + break; + case CMD_const: + case CMD_let: + case CMD_if: + case CMD_elseif: + case CMD_while: + case CMD_for: + case CMD_echo: + case CMD_echon: + case CMD_execute: + case CMD_echomsg: + case CMD_echoerr: + case CMD_call: + case CMD_return: + case CMD_cexpr: + case CMD_caddexpr: + case CMD_cgetexpr: + case CMD_lexpr: + case CMD_laddexpr: + case CMD_lgetexpr: + set_context_for_expression(xp, (char *)arg, cmdidx); + break; + + case CMD_unlet: + while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { + arg = (const char *)xp->xp_pattern + 1; + } + + xp->xp_context = EXPAND_USER_VARS; + xp->xp_pattern = (char *)arg; + + if (*xp->xp_pattern == '$') { + xp->xp_context = EXPAND_ENV_VARS; + xp->xp_pattern++; + } + + break; + + case CMD_function: + case CMD_delfunction: + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = (char *)arg; + break; + + case CMD_echohl: + set_context_in_echohl_cmd(xp, arg); + break; + case CMD_highlight: + set_context_in_highlight_cmd(xp, arg); + break; + case CMD_cscope: + case CMD_lcscope: + case CMD_scscope: + set_context_in_cscope_cmd(xp, arg, cmdidx); + break; + case CMD_sign: + set_context_in_sign_cmd(xp, (char_u *)arg); + break; + case CMD_bdelete: + case CMD_bwipeout: + case CMD_bunload: + while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { + arg = (const char *)xp->xp_pattern + 1; + } + FALLTHROUGH; + case CMD_buffer: + case CMD_sbuffer: + case CMD_checktime: + xp->xp_context = EXPAND_BUFFERS; + xp->xp_pattern = (char *)arg; + break; + case CMD_diffget: + case CMD_diffput: + // If current buffer is in diff mode, complete buffer names + // which are in diff mode, and different than current buffer. + xp->xp_context = EXPAND_DIFF_BUFFERS; + xp->xp_pattern = (char *)arg; + break; + + case CMD_USER: + case CMD_USER_BUF: + if (context != EXPAND_NOTHING) { + // EX_XFILE: file names are handled above. + if (!(argt & EX_XFILE)) { + if (context == EXPAND_MENUS) { + return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); + } else if (context == EXPAND_COMMANDS) { + return arg; + } else if (context == EXPAND_MAPPINGS) { + return (const char *)set_context_in_map_cmd(xp, "map", (char_u *)arg, forceit, + false, false, + CMD_map); + } + // Find start of last argument. + p = arg; + while (*p) { + if (*p == ' ') { + // argument starts after a space + arg = p + 1; + } else if (*p == '\\' && *(p + 1) != NUL) { + p++; // skip over escaped character + } + MB_PTR_ADV(p); + } + xp->xp_pattern = (char *)arg; + } + xp->xp_context = context; + } + break; + + case CMD_map: + case CMD_noremap: + case CMD_nmap: + case CMD_nnoremap: + case CMD_vmap: + case CMD_vnoremap: + case CMD_omap: + case CMD_onoremap: + case CMD_imap: + case CMD_inoremap: + case CMD_cmap: + case CMD_cnoremap: + case CMD_lmap: + case CMD_lnoremap: + case CMD_smap: + case CMD_snoremap: + case CMD_xmap: + case CMD_xnoremap: + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, + false, cmdidx); + case CMD_unmap: + case CMD_nunmap: + case CMD_vunmap: + case CMD_ounmap: + case CMD_iunmap: + case CMD_cunmap: + case CMD_lunmap: + case CMD_sunmap: + case CMD_xunmap: + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, + true, cmdidx); + case CMD_mapclear: + case CMD_nmapclear: + case CMD_vmapclear: + case CMD_omapclear: + case CMD_imapclear: + case CMD_cmapclear: + case CMD_lmapclear: + case CMD_smapclear: + case CMD_xmapclear: + xp->xp_context = EXPAND_MAPCLEAR; + xp->xp_pattern = (char *)arg; + break; + + case CMD_abbreviate: + case CMD_noreabbrev: + case CMD_cabbrev: + case CMD_cnoreabbrev: + case CMD_iabbrev: + case CMD_inoreabbrev: + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, + false, cmdidx); + case CMD_unabbreviate: + case CMD_cunabbrev: + case CMD_iunabbrev: + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, + true, cmdidx); + case CMD_menu: + case CMD_noremenu: + case CMD_unmenu: + case CMD_amenu: + case CMD_anoremenu: + case CMD_aunmenu: + case CMD_nmenu: + case CMD_nnoremenu: + case CMD_nunmenu: + case CMD_vmenu: + case CMD_vnoremenu: + case CMD_vunmenu: + case CMD_omenu: + case CMD_onoremenu: + case CMD_ounmenu: + case CMD_imenu: + case CMD_inoremenu: + case CMD_iunmenu: + case CMD_cmenu: + case CMD_cnoremenu: + case CMD_cunmenu: + case CMD_tlmenu: + case CMD_tlnoremenu: + case CMD_tlunmenu: + case CMD_tmenu: + case CMD_tunmenu: + case CMD_popup: + case CMD_emenu: + return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); + + case CMD_colorscheme: + xp->xp_context = EXPAND_COLORS; + xp->xp_pattern = (char *)arg; + break; + + case CMD_compiler: + xp->xp_context = EXPAND_COMPILER; + xp->xp_pattern = (char *)arg; + break; + + case CMD_ownsyntax: + xp->xp_context = EXPAND_OWNSYNTAX; + xp->xp_pattern = (char *)arg; + break; + + case CMD_setfiletype: + xp->xp_context = EXPAND_FILETYPE; + xp->xp_pattern = (char *)arg; + break; + + case CMD_packadd: + xp->xp_context = EXPAND_PACKADD; + xp->xp_pattern = (char *)arg; + break; + +#ifdef HAVE_WORKING_LIBINTL + case CMD_language: + p = (const char *)skiptowhite(arg); + if (*p == NUL) { + xp->xp_context = EXPAND_LANGUAGE; + xp->xp_pattern = (char *)arg; + } else { + if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 + || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 + || strncmp(arg, "time", (size_t)(p - arg)) == 0 + || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { + xp->xp_context = EXPAND_LOCALES; + xp->xp_pattern = skipwhite(p); + } else { + xp->xp_context = EXPAND_NOTHING; + } + } + break; +#endif + case CMD_profile: + set_context_in_profile_cmd(xp, arg); + break; + case CMD_checkhealth: + xp->xp_context = EXPAND_CHECKHEALTH; + xp->xp_pattern = (char *)arg; + break; + case CMD_behave: + xp->xp_context = EXPAND_BEHAVE; + xp->xp_pattern = (char *)arg; + break; + + case CMD_messages: + xp->xp_context = EXPAND_MESSAGES; + xp->xp_pattern = (char *)arg; + break; + + case CMD_history: + xp->xp_context = EXPAND_HISTORY; + xp->xp_pattern = (char *)arg; + break; + case CMD_syntime: + xp->xp_context = EXPAND_SYNTIME; + xp->xp_pattern = (char *)arg; + break; + + case CMD_argdelete: + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) { + arg = (const char *)(xp->xp_pattern + 1); + } + xp->xp_context = EXPAND_ARGLIST; + xp->xp_pattern = (char *)arg; + break; + + case CMD_lua: + xp->xp_context = EXPAND_LUA; + break; + + default: + break; + } + return NULL; +} + +/// This is all pretty much copied from do_one_cmd(), with all the extra stuff +/// we don't need/want deleted. Maybe this could be done better if we didn't +/// repeat all this stuff. The only problem is that they may not stay +/// perfectly compatible with each other, but then the command line syntax +/// probably won't change that much -- webb. +/// +/// @param buff buffer for command string +static const char *set_one_cmd_context(expand_T *xp, const char *buff) +{ + size_t len = 0; + exarg_T ea; + int context = EXPAND_NOTHING; + bool forceit = false; + bool usefilter = false; // Filter instead of file name. + + ExpandInit(xp); + xp->xp_pattern = (char *)buff; + xp->xp_line = (char *)buff; + xp->xp_context = EXPAND_COMMANDS; // Default until we get past command + ea.argt = 0; + + // 1. skip comment lines and leading space, colons or bars + const char *cmd; + for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} + xp->xp_pattern = (char *)cmd; + + if (*cmd == NUL) { + return NULL; + } + if (*cmd == '"') { // ignore comment lines + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + // 3. skip over a range specifier of the form: addr [,addr] [;addr] .. + cmd = (const char *)skip_range(cmd, &xp->xp_context); + xp->xp_pattern = (char *)cmd; + if (*cmd == NUL) { + return NULL; + } + if (*cmd == '"') { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') { + return cmd + 1; // There's another command + } + + // Get the command index. + const char *p = set_cmd_index(cmd, &ea, xp, &context); + if (p == NULL) { + return NULL; + } + + xp->xp_context = EXPAND_NOTHING; // Default now that we're past command + + if (*p == '!') { // forced commands + forceit = true; + p++; + } + + // 6. parse arguments + if (!IS_USER_CMDIDX(ea.cmdidx)) { + ea.argt = excmd_get_argt(ea.cmdidx); + } + + const char *arg = (const char *)skipwhite(p); + + // Skip over ++argopt argument + if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { + p = arg; + while (*p && !ascii_isspace(*p)) { + MB_PTR_ADV(p); + } + arg = (const char *)skipwhite(p); + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { + if (*arg == '>') { // append + if (*++arg == '>') { + arg++; + } + arg = (const char *)skipwhite(arg); + } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter + arg++; + usefilter = true; + } + } + + if (ea.cmdidx == CMD_read) { + usefilter = forceit; // :r! filter if forced + if (*arg == '!') { // :r !filter + arg++; + usefilter = true; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + while (*arg == *cmd) { // allow any number of '>' or '<' + arg++; + } + arg = (const char *)skipwhite(arg); + } + + // Does command allow "+command"? + if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { + // Check if we're in the +command + p = arg + 1; + arg = (const char *)skip_cmd_arg((char *)arg, false); + + // Still touching the command after '+'? + if (*arg == NUL) { + return p; + } + + // Skip space(s) after +command to get to the real argument. + arg = (const char *)skipwhite(arg); + } + + // 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 + if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { + p += 2; + } + while (*p) { + if (*p == Ctrl_V) { + if (p[1] != NUL) { + p++; + } + } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) + || *p == '|' + || *p == '\n') { + if (*(p - 1) != '\\') { + if (*p == '|' || *p == '\n') { + return p + 1; + } + return NULL; // It's a comment + } + } + MB_PTR_ADV(p); + } + } + + if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { + // no arguments allowed but there is something + return NULL; + } + + // Find start of last argument (argument just before cursor): + p = buff; + xp->xp_pattern = (char *)p; + len = strlen(buff); + while (*p && p < buff + len) { + if (*p == ' ' || *p == TAB) { + // argument starts after a space + xp->xp_pattern = (char *)++p; + } else { + if (*p == '\\' && *(p + 1) != NUL) { + p++; // skip over escaped character + } + MB_PTR_ADV(p); + } + } + + if (ea.argt & EX_XFILE) { + set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &context); + } + + // Switch on command name. + return set_context_by_cmdname(cmd, ea.cmdidx, arg, ea.argt, context, xp, forceit); +} + +/// @param str start of command line +/// @param len length of command line (excl. NUL) +/// @param col position of cursor +/// @param use_ccline use ccline for info +void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline) +{ + CmdlineInfo *const ccline = get_cmdline_info(); + char_u old_char = NUL; + + // Avoid a UMR warning from Purify, only save the character if it has been + // written before. + if (col < len) { + old_char = str[col]; + } + str[col] = NUL; + const char *nextcomm = (const char *)str; + + if (use_ccline && ccline->cmdfirstc == '=') { + // pass CMD_SIZE because there is no real command + set_context_for_expression(xp, (char *)str, CMD_SIZE); + } else if (use_ccline && ccline->input_fn) { + xp->xp_context = ccline->xp_context; + xp->xp_pattern = (char *)ccline->cmdbuff; + xp->xp_arg = (char *)ccline->xp_arg; + } else { + while (nextcomm != NULL) { + nextcomm = set_one_cmd_context(xp, nextcomm); + } + } + + // Store the string here so that call_user_expand_func() can get to them + // easily. + xp->xp_line = (char *)str; + xp->xp_col = col; + + str[col] = old_char; +} + +/// Expand the command line "str" from context "xp". +/// "xp" must have been set by set_cmd_context(). +/// xp->xp_pattern points into "str", to where the text that is to be expanded +/// starts. +/// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the +/// cursor. +/// Returns EXPAND_NOTHING when there is nothing to expand, might insert the +/// key that triggered expansion literally. +/// Returns EXPAND_OK otherwise. +/// +/// @param str start of command line +/// @param col position of cursor +/// @param matchcount return: nr of matches +/// @param matches return: array of pointers to matches +int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char ***matches) +{ + char_u *file_str = NULL; + int options = WILD_ADD_SLASH|WILD_SILENT; + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) { + beep_flush(); + return EXPAND_UNSUCCESSFUL; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) { + // Caller can use the character as a normal char instead + return EXPAND_NOTHING; + } + + // add star to file name, or convert to regexp if not exp. files. + assert((str + col) - (char_u *)xp->xp_pattern >= 0); + xp->xp_pattern_len = (size_t)((str + col) - (char_u *)xp->xp_pattern); + file_str = addstar((char_u *)xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + + if (p_wic) { + options += WILD_ICASE; + } + + // find all files that match the description + if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) { + *matchcount = 0; + *matches = NULL; + } + xfree(file_str); + + return EXPAND_OK; +} + +/// Expand file or directory names. +static int expand_files_and_dirs(expand_T *xp, char_u *pat, char ***file, int *num_file, int flags, + int options) +{ + bool free_pat = false; + + // for ":set path=" and ":set tags=" halve backslashes for escaped space + if (xp->xp_backslash != XP_BS_NONE) { + free_pat = true; + pat = vim_strsave(pat); + for (int i = 0; pat[i]; i++) { + if (pat[i] == '\\') { + if (xp->xp_backslash == XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') { + STRMOVE(pat + i, pat + i + 3); + } + if (xp->xp_backslash == XP_BS_ONE + && pat[i + 1] == ' ') { + STRMOVE(pat + i, pat + i + 1); + } + } + } + } + + if (xp->xp_context == EXPAND_FILES) { + flags |= EW_FILE; + } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { + flags |= (EW_FILE | EW_PATH); + } else { + flags = (flags | EW_DIR) & ~EW_FILE; + } + if (options & WILD_ICASE) { + flags |= EW_ICASE; + } + + // Expand wildcards, supporting %:h and the like. + int ret = expand_wildcards_eval(&pat, num_file, file, flags); + if (free_pat) { + xfree(pat); + } +#ifdef BACKSLASH_IN_FILENAME + if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { + for (int j = 0; j < *num_file; j++) { + char *ptr = (*file)[j]; + while (*ptr != NUL) { + if (p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif + return ret; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":behave {mswin,xterm}" command. +static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return "mswin"; + } + if (idx == 1) { + return "xterm"; + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":messages {clear}" command. +static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return "clear"; + } + return NULL; +} + +static char *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return "<buffer>"; + } + return NULL; +} + +/// Completion for |:checkhealth| command. +/// +/// Given to ExpandGeneric() to obtain all available heathcheck names. +/// @param[in] idx Index of the healthcheck item. +/// @param[in] xp Not used. +static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + static Object names = OBJECT_INIT; + static unsigned last_gen = 0; + + if (last_gen != get_cmdline_last_prompt_id() || last_gen == 0) { + Array a = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err); + api_clear_error(&err); + api_free_object(names); + names = res; + last_gen = get_cmdline_last_prompt_id(); + } + + if (names.type == kObjectTypeArray && idx < (int)names.data.array.size) { + return names.data.array.items[idx].data.string.data; + } + return NULL; +} + +/// Do the expansion based on xp->xp_context and "rmp". +static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***file) +{ + typedef CompleteListItemGetter ExpandFunc; + static struct expgen { + int context; + ExpandFunc func; + int ic; + int escaped; + } tab[] = { + { EXPAND_COMMANDS, get_command_name, false, true }, + { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, + { EXPAND_MESSAGES, get_messages_arg, true, true }, + { EXPAND_HISTORY, get_history_arg, true, true }, + { EXPAND_USER_COMMANDS, get_user_commands, false, true }, + { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, + { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, + { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, + { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, + { EXPAND_USER_VARS, get_user_var_name, false, true }, + { EXPAND_FUNCTIONS, get_function_name, false, true }, + { EXPAND_USER_FUNC, get_user_func_name, false, true }, + { EXPAND_EXPRESSION, get_expr_name, false, true }, + { EXPAND_MENUS, get_menu_name, false, true }, + { EXPAND_MENUNAMES, get_menu_names, false, true }, + { EXPAND_SYNTAX, get_syntax_name, true, true }, + { EXPAND_SYNTIME, get_syntime_arg, true, true }, + { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false }, + { 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 }, +#ifdef HAVE_WORKING_LIBINTL + { EXPAND_LANGUAGE, get_lang_arg, true, false }, + { EXPAND_LOCALES, get_locales, true, false }, +#endif + { EXPAND_ENV_VARS, get_env_name, true, true }, + { EXPAND_USER, get_users, true, false }, + { EXPAND_ARGLIST, get_arglist_name, true, false }, + { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, + }; + int ret = FAIL; + + // Find a context in the table and call the ExpandGeneric() with the + // right function to do the expansion. + for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) { + if (xp->xp_context == tab[i].context) { + if (tab[i].ic) { + rmp->rm_ic = true; + } + ExpandGeneric(xp, rmp, num_file, file, tab[i].func, tab[i].escaped); + ret = OK; + break; + } + } + + return ret; +} + +/// Do the expansion based on xp->xp_context and "pat". +/// +/// @param options WILD_ flags +static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***file, int options) +{ + regmatch_T regmatch; + int ret; + int flags; + + flags = EW_DIR; // include directories + if (options & WILD_LIST_NOTFOUND) { + flags |= EW_NOTFOUND; + } + if (options & WILD_ADD_SLASH) { + flags |= EW_ADDSLASH; + } + if (options & WILD_KEEP_ALL) { + flags |= EW_KEEPALL; + } + if (options & WILD_SILENT) { + flags |= EW_SILENT; + } + if (options & WILD_NOERROR) { + flags |= EW_NOERROR; + } + if (options & WILD_ALLLINKS) { + flags |= EW_ALLLINKS; + } + + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES_IN_PATH) { + return expand_files_and_dirs(xp, pat, file, num_file, flags, options); + } + + *file = NULL; + *num_file = 0; + if (xp->xp_context == EXPAND_HELP) { + // With an empty argument we would get all the help tags, which is + // very slow. Get matches for "help" instead. + if (find_help_tags(*pat == NUL ? "help" : (char *)pat, + num_file, file, false) == OK) { + cleanup_help_tags(*num_file, *file); + return OK; + } + return FAIL; + } + + if (xp->xp_context == EXPAND_SHELLCMD) { + *file = NULL; + expand_shellcmd(pat, num_file, file, flags); + return OK; + } + if (xp->xp_context == EXPAND_OLD_SETTING) { + ExpandOldSetting(num_file, file); + return OK; + } + if (xp->xp_context == EXPAND_BUFFERS) { + return ExpandBufnames((char *)pat, num_file, file, options); + } + if (xp->xp_context == EXPAND_DIFF_BUFFERS) { + return ExpandBufnames((char *)pat, num_file, file, options | BUF_DIFF_FILTER); + } + if (xp->xp_context == EXPAND_TAGS + || xp->xp_context == EXPAND_TAGS_LISTFILES) { + return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + } + if (xp->xp_context == EXPAND_COLORS) { + char *directories[] = { "colors", NULL }; + return ExpandRTDir((char *)pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, directories); + } + if (xp->xp_context == EXPAND_COMPILER) { + char *directories[] = { "compiler", NULL }; + return ExpandRTDir((char *)pat, DIP_LUA, num_file, file, directories); + } + if (xp->xp_context == EXPAND_OWNSYNTAX) { + char *directories[] = { "syntax", NULL }; + return ExpandRTDir((char *)pat, 0, num_file, file, directories); + } + if (xp->xp_context == EXPAND_FILETYPE) { + char *directories[] = { "syntax", "indent", "ftplugin", NULL }; + return ExpandRTDir((char *)pat, DIP_LUA, num_file, file, directories); + } + if (xp->xp_context == EXPAND_USER_LIST) { + return ExpandUserList(xp, num_file, file); + } + if (xp->xp_context == EXPAND_USER_LUA) { + return ExpandUserLua(xp, num_file, file); + } + if (xp->xp_context == EXPAND_PACKADD) { + return ExpandPackAddDir((char *)pat, num_file, file); + } + + // When expanding a function name starting with s:, match the <SNR>nr_ + // prefix. + char *tofree = NULL; + if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { + const size_t len = STRLEN(pat) + 20; + + tofree = xmalloc(len); + snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); + pat = (char_u *)tofree; + } + + if (xp->xp_context == EXPAND_LUA) { + ILOG("PAT %s", pat); + return nlua_expand_pat(xp, pat, num_file, file); + } + + regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + return FAIL; + } + + // set ignore-case according to p_ic, p_scs and pat + regmatch.rm_ic = ignorecase(pat); + + if (xp->xp_context == EXPAND_SETTINGS + || xp->xp_context == EXPAND_BOOL_SETTINGS) { + ret = ExpandSettings(xp, ®match, num_file, file); + } else if (xp->xp_context == EXPAND_MAPPINGS) { + ret = ExpandMappings(®match, num_file, file); + } else if (xp->xp_context == EXPAND_USER_DEFINED) { + ret = ExpandUserDefined(xp, ®match, num_file, file); + } else { + ret = ExpandOther(xp, ®match, num_file, file); + } + + vim_regfree(regmatch.regprog); + xfree(tofree); + + return ret; +} + +/// Expand a list of names. +/// +/// Generic function for command line completion. It calls a function to +/// obtain strings, one by one. The strings are matched against a regexp +/// program. Matching strings are copied into an array, which is returned. +/// +/// @param func returns a string from the list +static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file, + CompleteListItemGetter func, int escaped) +{ + int i; + size_t count = 0; + char_u *str; + + // count the number of matching names + for (i = 0;; i++) { + str = (char_u *)(*func)(xp, i); + if (str == NULL) { // end of list + break; + } + if (*str == NUL) { // skip empty strings + continue; + } + if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { + count++; + } + } + if (count == 0) { + return; + } + assert(count < INT_MAX); + *num_file = (int)count; + *file = xmalloc(count * sizeof(char_u *)); + + // copy the matching names into allocated memory + count = 0; + for (i = 0;; i++) { + str = (char_u *)(*func)(xp, i); + if (str == NULL) { // End of list. + break; + } + if (*str == NUL) { // Skip empty strings. + continue; + } + if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { + if (escaped) { + str = vim_strsave_escaped(str, (char_u *)" \t\\."); + } else { + str = vim_strsave(str); + } + (*file)[count++] = (char *)str; + if (func == get_menu_names) { + // Test for separator added by get_menu_names(). + str += STRLEN(str) - 1; + if (*str == '\001') { + *str = '.'; + } + } + } + } + + // Sort the results. Keep menu's in the specified order. + if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) { + if (xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC) { + // <SNR> functions should be sorted to the end. + qsort((void *)(*file), (size_t)(*num_file), sizeof(char_u *), + sort_func_compare); + } else { + sort_strings(*file, *num_file); + } + } + + // Reset the variables used for special highlight names expansion, so that + // they don't show up when getting normal highlight names by ID. + reset_expand_highlight(); +} + +/// Complete a shell command. +/// +/// @param filepat is a pattern to match with command names. +/// @param[out] num_file is pointer to number of matches. +/// @param[out] file is pointer to array of pointers to matches. +/// *file will either be set to NULL or point to +/// allocated memory. +/// @param flagsarg is a combination of EW_* flags. +static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int flagsarg) + FUNC_ATTR_NONNULL_ALL +{ + char_u *pat; + int i; + char_u *path = NULL; + garray_T ga; + char *buf = xmalloc(MAXPATHL); + size_t l; + char_u *s, *e; + int flags = flagsarg; + int ret; + bool did_curdir = false; + + // for ":set path=" and ":set tags=" halve backslashes for escaped space + pat = vim_strsave(filepat); + for (i = 0; pat[i]; i++) { + if (pat[i] == '\\' && pat[i + 1] == ' ') { + STRMOVE(pat + i, pat + i + 1); + } + } + + flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; + + bool mustfree = false; // Track memory allocation for *path. + if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' && vim_ispathsep(pat[2])))) { + path = (char_u *)"."; + } else { + // For an absolute name we don't use $PATH. + if (!path_is_absolute(pat)) { + path = (char_u *)vim_getenv("PATH"); + } + if (path == NULL) { + path = (char_u *)""; + } else { + mustfree = true; + } + } + + // Go over all directories in $PATH. Expand matches in that directory and + // collect them in "ga". When "." is not in $PATH also expaned for the + // current directory, to find "subdir/cmd". + ga_init(&ga, (int)sizeof(char *), 10); + hashtab_T found_ht; + hash_init(&found_ht); + for (s = path;; s = e) { + e = (char_u *)vim_strchr((char *)s, ENV_SEPCHAR); + if (e == NULL) { + e = s + STRLEN(s); + } + + if (*s == NUL) { + if (did_curdir) { + break; + } + // Find directories in the current directory, path is empty. + did_curdir = true; + flags |= EW_DIR; + } else if (STRNCMP(s, ".", e - s) == 0) { + did_curdir = true; + flags |= EW_DIR; + } else { + // Do not match directories inside a $PATH item. + flags &= ~EW_DIR; + } + + l = (size_t)(e - s); + if (l > MAXPATHL - 5) { + break; + } + STRLCPY(buf, s, l + 1); + add_pathsep(buf); + l = STRLEN(buf); + STRLCPY(buf + l, pat, MAXPATHL - l); + + // Expand matches in one directory of $PATH. + ret = expand_wildcards(1, &buf, num_file, file, flags); + if (ret == OK) { + ga_grow(&ga, *num_file); + { + for (i = 0; i < *num_file; i++) { + char_u *name = (char_u *)(*file)[i]; + + if (STRLEN(name) > l) { + // Check if this name was already found. + hash_T hash = hash_hash(name + l); + hashitem_T *hi = + hash_lookup(&found_ht, (const char *)(name + l), + STRLEN(name + l), hash); + if (HASHITEM_EMPTY(hi)) { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char_u **)ga.ga_data)[ga.ga_len++] = name; + hash_add_item(&found_ht, hi, name, hash); + name = NULL; + } + } + xfree(name); + } + xfree(*file); + } + } + if (*e != NUL) { + e++; + } + } + *file = ga.ga_data; + *num_file = ga.ga_len; + + xfree(buf); + xfree(pat); + if (mustfree) { + xfree(path); + } + hash_clear(&found_ht); +} + +/// Call "user_expand_func()" to invoke a user defined Vim script function and +/// return the result (either a string, a List or NULL). +static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file, + char ***file) + FUNC_ATTR_NONNULL_ALL +{ + CmdlineInfo *const ccline = get_cmdline_info(); + char_u keep = 0; + typval_T args[4]; + char_u *pat = NULL; + const sctx_T save_current_sctx = current_sctx; + + if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { + return NULL; + } + *num_file = 0; + *file = NULL; + + if (ccline->cmdbuff != NULL) { + keep = ccline->cmdbuff[ccline->cmdlen]; + ccline->cmdbuff[ccline->cmdlen] = 0; + } + + pat = vim_strnsave((char_u *)xp->xp_pattern, xp->xp_pattern_len); + args[0].v_type = VAR_STRING; + args[1].v_type = VAR_STRING; + args[2].v_type = VAR_NUMBER; + args[3].v_type = VAR_UNKNOWN; + args[0].vval.v_string = (char *)pat; + args[1].vval.v_string = xp->xp_line; + args[2].vval.v_number = xp->xp_col; + + current_sctx = xp->xp_script_ctx; + + void *const ret = user_expand_func((char_u *)xp->xp_arg, 3, args); + + current_sctx = save_current_sctx; + if (ccline->cmdbuff != NULL) { + ccline->cmdbuff[ccline->cmdlen] = keep; + } + + xfree(pat); + return ret; +} + +/// Expand names with a function defined by the user. +static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file) +{ + char_u *e; + garray_T ga; + + char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file, + file); + + if (retstr == NULL) { + return FAIL; + } + + ga_init(&ga, (int)sizeof(char *), 3); + for (char_u *s = retstr; *s != NUL; s = e) { + e = (char_u *)vim_strchr((char *)s, '\n'); + if (e == NULL) { + e = s + STRLEN(s); + } + const char_u keep = *e; + *e = NUL; + + const bool skip = xp->xp_pattern[0] + && vim_regexec(regmatch, (char *)s, (colnr_T)0) == 0; + *e = keep; + if (!skip) { + GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); + } + + if (*e != NUL) { + e++; + } + } + xfree(retstr); + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/// Expand names with a list returned by a function defined by the user. +static int ExpandUserList(expand_T *xp, int *num_file, char ***file) +{ + list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file, + file); + if (retlist == NULL) { + return FAIL; + } + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } + + GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) +{ + typval_T rettv; + nlua_call_user_expand_func(xp, &rettv); + if (rettv.v_type != VAR_LIST) { + tv_clear(&rettv); + return FAIL; + } + + list_T *const retlist = rettv.vval.v_list; + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } + + GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); + + *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 *path, char *file, garray_T *ga, int expand_options) +{ + expand_T xpc; + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + + char_u *buf = xmalloc(MAXPATHL); + + // 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(&path, (char *)buf, MAXPATHL, ","); + if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { + add_pathsep((char *)buf); + STRCAT(buf, file); // NOLINT + + char **p; + int num_p = 0; + (void)ExpandFromContext(&xpc, buf, &num_p, &p, + WILD_SILENT | expand_options); + if (num_p > 0) { + ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); + + // Concatenate new results to previous ones. + ga_grow(ga, num_p); + // take over the pointers and put them in "ga" + for (int i = 0; i < num_p; i++) { + ((char_u **)ga->ga_data)[ga->ga_len] = (char_u *)p[i]; + ga->ga_len++; + } + xfree(p); + } + } + } + + xfree(buf); +} + +/// Translate some keys pressed when 'wildmenu' is used. +int wildmenu_translate_key(CmdlineInfo *cclp, int key, expand_T *xp, int did_wild_list) +{ + int c = key; + + if (did_wild_list) { + if (c == K_LEFT) { + c = Ctrl_P; + } else if (c == K_RIGHT) { + c = Ctrl_N; + } + } + + // Hitting CR after "emenu Name.": complete submenu + if (xp->xp_context == EXPAND_MENUNAMES + && cclp->cmdpos > 1 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.' + && cclp->cmdbuff[cclp->cmdpos - 2] != '\\' + && (c == '\n' || c == '\r' || c == K_KENTER)) { + c = K_DOWN; + } + + return c; +} + +/// Delete characters on the command line, from "from" to the current position. +static void cmdline_del(CmdlineInfo *cclp, int from) +{ + assert(cclp->cmdpos <= cclp->cmdlen); + memmove(cclp->cmdbuff + from, cclp->cmdbuff + cclp->cmdpos, + (size_t)cclp->cmdlen - (size_t)cclp->cmdpos + 1); + cclp->cmdlen -= cclp->cmdpos - from; + cclp->cmdpos = from; +} + +/// Handle a key pressed when wild menu is displayed +int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp) +{ + int c = key; + + // Special translations for 'wildmenu' + if (xp->xp_context == EXPAND_MENUNAMES) { + // Hitting <Down> after "emenu Name.": complete submenu + if (c == K_DOWN && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.') { + c = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } else if (c == K_UP) { + // Hitting <Up>: Remove one submenu name in front of the + // cursor + int found = false; + + int j = (int)((char_u *)xp->xp_pattern - cclp->cmdbuff); + int i = 0; + while (--j > 0) { + // check for start of menu name + if (cclp->cmdbuff[j] == ' ' + && cclp->cmdbuff[j - 1] != '\\') { + i = j + 1; + break; + } + + // check for start of submenu name + if (cclp->cmdbuff[j] == '.' + && cclp->cmdbuff[j - 1] != '\\') { + if (found) { + i = j + 1; + break; + } else { + found = true; + } + } + } + if (i > 0) { + cmdline_del(cclp, i); + } + c = (int)p_wc; + KeyTyped = true; // in case the key was mapped + xp->xp_context = EXPAND_NOTHING; + } + } + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_SHELLCMD) { + char_u upseg[5]; + + upseg[0] = PATHSEP; + upseg[1] = '.'; + upseg[2] = '.'; + upseg[3] = PATHSEP; + upseg[4] = NUL; + + if (c == K_DOWN + && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP + && (cclp->cmdpos < 3 + || cclp->cmdbuff[cclp->cmdpos - 2] != '.' + || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) { + // go down a directory + c = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } else if (STRNCMP(xp->xp_pattern, upseg + 1, 3) == 0 + && c == K_DOWN) { + // If in a direct ancestor, strip off one ../ to go down + int found = false; + + int j = cclp->cmdpos; + int i = (int)((char_u *)xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { + j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j])) { + found = true; + break; + } + } + if (found + && cclp->cmdbuff[j - 1] == '.' + && cclp->cmdbuff[j - 2] == '.' + && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) { + cmdline_del(cclp, j - 2); + c = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } + } else if (c == K_UP) { + // go up a directory + int found = false; + + int j = cclp->cmdpos - 1; + int i = (int)((char_u *)xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { + j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j]) +#ifdef BACKSLASH_IN_FILENAME + && vim_strchr((const char_u *)" *?[{`$%#", cclp->cmdbuff[j + 1]) + == NULL +#endif + ) { + if (found) { + i = j + 1; + break; + } else { + found = true; + } + } + } + + if (!found) { + j = i; + } else if (STRNCMP(cclp->cmdbuff + j, upseg, 4) == 0) { + j += 4; + } else if (STRNCMP(cclp->cmdbuff + j, upseg + 1, 3) == 0 + && j == i) { + j += 3; + } else { + j = 0; + } + + if (j > 0) { + // TODO(tarruda): this is only for DOS/Unix systems - need to put in + // machine-specific stuff here and in upseg init + cmdline_del(cclp, j); + put_on_cmdline(upseg + 1, 3, false); + } else if (cclp->cmdpos > i) { + cmdline_del(cclp, i); + } + + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + c = (int)p_wc; + KeyTyped = true; + } + } + + return c; +} + +/// Free expanded names when finished walking through the matches +void wildmenu_cleanup(CmdlineInfo *cclp) +{ + if (!p_wmnu || wild_menu_showing == 0) { + return; + } + + const bool skt = KeyTyped; + const int old_RedrawingDisabled = RedrawingDisabled; + + if (cclp->input_fn) { + RedrawingDisabled = 0; + } + + if (wild_menu_showing == WM_SCROLLED) { + // Entered command line, move it up + cmdline_row--; + redrawcmd(); + wild_menu_showing = 0; + } else if (save_p_ls != -1) { + // restore 'laststatus' and 'winminheight' + p_ls = save_p_ls; + p_wmh = save_p_wmh; + last_status(false); + update_screen(UPD_VALID); // redraw the screen NOW + redrawcmd(); + save_p_ls = -1; + wild_menu_showing = 0; + // don't redraw statusline if WM_LIST is showing + } else if (wild_menu_showing != WM_LIST) { + win_redraw_last_status(topframe); + wild_menu_showing = 0; // must be before redraw_statuslines #8385 + redraw_statuslines(); + } else { + wild_menu_showing = 0; + } + KeyTyped = skt; + if (cclp->input_fn) { + RedrawingDisabled = old_RedrawingDisabled; + } +} + +/// "getcompletion()" function +void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char_u *pat; + expand_T xpc; + bool filtered = false; + int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH + | WILD_NO_BEEP | WILD_HOME_REPLACE; + + if (argvars[1].v_type != VAR_STRING) { + semsg(_(e_invarg2), "type must be a string"); + return; + } + const char *const type = tv_get_string(&argvars[1]); + + if (argvars[2].v_type != VAR_UNKNOWN) { + filtered = (bool)tv_get_number_chk(&argvars[2], NULL); + } + + if (p_wic) { + options |= WILD_ICASE; + } + + // For filtered results, 'wildignore' is used + if (!filtered) { + options |= WILD_KEEP_ALL; + } + + if (argvars[0].v_type != VAR_STRING) { + emsg(_(e_invarg)); + return; + } + const char *pattern = tv_get_string(&argvars[0]); + + if (strcmp(type, "cmdline") == 0) { + set_one_cmd_context(&xpc, pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_col = (int)STRLEN(pattern); + goto theend; + } + + ExpandInit(&xpc); + xpc.xp_pattern = (char *)pattern; + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_context = cmdcomplete_str_to_type(type); + if (xpc.xp_context == EXPAND_NOTHING) { + semsg(_(e_invarg2), type); + return; + } + + if (xpc.xp_context == EXPAND_MENUS) { + set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + + if (xpc.xp_context == EXPAND_CSCOPE) { + set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + + if (xpc.xp_context == EXPAND_SIGN) { + set_context_in_sign_cmd(&xpc, (char_u *)xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + +theend: + pat = addstar((char_u *)xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); + + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], + -1); + } + xfree(pat); + ExpandCleanup(&xpc); +} diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h new file mode 100644 index 0000000000..93e91af169 --- /dev/null +++ b/src/nvim/cmdexpand.h @@ -0,0 +1,44 @@ +#ifndef NVIM_CMDEXPAND_H +#define NVIM_CMDEXPAND_H + +#include "nvim/eval/typval.h" +#include "nvim/ex_getln.h" +#include "nvim/garray.h" +#include "nvim/types.h" + +// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. + +enum { + WILD_FREE = 1, + WILD_EXPAND_FREE = 2, + WILD_EXPAND_KEEP = 3, + WILD_NEXT = 4, + WILD_PREV = 5, + WILD_ALL = 6, + WILD_LONGEST = 7, + WILD_ALL_KEEP = 8, + WILD_CANCEL = 9, + WILD_APPLY = 10, +}; + +enum { + WILD_LIST_NOTFOUND = 0x01, + WILD_HOME_REPLACE = 0x02, + WILD_USE_NL = 0x04, + WILD_NO_BEEP = 0x08, + WILD_ADD_SLASH = 0x10, + WILD_KEEP_ALL = 0x20, + WILD_SILENT = 0x40, + WILD_ESCAPE = 0x80, + WILD_ICASE = 0x100, + WILD_ALLLINKS = 0x200, + WILD_IGNORE_COMPLETESLASH = 0x400, + WILD_NOERROR = 0x800, ///< sets EW_NOERROR + WILD_BUFLASTUSED = 0x1000, + BUF_DIFF_FILTER = 0x2000, +}; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "cmdexpand.h.generated.h" +#endif +#endif // NVIM_CMDEXPAND_H diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c new file mode 100644 index 0000000000..1426054d0b --- /dev/null +++ b/src/nvim/cmdhist.c @@ -0,0 +1,744 @@ +// 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_cmds.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) +{ + const char *short_names = ":=@>?/"; + const int short_names_count = (int)STRLEN(short_names); + const int history_name_count = ARRAY_SIZE(history_names) - 1; + + if (idx < short_names_count) { + xp->xp_buf[0] = short_names[idx]; + xp->xp_buf[1] = NUL; + return xp->xp_buf; + } + 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, EvalFuncData 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, EvalFuncData 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, EvalFuncData 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, EvalFuncData 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..34692cdf64 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); @@ -344,7 +345,7 @@ Dictionary ctx_to_dict(Context *ctx) PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps))); PUT(rv, "bufs", ARRAY_OBJ(sbuf_to_array(ctx->bufs))); PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars))); - PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs))); + PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs, NULL))); return rv; } @@ -380,7 +381,7 @@ int ctx_from_dict(Dictionary dict, Context *ctx) ctx->gvars = array_to_sbuf(item.value.data.array); } else if (strequal(item.key.data, "funcs")) { types |= kCtxFuncs; - ctx->funcs = copy_object(item.value).data.array; + ctx->funcs = copy_object(item.value, NULL).data.array; } } diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 1446257f7e..ed0488cf76 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" @@ -137,14 +137,18 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } } - char_u *ptr = line; - while (col <= wcol && *ptr != NUL) { + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); + while (cts.cts_vcol <= wcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - csize = win_lbr_chartabsize(curwin, line, ptr, col, &head); - MB_PTR_ADV(ptr); - col += csize; + csize = win_lbr_chartabsize(&cts, &head); + MB_PTR_ADV(cts.cts_ptr); + cts.cts_vcol += csize; } - idx = (int)(ptr - line); + col = cts.cts_vcol; + idx = (int)(cts.cts_ptr - (char *)line); + clear_chartabsize_arg(&cts); + // Handle all the special cases. The virtual_active() check // is needed to ensure that a virtual position off the end of // a line has the correct indexing. The one_more comparison @@ -471,7 +475,7 @@ bool leftcol_changed(void) if (retval) { curwin->w_set_curswant = true; } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); return retval; } diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 9c33b1a806..73ad99876a 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -120,7 +120,7 @@ char *parse_shape_opt(int what) } } // Repeat for all comma separated parts. - char *modep = (char *)p_guicursor; + char *modep = p_guicursor; while (modep != NULL && *modep != NUL) { colonp = vim_strchr(modep, ':'); commap = vim_strchr(modep, ','); diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 0eaff06833..36df62b502 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" @@ -46,7 +47,7 @@ struct debuggy { /// Debug mode. Repeatedly get Ex commands, until told to continue normal /// execution. -void do_debug(char_u *cmd) +void do_debug(char *cmd) { int save_msg_scroll = msg_scroll; int save_State = State; @@ -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; @@ -235,11 +239,11 @@ void do_debug(char_u *cmd) last_cmd = CMD_STEP; break; case CMD_BACKTRACE: - do_showbacktrace(cmd); + do_showbacktrace((char_u *)cmd); continue; case CMD_FRAME: if (*p == NUL) { - do_showbacktrace(cmd); + do_showbacktrace((char_u *)cmd); } else { p = skipwhite(p); do_setdebugtracelevel((char_u *)p); @@ -271,7 +275,7 @@ void do_debug(char_u *cmd) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; lines_left = Rows - 1; @@ -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); } @@ -404,7 +414,7 @@ void dbg_check_breakpoint(exarg_T *eap) debug_breakpoint_name + (*p == NUL ? 0 : 3), (int64_t)debug_breakpoint_lnum); debug_breakpoint_name = NULL; - do_debug((char_u *)eap->cmd); + do_debug(eap->cmd); } else { debug_skipped = true; debug_skipped_name = debug_breakpoint_name; @@ -412,7 +422,7 @@ void dbg_check_breakpoint(exarg_T *eap) } } else if (ex_nesting_level <= debug_break_level) { if (!eap->skip) { - do_debug((char_u *)eap->cmd); + do_debug(eap->cmd); } else { debug_skipped = true; debug_skipped_name = NULL; @@ -693,7 +703,7 @@ void ex_breaklist(exarg_T *eap) smsg(_("%3d %s %s line %" PRId64), bp->dbg_nr, bp->dbg_type == DBG_FUNC ? "func" : "file", - bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + bp->dbg_type == DBG_FUNC ? bp->dbg_name : (char_u *)NameBuff, (int64_t)bp->dbg_lnum); } else { smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); @@ -708,9 +718,9 @@ void ex_breaklist(exarg_T *eap) /// @param file true for a file, false for a function /// @param fname file or function name /// @param after after this line number -linenr_T dbg_find_breakpoint(bool file, char_u *fname, linenr_T after) +linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after) { - return debuggy_find(file, fname, after, &dbg_breakp, NULL); + return debuggy_find(file, (char_u *)fname, after, &dbg_breakp, NULL); } /// @param file true for a file, false for a function @@ -718,9 +728,9 @@ linenr_T dbg_find_breakpoint(bool file, char_u *fname, linenr_T after) /// @param fp[out] forceit /// /// @returns true if profiling is on for a function or sourced file. -bool has_profiling(bool file, char_u *fname, bool *fp) +bool has_profiling(bool file, char *fname, bool *fp) { - return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) + return debuggy_find(file, (char_u *)fname, (linenr_T)0, &prof_ga, fp) != (linenr_T)0; } @@ -815,9 +825,9 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T } /// Called when a breakpoint was encountered. -void dbg_breakpoint(char_u *name, linenr_T lnum) +void dbg_breakpoint(char *name, linenr_T lnum) { // We need to check if this line is actually executed in do_one_cmd() - debug_breakpoint_name = name; + debug_breakpoint_name = (char_u *)name; debug_breakpoint_lnum = lnum; } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e7c76fe38e..9f3e5a8638 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: @@ -539,7 +547,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) } int virt_lines = 0; - int row = (int)MAX(lnum - 2, 0); + int row = MAX(lnum - 2, 0); int end_row = (int)lnum; MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); @@ -550,7 +558,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { goto next_mark; } - bool above = mark.pos.row > (int)(lnum - 2); + bool above = mark.pos.row > (lnum - 2); Decoration *decor = mark.decor_full; if (decor && decor->virt_lines_above == above) { virt_lines += (int)kv_size(decor->virt_lines); diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 04d875c4e3..14c1238fa4 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(); } } } @@ -176,6 +174,7 @@ void decor_providers_invoke_end(DecorProviders *providers, char **err) DecorProvider *get_decor_provider(NS ns_id, bool force) { + assert(ns_id > 0); size_t i; size_t len = kv_size(decor_providers); for (i = 0; i < len; i++) { 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..1cfb535fe8 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" @@ -32,10 +34,10 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #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" @@ -118,7 +120,7 @@ void diff_buf_delete(buf_T *buf) // don't redraw right away, more might change or buffer state // is invalid right now need_diff_redraw = true; - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } } } @@ -571,9 +573,9 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) if (dir == BACKWARD) { off_org = dp->df_count[i_org] - 1; } - char_u *line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], - dp->df_lnum[i_org] + off_org, - false)); + char *line_org = (char *)vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], + dp->df_lnum[i_org] + off_org, + false)); int i_new; for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) { @@ -590,9 +592,9 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) break; } - if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new], - dp->df_lnum[i_new] + off_new, - false)) != 0) { + if (diff_cmp((char_u *)line_org, ml_get_buf(tp->tp_diffbuf[i_new], + dp->df_lnum[i_new] + off_new, + false)) != 0) { break; } } @@ -657,7 +659,7 @@ void diff_redraw(bool dofold) continue; } - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); if (wp != curwin) { wp_other = wp; } @@ -795,8 +797,8 @@ static int diff_write(buf_T *buf, diffin_T *din) } // Always use 'fileformat' set to "unix". - char_u *save_ff = buf->b_p_ff; - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + char *save_ff = buf->b_p_ff; + buf->b_p_ff = xstrdup(FF_UNIX); const bool save_cmod_flags = cmdmod.cmod_flags; // Writing the buffer is an implementation detail of performing the diff, // so it shouldn't update the '[ and '] marks. @@ -956,13 +958,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 +1077,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; @@ -1146,7 +1148,7 @@ static int diff_file(diffio_T *dio) (diff_flags & DIFF_IBLANK) ? "-B " : "", (diff_flags & DIFF_ICASE) ? "-i " : "", tmp_orig, tmp_new); - append_redir(cmd, len, (char *)p_srr, tmp_diff); + append_redir(cmd, len, p_srr, tmp_diff); block_autocmds(); // Avoid ShellCmdPost stuff (void)call_shell((char_u *)cmd, kShellOptFilter | kShellOptSilent | kShellOptDoOut, @@ -1372,7 +1374,7 @@ static void set_diff_option(win_T *wp, int value) curwin = wp; curbuf = curwin->w_buffer; curbuf->b_ro_locked++; - set_option_value("diff", (long)value, NULL, OPT_LOCAL); + set_option_value_give_err("diff", (long)value, NULL, OPT_LOCAL); curbuf->b_ro_locked--; curwin = old_curwin; curbuf = curwin->w_buffer; @@ -1406,18 +1408,14 @@ void diff_win_options(win_T *wp, int addbuf) } wp->w_p_wrap = false; } - curwin = wp; // -V519 - curbuf = curwin->w_buffer; if (!wp->w_p_diff) { if (wp->w_p_diff_saved) { free_string_option(wp->w_p_fdm_save); } - wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); + wp->w_p_fdm_save = xstrdup(wp->w_p_fdm); } - set_string_option_direct("fdm", -1, "diff", OPT_LOCAL | OPT_FREE, 0); - curwin = old_curwin; - curbuf = curwin->w_buffer; + set_string_option_direct_in_win(wp, "fdm", -1, "diff", OPT_LOCAL | OPT_FREE, 0); if (!wp->w_p_diff) { wp->w_p_fen_save = wp->w_p_fen; @@ -1426,12 +1424,12 @@ void diff_win_options(win_T *wp, int addbuf) if (wp->w_p_diff_saved) { free_string_option(wp->w_p_fdc_save); } - wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc); + wp->w_p_fdc_save = xstrdup(wp->w_p_fdc); } free_string_option(wp->w_p_fdc); - wp->w_p_fdc = (char_u *)xstrdup("2"); + wp->w_p_fdc = xstrdup("2"); assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9); - snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); + snprintf(wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); wp->w_p_fen = true; wp->w_p_fdl = 0; foldUpdateAll(wp); @@ -1450,7 +1448,7 @@ void diff_win_options(win_T *wp, int addbuf) if (addbuf) { diff_buf_add(wp->w_buffer); } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } /// Set options not to show diffs. For the current window or all windows. @@ -1482,11 +1480,9 @@ void ex_diffoff(exarg_T *eap) } } free_string_option(wp->w_p_fdm); - wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save - ? wp->w_p_fdm_save - : (char_u *)"manual"); + wp->w_p_fdm = xstrdup(*wp->w_p_fdm_save ? wp->w_p_fdm_save : "manual"); free_string_option(wp->w_p_fdc); - wp->w_p_fdc = vim_strsave(wp->w_p_fdc_save); + wp->w_p_fdc = xstrdup(wp->w_p_fdc_save); if (wp->w_p_fdl == 0) { wp->w_p_fdl = wp->w_p_fdl_save; @@ -1543,8 +1539,8 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) diff_T *dp = curtab->tp_first_diff; diff_T *dn, *dpl; diffout_T *dout = &dio->dio_diff; - char_u linebuf[LBUFLEN]; // only need to hold the diff line - char_u *line; + char linebuf[LBUFLEN]; // only need to hold the diff line + char *line; linenr_T off; int i; int notset = true; // block "*dp" not set yet @@ -1580,9 +1576,9 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) if (line_idx >= dout->dout_ga.ga_len) { break; // did last line } - line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; + line = ((char **)dout->dout_ga.ga_data)[line_idx++]; } else { - if (vim_fgets(linebuf, LBUFLEN, fd)) { + if (vim_fgets((char_u *)linebuf, LBUFLEN, fd)) { break; // end of file } line = linebuf; @@ -1604,9 +1600,9 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) } else if ((STRNCMP(line, "@@ ", 3) == 0)) { diffstyle = DIFF_UNIFIED; } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 && (STRNCMP(line, "@@ ", 3) == 0)) { diffstyle = DIFF_UNIFIED; } else { @@ -1620,7 +1616,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) if (!isdigit(*line)) { continue; // not the start of a diff block } - if (parse_diff_ed(line, hunk) == FAIL) { + if (parse_diff_ed((char_u *)line, hunk) == FAIL) { continue; } } else { @@ -1628,7 +1624,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) if (STRNCMP(line, "@@ ", 3) != 0) { continue; // not the start of a diff block } - if (parse_diff_unified(line, hunk) == FAIL) { + if (parse_diff_unified((char_u *)line, hunk) == FAIL) { continue; } } @@ -1924,11 +1920,11 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2) } for (int i = 0; i < dp->df_count[idx1]; i++) { - char_u *line = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], - dp->df_lnum[idx1] + i, false)); + char *line = (char *)vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], + dp->df_lnum[idx1] + i, false)); - int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], - dp->df_lnum[idx2] + i, false)); + int cmp = diff_cmp((char_u *)line, ml_get_buf(curtab->tp_diffbuf[idx2], + dp->df_lnum[idx2] + i, false)); xfree(line); if (cmp != 0) { @@ -2143,14 +2139,14 @@ int diffopt_changed(void) long diff_algorithm_new = 0; long diff_indent_heuristic = 0; - char_u *p = p_dip; + char *p = 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 +2170,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; @@ -2285,7 +2281,7 @@ bool diffopt_filler(void) bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - char_u *line_new; + char *line_new; int si_org; int si_new; int ei_org; @@ -2294,7 +2290,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) int l; // Make a copy of the line, the next ml_get() will invalidate it. - char_u *line_org = vim_strsave(ml_get_buf(wp->w_buffer, lnum, false)); + char *line_org = (char *)vim_strsave(ml_get_buf(wp->w_buffer, lnum, false)); int idx = diff_buf_idx(wp->w_buffer); if (idx == DB_COUNT) { @@ -2326,8 +2322,8 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) continue; } added = false; - line_new = ml_get_buf(curtab->tp_diffbuf[i], - dp->df_lnum[i] + off, false); + line_new = (char *)ml_get_buf(curtab->tp_diffbuf[i], + dp->df_lnum[i] + off, false); // Search for start of difference si_org = si_new = 0; @@ -2339,10 +2335,10 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) || ((diff_flags & DIFF_IWHITEALL) && (ascii_iswhite(line_org[si_org]) || ascii_iswhite(line_new[si_new])))) { - si_org = (int)((char_u *)skipwhite((char *)line_org + si_org) - line_org); - si_new = (int)((char_u *)skipwhite((char *)line_new + si_new) - line_new); + si_org = (int)(skipwhite(line_org + si_org) - line_org); + si_new = (int)(skipwhite(line_new + si_new) - line_new); } else { - if (!diff_equal_char(line_org + si_org, line_new + si_new, &l)) { + if (!diff_equal_char((char_u *)line_org + si_org, (char_u *)line_new + si_new, &l)) { break; } si_org += l; @@ -2352,8 +2348,8 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) // Move back to first byte of character in both lines (may // have "nn^" in line_org and "n^ in line_new). - si_org -= utf_head_off(line_org, line_org + si_org); - si_new -= utf_head_off(line_new, line_new + si_new); + si_org -= utf_head_off((char_u *)line_org, (char_u *)line_org + si_org); + si_new -= utf_head_off((char_u *)line_new, (char_u *)line_new + si_new); if (*startp > si_org) { *startp = si_org; @@ -2382,11 +2378,11 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) ei_new--; } } else { - const char_u *p1 = line_org + ei_org; - const char_u *p2 = line_new + ei_new; + const char_u *p1 = (char_u *)line_org + ei_org; + const char_u *p2 = (char_u *)line_new + ei_new; - p1 -= utf_head_off(line_org, p1); - p2 -= utf_head_off(line_new, p2); + p1 -= utf_head_off((char_u *)line_org, p1); + p2 -= utf_head_off((char_u *)line_new, p2); if (!diff_equal_char(p1, p2, &l)) { break; @@ -2515,7 +2511,7 @@ void ex_diffgetput(exarg_T *eap) diff_T *dfree; int i; int added; - char_u *p; + char *p; aco_save_T aco; buf_T *buf; linenr_T start_skip; @@ -2569,18 +2565,18 @@ void ex_diffgetput(exarg_T *eap) } } else { // Buffer number or pattern given. Ignore trailing white space. - p = (char_u *)eap->arg + STRLEN(eap->arg); - while (p > (char_u *)eap->arg && ascii_iswhite(p[-1])) { + p = eap->arg + STRLEN(eap->arg); + while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } - for (i = 0; ascii_isdigit(eap->arg[i]) && (char_u *)eap->arg + i < p; i++) {} + for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) {} - if ((char_u *)eap->arg + i == p) { + if (eap->arg + i == p) { // digits only i = (int)atol(eap->arg); } else { - i = buflist_findpat(eap->arg, (char *)p, false, true, false); + i = buflist_findpat(eap->arg, p, false, true, false); if (i < 0) { // error message already given @@ -2722,8 +2718,8 @@ void ex_diffgetput(exarg_T *eap) if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; } - p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); - ml_append(lnum + i - 1, (char *)p, 0, false); + p = (char *)vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); + ml_append(lnum + i - 1, p, 0, false); xfree(p); added++; if (buf_empty && (curbuf->b_ml.ml_line_count == 2)) { diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 733b3d3d5d..e4528f9038 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" @@ -1695,7 +1695,7 @@ static void digraph_header(const char *msg) if (msg_col > 0) { msg_putchar('\n'); } - msg_outtrans_attr((const char_u *)msg, HL_ATTR(HLF_CM)); + msg_outtrans_attr(msg, HL_ATTR(HLF_CM)); msg_putchar('\n'); } @@ -1856,7 +1856,7 @@ static void printdigraph(const digr_T *dp, result_T *previous) p += utf_char2bytes(dp->result, (char *)p); *p = NUL; - msg_outtrans_attr(buf, HL_ATTR(HLF_8)); + msg_outtrans_attr((char *)buf, HL_ATTR(HLF_8)); p = buf; if (char2cells(dp->result) == 1) { *p++ = ' '; @@ -1917,7 +1917,7 @@ static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigr } /// "digraph_get()" function -void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Return empty string for failure @@ -1938,7 +1938,7 @@ void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_getlist()" function -void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_getlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool flag_list_all; @@ -1957,7 +1957,7 @@ void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_set()" function -void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_set(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_BOOL; rettv->vval.v_bool = kBoolVarFalse; @@ -1970,7 +1970,7 @@ void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_setlist()" function -void f_digraph_setlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_setlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_BOOL; rettv->vval.v_bool = kBoolVarFalse; @@ -2096,10 +2096,10 @@ void ex_loadkeymap(exarg_T *eap) if ((*p != '"') && (*p != NUL)) { kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga); - s = skiptowhite(p); + s = (char_u *)skiptowhite((char *)p); kp->from = vim_strnsave(p, (size_t)(s - p)); p = (char_u *)skipwhite((char *)s); - s = skiptowhite(p); + s = (char_u *)skiptowhite((char *)p); kp->to = vim_strnsave(p, (size_t)(s - p)); if ((STRLEN(kp->from) + STRLEN(kp->to) >= KMAP_LLEN) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c new file mode 100644 index 0000000000..b6c4400c60 --- /dev/null +++ b/src/nvim/drawline.c @@ -0,0 +1,2680 @@ +// 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((char *)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((char *)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; + } + } +} + +static bool check_mb_utf8(int *c, int *u8cc) +{ + if (utf_char2len(*c) > 1) { + *u8cc = 0; + *c = 0xc0; + return true; + } + return false; +} + +/// 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((char *)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; + chartabsize_T cts; + int charsize; + + init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, ptr); + while (cts.cts_vcol < v && *cts.cts_ptr != NUL) { + charsize = win_lbr_chartabsize(&cts, NULL); + cts.cts_vcol += charsize; + prev_ptr = (char_u *)cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); + } + vcol = cts.cts_vcol; + ptr = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); + + // 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 -= charsize; + 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) >= charsize || *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 = (char_u *)skiptowhite((char *)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! + mb_utf8 = check_mb_utf8(&c, u8cc); + } 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((char *)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((char *)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((char *)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); + chartabsize_T cts; + + init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, p); + n_extra = win_lbr_chartabsize(&cts, 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 = ' '; + } + } + clear_chartabsize_arg(&cts); + } + + 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; + mb_utf8 = check_mb_utf8(&c, u8cc); + } + + 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; + mb_utf8 = check_mb_utf8(&c, u8cc); + } + } + + // 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; + mb_utf8 = check_mb_utf8(&c, u8cc); + } 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; + mb_utf8 = check_mb_utf8(&c, u8cc); + } 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(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; + mb_utf8 = check_mb_utf8(&c, u8cc); + } 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; + mb_utf8 = check_mb_utf8(&c, u8cc); + 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; + mb_utf8 = check_mb_utf8(&c, u8cc); + } + + // 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..29ff261461 --- /dev/null +++ b/src/nvim/drawscreen.c @@ -0,0 +1,2176 @@ +// 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, UPD_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, UPD_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(UPD_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, UPD_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 UPD_NOT_VALID then. +// +// Commands that move the window position must call redraw_later(wp, UPD_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(UPD_NOT_VALID) or redraw_all_later(UPD_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/cmdexpand.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/statusline.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 = UPD_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(UPD_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 = UPD_CLEAR; + } + } + if (must_redraw == UPD_CLEAR) { + must_redraw = UPD_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(UPD_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(UPD_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 UPD_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 < UPD_NOT_VALID) { + type = UPD_NOT_VALID; + } + + // Postpone the redrawing when it's not needed and when being called + // recursively. + if (!redrawing() || updating_screen) { + must_redraw = type; + if (type > UPD_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 >= UPD_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; + // UPD_CLEAR is already handled + if (type == UPD_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, UPD_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 (type != UPD_CLEAR) { + if (msg_scrolled > Rows - 5) { // redrawing is faster + type = UPD_NOT_VALID; + } else { + 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 < UPD_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 = UPD_REDRAW_TOP; + } else { + wp->w_redr_type = UPD_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 == UPD_CLEAR) { // first clear screen + screenclear(); // will reset clear_cmdline + cmdline_screen_cleared(); // clear external cmdline state + type = UPD_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 == UPD_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 < UPD_NOT_VALID + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + ? number_width(curwin) : 0)) { + curwin->w_redr_type = UPD_NOT_VALID; + } + + // Only start redrawing if there is really something to do. + if (type == UPD_INVERTED) { + update_curswant(); + } + if (curwin->w_redr_type < type + && !((type == UPD_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 == UPD_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 >= UPD_NOT_VALID) { + update_window_hl(curwin, type >= UPD_NOT_VALID); + FOR_ALL_TABS(tp) { + if (tp != curtab) { + update_window_hl(tp->tp_curwin, type >= UPD_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 >= UPD_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 == UPD_CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); + wp->w_redr_type = UPD_NOT_VALID; + } + + win_check_ns_hl(wp); + + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= UPD_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; + + 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++) { + grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); + } + 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); + } +} + +/// Show current cursor info in ruler and various other places +/// +/// @param always if false, only show ruler if position has changed. +void show_cursor_info(bool always) +{ + if (!always && !redrawing()) { + return; + } + + win_check_ns_hl(curwin); + 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(); + } + + win_check_ns_hl(NULL); + // 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. +/// UPD_NOT_VALID redraw the whole window +/// UPD_SOME_VALID redraw the whole window but do scroll when possible +/// UPD_REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like UPD_VALID +/// UPD_INVERTED redraw the changed part of the Visual area +/// UPD_INVERTED_ALL redraw the whole Visual area +/// UPD_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 >= UPD_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 = UPD_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 = UPD_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 == UPD_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 = UPD_NOT_VALID; + } else { + // top area defined, the rest is UPD_VALID + type = UPD_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 == UPD_VALID || type == UPD_SOME_VALID + || type == UPD_INVERTED || type == UPD_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 UPD_VALID or UPD_INVERTED: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + } + + if (type == UPD_SOME_VALID) { + // UPD_SOME_VALID: redraw all lines. + mid_start = 0; + mid_end = wp->w_grid.rows; + type = UPD_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 != UPD_NOT_VALID)) { + linenr_T from, to; + + if (VIsual_active) { + if (VIsual_mode != wp->w_old_visual_mode || type == UPD_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, "@@", 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 from 'fillchars' 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 >= UPD_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 UPD_CLEAR, type UPD_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 >= UPD_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, UPD_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, UPD_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 < UPD_VALID) { + must_redraw = UPD_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, UPD_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, UPD_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_check_ns_hl(wp); + win_redr_winbar(wp); + win_redr_status(wp); + } + } + + win_check_ns_hl(NULL); + if (redraw_tabline) { + draw_tabline(); + } +} + +/// Redraw all status lines at the bottom of frame "frp". +void win_redraw_last_status(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (frp->fr_layout == FR_LEAF) { + frp->fr_win->w_redr_status = true; + } else if (frp->fr_layout == FR_ROW) { + FOR_ALL_FRAMES(frp, frp->fr_child) { + win_redraw_last_status(frp); + } + } else { + assert(frp->fr_layout == FR_COL); + frp = frp->fr_child; + while (frp->fr_next != NULL) { + frp = frp->fr_next; + } + win_redraw_last_status(frp); + } +} + +/// 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, UPD_VALID); + } +} diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h new file mode 100644 index 0000000000..ef99899581 --- /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 { + UPD_VALID = 10, ///< buffer not changed, or changes marked with b_mod_* + UPD_INVERTED = 20, ///< redisplay inverted part that changed + UPD_INVERTED_ALL = 25, ///< redisplay whole inverted part + UPD_REDRAW_TOP = 30, ///< display first w_upd_rows screen lines + UPD_SOME_VALID = 35, ///< like UPD_NOT_VALID but may scroll + UPD_NOT_VALID = 40, ///< buffer needs complete redraw + UPD_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 5861e4c52b..aee8389900 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,15 +48,16 @@ #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" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -91,6 +94,10 @@ typedef struct insert_state { #define BACKSPACE_WORD_NOT_SPACE 3 #define BACKSPACE_LINE 4 +/// Set when doing something for completion that may call edit() recursively, +/// which is not allowed. +static bool compl_busy = false; + static colnr_T Insstart_textlen; // length of line when insert started static colnr_T Insstart_blank_vcol; // vcol for first inserted blank static bool update_Insstart_orig = true; // set Insstart_orig to Insstart @@ -103,8 +110,6 @@ static int did_restart_edit; // "restart_edit" when calling edit() static bool can_cindent; // may do cindenting on this line -static int old_indent = 0; // for ^^D command in insert mode - static int revins_on; // reverse insert mode on static int revins_chars; // how much to skip after edit static int revins_legal; // was the last char 'legal'? @@ -114,8 +119,6 @@ static bool ins_need_undo; // call u_save() before inserting a // char. Set when edit() is called. // after that arrow_used is used. -static bool did_add_space = false; // auto_format() added an extra space - // under the cursor static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo // for the next left/right cursor key @@ -447,7 +450,7 @@ static int insert_check(VimState *state) && (curwin->w_cursor.lnum != curwin->w_topline || curwin->w_topfill > 0)) { if (curwin->w_topfill > 0) { - --curwin->w_topfill; + curwin->w_topfill--; } else if (hasFolding(curwin->w_topline, NULL, &s->old_topline)) { set_topline(curwin, s->old_topline + 1); } else { @@ -623,16 +626,17 @@ static int insert_execute(VimState *state, int key) } if (cindent_on() && ctrl_x_mode_none()) { + s->line_is_white = inindent(0); // A key name preceded by a bang means this key is not to be // inserted. Skip ahead to the re-indenting below. - // A key name preceded by a star means that indenting has to be - // done before inserting the key. - s->line_is_white = inindent(0); - if (in_cinkeys(s->c, '!', s->line_is_white)) { - insert_do_cindent(s); + if (in_cinkeys(s->c, '!', s->line_is_white) + && stop_arrow() == OK) { + do_c_expr_indent(); return 1; // continue } + // A key name preceded by a star means that indenting has to be + // done before inserting the key. if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) && stop_arrow() == OK) { do_c_expr_indent(); @@ -1089,7 +1093,7 @@ check_pum: // but it is under other ^X modes if (*curbuf->b_p_cpt == NUL && (ctrl_x_mode_normal() || ctrl_x_mode_whole_line()) - && !(compl_cont_status & CONT_LOCAL)) { + && !compl_status_local()) { goto normalchar; } @@ -1175,9 +1179,11 @@ normalchar: static void insert_do_complete(InsertState *s) { compl_busy = true; + disable_fold_update++; // don't redraw folds here if (ins_complete(s->c, true) == FAIL) { - compl_cont_status = 0; + compl_status_clear(); } + disable_fold_update--; compl_busy = false; can_si = may_do_si(); // allow smartindenting } @@ -1348,7 +1354,7 @@ void ins_redraw(bool ready) } else if (clear_cmdline || redraw_cmdline) { showmode(); // clear cmdline and show mode } - showruler(false); + show_cursor_info(false); setcursor(); emsg_on_display = false; // may remove error message now } @@ -1517,8 +1523,7 @@ void edit_unputchar(void) if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) { redrawWinline(curwin, curwin->w_cursor.lnum); } else { - grid_puts(&curwin->w_grid, pc_bytes, pc_row - msg_scrolled, pc_col, - pc_attr); + grid_puts(&curwin->w_grid, (char *)pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr); } } } @@ -1553,7 +1558,7 @@ void display_dollar(colnr_T col) * Call this function before moving the cursor from the normal insert position * in insert mode. */ -static void undisplay_dollar(void) +void undisplay_dollar(void) { if (dollar_vcol >= 0) { dollar_vcol = -1; @@ -1567,7 +1572,7 @@ static void undisplay_dollar(void) /// type == INDENT_DEC decrease indent (for CTRL-D) /// type == INDENT_SET set indent to "amount" /// -/// @param round if TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec). +/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec). /// @param replaced replaced character, put on replace stack /// @param call_changed_bytes call changed_bytes() void change_indent(int type, int amount, int round, int replaced, int call_changed_bytes) @@ -1591,7 +1596,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang // for the following tricks we don't want list mode save_p_list = curwin->w_p_list; - curwin->w_p_list = FALSE; + curwin->w_p_list = false; vc = getvcol_nolist(&curwin->w_cursor); vcol = vc; @@ -1659,28 +1664,28 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } else if (!(State & MODE_INSERT)) { new_cursor_col = curwin->w_cursor.col; } else { - /* - * Compute the screen column where the cursor should be. - */ + // Compute the screen column where the cursor should be. vcol = get_indent() - vcol; curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); - /* - * Advance the cursor until we reach the right screen column. - */ - vcol = last_vcol = 0; - new_cursor_col = -1; + // Advance the cursor until we reach the right screen column. + last_vcol = 0; ptr = get_cursor_line_ptr(); - while (vcol <= (int)curwin->w_virtcol) { - last_vcol = vcol; - if (new_cursor_col >= 0) { - new_cursor_col += utfc_ptr2len((char *)ptr + new_cursor_col); - } else { - new_cursor_col++; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr); + while (cts.cts_vcol <= (int)curwin->w_virtcol) { + last_vcol = cts.cts_vcol; + if (cts.cts_vcol > 0) { + MB_PTR_ADV(cts.cts_ptr); + } + if (*cts.cts_ptr == NUL) { + break; } - vcol += lbr_chartabsize(ptr, ptr + new_cursor_col, (colnr_T)vcol); + cts.cts_vcol += lbr_chartabsize(&cts); } vcol = last_vcol; + new_cursor_col = (int)(cts.cts_ptr - cts.cts_line); + clear_chartabsize_arg(&cts); /* * May need to insert spaces to be able to position the cursor on @@ -1692,7 +1697,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang ptr = xmallocz(i); memset(ptr, ' ', i); new_cursor_col += (int)i; - ins_str(ptr); + ins_str((char *)ptr); xfree(ptr); } @@ -1710,7 +1715,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } else { curwin->w_cursor.col = (colnr_T)new_cursor_col; } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; changed_cline_bef_curs(); /* @@ -1747,7 +1752,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang replace_push(replaced); replaced = NUL; } - ++start_col; + start_col++; } } @@ -1772,7 +1777,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang backspace_until_column(0); // Insert new stuff into line again - ins_bytes(new_line); + ins_bytes((char *)new_line); xfree(new_line); @@ -1791,7 +1796,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang /// Truncate the space at the end of a line. This is to be used only in an /// insert mode. It handles fixing the replace stack for MODE_REPLACE and /// MODE_VREPLACE modes. -void truncate_spaces(char_u *line) +void truncate_spaces(char *line) { int i; @@ -1913,7 +1918,7 @@ int get_literal(bool no_simplify) cc = cc * 10 + nc - '0'; } - ++i; + i++; } if (cc > 255 @@ -1948,7 +1953,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. @@ -1970,7 +1975,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) // inserted with ins_str(), so as not to replace characters in replace // mode. // Only use mod_mask for special keys, to avoid things like <S-Space>, - // unless 'allow_modmask' is TRUE. + // unless 'allow_modmask' is true. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } @@ -1983,7 +1988,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) return; } p[len - 1] = NUL; - ins_str(p); + ins_str((char *)p); AppendToRedobuffLit((char *)p, -1); ctrlv = false; } @@ -2004,9 +2009,6 @@ static void insert_special(int c, int allow_modmask, int ctrlv) */ #define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL || (c) == '0' || (c) == '^') -#define WHITECHAR(cc) (ascii_iswhite(cc) \ - && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1))) - /// /// "flags": INSCHAR_FORMAT - force formatting /// INSCHAR_CTRLV - char typed just after CTRL-V @@ -2022,7 +2024,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 +2083,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 +2099,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; @@ -2114,7 +2116,7 @@ void insertchar(int c, int flags, int second_indent) // Insert the end-comment string, except for the last // character, which will get inserted as normal later. - ins_bytes_len(lead_end, (size_t)(end_len - 1)); + ins_bytes_len((char *)lead_end, (size_t)(end_len - 1)); } } } @@ -2174,7 +2176,7 @@ void insertchar(int c, int flags, int second_indent) do_digraph(-1); // clear digraphs do_digraph(buf[i - 1]); // may be the start of a digraph buf[i] = NUL; - ins_str(buf); + ins_str((char *)buf); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); i = 1; @@ -2192,7 +2194,7 @@ void insertchar(int c, int flags, int second_indent) utf_char2bytes(c, (char *)buf); buf[cc] = NUL; - ins_char_bytes((char_u *)buf, (size_t)cc); + ins_char_bytes(buf, (size_t)cc); AppendCharToRedobuff(c); } else { ins_char(c); @@ -2205,597 +2207,6 @@ void insertchar(int c, int flags, int second_indent) } } -/// Format text at the current insert position. -/// -/// If the INSCHAR_COM_LIST flag is present, then the value of second_indent -/// will be the comment leader length sent to open_line(). -/// -/// @param c character to be inserted (can be NUL) -static void internal_format(int textwidth, int second_indent, int flags, int format_only, int c) -{ - int cc; - int save_char = NUL; - bool haveto_redraw = false; - const bool fo_ins_blank = has_format_option(FO_INS_BLANK); - const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); - const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); - const bool fo_white_par = has_format_option(FO_WHITE_PAR); - bool first_line = true; - colnr_T leader_len; - bool no_leader = false; - int do_comments = (flags & INSCHAR_DO_COM); - int has_lbr = curwin->w_p_lbr; - - // make sure win_lbr_chartabsize() counts correctly - curwin->w_p_lbr = false; - - /* - * When 'ai' is off we don't want a space under the cursor to be - * deleted. Replace it with an 'x' temporarily. - */ - if (!curbuf->b_p_ai - && !(State & VREPLACE_FLAG)) { - cc = gchar_cursor(); - if (ascii_iswhite(cc)) { - save_char = cc; - pchar_cursor('x'); - } - } - - /* - * Repeat breaking lines, until the current line is not too long. - */ - while (!got_int) { - int startcol; // Cursor column at entry - int wantcol; // column at textwidth border - int foundcol; // column for start of spaces - int end_foundcol = 0; // column for start of word - colnr_T len; - colnr_T virtcol; - int orig_col = 0; - char_u *saved_text = NULL; - colnr_T col; - colnr_T end_col; - bool did_do_comment = false; - - virtcol = get_nolist_virtcol() - + char2cells(c != NUL ? c : gchar_cursor()); - if (virtcol <= (colnr_T)textwidth) { - break; - } - - if (no_leader) { - do_comments = false; - } else if (!(flags & INSCHAR_FORMAT) - && has_format_option(FO_WRAP_COMS)) { - do_comments = true; - } - - // Don't break until after the comment leader - if (do_comments) { - char_u *line = get_cursor_line_ptr(); - leader_len = get_leader_len((char *)line, NULL, false, true); - if (leader_len == 0 && curbuf->b_p_cin) { - // Check for a line comment after code. - int comment_start = check_linecomment(line); - if (comment_start != MAXCOL) { - leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); - if (leader_len != 0) { - leader_len += comment_start; - } - } - } - } else { - leader_len = 0; - } - - // If the line doesn't start with a comment leader, then don't - // start one in a following broken line. Avoids that a %word - // moved to the start of the next line causes all following lines - // to start with %. - if (leader_len == 0) { - no_leader = true; - } - if (!(flags & INSCHAR_FORMAT) - && leader_len == 0 - && !has_format_option(FO_WRAP)) { - break; - } - if ((startcol = curwin->w_cursor.col) == 0) { - break; - } - - // find column of textwidth border - coladvance((colnr_T)textwidth); - wantcol = curwin->w_cursor.col; - - curwin->w_cursor.col = startcol; - foundcol = 0; - int skip_pos = 0; - - /* - * Find position to break at. - * Stop at first entered white when 'formatoptions' has 'v' - */ - while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) - || (flags & INSCHAR_FORMAT) - || curwin->w_cursor.lnum != Insstart.lnum - || curwin->w_cursor.col >= Insstart.col) { - if (curwin->w_cursor.col == startcol && c != NUL) { - cc = c; - } else { - cc = gchar_cursor(); - } - if (WHITECHAR(cc)) { - // remember position of blank just before text - end_col = curwin->w_cursor.col; - - // find start of sequence of blanks - int wcc = 0; // counter for whitespace chars - while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { - dec_cursor(); - cc = gchar_cursor(); - - // Increment count of how many whitespace chars in this - // group; we only need to know if it's more than one. - if (wcc < 2) { - wcc++; - } - } - if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { - break; // only spaces in front of text - } - - // Don't break after a period when 'formatoptions' has 'p' and - // there are less than two spaces. - if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { - continue; - } - - // Don't break until after the comment leader - if (curwin->w_cursor.col < leader_len) { - break; - } - - if (has_format_option(FO_ONE_LETTER)) { - // do not break after one-letter words - if (curwin->w_cursor.col == 0) { - break; // one-letter word at begin - } - // do not break "#a b" when 'tw' is 2 - if (curwin->w_cursor.col <= leader_len) { - break; - } - col = curwin->w_cursor.col; - dec_cursor(); - cc = gchar_cursor(); - - if (WHITECHAR(cc)) { - continue; // one-letter, continue - } - curwin->w_cursor.col = col; - } - - inc_cursor(); - - end_foundcol = end_col + 1; - foundcol = curwin->w_cursor.col; - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - break; - } - } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { - int ncc; - bool allow_break; - - // Break after or before a multi-byte character. - if (curwin->w_cursor.col != startcol) { - // Don't break until after the comment leader - if (curwin->w_cursor.col < leader_len) { - break; - } - col = curwin->w_cursor.col; - inc_cursor(); - ncc = gchar_cursor(); - allow_break = utf_allow_break(cc, ncc); - - // If we have already checked this position, skip! - if (curwin->w_cursor.col != skip_pos && allow_break) { - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - break; - } - } - curwin->w_cursor.col = col; - } - - if (curwin->w_cursor.col == 0) { - break; - } - - ncc = cc; - col = curwin->w_cursor.col; - - dec_cursor(); - cc = gchar_cursor(); - - if (WHITECHAR(cc)) { - continue; // break with space - } - // Don't break until after the comment leader. - if (curwin->w_cursor.col < leader_len) { - break; - } - - curwin->w_cursor.col = col; - skip_pos = curwin->w_cursor.col; - - allow_break = utf_allow_break(cc, ncc); - - // Must handle this to respect line break prohibition. - if (allow_break) { - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - } - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - const bool ncc_allow_break = utf_allow_break_before(ncc); - - if (allow_break) { - break; - } - if (!ncc_allow_break && !fo_rigor_tw) { - // Enable at most 1 punct hang outside of textwidth. - if (curwin->w_cursor.col == startcol) { - // We are inserting a non-breakable char, postpone - // line break check to next insert. - end_foundcol = foundcol = 0; - break; - } - - // Neither cc nor ncc is NUL if we are here, so - // it's safe to inc_cursor. - col = curwin->w_cursor.col; - - inc_cursor(); - cc = ncc; - ncc = gchar_cursor(); - // handle insert - ncc = (ncc != NUL) ? ncc : c; - - allow_break = utf_allow_break(cc, ncc); - - if (allow_break) { - // Break only when we are not at end of line. - end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; - break; - } - curwin->w_cursor.col = col; - } - } - } - if (curwin->w_cursor.col == 0) { - break; - } - dec_cursor(); - } - - if (foundcol == 0) { // no spaces, cannot break line - curwin->w_cursor.col = startcol; - break; - } - - // Going to break the line, remove any "$" now. - undisplay_dollar(); - - // Offset between cursor position and line break is used by replace - // stack functions. MODE_VREPLACE does not use this, and backspaces - // over the text instead. - if (State & VREPLACE_FLAG) { - orig_col = startcol; // Will start backspacing from here - } else { - replace_offset = startcol - end_foundcol; - } - - /* - * adjust startcol for spaces that will be deleted and - * characters that will remain on top line - */ - curwin->w_cursor.col = foundcol; - while ((cc = gchar_cursor(), WHITECHAR(cc)) - && (!fo_white_par || curwin->w_cursor.col < startcol)) { - inc_cursor(); - } - startcol -= curwin->w_cursor.col; - if (startcol < 0) { - startcol = 0; - } - - if (State & VREPLACE_FLAG) { - // In MODE_VREPLACE state, we will backspace over the text to be - // wrapped, so save a copy now to put on the next line. - saved_text = vim_strsave(get_cursor_pos_ptr()); - curwin->w_cursor.col = orig_col; - saved_text[startcol] = NUL; - - // Backspace over characters that will move to the next line - if (!fo_white_par) { - backspace_until_column(foundcol); - } - } else { - // put cursor after pos. to break line - if (!fo_white_par) { - curwin->w_cursor.col = foundcol; - } - } - - /* - * Split the line just before the margin. - * Only insert/delete lines, but don't really redraw the window. - */ - open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX - + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) - + (do_comments ? OPENLINE_DO_COM : 0) - + OPENLINE_FORMAT - + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), - ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), - &did_do_comment); - if (!(flags & INSCHAR_COM_LIST)) { - old_indent = 0; - } - - // If a comment leader was inserted, may also do this on a following - // line. - if (did_do_comment) { - no_leader = false; - } - - replace_offset = 0; - if (first_line) { - if (!(flags & INSCHAR_COM_LIST)) { - // This section is for auto-wrap of numeric lists. When not - // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST - // flag will be set and open_line() will handle it (as seen - // above). The code here (and in get_number_indent()) will - // recognize comments if needed... - if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { - second_indent = get_number_indent(curwin->w_cursor.lnum - 1); - } - if (second_indent >= 0) { - if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, second_indent, false, NUL, true); - } else if (leader_len > 0 && second_indent - leader_len > 0) { - int padding = second_indent - leader_len; - - // We started at the first_line of a numbered list - // that has a comment. the open_line() function has - // inserted the proper comment leader and positioned - // the cursor at the end of the split line. Now we - // add the additional whitespace needed after the - // comment leader for the numbered list. - for (int i = 0; i < padding; i++) { - ins_str((char_u *)" "); - } - changed_bytes(curwin->w_cursor.lnum, leader_len); - } else { - (void)set_indent(second_indent, SIN_CHANGED); - } - } - } - first_line = false; - } - - if (State & VREPLACE_FLAG) { - // In MODE_VREPLACE state we have backspaced over the text to be - // moved, now we re-insert it into the new line. - ins_bytes(saved_text); - xfree(saved_text); - } else { - /* - * Check if cursor is not past the NUL off the line, cindent - * may have added or removed indent. - */ - curwin->w_cursor.col += startcol; - len = (colnr_T)STRLEN(get_cursor_line_ptr()); - if (curwin->w_cursor.col > len) { - curwin->w_cursor.col = len; - } - } - - haveto_redraw = true; - can_cindent = true; - // moved the cursor, don't autoindent or cindent now - did_ai = false; - did_si = false; - can_si = false; - can_si_back = false; - line_breakcheck(); - } - - if (save_char != NUL) { // put back space after cursor - pchar_cursor((char_u)save_char); - } - - curwin->w_p_lbr = has_lbr; - - if (!format_only && haveto_redraw) { - update_topline(curwin); - redraw_curbuf_later(VALID); - } -} - -/// Called after inserting or deleting text: When 'formatoptions' includes the -/// 'a' flag format from the current line until the end of the paragraph. -/// Keep the cursor at the same position relative to the text. -/// The caller must have saved the cursor line for undo, following ones will be -/// saved here. -/// -/// @param trailblank when true also format with trailing blank -/// @param prev_line may start in previous line -void auto_format(bool trailblank, bool prev_line) -{ - pos_T pos; - colnr_T len; - char_u *old; - char_u *new, *pnew; - int wasatend; - int cc; - - if (!has_format_option(FO_AUTO)) { - return; - } - - pos = curwin->w_cursor; - old = get_cursor_line_ptr(); - - // may remove added space - check_auto_format(false); - - // Don't format in Insert mode when the cursor is on a trailing blank, the - // user might insert normal text next. Also skip formatting when "1" is - // in 'formatoptions' and there is a single character before the cursor. - // Otherwise the line would be broken and when typing another non-white - // next they are not joined back together. - wasatend = (pos.col == (colnr_T)STRLEN(old)); - if (*old != NUL && !trailblank && wasatend) { - dec_cursor(); - cc = gchar_cursor(); - if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 - && has_format_option(FO_ONE_LETTER)) { - dec_cursor(); - } - cc = gchar_cursor(); - if (WHITECHAR(cc)) { - curwin->w_cursor = pos; - return; - } - curwin->w_cursor = pos; - } - - // With the 'c' flag in 'formatoptions' and 't' missing: only format - // comments. - if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) - && get_leader_len((char *)old, NULL, false, true) == 0) { - return; - } - - /* - * May start formatting in a previous line, so that after "x" a word is - * moved to the previous line if it fits there now. Only when this is not - * the start of a paragraph. - */ - if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { - --curwin->w_cursor.lnum; - if (u_save_cursor() == FAIL) { - return; - } - } - - /* - * Do the formatting and restore the cursor position. "saved_cursor" will - * be adjusted for the text formatting. - */ - saved_cursor = pos; - format_lines((linenr_T) - 1, false); - curwin->w_cursor = saved_cursor; - saved_cursor.lnum = 0; - - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - // "cannot happen" - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); - } else { - check_cursor_col(); - } - - // Insert mode: If the cursor is now after the end of the line while it - // previously wasn't, the line was broken. Because of the rule above we - // need to add a space when 'w' is in 'formatoptions' to keep a paragraph - // formatted. - if (!wasatend && has_format_option(FO_WHITE_PAR)) { - new = get_cursor_line_ptr(); - len = (colnr_T)STRLEN(new); - if (curwin->w_cursor.col == len) { - pnew = vim_strnsave(new, (size_t)len + 2); - pnew[len] = ' '; - pnew[len + 1] = NUL; - ml_replace(curwin->w_cursor.lnum, (char *)pnew, false); - // remove the space later - did_add_space = true; - } else { - // may remove added space - check_auto_format(false); - } - } - - check_cursor(); -} - -/// When an extra space was added to continue a paragraph for auto-formatting, -/// delete it now. The space must be under the cursor, just after the insert -/// position. -/// -/// @param end_insert true when ending Insert mode -static void check_auto_format(bool end_insert) -{ - int c = ' '; - int cc; - - if (did_add_space) { - cc = gchar_cursor(); - if (!WHITECHAR(cc)) { - // Somehow the space was removed already. - did_add_space = false; - } else { - if (!end_insert) { - inc_cursor(); - c = gchar_cursor(); - dec_cursor(); - } - if (c != NUL) { - // The space is no longer at the end of the line, delete it. - del_char(false); - did_add_space = false; - } - } - } -} - -/// Find out textwidth to be used for formatting: -/// if 'textwidth' option is set, use it -/// else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' -/// if invalid value, use 0. -/// Set default to window width (maximum 79) for "gq" operator. -/// -/// @param ff force formatting (for "gq" command) -int comp_textwidth(bool ff) -{ - int textwidth = (int)curbuf->b_p_tw; - if (textwidth == 0 && curbuf->b_p_wm) { - // The width is the window width minus 'wrapmargin' minus all the - // things that add to the margin. - textwidth = curwin->w_width_inner - (int)curbuf->b_p_wm; - if (cmdwin_type != 0) { - textwidth -= 1; - } - textwidth -= win_fdccol_count(curwin); - textwidth -= win_signcol_count(curwin); - - if (curwin->w_p_nu || curwin->w_p_rnu) { - textwidth -= 8; - } - } - if (textwidth < 0) { - textwidth = 0; - } - if (ff && textwidth == 0) { - textwidth = curwin->w_width_inner - 1; - if (textwidth > 79) { - textwidth = 79; - } - } - return textwidth; -} - /* * Put a character in the redo buffer, for when just after a CTRL-V. */ @@ -2985,7 +2396,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) check_cursor_col(); // make sure it is not past the line for (;;) { if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } cc = gchar_cursor(); if (!ascii_iswhite(cc)) { @@ -3002,7 +2413,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) tpos = curwin->w_cursor; tpos.col++; if (cc != NUL && gchar_pos(&tpos) == NUL) { - ++curwin->w_cursor.col; // put cursor back on the NUL + curwin->w_cursor.col++; // put cursor back on the NUL } } @@ -3074,11 +2485,11 @@ void beginline(int flags) char_u *ptr; for (ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr) - && !((flags & BL_FIX) && ptr[1] == NUL); ++ptr) { - ++curwin->w_cursor.col; + && !((flags & BL_FIX) && ptr[1] == NUL); ptr++) { + curwin->w_cursor.col++; } } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } } @@ -3122,7 +2533,7 @@ int oneright(void) } curwin->w_cursor.col += l; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; return OK; } @@ -3157,7 +2568,7 @@ int oneleft(void) } } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; return OK; } @@ -3165,8 +2576,8 @@ int oneleft(void) return FAIL; } - curwin->w_set_curswant = TRUE; - --curwin->w_cursor.col; + curwin->w_set_curswant = true; + curwin->w_cursor.col--; // if the character on the left of the current cursor is a multi-byte // character, move to its first byte @@ -3174,7 +2585,7 @@ int oneleft(void) return OK; } -/// @oaram upd_topline When TRUE: update topline +/// @oaram upd_topline When true: update topline int cursor_up(long n, int upd_topline) { linenr_T lnum; @@ -3229,7 +2640,7 @@ int cursor_up(long n, int upd_topline) /// Cursor down a number of logical lines. /// -/// @param upd_topline When TRUE: update topline +/// @param upd_topline When true: update topline int cursor_down(long n, int upd_topline) { linenr_T lnum; @@ -3253,7 +2664,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; @@ -3287,12 +2698,12 @@ int cursor_down(long n, int upd_topline) /// @param no_esc Don't add an ESC at the end int stuff_inserted(int c, long count, int no_esc) { - char_u *esc_ptr; - char_u *ptr; - char_u *last_ptr; - char_u last = NUL; + char *esc_ptr; + char *ptr; + char *last_ptr; + char last = NUL; - ptr = get_last_insert(); + ptr = (char *)get_last_insert(); if (ptr == NULL) { emsg(_(e_noinstext)); return FAIL; @@ -3302,7 +2713,7 @@ int stuff_inserted(int c, long count, int no_esc) if (c != NUL) { stuffcharReadbuff(c); } - if ((esc_ptr = STRRCHR(ptr, ESC)) != NULL) { + if ((esc_ptr = strrchr(ptr, ESC)) != NULL) { // remove the ESC. *esc_ptr = NUL; } @@ -3433,20 +2844,19 @@ void replace_push(int c) memmove(p + 1, p, (size_t)replace_offset); } *p = (char_u)c; - ++replace_stack_nr; + replace_stack_nr++; } -/* - * Push a character onto the replace stack. Handles a multi-byte character in - * reverse byte order, so that the first byte is popped off first. - * Return the number of bytes done (includes composing characters). - */ -int replace_push_mb(char_u *p) +/// Push a character onto the replace stack. Handles a multi-byte character in +/// reverse byte order, so that the first byte is popped off first. +/// +/// @return the number of bytes done (includes composing characters). +int replace_push_mb(char *p) { - int l = utfc_ptr2len((char *)p); + int l = utfc_ptr2len(p); int j; - for (j = l - 1; j >= 0; --j) { + for (j = l - 1; j >= 0; j--) { replace_push(p[j]); } return l; @@ -3468,7 +2878,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; @@ -3507,7 +2917,7 @@ static void mb_replace_pop_ins(int cc) for (i = 1; i < n; i++) { buf[i] = (char_u)replace_pop(); } - ins_bytes_len(buf, (size_t)n); + ins_bytes_len((char *)buf, (size_t)n); } else { ins_char(cc); } @@ -3530,7 +2940,7 @@ static void mb_replace_pop_ins(int cc) buf[i] = (char_u)replace_pop(); } if (utf_iscomposing(utf_ptr2char((char *)buf))) { - ins_bytes_len(buf, (size_t)n); + ins_bytes_len((char *)buf, (size_t)n); } else { // Not a composing char, put it back. for (i = n - 1; i >= 0; i--) { @@ -3579,7 +2989,7 @@ static void replace_do_bs(int limit_col) // Get the number of screen cells used by the character we are // going to delete. getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL); - orig_vcols = win_chartabsize(curwin, get_cursor_pos_ptr(), start_vcol); + orig_vcols = win_chartabsize(curwin, (char *)get_cursor_pos_ptr(), start_vcol); } (void)del_char_after_col(limit_col); if (l_State & VREPLACE_FLAG) { @@ -3594,7 +3004,7 @@ static void replace_do_bs(int limit_col) ins_len = (int)STRLEN(p) - orig_len; vcol = start_vcol; for (i = 0; i < ins_len; i++) { - vcol += win_chartabsize(curwin, p + i, vcol); + vcol += win_chartabsize(curwin, (char *)p + i, vcol); i += utfc_ptr2len((char *)p) - 1; } vcol -= start_vcol; @@ -3656,7 +3066,7 @@ void fix_indent(void) /// Check that "cinkeys" contains the key "keytyped", /// when == '*': Only if key is preceded with '*' (indent before insert) /// when == '!': Only if key is preceded with '!' (don't insert) -/// when == ' ': Only if key is not preceded with '*' (indent afterwards) +/// when == ' ': Only if key is not preceded with '*' or '!' (indent afterwards) /// /// "keytyped" can have a few special values: /// KEY_OPEN_FORW : @@ -3681,9 +3091,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } if (*curbuf->b_p_inde != NUL) { - look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' + look = (char_u *)curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' } else { - look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' + look = (char_u *)curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' } while (*look) { /* @@ -3696,7 +3106,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) case '!': try_match = (*look == '!'); break; default: - try_match = (*look != '*'); break; + try_match = (*look != '*') && (*look != '!'); break; } if (*look == '*' || *look == '!') { look++; @@ -3794,12 +3204,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++; @@ -3873,10 +3280,8 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } } - /* - * Skip over ", ". - */ - look = skip_to_option_part(look); + // Skip over ", ". + look = (char_u *)skip_to_option_part((char *)look); } return false; } @@ -4002,13 +3407,13 @@ static void ins_reg(void) no_mapping++; allow_keys++; regname = plain_vgetc(); - LANGMAP_ADJUST(regname, TRUE); + LANGMAP_ADJUST(regname, true); if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) { // Get a third key for literal register insertion literally = regname; add_to_showcmd_c(literally); regname = plain_vgetc(); - LANGMAP_ADJUST(regname, TRUE); + LANGMAP_ADJUST(regname, true); } no_mapping--; allow_keys--; @@ -4046,7 +3451,7 @@ static void ins_reg(void) need_redraw = true; // remove the '"' } else if (stop_insert_mode) { // When the '=' register was used and a function was invoked that - // did ":stopinsert" then stuff_empty() returns FALSE but we won't + // did ":stopinsert" then stuff_empty() returns false but we won't // insert anything, need to remove the '"' need_redraw = true; } @@ -4197,7 +3602,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Repeat the insert return false; } - stop_insert(&curwin->w_cursor, TRUE, nomove); + stop_insert(&curwin->w_cursor, true, nomove); undisplay_dollar(); } @@ -4251,7 +3656,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 @@ -4266,7 +3671,7 @@ static void ins_ctrl_(void) { if (revins_on && revins_chars && revins_scol >= 0) { while (gchar_cursor() != NUL && revins_chars--) { - ++curwin->w_cursor.col; + curwin->w_cursor.col++; } } p_ri = !p_ri; @@ -4395,9 +3800,9 @@ static void ins_shift(int c, int lastc) if (lastc == '^') { old_indent = get_indent(); // remember curr. indent } - change_indent(INDENT_SET, 0, TRUE, 0, TRUE); + change_indent(INDENT_SET, 0, true, 0, true); } else { - change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, TRUE, 0, TRUE); + change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, 0, true); } if (did_ai && *skipwhite((char *)get_cursor_line_ptr()) != NUL) { @@ -4569,7 +3974,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } } - do_join(2, FALSE, FALSE, FALSE, false); + do_join(2, false, false, false, false); if (temp == NUL && gchar_cursor() != NUL) { inc_cursor(); } @@ -4671,7 +4076,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str((char_u *)" "); + ins_str(" "); if ((State & REPLACE_FLAG)) { replace_push(NUL); } @@ -4715,7 +4120,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } else { const int l_p_deco = p_deco; if (l_p_deco) { - (void)utfc_ptr2char(get_cursor_pos_ptr(), cpc); + (void)utfc_ptr2char((char *)get_cursor_pos_ptr(), cpc); } (void)del_char(false); // If there are combining characters and 'delcombine' is set @@ -4851,7 +4256,7 @@ static void ins_mousescroll(int dir) } } - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; curwin = old_curwin; curbuf = curwin->w_buffer; @@ -4882,7 +4287,7 @@ static void ins_left(void) revins_legal++; } revins_chars++; - } else if (vim_strchr((char *)p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { + } else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { // if 'whichwrap' set for cursor in insert mode may go to previous line. // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); @@ -4975,7 +4380,7 @@ static void ins_right(void) if (revins_chars) { revins_chars--; } - } else if (vim_strchr((char *)p_ww, ']') != NULL + } else if (vim_strchr(p_ww, ']') != NULL && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { // if 'whichwrap' set for cursor in insert mode, may move the // cursor to the next line @@ -5019,13 +4424,13 @@ static void ins_up(bool startcol) undisplay_dollar(); tpos = curwin->w_cursor; - if (cursor_up(1L, TRUE) == OK) { + if (cursor_up(1L, true) == OK) { if (startcol) { coladvance(getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } start_arrow(&tpos); can_cindent = true; @@ -5067,13 +4472,13 @@ static void ins_down(bool startcol) undisplay_dollar(); tpos = curwin->w_cursor; - if (cursor_down(1L, TRUE) == OK) { + if (cursor_down(1L, true) == OK) { if (startcol) { coladvance(getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } start_arrow(&tpos); can_cindent = true; @@ -5177,7 +4582,7 @@ static bool ins_tab(void) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str((char_u *)" "); + ins_str(" "); if (State & REPLACE_FLAG) { // no char replaced replace_push(NUL); } @@ -5219,8 +4624,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. @@ -5235,11 +4640,15 @@ static bool ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); + char_u *tab = (char_u *)"\t"; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab); + // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. while (ascii_iswhite(*ptr)) { - i = lbr_chartabsize(NULL, (char_u *)"\t", vcol); - if (vcol + i > want_vcol) { + i = lbr_chartabsize(&cts); + if (cts.cts_vcol + i > want_vcol) { break; } if (*ptr != TAB) { @@ -5252,21 +4661,26 @@ static bool ins_tab(void) } } } - ++fpos.col; - ++ptr; - vcol += i; + fpos.col++; + ptr++; + cts.cts_vcol += i; } + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (change_col >= 0) { int repl_off = 0; - char_u *line = ptr; - // Skip over the spaces we need. - while (vcol < want_vcol && *ptr == ' ') { - vcol += lbr_chartabsize(line, ptr, vcol); - ++ptr; - ++repl_off; + init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr); + while (cts.cts_vcol < want_vcol && *cts.cts_ptr == ' ') { + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr++; + repl_off++; } + ptr = (char_u *)cts.cts_ptr; + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + if (vcol > want_vcol) { // Must have a char with 'showbreak' just before it. ptr--; @@ -5302,7 +4716,7 @@ static bool ins_tab(void) // Insert each char in saved_line from changed_col to // ptr-cursor - ins_bytes_len(saved_line + change_col, (size_t)(cursor->col - change_col)); + ins_bytes_len((char *)saved_line + change_col, (size_t)(cursor->col - change_col)); } } @@ -5402,7 +4816,7 @@ static int ins_digraph(void) if (IS_SPECIAL(c) || mod_mask) { // special key clear_showcmd(); - insert_special(c, TRUE, FALSE); + insert_special(c, true, false); return NUL; } if (c != ESC) { @@ -5446,7 +4860,6 @@ static int ins_digraph(void) int ins_copychar(linenr_T lnum) { int c; - int temp; char_u *ptr, *prev_ptr; char_u *line; @@ -5456,17 +4869,23 @@ int ins_copychar(linenr_T lnum) } // try to advance to the cursor column - temp = 0; - line = ptr = ml_get(lnum); - prev_ptr = ptr; + line = ml_get(lnum); + prev_ptr = line; validate_virtcol(); - while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) { - prev_ptr = ptr; - temp += lbr_chartabsize_adv(line, &ptr, (colnr_T)temp); + + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, lnum, 0, line, line); + while (cts.cts_vcol < curwin->w_virtcol && *cts.cts_ptr != NUL) { + prev_ptr = (char_u *)cts.cts_ptr; + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - if ((colnr_T)temp > curwin->w_virtcol) { + + if (cts.cts_vcol > curwin->w_virtcol) { ptr = prev_ptr; + } else { + ptr = (char_u *)cts.cts_ptr; } + clear_chartabsize_arg(&cts); c = utf_ptr2char((char *)ptr); if (c == NUL) { @@ -5488,7 +4907,7 @@ static int ins_ctrl_ey(int tc) } else { scrollup_clamp(); } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } else { c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); if (c != NUL) { @@ -5503,7 +4922,7 @@ static int ins_ctrl_ey(int tc) } tw_save = curbuf->b_p_tw; curbuf->b_p_tw = -1; - insert_special(c, TRUE, FALSE); + insert_special(c, true, false); curbuf->b_p_tw = tw_save; revins_chars++; revins_legal++; @@ -5552,7 +4971,7 @@ static void ins_try_si(int c) i = get_indent(); curwin->w_cursor = old_pos; if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, i, FALSE, NUL, TRUE); + change_indent(INDENT_SET, i, false, NUL, true); } else { (void)set_indent(i, SIN_CHANGED); } @@ -5577,7 +4996,7 @@ static void ins_try_si(int c) curwin->w_cursor = old_pos; } if (temp) { - shift_line(TRUE, FALSE, 1, TRUE); + shift_line(true, false, 1, true); } } } @@ -5655,11 +5074,16 @@ static char_u *do_insert_char_pre(int c) return res; } -bool can_cindent_get(void) +bool get_can_cindent(void) { return can_cindent; } +void set_can_cindent(bool val) +{ + can_cindent = val; +} + /// Trigger "event" and take care of fixing undo. int ins_apply_autocmds(event_T event) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5911a93099..2dbaa2f8ac 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1,24 +1,18 @@ // 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> -#include "auto/config.h" - -#ifdef HAVE_LOCALE_H -# include <locale.h> -#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" @@ -28,23 +22,29 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.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/ex_session.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight_group.h" +#include "nvim/locale.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #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 +71,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,11 +94,9 @@ 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_semicolon; // true if ending in '; var]' int fi_varcount; // nr of variables in the list listwatch_T fi_lw; // keep an eye on the item used. list_T *fi_list; // list being used @@ -356,8 +350,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 +357,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 +439,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 +463,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 +508,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 +539,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 +552,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 +572,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 +592,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 @@ -641,7 +626,7 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to, set_vim_var_string(VV_CC_TO, enc_to, -1); set_vim_var_string(VV_FNAME_IN, fname_from, -1); set_vim_var_string(VV_FNAME_OUT, fname_to, -1); - if (eval_to_bool((char *)p_ccv, &err, NULL, false)) { + if (eval_to_bool(p_ccv, &err, NULL, false)) { err = true; } set_vim_var_string(VV_CC_FROM, NULL, -1); @@ -661,7 +646,7 @@ int eval_printexpr(const char *const fname, const char *const args) set_vim_var_string(VV_FNAME_IN, fname, -1); set_vim_var_string(VV_CMDARG, args, -1); - if (eval_to_bool((char *)p_pexpr, &err, NULL, false)) { + if (eval_to_bool(p_pexpr, &err, NULL, false)) { err = true; } set_vim_var_string(VV_FNAME_IN, NULL, -1); @@ -681,7 +666,7 @@ void eval_diff(const char *const origfile, const char *const newfile, const char set_vim_var_string(VV_FNAME_IN, origfile, -1); set_vim_var_string(VV_FNAME_NEW, newfile, -1); set_vim_var_string(VV_FNAME_OUT, outfile, -1); - (void)eval_to_bool((char *)p_dex, &err, NULL, false); + (void)eval_to_bool(p_dex, &err, NULL, false); set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_FNAME_NEW, NULL, -1); set_vim_var_string(VV_FNAME_OUT, NULL, -1); @@ -701,11 +686,11 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch } /// Top level evaluation function, returning a boolean. -/// Sets "error" to TRUE if there was an error. +/// Sets "error" to true if there was an error. /// /// @param skip only parse, don't execute /// -/// @return TRUE or FALSE. +/// @return true or false. int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip) { typval_T tv; @@ -905,7 +890,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert) /// Call eval_to_string() without using current local variables and using /// textlock. /// -/// @param use_sandbox when TRUE, use the sandbox. +/// @param use_sandbox when true, use the sandbox. char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox) { char *retval; @@ -935,7 +920,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 +928,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 +985,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 +1023,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 +1035,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 +1117,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 +1173,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 +1181,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 +1206,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 +1244,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 +1296,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 +1313,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 +1557,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 +1710,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); @@ -1864,7 +1807,7 @@ notify: /// Evaluate the expression used in a ":for var in expr" command. /// "arg" points to "var". /// -/// @param[out] *errp set to TRUE for an error, FALSE otherwise; +/// @param[out] *errp set to true for an error, false otherwise; /// /// @return a pointer that holds the info. Null when there is an error. void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) @@ -1888,7 +1831,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 +1873,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) } } if (skip) { - --emsg_skip; + emsg_skip--; } return fi; @@ -2009,7 +1952,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 +1978,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 +1991,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 +2016,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; @@ -2099,7 +2042,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) || cmdidx == CMD_echomsg) && xp->xp_context == EXPAND_EXPRESSION) { for (;;) { - char *const n = (char *)skiptowhite((char_u *)arg); + char *const n = skiptowhite(arg); if (n == arg || ascii_iswhite_or_nul(*skipwhite(n))) { break; @@ -2123,11 +2066,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 +2112,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 +2129,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 +2143,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 +2157,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); } @@ -2238,7 +2179,7 @@ char *get_user_var_name(expand_T *xp, int idx) /// Does not use 'cpo' and always uses 'magic'. /// -/// @return TRUE if "pat" matches "text". +/// @return true if "pat" matches "text". int pattern_match(char *pat, char *text, bool ic) { int matches = 0; @@ -2246,7 +2187,7 @@ int pattern_match(char *pat, char *text, bool ic) // avoid 'l' flag in 'cpoptions' char *save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = ic; @@ -2291,7 +2232,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,15 +2258,13 @@ 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. -/// Put the result in "rettv" when returning OK and "evaluate" is TRUE. +/// Put the result in "rettv" when returning OK and "evaluate" is true. /// Note: "rettv.v_lock" is not set. /// /// @return OK or FAIL. @@ -2372,18 +2311,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 +2333,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 +2348,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 +2376,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 +2398,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 +2435,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 +2457,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 +2508,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 +2557,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 +2608,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 +2635,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 +2646,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]; @@ -2759,7 +2658,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) tv_clear(&var2); return FAIL; } - p = (char *)concat_str((const char_u *)s1, (const char_u *)s2); + p = concat_str(s1, s2); tv_clear(rettv); rettv->v_type = VAR_STRING; rettv->vval.v_string = p; @@ -2876,16 +2775,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 +2803,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 +2828,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 +2942,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 +3038,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,13 +3055,13 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // Register contents: @r. case '@': - ++*arg; + (*arg)++; if (evaluate) { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(**arg, kGRegExprSrc); } if (**arg != NUL) { - ++*arg; + (*arg)++; } break; @@ -3179,7 +3070,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); @@ -3325,7 +3216,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). @@ -3353,7 +3244,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 != '(') { @@ -3503,9 +3394,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) { @@ -3513,11 +3402,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; @@ -3529,9 +3416,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); @@ -3772,11 +3657,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. @@ -3793,10 +3674,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) { @@ -3837,9 +3722,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++; @@ -3863,10 +3746,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; @@ -3905,7 +3786,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++; @@ -3935,7 +3816,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>" @@ -3957,11 +3838,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; @@ -3979,19 +3860,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++; } } @@ -4006,10 +3884,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; @@ -4018,9 +3894,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; @@ -4119,16 +3995,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; } @@ -4141,8 +4015,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; @@ -4152,8 +4026,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; } @@ -4182,25 +4056,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. /// @@ -4219,6 +4091,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(); @@ -4232,7 +4121,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); } @@ -4691,21 +4580,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; @@ -4715,9 +4599,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; @@ -4750,7 +4636,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); @@ -4805,8 +4691,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; @@ -4820,6 +4704,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); } @@ -4833,23 +4718,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); @@ -4867,18 +4747,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) { @@ -4908,17 +4776,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") @@ -4949,18 +4810,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; @@ -4969,14 +4832,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))) { @@ -5100,7 +4963,7 @@ theend: return retval; } -void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr fptr) +void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) { char *s; char *name; @@ -5125,7 +4988,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) { @@ -5329,29 +5192,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) @@ -5383,12 +5223,12 @@ dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); tv_dict_add_nr(dict, S_LEN("winnr"), winnr); tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); - tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); + tv_dict_add_nr(dict, S_LEN("height"), wp->w_height_inner); tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); - tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); + tv_dict_add_nr(dict, S_LEN("width"), wp->w_width_inner); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); tv_dict_add_nr(dict, S_LEN("textoff"), win_col_off(wp)); @@ -5688,11 +5528,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; @@ -5714,6 +5550,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; @@ -5723,6 +5560,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); @@ -5806,7 +5646,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; @@ -5815,6 +5654,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; @@ -5924,9 +5764,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; @@ -5999,7 +5839,17 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co switch (callback->type) { case kCallbackFuncref: name = callback->data.funcref; - partial = NULL; + int len = (int)STRLEN(name); + if (len >= 6 && !memcmp(name, "v:lua.", 6)) { + name += 6; + len = check_luafunc_name(name, false); + if (len == 0) { + return false; + } + partial = vvlua_partial; + } else { + partial = NULL; + } break; case kCallbackPartial: @@ -6135,7 +5985,7 @@ void timer_due_cb(TimeWatcher *tw, void *data) // Handle error message if (called_emsg > called_emsg_before && did_emsg) { timer->emsg_count++; - if (current_exception != NULL) { + if (did_throw) { discard_current_exception(); } } @@ -6498,12 +6348,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; } @@ -6520,6 +6367,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 { @@ -6527,7 +6375,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) { @@ -6627,8 +6475,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. @@ -6639,6 +6485,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) { @@ -6691,15 +6539,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; } @@ -6747,8 +6593,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 @@ -6757,7 +6601,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; @@ -6775,10 +6619,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; @@ -6810,10 +6652,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; @@ -6824,6 +6662,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) @@ -6841,7 +6683,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) { @@ -6859,9 +6701,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--; } } @@ -6897,20 +6739,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); @@ -6940,14 +6781,14 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex return retval; } -/// @return TRUE if character "c" can be used in a variable or function name. +/// @return true if character "c" can be used in a variable or function name. /// Does not include '{' or '}' for magic braces. int eval_isnamec(int c) { return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; } -/// @return TRUE if character "c" can be used as the first character in a +/// @return true if character "c" can be used as the first character in a /// variable or function name (excluding '{' and '}'). int eval_isnamec1(int c) { @@ -7000,7 +6841,7 @@ void set_vim_var_char(int c) /// Set v:count to "count" and v:count1 to "count1". /// -/// @param set_prevcount if TRUE, first set v:prevcount from v:count. +/// @param set_prevcount if true, first set v:prevcount from v:count. void set_vcount(long count, long count1, int set_prevcount) { if (set_prevcount) { @@ -7353,7 +7194,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; @@ -7430,8 +7271,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) { @@ -7455,7 +7294,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 @@ -7490,7 +7329,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; @@ -7506,7 +7344,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; } @@ -7562,7 +7400,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char bool should_free; // should_free is ignored as script_sctx will be resolved to a fnmae // & new_script_item will consume it. - char *sc_name = (char *)get_scriptname(last_set, &should_free); + char *sc_name = get_scriptname(last_set, &should_free); new_script_item(sc_name, ¤t_sctx.sc_sid); } } @@ -7595,28 +7433,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++; } } @@ -7640,8 +7476,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); } @@ -7673,7 +7509,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: @@ -7691,9 +7527,9 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c } else { to->v_type = VAR_STRING; to->v_lock = VAR_UNLOCKED; - if ((to->vval.v_string = (char *)string_convert((vimconv_T *)conv, - (char_u *)from->vval.v_string, - NULL)) + if ((to->vval.v_string = string_convert((vimconv_T *)conv, + from->vval.v_string, + NULL)) == NULL) { to->vval.v_string = xstrdup(from->vval.v_string); } @@ -7726,7 +7562,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); } @@ -7738,7 +7574,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; } @@ -7755,7 +7591,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 @@ -7835,12 +7671,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); @@ -7884,7 +7719,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) { @@ -7898,7 +7733,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); @@ -7914,7 +7749,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; @@ -7940,174 +7775,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. /// @@ -8182,61 +7849,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 { @@ -8324,11 +7936,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; @@ -8370,7 +7980,7 @@ void option_last_set_msg(LastSet last_set) { if (last_set.script_ctx.sc_sid != 0) { bool should_free; - char *p = (char *)get_scriptname(last_set, &should_free); + char *p = get_scriptname(last_set, &should_free); verbose_enter(); msg_puts(_("\n\tLast set from ")); msg_puts(p); @@ -8413,10 +8023,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; @@ -8469,7 +8077,7 @@ repeat: } // Append a path separator to a directory. - if (os_isdir((char_u *)(*fnamep))) { + if (os_isdir(*fnamep)) { // Make room for one or two extra characters. *fnamep = xstrnsave(*fnamep, STRLEN(*fnamep) + 2); xfree(*bufp); // free any allocated file name @@ -8478,6 +8086,8 @@ repeat: } } + int c; + // ":." - path relative to the current directory // ":~" - path relative to the home directory // ":8" - shortname path - postponed till after @@ -8533,6 +8143,7 @@ repeat: // Only replace it when it starts with '~' if (*dirname == '~') { s = xstrdup(dirname); + assert(s != NULL); // suppress clang "Argument with 'nonnull' attribute passed null" *fnamep = s; xfree(*bufp); *bufp = s; @@ -8543,7 +8154,7 @@ repeat: } } - tail = path_tail(*fnamep); + char *tail = path_tail(*fnamep); *fnamelen = STRLEN(*fnamep); // ":h" - head, remove "/file_name", can be repeated @@ -8551,7 +8162,7 @@ repeat: while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { valid |= VALID_HEAD; *usedlen += 2; - s = (char *)get_past_head((char_u *)(*fnamep)); + s = get_past_head(*fnamep); while (tail > s && after_pathsep(s, tail)) { MB_PTR_BACK(*fnamep, tail); } @@ -8584,10 +8195,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; @@ -8638,18 +8248,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); @@ -8667,7 +8275,7 @@ repeat: *fnamelen = STRLEN(s); xfree(*bufp); *bufp = s; - didit = TRUE; + didit = true; xfree(sub); xfree(str); } @@ -8708,26 +8316,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; - p_cpo = (char *)empty_option; + char *save_cpo = p_cpo; + p_cpo = 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]) { @@ -8777,11 +8381,16 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags char *ret = xstrdup(ga.ga_data == NULL ? str : ga.ga_data); ga_clear(&ga); - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { // Darn, evaluating {sub} expression or {expr} changed the value. - free_string_option((char_u *)save_cpo); + // If it's still empty it was changed and restored, need to restore in + // the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); } return ret; @@ -8868,8 +8477,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, @@ -8968,8 +8576,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, "?"); } @@ -8988,7 +8596,7 @@ void ex_checkhealth(exarg_T *eap) if (vimruntime_env == NULL) { emsg(_("E5009: $VIMRUNTIME is empty or unset")); } else { - bool rtp_ok = NULL != strstr((char *)p_rtp, vimruntime_env); + bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env); if (rtp_ok) { semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env); } else { @@ -9011,8 +8619,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 @@ -9024,8 +8630,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..3e89489459 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -23,7 +23,7 @@ end return { funcs={ abs={args=1, base=1}, - acos={args=1, base=1, func="float_op_wrapper", data="&acos"}, -- WJMc + acos={args=1, base=1, float_func="acos"}, -- WJMc add={args=2, base=1}, ['and']={args=2, base=1}, api_info={}, @@ -33,7 +33,7 @@ return { argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, - asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc + asin={args=1, base=1, float_func="asin"}, -- WJMc assert_beeps={args=1, base=1}, assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, @@ -47,7 +47,7 @@ return { assert_notmatch={args={2, 3}, base=2}, assert_report={args=1, base=1}, assert_true={args={1, 2}, base=1}, - atan={args=1, base=1, func="float_op_wrapper", data="&atan"}, + atan={args=1, base=1, float_func="atan"}, atan2={args=2, base=1}, browse={args=4}, browsedir={args=2}, @@ -67,11 +67,12 @@ return { byteidx={args=2, base=1}, byteidxcomp={args=2, base=1}, call={args={2, 3}, base=1}, - ceil={args=1, base=1, func="float_op_wrapper", data="&ceil"}, + ceil={args=1, base=1, float_func="ceil"}, changenr={}, 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}, @@ -84,8 +85,8 @@ return { complete_info={args={0, 1}, base=1}, confirm={args={1, 4}, base=1}, copy={args=1, base=1}, - cos={args=1, base=1, func="float_op_wrapper", data="&cos"}, - cosh={args=1, base=1, func="float_op_wrapper", data="&cosh"}, + cos={args=1, base=1, float_func="cos"}, + cosh={args=1, base=1, float_func="cosh"}, count={args={2, 4}, base=1}, cscope_connection={args={0, 3}}, ctxget={args={0, 1}}, @@ -116,7 +117,7 @@ return { execute={args={1, 2}, base=1}, exepath={args=1, base=1}, exists={args=1, base=1}, - exp={args=1, base=1, func="float_op_wrapper", data="&exp"}, + exp={args=1, base=1, float_func="exp"}, expand={args={1, 3}, base=1}, expandcmd={args=1, base=1}, extend={args={2, 3}, base=1}, @@ -129,7 +130,7 @@ return { findfile={args={1, 3}, base=1}, flatten={args={1, 2}, base=1}, float2nr={args=1, base=1}, - floor={args=1, base=1, func="float_op_wrapper", data="&floor"}, + floor={args=1, base=1, float_func="floor"}, fmod={args=2, base=1}, fnameescape={args=1, base=1}, fnamemodify={args=2, base=1}, @@ -244,8 +245,8 @@ return { lispindent={args=1, base=1}, list2str={args={1, 2}, base=1}, localtime={}, - log={args=1, base=1, func="float_op_wrapper", data="&log"}, - log10={args=1, base=1, func="float_op_wrapper", data="&log10"}, + log={args=1, base=1, float_func="log"}, + log10={args=1, base=1, float_func="log10"}, luaeval={args={1, 2}, base=1}, map={args=2, base=1}, maparg={args={1, 4}, base=1}, @@ -303,7 +304,7 @@ return { ['repeat']={args=2, base=1}, resolve={args=1, base=1}, reverse={args=1, base=1}, - round={args=1, base=1, func="float_op_wrapper", data="&round"}, + round={args=1, base=1, float_func="round"}, rpcnotify={args=varargs(2)}, rpcrequest={args=varargs(2)}, rpcstart={args={1, 2}}, @@ -327,9 +328,11 @@ 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}, + setcmdline={args={1, 2}, base=1}, setcursorcharpos={args={1, 3}, base=1}, setenv={args=2, base=2}, setfperm={args=2, base=1}, @@ -356,8 +359,8 @@ return { sign_unplace={args={1, 2}, base=1}, sign_unplacelist={args=1, base=1}, simplify={args=1, base=1}, - sin={args=1, base=1, func="float_op_wrapper", data="&sin"}, - sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"}, + sin={args=1, base=1, float_func="sin"}, + sinh={args=1, base=1, float_func="sinh"}, sockconnect={args={2,3}}, sort={args={1, 3}, base=1}, soundfold={args=1, base=1}, @@ -365,7 +368,7 @@ return { spellbadword={args={0, 1}, base=1}, spellsuggest={args={1, 3}, base=1}, split={args={1, 3}, base=1}, - sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, + sqrt={args=1, base=1, float_func="sqrt"}, srand={args={0, 1}, base=1}, stdpath={args=1}, str2float={args=1, base=1}, @@ -400,8 +403,8 @@ return { tabpagewinnr={args={1, 2}, base=1}, tagfiles={}, taglist={args={1, 2}, base=1}, - tan={args=1, base=1, func="float_op_wrapper", data="&tan"}, - tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"}, + tan={args=1, base=1, float_func="tan"}, + tanh={args=1, base=1, float_func="tanh"}, tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, @@ -415,7 +418,7 @@ return { toupper={args=1, base=1}, tr={args=3, base=1}, trim={args={1, 3}, base=1}, - trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"}, + trunc={args=1, base=1, float_func="trunc"}, type={args=1, base=1}, undofile={args=1, base=1}, undotree={}, 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/executor.c b/src/nvim/eval/executor.c index b461456a3a..0e0d0fe696 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -108,8 +108,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons const char *tvs = tv_get_string(tv1); char numbuf[NUMBUFLEN]; char *const s = - (char *)concat_str((const char_u *)tvs, (const char_u *)tv_get_string_buf(tv2, - numbuf)); + concat_str(tvs, tv_get_string_buf(tv2, numbuf)); tv_clear(tv1); tv1->v_type = VAR_STRING; tv1->vval.v_string = s; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 060dc50f52..aa328d50ef 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7,12 +7,15 @@ #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/cmdexpand.h" +#include "nvim/cmdhist.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -26,11 +29,14 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds.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,25 +51,29 @@ #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" #include "nvim/msgpack_rpc/server.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.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 +127,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 +163,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; } @@ -226,7 +234,7 @@ int call_internal_method(const char_u *const fname, const int argcount, typval_T return ERROR_NONE; } -/// @return TRUE for a non-zero Number and a non-empty String. +/// @return true for a non-zero Number and a non-empty String. static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER @@ -243,26 +251,25 @@ static int non_zero_arg(typval_T *argvars) /// Some versions of glibc on i386 have an optimization that makes it harder to /// call math functions indirectly from inside an inlined function, causing /// compile-time errors. Avoid `inline` in that case. #3072 -static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void float_op_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; - float_T (*function)(float_T) = (float_T (*)(float_T)) fptr; rettv->v_type = VAR_FLOAT; if (tv_get_float_chk(argvars, &f)) { - rettv->vval.v_float = function(f); + rettv->vval.v_float = fptr.float_func(f); } else { rettv->vval.v_float = 0.0; } } -static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; } - ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; + MsgpackRpcRequestHandler handler = *fptr.api_handler; Array args = ARRAY_DICT_INIT; @@ -271,7 +278,8 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Error err = ERROR_INIT; - Object result = fn(VIML_INTERNAL_CALL, args, &err); + Arena res_arena = ARENA_EMPTY; + Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err); if (ERROR_SET(&err)) { semsg_multiline((const char *)e_api_error, err.msg); @@ -284,20 +292,23 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) end: api_free_array(args); - api_free_object(result); + if (handler.arena_return) { + arena_mem_free(arena_finish(&res_arena)); + } else { + api_free_object(result); + } api_clear_error(&err); } /// "abs(expr)" function -static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_abs(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_FLOAT) { - float_op_wrapper(argvars, rettv, (FunPtr)&fabs); + float_op_wrapper(argvars, rettv, (EvalFuncData){ .float_func = &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) { @@ -309,7 +320,7 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "add(list, item)" function -static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { @@ -337,22 +348,21 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "and(expr, expr)" function -static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_and(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) & tv_get_number_chk(&argvars[1], NULL); } /// "api_info()" function -static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_api_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { Dictionary metadata = api_metadata(); (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); - api_free_dictionary(metadata); } /// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(&argvars[0]); @@ -360,7 +370,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *const buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { @@ -371,79 +381,8 @@ 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) +static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -457,16 +396,16 @@ static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "browse(save, title, initdir, default)" function -static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; } /// "browsedir(title, initdir)" function -static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - f_browse(argvars, rettv, NULL); + f_browse(argvars, rettv, fptr); } /// Find a buffer by number or exact name. @@ -479,8 +418,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)) @@ -495,7 +434,7 @@ static buf_T *find_buffer(typval_T *avar) } /// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *name = (char_u *)tv_get_string(&argvars[0]); @@ -503,13 +442,13 @@ static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufexists(expr)" function -static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); } /// "buflisted(expr)" function -static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf; @@ -518,7 +457,7 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { buf_T *buf = get_buf_arg(&argvars[0]); @@ -533,7 +472,7 @@ static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) } /// "bufloaded(expr)" function -static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf; @@ -542,7 +481,7 @@ static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufname(expr)" function -static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const buf_T *buf; rettv->v_type = VAR_STRING; @@ -558,7 +497,7 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "bufnr(expr)" function -static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const buf_T *buf; bool error = false; @@ -618,13 +557,13 @@ static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) } /// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufwinid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_win_common(argvars, rettv, false); } /// "bufwinnr(nr)" function -static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_win_common(argvars, rettv, true); } @@ -632,17 +571,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 +588,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; - p_cpo = ""; + int save_magic = p_magic; + p_magic = true; + char *save_cpo = p_cpo; + p_cpo = empty_option; - 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 +623,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)); @@ -698,7 +633,7 @@ buf_T *get_buf_arg(typval_T *arg) } /// "byte2line(byte)" function -static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long boff = tv_get_number(&argvars[0]) - 1; if (boff < 0) { @@ -733,19 +668,19 @@ 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) +static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - byteidx(argvars, rettv, FALSE); + byteidx(argvars, rettv, false); } /// "byteidxcomp()" function -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - byteidx(argvars, rettv, TRUE); + byteidx(argvars, rettv, true); } /// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_LIST) { emsg(_(e_listreq)); @@ -758,7 +693,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 +710,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)); @@ -794,13 +729,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "changenr()" function -static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_changenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = curbuf->b_u_seq_cur; } /// "chanclose(id[, stream])" function -static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chanclose(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -839,7 +774,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "chansend(id, data)" function -static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -880,7 +815,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "char2nr(string)" function -static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_char2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { if (!tv_check_num(&argvars[1])) { @@ -898,10 +833,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 @@ -915,11 +849,12 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) // col(".") when the cursor is on the NUL at the end of the line // because of "coladd" can be seen as an extra column. if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); + char *p = (char *)get_cursor_pos_ptr(); if (curwin->w_cursor.coladd >= - (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) { + (colnr_T)win_chartabsize(curwin, p, + curwin->w_virtcol - curwin->w_cursor.coladd)) { int l; - if (*p != NUL && p[(l = utfc_ptr2len((char *)p))] == NUL) { + if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { col += l; } } @@ -930,20 +865,21 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) } /// "charcol()" function -static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_col(argvars, rettv, true); } /// "charidx()" function -static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_NUMBER || (argvars[2].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_NUMBER)) { + && argvars[2].v_type != VAR_NUMBER + && argvars[2].v_type != VAR_BOOL)) { emsg(_(e_invarg)); return; } @@ -982,11 +918,8 @@ 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) +static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *cwd; - CdScope scope = kCdScopeGlobal; - rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -997,7 +930,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 +939,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) { @@ -1019,13 +953,10 @@ 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) +static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData 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(); @@ -1050,24 +981,22 @@ win_T *get_optional_window(typval_T *argvars, int idx) } /// "col(string)" function -static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_col(argvars, rettv, false); } /// "confirm(message, buttons[, default [, type]])" function -static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData 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 +1008,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 { @@ -1111,13 +1040,13 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "copy()" function -static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { var_item_copy(NULL, &argvars[0], rettv, false, 0); } /// "count()" function -static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long n = 0; int ic = 0; @@ -1152,15 +1081,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 +1107,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)) { @@ -1210,7 +1135,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function /// /// Checks the existence of a cscope connection. -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cscope_connection(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int num = 0; const char *dbpath = NULL; @@ -1231,7 +1156,7 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxget([{index}])" function -static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { size_t index = 0; if (argvars[0].v_type == VAR_NUMBER) { @@ -1255,7 +1180,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxpop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (!ctx_restore(NULL, kCtxAll)) { emsg(_("Context stack is empty")); @@ -1263,7 +1188,7 @@ static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int types = kCtxAll; if (argvars[0].v_type == VAR_LIST) { @@ -1294,7 +1219,7 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxset({context}[, {index}])" function -static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { semsg(_(e_invarg2), "expected dictionary as first argument"); @@ -1334,7 +1259,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = (varnumber_T)ctx_size(); @@ -1406,18 +1331,16 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) /// Moves the cursor to the specified line and column. /// /// @return 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cursor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_cursorpos(argvars, rettv, false); } /// "debugbreak()" function -static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData 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 { @@ -1436,7 +1359,7 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "deepcopy()" function -static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int noref = 0; @@ -1453,7 +1376,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; if (check_secure()) { @@ -1489,7 +1412,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// dictwatcheradd(dict, key, funcref) function -static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -1527,7 +1450,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// dictwatcherdel(dict, key, funcref) function -static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -1562,12 +1485,8 @@ 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) +static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData 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 +1495,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 +1509,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; @@ -1639,19 +1561,19 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "did_filetype()" function -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = did_filetype; } /// "diff_filler()" function -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); } /// "diff_hlID()" function -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(argvars); static linenr_T prev_lnum = 0; @@ -1660,8 +1582,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 +1590,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 +1612,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 { @@ -1703,7 +1623,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "empty({expr})" function -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool n = true; @@ -1753,7 +1673,7 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "environ()" function -static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_environ(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -1798,7 +1718,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "escape({string}, {chars})" function -static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_escape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; @@ -1809,7 +1729,7 @@ static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getenv()" function -static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); @@ -1823,7 +1743,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "eval()" function -static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string_chk(&argvars[0]); if (s != NULL) { @@ -1844,13 +1764,13 @@ static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "eventhandler()" function -static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = vgetc_busy; } /// "executable()" function -static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (tv_check_for_string(&argvars[0]) == FAIL) { return; @@ -1879,7 +1799,7 @@ static char *get_list_line(int c, void *cookie, int indent, bool do_concat) return s == NULL ? NULL : xstrdup(s); } -static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int arg_off) +static void execute_common(typval_T *argvars, typval_T *rettv, int arg_off) { const int save_msg_silent = msg_silent; const int save_emsg_silent = emsg_silent; @@ -1958,13 +1878,13 @@ static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int } /// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - execute_common(argvars, rettv, fptr, 0); + execute_common(argvars, rettv, 0); } /// "win_execute(win_id, command)" function -static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Return an empty string if something fails. rettv->v_type = VAR_STRING; @@ -1974,12 +1894,12 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) tabpage_T *tp; win_T *wp = win_id2wp_tp(id, &tp); if (wp != NULL && tp != NULL) { - WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1)); + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, 1)); } } /// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (tv_check_for_nonempty_string(&argvars[0]) == FAIL) { return; @@ -2000,7 +1920,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "exists()" function -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = false; @@ -2040,14 +1960,10 @@ 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) +static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData 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; @@ -2065,9 +1981,17 @@ 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); - emsg_off--; + if (p_verbose == 0) { + emsg_off++; + } + size_t len; + char *errormsg = NULL; + char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL, false); + if (p_verbose == 0) { + emsg_off--; + } else if (errormsg != NULL) { + emsg(errormsg); + } if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { @@ -2085,6 +2009,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) { @@ -2112,7 +2037,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "menu_get(path [, modes])" function -static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; @@ -2125,7 +2050,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "expandcmd()" function /// Expand all the special characters in a command string. -static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *errormsg = NULL; @@ -2141,18 +2066,16 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) }; eap.argt |= EX_NOSPC; + emsg_off++; expand_filename(&eap, &cmdstr, &errormsg); - if (errormsg != NULL && *errormsg != NUL) { - emsg(errormsg); - } + emsg_off--; + rettv->vval.v_string = cmdstr; } /// "flatten(list[, {maxdepth}])" function -static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - list_T *list; - long maxdepth; bool error = false; if (argvars[0].v_type != VAR_LIST) { @@ -2160,6 +2083,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 +2097,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"), @@ -2185,12 +2109,11 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "extend(list, list [, idx])" function /// "extend(dict, dict [, action])" function -static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData 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 +2121,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. } @@ -2262,7 +2185,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "feedkeys()" function -static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is not allowed in the sandbox. If the commands would still be // executed in the sandbox it would be OK, but it probably happens later, @@ -2283,17 +2206,17 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); rettv->vval.v_number = - (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); + (*p && !os_isdir(p) && os_file_is_readable(p)); } /// @return 0 for not writable /// 1 for writable file /// 2 for a dir which we have rights to write into. -static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *filename = tv_get_string(&argvars[0]); rettv->vval.v_number = os_file_is_writable(filename); @@ -2302,7 +2225,7 @@ static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) { char_u *fresult = NULL; - char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + char_u *path = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path; int count = 1; bool first = true; bool error = false; @@ -2343,7 +2266,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) find_what, (char_u *)curbuf->b_ffname, (find_what == FINDFILE_DIR ? (char_u *)"" - : curbuf->b_p_sua)); + : (char_u *)curbuf->b_p_sua)); first = false; if (fresult != NULL && rettv->v_type == VAR_LIST) { @@ -2358,25 +2281,25 @@ 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) +static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - filter_map(argvars, rettv, FALSE); + filter_map(argvars, rettv, false); } /// "finddir({fname}[, {path}[, {count}]])" function -static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { findfilendir(argvars, rettv, FINDFILE_DIR); } /// "findfile({fname}[, {path}[, {count}]])" function -static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { findfilendir(argvars, rettv, FINDFILE_FILE); } /// "float2nr({float})" function -static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; @@ -2392,7 +2315,7 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "fmod()" function -static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fmod(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -2406,14 +2329,14 @@ static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "fnameescape({string})" function -static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fnameescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = vim_strsave_fnameescape(tv_get_string(&argvars[0]), VSE_NONE); rettv->v_type = VAR_STRING; } /// "fnamemodify({fname}, {mods})" function -static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *fbuf = NULL; size_t len = 0; @@ -2440,146 +2363,22 @@ 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) +static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) {} -static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - common_function(argvars, rettv, true, fptr); + common_function(argvars, rettv, true); } -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_function(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - common_function(argvars, rettv, false, fptr); + common_function(argvars, rettv, false); } /// "garbagecollect()" function -static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_garbagecollect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is postponed until we are back at the toplevel, because we may be // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". @@ -2591,12 +2390,8 @@ 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) +static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; typval_T *tv = NULL; bool what_is_dict = false; @@ -2617,17 +2412,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 +2436,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; } @@ -2690,7 +2487,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *argbuf = NULL; bool filtered = false; @@ -2752,7 +2549,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Get line or list of lines from buffer "buf" into "rettv". /// -/// @param retlist if TRUE, then the lines are returned as a Vim List. +/// @param retlist if true, then the lines are returned as a Vim List. /// /// @return range (from start to end) of lines in rettv from the specified /// buffer. @@ -2789,7 +2586,7 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli } /// "getbufline()" function -static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -2802,7 +2599,7 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getchangelist()" function -static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, 2); @@ -2851,147 +2648,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; @@ -3048,13 +2704,13 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool } /// "getcharpos()" function -static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, false, true); } /// "getcharsearch()" function -static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -3065,42 +2721,8 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/// "getcmdcompltype()" function -static void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)get_cmdline_completion(); -} - -/// "getcmdline()" function -static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)get_cmdline_str(); -} - -/// "getcmdpos()" function -static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_pos() + 1; -} - -/// "getcmdscreenpos()" function -static void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_screen_pos() + 1; -} - -/// "getcmdtype()" function -static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = (char)get_cmdline_type(); -} - /// "getcmdwintype()" function -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -3108,84 +2730,6 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string[0] = (char)cmdwin_type; } -/// "getcompletion()" function -static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *pat; - expand_T xpc; - bool filtered = false; - int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH - | WILD_NO_BEEP | WILD_HOME_REPLACE; - - if (argvars[1].v_type != VAR_STRING) { - semsg(_(e_invarg2), "type must be a string"); - return; - } - const char *const type = tv_get_string(&argvars[1]); - - if (argvars[2].v_type != VAR_UNKNOWN) { - filtered = (bool)tv_get_number_chk(&argvars[2], NULL); - } - - if (p_wic) { - options |= WILD_ICASE; - } - - // For filtered results, 'wildignore' is used - if (!filtered) { - options |= WILD_KEEP_ALL; - } - - if (argvars[0].v_type != VAR_STRING) { - emsg(_(e_invarg)); - return; - } - const char *pattern = tv_get_string(&argvars[0]); - - if (strcmp(type, "cmdline") == 0) { - set_one_cmd_context(&xpc, pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_col = (int)STRLEN(pattern); - goto theend; - } - - ExpandInit(&xpc); - xpc.xp_pattern = (char *)pattern; - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type(type); - if (xpc.xp_context == EXPAND_NOTHING) { - semsg(_(e_invarg2), type); - return; - } - - if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_CSCOPE) { - set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_SIGN) { - set_context_in_sign_cmd(&xpc, (char_u *)xpc.xp_pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - -theend: - pat = addstar((char_u *)xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - xfree(pat); - ExpandCleanup(&xpc); -} - /// `getcwd([{win}[, {tab}]])` function /// /// Every scope not specified implies the currently selected scope object. @@ -3195,7 +2739,7 @@ theend: /// @pre An argument may not be -1 if preceding arguments are not all -1. /// /// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Possible scope of working directory to return. CdScope scope = kCdScopeInvalid; @@ -3203,15 +2747,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; @@ -3308,14 +2852,14 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getfontname()" function -static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; } /// "getfperm({fname})" function -static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *perm = NULL; char_u flags[] = "rwx"; @@ -3335,7 +2879,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getfsize({fname})" function -static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3344,7 +2888,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) FileInfo file_info; if (os_fileinfo(fname, &file_info)) { uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir((const char_u *)fname)) { + if (os_isdir(fname)) { rettv->vval.v_number = 0; } else { rettv->vval.v_number = (varnumber_T)filesize; @@ -3360,7 +2904,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getftime({fname})" function -static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3373,7 +2917,7 @@ static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getftype({fname})" function -static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u *type = NULL; char *t; @@ -3407,7 +2951,7 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getjumplist()" function -static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); @@ -3438,7 +2982,7 @@ static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getline(lnum, [end])" function -static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T end; bool retlist; @@ -3455,15 +2999,8 @@ 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) +static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -3481,10 +3018,8 @@ 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) +static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - dict_T *d; - win_T *wp; int row = mouse_row; int col = mouse_col; int grid = mouse_grid; @@ -3495,12 +3030,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 @@ -3524,34 +3059,28 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getpid()" function -static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = os_get_pid(); } /// "getcurpos(string)" function -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcurpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, true, false); } -static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, true, true); } /// "getpos(string)" function -static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData 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. @@ -3573,7 +3102,7 @@ static int getreg_get_regname(typval_T *argvars) } /// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int arg2 = false; bool return_list = false; @@ -3609,7 +3138,7 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getregtype()" function -static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getregtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // on error return an empty string rettv->v_type = VAR_STRING; @@ -3629,7 +3158,7 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_gettabinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tabpage_T *tparg = NULL; @@ -3661,7 +3190,7 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettagstack()" function -static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = curwin; // default is current window @@ -3678,7 +3207,7 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wparg = NULL; @@ -3723,7 +3252,7 @@ static void dummy_timer_close_cb(TimeWatcher *tw, void *data) } /// "wait(timeout, condition[, interval])" function -static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = -1; @@ -3777,7 +3306,7 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, 2); const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); @@ -3788,7 +3317,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 +3330,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 @@ -3824,14 +3353,10 @@ 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) +static void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData 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 +3366,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; @@ -3864,7 +3391,7 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getwinpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, 2); tv_list_append_number(rettv->vval.v_list, -1); @@ -3872,26 +3399,26 @@ static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getwinposx()" function -static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getwinposx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; } /// "getwinposy()" function -static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getwinposy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; } /// "glob()" function -static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int options = WILD_SILENT|WILD_USE_NL; 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)) { @@ -3933,7 +3460,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. bool error = false; @@ -3964,7 +3491,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 *)file, &ga, flags); if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); @@ -3983,7 +3510,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -3992,7 +3519,7 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "has()" function -static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { static const char *const has_list[] = { #if defined(BSD) && !defined(__APPLE__) @@ -4214,7 +3741,7 @@ static bool has_wsl(void) /// @pre An argument may not be -1 if preceding arguments are not all -1. /// /// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Possible scope of working directory to return. CdScope scope = kCdScopeInvalid; @@ -4303,101 +3830,20 @@ 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) +static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0])); } /// "highlight_exists()" function -static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hlexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0])); } /// "hostname()" function -static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char hostname[256]; @@ -4407,7 +3853,7 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// iconv() function -static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { vimconv_T vimconv; @@ -4416,17 +3862,19 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const str = tv_get_string(&argvars[0]); char buf1[NUMBUFLEN]; - char_u *const from = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[1], buf1))); + char_u *const from = + (char_u *)enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); char buf2[NUMBUFLEN]; - char_u *const to = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[2], buf2))); + char_u *const to = + (char_u *)enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); + convert_setup(&vimconv, (char *)from, (char *)to); // If the encodings are equal, no conversion needed. if (vimconv.vc_type == CONV_NONE) { rettv->vval.v_string = xstrdup(str); } else { - rettv->vval.v_string = (char *)string_convert(&vimconv, (char_u *)str, NULL); + rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL); } convert_setup(&vimconv, NULL, NULL); @@ -4435,7 +3883,7 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "indent()" function -static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { @@ -4446,7 +3894,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "index()" function -static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long idx = 0; bool ic = false; @@ -4521,23 +3969,20 @@ static bool inputsecret_flag = false; /// "input()" function /// Also handles inputsecret() when inputsecret is set. -static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_input(typval_T *argvars, typval_T *rettv, EvalFuncData 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) +static void f_inputdialog(typval_T *argvars, typval_T *rettv, EvalFuncData 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) +static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int selected; - int mouse_used; - if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listarg), "inputlist()"); return; @@ -4555,7 +4000,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; } @@ -4566,7 +4012,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL }; /// "inputrestore()" function -static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputrestore(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (!GA_EMPTY(&ga_userinput)) { ga_userinput.ga_len--; @@ -4580,7 +4026,7 @@ static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "inputsave()" function -static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputsave(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Add an entry to the stack of typeahead storage. tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); @@ -4588,17 +4034,17 @@ static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "inputsecret()" function -static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { cmdline_star++; inputsecret_flag = true; - f_input(argvars, rettv, NULL); + f_input(argvars, rettv, fptr); cmdline_star--; inputsecret_flag = false; } /// "insert()" function -static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { list_T *l; bool error = false; @@ -4671,28 +4117,27 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "interrupt()" function static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED, - FunPtr fptr FUNC_ATTR_UNUSED) + EvalFuncData fptr FUNC_ATTR_UNUSED) { got_int = true; } /// "invert(expr)" function -static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } /// "isdirectory()" function -static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); + rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); } /// "islocked()" function -static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData 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]), @@ -4705,7 +4150,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 @@ -4732,7 +4177,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "isinf()" function -static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isinf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_FLOAT && xisinf(argvars[0].vval.v_float)) { @@ -4741,14 +4186,14 @@ static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "isnan()" function -static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isnan(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT && xisnan(argvars[0].vval.v_float); } /// "id()" function -static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_id(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); @@ -4758,7 +4203,7 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobpid(id)" function -static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4782,7 +4227,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobresize(job, width, height)" function -static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4849,7 +4294,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en if (!clear_env) { typval_T temp_env = TV_INITIAL_VALUE; - f_environ(NULL, &temp_env, NULL); + f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL }); tv_dict_extend(env, temp_env.vval.v_dict, "force"); tv_dict_free(temp_env.vval.v_dict); @@ -4938,7 +4383,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } /// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -5013,7 +4458,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(cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -5058,7 +4503,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobstop()" function -static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -5091,7 +4536,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobwait(ids[, timeout])" function -static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -5190,7 +4635,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// json_decode() function -static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_json_decode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char numbuf[NUMBUFLEN]; const char *s = NULL; @@ -5224,14 +4669,14 @@ static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// json_encode() function -static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_json_encode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = encode_tv2json(&argvars[0], NULL); } /// "last_buffer_nr()" function. -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = 0; @@ -5245,7 +4690,7 @@ static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "len()" function -static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { switch (argvars[0].v_type) { case VAR_STRING: @@ -5316,19 +4761,19 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) } /// "libcall()" function -static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_libcall(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { libcall_common(argvars, rettv, VAR_STRING); } /// "libcallnr()" function -static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_libcallnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { libcall_common(argvars, rettv, VAR_NUMBER); } /// "line(string, [winid])" function -static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = 0; pos_T *fp = NULL; @@ -5359,7 +4804,7 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "line2byte(lnum)" function -static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_line2byte(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { @@ -5373,7 +4818,7 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "lispindent(lnum)" function -static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_lispindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const pos_T pos = curwin->w_cursor; const linenr_T lnum = tv_get_lnum(argvars); @@ -5387,13 +4832,13 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "localtime()" function -static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_localtime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = (varnumber_T)time(NULL); } /// luaeval() function implementation -static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { const char *const str = tv_get_string_chk(&argvars[0]); @@ -5405,9 +4850,9 @@ 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) +static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - filter_map(argvars, rettv, TRUE); + filter_map(argvars, rettv, true); } static void find_some_match(typval_T *const argvars, typval_T *const rettv, @@ -5417,19 +4862,17 @@ 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; - p_cpo = ""; + char *save_cpo = p_cpo; + p_cpo = empty_option; rettv->vval.v_number = -1; switch (type) { @@ -5455,6 +4898,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; @@ -5563,10 +5007,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; } @@ -5626,31 +5068,31 @@ theend: } /// "match()" function -static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatch); } /// "matchend()" function -static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchEnd); } /// "matchlist()" function -static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchList); } /// "matchstr()" function -static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function -static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchstrpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchStrPos); } @@ -5705,19 +5147,19 @@ 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) +static void f_max(typval_T *argvars, typval_T *rettv, EvalFuncData 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) +static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - max_min(argvars, rettv, FALSE); + max_min(argvars, rettv, false); } /// "mkdir()" function -static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int prot = 0755; // -V536 @@ -5762,7 +5204,7 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "mode()" function -static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[MODE_MAX_LENGTH]; @@ -5779,7 +5221,7 @@ static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "msgpackdump()" function -static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { if (argvars[0].v_type != VAR_LIST) { @@ -5918,7 +5360,7 @@ static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret } /// "msgpackparse" function -static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_msgpackparse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { @@ -5934,7 +5376,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "nextnonblank()" function -static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_nextnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum; @@ -5951,7 +5393,7 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "nr2char()" function -static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { if (!tv_check_num(&argvars[1])) { @@ -5982,14 +5424,14 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "or(expr, expr)" function -static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) | tv_get_number_chk(&argvars[1], NULL); } /// "pathshorten()" function -static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int trim_len = 1; @@ -6011,7 +5453,7 @@ static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "pow()" function -static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -6025,7 +5467,7 @@ static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prevnonblank()" function -static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prevnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(argvars); if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { @@ -6039,19 +5481,18 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "printf()" function -static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_printf(typval_T *argvars, typval_T *rettv, EvalFuncData 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; @@ -6062,15 +5503,14 @@ 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) +static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData 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; } @@ -6086,15 +5526,14 @@ 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) +static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData 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; } @@ -6110,7 +5549,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "prompt_getprompt({buffer})" function -static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { // return an empty string by default, e.g. it's not a prompt buffer @@ -6130,7 +5569,7 @@ static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prompt_setprompt({buffer}, {text})" function -static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -6146,14 +5585,14 @@ static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "pum_getpos()" function -static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pum_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); pum_set_event_info(rettv->vval.v_dict); } /// "pumvisible()" function -static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pumvisible(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (pum_visible()) { rettv->vval.v_number = 1; @@ -6161,7 +5600,7 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "py3eval()" and "pyxeval()" functions (always python3) -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_py3eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("python3", argvars, rettv); } @@ -6233,7 +5672,7 @@ static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *c } /// "rand()" function -static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { uint32_t result; @@ -6244,7 +5683,7 @@ static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) // When no argument is given use the global seed list. if (!initialized) { // Initialize the global seed list. - uint32_t x; + uint32_t x = 0; init_srand(&x); gx = splitmix32(&x); @@ -6303,7 +5742,7 @@ theend: } /// "srand()" function -static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_srand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { uint32_t x = 0; @@ -6325,27 +5764,25 @@ static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "perleval()" function -static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_perleval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("perl", argvars, rettv); } /// "rubyeval()" function -static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rubyeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("ruby", argvars, rettv); } /// "range()" function -static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData 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; @@ -6365,7 +5802,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); } } @@ -6376,8 +5813,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; @@ -6386,11 +5821,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; } @@ -6409,7 +5846,7 @@ theend: } /// "readdir()" function -static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); @@ -6427,17 +5864,16 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData 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) { @@ -6455,7 +5891,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // their own about CR-LF conversion. const char *const fname = tv_get_string(&argvars[0]); - if (os_isdir((const char_u *)fname)) { + if (os_isdir(fname)) { semsg(_(e_isadir2), fname); return; } @@ -6479,7 +5915,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: @@ -6511,9 +5947,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; @@ -6587,10 +6023,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 { @@ -6611,7 +6047,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getreginfo()" function -static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int regname = getreg_get_regname(argvars); if (regname == 0) { @@ -6661,18 +6097,18 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reg_executing()" function -static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_executing(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_executing, rettv); } /// "reg_recording()" function -static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_recording(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_recording, rettv); } -static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_recorded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_recorded, rettv); } @@ -6714,7 +6150,7 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL /// one argument it returns the time passed since the argument. /// With two arguments it returns the time passed between /// the two arguments. -static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { proftime_T res; proftime_T start; @@ -6756,7 +6192,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reltimestr()" function -static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltimestr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { proftime_T tm; @@ -6769,7 +6205,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "remove()" function -static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const arg_errmsg = N_("remove() argument"); @@ -6785,7 +6221,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rename({from}, {to})" function -static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { rettv->vval.v_number = -1; @@ -6797,7 +6233,7 @@ static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "repeat()" function -static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { @@ -6834,7 +6270,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "resolve()" function -static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *fname = tv_get_string(&argvars[0]); @@ -6845,7 +6281,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) v = os_realpath(fname, v); } } - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); + rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); #else # ifdef HAVE_READLINK { @@ -6907,7 +6343,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (*q != NUL) { cpy = remain; remain = (remain - ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) + ? concat_str(q - 1, remain) : xstrdup(q - 1)); xfree(cpy); q[-1] = NUL; @@ -6966,7 +6402,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) && (p[2] == NUL || vim_ispathsep(p[2])))))) { // Prepend "./". - cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); + cpy = concat_str("./", p); xfree(p); p = cpy; } else if (!is_relative_to_current) { @@ -7003,7 +6439,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reverse({list})" function -static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; @@ -7028,7 +6464,7 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reduce(list, { accumulator, element -> value } [, initial])" function -static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { emsg(_(e_listblobreq)); @@ -7139,7 +6575,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]; @@ -7147,6 +6582,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': @@ -7196,26 +6632,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; } @@ -7242,25 +6671,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 (;;) { @@ -7333,7 +6764,7 @@ theend: } /// "rpcnotify()" function -static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcnotify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7368,7 +6799,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rpcrequest()" function -static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7395,8 +6826,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; @@ -7404,16 +6834,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; @@ -7430,8 +6858,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; @@ -7461,12 +6888,12 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) } end: - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); api_clear_error(&err); } /// "rpcstart()" function (DEPRECATED) -static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7533,7 +6960,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rpcstop()" function -static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7551,7 +6978,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) // if called with a job, stop it, else closes the channel uint64_t id = (uint64_t)argvars[0].vval.v_number; if (find_job(id, false)) { - f_jobstop(argvars, rettv, NULL); + f_jobstop(argvars, rettv, fptr); } else { const char *error; rettv->vval.v_number = @@ -7563,16 +6990,15 @@ 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) +static void f_screenattr(typval_T *argvars, typval_T *rettv, EvalFuncData 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 { @@ -7582,16 +7008,15 @@ 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) +static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData 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 { @@ -7601,12 +7026,12 @@ 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) +static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData 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) { @@ -7614,7 +7039,7 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } int pcc[MAX_MCO]; - int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + (size_t)col], pcc); + int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc); int composing_len = 0; while (pcc[composing_len] != 0) { composing_len++; @@ -7629,18 +7054,14 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screencol()" function /// /// First column is 1 to be consistent with virtcol(). -static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screencol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ui_current_col() + 1; } /// "screenpos({winid}, {lnum}, {col})" function -static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData 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; @@ -7649,9 +7070,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); @@ -7661,13 +7086,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenrow()" function -static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenrow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ui_current_row() + 1; } /// "screenstring()" function -static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; @@ -7686,7 +7111,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "search()" function -static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_search(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int flags = 0; @@ -7694,7 +7119,7 @@ static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "searchdecl()" function -static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchdecl(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int locally = 1; int thisblock = 0; @@ -7719,7 +7144,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; @@ -7737,7 +7161,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; } @@ -7790,13 +7214,13 @@ theend: } /// "searchpair()" function -static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpair(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = searchpair_cmn(argvars, NULL); } /// "searchpairpos()" function -static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T match_pos; int lnum = 0; @@ -7831,33 +7255,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; - p_cpo = (char *)empty_option; + char *save_cpo = p_cpo; + p_cpo = 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); @@ -7873,19 +7288,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; @@ -7911,7 +7328,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); @@ -7971,18 +7388,23 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir xfree(pat2); xfree(pat3); - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { // Darn, evaluating the {skip} expression changed the value. - free_string_option((char_u *)save_cpo); + // If it's still empty it was changed and restored, need to restore in + // the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); } return retval; } /// "searchpos()" function -static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T match_pos; int flags = 0; @@ -8002,7 +7424,7 @@ static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverlist()" function -static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { size_t n; char **addrs = server_address_list(&n); @@ -8016,7 +7438,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverstart()" function -static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Address of the new server @@ -8061,7 +7483,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverstop()" function -static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -8081,7 +7503,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setbufline()" function -static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum; buf_T *buf; @@ -8096,17 +7518,17 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Set the cursor or mark position. -/// If 'charpos' is TRUE, then use the column number as a character offset. +/// If 'charpos' is true, then use the column number as a character offset. /// 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; @@ -8133,30 +7555,28 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) } /// "setcharpos()" function -static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_position(argvars, rettv, true); } -static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData 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]; - const int c = utfc_ptr2char(csearch, pcc); + const int c = utfc_ptr2char((char *)csearch, pcc); 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); } @@ -8168,24 +7588,14 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "setcmdpos()" function -static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int pos = (int)tv_get_number(&argvars[0]) - 1; - - if (pos >= 0) { - rettv->vval.v_number = set_cmdline_pos(pos); - } -} - /// "setcursorcharpos" function -static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_cursorpos(argvars, rettv, true); } /// "setenv()" function -static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char namebuf[NUMBUFLEN]; char valbuf[NUMBUFLEN]; @@ -8193,14 +7603,14 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type == VAR_SPECIAL && argvars[1].vval.v_special == kSpecialVarNull) { - os_unsetenv(name); + vim_unsetenv_ext(name); } else { - os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); + vim_setenv_ext(name, tv_get_string_buf(&argvars[1], valbuf)); } } /// "setfperm({fname}, {mode})" function -static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -8231,123 +7641,23 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setline()" function -static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(&argvars[0]); 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) +static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData 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 @@ -8369,19 +7679,17 @@ 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; } /// "setreg()" function -static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData 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. @@ -8401,7 +7709,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; } @@ -8413,7 +7721,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"); @@ -8456,7 +7764,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); } } } @@ -8490,7 +7798,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) { @@ -8517,17 +7825,15 @@ free_lstval: } /// "settagstack()" function -static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData 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; } @@ -8537,7 +7843,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; } @@ -8570,7 +7876,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// f_sha256 - sha256({string}) function -static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_sha256(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *p = tv_get_string(&argvars[0]); const char *hash = sha256_bytes((const uint8_t *)p, strlen(p), NULL, 0); @@ -8581,7 +7887,7 @@ static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "shellescape({string})" function -static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_shellescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const bool do_special = non_zero_arg(&argvars[1]); @@ -8592,14 +7898,12 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// shiftwidth() function -static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData 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 } @@ -8610,7 +7914,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "simplify()" function -static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); rettv->vval.v_string = xstrdup(p); @@ -8619,7 +7923,7 @@ static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sockconnect()" function -static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { emsg(_(e_invarg)); @@ -8671,17 +7975,16 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "stdioopen()" function -static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stdioopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_invarg)); 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; @@ -8706,7 +8009,7 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reltimefloat()" function -static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltimefloat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { proftime_T tm; @@ -8719,7 +8022,7 @@ static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "soundfold({word})" function -static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_soundfold(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *const s = tv_get_string(&argvars[0]); @@ -8727,11 +8030,8 @@ 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) +static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData 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) { @@ -8745,6 +8045,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); @@ -8776,22 +8079,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) +static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData 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) { @@ -8805,8 +8102,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; @@ -8833,17 +8133,15 @@ f_spellsuggest_return: curwin->w_p_spell = wo_spell_save; } -static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData 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; - p_cpo = ""; + char *save_cpo = p_cpo; + p_cpo = empty_option; const char *str = tv_get_string(&argvars[0]); const char *pat = NULL; @@ -8875,6 +8173,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 { @@ -8913,7 +8212,7 @@ theend: } /// "stdpath(type)" function -static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stdpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -8945,7 +8244,7 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2float()" function -static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *p = skipwhite(tv_get_string(&argvars[0])); bool isneg = (*p == '-'); @@ -8961,7 +8260,7 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2list()" function -static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); const char_u *p = (const char_u *)tv_get_string(&argvars[0]); @@ -8972,10 +8271,9 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int base = 10; - varnumber_T n; int what = 0; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -9005,6 +8303,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) { @@ -9015,7 +8314,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strftime({format}[, {time}])" function -static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { time_t seconds; @@ -9035,13 +8334,12 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = xstrdup(_("(Invalid)")); } else { vimconv_T conv; - char_u *enc; conv.vc_type = CONV_NONE; - enc = enc_locale(); + char *enc = (char *)enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { - p = (char *)string_convert(&conv, (char_u *)p, NULL); + p = string_convert(&conv, p, NULL); } char result_buf[256]; if (p != NULL) { @@ -9055,7 +8353,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } convert_setup(&conv, enc, p_enc); if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = (char *)string_convert(&conv, (char_u *)result_buf, NULL); + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); } else { rettv->vval.v_string = xstrdup(result_buf); } @@ -9067,7 +8365,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -9095,7 +8393,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "stridx()" function -static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -9127,20 +8425,20 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "string()" function -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = encode_tv2string(&argvars[0], NULL); } /// "strlen()" function -static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); } /// "strchars()" function -static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string(&argvars[0]); int skipcc = 0; @@ -9163,7 +8461,7 @@ static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strdisplaywidth()" function -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const s = tv_get_string(&argvars[0]); int col = 0; @@ -9172,11 +8470,11 @@ static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) col = (int)tv_get_number(&argvars[1]); } - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); + rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col); } /// "strwidth()" function -static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -9184,7 +8482,7 @@ static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); const size_t slen = STRLEN(p); @@ -9238,7 +8536,7 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strpart()" function -static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; @@ -9284,7 +8582,7 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strptime({format}, {timestring})" function -static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char fmt_buf[NUMBUFLEN]; char str_buf[NUMBUFLEN]; @@ -9298,10 +8596,10 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) vimconv_T conv = { .vc_type = CONV_NONE, }; - char_u *enc = enc_locale(); + char *enc = (char *)enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { - fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL); + fmt = string_convert(&conv, fmt, NULL); } if (fmt == NULL || os_strptime(str, fmt, &tmval) == NULL @@ -9316,7 +8614,7 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strridx()" function -static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; const char *const needle = tv_get_string_chk(&argvars[1]); @@ -9359,14 +8657,14 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strtrans()" function -static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true); } /// "submatch()" function -static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; int no = (int)tv_get_number_chk(&argvars[0], &error); @@ -9397,7 +8695,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "substitute()" function -static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char patbuf[NUMBUFLEN]; char subbuf[NUMBUFLEN]; @@ -9426,14 +8724,14 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "swapinfo(swap_filename)" function -static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); } /// "swapname(expr)" function -static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_swapname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; buf_T *buf = tv_get_buf(&argvars[0], false); @@ -9447,7 +8745,7 @@ static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synID(lnum, col, trans)" function -static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // -1 on type error (both) const linenr_T lnum = tv_get_lnum(argvars); @@ -9466,7 +8764,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synIDattr(id, what [, mode])" function -static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int id = (int)tv_get_number(&argvars[0]); const char *const what = tv_get_string(&argvars[1]); @@ -9553,7 +8851,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synIDtrans(id)" function -static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synIDtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int id = (int)tv_get_number(&argvars[0]); @@ -9567,7 +8865,7 @@ static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synconcealed(lnum, col)" function -static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int syntax_flags = 0; int cchar; @@ -9580,7 +8878,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) { @@ -9609,7 +8907,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synstack(lnum, col)" function -static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_set_ret(rettv, NULL); @@ -9633,18 +8931,18 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// f_system - the VimL system() function -static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, false); } -static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_systemlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, true); } /// "tabpagebuflist()" function -static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = NULL; @@ -9666,7 +8964,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "tabpagenr()" function -static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tabpagenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int nr = 1; @@ -9691,11 +8989,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); @@ -9710,20 +9006,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; @@ -9740,21 +9036,21 @@ 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; } /// "tabpagewinnr()" function -static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int nr = 1; tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); @@ -9767,16 +9063,14 @@ 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) +static void f_tagfiles(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char *fname; - tagname_T tn; - tv_list_alloc_ret(rettv, kListLenUnknown); - fname = xmalloc(MAXPATHL); + char *fname = xmalloc(MAXPATHL); bool first = true; - while (get_tagfname(&tn, first, (char_u *)fname) == OK) { + tagname_T tn; + while (get_tagfname(&tn, first, fname) == OK) { tv_list_append_string(rettv->vval.v_list, fname, -1); first = false; } @@ -9786,7 +9080,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "taglist()" function -static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const tag_pattern = tv_get_string(&argvars[0]); @@ -9804,14 +9098,14 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "tempname()" function -static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char *)vim_tempname(); } /// "termopen(cmd[, cwd])" function -static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -9854,7 +9148,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(cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -9934,7 +9228,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_info([timer])" function -static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { @@ -9952,7 +9246,7 @@ static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_pause(timer, paused)" function -static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); @@ -9972,10 +9266,9 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) } /// "timer_start(timeout, callback, opts)" function -static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int repeat = 1; - dict_T *dict; rettv->vval.v_number = -1; if (check_secure()) { @@ -9983,8 +9276,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; } @@ -10005,7 +9298,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_stop(timerid)" function -static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); @@ -10020,27 +9313,27 @@ static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) timer_stop(timer); } -static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { timer_stop_all(); } /// "tolower(string)" function -static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false); } /// "toupper(string)" function -static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true); } /// "tr(string, fromstr, tostr)" function -static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -10119,16 +9412,14 @@ error: } /// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf1[NUMBUFLEN]; 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; @@ -10137,6 +9428,11 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) { + semsg(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + if (argvars[1].v_type == VAR_STRING) { mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); if (argvars[2].v_type != VAR_UNKNOWN) { @@ -10153,6 +9449,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) { @@ -10175,7 +9472,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) { @@ -10202,7 +9499,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "type(expr)" function -static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = -1; @@ -10234,7 +9531,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "undofile(name)" function -static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *const fname = tv_get_string(&argvars[0]); @@ -10253,7 +9550,7 @@ static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "undotree()" function -static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -10271,13 +9568,12 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "virtcol(string)" function -static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData 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. @@ -10290,14 +9586,14 @@ 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; } /// "visualmode()" function -static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_visualmode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char_u str[2]; @@ -10313,28 +9609,28 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "wildmenumode()" function -static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wildmenumode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (wild_menu_showing || ((State & MODE_CMDLINE) && pum_visible())) { + if (wild_menu_showing || ((State & MODE_CMDLINE) && cmdline_pum_active())) { rettv->vval.v_number = 1; } } /// "win_findbuf()" function -static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_findbuf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); win_findbuf(argvars, rettv->vval.v_list); } /// "win_getid()" function -static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = win_getid(argvars); } /// "win_gettype(nr)" function -static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = curwin; @@ -10361,43 +9657,52 @@ static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "win_gotoid()" function -static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->vval.v_number = win_gotoid(argvars); + int id = (int)tv_get_number(&argvars[0]); + + if (cmdwin_type != 0) { + emsg(_(e_cmdwin)); + return; + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->handle == id) { + goto_tabpage_win(tp, wp); + rettv->vval.v_number = 1; + return; + } + } } /// "win_id2tabwin()" function -static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_id2tabwin(argvars, rettv); } /// "win_id2win()" function -static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_id2win(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = win_id2win(argvars); } /// "win_move_separator()" function -static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_move_separator(typval_T *argvars, typval_T *rettv, EvalFuncData 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; } /// "win_move_statusline()" function -static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp; int offset; @@ -10415,7 +9720,7 @@ static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "winbufnr(nr)" function -static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winbufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL) { @@ -10426,25 +9731,25 @@ static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "wincol()" function -static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wincol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { validate_cursor(); rettv->vval.v_number = curwin->w_wcol + 1; } /// "winheight(nr)" function -static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winheight(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL) { rettv->vval.v_number = -1; } else { - rettv->vval.v_number = wp->w_height; + rettv->vval.v_number = wp->w_height_inner; } } /// "winlayout()" function -static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tabpage_T *tp; @@ -10463,27 +9768,24 @@ static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "winline()" function -static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { validate_cursor(); rettv->vval.v_number = curwin->w_wrow + 1; } /// "winnr()" function -static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winnr(typval_T *argvars, typval_T *rettv, EvalFuncData 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) +static void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData 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. @@ -10506,12 +9808,11 @@ 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) +static void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData 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; @@ -10557,12 +9858,10 @@ 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) +static void f_winsaveview(typval_T *argvars, typval_T *rettv, EvalFuncData 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); @@ -10577,32 +9876,32 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "winwidth(nr)" function -static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_winwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = find_win_by_nr_or_id(&argvars[0]); if (wp == NULL) { rettv->vval.v_number = -1; } else { - rettv->vval.v_number = wp->w_width; + rettv->vval.v_number = wp->w_width_inner; } } /// "windowsversion()" function -static void f_windowsversion(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_windowsversion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = xstrdup(windowsVersion); } /// "wordcount()" function -static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); cursor_pos_info(rettv->vval.v_dict); } /// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -10680,7 +9979,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "xor(expr, expr)" function -static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) ^ tv_get_number_chk(&argvars[1], NULL); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index 583ee0e75e..adff0b2441 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -1,11 +1,12 @@ #ifndef NVIM_EVAL_FUNCS_H #define NVIM_EVAL_FUNCS_H +#include "nvim/api/private/dispatch.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" /// Prototype of C function that implements VimL function -typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); +typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data); /// Special flags for base_arg @see EvalFuncDef #define BASE_NONE 0 ///< Not a method (no base argument). @@ -13,13 +14,13 @@ typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); /// Structure holding VimL function definition typedef struct { - char *name; ///< Name of the function. - uint8_t min_argc; ///< Minimal number of arguments. - uint8_t max_argc; ///< Maximal number of arguments. - uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. - bool fast; ///< Can be run in |api-fast| events - VimLFunc func; ///< Function implementation. - FunPtr data; ///< Userdata for function implementation. + char *name; ///< Name of the function. + uint8_t min_argc; ///< Minimal number of arguments. + uint8_t max_argc; ///< Maximal number of arguments. + uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. + bool fast; ///< Can be run in |api-fast| events + VimLFunc func; ///< Function implementation. + EvalFuncData data; ///< Userdata for function implementation. } EvalFuncDef; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ff1808ed91..d5b2b1f2ae 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"); } @@ -831,7 +831,7 @@ int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) } /// "join()" function -void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_join(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST) { emsg(_(e_listreq)); @@ -855,7 +855,7 @@ void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "list2str()" function -void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_list2str(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -1247,15 +1247,15 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) ; li != NULL;) { listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - emsg(_("E882: Uniq compare function failed")); - break; - } li = tv_list_item_remove(l, li); } else { idx++; li = TV_LIST_ITEM_NEXT(l, li); } + if (info.item_compare_func_err) { // -V547 + emsg(_("E882: Uniq compare function failed")); + break; + } } } @@ -1267,13 +1267,13 @@ theend: } /// "sort"({list})" function -void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, true); } /// "uniq({list})" function -void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_uniq(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, false); } @@ -2533,7 +2533,7 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool new_di = tv_dict_item_alloc((const char *)di->di_key); } else { size_t len = STRLEN(di->di_key); - char *const key = (char *)string_convert(conv, di->di_key, &len); + char *const key = (char *)string_convert(conv, (char *)di->di_key, &len); if (key == NULL) { new_di = tv_dict_item_alloc_len((const char *)di->di_key, len); } else { @@ -2806,25 +2806,25 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi } /// "items(dict)" function -void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 2); } /// "keys()" function -void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_keys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 0); } /// "values(dict)" function -void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 1); } /// "has_key()" function -void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_dictreq)); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c02351947b..8177d01f90 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" @@ -26,9 +25,6 @@ typedef int64_t varnumber_T; typedef uint64_t uvarnumber_T; -/// Type used for VimL VAR_FLOAT values -typedef double float_T; - /// Refcount for dict or list that should not be freed enum { DO_NOT_FREE_CNT = (INT_MAX / 2), }; @@ -356,6 +352,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..4be922a055 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; @@ -391,7 +409,7 @@ void emsg_funcname(char *ermsg, const char_u *name) char_u *p; if (*name == K_SPECIAL) { - p = concat_str((char_u *)"<SNR>", name + 3); + p = (char_u *)concat_str("<SNR>", (char *)name + 3); } else { p = (char_u *)name; } @@ -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,14 +845,14 @@ 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()) { saveRedobuff(&save_redo); did_save_redo = true; } - ++fp->uf_calls; + fp->uf_calls++; // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure @@ -848,7 +863,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett fc->rettv = rettv; fc->level = ex_nesting_level; // Check if this function has a breakpoint. - fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0); + fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; // Set up fields for closure. @@ -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; @@ -878,7 +893,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett v->di_tv.v_type = VAR_DICT; v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; - ++selfdict->dv_refcount; + selfdict->dv_refcount++; } // Init a: variables, unless none found (in lambda). @@ -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,80 +1009,56 @@ 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; bool func_not_yet_profiling_but_should = do_profiling_yes - && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); + && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL); if (func_not_yet_profiling_but_should) { started_profiling = true; @@ -1080,7 +1071,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett || (fc->caller != NULL && fc->caller->func->uf_profiling)); if (func_or_func_caller_profiling) { - ++fp->uf_tm_count; + fp->uf_tm_count++; call_start = profile_start(); fp->uf_tm_children = profile_zero(); } @@ -1092,17 +1083,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett const sctx_T save_current_sctx = current_sctx; current_sctx = fp->uf_script_ctx; save_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; 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,13 +1878,15 @@ 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); return name; } +#define MAX_FUNC_NESTING 50 + /// ":function" void ex_function(exarg_T *eap) { @@ -1939,11 +1929,11 @@ void ex_function(exarg_T *eap) if (ends_excmd(*eap->arg)) { if (!eap->skip) { todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + 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)) { + if (message_filtered((char *)fp->uf_name)) { continue; } if (!func_name_refcount(fp->uf_name)) { @@ -1960,7 +1950,7 @@ void ex_function(exarg_T *eap) * ":function /pat": list functions matching pattern. */ if (*eap->arg == '/') { - p = skip_regexp((char_u *)eap->arg + 1, '/', true, NULL); + p = (char_u *)skip_regexp(eap->arg + 1, '/', true, NULL); if (!eap->skip) { regmatch_T regmatch; @@ -1972,9 +1962,9 @@ void ex_function(exarg_T *eap) regmatch.rm_ic = p_ic; todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + 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 +1976,7 @@ void ex_function(exarg_T *eap) } } if (*p == '/') { - ++p; + p++; } eap->nextcmd = (char *)check_nextcmd(p); return; @@ -2007,7 +1997,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) { /* @@ -2022,14 +2012,14 @@ void ex_function(exarg_T *eap) xfree(fudi.fd_newkey); return; } else { - eap->skip = TRUE; + eap->skip = true; } } // An error in a function call during evaluation of an expression in magic // braces should not cause the function not to be defined. saved_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; // // ":function func" with only function name: list function. @@ -2064,7 +2054,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 +2098,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 +2111,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 +2181,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 +2223,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,10 +2304,14 @@ 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; + if (nesting == MAX_FUNC_NESTING - 1) { + emsg(_("E1058: function nesting too deep")); + } else { + nesting++; + indent += 2; + } } } @@ -2339,7 +2332,7 @@ void ex_function(exarg_T *eap) } // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. - arg = (char_u *)skipwhite((char *)skiptowhite(p)); + arg = (char_u *)skipwhite(skiptowhite((char *)p)); if (arg[0] == '<' && arg[1] == '<' && ((p[0] == 'p' && p[1] == 'y' && (!ASCII_ISALNUM(p[2]) || p[2] == 't' @@ -2366,12 +2359,12 @@ void ex_function(exarg_T *eap) // Check for ":let v =<< [trim] EOF" // and ":let [a, b] =<< [trim] EOF" - arg = (char_u *)skipwhite((char *)skiptowhite(p)); + arg = (char_u *)skipwhite(skiptowhite((char *)p)); if (*arg == '[') { arg = (char_u *)vim_strchr((char *)arg, ']'); } if (arg != NULL) { - arg = (char_u *)skipwhite((char *)skiptowhite(arg)); + arg = (char_u *)skipwhite(skiptowhite((char *)arg)); if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<' @@ -2386,7 +2379,7 @@ void ex_function(exarg_T *eap) heredoc_trimmed = vim_strnsave(theline, (size_t)((char_u *)skipwhite((char *)theline) - theline)); } - skip_until = vim_strnsave(p, (size_t)(skiptowhite(p) - p)); + skip_until = vim_strnsave(p, (size_t)((char_u *)skiptowhite((char *)p) - p)); do_concat = false; is_heredoc = true; } @@ -2400,12 +2393,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 +2447,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 +2491,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 +2531,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 +2623,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 +2650,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 +2687,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) { @@ -2859,7 +2853,7 @@ void ex_return(exarg_T *eap) { char_u *arg = (char_u *)eap->arg; typval_T rettv; - int returning = FALSE; + int returning = false; if (current_funccal == NULL) { emsg(_("E133: :return not inside a function")); @@ -2867,7 +2861,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - ++emsg_skip; + emsg_skip++; } eap->nextcmd = NULL; @@ -2899,7 +2893,7 @@ void ex_return(exarg_T *eap) } if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -2932,7 +2926,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 +2981,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; } @@ -3016,7 +3010,7 @@ void ex_call(exarg_T *eap) // When inside :try we need to check for following "| catch" or "| endtry". // Not when there was an error, but do check if an exception was thrown. - if ((!aborting() || current_exception != NULL) && (!failed || eap->cstack->cs_trylevel > 0)) { + if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0)) { // Check for trailing illegal characters and a following command. if (!ends_excmd(*arg)) { if (!failed && !aborting()) { @@ -3041,8 +3035,8 @@ end: /// @param is_cmd set when called due to a ":return" command. /// @param rettv may point to a typval_T with the return rettv. /// -/// @return TRUE when the return can be carried out, -/// FALSE when the return gets pending. +/// @return true when the return can be carried out, +/// false when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { int idx; @@ -3092,7 +3086,7 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) } report_make_pending(CSTP_RETURN, rettv); } else { - current_funccal->returned = TRUE; + current_funccal->returned = true; // If the return is carried out now, store the return value. For // a return immediately after reanimation, the value is already @@ -3111,16 +3105,16 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) /// Generate a return command for producing the value of "rettv". The result /// is an allocated string. Used by report_pending() for verbose messages. -char_u *get_return_cmd(void *rettv) +char *get_return_cmd(void *rettv) { - char_u *s = NULL; - char_u *tofree = NULL; + char *s = NULL; + char *tofree = NULL; if (rettv != NULL) { - tofree = s = (char_u *)encode_tv2echo((typval_T *)rettv, NULL); + tofree = s = encode_tv2echo((typval_T *)rettv, NULL); } if (s == NULL) { - s = (char_u *)""; + s = ""; } STRCPY(IObuff, ":return "); @@ -3129,7 +3123,7 @@ char_u *get_return_cmd(void *rettv) STRCPY(IObuff + IOSIZE - 4, "..."); } xfree(tofree); - return vim_strsave(IObuff); + return (char *)vim_strsave(IObuff); } /// Get next function line. @@ -3140,13 +3134,12 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; ufunc_T *fp = fcp->func; - char_u *retval; + char *retval; garray_T *gap; // growarray with function lines // 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, (char *)fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -3160,14 +3153,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 = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); + SOURCING_LNUM = fcp->linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); } @@ -3175,18 +3168,17 @@ 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((char *)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, (char *)fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } - return (char *)retval; + return retval; } -/// @return TRUE if the currently active function should be ended, because a +/// @return true if the currently active function should be ended, because a /// return was encountered or an error occurred. Used inside a ":while". int func_has_ended(void *cookie) { @@ -3198,7 +3190,7 @@ int func_has_ended(void *cookie) || fcp->returned; } -/// @return TRUE if cookie indicates a function which "abort"s on errors. +/// @return true if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; @@ -3289,7 +3281,7 @@ int func_level(void *cookie) return ((funccall_T *)cookie)->level; } -/// @return TRUE when a function was ended by a ":return" command. +/// @return true when a function was ended by a ":return" command. int current_func_returned(void) { return current_funccal->returned; 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 cf2755f639..75410d40d8 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" @@ -78,7 +81,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // The marker is the next word. if (*cmd != NUL && *cmd != '"') { marker = skipwhite(cmd); - p = (char *)skiptowhite((char_u *)marker); + p = skiptowhite(marker); if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { semsg(_(e_trailing_arg), p); return NULL; @@ -410,7 +413,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs // apply :filter /pat/ to variable name xstrlcpy(buf, prefix, IOSIZE); xstrlcat(buf, (char *)di->di_key, IOSIZE); - if (message_filtered((char_u *)buf)) { + if (message_filtered(buf)) { continue; } @@ -584,21 +587,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *s = vim_getenv(name); if (s != NULL) { - tofree = (char *)concat_str((const char_u *)s, (const char_u *)p); + tofree = concat_str(s, p); p = (const char *)tofree; xfree(s); } } if (p != NULL) { - os_setenv(name, p, 1); - if (STRICMP(name, "HOME") == 0) { - init_homedir(); - } else if (didset_vim && STRICMP(name, "VIM") == 0) { - didset_vim = false; - } else if (didset_vimruntime - && STRICMP(name, "VIMRUNTIME") == 0) { - didset_vimruntime = false; - } + vim_setenv_ext(name, p); arg_end = arg; } name[len] = c1; @@ -668,8 +663,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo } else if (opt_type == gov_string && stringval != NULL && s != NULL) { // string char *const oldstringval = stringval; - stringval = (char *)concat_str((const char_u *)stringval, - (const char_u *)s); + stringval = concat_str(stringval, s); xfree(oldstringval); s = stringval; } @@ -710,7 +704,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (p != NULL && op != NULL && *op == '.') { s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); if (s != NULL) { - ptofree = (char *)concat_str((char_u *)s, (const char_u *)p); + ptofree = concat_str(s, p); p = (const char *)ptofree; xfree(s); } @@ -796,6 +790,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca semsg(_(e_invarg2), arg - 1); return; } + assert(*lv.ll_name == '$'); // suppress clang "Uninitialized argument value" if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) { error = true; } @@ -856,7 +851,7 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ // Environment variable, normal name or expanded name. if (*lp->ll_name == '$') { - os_unsetenv(lp->ll_name + 1); + vim_unsetenv_ext(lp->ll_name + 1); } else if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) { ret = FAIL; } @@ -1123,7 +1118,7 @@ void vars_clear(hashtab_T *ht) vars_clear_ext(ht, true); } -/// Like vars_clear(), but only free the value if "free_val" is TRUE. +/// Like vars_clear(), but only free the value if "free_val" is true. void vars_clear_ext(hashtab_T *ht, int free_val) { int todo; @@ -1305,7 +1300,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); } else if (strcmp(varname, "hlsearch") == 0) { no_hlsearch = !v->di_tv.vval.v_number; - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } return; } else if (v->di_tv.v_type != tv->v_type) { @@ -1632,7 +1627,7 @@ static void set_option_from_tv(const char *varname, typval_T *varp) strval = tv_get_string_buf_chk(varp, nbuf); } if (!error && strval != NULL) { - set_option_value(varname, numval, strval, OPT_LOCAL); + set_option_value_give_err(varname, numval, strval, OPT_LOCAL); } } @@ -1707,7 +1702,7 @@ bool var_exists(const char *var) } /// "gettabvar()" function -void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_gettabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const varname = tv_get_string_chk(&argvars[1]); tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); @@ -1721,19 +1716,19 @@ void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettabwinvar()" function -void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_gettabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getwinvar(argvars, rettv, 1); } /// "getwinvar()" function -void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getwinvar(argvars, rettv, 0); } /// "getbufvar()" function -void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const varname = tv_get_string_chk(&argvars[1]); buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -1742,7 +1737,7 @@ void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "settabvar()" function -void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_settabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -1773,19 +1768,19 @@ void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "settabwinvar()" function -void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_settabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { setwinvar(argvars, rettv, 1); } /// "setwinvar()" function -void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { setwinvar(argvars, rettv, 0); } /// "setbufvar()" function -void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure() || !tv_check_str_or_nr(&argvars[0])) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 9d1ac185d4..e672b80d69 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" @@ -52,6 +56,7 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" @@ -59,9 +64,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" @@ -115,7 +120,7 @@ void do_ascii(const exarg_T *const eap) { char *dig; int cc[MAX_MCO]; - int c = utfc_ptr2char(get_cursor_pos_ptr(), cc); + int c = utfc_ptr2char((char *)get_cursor_pos_ptr(), cc); if (c == NUL) { msg("NUL"); return; @@ -269,7 +274,7 @@ void ex_align(exarg_T *eap) if (eap->cmdidx == CMD_left) { // left align new_indent = indent; } else { - has_tab = FALSE; // avoid uninit warnings + has_tab = false; // avoid uninit warnings len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent(); @@ -300,7 +305,7 @@ void ex_align(exarg_T *eap) new_indent--; break; } - --new_indent; + new_indent--; } } } @@ -399,7 +404,7 @@ static int sort_compare(const void *s1, const void *s2) } fast_breakcheck(); if (got_int) { - sort_abort = TRUE; + sort_abort = true; } // When sorting numbers "start_col_nr" is the number, not the column @@ -508,7 +513,7 @@ void ex_sort(exarg_T *eap) eap->nextcmd = (char *)check_nextcmd((char_u *)p); break; } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { - s = (char *)skip_regexp((char_u *)p + 1, *p, true, NULL); + s = skip_regexp(p + 1, *p, true, NULL); if (*s != *p) { emsg(_(e_invalpat)); goto sortend; @@ -581,11 +586,11 @@ void ex_sort(exarg_T *eap) p = s + start_col; if (sort_nr) { if (sort_what & STR2NR_HEX) { - s = (char *)skiptohex((char_u *)p); + s = skiptohex(p); } else if (sort_what & STR2NR_BIN) { s = (char *)skiptobin(p); } else { - s = (char *)skiptodigit((char_u *)p); + s = skiptodigit(p); } if (s > p && s[-1] == '-') { s--; // include preceding negative sign @@ -673,7 +678,7 @@ void ex_sort(exarg_T *eap) // delete the original lines if appending worked if (i == count) { - for (i = 0; i < count; ++i) { + for (i = 0; i < count; i++) { ml_delete(eap->line1, false); } } else { @@ -738,7 +743,7 @@ void ex_retab(exarg_T *eap) curwin->w_p_list = 0; // don't want list mode here new_ts_str = eap->arg; - if (!tabstop_set((char_u *)eap->arg, &new_vts_array)) { + if (!tabstop_set(eap->arg, &new_vts_array)) { return; } while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { @@ -836,7 +841,7 @@ void ex_retab(exarg_T *eap) if (ptr[col] == NUL) { break; } - vcol += win_chartabsize(curwin, (char_u *)ptr + col, (colnr_T)vcol); + vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); if (vcol >= MAXCOL) { emsg(_(e_resulting_text_too_long)); break; @@ -862,7 +867,7 @@ void ex_retab(exarg_T *eap) && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { // not changed } else { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } if (first_line != 0) { changed_lines(first_line, 0, last_line + 1, 0L, true); @@ -1091,14 +1096,14 @@ 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; + curwin->w_cursor.lnum++; } appended_lines_mark(n, count); @@ -1145,7 +1150,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out } if (addr_count == 0) { // :! - msg_scroll = FALSE; // don't scroll here + msg_scroll = false; // don't scroll here autowrite_all(); msg_scroll = scroll_save; } @@ -1198,7 +1203,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out break; } } - ++p; + p++; } } while (trailarg != NULL); @@ -1342,7 +1347,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b msg_putchar('\n'); // Keep message from buf_write(). no_wait_return--; if (!aborting()) { - semsg(_("E482: Can't create file %s"), itmp); // Will call wait_return. + // will call wait_return() + semsg(_("E482: Can't create file %s"), itmp); } goto filterend; } @@ -1363,7 +1369,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b xfree(cmd_buf); goto error; } - redraw_curbuf_later(VALID); + redraw_curbuf_later(UPD_VALID); } read_linecount = curbuf->b_ml.ml_line_count; @@ -1371,14 +1377,14 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b call_shell((char_u *)cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL); xfree(cmd_buf); - did_check_timestamps = FALSE; - need_check_timestamps = TRUE; + did_check_timestamps = false; + need_check_timestamps = true; // When interrupting the shell command, it may still have produced some // useful output. Reset got_int here, so that readfile() won't cancel // reading. os_breakcheck(); - got_int = FALSE; + got_int = false; if (do_out) { if (otmp != NULL) { @@ -1442,7 +1448,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 +1466,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: @@ -1651,7 +1657,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp) } #endif if (otmp != NULL) { - append_redir(buf, len, (char *)p_srr, otmp); + append_redir(buf, len, p_srr, otmp); } return buf; } @@ -1704,12 +1710,12 @@ void print_line(linenr_T lnum, int use_number, int list) int save_silent = silent_mode; // apply :filter /pat/ - if (message_filtered(ml_get(lnum))) { + if (message_filtered((char *)ml_get(lnum))) { return; } msg_start(); - silent_mode = FALSE; + silent_mode = false; info_message = true; // use mch_msg(), not mch_errmsg() print_line_no_prefix(lnum, use_number, list); if (save_silent) { @@ -1817,7 +1823,7 @@ void ex_write(exarg_T *eap) } /// write current buffer to file 'eap->arg' -/// if 'eap->append' is TRUE, append to the file +/// if 'eap->append' is true, append to the file /// /// if *eap->arg == NUL write to current file /// @@ -1842,7 +1848,7 @@ int do_write(exarg_T *eap) emsg(_(e_argreq)); goto theend; } - other = FALSE; + other = false; } else { fname = ffname; free_fname = fix_fname(ffname); @@ -1875,8 +1881,7 @@ int do_write(exarg_T *eap) // Writing to the current file is not allowed in readonly mode // and a file name is required. // "nofile" and "nowrite" buffers cannot be written implicitly either. - if (!other && (bt_dontwrite_msg(curbuf) - || check_fname() == FAIL + if (!other && (bt_dontwrite_msg(curbuf) || check_fname() == FAIL || check_readonly(&eap->forceit, curbuf))) { goto theend; } @@ -1895,7 +1900,7 @@ int do_write(exarg_T *eap) (char_u *)_("Write partial file?"), 2) != VIM_YES) { goto theend; } - eap->forceit = TRUE; + eap->forceit = true; } else { emsg(_("E140: Use ! to write partial buffer")); goto theend; @@ -1932,8 +1937,8 @@ int do_write(exarg_T *eap) apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, alt_buf); if (!alt_buf->b_p_bl) { - alt_buf->b_p_bl = TRUE; - apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, alt_buf); + alt_buf->b_p_bl = true; + apply_autocmds(EVENT_BUFADD, NULL, NULL, false, alt_buf); } if (curbuf != was_curbuf || aborting()) { // buffer changed, don't write the file @@ -1961,7 +1966,7 @@ int do_write(exarg_T *eap) // After ":saveas fname" reset 'readonly'. if (eap->cmdidx == CMD_saveas) { if (retval == OK) { - curbuf->b_p_ro = FALSE; + curbuf->b_p_ro = false; redraw_tabline = true; } } @@ -1991,17 +1996,22 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth { // Write to another file or b_flags set or not writing the whole file: // overwriting only allowed with '!' + // If "other" is false and bt_nofilename(buf) is true, this must be + // writing an "acwrite" buffer to the same file as its b_ffname, and + // buf_write() will only allow writing with BufWriteCmd autocommands, + // so there is no need for an overwrite check. if ((other - || (buf->b_flags & BF_NOTEDITED) - || ((buf->b_flags & BF_NEW) - && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) - || (buf->b_flags & BF_READERR)) + || (!bt_nofilename(buf) + && ((buf->b_flags & BF_NOTEDITED) + || ((buf->b_flags & BF_NEW) + && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) + || (buf->b_flags & BF_READERR)))) && !p_wa && os_path_exists((char_u *)ffname)) { if (!eap->forceit && !eap->append) { #ifdef UNIX // It is possible to open a directory on Unix. - if (os_isdir((char_u *)ffname)) { + if (os_isdir(ffname)) { semsg(_(e_isadir2), ffname); return FAIL; } @@ -2013,7 +2023,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) != VIM_YES) { return FAIL; } - eap->forceit = TRUE; + eap->forceit = true; } else { emsg(_(e_exists)); return FAIL; @@ -2036,7 +2046,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth STRCPY(dir, "."); } else { dir = xmalloc(MAXPATHL); - p = (char *)p_dir; + p = p_dir; copy_option_part(&p, dir, MAXPATHL, ","); } swapname = (char *)makeswapname((char_u *)fname, (char_u *)ffname, curbuf, (char_u *)dir); @@ -2053,7 +2063,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth xfree(swapname); return FAIL; } - eap->forceit = TRUE; + eap->forceit = true; } else { semsg(_("E768: Swap file exists: %s (:silent! overrides)"), swapname); @@ -2111,7 +2121,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) { @@ -2154,7 +2164,7 @@ bool not_writing(void) } /// Check if a buffer is read-only (either 'readonly' option is set or file is -/// read-only). Ask for overruling in a dialog. Return TRUE and give an error +/// read-only). Ask for overruling in a dialog. Return true and give an error /// message when the buffer is readonly. static int check_readonly(int *forceit, buf_T *buf) { @@ -2179,10 +2189,10 @@ static int check_readonly(int *forceit, buf_T *buf) if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) == VIM_YES) { // Set forceit, to force the writing of a readonly file - *forceit = TRUE; - return FALSE; + *forceit = true; + return false; } else { - return TRUE; + return true; } } else if (buf->b_p_ro) { emsg(_(e_readonly)); @@ -2190,10 +2200,10 @@ static int check_readonly(int *forceit, buf_T *buf) semsg(_("E505: \"%s\" is read-only (add ! to override)"), buf->b_fname); } - return TRUE; + return true; } - return FALSE; + return false; } /// Try to abandon the current file and edit a new or existing file. @@ -2246,7 +2256,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(); @@ -2287,7 +2297,7 @@ theend: /// ECMD_LASTL: use last position in loaded file /// ECMD_LAST: use last position in all files /// ECMD_ONE: use first line -/// @param flags ECMD_HIDE: if TRUE don't free the current buffer +/// @param flags ECMD_HIDE: if true don't free the current buffer /// ECMD_SET_HELP: set b_help flag of (new) buffer before /// opening file /// ECMD_OLDBUF: use existing buffer if it exists @@ -2305,7 +2315,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum win_T *oldwin) { bool other_file; // true if editing another file - int oldbuf; // TRUE if using existing buffer + int oldbuf; // true if using existing buffer bool auto_buf = false; // true if autocommands brought us // into the buffer unexpectedly char *new_name = NULL; @@ -2381,7 +2391,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // If the file was changed we may not be allowed to abandon it: // - if we are going to re-edit the same file - // - or if we are the only window on this file and if ECMD_HIDE is FALSE + // - or if we are the only window on this file and if ECMD_HIDE is false if (((!other_file && !(flags & ECMD_OLDBUF)) || (curbuf->b_nwindows == 1 && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF)))) @@ -2499,7 +2509,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum /* * Make the (new) buffer the one used by the current window. - * If the old buffer becomes unused, free it if ECMD_HIDE is FALSE. + * If the old buffer becomes unused, free it if ECMD_HIDE is false. * If the current buffer was empty and has no file name, curbuf * is returned by buflist_new(), nothing to do here. */ @@ -2596,11 +2606,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curwin->w_buffer = buf; curbuf = buf; - ++curbuf->b_nwindows; + curbuf->b_nwindows++; // Set 'fileformat', 'binary' and 'fenc' when forced. if (!oldbuf && eap != NULL) { - set_file_options(TRUE, eap); + set_file_options(true, eap); set_forced_fenc(eap); } } @@ -2636,7 +2646,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum } else if (!curbuf->b_help) { // Don't make a buffer listed if it's a help buffer. Useful when using // CTRL-O to go back to a help file. - set_buflisted(TRUE); + set_buflisted(true); } // If autocommands change buffers under our fingers, forget about @@ -2655,10 +2665,10 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum /* * other_file oldbuf - * FALSE FALSE re-edit same file, buffer is re-used - * FALSE TRUE re-edit same file, nothing changes - * TRUE FALSE start editing new file, new buffer - * TRUE TRUE start editing in existing buffer (nothing to do) + * false false re-edit same file, buffer is re-used + * false true re-edit same file, nothing changes + * true false start editing new file, new buffer + * true true start editing in existing buffer (nothing to do) */ if (!other_file && !oldbuf) { // re-use the buffer set_last_cursor(curwin); // may set b_last_cursor @@ -2785,7 +2795,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // changed by the user. do_modelines(OPT_WINONLY); - apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); if ((flags & ECMD_NOWINENTER) == 0) { apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, @@ -2843,7 +2853,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curwin->w_cursor.col = solcol; check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } else { beginline(BL_SOL | BL_FIX); } @@ -2869,7 +2879,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Obey the 'O' flag in 'cpoptions': overwrite any previous file // message. if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { - msg_scroll = FALSE; + msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg check_for_delay(false); @@ -2905,7 +2915,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum update_topline(curwin); curwin->w_scbind_pos = curwin->w_topline; *so_ptr = n; - redraw_curbuf_later(NOT_VALID); // redraw this buffer later + redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later } // Change directories when the 'acd' option is set. @@ -2959,7 +2969,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 @@ -3013,9 +3023,9 @@ void ex_append(exarg_T *eap) // Look for the "." after automatic indent. vcol = 0; - for (p = theline; indent > vcol; ++p) { + for (p = theline; indent > vcol; p++) { if (*p == ' ') { - ++vcol; + vcol++; } else if (*p == TAB) { vcol += 8 - vcol % 8; } else { @@ -3044,7 +3054,7 @@ void ex_append(exarg_T *eap) } xfree(theline); - ++lnum; + lnum++; if (empty) { ml_delete(2L, false); @@ -3093,7 +3103,7 @@ void ex_change(exarg_T *eap) append_indent = get_indent_lnum(eap->line1); } - for (lnum = eap->line2; lnum >= eap->line1; --lnum) { + for (lnum = eap->line2; lnum >= eap->line1; lnum--) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete break; } @@ -3136,10 +3146,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 +3206,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 +3353,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 +3552,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 = skip_regexp(cmd, delimiter, p_magic, &eap->arg); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; @@ -3798,7 +3809,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // Save the line number of the last change for the final // cursor position (just like Vi). curwin->w_cursor.lnum = lnum; - do_again = FALSE; + do_again = false; /* * 1. Match empty string does not count, except for first @@ -3934,8 +3945,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // what matches. Temporarily replace the line // and change it back afterwards. orig_line = (char *)vim_strsave(ml_get(lnum)); - char *new_line = (char *)concat_str((char_u *)new_start, - (char_u *)sub_firstline + copycol); + char *new_line = concat_str(new_start, sub_firstline + copycol); // Position the cursor relative to the end of the line, the // previous substitute may have inserted or deleted characters @@ -3949,13 +3959,13 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T - regmatch.startpos[0].lnum; search_match_endcol = regmatch.endpos[0].col + len_change; - highlight_match = TRUE; + highlight_match = true; update_topline(curwin); validate_cursor(); - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); highlight_match = false; - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); curwin->w_p_fen = save_p_fen; if (msg_row == Rows - 1) { @@ -3971,7 +3981,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); msg_no_more = false; msg_scroll = (int)i; - showruler(true); + show_cursor_info(true); ui_cursor_goto(msg_row, msg_col); RedrawingDisabled = temp; @@ -4296,7 +4306,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 +4636,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 = skip_regexp(cmd, delim, p_magic, &eap->arg); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } @@ -4762,10 +4772,9 @@ bool prepare_tagpreview(bool undo_sync) == FAIL) { return false; } - curwin->w_p_pvw = TRUE; - curwin->w_p_wfh = TRUE; - RESET_BINDING(curwin); /* don't take over 'scrollbind' - and 'cursorbind' */ + curwin->w_p_pvw = true; + curwin->w_p_wfh = true; + RESET_BINDING(curwin); // don't take over 'scrollbind' and 'cursorbind' curwin->w_p_diff = false; // no 'diff' set_string_option_direct("fdc", -1, // no 'foldcolumn' "0", OPT_FREE, SID_NONE); @@ -4775,1124 +4784,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. /// @@ -5901,7 +4792,7 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i long cmdpreview_ns, handle_T cmdpreview_bufnr) FUNC_ATTR_NONNULL_ALL { - char *save_shm_p = (char *)vim_strsave(p_shm); + char *save_shm_p = xstrdup(p_shm); PreviewLines lines = *preview_lines; buf_T *orig_buf = curbuf; // We keep a special-purpose buffer around, but don't assume it exists. @@ -6052,7 +4943,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) if (s != NULL) { *s = p; } - p = (char *)skiptowhite((char_u *)p); + p = skiptowhite(p); if (s != NULL && *p != NUL) { *p++ = NUL; } @@ -6062,7 +4953,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) *s = p + 1; } c = (char_u)(*p); - p = (char *)skip_regexp((char_u *)p + 1, c, true, NULL); + p = skip_regexp(p + 1, c, true, NULL); if (*p != c) { return NULL; } @@ -6107,7 +4998,7 @@ void ex_oldfiles(exarg_T *eap) } nr++; const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); - if (!message_filtered((char_u *)fname)) { + if (!message_filtered((char *)fname)) { msg_outnum(nr); msg_puts(": "); msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li))); 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..11280eecbb 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -11,30 +11,23 @@ #include <stdbool.h> #include <string.h> +#include "nvim/arglist.h" #include "nvim/ascii.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/autocmd.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/globals.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 +35,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/vim.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 +93,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! /// @@ -720,7 +364,7 @@ bool check_changed_any(bool hidden, bool unload) exiting = false; // When ":confirm" used, don't give an error message. if (!(p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM))) { - // There must be a wait_return for this message, do_buffer() + // There must be a wait_return() for this message, do_buffer() // may cause a redraw. But wait_return() is a no-op when vgetc() // is busy (Quit used from window menu), then make sure we don't // cause a scroll up. @@ -796,483 +440,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) { @@ -1372,10 +539,10 @@ void ex_listdo(exarg_T *eap) if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { // Clear 'shm' to avoid that the file message overwrites // any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); do_argfile(eap, i); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } if (curwin->w_arg_idx != i) { @@ -1441,10 +608,10 @@ void ex_listdo(exarg_T *eap) // Go to the next buffer. Clear 'shm' to avoid that the file // message overwrites any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); // If autocommands took us elsewhere, quit here. @@ -1464,10 +631,10 @@ void ex_listdo(exarg_T *eap) // Clear 'shm' to avoid that the file message overwrites // any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); ex_cnext(eap); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); // If jumping to the next quickfix entry fails, quit here. @@ -1509,11 +676,11 @@ void ex_listdo(exarg_T *eap) // buffer was opened while Syntax autocommands were disabled, // need to trigger them now. if (buf == curbuf) { - apply_autocmds(EVENT_SYNTAX, (char *)curbuf->b_p_syn, curbuf->b_fname, true, + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, true, curbuf); } else { aucmd_prepbuf(&aco, buf); - apply_autocmds(EVENT_SYNTAX, (char *)buf->b_p_syn, buf->b_fname, true, buf); + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, true, buf); aucmd_restbuf(&aco); } @@ -1524,51 +691,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 +754,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) { @@ -2631,324 +772,6 @@ void ex_checktime(exarg_T *eap) no_check_timestamps = save_no_check_timestamps; } -#if defined(HAVE_LOCALE_H) -# define HAVE_GET_LOCALE_VAL - -static char *get_locale_val(int what) -{ - // Obtain the locale value from the libraries. - char *loc = setlocale(what, NULL); - - return loc; -} -#endif - -/// @return true when "lang" starts with a valid language name. -/// Rejects NULL, empty string, "C", "C.UTF-8" and others. -static bool is_valid_mess_lang(char *lang) -{ - return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); -} - -/// Obtain the current messages language. Used to set the default for -/// 'helplang'. May return NULL or an empty string. -char *get_mess_lang(void) -{ - char *p; - -#ifdef HAVE_GET_LOCALE_VAL -# if defined(LC_MESSAGES) - p = get_locale_val(LC_MESSAGES); -# else - // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG - // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME - // and LC_MONETARY may be set differently for a Japanese working in the - // US. - p = get_locale_val(LC_COLLATE); -# endif -#else - p = os_getenv("LC_ALL"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LC_MESSAGES"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LANG"); - } - } -#endif - return is_valid_mess_lang(p) ? p : NULL; -} - -// Complicated #if; matches with where get_mess_env() is used below. -#ifdef HAVE_WORKING_LIBINTL -/// Get the language used for messages from the environment. -static char *get_mess_env(void) -{ - char *p; - - p = (char *)os_getenv("LC_ALL"); - if (p == NULL) { - p = (char *)os_getenv("LC_MESSAGES"); - if (p == NULL) { - p = (char *)os_getenv("LANG"); - if (p != NULL && ascii_isdigit(*p)) { - p = NULL; // ignore something like "1043" - } -# ifdef HAVE_GET_LOCALE_VAL - if (p == NULL) { - p = get_locale_val(LC_CTYPE); - } -# endif - } - } - return p; -} - -#endif - -/// Set the "v:lang" variable according to the current locale setting. -/// Also do "v:lc_time"and "v:ctype". -void set_lang_var(void) -{ - const char *loc; - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_CTYPE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_CTYPE, loc, -1); - - // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall - // back to LC_CTYPE if it's empty. -#ifdef HAVE_WORKING_LIBINTL - loc = get_mess_env(); -#elif defined(LC_MESSAGES) - loc = get_locale_val(LC_MESSAGES); -#else - // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE - loc = get_locale_val(LC_CTYPE); -#endif - set_vim_var_string(VV_LANG, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_TIME); -#endif - set_vim_var_string(VV_LC_TIME, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_COLLATE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_COLLATE, loc, -1); -} - -#ifdef HAVE_WORKING_LIBINTL - -/// ":language": Set the language (locale). -/// -/// @param eap -void ex_language(exarg_T *eap) -{ - char *loc; - char *p; - char *name; - int what = LC_ALL; - char *whatstr = ""; -# ifdef LC_MESSAGES -# define VIM_LC_MESSAGES LC_MESSAGES -# else -# define VIM_LC_MESSAGES 6789 -# endif - - name = eap->arg; - - // Check for "messages {name}", "ctype {name}" or "time {name}" argument. - // Allow abbreviation, but require at least 3 characters to avoid - // confusion with a two letter language name "me" or "ct". - p = (char *)skiptowhite((char_u *)eap->arg); - if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { - if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { - what = VIM_LC_MESSAGES; - name = skipwhite(p); - whatstr = "messages "; - } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { - what = LC_CTYPE; - name = skipwhite(p); - whatstr = "ctype "; - } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { - what = LC_TIME; - name = skipwhite(p); - whatstr = "time "; - } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { - what = LC_COLLATE; - name = skipwhite(p); - whatstr = "collate "; - } - } - - if (*name == NUL) { - if (what == VIM_LC_MESSAGES) { - p = get_mess_env(); - } else { - p = setlocale(what, NULL); - } - if (p == NULL || *p == NUL) { - p = "Unknown"; - } - smsg(_("Current %slanguage: \"%s\""), whatstr, p); - } else { -# ifndef LC_MESSAGES - if (what == VIM_LC_MESSAGES) { - loc = ""; - } else { -# endif - loc = setlocale(what, name); -# ifdef LC_NUMERIC - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif -# ifndef LC_MESSAGES - } -# endif - if (loc == NULL) { - semsg(_("E197: Cannot set language to \"%s\""), name); - } else { -# ifdef HAVE_NL_MSG_CAT_CNTR - // Need to do this for GNU gettext, otherwise cached translations - // will be used again. - extern int _nl_msg_cat_cntr; - - _nl_msg_cat_cntr++; -# endif - // Reset $LC_ALL, otherwise it would overrule everything. - os_setenv("LC_ALL", "", 1); - - if (what != LC_TIME && what != LC_COLLATE) { - // Tell gettext() what to translate to. It apparently doesn't - // use the currently effective locale. - if (what == LC_ALL) { - os_setenv("LANG", name, 1); - - // Clear $LANGUAGE because GNU gettext uses it. - os_setenv("LANGUAGE", "", 1); - } - if (what != LC_CTYPE) { - os_setenv("LC_MESSAGES", name, 1); - set_helplang_default(name); - } - } - - // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. - set_lang_var(); - maketitle(); - } - } -} - -static char **locales = NULL; // Array of all available locales - -# ifndef WIN32 -static bool did_init_locales = false; - -/// @return an array of strings for all available locales + NULL for the -/// last element or, -/// NULL in case of error. -static char **find_locales(void) -{ - garray_T locales_ga; - char *loc; - char *saveptr = NULL; - - // Find all available locales by running command "locale -a". If this - // doesn't work we won't have completion. - char *locale_a = (char *)get_cmd_output((char_u *)"locale -a", NULL, - kShellOptSilent, NULL); - if (locale_a == NULL) { - return NULL; - } - ga_init(&locales_ga, sizeof(char_u *), 20); - - // Transform locale_a string where each locale is separated by "\n" - // into an array of locale strings. - loc = os_strtok(locale_a, "\n", &saveptr); - - while (loc != NULL) { - loc = xstrdup(loc); - GA_APPEND(char *, &locales_ga, loc); - loc = os_strtok(NULL, "\n", &saveptr); - } - xfree(locale_a); - // Guarantee that .ga_data is NULL terminated - ga_grow(&locales_ga, 1); - ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; - return locales_ga.ga_data; -} -# endif - -/// Lazy initialization of all available locales. -static void init_locales(void) -{ -# ifndef WIN32 - if (!did_init_locales) { - did_init_locales = true; - locales = find_locales(); - } -# endif -} - -# if defined(EXITFREE) -void free_locales(void) -{ - int i; - if (locales != NULL) { - for (i = 0; locales[i] != NULL; i++) { - xfree(locales[i]); - } - XFREE_CLEAR(locales); - } -} - -# endif - -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":language" command. -char *get_lang_arg(expand_T *xp, int idx) -{ - if (idx == 0) { - return "messages"; - } - if (idx == 1) { - return "ctype"; - } - if (idx == 2) { - return "time"; - } - if (idx == 3) { - return "collate"; - } - - init_locales(); - if (locales == NULL) { - return NULL; - } - return locales[idx - 4]; -} - -/// Function given to ExpandGeneric() to obtain the available locales. -char *get_locales(expand_T *xp, int idx) -{ - init_locales(); - if (locales == NULL) { - return NULL; - } - return locales[idx]; -} - -#endif - static void script_host_execute(char *name, exarg_T *eap) { size_t len; @@ -3007,7 +830,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..3a41e105f3 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -1,10 +1,7 @@ #ifndef NVIM_EX_CMDS2_H #define NVIM_EX_CMDS2_H -#include <stdbool.h> - -#include "nvim/ex_docmd.h" -#include "nvim/runtime.h" +#include "nvim/ex_cmds_defs.h" // // flags for check_changed() @@ -15,27 +12,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_cmds_defs.h b/src/nvim/ex_cmds_defs.h index e80e47bcff..71956b2246 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -188,7 +188,7 @@ struct exarg { cmdidx_T cmdidx; ///< the index for the command uint32_t argt; ///< flags for the command int skip; ///< don't execute the command, only parse it - int forceit; ///< TRUE if ! present + int forceit; ///< true if ! present int addr_count; ///< the number of addresses given linenr_T line1; ///< the first line number linenr_T line2; ///< the second line number or count @@ -196,8 +196,8 @@ struct exarg { int flags; ///< extra flags after count: EXFLAG_ char *do_ecmd_cmd; ///< +command arg to be used in edited file linenr_T do_ecmd_lnum; ///< the line number in an edited file - int append; ///< TRUE with ":w >>file" command - int usefilter; ///< TRUE with ":w !command" and ":r!command" + int append; ///< true with ":w >>file" command + int usefilter; ///< true with ":w !command" and ":r!command" int amount; ///< number of '>' or '<' for shift command int regname; ///< register name (NUL if none) int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN @@ -230,13 +230,15 @@ struct expand { sctx_T xp_script_ctx; // SCTX for completion function int xp_backslash; // one of the XP_BS_ values #ifndef BACKSLASH_IN_FILENAME - int xp_shell; // TRUE for a shell command, more + int xp_shell; // true for a shell command, more // characters need to be escaped #endif int xp_numfiles; // number of files found by file name completion int xp_col; // cursor position in line char **xp_files; // list of files char *xp_line; // text being completed +#define EXPAND_BUF_LEN 256 + char xp_buf[EXPAND_BUF_LEN]; // buffer for returned match }; // values for xp_backslash diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0dde82c235..26b0c0f1ef 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,14 +9,18 @@ #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/cmdexpand.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,10 +42,12 @@ #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" #include "nvim/keycodes.h" +#include "nvim/locale.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mapping.h" @@ -57,15 +63,16 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #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" @@ -88,6 +95,12 @@ static char e_ambiguous_use_of_user_defined_command[] = N_("E464: Ambiguous use of user-defined command"); static char e_not_an_editor_command[] = N_("E492: Not an editor command"); +static char e_no_source_file_name_to_substitute_for_sfile[] + = N_("E498: no :source file name to substitute for \"<sfile>\""); +static char e_no_call_stack_to_substitute_for_stack[] + = N_("E489: no call stack to substitute for \"<stack>\""); +static char e_no_script_file_name_to_substitute_for_script[] + = N_("E1274: No script file name to substitute for \"<script>\""); static int quitmore = 0; static bool ex_pressedreturn = false; @@ -100,16 +113,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; }; @@ -123,6 +134,7 @@ struct dbg_stuff { char *vv_throwpoint; int did_emsg; int got_int; + bool did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -136,9 +148,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 +158,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); @@ -156,6 +166,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) // Necessary for debugging an inactive ":catch", ":finally", ":endtry". dsp->did_emsg = did_emsg; did_emsg = false; dsp->got_int = got_int; got_int = false; + dsp->did_throw = did_throw; did_throw = false; dsp->need_rethrow = need_rethrow; need_rethrow = false; dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; @@ -171,6 +182,7 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; + did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -179,11 +191,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 +201,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 +216,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 +239,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 @@ -246,8 +253,8 @@ void do_exmode(void) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); - update_screen(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); + update_screen(UPD_NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; } @@ -301,26 +308,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 +368,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 +378,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 +395,14 @@ 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_throw" will be set to true when an exception is being thrown. + did_throw = false; + // "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 +414,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 +431,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 +466,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); + 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(fname, SOURCING_LNUM); // Find next breakpoint. *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, sourcing_lnum); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -509,10 +510,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; } @@ -521,7 +520,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2, true)) == NULL) { - // Don't call wait_return for aborted command line. The NULL + // Don't call wait_return() for aborted command line. The NULL // returned for the end of a sourced file or executed function // doesn't do this. if (KeyTyped && !(flags & DOCMD_REPEAT)) { @@ -532,13 +531,11 @@ 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) { - repeat_cmdline = vim_strsave((char_u *)next_cmdline); + repeat_cmdline = (char *)vim_strsave((char_u *)next_cmdline); } else { repeat_cmdline = NULL; } @@ -549,13 +546,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 +558,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 +592,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 +612,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); @@ -641,7 +629,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // not to use a cs_line[] from an entry that isn't a ":while" // or ":for": It would make "current_line" invalid and can // cause a crash. - if (!did_emsg && !got_int && !current_exception + if (!did_emsg && !got_int && !did_throw && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -655,8 +643,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Check for the next breakpoint at or after the ":while" // or ":for". if (breakpoint != NULL) { - *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, + *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), fname, ((wcmd_T *)lines_ga.ga_data)[current_line].lnum - 1); *dbg_tick = debug_tick; } @@ -667,41 +654,33 @@ 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 did_throw 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] & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), - current_exception); - did_emsg = got_int = false; - current_exception = NULL; + did_throw ? current_exception : NULL); + did_emsg = got_int = did_throw = false; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -714,45 +693,41 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // exception, cancel everything. If it is left normally, reset // force_abort to get the non-EH compatible abortion behavior for // the rest of the script. - if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + if (trylevel == 0 && !did_emsg && !got_int && !did_throw) { force_abort = false; } // 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) || did_throw) + && 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 (!got_int && !current_exception + // If a sourced file or executed function ran to its end, report the + // unclosed conditional. + if (!got_int && !did_throw && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -768,18 +743,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); @@ -797,19 +770,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // conditional, discard the uncaught exception, disable the conversion // of interrupts or errors to exceptions, and ensure that no more // commands are executed. - if (current_exception) { + if (did_throw) { + assert(current_exception != NULL); 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 +795,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 +815,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 @@ -867,38 +833,34 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // cstack belongs to the same function or, respectively, script file, it // will have to be checked for finally clauses to be executed due to the // ":return" or ":finish". This is done in do_one_cmd(). - if (current_exception) { + if (did_throw) { need_rethrow = true; } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!current_exception) { + if (!did_throw) { check_cstack = true; } } 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")); + ? _("End of sourced file") + : _("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 +876,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 +910,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 +924,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 +932,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 +942,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 +969,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 +987,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 +1033,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 +1046,7 @@ static int current_tab_nr(tabpage_T *tab) int nr = 0; FOR_ALL_TABS(tp) { - ++nr; + nr++; if (tp == tab) { break; } @@ -1385,12 +1333,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,56 +1370,59 @@ 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; + bool retval = false; + // parsing the command modifiers may set ex_pressedreturn + const bool save_ex_pressedreturn = ex_pressedreturn; + // parsing the command range may require moving the cursor + const pos_T save_cursor = curwin->w_cursor; + // parsing the command range may set the last search pattern + save_last_search_pattern(); // 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 if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) { - ex_pressedreturn = save_ex_pressedreturn; - goto err; + goto end; } - ex_pressedreturn = save_ex_pressedreturn; 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; + goto end; } // Set command address type and parse command range set_cmd_addr_type(eap, p); eap->cmd = cmd; - if (parse_cmd_address(eap, errormsg, false) == FAIL) { - goto err; + if (parse_cmd_address(eap, errormsg, true) == FAIL) { + goto end; } // Skip colon and whitespace eap->cmd = skip_colon_white(eap->cmd, true); // Fail if command is a comment or if command doesn't exist if (*eap->cmd == NUL || *eap->cmd == '"') { - goto err; + goto end; } // Fail if command is invalid if (eap->cmdidx == CMD_SIZE) { @@ -1481,7 +1431,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); *errormsg = (char *)IObuff; - goto err; + goto end; } // Correctly set 'forceit' for commands @@ -1520,12 +1470,12 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Fail if command doesn't support bang but is used with a bang if (!(eap->argt & EX_BANG) && eap->forceit) { *errormsg = _(e_nobang); - goto err; + goto end; } // Fail if command doesn't support a range but it is given a range if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) { *errormsg = _(e_norange); - goto err; + goto end; } // Set default range for command if required if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { @@ -1535,7 +1485,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Parse register and count parse_register(eap); if (parse_count(eap, errormsg, false) == FAIL) { - goto err; + goto end; } // Remove leading whitespace and colon from next command @@ -1551,10 +1501,15 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er cmdinfo->magic.bar = true; } - return true; -err: - undo_cmdmod(&cmdinfo->cmdmod); - return false; + retval = true; +end: + if (!retval) { + undo_cmdmod(&cmdinfo->cmdmod); + } + ex_pressedreturn = save_ex_pressedreturn; + curwin->w_cursor = save_cursor; + restore_last_search_pattern(); + return retval; } static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) @@ -1719,12 +1674,12 @@ 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 && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { - int skip = did_emsg || got_int || current_exception; + bool skip = did_emsg || got_int || did_throw; if (eap->cmdidx == CMD_catch) { skip = !skip && !(cstack->cs_idx >= 0 @@ -1855,7 +1810,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 +1832,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 +1845,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 @@ -1921,7 +1876,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.skip = (did_emsg || got_int - || current_exception + || did_throw || (cstack->cs_idx >= 0 && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); @@ -1941,7 +1896,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 +1970,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 +2149,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 +2249,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 +2287,7 @@ doend: ea.nextcmd = NULL; } - --ex_nesting_level; + ex_nesting_level--; return ea.nextcmd; } @@ -2368,8 +2323,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 +2354,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': @@ -2631,7 +2584,7 @@ static void apply_cmdmod(cmdmod_T *cmod) if ((cmod->cmod_flags & CMOD_NOAUTOCMD) && cmod->cmod_save_ei == NULL) { // Set 'eventignore' to "all". // First save the existing option value for restoring it later. - cmod->cmod_save_ei = (char *)vim_strsave(p_ei); + cmod->cmod_save_ei = xstrdup(p_ei); set_string_option_direct("ei", -1, "all", OPT_FREE, SID_NONE); } } @@ -2653,7 +2606,7 @@ void undo_cmdmod(cmdmod_T *cmod) if (cmod->cmod_save_ei != NULL) { // Restore 'eventignore' to the value before ":noautocmd". set_string_option_direct("ei", -1, cmod->cmod_save_ei, OPT_FREE, SID_NONE); - free_string_option((char_u *)cmod->cmod_save_ei); + free_string_option(cmod->cmod_save_ei); cmod->cmod_save_ei = NULL; } @@ -2840,12 +2793,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 +2811,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 +2839,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 +2848,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 +2876,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 +2892,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 +2940,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 +2951,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 +3023,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 +3038,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; } @@ -3111,9 +3057,8 @@ int cmd_exists(const char *const name) } /// "fullcommand" function -void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - exarg_T ea; char *name = argvars[0].vval.v_string; rettv->v_type = VAR_STRING; @@ -3127,6 +3072,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; @@ -3141,843 +3087,22 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) : (char_u *)cmdnames[ea.cmdidx].cmd_name); } -/// This is all pretty much copied from do_one_cmd(), with all the extra stuff -/// we don't need/want deleted. Maybe this could be done better if we didn't -/// repeat all this stuff. The only problem is that they may not stay -/// perfectly compatible with each other, but then the command line syntax -/// probably won't change that much -- webb. -/// -/// @param buff buffer for command string -const char *set_one_cmd_context(expand_T *xp, const char *buff) +cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len) { - size_t len = 0; - exarg_T ea; - int context = EXPAND_NOTHING; - bool forceit = false; - bool usefilter = false; // Filter instead of file name. - - ExpandInit(xp); - xp->xp_pattern = (char *)buff; - xp->xp_line = (char *)buff; - xp->xp_context = EXPAND_COMMANDS; // Default until we get past command - ea.argt = 0; - - // 2. skip comment lines and leading space, colons or bars - const char *cmd; - for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} - xp->xp_pattern = (char *)cmd; - - if (*cmd == NUL) { - return NULL; - } - if (*cmd == '"') { // ignore comment lines - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - - /* - * 3. parse a range specifier of the form: addr [,addr] [;addr] .. - */ - cmd = (const char *)skip_range(cmd, &xp->xp_context); - - /* - * 4. parse command - */ - xp->xp_pattern = (char *)cmd; - if (*cmd == NUL) { - return NULL; - } - if (*cmd == '"') { - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - - 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' - */ - const char *p; - if (*cmd == 'k' && cmd[1] != 'e') { - ea.cmdidx = CMD_k; - p = cmd + 1; - } else { - p = cmd; - while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card - p++; - } - // a user command may contain digits - if (ASCII_ISUPPER(cmd[0])) { - while (ASCII_ISALNUM(*p) || *p == '*') { - p++; - } - } - // for python 3.x: ":py3*" commands completion - if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { - p++; - while (ASCII_ISALPHA(*p) || *p == '*') { - p++; - } - } - // check for non-alpha command - if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { - p++; - } - len = (size_t)(p - cmd); - - if (len == 0) { - xp->xp_context = EXPAND_UNSUCCESSFUL; - return NULL; - } - for (ea.cmdidx = (cmdidx_T)0; (int)ea.cmdidx < CMD_SIZE; - ea.cmdidx = (cmdidx_T)((int)ea.cmdidx + 1)) { - if (STRNCMP(cmdnames[(int)ea.cmdidx].cmd_name, cmd, len) == 0) { - break; - } - } - - if (cmd[0] >= 'A' && cmd[0] <= 'Z') { - while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card - p++; - } - } - } - - // - // If the cursor is touching the command, and it ends in an alphanumeric - // character, complete the command name. - // - if (*p == NUL && ASCII_ISALNUM(p[-1])) { - return NULL; - } - - if (ea.cmdidx == CMD_SIZE) { - if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { - ea.cmdidx = CMD_substitute; - p = cmd + 1; - } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { - ea.cmd = (char *)cmd; - p = (const char *)find_ucmd(&ea, (char *)p, NULL, xp, &context); - if (p == NULL) { - ea.cmdidx = CMD_SIZE; // Ambiguous user command. - } - } - } - if (ea.cmdidx == CMD_SIZE) { - // Not still touching the command and it was an illegal one - xp->xp_context = EXPAND_UNSUCCESSFUL; - return NULL; - } - - xp->xp_context = EXPAND_NOTHING; // Default now that we're past command - - if (*p == '!') { // forced commands - forceit = true; - p++; - } - - /* - * 5. parse arguments - */ - if (!IS_USER_CMDIDX(ea.cmdidx)) { - ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; - } - - const char *arg = (const char *)skipwhite(p); - - // Skip over ++argopt argument - if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { - p = arg; - while (*p && !ascii_isspace(*p)) { - MB_PTR_ADV(p); - } - arg = (const char *)skipwhite(p); - } - - if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { - if (*arg == '>') { // Append. - if (*++arg == '>') { - arg++; - } - arg = (const char *)skipwhite(arg); - } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter - arg++; - usefilter = true; - } - } + cmdidx_T idx; - if (ea.cmdidx == CMD_read) { - usefilter = forceit; // :r! filter if forced - if (*arg == '!') { // :r !filter - arg++; - usefilter = true; - } - } - - if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { - while (*arg == *cmd) { // allow any number of '>' or '<' - arg++; - } - arg = (const char *)skipwhite(arg); - } - - // Does command allow "+command"? - if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { - // Check if we're in the +command - p = arg + 1; - arg = (const char *)skip_cmd_arg((char *)arg, false); - - // Still touching the command after '+'? - if (*arg == NUL) { - return p; - } - - // Skip space(s) after +command to get to the real argument. - arg = (const char *)skipwhite(arg); - } - - /* - * 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 - if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { - p += 2; - } - while (*p) { - if (*p == Ctrl_V) { - if (p[1] != NUL) { - p++; - } - } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) - || *p == '|' - || *p == '\n') { - if (*(p - 1) != '\\') { - if (*p == '|' || *p == '\n') { - return p + 1; - } - return NULL; // It's a comment - } - } - MB_PTR_ADV(p); - } - } - - if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { - // no arguments allowed but there is something - return NULL; - } - - // Find start of last argument (argument just before cursor): - p = buff; - xp->xp_pattern = (char *)p; - len = strlen(buff); - while (*p && p < buff + len) { - if (*p == ' ' || *p == TAB) { - // Argument starts after a space. - xp->xp_pattern = (char *)++p; - } else { - if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character - } - MB_PTR_ADV(p); - } - } - - 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. - */ - xp->xp_pattern = skipwhite(arg); - p = (const char *)xp->xp_pattern; - while (*p != NUL) { - c = utf_ptr2char(p); - if (c == '\\' && p[1] != NUL) { - p++; - } else if (c == '`') { - if (!in_quote) { - xp->xp_pattern = (char *)p; - 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)) { - len = 0; // avoid getting stuck when space is in 'isfname' - while (*p != NUL) { - c = utf_ptr2char(p); - if (c == '`' || vim_isfilec_or_wc(c)) { - break; - } - len = (size_t)utfc_ptr2len(p); - MB_PTR_ADV(p); - } - if (in_quote) { - bow = p; - } else { - xp->xp_pattern = (char *)p; - } - p -= len; - } - MB_PTR_ADV(p); - } - - /* - * 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; - } - xp->xp_context = EXPAND_FILES; - - // 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; -#endif - // When still after the command name expand executables. - if (xp->xp_pattern == skipwhite(arg)) { - xp->xp_context = EXPAND_SHELLCMD; - } - } - - // Check for environment variable. - if (*xp->xp_pattern == '$') { - for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { - if (!vim_isIDc((uint8_t)(*p))) { - break; - } - } - if (*p == NUL) { - xp->xp_context = EXPAND_ENV_VARS; - xp->xp_pattern++; - // Avoid that the assignment uses EXPAND_FILES again. - if (context != EXPAND_USER_DEFINED && context != EXPAND_USER_LIST) { - context = EXPAND_ENV_VARS; - } - } - } - // Check for user names. - if (*xp->xp_pattern == '~') { - for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} - // Complete ~user only if it partially matches a user name. - // A full match ~user<Tab> will be replaced by user's home - // directory i.e. something like ~user<Tab> -> /home/user/ - 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; - } - } - } - - /* - * 6. switch on command name - */ - switch (ea.cmdidx) { - case CMD_find: - case CMD_sfind: - case CMD_tabfind: - if (xp->xp_context == EXPAND_FILES) { - xp->xp_context = EXPAND_FILES_IN_PATH; - } - break; - case CMD_cd: - case CMD_chdir: - case CMD_lcd: - case CMD_lchdir: - case CMD_tcd: - case CMD_tchdir: - if (xp->xp_context == EXPAND_FILES) { - xp->xp_context = EXPAND_DIRECTORIES; - } - break; - case CMD_help: - xp->xp_context = EXPAND_HELP; - xp->xp_pattern = (char *)arg; - break; - - /* Command modifiers: return the argument. - * Also for commands with an argument that is a command. */ - case CMD_aboveleft: - case CMD_argdo: - case CMD_belowright: - case CMD_botright: - case CMD_browse: - case CMD_bufdo: - case CMD_cdo: - case CMD_cfdo: - case CMD_confirm: - case CMD_debug: - case CMD_folddoclosed: - case CMD_folddoopen: - case CMD_hide: - case CMD_keepalt: - case CMD_keepjumps: - case CMD_keepmarks: - case CMD_keeppatterns: - case CMD_ldo: - case CMD_leftabove: - case CMD_lfdo: - case CMD_lockmarks: - case CMD_noautocmd: - case CMD_noswapfile: - case CMD_rightbelow: - case CMD_sandbox: - case CMD_silent: - case CMD_tab: - case CMD_tabdo: - case CMD_topleft: - case CMD_verbose: - case CMD_vertical: - case CMD_windo: - return arg; - - case CMD_filter: - if (*arg != NUL) { - arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL); - } - if (arg == NULL || *arg == NUL) { - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - return (const char *)skipwhite(arg); - - case CMD_match: - if (*arg == NUL || !ends_excmd(*arg)) { - // also complete "None" - set_context_in_echohl_cmd(xp, arg); - arg = (const char *)skipwhite((char *)skiptowhite((const char_u *)arg)); - if (*arg != NUL) { - xp->xp_context = EXPAND_NOTHING; - arg = (const char *)skip_regexp((char_u *)arg + 1, (uint8_t)(*arg), - p_magic, NULL); - } - } - return (const char *)find_nextcmd((char_u *)arg); - - /* - * All completion for the +cmdline_compl feature goes here. - */ - - case CMD_command: - return set_context_in_user_cmd(xp, arg); - - case CMD_delcommand: - xp->xp_context = EXPAND_USER_COMMANDS; - xp->xp_pattern = (char *)arg; - break; - - case CMD_global: - case CMD_vglobal: { - const int delim = (uint8_t)(*arg); // Get the delimiter. - if (delim) { - arg++; // Skip delimiter if there is one. - } - - while (arg[0] != NUL && (uint8_t)arg[0] != delim) { - if (arg[0] == '\\' && arg[1] != NUL) { - arg++; - } - arg++; - } - if (arg[0] != NUL) { - return arg + 1; - } - break; - } - case CMD_and: - case CMD_substitute: { - const int delim = (uint8_t)(*arg); - if (delim) { - // Skip "from" part. - arg++; - arg = (const char *)skip_regexp((char_u *)arg, delim, p_magic, NULL); - } - // Skip "to" part. - while (arg[0] != NUL && (uint8_t)arg[0] != delim) { - if (arg[0] == '\\' && arg[1] != NUL) { - arg++; - } - arg++; - } - if (arg[0] != NUL) { // Skip delimiter. - arg++; - } - while (arg[0] && strchr("|\"#", arg[0]) == NULL) { - arg++; - } - if (arg[0] != NUL) { - return arg; + for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { + if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { + break; } - break; } - case CMD_isearch: - case CMD_dsearch: - case CMD_ilist: - case CMD_dlist: - case CMD_ijump: - case CMD_psearch: - case CMD_djump: - case CMD_isplit: - case CMD_dsplit: - // Skip count. - arg = (const char *)skipwhite(skipdigits(arg)); - if (*arg == '/') { // Match regexp, not just whole words. - for (++arg; *arg && *arg != '/'; arg++) { - if (*arg == '\\' && arg[1] != NUL) { - arg++; - } - } - if (*arg) { - arg = (const char *)skipwhite(arg + 1); - - // Check for trailing illegal characters. - if (*arg && strchr("|\"\n", *arg) == NULL) { - xp->xp_context = EXPAND_NOTHING; - } else { - return arg; - } - } - } - break; - case CMD_autocmd: - return (const char *)set_context_in_autocmd(xp, (char *)arg, false); - - case CMD_doautocmd: - case CMD_doautoall: - return (const char *)set_context_in_autocmd(xp, (char *)arg, true); - case CMD_set: - set_context_in_set_cmd(xp, (char_u *)arg, 0); - break; - case CMD_setglobal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); - break; - case CMD_setlocal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); - break; - case CMD_tag: - case CMD_stag: - case CMD_ptag: - case CMD_ltag: - case CMD_tselect: - case CMD_stselect: - case CMD_ptselect: - case CMD_tjump: - case CMD_stjump: - case CMD_ptjump: - if (wop_flags & WOP_TAGFILE) { - xp->xp_context = EXPAND_TAGS_LISTFILES; - } else { - xp->xp_context = EXPAND_TAGS; - } - xp->xp_pattern = (char *)arg; - break; - case CMD_augroup: - xp->xp_context = EXPAND_AUGROUP; - xp->xp_pattern = (char *)arg; - break; - case CMD_syntax: - set_context_in_syntax_cmd(xp, arg); - break; - case CMD_const: - case CMD_let: - case CMD_if: - case CMD_elseif: - case CMD_while: - case CMD_for: - case CMD_echo: - case CMD_echon: - case CMD_execute: - case CMD_echomsg: - case CMD_echoerr: - case CMD_call: - case CMD_return: - case CMD_cexpr: - case CMD_caddexpr: - case CMD_cgetexpr: - case CMD_lexpr: - case CMD_laddexpr: - case CMD_lgetexpr: - set_context_for_expression(xp, (char *)arg, ea.cmdidx); - break; - - case CMD_unlet: - while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { - arg = (const char *)xp->xp_pattern + 1; - } - - xp->xp_context = EXPAND_USER_VARS; - xp->xp_pattern = (char *)arg; - - if (*xp->xp_pattern == '$') { - xp->xp_context = EXPAND_ENV_VARS; - xp->xp_pattern++; - } - break; - - case CMD_function: - case CMD_delfunction: - xp->xp_context = EXPAND_USER_FUNC; - xp->xp_pattern = (char *)arg; - break; - - case CMD_echohl: - set_context_in_echohl_cmd(xp, arg); - break; - case CMD_highlight: - set_context_in_highlight_cmd(xp, arg); - break; - case CMD_cscope: - case CMD_lcscope: - case CMD_scscope: - set_context_in_cscope_cmd(xp, arg, ea.cmdidx); - break; - case CMD_sign: - set_context_in_sign_cmd(xp, (char_u *)arg); - break; - case CMD_bdelete: - case CMD_bwipeout: - case CMD_bunload: - while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { - arg = (const char *)xp->xp_pattern + 1; - } - FALLTHROUGH; - case CMD_buffer: - case CMD_sbuffer: - case CMD_checktime: - xp->xp_context = EXPAND_BUFFERS; - xp->xp_pattern = (char *)arg; - break; - case CMD_diffget: - case CMD_diffput: - // If current buffer is in diff mode, complete buffer names - // which are in diff mode, and different than current buffer. - xp->xp_context = EXPAND_DIFF_BUFFERS; - xp->xp_pattern = (char *)arg; - break; - - case CMD_USER: - case CMD_USER_BUF: - if (context != EXPAND_NOTHING) { - // EX_XFILE: file names are handled above. - if (!(ea.argt & EX_XFILE)) { - if (context == EXPAND_MENUS) { - return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); - } 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, - false, false, - CMD_map); - } - // Find start of last argument. - p = arg; - while (*p) { - if (*p == ' ') { - // argument starts after a space - arg = p + 1; - } else if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character - } - MB_PTR_ADV(p); - } - xp->xp_pattern = (char *)arg; - } - xp->xp_context = context; - } - break; - case CMD_map: - case CMD_noremap: - case CMD_nmap: - case CMD_nnoremap: - case CMD_vmap: - case CMD_vnoremap: - case CMD_omap: - case CMD_onoremap: - case CMD_imap: - case CMD_inoremap: - case CMD_cmap: - case CMD_cnoremap: - case CMD_lmap: - case CMD_lnoremap: - case CMD_smap: - 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, - false, ea.cmdidx); - case CMD_unmap: - case CMD_nunmap: - case CMD_vunmap: - case CMD_ounmap: - case CMD_iunmap: - case CMD_cunmap: - 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, - true, ea.cmdidx); - case CMD_mapclear: - case CMD_nmapclear: - case CMD_vmapclear: - case CMD_omapclear: - case CMD_imapclear: - case CMD_cmapclear: - case CMD_lmapclear: - case CMD_smapclear: - case CMD_xmapclear: - xp->xp_context = EXPAND_MAPCLEAR; - xp->xp_pattern = (char *)arg; - break; - - case CMD_abbreviate: - case CMD_noreabbrev: - case CMD_cabbrev: - 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, - 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, - true, ea.cmdidx); - case CMD_menu: - case CMD_noremenu: - case CMD_unmenu: - case CMD_amenu: - case CMD_anoremenu: - case CMD_aunmenu: - case CMD_nmenu: - case CMD_nnoremenu: - case CMD_nunmenu: - case CMD_vmenu: - case CMD_vnoremenu: - case CMD_vunmenu: - case CMD_omenu: - case CMD_onoremenu: - case CMD_ounmenu: - case CMD_imenu: - case CMD_inoremenu: - case CMD_iunmenu: - case CMD_cmenu: - case CMD_cnoremenu: - case CMD_cunmenu: - case CMD_tlmenu: - case CMD_tlnoremenu: - case CMD_tlunmenu: - case CMD_tmenu: - case CMD_tunmenu: - case CMD_popup: - case CMD_emenu: - return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); - - case CMD_colorscheme: - xp->xp_context = EXPAND_COLORS; - xp->xp_pattern = (char *)arg; - break; - - case CMD_compiler: - xp->xp_context = EXPAND_COMPILER; - xp->xp_pattern = (char *)arg; - break; - - case CMD_ownsyntax: - xp->xp_context = EXPAND_OWNSYNTAX; - xp->xp_pattern = (char *)arg; - break; - - case CMD_setfiletype: - xp->xp_context = EXPAND_FILETYPE; - xp->xp_pattern = (char *)arg; - break; - - case CMD_packadd: - xp->xp_context = EXPAND_PACKADD; - xp->xp_pattern = (char *)arg; - break; - -#ifdef HAVE_WORKING_LIBINTL - case CMD_language: - p = (const char *)skiptowhite((const char_u *)arg); - if (*p == NUL) { - xp->xp_context = EXPAND_LANGUAGE; - xp->xp_pattern = (char *)arg; - } else { - if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 - || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 - || strncmp(arg, "time", (size_t)(p - arg)) == 0 - || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { - xp->xp_context = EXPAND_LOCALES; - xp->xp_pattern = skipwhite(p); - } else { - xp->xp_context = EXPAND_NOTHING; - } - } - break; -#endif - case CMD_profile: - set_context_in_profile_cmd(xp, arg); - break; - case CMD_checkhealth: - xp->xp_context = EXPAND_CHECKHEALTH; - xp->xp_pattern = (char *)arg; - break; - case CMD_behave: - xp->xp_context = EXPAND_BEHAVE; - xp->xp_pattern = (char *)arg; - break; - - case CMD_messages: - xp->xp_context = EXPAND_MESSAGES; - xp->xp_pattern = (char *)arg; - break; - - case CMD_history: - xp->xp_context = EXPAND_HISTORY; - xp->xp_pattern = (char *)arg; - break; - case CMD_syntime: - xp->xp_context = EXPAND_SYNTIME; - xp->xp_pattern = (char *)arg; - break; - - case CMD_argdelete: - while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) { - arg = (const char *)(xp->xp_pattern + 1); - } - xp->xp_context = EXPAND_ARGLIST; - xp->xp_pattern = (char *)arg; - break; - - case CMD_lua: - xp->xp_context = EXPAND_LUA; - break; + return idx; +} - default: - break; - } - return NULL; +uint32_t excmd_get_argt(cmdidx_T idx) +{ + return cmdnames[(int)idx].cmd_argt; } /// Skip a range specifier of the form: addr [,addr] [;addr] .. @@ -3992,8 +3117,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 +3129,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) { @@ -4055,17 +3178,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 +3222,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: @@ -4192,9 +3313,9 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } if (skip) { // skip "/pat/" - cmd = (char *)skip_regexp((char_u *)cmd, c, p_magic, NULL); + cmd = skip_regexp(cmd, c, p_magic, NULL); if (*cmd == c) { - ++cmd; + cmd++; } } else { int flags; @@ -4234,7 +3355,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 +3386,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } } - ++cmd; + cmd++; break; default: @@ -4468,6 +3589,9 @@ char *invalid_range(exarg_T *eap) assert(eap->line2 >= 0); // No error for value that is too big, will use the last entry. if (eap->line2 <= 0) { + if (eap->addr_count == 0) { + return _(e_no_errors); + } return _(e_invrange); } break; @@ -4529,8 +3653,8 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) // Don't do it when ":vimgrep" is used for ":grep". if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake || isgrep) && !grep_internal(eap->cmdidx)) { - const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? (char *)p_gp : (char *)curbuf->b_p_gp) - : (*curbuf->b_p_mp == NUL ? (char *)p_mp : (char *)curbuf->b_p_mp); + const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? (char *)p_gp : curbuf->b_p_gp) + : (*curbuf->b_p_mp == NUL ? (char *)p_mp : curbuf->b_p_mp); arg = skipwhite(arg); @@ -4544,7 +3668,7 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) STRCAT(new_cmdline, arg); } - msg_make((char_u *)arg); + msg_make(arg); // 'eap->cmd' is not set here, because it is not used at CMD_make xfree(*cmdlinep); @@ -4560,45 +3684,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, true); if (*errormsgp != NULL) { // error detected return FAIL; } @@ -4668,24 +3782,20 @@ 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); - has_wildcards = path_has_wildcard(NameBuff); + expand_env_esc((char_u *)eap->arg, (char_u *)NameBuff, MAXPATHL, true, true, NULL); + has_wildcards = path_has_wildcard((char_u *)NameBuff); p = (char *)NameBuff; } else { p = NULL; @@ -4695,15 +3805,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(eap->arg); + } +#else backslash_halve((char_u *)eap->arg); +#endif if (has_wildcards) { expand_T xpc; @@ -4733,11 +3844,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 +3855,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 +3954,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,15 +3973,15 @@ static char *getargcmd(char **argp) /// Find end of "+command" argument. Skip over "\ " and "\\". /// -/// @param rembs TRUE to halve the number of backslashes -static char *skip_cmd_arg(char *p, int rembs) +/// @param rembs true to halve the number of backslashes +char *skip_cmd_arg(char *p, int rembs) { while (*p && !ascii_isspace(*p)) { if (*p == '\\' && p[1] != NUL) { if (rembs) { STRMOVE(p, p + 1); } else { - ++p; + p++; } } MB_PTR_ADV(p); @@ -4905,7 +4012,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) { @@ -4958,13 +4064,13 @@ static int getargopt(exarg_T *eap) *arg = NUL; if (pp == &eap->force_ff) { - if (check_ff_value((char_u *)eap->cmd + eap->force_ff) == FAIL) { + if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) { return FAIL; } 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 +4095,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 +4106,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) { @@ -5193,7 +4298,7 @@ int ends_excmd(int c) FUNC_ATTR_CONST /// @return the next command, after the first '|' or '\n' or, /// NULL if not found. -char_u *find_nextcmd(const char_u *p) +char *find_nextcmd(const char *p) { while (*p != '|' && *p != '\n') { if (*p == NUL) { @@ -5201,7 +4306,7 @@ char_u *find_nextcmd(const char_u *p) } p++; } - return (char_u *)p + 1; + return (char *)p + 1; } /// Check if *p is a separator between Ex commands, skipping over white space. @@ -5223,9 +4328,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 +4371,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 +4577,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 +4613,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 +4620,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 +4692,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 +4699,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 +4719,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 +4735,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 +4850,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 +4918,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 +4952,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 +5022,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 @@ -6105,7 +5035,7 @@ static void ex_tabs(exarg_T *eap) msg_putchar('\n'); vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); - msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); + msg_outtrans_attr((char *)IObuff, HL_ATTR(HLF_T)); ui_flush(); // output one line at a time os_breakcheck(); @@ -6136,7 +5066,7 @@ static void ex_tabs(exarg_T *eap) static void ex_mode(exarg_T *eap) { if (*eap->arg == NUL) { - must_redraw = CLEAR; + must_redraw = UPD_CLEAR; ex_redraw(eap); } else { emsg(_(e_screenmode)); @@ -6147,15 +5077,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 +5105,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 +5136,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; @@ -6235,7 +5158,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) no_wait_return = 0; need_wait_return = false; msg_scroll = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); pending_exmode_active = true; normal_enter(false, true); @@ -6286,12 +5209,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 +5243,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 +5290,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 +5308,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); + redraw_later(curwin, UPD_VALID); cursor_correct(); - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } } curwin = save_curwin; @@ -6425,9 +5342,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 +5350,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 +5373,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 { @@ -6471,7 +5388,7 @@ static void ex_read(exarg_T *eap) deleted_lines_mark(lnum, 1L); } } - redraw_curbuf_later(VALID); + redraw_curbuf_later(UPD_VALID); } } } @@ -6571,8 +5488,8 @@ bool changedir_func(char *new_dir, CdScope scope) new_dir = pdir; } - if (os_dirname(NameBuff, MAXPATHL) == OK) { - pdir = (char *)vim_strsave(NameBuff); + if (os_dirname((char_u *)NameBuff, MAXPATHL) == OK) { + pdir = xstrdup(NameBuff); } else { pdir = NULL; } @@ -6585,7 +5502,7 @@ bool changedir_func(char *new_dir, CdScope scope) if (*new_dir == NUL && p_cdh) { #endif // Use NameBuff for home directory name. - expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); + expand_env("$HOME", NameBuff, MAXPATHL); new_dir = (char *)NameBuff; } @@ -6654,7 +5571,7 @@ void ex_cd(exarg_T *eap) /// ":pwd". static void ex_pwd(exarg_T *eap) { - if (os_dirname(NameBuff, MAXPATHL) == OK) { + if (os_dirname((char_u *)NameBuff, MAXPATHL) == OK) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(NameBuff); #endif @@ -6685,17 +5602,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 +5729,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 +5742,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 +5760,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 +5822,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 +5851,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 +5942,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 +5965,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 +5984,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 +5994,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 +6023,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 +6055,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_all_later(UPD_NOT_VALID); + redraw_cmdline = true; } - update_screen(eap->forceit ? NOT_VALID - : VIsual_active ? INVERTED : 0); + update_screen(eap->forceit ? UPD_NOT_VALID + : VIsual_active ? UPD_INVERTED : 0); if (need_maketitle) { maketitle(); } @@ -7180,13 +6090,13 @@ 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 { status_redraw_curbuf(); } - update_screen(VIsual_active ? INVERTED : 0); + update_screen(VIsual_active ? UPD_INVERTED : 0); RedrawingDisabled = r; p_lz = p; ui_flush(); @@ -7245,11 +6155,9 @@ 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)) { + if (os_isdir((char *)fname)) { semsg(_(e_isadir2), fname); return NULL; } @@ -7259,6 +6167,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 +6178,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 +6264,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 +6282,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 +6309,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 +6428,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 +6449,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 +6457,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 = skip_regexp(eap->arg, '/', p_magic, NULL); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -7594,7 +6499,7 @@ static void ex_pedit(exarg_T *eap) if (curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } g_do_tagpreview = 0; @@ -7672,6 +6577,7 @@ enum { SPEC_SFILE, SPEC_SLNUM, SPEC_STACK, + SPEC_SCRIPT, SPEC_AFILE, SPEC_ABUF, SPEC_AMATCH, @@ -7686,7 +6592,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] = "#", @@ -7697,6 +6602,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) [SPEC_SFILE] = "<sfile>", // ":so" file name [SPEC_SLNUM] = "<slnum>", // ":so" file line number [SPEC_STACK] = "<stack>", // call stack + [SPEC_SCRIPT] = "<script>", // script file name [SPEC_AFILE] = "<afile>", // autocommand file name [SPEC_ABUF] = "<abuf>", // autocommand buffer number [SPEC_AMATCH] = "<amatch>", // autocommand match name @@ -7705,8 +6611,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); @@ -7718,40 +6624,40 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// Evaluate cmdline variables. /// -/// change '%' to curbuf->b_ffname -/// '#' to curwin->w_alt_fnum -/// '<cword>' to word under the cursor -/// '<cWORD>' to WORD under the cursor -/// '<cexpr>' to C-expression under the cursor -/// '<cfile>' to path name under the cursor -/// '<sfile>' to sourced file name -/// '<slnum>' to sourced file line number -/// '<afile>' to file name for autocommand -/// '<abuf>' to buffer number for autocommand -/// '<amatch>' to matching name for autocommand +/// change "%" to curbuf->b_ffname +/// "#" to curwin->w_alt_fnum +/// "<cword>" to word under the cursor +/// "<cWORD>" to WORD under the cursor +/// "<cexpr>" to C-expression under the cursor +/// "<cfile>" to path name under the cursor +/// "<sfile>" to sourced file name +/// "<stack>" to call stack +/// "<script>" to current script name +/// "<slnum>" to sourced file line number +/// "<afile>" to file name for autocommand +/// "<abuf>" to buffer number for autocommand +/// "<amatch>" to matching name for autocommand /// /// When an error is detected, "errormsg" is set to a non-NULL pointer (may be /// "" for error without a message) and NULL is returned. /// -/// @param src pointer into commandline -/// @param srcstart beginning of valid memory for src -/// @param usedlen characters after src that are used -/// @param lnump line number for :e command, or NULL -/// @param errormsg pointer to error message -/// @param escaped return value has escaped white space (can be NULL) +/// @param src pointer into commandline +/// @param srcstart beginning of valid memory for src +/// @param usedlen characters after src that are used +/// @param lnump line number for :e command, or NULL +/// @param errormsg pointer to error message +/// @param escaped return value has escaped white space (can be NULL) +/// @param empty_is_error empty result is considered an error /// /// @return an allocated string if a valid match was found. /// Returns NULL if no match was found. "usedlen" then still contains the /// number of characters to skip. char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, - int *escaped) + int *escaped, bool empty_is_error) { - 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 +6665,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 +6722,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 +6753,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 +6818,46 @@ 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; + result = estack_sfile(ESTACK_SFILE); if (result == NULL) { - *errormsg = _("E498: no :source file name to substitute for \"<sfile>\""); + *errormsg = _(e_no_source_file_name_to_substitute_for_sfile); return NULL; } + resultbuf = result; // remember allocated string + break; + case SPEC_STACK: // call stack + result = estack_sfile(ESTACK_STACK); + if (result == NULL) { + *errormsg = _(e_no_call_stack_to_substitute_for_stack); + return NULL; + } + resultbuf = result; // remember allocated string + break; + case SPEC_SCRIPT: // script file name + result = estack_sfile(ESTACK_SCRIPT); + if (result == NULL) { + *errormsg = _(e_no_script_file_name_to_substitute_for_script); + 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,7 +6883,8 @@ 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)++; - if ((s = (char *)STRRCHR(result, '.')) != NULL + char *s; + if ((s = strrchr(result, '.')) != NULL && s >= path_tail(result)) { resultlen = (size_t)(s - result); } @@ -7981,11 +6899,13 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum } if (resultlen == 0 || valid != VALID_HEAD + VALID_PATH) { - if (valid != VALID_HEAD + VALID_PATH) { - // xgettext:no-c-format - *errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\""); - } else { - *errormsg = _("E500: Evaluates to an empty string"); + if (empty_is_error) { + if (valid != VALID_HEAD + VALID_PATH) { + // xgettext:no-c-format + *errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\""); + } else { + *errormsg = _("E500: Evaluates to an empty string"); + } } result = NULL; } else { @@ -7995,88 +6915,22 @@ 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; + char *result = xstrdup(arg); - 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, + true); if (errormsg != NULL) { if (*errormsg) { emsg(errormsg); @@ -8088,8 +6942,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,18 +6961,16 @@ 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 = p_shada; if (*p_shada == NUL) { - p_shada = (char_u *)"'100"; + p_shada = "'100"; } if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) { (void)shada_read_everything(eap->arg, eap->forceit, false); } else { shada_write_file(eap->arg, eap->forceit); } - p_shada = (char_u *)save_shada; + p_shada = save_shada; } /// Make a dialog message in "buff[DIALOG_MSG_SIZE]". @@ -8135,51 +6987,20 @@ void dialog_msg(char *buff, char *format, char *fname) static void ex_behave(exarg_T *eap) { if (STRCMP(eap->arg, "mswin") == 0) { - set_option_value("selection", 0L, "exclusive", 0); - set_option_value("selectmode", 0L, "mouse,key", 0); - set_option_value("mousemodel", 0L, "popup", 0); - set_option_value("keymodel", 0L, "startsel,stopsel", 0); + set_option_value_give_err("selection", 0L, "exclusive", 0); + set_option_value_give_err("selectmode", 0L, "mouse,key", 0); + set_option_value_give_err("mousemodel", 0L, "popup", 0); + set_option_value_give_err("keymodel", 0L, "startsel,stopsel", 0); } else if (STRCMP(eap->arg, "xterm") == 0) { - set_option_value("selection", 0L, "inclusive", 0); - set_option_value("selectmode", 0L, "", 0); - set_option_value("mousemodel", 0L, "extend", 0); - set_option_value("keymodel", 0L, "", 0); + set_option_value_give_err("selection", 0L, "inclusive", 0); + set_option_value_give_err("selectmode", 0L, "", 0); + set_option_value_give_err("mousemodel", 0L, "extend", 0); + set_option_value_give_err("keymodel", 0L, "", 0); } else { semsg(_(e_invarg2), eap->arg); } } -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":behave {mswin,xterm}" command. -char *get_behave_arg(expand_T *xp, int idx) -{ - if (idx == 0) { - return "mswin"; - } - if (idx == 1) { - return "xterm"; - } - return NULL; -} - -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":messages {clear}" command. -char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx == 0) { - return "clear"; - } - return NULL; -} - -char *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) -{ - if (idx == 0) { - return "<buffer>"; - } - return NULL; -} - static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; @@ -8193,10 +7014,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 +7023,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) { @@ -8294,7 +7115,7 @@ static void ex_setfiletype(exarg_T *eap) arg += 9; } - set_option_value("filetype", 0L, arg, OPT_LOCAL); + set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL); if (arg != eap->arg) { did_filetype = false; } @@ -8320,7 +7141,7 @@ void set_no_hlsearch(bool flag) static void ex_nohlsearch(exarg_T *eap) { set_no_hlsearch(true); - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } static void ex_fold(exarg_T *eap) @@ -8342,7 +7163,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..6dddafcfcb 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" @@ -45,20 +45,19 @@ // there or even outside the try conditional. Try conditionals may be nested. // Configuration whether an exception is thrown on error or interrupt. When -// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// the preprocessor macros below evaluate to false, an error (did_emsg) or // interrupt (got_int) under an active try conditional terminates the script // after the non-active finally clauses of all active try conditionals have been // executed. Otherwise, errors and/or interrupts are converted into catchable -// exceptions, which terminate the script only if not caught. For user -// exceptions, only current_exception is set. (Note: got_int can be set -// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// exceptions (did_throw additionally set), which terminate the script only if +// not caught. For user exceptions, only did_throw is set. (Note: got_int can +// be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not // a reliant test that the exception currently being thrown is an interrupt // exception. Similarly, did_emsg can be set afterwards on an error in an -// (unskipped) conditional command inside an inactive conditional, so -// current_exception && did_emsg is not a reliant test that the exception -// currently being thrown is an error exception.) - The macros can be defined -// as expressions checking for a variable that is allowed to be changed during -// execution of a script. +// (unskipped) conditional command inside an inactive conditional, so did_throw +// && did_emsg is not a reliant test that the exception currently being thrown +// is an error exception.) - The macros can be defined as expressions checking +// for a variable that is allowed to be changed during execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -71,7 +70,7 @@ #define CHECK_SKIP \ (did_emsg \ || got_int \ - || current_exception \ + || did_throw \ || (cstack->cs_idx > 0 \ && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) @@ -82,14 +81,14 @@ static void discard_pending_return(typval_T *p) /* * When several errors appear in a row, setting "force_abort" is delayed until - * the failing command returned. "cause_abort" is set to TRUE meanwhile, in + * the failing command returned. "cause_abort" is set to true meanwhile, in * order to indicate that situation. This is useful when "force_abort" was set * during execution of a function call from an expression: the aborting of the * expression evaluation is done without producing any error messages, but all * error messages on parsing errors during the expression evaluation are given * (even if a try conditional is active). */ -static int cause_abort = FALSE; +static int cause_abort = false; /// @return true when immediately aborting on error, or when an interrupt /// occurred or an exception was thrown but not caught. @@ -104,7 +103,7 @@ static int cause_abort = FALSE; /// value. "got_int" is also set by calling interrupt(). int aborting(void) { - return (did_emsg && force_abort) || got_int || current_exception; + return (did_emsg && force_abort) || got_int || did_throw; } /// The value of "force_abort" is temporarily reset by the first emsg() call @@ -114,11 +113,11 @@ int aborting(void) void update_force_abort(void) { if (cause_abort) { - force_abort = TRUE; + force_abort = true; } } -/// @return TRUE if a command with a subcommand resulting in "retcode" should +/// @return true if a command with a subcommand resulting in "retcode" should /// abort the script processing. Can be used to suppress an autocommand after /// execution of a failing subcommand as long as the error message has not been /// displayed and actually caused the abortion. @@ -127,7 +126,7 @@ int should_abort(int retcode) return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting(); } -/// @return TRUE if a function with the "abort" flag should not be considered +/// @return true if a function with the "abort" flag should not be considered /// ended on an error. This means that parsing commands is continued in order /// to find finally clauses to be executed, and that some errors in skipped /// commands are still reported. @@ -151,8 +150,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 @@ -175,7 +174,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) */ if (!did_emsg) { cause_abort = force_abort; - force_abort = FALSE; + force_abort = false; } /* @@ -186,7 +185,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) * currently throwing an exception, do nothing. The message text will * then be stored to v:errmsg by emsg() without displaying it. */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) { return false; } @@ -206,7 +205,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) * Ensure that all commands in nested function calls and sourced files * are aborted immediately. */ - cause_abort = TRUE; + cause_abort = true; /* * When an exception is being thrown, some commands (like conditionals) are @@ -217,7 +216,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) * exception currently being thrown to prevent it from being caught. Just * execute finally clauses and terminate. */ - if (current_exception) { + if (did_throw) { // When discarding an interrupt exception, reset got_int to prevent the // same interrupt being converted to an exception again and discarding // the error exception we are about to throw here. @@ -254,7 +253,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 +274,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; } @@ -312,8 +317,8 @@ void do_errthrow(cstack_T *cstack, char *cmdname) * are aborted immediately. */ if (cause_abort) { - cause_abort = FALSE; - force_abort = TRUE; + cause_abort = false; + force_abort = true; } // If no exception is to be thrown or the conversion should be done after @@ -328,7 +333,7 @@ void do_errthrow(cstack_T *cstack, char *cmdname) if (cstack != NULL) { do_throw(cstack); } else { - need_rethrow = TRUE; + need_rethrow = true; } } *msg_list = NULL; @@ -337,13 +342,13 @@ void do_errthrow(cstack_T *cstack, char *cmdname) /// do_intthrow(): Replace the current exception by an interrupt or interrupt /// exception if appropriate. /// -/// @return TRUE if the current exception is discarded or, -/// FALSE otherwise. +/// @return true if the current exception is discarded or, +/// false otherwise. int do_intthrow(cstack_T *cstack) { // If no interrupt occurred or no try conditional is active and no exception // is being thrown, do nothing (for compatibility of non-EH scripts). - if (!got_int || (trylevel == 0 && !current_exception)) { + if (!got_int || (trylevel == 0 && !did_throw)) { return false; } @@ -352,7 +357,7 @@ int do_intthrow(cstack_T *cstack) // The interrupt aborts everything except for executing finally clauses. // Discard any user or error or interrupt exception currently being // thrown. - if (current_exception) { + if (did_throw) { discard_current_exception(); } } else { @@ -363,7 +368,7 @@ int do_intthrow(cstack_T *cstack) // will be terminated then. - If an interrupt exception is already // being thrown, do nothing. - if (current_exception) { + if (did_throw) { if (current_exception->type == ET_INTERRUPT) { return false; } @@ -389,7 +394,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 +474,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,20 +483,30 @@ 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; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } 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 + msg_scroll = true; // always scroll up, don't overwrite } smsg(_("Exception thrown: %s"), excp->value); msg_puts("\n"); // don't overwrite this either @@ -499,7 +514,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 { @@ -538,13 +553,13 @@ static void discard_exception(except_T *excp, bool was_finished) saved_IObuff = (char *)vim_strsave(IObuff); if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } 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 + msg_scroll = true; // always scroll up, don't overwrite } smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), @@ -580,6 +595,7 @@ void discard_current_exception(void) } // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. + did_throw = false; need_rethrow = false; } @@ -606,13 +622,13 @@ static void catch_exception(except_T *excp) int save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } 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 + msg_scroll = true; // always scroll up, don't overwrite } smsg(_("Exception caught: %s"), excp->value); msg_puts("\n"); // don't overwrite this either @@ -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 { @@ -710,14 +726,14 @@ static void report_pending(int action, int pending, void *value) break; case CSTP_RETURN: // ":return" command producing value, allocated - s = (char *)get_return_cmd(value); + s = get_return_cmd(value); break; default: if (pending & CSTP_THROW) { vim_snprintf((char *)IObuff, IOSIZE, mesg, _("Exception")); - mesg = (char *)concat_str(IObuff, (char_u *)": %s"); + mesg = concat_str((char *)IObuff, ": %s"); s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); @@ -730,14 +746,14 @@ static void report_pending(int action, int pending, void *value) save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + 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; } @@ -814,7 +830,7 @@ void ex_if(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E579: :if nesting too deep"); } else { - ++cstack->cs_idx; + cstack->cs_idx++; cstack->cs_flags[cstack->cs_idx] = 0; skip = CHECK_SKIP; @@ -854,7 +870,7 @@ void ex_endif(exarg_T *eap) (void)do_intthrow(eap->cstack); } - --eap->cstack->cs_idx; + eap->cstack->cs_idx--; } } @@ -948,8 +964,8 @@ void ex_while(exarg_T *eap) * cstack entry. */ if ((cstack->cs_lflags & CSL_HAD_LOOP) == 0) { - ++cstack->cs_idx; - ++cstack->cs_looplevel; + cstack->cs_idx++; + cstack->cs_looplevel++; cstack->cs_line[cstack->cs_idx] = -1; } cstack->cs_flags[cstack->cs_idx] = @@ -971,7 +987,7 @@ void ex_while(exarg_T *eap) // Jumping here from a ":continue" or ":endfor": use the // previously evaluated list. fi = cstack->cs_forinfo[cstack->cs_idx]; - error = FALSE; + error = false; } else { // Evaluate the argument and get the info in a structure. fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip); @@ -982,7 +998,7 @@ void ex_while(exarg_T *eap) if (!error && fi != NULL && !skip) { result = next_for_item(fi, eap->arg); } else { - result = FALSE; + result = false; } if (!result) { @@ -1102,7 +1118,7 @@ void ex_endwhile(exarg_T *eap) eap->errmsg = _(e_endtry); } // Try to find the matching ":while" and report what's missing. - for (idx = cstack->cs_idx; idx > 0; --idx) { + for (idx = cstack->cs_idx; idx > 0; idx--) { fl = cstack->cs_flags[idx]; if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { // Give up at a try conditional not in its finally clause. @@ -1115,7 +1131,7 @@ void ex_endwhile(exarg_T *eap) } } // Cleanup and rewind all contained (and unclosed) conditionals. - (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, FALSE); + (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false); rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel); } else if (cstack->cs_flags[cstack->cs_idx] & CSF_TRUE && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE) @@ -1170,7 +1186,7 @@ void ex_throw(exarg_T *eap) void do_throw(cstack_T *cstack) { int idx; - int inactivate_try = FALSE; + int inactivate_try = false; // // Cleanup and deactivate up to the next surrounding try conditional that @@ -1183,14 +1199,14 @@ void do_throw(cstack_T *cstack) // #ifndef THROW_ON_ERROR_TRUE if (did_emsg && !THROW_ON_ERROR) { - inactivate_try = TRUE; - did_emsg = FALSE; + inactivate_try = true; + did_emsg = false; } #endif #ifndef THROW_ON_INTERRUPT_TRUE if (got_int && !THROW_ON_INTERRUPT) { - inactivate_try = TRUE; - got_int = FALSE; + inactivate_try = true; + got_int = false; } #endif idx = cleanup_conditionals(cstack, 0, inactivate_try); @@ -1221,6 +1237,8 @@ void do_throw(cstack_T *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } + + did_throw = true; } /// Handle ":try" @@ -1232,8 +1250,8 @@ void ex_try(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E601: :try nesting too deep"); } else { - ++cstack->cs_idx; - ++cstack->cs_trylevel; + cstack->cs_idx++; + cstack->cs_trylevel++; cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; @@ -1298,7 +1316,7 @@ void ex_catch(exarg_T *eap) eap->errmsg = get_end_emsg(cstack); skip = true; } - for (idx = cstack->cs_idx; idx > 0; --idx) { + for (idx = cstack->cs_idx; idx > 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { break; } @@ -1317,10 +1335,10 @@ void ex_catch(exarg_T *eap) if (ends_excmd(*eap->arg)) { // no argument, catch all errors pat = ".*"; end = NULL; - eap->nextcmd = (char *)find_nextcmd((char_u *)eap->arg); + eap->nextcmd = find_nextcmd(eap->arg); } else { pat = eap->arg + 1; - end = (char *)skip_regexp((char_u *)pat, *eap->arg, true, NULL); + end = skip_regexp(pat, *eap->arg, true, NULL); } if (!give_up) { @@ -1329,7 +1347,7 @@ void ex_catch(exarg_T *eap) * corresponding try block never got active (because of an inactive * surrounding conditional or after an error or interrupt or throw). */ - if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) { skip = true; } @@ -1360,7 +1378,7 @@ void ex_catch(exarg_T *eap) *end = NUL; } save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; // Disable error messages, it will make current exception // invalid emsg_off++; @@ -1390,10 +1408,10 @@ void ex_catch(exarg_T *eap) } if (caught) { - // Make this ":catch" clause active and reset did_emsg and got_int. - // Put the exception on the caught stack. + // Make this ":catch" clause active and reset did_emsg, got_int, + // and did_throw. Put the exception on the caught stack. cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = false; + did_emsg = got_int = did_throw = false; catch_exception((except_T *)cstack->cs_exception[idx]); // It's mandatory that the current exception is stored in the cstack // so that it can be discarded at the next ":catch", ":finally", or @@ -1403,10 +1421,6 @@ void ex_catch(exarg_T *eap) if (cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_catch()"); } - // Discarding current_exceptions happens based on what is stored in - // cstack->cs_exception, *all* calls to discard_current_exception() are - // (and must be) guarded by current_exception check. - current_exception = NULL; } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1418,12 +1432,12 @@ void ex_catch(exarg_T *eap) * a ":continue", ":break", ":return", or ":finish", discard the * pending action. */ - cleanup_conditionals(cstack, CSF_TRY, TRUE); + cleanup_conditionals(cstack, CSF_TRY, true); } } if (end != NULL) { - eap->nextcmd = (char *)find_nextcmd((char_u *)end); + eap->nextcmd = find_nextcmd(end); } } @@ -1431,7 +1445,7 @@ void ex_catch(exarg_T *eap) void ex_finally(exarg_T *eap) { int idx; - int skip = FALSE; + int skip = false; int pending = CSTP_NONE; cstack_T *const cstack = eap->cstack; @@ -1440,7 +1454,7 @@ void ex_finally(exarg_T *eap) } else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); - for (idx = cstack->cs_idx - 1; idx > 0; --idx) { + for (idx = cstack->cs_idx - 1; idx > 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { break; } @@ -1461,14 +1475,12 @@ void ex_finally(exarg_T *eap) rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, &cstack->cs_looplevel); - /* - * Don't do something when the corresponding try block never got active - * (because of an inactive surrounding conditional or after an error or - * interrupt or throw) or for a ":finally" without ":try" or a multiple - * ":finally". After every other error (did_emsg or the conditional - * errors detected above) or after an interrupt (got_int) or an - * exception (current_exception), the finally clause must be executed. - */ + // Don't do something when the corresponding try block never got active + // (because of an inactive surrounding conditional or after an error or + // interrupt or throw) or for a ":finally" without ":try" or a multiple + // ":finally". After every other error (did_emsg or the conditional + // errors detected above) or after an interrupt (got_int) or an + // exception (did_throw), the finally clause must be executed. skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!skip) { @@ -1491,24 +1503,22 @@ void ex_finally(exarg_T *eap) * ":continue", ":break", ":finish", or ":return" from the preceding * try block or catch clause. */ - cleanup_conditionals(cstack, CSF_TRY, FALSE); - - /* - * Make did_emsg, got_int, current_exception pending. If set, they - * overrule a pending ":continue", ":break", ":return", or ":finish". - * Then we have particularly to discard a pending return value (as done - * by the call to cleanup_conditionals() above when did_emsg or - * got_int is set). The pending values are restored by the - * ":endtry", except if there is a new error, interrupt, exception, - * ":continue", ":break", ":return", or ":finish" in the following - * finally clause. A missing ":endwhile", ":endfor" or ":endif" - * detected here is treated as if did_emsg and current_exception had - * already been set, respectively in case that the error is not - * converted to an exception, current_exception had already been unset. - * We must not set did_emsg here since that would suppress the - * error message. - */ - if (pending == CSTP_ERROR || did_emsg || got_int || current_exception) { + cleanup_conditionals(cstack, CSF_TRY, false); + + // Make did_emsg, got_int, did_throw pending. If set, they overrule + // a pending ":continue", ":break", ":return", or ":finish". Then + // we have particularly to discard a pending return value (as done + // by the call to cleanup_conditionals() above when did_emsg or + // got_int is set). The pending values are restored by the + // ":endtry", except if there is a new error, interrupt, exception, + // ":continue", ":break", ":return", or ":finish" in the following + // finally clause. A missing ":endwhile", ":endfor" or ":endif" + // detected here is treated as if did_emsg and did_throw had + // already been set, respectively in case that the error is not + // converted to an exception, did_throw had already been unset. + // We must not set did_emsg here since that would suppress the + // error message. + if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { report_discard_pending(CSTP_RETURN, cstack->cs_rettv[cstack->cs_idx]); @@ -1517,7 +1527,7 @@ void ex_finally(exarg_T *eap) if (pending == CSTP_ERROR && !did_emsg) { pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); } else { - pending |= (current_exception ? CSTP_THROW : 0); + pending |= (did_throw ? CSTP_THROW : 0); } pending |= did_emsg ? CSTP_ERROR : 0; pending |= got_int ? CSTP_INTERRUPT : 0; @@ -1531,19 +1541,16 @@ void ex_finally(exarg_T *eap) // exception. When emsg() is called for a missing ":endif" or // a missing ":endwhile"/":endfor" detected here, the // exception will be discarded. - if (current_exception - && cstack->cs_exception[cstack->cs_idx] != current_exception) { + if (did_throw && cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_finally()"); } } - /* - * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - * got_int, and current_exception and make the finally clause active. - * This will happen after emsg() has been called for a missing - * ":endif" or a missing ":endwhile"/":endfor" detected here, so - * that the following finally clause will be executed even then. - */ + // Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, + // got_int, and did_throw and make the finally clause active. + // This will happen after emsg() has been called for a missing + // ":endif" or a missing ":endwhile"/":endfor" detected here, so + // that the following finally clause will be executed even then. cstack->cs_lflags |= CSL_HAD_FINA; } } @@ -1571,8 +1578,7 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - bool skip = did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + bool skip = did_emsg || got_int || did_throw || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); @@ -1586,14 +1592,12 @@ void ex_endtry(exarg_T *eap) &cstack->cs_looplevel); skip = true; - /* - * If an exception is being thrown, discard it to prevent it from - * being rethrown at the end of this function. It would be - * discarded by the error message, anyway. Resets current_exception. - * This does not affect the script termination due to the error - * since "trylevel" is decremented after emsg() has been called. - */ - if (current_exception) { + // If an exception is being thrown, discard it to prevent it from + // being rethrown at the end of this function. It would be + // discarded by the error message, anyway. Resets did_throw. + // This does not affect the script termination due to the error + // since "trylevel" is decremented after emsg() has been called. + if (did_throw) { discard_current_exception(); } @@ -1608,7 +1612,7 @@ void ex_endtry(exarg_T *eap) * a finally clause, we need to rethrow it after closing the try * conditional. */ - if (current_exception + if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) && !(cstack->cs_flags[idx] & CSF_FINALLY)) { rethrow = true; @@ -1632,10 +1636,10 @@ void ex_endtry(exarg_T *eap) if (got_int) { skip = true; (void)do_intthrow(cstack); - // The do_intthrow() call may have reset current_exception or + // The do_intthrow() call may have reset did_throw or // cstack->cs_pending[idx]. rethrow = false; - if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) { rethrow = true; } } @@ -1667,7 +1671,7 @@ void ex_endtry(exarg_T *eap) * after errors except when this ":endtry" is not within a ":try". * Restore "emsg_silent" if it has been reset by this try conditional. */ - (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE); + (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, true); if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { cstack->cs_idx--; @@ -1696,16 +1700,16 @@ void ex_endtry(exarg_T *eap) ex_break(eap); break; case CSTP_RETURN: - do_return(eap, FALSE, FALSE, rettv); + do_return(eap, false, false, rettv); break; case CSTP_FINISH: - do_finish(eap, FALSE); + do_finish(eap, false); break; // When the finally clause was entered due to an error, // interrupt or throw (as opposed to a ":continue", ":break", // ":return", or ":finish"), restore the pending values of - // did_emsg, got_int, and current_exception. This is skipped, if there + // did_emsg, got_int, and did_throw. This is skipped, if there // was a new error, interrupt, throw, ":continue", ":break", // ":return", or ":finish". in the finally clause. default: @@ -1752,35 +1756,32 @@ void enter_cleanup(cleanup_T *csp) { int pending = CSTP_NONE; - /* - * Postpone did_emsg, got_int, current_exception. The pending values will be - * restored by leave_cleanup() except if there was an aborting error, - * interrupt, or uncaught exception after this function ends. - */ - if (did_emsg || got_int || current_exception || need_rethrow) { + // Postpone did_emsg, got_int, did_throw. The pending values will be + // restored by leave_cleanup() except if there was an aborting error, + // interrupt, or uncaught exception after this function ends. + if (did_emsg || got_int || did_throw || need_rethrow) { csp->pending = (did_emsg ? CSTP_ERROR : 0) | (got_int ? CSTP_INTERRUPT : 0) - | (current_exception ? CSTP_THROW : 0) + | (did_throw ? CSTP_THROW : 0) | (need_rethrow ? CSTP_THROW : 0); - // If we are currently throwing an exception, save it as well. On an error - // not yet converted to an exception, update "force_abort" and reset - // "cause_abort" (as do_errthrow() would do). This is needed for the - // do_cmdline() call that is going to be made for autocommand execution. We - // need not save *msg_list because there is an extra instance for every call - // of do_cmdline(), anyway. - if (current_exception || need_rethrow) { + // If we are currently throwing an exception (did_throw), save it as + // well. On an error not yet converted to an exception, update + // "force_abort" and reset "cause_abort" (as do_errthrow() would do). + // This is needed for the do_cmdline() call that is going to be made + // for autocommand execution. We need not save *msg_list because + // there is an extra instance for every call of do_cmdline(), anyway. + if (did_throw || need_rethrow) { csp->exception = current_exception; current_exception = NULL; } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; - cause_abort = FALSE; + cause_abort = false; } } - did_emsg = got_int = need_rethrow = false; - current_exception = NULL; + did_emsg = got_int = did_throw = need_rethrow = false; // Report if required by the 'verbose' option or when debugging. report_make_pending(pending, csp->exception); @@ -1847,10 +1848,10 @@ void leave_cleanup(cleanup_T *csp) // enter_cleanup() was called, let "cause_abort" take the part of // "force_abort" (as done by cause_errthrow()). cause_abort = force_abort; - force_abort = FALSE; + force_abort = false; } - // Restore the pending values of did_emsg, got_int, and current_exception. + // Restore the pending values of did_emsg, got_int, and did_throw. if (pending & CSTP_ERROR) { did_emsg = true; } @@ -1858,7 +1859,7 @@ void leave_cleanup(cleanup_T *csp) got_int = true; } if (pending & CSTP_THROW) { - need_rethrow = true; // current_exception will be set by do_one_cmd() + need_rethrow = true; // did_throw will be set by do_one_cmd() } // Report if required by the 'verbose' option or when debugging. @@ -1879,7 +1880,7 @@ void leave_cleanup(cleanup_T *csp) /// inactive itself (a try conditional not in its finally /// clause possibly find before is always made inactive). /// -/// If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, the saved +/// If "inclusive" is true and "searched_cond" is CSF_TRY|CSF_SILENT, the saved /// former value of "emsg_silent", if reset when the try conditional finally /// reached was entered, is restored (used by ex_endtry()). This is normally /// done only when such a try conditional is left. @@ -1888,9 +1889,9 @@ void leave_cleanup(cleanup_T *csp) int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) { int idx; - int stop = FALSE; + int stop = false; - for (idx = cstack->cs_idx; idx >= 0; --idx) { + for (idx = cstack->cs_idx; idx >= 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { /* * Discard anything pending in a finally clause and continue the @@ -1952,20 +1953,20 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) if (searched_cond == 0 && !inclusive) { break; } - stop = TRUE; + stop = true; } } } // Stop on the searched conditional type (even when the surrounding // conditional is not active or something has been made pending). - // If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, + // If "inclusive" is true and "searched_cond" is CSF_TRY|CSF_SILENT, // check first whether "emsg_silent" needs to be restored. if (cstack->cs_flags[idx] & searched_cond) { if (!inclusive) { break; } - stop = TRUE; + stop = true; } cstack->cs_flags[idx] &= ~CSF_ACTIVE; if (stop && searched_cond != (CSF_TRY | CSF_SILENT)) { @@ -2020,7 +2021,7 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) { free_for_info(cstack->cs_forinfo[cstack->cs_idx]); } - --cstack->cs_idx; + cstack->cs_idx--; } } @@ -2030,7 +2031,7 @@ void ex_endfunction(exarg_T *eap) emsg(_("E193: :endfunction not inside a function")); } -/// @return TRUE if the string "p" looks like a ":while" or ":for" command. +/// @return true if the string "p" looks like a ":while" or ":for" command. int has_loop_cmd(char *p) { int len; @@ -2038,7 +2039,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) { @@ -2048,7 +2049,7 @@ int has_loop_cmd(char *p) } if ((p[0] == 'w' && p[1] == 'h') || (p[0] == 'f' && p[1] == 'o' && p[2] == 'r')) { - return TRUE; + return true; } - return FALSE; + return false; } 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..0998bdd867 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -12,23 +12,22 @@ #include <string.h> #include "nvim/api/extmark.h" -#include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/arabic.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.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" -#include "nvim/eval/userfunc.h" #include "nvim/event/loop.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" @@ -37,113 +36,47 @@ #include "nvim/garray.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" -#include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/keycodes.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" -#include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/mbyte.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" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" #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" #include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/tag.h" #include "nvim/ui.h" #include "nvim/undo.h" +#include "nvim/usercmd.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" #include "nvim/window.h" -/// Command-line colors: one chunk -/// -/// Defines a region which has the same highlighting. -typedef struct { - int start; ///< Colored chunk start. - int end; ///< Colored chunk end (exclusive, > start). - int attr; ///< Highlight attr. -} CmdlineColorChunk; - -/// Command-line colors -/// -/// Holds data about all colors. -typedef kvec_t(CmdlineColorChunk) CmdlineColors; - -/// Command-line coloring -/// -/// Holds both what are the colors and what have been colored. Latter is used to -/// suppress unnecessary calls to coloring callbacks. -typedef struct { - unsigned prompt_id; ///< ID of the prompt which was colored last. - char *cmdbuff; ///< What exactly was colored last time or NULL. - CmdlineColors colors; ///< Last colors. -} ColoredCmdline; - -/// Keeps track how much state must be sent to external ui. -typedef enum { - kCmdRedrawNone, - kCmdRedrawPos, - kCmdRedrawAll, -} CmdRedraw; - -// Variables shared between getcmdline(), redrawcmdline() and others. -// These need to be saved when using CTRL-R |, that's why they are in a -// structure. -struct cmdline_info { - char_u *cmdbuff; // pointer to command line buffer - int cmdbufflen; // length of cmdbuff - int cmdlen; // number of chars in command line - int cmdpos; // current cursor position - int cmdspos; // cursor column on screen - int cmdfirstc; // ':', '/', '?', '=', '>' or NUL - int cmdindent; // number of spaces before cmdline - char_u *cmdprompt; // message in front of cmdline - int cmdattr; // attributes for prompt - int overstrike; // Typing mode on the command line. Shared by - // getcmdline() and put_on_cmdline(). - expand_T *xpc; // struct being used for expansion, xp_pattern - // may point into cmdbuff - int xp_context; // type of expansion - char_u *xp_arg; // user-defined expansion arg - int input_fn; // when TRUE Invoked for input() function - unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. - Callback highlight_callback; ///< Callback used for coloring user input. - ColoredCmdline last_colors; ///< Last cmdline colors - int level; // current cmdline level - struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state - char special_char; ///< last putcmdline char (used for redraws) - bool special_shift; ///< shift of last putcmdline char - CmdRedraw redraw_state; ///< needed redraw for external cmdline -}; - /// 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; @@ -172,8 +105,8 @@ typedef struct command_line_state { long count; int indent; int c; - int gotesc; // TRUE when <ESC> just typed - int do_abbr; // when TRUE check for abbr. + int gotesc; // true when <ESC> just typed + int do_abbr; // when true check for abbr. char_u *lookfor; // string to match int hiscnt; // current history line in use int save_hiscnt; // history line before attempting @@ -195,44 +128,49 @@ typedef struct command_line_state { long *b_im_ptr; } CommandLineState; -typedef struct cmdline_info CmdlineInfo; +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; /// The current cmdline_info. It is initialized in getcmdline() and after that /// used by other functions. When invoking getcmdline() recursively it needs /// to be saved with save_cmdline() and restored with restore_cmdline(). -static struct cmdline_info ccline; - -static int cmd_showtail; // Only show path tail in lists ? +static CmdlineInfo ccline; static int new_cmdpos; // position set by set_cmdline_pos() /// currently displayed block of context static Array cmdline_block = ARRAY_DICT_INIT; -/* - * Type used by call_user_expand_func - */ -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 /// user interrupting highlight function to not interrupt command-line. static bool getln_interrupted_highlight = false; -// "compl_match_array" points the currently displayed list of entries in the -// popup menu. It is NULL when there is no popup menu. -static pumitem_T *compl_match_array = NULL; -static int compl_match_arraysize; -// First column in cmdline of the matched item for completion. -static int compl_startcol; -static int compl_selected; +static int cedit_key = -1; ///< key value of 'cedit' option #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" @@ -243,26 +181,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,34 +212,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); -} - -/// Completion for |:checkhealth| command. -/// -/// Given to ExpandGeneric() to obtain all available heathcheck names. -/// @param[in] idx Index of the healthcheck item. -/// @param[in] xp Not used. -static char *get_healthcheck_names(expand_T *xp, int idx) -{ - static Object names = OBJECT_INIT; - static unsigned last_gen = 0; - - if (last_gen != last_prompt_id || last_gen == 0) { - Array a = ARRAY_DICT_INIT; - Error err = ERROR_INIT; - Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err); - api_clear_error(&err); - api_free_object(names); - names = res; - last_gen = last_prompt_id; - } - - if (names.type == kObjectTypeArray && idx < (int)names.data.array.size) { - return names.data.array.items[idx].data.string.data; - } - return NULL; + save_viewstate(curwin, &s->init_viewstate); + save_viewstate(curwin, &s->old_viewstate); } // Return true when 'incsearch' highlighting is to be done. @@ -316,7 +228,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 +252,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); @@ -403,7 +315,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s p = skipwhite(p); delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; *search_delim = delim; - end = (char *)skip_regexp((char_u *)p, delim, p_magic, NULL); + end = skip_regexp(p, delim, p_magic, NULL); use_last_pat = end == p && *end == delim; if (end == p && !use_last_pat) { @@ -458,7 +370,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; @@ -506,7 +417,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat if (patlen == 0 && !use_last_pat) { found = 0; set_no_hlsearch(true); // turn off previous highlight - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else { int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; ui_busy_start(); @@ -521,8 +432,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 +467,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); @@ -578,7 +490,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat next_char = ccline.cmdbuff[skiplen + patlen]; ccline.cmdbuff[skiplen + patlen] = NUL; if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(true); } ccline.cmdbuff[skiplen + patlen] = next_char; @@ -590,7 +502,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat curwin->w_redr_status = true; } - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); highlight_match = false; restore_last_search_pattern(); @@ -665,7 +577,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 @@ -675,9 +587,9 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool p_magic = s->magic_save; validate_cursor(); // needed for TAB - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); if (call_update_screen) { - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); } } } @@ -701,7 +613,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init lastwin->w_p_so = 0; set_option_value("ch", 1L, NULL, 0); - update_screen(VALID); // redraw the screen NOW + update_screen(UPD_VALID); // redraw the screen NOW made_cmdheight_nonzero = false; lastwin->w_p_so = save_so; @@ -722,7 +634,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init .ignore_drag_release = true, }; CommandLineState *s = &state; - s->save_p_icm = vim_strsave(p_icm); + s->save_p_icm = vim_strsave((char_u *)p_icm); init_incsearch_state(&s->is_state); CmdlineInfo save_ccline; bool did_save_ccline = false; @@ -736,7 +648,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 +781,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 @@ -942,7 +854,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init s->histype == HIST_SEARCH ? s->firstc : NUL); if (s->firstc == ':') { xfree(new_last_cmdline); - new_last_cmdline = vim_strsave(ccline.cmdbuff); + new_last_cmdline = (char *)vim_strsave(ccline.cmdbuff); } } @@ -973,7 +885,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init State = s->save_State; if (cmdpreview != save_cmdpreview) { cmdpreview = save_cmdpreview; // restore preview state - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } may_trigger_modechanged(); setmouse(); @@ -1006,7 +918,7 @@ theend: // Restore cmdheight set_option_value("ch", 0L, NULL, 0); // Redraw is needed for command line completion - redraw_all_later(CLEAR); + redraw_all_later(UPD_NOT_VALID); made_cmdheight_nonzero = false; } @@ -1111,41 +1023,24 @@ static int command_line_execute(VimState *state, int key) s->c = Ctrl_P; } - // Special translations for 'wildmenu' - if (s->did_wild_list && p_wmnu) { - if (s->c == K_LEFT) { - s->c = Ctrl_P; - } else if (s->c == K_RIGHT) { - s->c = Ctrl_N; - } + if (p_wmnu) { + s->c = wildmenu_translate_key(&ccline, s->c, &s->xpc, s->did_wild_list); } - if (compl_match_array || s->did_wild_list) { - if (s->c == Ctrl_E) { - s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP, - s->firstc != '@'); - } else if (s->c == Ctrl_Y) { - s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, - s->firstc != '@'); + + if (cmdline_pum_active() || s->did_wild_list) { + if (s->c == Ctrl_E || s->c == Ctrl_Y) { + const int wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY; + s->res = nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@'); s->c = Ctrl_E; } } - // Hitting CR after "emenu Name.": complete submenu - if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu - && ccline.cmdpos > 1 - && ccline.cmdbuff[ccline.cmdpos - 1] == '.' - && ccline.cmdbuff[ccline.cmdpos - 2] != '\\' - && (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) { - s->c = K_DOWN; - } - // free expanded names when finished walking through matches if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A && s->c != Ctrl_L) { - if (compl_match_array) { - pum_undisplay(true); - XFREE_CLEAR(compl_match_array); + if (cmdline_pum_active()) { + cmdline_pum_remove(); } if (s->xpc.xp_numfiles != -1) { (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); @@ -1155,174 +1050,11 @@ static int command_line_execute(VimState *state, int key) s->xpc.xp_context = EXPAND_NOTHING; } s->wim_index = 0; - if (p_wmnu && wild_menu_showing != 0) { - const bool skt = KeyTyped; - int old_RedrawingDisabled = RedrawingDisabled; - - if (ccline.input_fn) { - RedrawingDisabled = 0; - } - - if (wild_menu_showing == WM_SCROLLED) { - // Entered command line, move it up - cmdline_row--; - redrawcmd(); - wild_menu_showing = 0; - } else if (save_p_ls != -1) { - // restore 'laststatus' and 'winminheight' - p_ls = save_p_ls; - p_wmh = save_p_wmh; - last_status(false); - update_screen(VALID); // redraw the screen NOW - redrawcmd(); - save_p_ls = -1; - wild_menu_showing = 0; - // don't redraw statusline if WM_LIST is showing - } else if (wild_menu_showing != WM_LIST) { - win_redraw_last_status(topframe); - wild_menu_showing = 0; // must be before redraw_statuslines #8385 - redraw_statuslines(); - } else { - wild_menu_showing = 0; - } - KeyTyped = skt; - if (ccline.input_fn) { - RedrawingDisabled = old_RedrawingDisabled; - } - } + wildmenu_cleanup(&ccline); } - // Special translations for 'wildmenu' - if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { - // Hitting <Down> after "emenu Name.": complete submenu - if (s->c == K_DOWN && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == '.') { - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (s->c == K_UP) { - // Hitting <Up>: Remove one submenu name in front of the - // cursor - int found = false; - - int j = (int)((char_u *)s->xpc.xp_pattern - ccline.cmdbuff); - int i = 0; - while (--j > 0) { - // check for start of menu name - if (ccline.cmdbuff[j] == ' ' - && ccline.cmdbuff[j - 1] != '\\') { - i = j + 1; - break; - } - - // check for start of submenu name - if (ccline.cmdbuff[j] == '.' - && ccline.cmdbuff[j - 1] != '\\') { - if (found) { - i = j + 1; - break; - } else { - found = true; - } - } - } - if (i > 0) { - cmdline_del(i); - } - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - s->xpc.xp_context = EXPAND_NOTHING; - } - } - if ((s->xpc.xp_context == EXPAND_FILES - || s->xpc.xp_context == EXPAND_DIRECTORIES - || s->xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) { - char_u upseg[5]; - - upseg[0] = PATHSEP; - upseg[1] = '.'; - upseg[2] = '.'; - upseg[3] = PATHSEP; - upseg[4] = NUL; - - if (s->c == K_DOWN - && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP - && (ccline.cmdpos < 3 - || ccline.cmdbuff[ccline.cmdpos - 2] != '.' - || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) { - // go down a directory - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0 - && s->c == K_DOWN) { - // If in a direct ancestor, strip off one ../ to go down - int found = false; - - int j = ccline.cmdpos; - int i = (int)((char_u *)s->xpc.xp_pattern - ccline.cmdbuff); - while (--j > i) { - j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j); - if (vim_ispathsep(ccline.cmdbuff[j])) { - found = true; - break; - } - } - if (found - && ccline.cmdbuff[j - 1] == '.' - && ccline.cmdbuff[j - 2] == '.' - && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) { - cmdline_del(j - 2); - s->c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } - } else if (s->c == K_UP) { - // go up a directory - int found = false; - - int j = ccline.cmdpos - 1; - int i = (int)((char_u *)s->xpc.xp_pattern - ccline.cmdbuff); - while (--j > i) { - j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j); - if (vim_ispathsep(ccline.cmdbuff[j]) -#ifdef BACKSLASH_IN_FILENAME - && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[j + 1]) - == NULL -#endif - ) { - if (found) { - i = j + 1; - break; - } else { - found = true; - } - } - } - - if (!found) { - j = i; - } else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) { - j += 4; - } else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0 - && j == i) { - j += 3; - } else { - j = 0; - } - - if (j > 0) { - // TODO(tarruda): this is only for DOS/Unix systems - need to put in - // machine-specific stuff here and in upseg init - cmdline_del(j); - put_on_cmdline(upseg + 1, 3, false); - } else if (ccline.cmdpos > i) { - cmdline_del(i); - } - - // Now complete in the new directory. Set KeyTyped in case the - // Up key came from a mapping. - s->c = (int)p_wc; - KeyTyped = true; - } + if (p_wmnu) { + s->c = wildmenu_process_key(&ccline, s->c, &s->xpc); } // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ e prompts for an expression. @@ -1530,7 +1262,7 @@ static int command_line_execute(VimState *state, int key) } if (s->wim_index < 3) { - ++s->wim_index; + s->wim_index++; } if (s->c == ESC) { @@ -1547,8 +1279,11 @@ static int command_line_execute(VimState *state, int key) // <S-Tab> goes to last match, in a clumsy way if (s->c == K_S_TAB && KeyTyped) { if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK) { - showmatches(&s->xpc, p_wmnu - && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + if (s->xpc.xp_numfiles > 1 + && ((!s->did_wild_list && (wim_flags[s->wim_index] & WIM_LIST)) || p_wmnu)) { + // Trigger the popup menu when wildoptions=pum + showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + } nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); return command_line_changed(s); @@ -1664,8 +1399,8 @@ 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); - update_screen(NOT_VALID); + save_viewstate(curwin, &s->old_viewstate); + update_screen(UPD_NOT_VALID); highlight_match = false; redrawcmdline(); curwin->w_cursor = s->match_end; @@ -1682,12 +1417,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 +1431,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 +1449,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; } @@ -1744,7 +1479,7 @@ static int command_line_handle_key(CommandLineState *s) // delete current character is the same as backspace on next // character, except at end of line if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) { - ++ccline.cmdpos; + ccline.cmdpos++; } if (s->c == K_DEL) { @@ -2068,9 +1803,17 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); case Ctrl_A: // all matches + if (cmdline_pum_active()) { + // As Ctrl-A completes all the matches, close the popup + // menu (if present) + cmdline_pum_cleanup(&ccline); + } + if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) { break; } + s->xpc.xp_context = EXPAND_NOTHING; + s->did_wild_list = false; return command_line_changed(s); case Ctrl_L: @@ -2103,7 +1846,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 +1871,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 +1902,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 +2139,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 +2303,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 +2322,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,11 +2348,11 @@ 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; - update_screen(SOME_VALID); + update_screen(UPD_SOME_VALID); RedrawingDisabled = save_rd; } @@ -2512,53 +2360,22 @@ 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 - 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); - } + // Restore state. + cmdpreview_restore_state(&cpinfo); - 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); return cmdpreview_type != 0; } -static int command_line_changed(CommandLineState *s) +/// Trigger CmdlineChanged autocommands. +static void do_autocmd_cmdlinechanged(int firstc) { - // Trigger CmdlineChanged autocommands. if (has_event(EVENT_CMDLINECHANGED)) { TryState tstate; Error err = ERROR_INIT; @@ -2566,7 +2383,7 @@ static int command_line_changed(CommandLineState *s) dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; - firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); + firstcbuf[0] = (char)firstc; firstcbuf[1] = 0; // set v:event to a dictionary with information about the commandline @@ -2586,6 +2403,12 @@ static int command_line_changed(CommandLineState *s) redrawcmd(); } } +} + +static int command_line_changed(CommandLineState *s) +{ + // Trigger CmdlineChanged autocommands. + do_autocmd_cmdlinechanged(s->firstc > 0 ? s->firstc : '-'); if (s->firstc == ':' && current_sctx.sc_sid == 0 // only if interactive @@ -2597,7 +2420,7 @@ static int command_line_changed(CommandLineState *s) // 'inccommand' preview has been shown. } else if (cmdpreview) { cmdpreview = false; - update_screen(SOME_VALID); // Clear 'inccommand' preview. + update_screen(UPD_SOME_VALID); // Clear 'inccommand' preview. } else { if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) { may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state); @@ -2682,7 +2505,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; @@ -2718,6 +2541,58 @@ char_u *get_cmdprompt(void) return ccline.cmdprompt; } +/// Read the 'wildmode' option, fill wim_flags[]. +int check_opt_wim(void) +{ + char_u new_wim_flags[4]; + int i; + int idx = 0; + + for (i = 0; i < 4; i++) { + new_wim_flags[i] = 0; + } + + for (char *p = p_wim; *p; p++) { + for (i = 0; ASCII_ISALPHA(p[i]); i++) {} + if (p[i] != NUL && p[i] != ',' && p[i] != ':') { + return FAIL; + } + if (i == 7 && STRNCMP(p, "longest", 7) == 0) { + new_wim_flags[idx] |= WIM_LONGEST; + } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { + new_wim_flags[idx] |= WIM_FULL; + } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { + new_wim_flags[idx] |= WIM_LIST; + } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { + new_wim_flags[idx] |= WIM_BUFLASTUSED; + } else { + return FAIL; + } + p += i; + if (*p == NUL) { + break; + } + if (*p == ',') { + if (idx == 3) { + return FAIL; + } + idx++; + } + } + + // fill remaining entries with last flag + while (idx < 3) { + new_wim_flags[idx + 1] = new_wim_flags[idx]; + idx++; + } + + // only when there are no errors, wim_flags[] is changed + for (i = 0; i < 4; i++) { + wim_flags[i] = new_wim_flags[i]; + } + return OK; +} + /// Return true when the text must not be changed and we can't switch to /// another window or buffer. True when editing the command line etc. bool text_locked(void) @@ -2795,7 +2670,7 @@ static int cmd_startcol(void) } /// Compute the column position for a byte position on the command line. -static int cmd_screencol(int bytepos) +int cmd_screencol(int bytepos) { int m; // maximum column @@ -2882,10 +2757,8 @@ static void alloc_cmdbuff(int len) ccline.cmdbufflen = len; } -/* - * Re-allocate the command line to length len + something extra. - */ -static void realloc_cmdbuff(int len) +/// Re-allocate the command line to length len + something extra. +void realloc_cmdbuff(int len) { if (len < ccline.cmdbufflen) { return; // no need to resize @@ -3217,7 +3090,7 @@ color_cmdline_error: /* * Draw part of the cmdline at the current cursor position. But draw stars - * when cmdline_star is TRUE. + * when cmdline_star is true. */ static void draw_cmdline(int start, int len) { @@ -3325,7 +3198,7 @@ static void draw_cmdline(int start, int len) } } - msg_outtrans_len((char_u *)arshape_buf, newlen); + msg_outtrans_len(arshape_buf, newlen); } else { draw_cmdline_no_arabicshape: if (kv_size(ccline.last_colors.colors)) { @@ -3340,7 +3213,7 @@ draw_cmdline_no_arabicshape: chunk.attr); } } else { - msg_outtrans_len(ccline.cmdbuff + start, len); + msg_outtrans_len((char *)ccline.cmdbuff + start, len); } } } @@ -3348,7 +3221,6 @@ draw_cmdline_no_arabicshape: static void ui_ext_cmdline_show(CmdlineInfo *line) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array content; if (cmdline_star) { content = arena_array(&arena, 1); @@ -3393,7 +3265,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) line->special_shift, line->level); } - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } void ui_ext_cmdline_block_append(size_t indent, const char *line) @@ -3472,7 +3344,7 @@ void cmdline_ui_flush(void) /* * Put a character on the command line. Shifts the following text to the - * right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc. + * right when "shift" is true. Used for CTRL-V, CTRL-K, etc. * "c" must be printable (fit in one display cell)! */ void putcmdline(char c, int shift) @@ -3498,7 +3370,7 @@ void putcmdline(char c, int shift) ui_cursor_shape(); } -/// Undo a putcmdline(c, FALSE). +/// Undo a putcmdline(c, false). void unputcmdline(void) { if (cmd_silent) { @@ -3519,9 +3391,9 @@ void unputcmdline(void) /* * Put the given string, of the given length, onto the command line. * If len is -1, then STRLEN() is used to calculate the length. - * If 'redraw' is TRUE then the new part of the command line, and the remaining + * If 'redraw' is true then the new part of the command line, and the remaining * part will be redrawn, otherwise it will not. If this function is called - * twice in a row, then 'redraw' should be FALSE and redrawcmd() should be + * twice in a row, then 'redraw' should be false and redrawcmd() should be * called afterwards. */ void put_on_cmdline(char_u *str, int len, int redraw) @@ -3592,13 +3464,13 @@ 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--; } } } if (redraw && !cmd_silent) { - msg_no_more = TRUE; + msg_no_more = true; i = cmdline_row; cursorcmd(); draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); @@ -3606,7 +3478,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) if (cmdline_row != i || ccline.overstrike) { msg_clr_eos(); } - msg_no_more = FALSE; + msg_no_more = false; } if (KeyTyped) { m = Columns * Rows; @@ -3642,16 +3514,16 @@ void put_on_cmdline(char_u *str, int len, int redraw) /// Save ccline, because obtaining the "=" register may execute "normal :cmd" /// and overwrite it. -static void save_cmdline(struct cmdline_info *ccp) +static void save_cmdline(CmdlineInfo *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 } /// Restore ccline after it has been saved with save_cmdline(). -static void restore_cmdline(struct cmdline_info *ccp) +static void restore_cmdline(CmdlineInfo *ccp) FUNC_ATTR_NONNULL_ALL { ccline = *ccp; @@ -3669,7 +3541,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 +3574,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; @@ -3733,8 +3605,8 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) /* * Put a string on the command line. - * When "literally" is TRUE, insert literally. - * When "literally" is FALSE, insert as typed, but don't leave the command + * When "literally" is true, insert literally. + * When "literally" is false, insert as typed, but don't leave the command * line. */ void cmdline_paste_str(char_u *s, int literally) @@ -3742,7 +3614,7 @@ void cmdline_paste_str(char_u *s, int literally) int c, cv; if (literally) { - put_on_cmdline(s, -1, TRUE); + put_on_cmdline(s, -1, true); } else { while (*s != NUL) { cv = *s; @@ -3760,16 +3632,6 @@ void cmdline_paste_str(char_u *s, int literally) } } -/// Delete characters on the command line, from "from" to the current position. -static void cmdline_del(int from) -{ - assert(ccline.cmdpos <= ccline.cmdlen); - memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos, - (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1); - ccline.cmdlen -= ccline.cmdpos - from; - ccline.cmdpos = from; -} - // This function is called when the screen size changes and with incremental // search and in other situations where the command line may have been // overwritten. @@ -3841,7 +3703,7 @@ void redrawcmd(void) redrawcmdprompt(); // Don't use more prompt, truncate the cmdline if it doesn't fit. - msg_no_more = TRUE; + msg_no_more = true; draw_cmdline(0, ccline.cmdlen); msg_clr_eos(); msg_no_more = false; @@ -3856,7 +3718,7 @@ void redrawcmd(void) * An emsg() before may have set msg_scroll. This is used in normal mode, * in cmdline mode we can reset them now. */ - msg_scroll = FALSE; // next message overwrites cmdline + msg_scroll = false; // next message overwrites cmdline // Typing ':' at the more prompt may set skip_redraw. We don't want this // in cmdline mode. @@ -3877,7 +3739,7 @@ void compute_cmdrow(void) lines_left = cmdline_row; } -static void cursorcmd(void) +void cursorcmd(void) { if (cmd_silent) { return; @@ -3964,462 +3826,6 @@ static int ccheck_abbr(int c) return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos); } -static int sort_func_compare(const void *s1, const void *s2) -{ - char_u *p1 = *(char_u **)s1; - char_u *p2 = *(char_u **)s2; - - if (*p1 != '<' && *p2 == '<') { - return -1; - } - if (*p1 == '<' && *p2 != '<') { - return 1; - } - return STRCMP(p1, p2); -} - -/// Return FAIL if this is not an appropriate context in which to do -/// completion of anything, return OK if it is (even if there are no matches). -/// For the caller, this means that the character is just passed through like a -/// normal character (instead of being expanded). This allows :s/^I^D etc. -/// -/// @param options extra options for ExpandOne() -/// @param escape if TRUE, escape the returned matches -static int nextwild(expand_T *xp, int type, int options, int escape) -{ - int i, j; - char_u *p1; - char_u *p2; - int difflen; - - if (xp->xp_numfiles == -1) { - set_expand_context(xp); - cmd_showtail = expand_showtail(xp); - } - - if (xp->xp_context == EXPAND_UNSUCCESSFUL) { - beep_flush(); - return OK; // Something illegal on command line - } - if (xp->xp_context == EXPAND_NOTHING) { - // Caller can use the character as a normal char instead - return FAIL; - } - - if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { - msg_puts("..."); // show that we are busy - ui_flush(); - } - - i = (int)((char_u *)xp->xp_pattern - ccline.cmdbuff); - assert(ccline.cmdpos >= i); - xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i; - - if (type == WILD_NEXT || type == WILD_PREV) { - // Get next/previous match for a previous expanded pattern. - p2 = ExpandOne(xp, NULL, NULL, 0, type); - } else { - // Translate string into pattern and expand it. - p1 = addstar((char_u *)xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); - const int use_options = ( - options - | WILD_HOME_REPLACE - | WILD_ADD_SLASH - | WILD_SILENT - | (escape ? WILD_ESCAPE : 0) - | (p_wic ? WILD_ICASE : 0)); - p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), - use_options, type); - xfree(p1); - - // xp->xp_pattern might have been modified by ExpandOne (for example, - // in lua completion), so recompute the pattern index and length - i = (int)((char_u *)xp->xp_pattern - ccline.cmdbuff); - xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i; - - // Longest match: make sure it is not shorter, happens with :help. - if (p2 != NULL && type == WILD_LONGEST) { - for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { - if (ccline.cmdbuff[i + j] == '*' - || ccline.cmdbuff[i + j] == '?') { - break; - } - } - if ((int)STRLEN(p2) < j) { - XFREE_CLEAR(p2); - } - } - } - - if (p2 != NULL && !got_int) { - difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len); - if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) { - realloc_cmdbuff(ccline.cmdlen + difflen + 4); - xp->xp_pattern = (char *)ccline.cmdbuff + i; - } - assert(ccline.cmdpos <= ccline.cmdlen); - memmove(&ccline.cmdbuff[ccline.cmdpos + difflen], - &ccline.cmdbuff[ccline.cmdpos], - (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1); - memmove(&ccline.cmdbuff[i], p2, STRLEN(p2)); - ccline.cmdlen += difflen; - ccline.cmdpos += difflen; - } - xfree(p2); - - redrawcmd(); - cursorcmd(); - - /* When expanding a ":map" command and no matches are found, assume that - * the key is supposed to be inserted literally */ - if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) { - return FAIL; - } - - if (xp->xp_numfiles <= 0 && p2 == NULL) { - beep_flush(); - } else if (xp->xp_numfiles == 1) { - // free expanded pattern - (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); - } - - return OK; -} - -/// Do wildcard expansion on the string 'str'. -/// Chars that should not be expanded must be preceded with a backslash. -/// Return a pointer to allocated memory containing the new string. -/// Return NULL for failure. -/// -/// "orig" is the originally expanded string, copied to allocated memory. It -/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or -/// WILD_PREV "orig" should be NULL. -/// -/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" -/// is WILD_EXPAND_FREE or WILD_ALL. -/// -/// mode = WILD_FREE: just free previously expanded matches -/// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches -/// mode = WILD_EXPAND_KEEP: normal expansion, keep matches -/// mode = WILD_NEXT: use next match in multiple match, wrap to first -/// mode = WILD_PREV: use previous match in multiple match, wrap to first -/// mode = WILD_ALL: return all matches concatenated -/// mode = WILD_LONGEST: return longest matched part -/// mode = WILD_ALL_KEEP: get all matches, keep matches -/// -/// options = WILD_LIST_NOTFOUND: list entries without a match -/// options = WILD_HOME_REPLACE: do home_replace() for buffer names -/// options = WILD_USE_NL: Use '\n' for WILD_ALL -/// options = WILD_NO_BEEP: Don't beep for multiple matches -/// options = WILD_ADD_SLASH: add a slash after directory names -/// options = WILD_KEEP_ALL: don't remove 'wildignore' entries -/// options = WILD_SILENT: don't print warning messages -/// options = WILD_ESCAPE: put backslash before special chars -/// options = WILD_ICASE: ignore case for files -/// -/// The variables xp->xp_context and xp->xp_backslash must have been set! -/// -/// @param orig allocated copy of original of expanded string -char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode) -{ - char_u *ss = NULL; - static int findex; - static char_u *orig_save = NULL; // kept value of orig - int orig_saved = FALSE; - int i; - int non_suf_match; // number without matching suffix - - /* - * first handle the case of using an old match - */ - if (mode == WILD_NEXT || mode == WILD_PREV) { - if (xp->xp_numfiles > 0) { - if (mode == WILD_PREV) { - if (findex == -1) { - findex = xp->xp_numfiles; - } - --findex; - } else { // mode == WILD_NEXT - ++findex; - } - - /* - * When wrapping around, return the original string, set findex to - * -1. - */ - if (findex < 0) { - if (orig_save == NULL) { - findex = xp->xp_numfiles - 1; - } else { - findex = -1; - } - } - if (findex >= xp->xp_numfiles) { - if (orig_save == NULL) { - findex = 0; - } else { - findex = -1; - } - } - if (compl_match_array) { - compl_selected = findex; - cmdline_pum_display(false); - } else if (p_wmnu) { - win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); - } - if (findex == -1) { - return vim_strsave(orig_save); - } - return vim_strsave((char_u *)xp->xp_files[findex]); - } else { - return NULL; - } - } - - if (mode == WILD_CANCEL) { - ss = vim_strsave(orig_save ? orig_save : (char_u *)""); - } else if (mode == WILD_APPLY) { - ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") : - (char_u *)xp->xp_files[findex]); - } - - // free old names - if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { - FreeWild(xp->xp_numfiles, xp->xp_files); - xp->xp_numfiles = -1; - XFREE_CLEAR(orig_save); - } - findex = 0; - - if (mode == WILD_FREE) { // only release file name - return NULL; - } - - if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { - xfree(orig_save); - orig_save = orig; - orig_saved = TRUE; - - /* - * Do the expansion. - */ - if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, options) == FAIL) { -#ifdef FNAME_ILLEGAL - /* Illegal file name has been silently skipped. But when there - * are wildcards, the real problem is that there was no match, - * causing the pattern to be added, which has illegal characters. - */ - if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { - semsg(_(e_nomatch2), str); - } -#endif - } else if (xp->xp_numfiles == 0) { - if (!(options & WILD_SILENT)) { - semsg(_(e_nomatch2), str); - } - } else { - // Escape the matches for use on the command line. - ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); - - /* - * Check for matching suffixes in file names. - */ - if (mode != WILD_ALL && mode != WILD_ALL_KEEP - && mode != WILD_LONGEST) { - if (xp->xp_numfiles) { - non_suf_match = xp->xp_numfiles; - } else { - non_suf_match = 1; - } - if ((xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES) - && xp->xp_numfiles > 1) { - /* - * More than one match; check suffix. - * The files will have been sorted on matching suffix in - * expand_wildcards, only need to check the first two. - */ - non_suf_match = 0; - for (i = 0; i < 2; i++) { - if (match_suffix((char_u *)xp->xp_files[i])) { - non_suf_match++; - } - } - } - if (non_suf_match != 1) { - /* Can we ever get here unless it's while expanding - * interactively? If not, we can get rid of this all - * together. Don't really want to wait for this message - * (and possibly have to hit return to continue!). - */ - if (!(options & WILD_SILENT)) { - emsg(_(e_toomany)); - } else if (!(options & WILD_NO_BEEP)) { - beep_flush(); - } - } - if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { - ss = vim_strsave((char_u *)xp->xp_files[0]); - } - } - } - } - - // Find longest common part - if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { - size_t len = 0; - - for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { - mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); - int c0 = utf_ptr2char(&xp->xp_files[0][len]); - for (i = 1; i < xp->xp_numfiles; i++) { - int ci = utf_ptr2char(&xp->xp_files[i][len]); - - if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS)) { - if (mb_tolower(c0) != mb_tolower(ci)) { - break; - } - } else if (c0 != ci) { - break; - } - } - if (i < xp->xp_numfiles) { - if (!(options & WILD_NO_BEEP)) { - vim_beep(BO_WILD); - } - break; - } - } - - ss = (char_u *)xstrndup(xp->xp_files[0], len); - findex = -1; // next p_wc gets first one - } - - // Concatenate all matching names - // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne) - if (mode == WILD_ALL && xp->xp_numfiles > 0) { - size_t len = 0; - for (i = 0; i < xp->xp_numfiles; ++i) { - len += STRLEN(xp->xp_files[i]) + 1; - } - ss = xmalloc(len); - *ss = NUL; - for (i = 0; i < xp->xp_numfiles; ++i) { - STRCAT(ss, xp->xp_files[i]); - if (i != xp->xp_numfiles - 1) { - STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); - } - } - } - - if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) { - ExpandCleanup(xp); - } - - // Free "orig" if it wasn't stored in "orig_save". - if (!orig_saved) { - xfree(orig); - } - - return ss; -} - -/* - * Prepare an expand structure for use. - */ -void ExpandInit(expand_T *xp) - FUNC_ATTR_NONNULL_ALL -{ - CLEAR_POINTER(xp); - xp->xp_backslash = XP_BS_NONE; - xp->xp_numfiles = -1; -} - -/* - * Cleanup an expand structure after use. - */ -void ExpandCleanup(expand_T *xp) -{ - if (xp->xp_numfiles >= 0) { - FreeWild(xp->xp_numfiles, xp->xp_files); - xp->xp_numfiles = -1; - } -} - -void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char **files, int options) -{ - int i; - char_u *p; - const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; - - /* - * May change home directory back to "~" - */ - if (options & WILD_HOME_REPLACE) { - tilde_replace(str, numfiles, files); - } - - if (options & WILD_ESCAPE) { - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_FILES_IN_PATH - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS - || xp->xp_context == EXPAND_DIRECTORIES) { - /* - * Insert a backslash into a file name before a space, \, %, # - * and wildmatch characters, except '~'. - */ - for (i = 0; i < numfiles; ++i) { - // for ":set path=" we need to escape spaces twice - if (xp->xp_backslash == XP_BS_THREE) { - p = vim_strsave_escaped((char_u *)files[i], (char_u *)" "); - xfree(files[i]); - files[i] = (char *)p; -#if defined(BACKSLASH_IN_FILENAME) - p = vim_strsave_escaped(files[i], (char_u *)" "); - xfree(files[i]); - files[i] = p; -#endif - } -#ifdef BACKSLASH_IN_FILENAME - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); -#else - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], - xp->xp_shell ? VSE_SHELL : vse_what); -#endif - xfree(files[i]); - files[i] = (char *)p; - - /* If 'str' starts with "\~", replace "~" at start of - * files[i] with "\~". */ - if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { - escape_fname(&files[i]); - } - } - xp->xp_backslash = XP_BS_NONE; - - /* If the first file starts with a '+' escape it. Otherwise it - * could be seen as "+cmd". */ - if (*files[0] == '+') { - escape_fname(&files[0]); - } - } else if (xp->xp_context == EXPAND_TAGS) { - /* - * Insert a backslash before characters in a tag name that - * would terminate the ":tag" command. - */ - for (i = 0; i < numfiles; i++) { - p = vim_strsave_escaped((char_u *)files[i], (char_u *)"\\|\""); - xfree(files[i]); - files[i] = (char *)p; - } - } - } -} - /// Escape special characters in "fname", depending on "what": /// /// @param[in] fname File name to escape. @@ -4476,10 +3882,8 @@ char *vim_strsave_fnameescape(const char *const fname, const int what) return p; } -/* - * Put a backslash before the file name in "pp", which is in allocated memory. - */ -static void escape_fname(char **pp) +/// Put a backslash before the file name in "pp", which is in allocated memory. +void escape_fname(char **pp) { char_u *p = xmalloc(STRLEN(*pp) + 2); p[0] = '\\'; @@ -4503,1761 +3907,152 @@ void tilde_replace(char_u *orig_pat, int num_files, char **files) } } -void cmdline_pum_display(bool changed_array) -{ - pum_display(compl_match_array, compl_match_arraysize, compl_selected, - changed_array, compl_startcol); -} - -/* - * Show all matches for completion on the command line. - * Returns EXPAND_NOTHING when the character that triggered expansion should - * be inserted like a normal character. - */ -static int showmatches(expand_T *xp, int wildmenu) -{ -#define L_SHOWFILE(m) (showtail \ - ? sm_gettail(files_found[m], false) : files_found[m]) - int num_files; - char **files_found; - int i, j, k; - int maxlen; - int lines; - int columns; - char_u *p; - int lastlen; - int attr; - int showtail; - - if (xp->xp_numfiles == -1) { - set_expand_context(xp); - i = expand_cmdline(xp, ccline.cmdbuff, ccline.cmdpos, - &num_files, &files_found); - showtail = expand_showtail(xp); - if (i != EXPAND_OK) { - return i; - } - } else { - num_files = xp->xp_numfiles; - files_found = xp->xp_files; - showtail = cmd_showtail; - } - - bool compl_use_pum = (ui_has(kUICmdline) - ? ui_has(kUIPopupmenu) - : wildmenu && (wop_flags & WOP_PUM)) - || ui_has(kUIWildmenu); - - if (compl_use_pum) { - assert(num_files >= 0); - compl_match_arraysize = num_files; - compl_match_array = xcalloc((size_t)compl_match_arraysize, - sizeof(pumitem_T)); - for (i = 0; i < num_files; i++) { - compl_match_array[i].pum_text = (char_u *)L_SHOWFILE(i); - } - char_u *endpos = (char_u *)(showtail ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern); - if (ui_has(kUICmdline)) { - compl_startcol = (int)(endpos - ccline.cmdbuff); - } else { - compl_startcol = cmd_screencol((int)(endpos - ccline.cmdbuff)); - } - compl_selected = -1; - cmdline_pum_display(true); - return EXPAND_OK; - } - - if (!wildmenu) { - msg_didany = false; // lines_left will be set - msg_start(); // prepare for paging - msg_putchar('\n'); - ui_flush(); - cmdline_row = msg_row; - msg_didany = false; // lines_left will be set again - msg_start(); // prepare for paging - } - - if (got_int) { - got_int = false; // only int. the completion, not the cmd line - } else if (wildmenu) { - win_redr_status_matches(xp, num_files, files_found, -1, showtail); - } else { - // find the length of the longest file name - maxlen = 0; - for (i = 0; i < num_files; ++i) { - if (!showtail && (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS)) { - home_replace(NULL, files_found[i], (char *)NameBuff, MAXPATHL, true); - j = vim_strsize((char *)NameBuff); - } else { - j = vim_strsize(L_SHOWFILE(i)); - } - if (j > maxlen) { - maxlen = j; - } - } - - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - lines = num_files; - } else { - // compute the number of columns and lines for the listing - maxlen += 2; // two spaces between file names - columns = (Columns + 2) / maxlen; - if (columns < 1) { - columns = 1; - } - lines = (num_files + columns - 1) / columns; - } - - attr = HL_ATTR(HLF_D); // find out highlighting for directories - - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); - msg_clr_eos(); - msg_advance(maxlen - 3); - msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); - } - - // list the files line by line - for (i = 0; i < lines; ++i) { - lastlen = 999; - for (k = i; k < num_files; k += lines) { - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_outtrans_attr((char_u *)files_found[k], HL_ATTR(HLF_D)); - p = (char_u *)files_found[k] + STRLEN(files_found[k]) + 1; - msg_advance(maxlen + 1); - msg_puts((const char *)p); - msg_advance(maxlen + 3); - msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); - break; - } - for (j = maxlen - lastlen; --j >= 0;) { - msg_putchar(' '); - } - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS) { - // highlight directories - if (xp->xp_numfiles != -1) { - // Expansion was done before and special characters - // were escaped, need to halve backslashes. Also - // $HOME has been replaced with ~/. - char_u *exp_path = expand_env_save_opt((char_u *)files_found[k], true); - char_u *path = exp_path != NULL ? exp_path : (char_u *)files_found[k]; - char_u *halved_slash = backslash_halve_save(path); - j = os_isdir(halved_slash); - xfree(exp_path); - if (halved_slash != path) { - xfree(halved_slash); - } - } else { - // Expansion was done here, file names are literal. - j = os_isdir((char_u *)files_found[k]); - } - if (showtail) { - p = (char_u *)L_SHOWFILE(k); - } else { - home_replace(NULL, files_found[k], (char *)NameBuff, MAXPATHL, true); - p = NameBuff; - } - } else { - j = false; - p = (char_u *)L_SHOWFILE(k); - } - lastlen = msg_outtrans_attr(p, j ? attr : 0); - } - if (msg_col > 0) { // when not wrapped around - msg_clr_eos(); - msg_putchar('\n'); - } - ui_flush(); // show one line at a time - if (got_int) { - got_int = FALSE; - break; - } - } - - /* - * we redraw the command below the lines that we have just listed - * This is a bit tricky, but it saves a lot of screen updating. - */ - cmdline_row = msg_row; // will put it back later - } - - if (xp->xp_numfiles == -1) { - FreeWild(num_files, files_found); - } - - return EXPAND_OK; -} - -/// Private path_tail for showmatches() (and win_redr_status_matches()): -/// Find tail of file name path, but ignore trailing "/". -char *sm_gettail(char *s, bool eager) -{ - char_u *p; - char_u *t = (char_u *)s; - int had_sep = false; - - for (p = (char_u *)s; *p != NUL;) { - if (vim_ispathsep(*p) -#ifdef BACKSLASH_IN_FILENAME - && !rem_backslash(p) -#endif - ) { - if (eager) { - t = p + 1; - } else { - had_sep = true; - } - } else if (had_sep) { - t = p; - had_sep = FALSE; - } - MB_PTR_ADV(p); - } - return (char *)t; -} - -/* - * Return TRUE if we only need to show the tail of completion matches. - * When not completing file names or there is a wildcard in the path FALSE is - * returned. - */ -static int expand_showtail(expand_T *xp) +/// Get a pointer to the current command line info. +CmdlineInfo *get_cmdline_info(void) { - char_u *s; - char_u *end; - - // When not completing file names a "/" may mean something different. - if (xp->xp_context != EXPAND_FILES - && xp->xp_context != EXPAND_SHELLCMD - && xp->xp_context != EXPAND_DIRECTORIES) { - return FALSE; - } - - end = (char_u *)path_tail(xp->xp_pattern); - if (end == (char_u *)xp->xp_pattern) { // there is no path separator - return false; - } - - for (s = (char_u *)xp->xp_pattern; s < end; s++) { - // Skip escaped wildcards. Only when the backslash is not a path - // separator, on DOS the '*' "path\*\file" must not be skipped. - if (rem_backslash(s)) { - s++; - } else if (vim_strchr("*?[", *s) != NULL) { - return false; - } - } - return TRUE; -} - -/// Prepare a string for expansion. -/// -/// When expanding file names: The string will be used with expand_wildcards(). -/// Copy "fname[len]" into allocated memory and add a '*' at the end. -/// When expanding other names: The string will be used with regcomp(). Copy -/// the name into allocated memory and prepend "^". -/// -/// @param context EXPAND_FILES etc. -char_u *addstar(char_u *fname, size_t len, int context) - FUNC_ATTR_NONNULL_RET -{ - char_u *retval; - size_t i, j; - size_t new_len; - char_u *tail; - int ends_in_star; - - if (context != EXPAND_FILES - && context != EXPAND_FILES_IN_PATH - && context != EXPAND_SHELLCMD - && context != EXPAND_DIRECTORIES) { - /* - * Matching will be done internally (on something other than files). - * So we convert the file-matching-type wildcards into our kind for - * use with vim_regcomp(). First work out how long it will be: - */ - - // For help tags the translation is done in find_help_tags(). - // For a tag pattern starting with "/" no translation is needed. - if (context == EXPAND_HELP - || context == EXPAND_CHECKHEALTH - || context == EXPAND_COLORS - || context == EXPAND_COMPILER - || context == EXPAND_OWNSYNTAX - || context == EXPAND_FILETYPE - || context == EXPAND_PACKADD - || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) - && fname[0] == '/')) { - retval = vim_strnsave(fname, len); - } else { - new_len = len + 2; // +2 for '^' at start, NUL at end - for (i = 0; i < len; i++) { - if (fname[i] == '*' || fname[i] == '~') { - new_len++; /* '*' needs to be replaced by ".*" - '~' needs to be replaced by "\~" */ - } - // Buffer names are like file names. "." should be literal - if (context == EXPAND_BUFFERS && fname[i] == '.') { - new_len++; // "." becomes "\." - } - /* Custom expansion takes care of special things, match - * backslashes literally (perhaps also for other types?) */ - if ((context == EXPAND_USER_DEFINED - || context == EXPAND_USER_LIST) && fname[i] == '\\') { - new_len++; // '\' becomes "\\" - } - } - retval = xmalloc(new_len); - { - retval[0] = '^'; - j = 1; - for (i = 0; i < len; i++, j++) { - /* Skip backslash. But why? At least keep it for custom - * expansion. */ - if (context != EXPAND_USER_DEFINED - && context != EXPAND_USER_LIST - && fname[i] == '\\' - && ++i == len) { - break; - } - - switch (fname[i]) { - case '*': - retval[j++] = '.'; - break; - case '~': - retval[j++] = '\\'; - break; - case '?': - retval[j] = '.'; - continue; - case '.': - if (context == EXPAND_BUFFERS) { - retval[j++] = '\\'; - } - break; - case '\\': - if (context == EXPAND_USER_DEFINED - || context == EXPAND_USER_LIST) { - retval[j++] = '\\'; - } - break; - } - retval[j] = fname[i]; - } - retval[j] = NUL; - } - } - } else { - retval = xmalloc(len + 4); - STRLCPY(retval, fname, len + 1); - - /* - * Don't add a star to *, ~, ~user, $var or `cmd`. - * * would become **, which walks the whole tree. - * ~ would be at the start of the file name, but not the tail. - * $ could be anywhere in the tail. - * ` could be anywhere in the file name. - * When the name ends in '$' don't add a star, remove the '$'. - */ - tail = (char_u *)path_tail((char *)retval); - ends_in_star = (len > 0 && retval[len - 1] == '*'); -#ifndef BACKSLASH_IN_FILENAME - for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { - if (retval[k] != '\\') { - break; - } - ends_in_star = !ends_in_star; - } -#endif - if ((*retval != '~' || tail != retval) - && !ends_in_star - && vim_strchr((char *)tail, '$') == NULL - && vim_strchr((char *)retval, '`') == NULL) { - retval[len++] = '*'; - } else if (len > 0 && retval[len - 1] == '$') { - --len; - } - retval[len] = NUL; - } - return retval; -} - -/* - * Must parse the command line so far to work out what context we are in. - * Completion can then be done based on that context. - * This routine sets the variables: - * xp->xp_pattern The start of the pattern to be expanded within - * the command line (ends at the cursor). - * xp->xp_context The type of thing to expand. Will be one of: - * - * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on - * the command line, like an unknown command. Caller - * should beep. - * EXPAND_NOTHING Unrecognised context for completion, use char like - * a normal char, rather than for completion. eg - * :s/^I/ - * EXPAND_COMMANDS Cursor is still touching the command, so complete - * it. - * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. - * EXPAND_FILES After command with EX_XFILE set, or after setting - * with P_EXPAND set. eg :e ^I, :w>>^I - * EXPAND_DIRECTORIES In some cases this is used instead of the latter - * when we know only directories are of interest. eg - * :set dir=^I - * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". - * EXPAND_SETTINGS Complete variable names. eg :set d^I - * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I - * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I - * EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect - * EXPAND_HELP Complete tags from the file 'helpfile'/tags - * EXPAND_EVENTS Complete event names - * EXPAND_SYNTAX Complete :syntax command arguments - * EXPAND_HIGHLIGHT Complete highlight (syntax) group names - * EXPAND_AUGROUP Complete autocommand group names - * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I - * EXPAND_MAPPINGS Complete mapping and abbreviation names, - * eg :unmap a^I , :cunab x^I - * EXPAND_FUNCTIONS Complete internal or user defined function names, - * eg :call sub^I - * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I - * EXPAND_EXPRESSION Complete internal or user defined function/variable - * names in expressions, eg :while s^I - * EXPAND_ENV_VARS Complete environment variable names - * EXPAND_USER Complete user names - */ -void set_expand_context(expand_T *xp) -{ - // only expansion for ':', '>' and '=' command-lines - if (ccline.cmdfirstc != ':' - && ccline.cmdfirstc != '>' && ccline.cmdfirstc != '=' - && !ccline.input_fn) { - xp->xp_context = EXPAND_NOTHING; - return; - } - set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos, true); -} - -/// @param str start of command line -/// @param len length of command line (excl. NUL) -/// @param col position of cursor -/// @param use_ccline use ccline for info -void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline) -{ - char_u old_char = NUL; - - /* - * Avoid a UMR warning from Purify, only save the character if it has been - * written before. - */ - if (col < len) { - old_char = str[col]; - } - str[col] = NUL; - const char *nextcomm = (const char *)str; - - if (use_ccline && ccline.cmdfirstc == '=') { - // pass CMD_SIZE because there is no real command - set_context_for_expression(xp, (char *)str, CMD_SIZE); - } else if (use_ccline && ccline.input_fn) { - xp->xp_context = ccline.xp_context; - xp->xp_pattern = (char *)ccline.cmdbuff; - xp->xp_arg = (char *)ccline.xp_arg; - } else { - while (nextcomm != NULL) { - nextcomm = set_one_cmd_context(xp, nextcomm); - } - } - - /* Store the string here so that call_user_expand_func() can get to them - * easily. */ - xp->xp_line = (char *)str; - xp->xp_col = col; - - str[col] = old_char; -} - -/// Expand the command line "str" from context "xp". -/// "xp" must have been set by set_cmd_context(). -/// xp->xp_pattern points into "str", to where the text that is to be expanded -/// starts. -/// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the -/// cursor. -/// Returns EXPAND_NOTHING when there is nothing to expand, might insert the -/// key that triggered expansion literally. -/// Returns EXPAND_OK otherwise. -/// -/// @param str start of command line -/// @param col position of cursor -/// @param matchcount return: nr of matches -/// @param matches return: array of pointers to matches -int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char ***matches) -{ - char_u *file_str = NULL; - int options = WILD_ADD_SLASH|WILD_SILENT; - - if (xp->xp_context == EXPAND_UNSUCCESSFUL) { - beep_flush(); - return EXPAND_UNSUCCESSFUL; // Something illegal on command line - } - if (xp->xp_context == EXPAND_NOTHING) { - // Caller can use the character as a normal char instead - return EXPAND_NOTHING; - } - - // add star to file name, or convert to regexp if not exp. files. - assert((str + col) - (char_u *)xp->xp_pattern >= 0); - xp->xp_pattern_len = (size_t)((str + col) - (char_u *)xp->xp_pattern); - file_str = addstar((char_u *)xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); - - if (p_wic) { - options += WILD_ICASE; - } - - // find all files that match the description - if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) { - *matchcount = 0; - *matches = NULL; - } - xfree(file_str); - - 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". -/// -/// @param options WILD_ flags -static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***file, int options) -{ - regmatch_T regmatch; - int ret; - int flags; - - flags = EW_DIR; // include directories - if (options & WILD_LIST_NOTFOUND) { - flags |= EW_NOTFOUND; - } - if (options & WILD_ADD_SLASH) { - flags |= EW_ADDSLASH; - } - if (options & WILD_KEEP_ALL) { - flags |= EW_KEEPALL; - } - if (options & WILD_SILENT) { - flags |= EW_SILENT; - } - if (options & WILD_NOERROR) { - flags |= EW_NOERROR; - } - if (options & WILD_ALLLINKS) { - flags |= EW_ALLLINKS; - } - - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES_IN_PATH) { - /* - * Expand file or directory names. - */ - int free_pat = FALSE; - int i; - - // for ":set path=" and ":set tags=" halve backslashes for escaped space - if (xp->xp_backslash != XP_BS_NONE) { - free_pat = TRUE; - pat = vim_strsave(pat); - for (i = 0; pat[i]; ++i) { - if (pat[i] == '\\') { - if (xp->xp_backslash == XP_BS_THREE - && pat[i + 1] == '\\' - && pat[i + 2] == '\\' - && pat[i + 3] == ' ') { - STRMOVE(pat + i, pat + i + 3); - } - if (xp->xp_backslash == XP_BS_ONE - && pat[i + 1] == ' ') { - STRMOVE(pat + i, pat + i + 1); - } - } - } - } - - if (xp->xp_context == EXPAND_FILES) { - flags |= EW_FILE; - } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { - flags |= (EW_FILE | EW_PATH); - } else { - flags = (flags | EW_DIR) & ~EW_FILE; - } - if (options & WILD_ICASE) { - flags |= EW_ICASE; - } - - // Expand wildcards, supporting %:h and the like. - ret = expand_wildcards_eval(&pat, num_file, file, flags); - if (free_pat) { - xfree(pat); - } -#ifdef BACKSLASH_IN_FILENAME - if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { - for (int i = 0; i < *num_file; i++) { - char_u *ptr = (*file)[i]; - while (*ptr != NUL) { - if (p_csl[0] == 's' && *ptr == '\\') { - *ptr = '/'; - } else if (p_csl[0] == 'b' && *ptr == '/') { - *ptr = '\\'; - } - ptr += utfc_ptr2len(ptr); - } - } - } -#endif - return ret; - } - - *file = NULL; - *num_file = 0; - if (xp->xp_context == EXPAND_HELP) { - /* With an empty argument we would get all the help tags, which is - * very slow. Get matches for "help" instead. */ - if (find_help_tags(*pat == NUL ? "help" : (char *)pat, - num_file, file, false) == OK) { - cleanup_help_tags(*num_file, *file); - return OK; - } - return FAIL; - } - - if (xp->xp_context == EXPAND_SHELLCMD) { - *file = NULL; - expand_shellcmd(pat, num_file, file, flags); - return OK; - } - if (xp->xp_context == EXPAND_OLD_SETTING) { - ExpandOldSetting(num_file, file); - return OK; - } - if (xp->xp_context == EXPAND_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options); - } - if (xp->xp_context == EXPAND_DIFF_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options | BUF_DIFF_FILTER); - } - if (xp->xp_context == EXPAND_TAGS - || xp->xp_context == EXPAND_TAGS_LISTFILES) { - return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); - } - if (xp->xp_context == EXPAND_COLORS) { - char *directories[] = { "colors", NULL }; - return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, directories); - } - if (xp->xp_context == EXPAND_COMPILER) { - char *directories[] = { "compiler", NULL }; - return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); - } - if (xp->xp_context == EXPAND_OWNSYNTAX) { - char *directories[] = { "syntax", NULL }; - return ExpandRTDir(pat, 0, num_file, file, directories); - } - if (xp->xp_context == EXPAND_FILETYPE) { - char *directories[] = { "syntax", "indent", "ftplugin", NULL }; - return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); - } - if (xp->xp_context == EXPAND_USER_LIST) { - return ExpandUserList(xp, num_file, file); - } - if (xp->xp_context == EXPAND_USER_LUA) { - return ExpandUserLua(xp, num_file, file); - } - if (xp->xp_context == EXPAND_PACKADD) { - return ExpandPackAddDir(pat, num_file, file); - } - - // When expanding a function name starting with s:, match the <SNR>nr_ - // prefix. - char *tofree = NULL; - if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { - const size_t len = STRLEN(pat) + 20; - - tofree = xmalloc(len); - snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); - pat = (char_u *)tofree; - } - - if (xp->xp_context == EXPAND_LUA) { - ILOG("PAT %s", pat); - return nlua_expand_pat(xp, pat, num_file, file); - } - - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - return FAIL; - } - - // set ignore-case according to p_ic, p_scs and pat - regmatch.rm_ic = ignorecase(pat); - - if (xp->xp_context == EXPAND_SETTINGS - || xp->xp_context == EXPAND_BOOL_SETTINGS) { - ret = ExpandSettings(xp, ®match, num_file, file); - } else if (xp->xp_context == EXPAND_MAPPINGS) { - ret = ExpandMappings(®match, num_file, file); - } else if (xp->xp_context == EXPAND_USER_DEFINED) { - ret = ExpandUserDefined(xp, ®match, num_file, file); - } else { - static struct expgen { - int context; - ExpandFunc func; - int ic; - int escaped; - } tab[] = { - { EXPAND_COMMANDS, get_command_name, false, true }, - { EXPAND_BEHAVE, get_behave_arg, true, true }, - { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, - { EXPAND_MESSAGES, get_messages_arg, true, true }, - { EXPAND_HISTORY, get_history_arg, true, true }, - { EXPAND_USER_COMMANDS, get_user_commands, false, true }, - { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, - { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, - { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, - { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, - { EXPAND_USER_VARS, get_user_var_name, false, true }, - { EXPAND_FUNCTIONS, get_function_name, false, true }, - { EXPAND_USER_FUNC, get_user_func_name, false, true }, - { EXPAND_EXPRESSION, get_expr_name, false, true }, - { EXPAND_MENUS, get_menu_name, false, true }, - { EXPAND_MENUNAMES, get_menu_names, false, true }, - { 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_CSCOPE, get_cscope_name, true, true }, - { EXPAND_SIGN, get_sign_name, true, true }, - { EXPAND_PROFILE, get_profile_name, true, true }, -#ifdef HAVE_WORKING_LIBINTL - { EXPAND_LANGUAGE, get_lang_arg, true, false }, - { EXPAND_LOCALES, get_locales, true, false }, -#endif - { EXPAND_ENV_VARS, get_env_name, true, true }, - { EXPAND_USER, get_users, true, false }, - { EXPAND_ARGLIST, get_arglist_name, true, false }, - { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, - }; - int i; - - /* - * Find a context in the table and call the ExpandGeneric() with the - * right function to do the expansion. - */ - ret = FAIL; - for (i = 0; i < (int)ARRAY_SIZE(tab); ++i) { - if (xp->xp_context == tab[i].context) { - if (tab[i].ic) { - regmatch.rm_ic = TRUE; - } - ExpandGeneric(xp, ®match, num_file, file, tab[i].func, tab[i].escaped); - ret = OK; - break; - } - } - } - - vim_regfree(regmatch.regprog); - xfree(tofree); - - return ret; + return &ccline; } -/// Expand a list of names. -/// -/// Generic function for command line completion. It calls a function to -/// obtain strings, one by one. The strings are matched against a regexp -/// program. Matching strings are copied into an array, which is returned. -/// -/// @param func returns a string from the list -static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file, - CompleteListItemGetter func, int escaped) +unsigned get_cmdline_last_prompt_id(void) { - int i; - size_t count = 0; - char_u *str; - - // count the number of matching names - for (i = 0;; i++) { - str = (char_u *)(*func)(xp, i); - if (str == NULL) { // end of list - break; - } - if (*str == NUL) { // skip empty strings - continue; - } - if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { - count++; - } - } - if (count == 0) { - return; - } - assert(count < INT_MAX); - *num_file = (int)count; - *file = xmalloc(count * sizeof(char_u *)); - - // copy the matching names into allocated memory - count = 0; - for (i = 0;; i++) { - str = (char_u *)(*func)(xp, i); - if (str == NULL) { // End of list. - break; - } - if (*str == NUL) { // Skip empty strings. - continue; - } - if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { - if (escaped) { - str = vim_strsave_escaped(str, (char_u *)" \t\\."); - } else { - str = vim_strsave(str); - } - (*file)[count++] = (char *)str; - if (func == get_menu_names) { - // Test for separator added by get_menu_names(). - str += STRLEN(str) - 1; - if (*str == '\001') { - *str = '.'; - } - } - } - } - - // Sort the results. Keep menu's in the specified order. - if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) { - if (xp->xp_context == EXPAND_EXPRESSION - || xp->xp_context == EXPAND_FUNCTIONS - || xp->xp_context == EXPAND_USER_FUNC) { - // <SNR> functions should be sorted to the end. - qsort((void *)*file, (size_t)*num_file, sizeof(char_u *), - sort_func_compare); - } else { - sort_strings(*file, *num_file); - } - } - - /* Reset the variables used for special highlight names expansion, so that - * they don't show up when getting normal highlight names by ID. */ - reset_expand_highlight(); + return last_prompt_id; } -/// Complete a shell command. -/// -/// @param filepat is a pattern to match with command names. -/// @param[out] num_file is pointer to number of matches. -/// @param[out] file is pointer to array of pointers to matches. -/// *file will either be set to NULL or point to -/// allocated memory. -/// @param flagsarg is a combination of EW_* flags. -static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int flagsarg) - FUNC_ATTR_NONNULL_ALL +/// Get pointer to the command line info to use. save_cmdline() may clear +/// ccline and put the previous value in ccline.prev_ccline. +static CmdlineInfo *get_ccline_ptr(void) { - char_u *pat; - int i; - char_u *path = NULL; - garray_T ga; - char *buf = xmalloc(MAXPATHL); - size_t l; - char_u *s, *e; - int flags = flagsarg; - int ret; - bool did_curdir = false; - - // for ":set path=" and ":set tags=" halve backslashes for escaped space - pat = vim_strsave(filepat); - for (i = 0; pat[i]; ++i) { - if (pat[i] == '\\' && pat[i + 1] == ' ') { - STRMOVE(pat + i, pat + i + 1); - } - } - - flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; - - bool mustfree = false; // Track memory allocation for *path. - if (pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' && vim_ispathsep(pat[2])))) { - path = (char_u *)"."; + if ((State & MODE_CMDLINE) == 0) { + return NULL; + } else if (ccline.cmdbuff != NULL) { + return &ccline; + } else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) { + return ccline.prev_ccline; } else { - // For an absolute name we don't use $PATH. - if (!path_is_absolute(pat)) { - path = (char_u *)vim_getenv("PATH"); - } - if (path == NULL) { - path = (char_u *)""; - } else { - mustfree = true; - } - } - - /* - * Go over all directories in $PATH. Expand matches in that directory and - * collect them in "ga". When "." is not in $PATH also expaned for the - * current directory, to find "subdir/cmd". - */ - ga_init(&ga, (int)sizeof(char *), 10); - hashtab_T found_ht; - hash_init(&found_ht); - for (s = path;; s = e) { - e = (char_u *)vim_strchr((char *)s, ENV_SEPCHAR); - if (e == NULL) { - e = s + STRLEN(s); - } - - if (*s == NUL) { - if (did_curdir) { - break; - } - // Find directories in the current directory, path is empty. - did_curdir = true; - flags |= EW_DIR; - } else if (STRNCMP(s, ".", e - s) == 0) { - did_curdir = true; - flags |= EW_DIR; - } else { - // Do not match directories inside a $PATH item. - flags &= ~EW_DIR; - } - - l = (size_t)(e - s); - if (l > MAXPATHL - 5) { - break; - } - STRLCPY(buf, s, l + 1); - add_pathsep(buf); - l = STRLEN(buf); - STRLCPY(buf + l, pat, MAXPATHL - l); - - // Expand matches in one directory of $PATH. - ret = expand_wildcards(1, &buf, num_file, file, flags); - if (ret == OK) { - ga_grow(&ga, *num_file); - { - for (i = 0; i < *num_file; i++) { - char_u *name = (char_u *)(*file)[i]; - - if (STRLEN(name) > l) { - // Check if this name was already found. - hash_T hash = hash_hash(name + l); - hashitem_T *hi = - hash_lookup(&found_ht, (const char *)(name + l), - STRLEN(name + l), hash); - if (HASHITEM_EMPTY(hi)) { - // Remove the path that was prepended. - STRMOVE(name, name + l); - ((char_u **)ga.ga_data)[ga.ga_len++] = name; - hash_add_item(&found_ht, hi, name, hash); - name = NULL; - } - } - xfree(name); - } - xfree(*file); - } - } - if (*e != NUL) { - ++e; - } - } - *file = ga.ga_data; - *num_file = ga.ga_len; - - xfree(buf); - xfree(pat); - if (mustfree) { - xfree(path); - } - hash_clear(&found_ht); -} - -/// Call "user_expand_func()" to invoke a user defined Vim script function and -/// return the result (either a string, a List or NULL). -static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file, - char ***file) - FUNC_ATTR_NONNULL_ALL -{ - char_u keep = 0; - typval_T args[4]; - char_u *pat = NULL; - const sctx_T save_current_sctx = current_sctx; - - if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { return NULL; } - *num_file = 0; - *file = NULL; - - if (ccline.cmdbuff != NULL) { - keep = ccline.cmdbuff[ccline.cmdlen]; - ccline.cmdbuff[ccline.cmdlen] = 0; - } - - pat = vim_strnsave((char_u *)xp->xp_pattern, xp->xp_pattern_len); - args[0].v_type = VAR_STRING; - args[1].v_type = VAR_STRING; - args[2].v_type = VAR_NUMBER; - args[3].v_type = VAR_UNKNOWN; - args[0].vval.v_string = (char *)pat; - args[1].vval.v_string = xp->xp_line; - args[2].vval.v_number = xp->xp_col; - - current_sctx = xp->xp_script_ctx; - - void *const ret = user_expand_func((char_u *)xp->xp_arg, 3, args); - - current_sctx = save_current_sctx; - if (ccline.cmdbuff != NULL) { - ccline.cmdbuff[ccline.cmdlen] = keep; - } - - xfree(pat); - return ret; } -/// Expand names with a function defined by the user. -static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file) +/// Get the current command-line type. +/// Returns ':' or '/' or '?' or '@' or '>' or '-' +/// Only works when the command line is being edited. +/// Returns NUL when something is wrong. +static int get_cmdline_type(void) { - char_u *e; - garray_T ga; - - char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file, - file); - - if (retstr == NULL) { - return FAIL; - } - - ga_init(&ga, (int)sizeof(char *), 3); - for (char_u *s = retstr; *s != NUL; s = e) { - e = (char_u *)vim_strchr((char *)s, '\n'); - if (e == NULL) { - e = s + STRLEN(s); - } - const char_u keep = *e; - *e = NUL; - - const bool skip = xp->xp_pattern[0] - && vim_regexec(regmatch, (char *)s, (colnr_T)0) == 0; - *e = keep; - if (!skip) { - GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); - } - - if (*e != NUL) { - e++; - } - } - xfree(retstr); - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} + CmdlineInfo *p = get_ccline_ptr(); -/// Expand names with a list returned by a function defined by the user. -static int ExpandUserList(expand_T *xp, int *num_file, char ***file) -{ - list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file, - file); - if (retlist == NULL) { - return FAIL; + if (p == NULL) { + return NUL; } - - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 3); - // Loop over the items in the list. - TV_LIST_ITER_CONST(retlist, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING - || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { - continue; // Skip non-string items and empty strings. - } - - GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); - }); - tv_list_unref(retlist); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) -{ - typval_T rettv; - nlua_call_user_expand_func(xp, &rettv); - if (rettv.v_type != VAR_LIST) { - tv_clear(&rettv); - return FAIL; + if (p->cmdfirstc == NUL) { + return (p->input_fn) ? '@' : '-'; } - - list_T *const retlist = rettv.vval.v_list; - - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 3); - // Loop over the items in the list. - TV_LIST_ITER_CONST(retlist, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING - || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { - continue; // Skip non-string items and empty strings. - } - - GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); - }); - tv_list_unref(retlist); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; + return p->cmdfirstc; } -/// 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[]) +/// Get the current command line in allocated memory. +/// Only works when the command line is being edited. +/// Returns NULL when something is wrong. +static char_u *get_cmdline_str(void) { - *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 (cmdline_star > 0) { + return NULL; } + CmdlineInfo *p = get_ccline_ptr(); - if (GA_EMPTY(&ga)) { - return FAIL; + if (p == NULL) { + return NULL; } - - /* 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; + return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen); } -/// Expand loadplugin names: -/// 'packpath'/pack/ * /opt/{pat} -static int ExpandPackAddDir(char_u *pat, int *num_file, char ***file) +/// Get the current command-line completion type. +static char_u *get_cmdline_completion(void) { - 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; + if (cmdline_star > 0) { + return NULL; } + CmdlineInfo *p = get_ccline_ptr(); - // 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) -{ - expand_T xpc; - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - - char_u *buf = xmalloc(MAXPATHL); - - // 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, ","); - if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { - add_pathsep((char *)buf); - STRCAT(buf, file); // NOLINT - - char **p; - int num_p = 0; - (void)ExpandFromContext(&xpc, buf, &num_p, &p, - WILD_SILENT | expand_options); - if (num_p > 0) { - ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); - - // Concatenate new results to previous ones. - ga_grow(ga, num_p); - // take over the pointers and put them in "ga" - for (int i = 0; i < num_p; i++) { - ((char_u **)ga->ga_data)[ga->ga_len] = (char_u *)p[i]; - ga->ga_len++; - } - xfree(p); - } + if (p != NULL && p->xpc != NULL) { + set_expand_context(p->xpc); + char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context); + if (cmd_compl != NULL) { + return vim_strsave((char_u *)cmd_compl); } } - 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 +/// "getcmdcompltype()" function +void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - xfree(hisptr->hisstr); - tv_list_unref(hisptr->additional_elements); - clear_hist_entry(hisptr); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char *)get_cmdline_completion(); } -static inline void clear_hist_entry(histentry_T *hisptr) - FUNC_ATTR_NONNULL_ALL +/// "getcmdline()" function +void f_getcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - memset(hisptr, 0, sizeof(*hisptr)); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char *)get_cmdline_str(); } -/// 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) +/// "getcmdpos()" function +void f_getcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - 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; + CmdlineInfo *p = get_ccline_ptr(); + rettv->vval.v_number = p != NULL ? p->cmdpos + 1 : 0; } -/// 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 +/// "getcmdscreenpos()" function +void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - // 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; + CmdlineInfo *p = get_ccline_ptr(); + rettv->vval.v_number = p != NULL ? p->cmdspos + 1 : 0; } -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) +/// "getcmdtype()" function +void f_getcmdtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - 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; - } - } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = (char)get_cmdline_type(); } -/* - * Get identifier of newest history entry. - * "histype" may be one of the HIST_ values. - */ -int get_history_idx(int histype) +/// Set the command line str to "str". +/// @return 1 when failed, 0 when OK. +static int set_cmdline_str(const char *str, int pos) { - 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) -{ - if ((State & MODE_CMDLINE) == 0) { - return NULL; - } else if (ccline.cmdbuff != NULL) { - return &ccline; - } else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) { - return ccline.prev_ccline; - } else { - return NULL; - } -} - -/// Get the current command-line completion type. -char_u *get_cmdline_completion(void) -{ - if (cmdline_star > 0) { - return NULL; - } - struct cmdline_info *p = get_ccline_ptr(); - - if (p != NULL && p->xpc != NULL) { - set_expand_context(p->xpc); - char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context); - if (cmd_compl != NULL) { - return vim_strsave((char_u *)cmd_compl); - } - } - - return NULL; -} - -/* - * Get the current command line in allocated memory. - * Only works when the command line is being edited. - * Returns NULL when something is wrong. - */ -char_u *get_cmdline_str(void) -{ - if (cmdline_star > 0) { - return NULL; - } - struct cmdline_info *p = get_ccline_ptr(); + CmdlineInfo *p = get_ccline_ptr(); if (p == NULL) { - return NULL; + return 1; } - return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen); -} -/* - * Get the current command line position, counted in bytes. - * Zero is the first position. - * Only works when the command line is being edited. - * Returns -1 when something is wrong. - */ -int get_cmdline_pos(void) -{ - struct cmdline_info *p = get_ccline_ptr(); + int len = (int)STRLEN(str); + realloc_cmdbuff(len + 1); + p->cmdlen = len; + STRCPY(p->cmdbuff, str); - if (p == NULL) { - return -1; - } - return p->cmdpos; -} + p->cmdpos = pos < 0 || pos > p->cmdlen ? p->cmdlen : pos; + new_cmdpos = p->cmdpos; -/// Get the command line cursor screen position. -int get_cmdline_screen_pos(void) -{ - struct cmdline_info *p = get_ccline_ptr(); + redrawcmd(); - if (p == NULL) { - return -1; - } - return p->cmdspos; + // Trigger CmdlineChanged autocommands. + do_autocmd_cmdlinechanged(get_cmdline_type()); + + return 0; } -/* - * Set the command line byte position to "pos". Zero is the first position. - * Only works when the command line is being edited. - * Returns 1 when failed, 0 when OK. - */ -int set_cmdline_pos(int pos) +/// Set the command line byte position to "pos". Zero is the first position. +/// Only works when the command line is being edited. +/// @return 1 when failed, 0 when OK. +static int set_cmdline_pos(int pos) { - struct cmdline_info *p = get_ccline_ptr(); + CmdlineInfo *p = get_ccline_ptr(); if (p == NULL) { return 1; @@ -6273,187 +4068,45 @@ int set_cmdline_pos(int pos) return 0; } -/* - * Get the current command-line type. - * Returns ':' or '/' or '?' or '@' or '>' or '-' - * Only works when the command line is being edited. - * Returns NUL when something is wrong. - */ -int get_cmdline_type(void) +/// "setcmdline()" function +void f_setcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - struct cmdline_info *p = get_ccline_ptr(); - - if (p == NULL) { - return NUL; - } - if (p->cmdfirstc == NUL) { - return (p->input_fn) ? '@' : '-'; + if (argvars[0].v_type != VAR_STRING || argvars[0].vval.v_string == NULL) { + emsg(_(e_stringreq)); + return; } - 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; - } + int pos = -1; + if (argvars[1].v_type != VAR_UNKNOWN) { + bool error = false; - 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; + pos = (int)tv_get_number_chk(&argvars[1], &error) - 1; + if (error) { + return; } - if (hist[i].hisstr != NULL) { - return i; + if (pos < 0) { + emsg(_(e_positive)); + return; } } - 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 *)""; - } + rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos); } -/// 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) +/// "setcmdpos()" function +void f_setcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - 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; -} + const int pos = (int)tv_get_number(&argvars[0]) - 1; -/* - * 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) -{ - 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; - } + if (pos >= 0) { + rettv->vval.v_number = set_cmdline_pos(pos); } - 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) +/// Return the first character of the current command line. +int get_cmdline_firstc(void) { - 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 +4117,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 +4146,27 @@ int get_list_range(char_u **str, int *num1, int *num2) return OK; } -/* - * :history command - print a history - */ -void ex_history(exarg_T *eap) +void cmdline_init(void) { - 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; - } - } - } - } + CLEAR_FIELD(ccline); } -/// Translate a history type number to the associated character -int hist_type2char(int type) - FUNC_ATTR_CONST +/// Check value of 'cedit' and set cedit_key. +/// Returns NULL if value is OK, error message otherwise. +char *check_cedit(void) { - 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; -} + int n; -void cmdline_init(void) -{ - memset(&ccline, 0, sizeof(struct cmdline_info)); + if (*p_cedit == NUL) { + cedit_key = -1; + } else { + n = string_to_key((char_u *)p_cedit); + if (vim_isprintc(n)) { + return e_invarg; + } + cedit_key = n; + } + return NULL; } /// Open a window on the current command line and history. Allow editing in @@ -6654,17 +4216,27 @@ static int open_cmdwin(void) ga_clear(&winsizes); return K_IGNORE; } + // Don't let quitting the More prompt make this fail. + got_int = false; + + // Set "cmdwin_type" before any autocommands may mess things up. cmdwin_type = get_cmdline_type(); cmdwin_level = ccline.level; // Create empty command-line buffer. - buf_open_scratch(0, _("[Command Line]")); + if (buf_open_scratch(0, _("[Command Line]")) == FAIL) { + // Some autocommand messed it up? + win_close(curwin, true, false); + ga_clear(&winsizes); + cmdwin_type = 0; + return Ctrl_C; + } // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer. - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - curwin->w_p_rl = cmdmsg_rl; - cmdmsg_rl = false; + set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL); curbuf->b_p_ma = true; curwin->w_p_fen = false; + curwin->w_p_rl = cmdmsg_rl; + cmdmsg_rl = false; // Don't allow switching to another buffer. curbuf->b_ro_locked++; @@ -6678,7 +4250,7 @@ static int open_cmdwin(void) add_map("<Tab>", "<C-X><C-V>", MODE_INSERT, true); add_map("<Tab>", "a<C-X><C-V>", MODE_NORMAL, true); } - set_option_value("ft", 0L, "vim", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "vim", OPT_LOCAL); } curbuf->b_ro_locked--; @@ -6688,18 +4260,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)); } } @@ -6714,7 +4286,7 @@ static int open_cmdwin(void) ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level); } - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); // No Ex mode here! exmode_active = false; @@ -6904,90 +4476,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..5e3ad20a31 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -2,66 +2,77 @@ #define NVIM_EX_GETLN_H #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" +#include "nvim/types.h" -// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. -#define WILD_FREE 1 -#define WILD_EXPAND_FREE 2 -#define WILD_EXPAND_KEEP 3 -#define WILD_NEXT 4 -#define WILD_PREV 5 -#define WILD_ALL 6 -#define WILD_LONGEST 7 -#define WILD_ALL_KEEP 8 -#define WILD_CANCEL 9 -#define WILD_APPLY 10 +/// Command-line colors: one chunk +/// +/// Defines a region which has the same highlighting. +typedef struct { + int start; ///< Colored chunk start. + int end; ///< Colored chunk end (exclusive, > start). + int attr; ///< Highlight attr. +} CmdlineColorChunk; -#define WILD_LIST_NOTFOUND 0x01 -#define WILD_HOME_REPLACE 0x02 -#define WILD_USE_NL 0x04 -#define WILD_NO_BEEP 0x08 -#define WILD_ADD_SLASH 0x10 -#define WILD_KEEP_ALL 0x20 -#define WILD_SILENT 0x40 -#define WILD_ESCAPE 0x80 -#define WILD_ICASE 0x100 -#define WILD_ALLLINKS 0x200 -#define WILD_IGNORE_COMPLETESLASH 0x400 -#define WILD_NOERROR 0x800 // sets EW_NOERROR -#define WILD_BUFLASTUSED 0x1000 -#define BUF_DIFF_FILTER 0x2000 +/// Command-line colors +/// +/// Holds data about all colors. +typedef kvec_t(CmdlineColorChunk) CmdlineColors; -// flags used by vim_strsave_fnameescape() -#define VSE_NONE 0 -#define VSE_SHELL 1 ///< escape for a shell command -#define VSE_BUFFER 2 ///< escape for a ":buffer" command +/// Command-line coloring +/// +/// Holds both what are the colors and what have been colored. Latter is used to +/// suppress unnecessary calls to coloring callbacks. +typedef struct { + unsigned prompt_id; ///< ID of the prompt which was colored last. + char *cmdbuff; ///< What exactly was colored last time or NULL. + CmdlineColors colors; ///< Last colors. +} ColoredCmdline; -/// Present history tables +/// Keeps track how much state must be sent to external ui. 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; + kCmdRedrawNone, + kCmdRedrawPos, + kCmdRedrawAll, +} CmdRedraw; -/// Number of history tables -#define HIST_COUNT (HIST_DEBUG + 1) +/// Variables shared between getcmdline(), redrawcmdline() and others. +/// These need to be saved when using CTRL-R |, that's why they are in a +/// structure. +typedef struct cmdline_info CmdlineInfo; +struct cmdline_info { + char_u *cmdbuff; ///< pointer to command line buffer + int cmdbufflen; ///< length of cmdbuff + int cmdlen; ///< number of chars in command line + int cmdpos; ///< current cursor position + int cmdspos; ///< cursor column on screen + int cmdfirstc; ///< ':', '/', '?', '=', '>' or NUL + int cmdindent; ///< number of spaces before cmdline + char_u *cmdprompt; ///< message in front of cmdline + int cmdattr; ///< attributes for prompt + int overstrike; ///< Typing mode on the command line. Shared by + ///< getcmdline() and put_on_cmdline(). + expand_T *xpc; ///< struct being used for expansion, xp_pattern + ///< may point into cmdbuff + int xp_context; ///< type of expansion + char_u *xp_arg; ///< user-defined expansion arg + int input_fn; ///< when true Invoked for input() function + unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. + Callback highlight_callback; ///< Callback used for coloring user input. + ColoredCmdline last_colors; ///< Last cmdline colors + int level; ///< current cmdline level + CmdlineInfo *prev_ccline; ///< pointer to saved cmdline state + char special_char; ///< last putcmdline char (used for redraws) + bool special_shift; ///< shift of last putcmdline char + CmdRedraw redraw_state; ///< needed redraw for external cmdline +}; -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; +/// flags used by vim_strsave_fnameescape() +enum { + VSE_NONE = 0, + VSE_SHELL = 1, ///< escape for a shell command + VSE_BUFFER = 2, ///< escape for a ":buffer" command +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.h.generated.h" diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 1d670afa6d..e907c45e79 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" @@ -358,7 +359,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Then ":help" will re-use both the buffer and the window and set // the options, even when "options" is not in 'sessionoptions'. if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) { - curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname; + curtag = wp->w_tagstack[wp->w_tagstackidx - 1].tagname; } if (put_line(fd, "enew | setl bt=help") == FAIL @@ -955,7 +956,7 @@ void ex_mkrc(exarg_T *eap) } // When using 'viewdir' may have to create the directory. - if (using_vdir && !os_isdir(p_vdir)) { + if (using_vdir && !os_isdir((char *)p_vdir)) { vim_mkdir_emsg((const char *)p_vdir, 0755); } diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 1639f72990..8e780f4aaa 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -510,11 +510,11 @@ void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount, bcount_t old_byte = 0, new_byte = 0; int old_row, new_row; if (amount == MAXLNUM) { - old_row = (int)(line2 - line1 + 1); + old_row = line2 - line1 + 1; // TODO(bfredl): ej kasta? old_byte = (bcount_t)buf->deleted_bytes2; - new_row = (int)(amount_after + old_row); + new_row = amount_after + old_row; } else { // A region is either deleted (amount == MAXLNUM) or // added (line2 == MAXLNUM). The only other case is :move diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index ebefd1157c..bbc6e53aa8 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" @@ -109,16 +110,15 @@ typedef struct ff_stack { typedef struct ff_visited { struct ff_visited *ffv_next; - /* Visited directories are different if the wildcard string are - * different. So we have to save it. - */ + // Visited directories are different if the wildcard string are + // different. So we have to save it. char_u *ffv_wc_path; + // use FileID for comparison (needed because of links), else use filename. bool file_id_valid; FileID file_id; - /* The memory for this struct is allocated according to the length of - * ffv_fname. - */ + // The memory for this struct is allocated according to the length of + // ffv_fname. char_u ffv_fname[1]; // actually longer } ff_visited_T; @@ -220,8 +220,8 @@ static char_u e_pathtoolong[] = N_("E854: path too long for completion"); /// /// Upward search is only done on the starting dir. /// -/// If 'free_visited' is TRUE the list of already visited files/directories is -/// cleared. Set this to FALSE if you just want to search from another +/// If 'free_visited' is true the list of already visited files/directories is +/// cleared. Set this to false if you just want to search from another /// directory, but want to be sure that no directory from a previous search is /// searched again. This is useful if you search for a file at different places. /// The list of visited files/dirs can also be cleared with the function @@ -268,7 +268,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le ff_clear(search_ctx); // clear visited list if wanted - if (free_visited == TRUE) { + if (free_visited == true) { vim_findfile_free_visited(search_ctx); } else { /* Reuse old visited lists. Get the visited list for the given @@ -301,12 +301,12 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) { // Make the start dir an absolute path name. STRLCPY(ff_expand_buffer, rel_fname, len + 1); - search_ctx->ffsc_start_dir = (char_u *)FullName_save((char *)ff_expand_buffer, FALSE); + search_ctx->ffsc_start_dir = (char_u *)FullName_save((char *)ff_expand_buffer, false); } else { 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 @@ -471,7 +471,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le STRCPY(buf, ff_expand_buffer); STRCPY(buf + eb_len, search_ctx->ffsc_fix_path); - if (os_isdir(buf)) { + if (os_isdir((char *)buf)) { STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path); add_pathsep((char *)ff_expand_buffer); } else { @@ -509,9 +509,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le xfree(buf); } - sptr = ff_create_stack_element(ff_expand_buffer, - search_ctx->ffsc_wc_path, - level, 0); + sptr = ff_create_stack_element(ff_expand_buffer, search_ctx->ffsc_wc_path, level, 0); ff_push(search_ctx, sptr); search_ctx->ffsc_file_to_search = vim_strsave(filename); @@ -583,7 +581,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) { @@ -640,11 +638,8 @@ char_u *vim_findfile(void *search_ctx_arg) * first time (hence stackp->ff_filearray == NULL) */ if (stackp->ffs_filearray == NULL - && ff_check_visited(&search_ctx->ffsc_dir_visited_list - ->ffvl_visited_list, - stackp->ffs_fix_path, - stackp->ffs_wc_path - ) == FAIL) { + && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list, + stackp->ffs_fix_path, stackp->ffs_wc_path) == FAIL) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); @@ -789,8 +784,7 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = 0; stackp->ffs_stage = 0; } else { - rest_of_wildcards = &stackp->ffs_wc_path[ - STRLEN(stackp->ffs_wc_path)]; + rest_of_wildcards = &stackp->ffs_wc_path[STRLEN(stackp->ffs_wc_path)]; } if (stackp->ffs_stage == 0) { @@ -802,7 +796,7 @@ char_u *vim_findfile(void *search_ctx_arg) */ for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { if (!path_with_url(stackp->ffs_filearray[i]) - && !os_isdir((char_u *)stackp->ffs_filearray[i])) { + && !os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } // prepare the filename to be checked for existence below @@ -824,7 +818,7 @@ 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; } @@ -832,23 +826,17 @@ char_u *vim_findfile(void *search_ctx_arg) // if file exists and we didn't already find it if ((path_with_url((char *)file_path) || (os_path_exists(file_path) - && (search_ctx->ffsc_find_what - == FINDFILE_BOTH - || ((search_ctx->ffsc_find_what - == FINDFILE_DIR) - == os_isdir(file_path))))) + && (search_ctx->ffsc_find_what == FINDFILE_BOTH + || ((search_ctx->ffsc_find_what == FINDFILE_DIR) + == os_isdir((char *)file_path))))) #ifndef FF_VERBOSE && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, - (char_u *)"" - ) == OK) + file_path, (char_u *)"") == OK) #endif ) { #ifdef FF_VERBOSE if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, - (char_u *)"" - ) == FAIL) { + file_path, (char_u *)"") == FAIL) { if (p_verbose >= 5) { verbose_enter_scroll(); smsg("Already: %s", file_path); @@ -891,13 +879,13 @@ 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 { // still wildcards left, push the directories for further search for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { - if (!os_isdir((char_u *)stackp->ffs_filearray[i])) { + if (!os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } ff_push(search_ctx, @@ -921,7 +909,7 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_fix_path) == 0) { continue; // don't repush same directory } - if (!os_isdir((char_u *)stackp->ffs_filearray[i])) { + if (!os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } ff_push(search_ctx, @@ -944,7 +932,7 @@ char_u *vim_findfile(void *search_ctx_arg) // is the last starting directory in the stop list? if (ff_path_in_stoplist(search_ctx->ffsc_start_dir, (int)(path_end - search_ctx->ffsc_start_dir), - search_ctx->ffsc_stopdirs_v) == TRUE) { + search_ctx->ffsc_stopdirs_v) == true) { break; } @@ -1288,7 +1276,7 @@ static void ff_clear(ff_search_ctx_T *search_ctx) /// check if the given path is in the stopdirs /// -/// @return TRUE if yes else FALSE +/// @return true if yes else false static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) { int i = 0; @@ -1300,7 +1288,7 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) // if no path consider it as match if (path_len == 0) { - return TRUE; + return true; } for (i = 0; stopdirs_v[i] != NULL; i++) { @@ -1311,7 +1299,7 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) */ if (FNAMENCMP(stopdirs_v[i], path, path_len) == 0 && vim_ispathsep(stopdirs_v[i][path_len])) { - return TRUE; + return true; } } else { if (FNAMECMP(stopdirs_v[i], path) == 0) { @@ -1319,13 +1307,13 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) } } } - return FALSE; + return false; } /// Find the file name "ptr[len]" in the path. Also finds directory names. /// -/// On the first call set the parameter 'first' to TRUE to initialize -/// the search. For repeating calls to FALSE. +/// On the first call set the parameter 'first' to true to initialize +/// the search. For repeating calls to false. /// /// Repeating calls will return other files called 'ptr[len]' from the path. /// @@ -1354,8 +1342,8 @@ char_u *find_file_in_path(char_u *ptr, size_t len, int options, int first, char_ return find_file_in_path_option(ptr, len, options, first, (*curbuf->b_p_path == NUL ? p_path - : curbuf->b_p_path), - FINDFILE_BOTH, rel_fname, curbuf->b_p_sua); + : (char_u *)curbuf->b_p_path), + FINDFILE_BOTH, rel_fname, (char_u *)curbuf->b_p_sua); } static char_u *ff_file_to_find = NULL; @@ -1386,7 +1374,7 @@ void free_findfile(void) /// @return an allocated string for the file name. NULL for error. char_u *find_directory_in_path(char_u *ptr, size_t len, int options, char_u *rel_fname) { - return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath, + return find_file_in_path_option(ptr, len, options, true, p_cdpath, FINDFILE_DIR, rel_fname, (char_u *)""); } @@ -1401,11 +1389,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)) { @@ -1421,14 +1409,14 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first // copy file name into NameBuff, expanding environment variables save_char = ptr[len]; ptr[len] = NUL; - expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL); + expand_env_esc(ptr, (char_u *)NameBuff, MAXPATHL, false, true, NULL); ptr[len] = save_char; xfree(ff_file_to_find); - ff_file_to_find = vim_strsave(NameBuff); + ff_file_to_find = vim_strsave((char_u *)NameBuff); if (options & FNAME_UNESC) { // Change all "\ " to " ". - for (ptr = ff_file_to_find; *ptr != NUL; ++ptr) { + for (ptr = ff_file_to_find; *ptr != NUL; ptr++) { if (ptr[0] == '\\' && ptr[1] == ' ') { memmove(ptr, ptr + 1, STRLEN(ptr)); } @@ -1457,7 +1445,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first * If this is not a first call, return NULL. We already returned a * filename on the first call. */ - if (first == TRUE) { + if (first == true) { if (path_with_url((char *)ff_file_to_find)) { file_name = vim_strsave(ff_file_to_find); goto theend; @@ -1465,7 +1453,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first /* When FNAME_REL flag given first use the directory of the file. * Otherwise or when this fails use the current directory. */ - for (int run = 1; run <= 2; ++run) { + for (int run = 1; run <= 2; run++) { size_t l = STRLEN(ff_file_to_find); if (run == 1 && rel_to_curdir @@ -1482,21 +1470,21 @@ 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) + (os_path_exists((char_u *)NameBuff) && (find_what == FINDFILE_BOTH || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { - file_name = vim_strsave(NameBuff); + file_name = vim_strsave((char_u *)NameBuff); goto theend; } if (*buf == NUL) { break; } assert(MAXPATHL >= l); - copy_option_part((char **)&buf, (char *)NameBuff + l, MAXPATHL - l, ","); + copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ","); } } } @@ -1506,11 +1494,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first * When "first" is set, first setup to the start of the option. * Otherwise continue to find the next match. */ - if (first == TRUE) { + 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 (;;) { @@ -1520,7 +1508,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } - did_findfile_init = FALSE; + did_findfile_init = false; } else { char_u *r_ptr; @@ -1536,22 +1524,22 @@ 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; + did_findfile_init = true; } xfree(buf); } } } if (file_name == NULL && (options & FNAME_MESS)) { - if (first == TRUE) { + if (first == true) { if (find_what == FINDFILE_DIR) { semsg(_("E344: Can't find directory \"%s\" in cdpath"), ff_file_to_find); @@ -1653,7 +1641,7 @@ int vim_chdirfile(char *fname, CdCause cause) STRLCPY(dir, fname, MAXPATHL); *path_tail_with_sep(dir) = NUL; - if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) { + if (os_dirname((char_u *)NameBuff, sizeof(NameBuff)) != OK) { NameBuff[0] = NUL; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index b98984017b..d67cd36a85 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" @@ -40,6 +42,7 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" @@ -48,7 +51,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" @@ -132,7 +134,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) // calling filemess(). msg_scroll_save = msg_scroll; if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { - msg_scroll = FALSE; + msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg check_for_delay(false); @@ -141,7 +143,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scroll = msg_scroll_save; msg_scrolled_ign = true; // may truncate the message to avoid a hit-return prompt - msg_outtrans_attr(msg_may_trunc(FALSE, IObuff), attr); + msg_outtrans_attr((char *)msg_may_trunc(false, IObuff), attr); msg_clr_eos(); ui_flush(); msg_scrolled_ign = false; @@ -165,6 +167,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) /// READ_STDIN read from stdin instead of a file /// READ_BUFFER read from curbuf instead of a file (converting after reading /// stdin) +/// READ_NOFILE do not read a file, only trigger BufReadCmd /// READ_DUMMY read into a dummy buffer (to check if file contents changed) /// READ_KEEP_UNDO don't clear undo info or read it from a file /// READ_FIFO read from fifo/socket instead of a file @@ -332,12 +335,18 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } curbuf->b_op_start = orig_start; + + if (flags & READ_NOFILE) { + // Return NOTDONE instead of FAIL so that BufEnter can be triggered + // and other operations don't fail. + return NOTDONE; + } } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { - msg_scroll = FALSE; // overwrite previous file message + msg_scroll = false; // overwrite previous file message } else { - msg_scroll = TRUE; // don't overwrite previous file message + msg_scroll = true; // don't overwrite previous file message } // If the name is too long we might crash further on, quit here. if (fname != NULL && *fname != NUL) { @@ -400,7 +409,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, */ check_readonly = (newfile && (curbuf->b_flags & BF_CHECK_RO)); if (check_readonly && !readonlymode) { - curbuf->b_p_ro = FALSE; + curbuf->b_p_ro = false; } if (newfile && !read_stdin && !read_buffer && !read_fifo) { @@ -516,18 +525,18 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, * loaded. Help files always get readonly mode */ if ((check_readonly && file_readonly) || curbuf->b_help) { - curbuf->b_p_ro = TRUE; + curbuf->b_p_ro = true; } if (set_options) { // Don't change 'eol' if reading from buffer as it will already be // correctly set when reading stdin. if (!read_buffer) { - curbuf->b_p_eol = TRUE; - curbuf->b_start_eol = TRUE; + curbuf->b_p_eol = true; + curbuf->b_start_eol = true; } - curbuf->b_p_bomb = FALSE; - curbuf->b_start_bomb = FALSE; + curbuf->b_p_bomb = false; + curbuf->b_start_bomb = false; } // Create a swap file now, so that other Vims are warned that we are @@ -581,7 +590,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, return FAIL; } - ++no_wait_return; // don't wait for return yet + no_wait_return++; // don't wait for return yet // Set '[ mark to the line above where the lines go (line 1 if zero). orig_start = curbuf->b_op_start; @@ -631,9 +640,9 @@ 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 + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } /* @@ -654,7 +663,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } else { emsg(_("E201: *ReadPre autocommands must not change current buffer")); } - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } } @@ -668,7 +677,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } - msg_scroll = FALSE; // overwrite the file message + msg_scroll = false; // overwrite the file message /* * Set linecnt now, before the "retry" caused by a wrong guess for @@ -690,7 +699,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, * Decide which 'encoding' to use or use first. */ if (eap != NULL && eap->force_enc != 0) { - fenc = (char *)enc_canonize((char_u *)eap->cmd + eap->force_enc); + fenc = enc_canonize(eap->cmd + eap->force_enc); fenc_alloced = true; keep_dest_enc = true; } else if (curbuf->b_p_bin) { @@ -706,10 +715,10 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, fenc_alloced = false; } else if (*p_fencs == NUL) { - fenc = (char *)curbuf->b_p_fenc; // use format from buffer + fenc = curbuf->b_p_fenc; // use format from buffer fenc_alloced = false; } else { - fenc_next = (char *)p_fencs; // try items in 'fileencodings' + fenc_next = p_fencs; // try items in 'fileencodings' fenc = (char *)next_fenc(&fenc_next, &fenc_alloced); } @@ -749,8 +758,8 @@ retry: } file_rewind = false; if (set_options) { - curbuf->b_p_bomb = FALSE; - curbuf->b_start_bomb = FALSE; + curbuf->b_p_bomb = false; + curbuf->b_start_bomb = false; } conv_error = 0; } @@ -764,7 +773,7 @@ retry: } else { if (eap != NULL && eap->force_ff != 0) { fileformat = get_fileformat_force(curbuf, eap); - try_unix = try_dos = try_mac = FALSE; + try_unix = try_dos = try_mac = false; } else if (curbuf->b_p_bin) { fileformat = EOL_UNIX; // binary: use Unix format } else if (*p_ffs == @@ -890,7 +899,7 @@ retry: } // Set "can_retry" when it's possible to rewind the file and try with - // another "fenc" value. It's FALSE when no other "fenc" to try, reading + // another "fenc" value. It's false when no other "fenc" to try, reading // stdin or fixed at a specific encoding. can_retry = (*fenc != NUL && !read_stdin && !keep_dest_enc && !read_fifo); @@ -1009,7 +1018,7 @@ retry: // Change NL to NUL to reverse the effect done // below. n = (int)(size - tlen); - for (ni = 0; ni < n; ++ni) { + for (ni = 0; ni < n; ni++) { if (p[ni] == NL) { ptr[tlen++] = NUL; } else { @@ -1136,8 +1145,8 @@ retry: size -= blen; memmove(ptr, ptr + blen, (size_t)size); if (set_options) { - curbuf->b_p_bomb = TRUE; - curbuf->b_start_bomb = TRUE; + curbuf->b_p_bomb = true; + curbuf->b_start_bomb = true; } } @@ -1196,11 +1205,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 +1247,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 +1565,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 +1586,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 +1642,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; } @@ -1670,7 +1679,7 @@ failed: && ptr == line_start + 1)) { // remember for when writing if (set_options) { - curbuf->b_p_eol = FALSE; + curbuf->b_p_eol = false; } *ptr = NUL; len = (colnr_T)(ptr - line_start + 1); @@ -1727,7 +1736,7 @@ failed: os_remove(tmpname); // delete converted file xfree(tmpname); } - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now /* * In recovery mode everything but autocommands is skipped. @@ -1747,7 +1756,7 @@ failed: linecnt = 0; } if (newfile || read_buffer) { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); // After reading the text into the buffer the diff info needs to // be updated. diff_invalidate(curbuf); @@ -1771,7 +1780,7 @@ failed: if (!(flags & READ_DUMMY)) { filemess(curbuf, (char_u *)sfname, (char_u *)_(e_interr), 0); if (newfile) { - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now } } msg_scroll = msg_save; @@ -1786,30 +1795,30 @@ failed: #ifdef UNIX if (S_ISFIFO(perm)) { // fifo STRCAT(IObuff, _("[fifo]")); - c = TRUE; + c = true; } if (S_ISSOCK(perm)) { // or socket STRCAT(IObuff, _("[socket]")); - c = TRUE; + c = true; } # ifdef OPEN_CHR_FILES if (S_ISCHR(perm)) { // or character special STRCAT(IObuff, _("[character special]")); - c = TRUE; + c = true; } # endif #endif if (curbuf->b_p_ro) { STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")); - c = TRUE; + c = true; } if (read_no_eol_lnum) { msg_add_eol(); - c = TRUE; + c = true; } if (ff_error == EOL_DOS) { STRCAT(IObuff, _("[CR missing]")); - c = TRUE; + c = true; } if (split) { STRCAT(IObuff, _("[long lines split]")); @@ -1817,25 +1826,25 @@ failed: } if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); - c = TRUE; + c = true; } else if (converted) { STRCAT(IObuff, _("[converted]")); - c = TRUE; + c = true; } if (conv_error != 0) { sprintf((char *)IObuff + STRLEN(IObuff), _("[CONVERSION ERROR in line %" PRId64 "]"), (int64_t)conv_error); - c = TRUE; + c = true; } else if (illegal_byte > 0) { sprintf((char *)IObuff + STRLEN(IObuff), _("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte); - c = TRUE; + c = true; } else if (error) { STRCAT(IObuff, _("[READ ERRORS]")); - c = TRUE; + c = true; } if (msg_add_fileformat(fileformat)) { - c = TRUE; + c = true; } msg_add_lines(c, (long)linecnt, filesize); @@ -1863,9 +1872,8 @@ failed: // with errors writing the file requires ":w!" if (newfile && (error || conv_error != 0 - || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP) - )) { - curbuf->b_p_ro = TRUE; + || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP))) { + curbuf->b_p_ro = true; } u_clearline(); // cannot use "U" command after adding lines @@ -1946,7 +1954,7 @@ failed: if (!au_did_filetype && *curbuf->b_p_ft != NUL) { // EVENT_FILETYPE was not triggered but the buffer already has a // filetype. Trigger EVENT_FILETYPE using the existing filetype. - apply_autocmds(EVENT_FILETYPE, (char *)curbuf->b_p_ft, curbuf->b_fname, true, curbuf); + apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname, true, curbuf); } } else { apply_autocmds_exarg(EVENT_FILEREADPOST, sfname, sfname, @@ -1997,9 +2005,9 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) linenr_T lnum; lnum = curbuf->b_ml.ml_line_count - linecnt + 1; - for (s = p; s < endp; ++s) { + for (s = p; s < endp; s++) { if (*s == '\n') { - ++lnum; + lnum++; } } return lnum; @@ -2016,11 +2024,11 @@ void prep_exarg(exarg_T *eap, const buf_T *buf) snprintf(eap->cmd, cmd_len, "e ++enc=%s", buf->b_p_fenc); eap->force_enc = 8; eap->bad_char = buf->b_bad_char; - eap->force_ff = *buf->b_p_ff; + eap->force_ff = (unsigned char)(*buf->b_p_ff); eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN; - eap->read_edit = FALSE; - eap->forceit = FALSE; + eap->read_edit = false; + eap->forceit = false; } /// Set default or forced 'fileformat' and 'binary'. @@ -2048,8 +2056,8 @@ void set_file_options(int set_options, exarg_T *eap) void set_forced_fenc(exarg_T *eap) { if (eap->force_enc != 0) { - char_u *fenc = enc_canonize((char_u *)eap->cmd + eap->force_enc); - set_string_option_direct("fenc", -1, (char *)fenc, OPT_FREE|OPT_LOCAL, 0); + char *fenc = enc_canonize(eap->cmd + eap->force_enc); + set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); xfree(fenc); } } @@ -2073,12 +2081,12 @@ static char_u *next_fenc(char **pp, bool *alloced) } p = (char_u *)vim_strchr((*pp), ','); if (p == NULL) { - r = enc_canonize((char_u *)(*pp)); + r = (char_u *)enc_canonize(*pp); *pp += STRLEN(*pp); } else { r = vim_strnsave((char_u *)(*pp), (size_t)(p - (char_u *)(*pp))); *pp = (char *)p + 1; - p = enc_canonize(r); + p = (char_u *)enc_canonize((char *)r); xfree(r); r = p; } @@ -2159,9 +2167,9 @@ char *new_file_message(void) /// /// If "forceit" is true, we don't care for errors when attempting backups. /// In case of an error everything possible is done to restore the original -/// file. But when "forceit" is TRUE, we risk losing it. +/// file. But when "forceit" is true, we risk losing it. /// -/// When "reset_changed" is TRUE and "append" == FALSE and "start" == 1 and +/// When "reset_changed" is true and "append" == false and "start" == 1 and /// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed. /// /// This function must NOT use NameBuff (because it's called by autowrite()). @@ -2202,9 +2210,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en int bufsize; long perm; // file permissions int retval = OK; - int newfile = false; // TRUE if file doesn't exist yet + int newfile = false; // true if file doesn't exist yet int msg_save = msg_scroll; - int overwriting; // TRUE if writing over original + int overwriting; // true if writing over original int no_eol = false; // no end-of-line written int device = false; // writing to a device int prev_got_int = got_int; @@ -2213,7 +2221,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; #if defined(UNIX) - int made_writable = FALSE; // 'w' bit has been set + int made_writable = false; // 'w' bit has been set #endif // writing everything int whole = (start == 1 && end == buf->b_ml.ml_line_count); @@ -2232,7 +2240,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en vim_acl_T acl = NULL; /* ACL copied from original file to backup or new file */ #endif - int write_undo_file = FALSE; + int write_undo_file = false; context_sha256_T sha_ctx; unsigned int bkc = get_bkc_value(buf); const pos_T orig_start = buf->b_op_start; @@ -2264,7 +2272,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en // must init bw_conv_buf and bw_iconv_fd before jumping to "fail" write_info.bw_conv_buf = NULL; - write_info.bw_conv_error = FALSE; + write_info.bw_conv_error = false; write_info.bw_conv_error_lnum = 0; write_info.bw_restlen = 0; #ifdef HAVE_ICONV @@ -2312,10 +2320,10 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (buf->b_ffname != NULL && FNAMECMP(ffname, buf->b_ffname) == 0) { overwriting = true; } else { - overwriting = FALSE; + overwriting = false; } - ++no_wait_return; // don't wait for return yet + no_wait_return++; // don't wait for return yet /* * Set '[ and '] marks to the lines to be written. @@ -2327,12 +2335,12 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en { aco_save_T aco; - int buf_ffname = FALSE; - int buf_sfname = FALSE; - int buf_fname_f = FALSE; - int buf_fname_s = FALSE; - int did_cmd = FALSE; - int nofile_err = FALSE; + int buf_ffname = false; + int buf_sfname = false; + int buf_fname_f = false; + int buf_fname_s = false; + int did_cmd = false; + int nofile_err = false; int empty_memline = (buf->b_ml.ml_mfp == NULL); bufref_T bufref; @@ -2477,7 +2485,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; @@ -2510,9 +2518,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } if (shortmess(SHM_OVER) && !exiting) { - msg_scroll = FALSE; // overwrite previous file message + msg_scroll = false; // overwrite previous file message } else { - msg_scroll = TRUE; // don't overwrite previous file message + msg_scroll = true; // don't overwrite previous file message } if (!filtering) { filemess(buf, @@ -2523,7 +2531,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #endif (char_u *)"", 0); // show that we are busy } - msg_scroll = FALSE; // always overwrite the file message now + msg_scroll = false; // always overwrite the file message now buffer = verbose_try_malloc(BUFSIZE); // can't allocate big buffer, use small one (to be able to write when out of @@ -2556,8 +2564,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } /* It's a device of some kind (or a fifo) which we can write to * but for which we can't make a backup. */ - device = TRUE; - newfile = TRUE; + device = true; + newfile = true; perm = -1; } } @@ -2569,8 +2577,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en goto fail; } if (c == NODE_WRITABLE) { - device = TRUE; - newfile = TRUE; + device = true; + newfile = true; perm = -1; } else { perm = os_getperm((const char *)fname); @@ -2636,7 +2644,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en * abort it. */ prev_got_int = got_int; - got_int = FALSE; + got_int = false; // Mark the buffer as 'being saved' to prevent changed buffer warnings buf->b_saving = true; @@ -2654,7 +2662,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en const bool no_prepend_dot = false; if ((bkc & BKC_YES) || append) { // "yes" - backup_copy = TRUE; + backup_copy = true; } else if ((bkc & BKC_AUTO)) { // "auto" int i; @@ -2667,7 +2675,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (os_fileinfo_hardlinks(&file_info_old) > 1 || !os_fileinfo_link(fname, &file_info) || !os_fileinfo_id_equal(&file_info, &file_info_old)) { - backup_copy = TRUE; + backup_copy = true; } else { /* * Check if we can create a file and set the owner/group to @@ -2687,7 +2695,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en fd = os_open((char *)IObuff, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, (int)perm); if (fd < 0) { // can't write in directory - backup_copy = TRUE; + backup_copy = true; } else { #ifdef UNIX os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid); @@ -2695,7 +2703,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en || file_info.stat.st_uid != file_info_old.stat.st_uid || file_info.stat.st_gid != file_info_old.stat.st_gid || (long)file_info.stat.st_mode != perm) { - backup_copy = TRUE; + backup_copy = true; } #endif /* Close the file before removing it, on MS-Windows we @@ -2717,7 +2725,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if ((bkc & BKC_BREAKSYMLINK) && file_info_link_ok && !os_fileinfo_id_equal(&file_info, &file_info_old)) { - backup_copy = FALSE; + backup_copy = false; } // Hardlinks. @@ -2725,7 +2733,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && os_fileinfo_hardlinks(&file_info_old) > 1 && (!file_info_link_ok || os_fileinfo_id_equal(&file_info, &file_info_old))) { - backup_copy = FALSE; + backup_copy = false; } #endif } @@ -2734,7 +2742,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (*p_bex == NUL) { backup_ext = ".bak"; } else { - backup_ext = (char *)p_bex; + backup_ext = p_bex; } if (backup_copy) { @@ -2756,7 +2764,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en * For these reasons, the existing writable file must be truncated * and reused. Creation of a backup COPY will be attempted. */ - dirp = (char *)p_bdir; + dirp = p_bdir; while (*dirp) { /* * Isolate one directory name, using an entry in 'bdir'. @@ -2767,7 +2775,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } - if (*dirp == NUL && !os_isdir(IObuff)) { + if (*dirp == NUL && !os_isdir((char *)IObuff)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { @@ -2787,7 +2795,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en rootname = (char *)get_file_in_dir((char_u *)fname, IObuff); if (rootname == NULL) { - some_error = TRUE; // out of memory + some_error = true; // out of memory goto nobackup; } @@ -2802,7 +2810,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (backup == NULL) { xfree(rootname); - some_error = TRUE; // out of memory + some_error = true; // out of memory goto nobackup; } @@ -2887,7 +2895,7 @@ nobackup: if (backup == NULL && errmsg == NULL) { SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)")); } - // Ignore errors when forceit is TRUE. + // Ignore errors when forceit is true. if ((some_error || errmsg != NULL) && !forceit) { retval = FAIL; goto fail; @@ -2917,7 +2925,7 @@ nobackup: * path/fo.o.h.bak Try all directories in 'backupdir', first one * that works is used. */ - dirp = (char *)p_bdir; + dirp = p_bdir; while (*dirp) { /* * Isolate one directory name and make the backup file name. @@ -2928,7 +2936,7 @@ nobackup: if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } - if (*dirp == NUL && !os_isdir(IObuff)) { + if (*dirp == NUL && !os_isdir((char *)IObuff)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { @@ -3047,10 +3055,10 @@ nobackup: // Check for forced 'fileencoding' from "++opt=val" argument. if (eap != NULL && eap->force_enc != 0) { fenc = eap->cmd + eap->force_enc; - fenc = (char *)enc_canonize((char_u *)fenc); + fenc = enc_canonize(fenc); fenc_tofree = fenc; } else { - fenc = (char *)buf->b_p_fenc; + fenc = buf->b_p_fenc; } // Check if the file needs to be converted. @@ -3087,8 +3095,8 @@ nobackup: if (!write_info.bw_conv_buf) { end = 0; } - write_info.bw_first = TRUE; - } else + write_info.bw_first = true; + } else { #endif /* @@ -3104,6 +3112,11 @@ nobackup: } } } + +#ifdef HAVE_ICONV +} +#endif + if (converted && wb_flags == 0 #ifdef HAVE_ICONV && write_info.bw_iconv_fd == (iconv_t)-1 @@ -3113,7 +3126,7 @@ nobackup: SET_ERRMSG(_("E213: Cannot convert (add ! to write without conversion)")); goto restore_backup; } - notconverted = TRUE; + notconverted = true; } // If conversion is taking place, we may first pretend to write and check @@ -3135,12 +3148,12 @@ nobackup: } else { // Open the file "wfname" for writing. // We may try to open the file twice: If we can't write to the file - // and forceit is TRUE we delete the existing file and try to + // and forceit is true we delete the existing file and try to // create a new one. If this still fails we may have lost the // original file! (this may happen when the user reached his // quotum for number of files). // Appending will fail if the file does not exist and forceit is - // FALSE. + // false. while ((fd = os_open(wfname, O_WRONLY | (append ? @@ -3364,7 +3377,7 @@ restore_backup: // been written to disk and we don't lose it. // For a device do try the fsync() but don't complain if it does not work // (could be a pipe). - // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. + // If the 'fsync' option is false, don't fsync(). Useful for laptops. int error; if (p_fs && (error = os_fsync(fd)) != 0 && !device // fsync not supported on this storage. @@ -3483,7 +3496,7 @@ restore_backup: } lnum -= start; // compute number of written lines - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now #if !defined(UNIX) fname = sfname; // use shortname now, for the messages @@ -3493,32 +3506,32 @@ restore_backup: c = false; if (write_info.bw_conv_error) { STRCAT(IObuff, _(" CONVERSION ERROR")); - c = TRUE; + c = true; if (write_info.bw_conv_error_lnum != 0) { vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %" PRId64 ";"), (int64_t)write_info.bw_conv_error_lnum); } } else if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); - c = TRUE; + c = true; } else if (converted) { STRCAT(IObuff, _("[converted]")); - c = TRUE; + c = true; } if (device) { STRCAT(IObuff, _("[Device]")); - c = TRUE; + c = true; } else if (newfile) { STRCAT(IObuff, new_file_message()); c = true; } if (no_eol) { msg_add_eol(); - c = TRUE; + c = true; } // may add [unix/dos/mac] if (msg_add_fileformat(fileformat)) { - c = TRUE; + c = true; } msg_add_lines(c, (long)lnum, nchars); // add line/char count if (!shortmess(SHM_WRITE)) { @@ -3566,7 +3579,7 @@ restore_backup: * the backup file our 'original' file. */ if (*p_pm && dobackup) { - char *const org = modname(fname, (char *)p_pm, false); + char *const org = modname(fname, p_pm, false); if (backup != NULL) { /* @@ -3622,7 +3635,7 @@ restore_backup: * Finish up. We get here either after failure or success. */ fail: - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now nofail: // Done saving, we accept changed buffer warnings again @@ -3709,23 +3722,23 @@ nofail: if (append) { apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else if (filtering) { apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else if (reset_changed && whole) { apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else { apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); if (aborting()) { // autocmds may abort script processing - retval = FALSE; + retval = false; } } @@ -3992,11 +4005,11 @@ static int buf_write_bytes(struct bw_info *ip) } if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) { - ip->bw_conv_error = TRUE; + ip->bw_conv_error = true; ip->bw_conv_error_lnum = ip->bw_start_lnum; } if (c == NL) { - ++ip->bw_start_lnum; + ip->bw_start_lnum++; } } if (flags & FIO_LATIN1) { @@ -4047,7 +4060,7 @@ static int buf_write_bytes(struct bw_info *ip) to = (char *)ip->bw_conv_buf; tolen = save_len; } - ip->bw_first = FALSE; + ip->bw_first = false; } /* @@ -4056,7 +4069,7 @@ static int buf_write_bytes(struct bw_info *ip) if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen) == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) || fromlen > CONV_RESTLEN) { - ip->bw_conv_error = TRUE; + ip->bw_conv_error = true; return FAIL; } @@ -4161,12 +4174,12 @@ static bool need_conversion(const char_u *fenc) int fenc_flags; if (*fenc == NUL || STRCMP(p_enc, fenc) == 0) { - same_encoding = TRUE; + same_encoding = true; fenc_flags = 0; } else { // Ignore difference between "ansi" and "latin1", "ucs-4" and // "ucs-4be", etc. - enc_flags = get_fio_flags(p_enc); + enc_flags = get_fio_flags((char_u *)p_enc); fenc_flags = get_fio_flags(fenc); same_encoding = (enc_flags != 0 && fenc_flags == enc_flags); } @@ -4190,7 +4203,7 @@ static int get_fio_flags(const char_u *name) int prop; if (*name == NUL) { - name = p_enc; + name = (char_u *)p_enc; } prop = enc_canon_props(name); if (prop & ENC_UNICODE) { @@ -4294,10 +4307,10 @@ static int make_bom(char_u *buf, char_u *name) /// Shorten filename of a buffer. /// -/// @param force when TRUE: Use full path from now on for files currently being +/// @param force when true: Use full path from now on for files currently being /// edited, both for file name and swap file name. Try to shorten the file /// names a bit, if safe to do so. -/// when FALSE: Only try to shorten absolute file names. +/// when false: Only try to shorten absolute file names. /// /// For buffers that have buftype "nofile" or "scratch": never change the file /// name. @@ -4384,7 +4397,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) } add_pathsep(retval); fnamelen = strlen(retval); - prepend_dot = FALSE; // nothing to prepend a dot to + prepend_dot = false; // nothing to prepend a dot to } else { fnamelen = strlen(fname); retval = xmalloc(fnamelen + extlen + 3); @@ -4767,7 +4780,7 @@ int vim_rename(const char_u *from, const char_u *to) return 0; } -static int already_warned = FALSE; +static int already_warned = false; /// Check if any not hidden buffer has been changed. /// Postpone the check if there are characters in the stuff buffer, a global @@ -4776,7 +4789,7 @@ static int already_warned = FALSE; /// /// @param focus called for GUI focus event /// -/// @return TRUE if some message was written (screen should be redrawn and cursor positioned). +/// @return true if some message was written (screen should be redrawn and cursor positioned). int check_timestamps(int focus) { int didit = 0; @@ -4784,15 +4797,15 @@ int check_timestamps(int focus) // Don't check timestamps while system() or another low-level function may // cause us to lose and gain focus. if (no_check_timestamps > 0) { - return FALSE; + return false; } // Avoid doing a check twice. The OK/Reload dialog can cause a focus // event and we would keep on checking if the file is steadily growing. // Do check again after typing something. if (focus && did_check_timestamps) { - need_check_timestamps = TRUE; - return FALSE; + need_check_timestamps = true; + return false; } if (!stuff_empty() || global_busy || !typebuf_typed() @@ -4819,8 +4832,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"); @@ -4935,7 +4948,7 @@ int buf_check_timestamp(buf_T *buf) buf_store_file_info(buf, &file_info); } - if (os_isdir((char_u *)buf->b_fname)) { + if (os_isdir(buf->b_fname)) { // Don't do anything for a directory. Might contain the file explorer. } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) && !bufIsChanged(buf) && file_info_ok) { @@ -5073,7 +5086,7 @@ int buf_check_timestamp(buf_T *buf) redraw_cmdline = false; } } - already_warned = TRUE; + already_warned = true; } xfree(path); @@ -5122,7 +5135,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); } @@ -5304,8 +5317,8 @@ static void vim_mktempdir(void) mode_t umask_save = umask(0077); for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) { // Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999". - expand_env((char_u *)temp_dirs[i], (char_u *)tmp, TEMP_FILE_PATH_MAXLEN - 64); - if (!os_isdir((char_u *)tmp)) { + expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64); + if (!os_isdir(tmp)) { continue; } @@ -5315,7 +5328,7 @@ static void vim_mktempdir(void) xstrlcat(tmp, user, sizeof(tmp)); (void)os_mkdir(tmp, 0700); // Always create, to avoid a race. bool owned = os_file_owned(tmp); - bool isdir = os_isdir((char_u *)tmp); + bool isdir = os_isdir(tmp); #ifdef UNIX int perm = os_getperm(tmp); // XDG_RUNTIME_DIR must be owned by the user, mode 0700. bool valid = isdir && owned && 0700 == (perm & 0777); @@ -5580,14 +5593,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; @@ -5604,8 +5617,8 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) /// Convert the given pattern "pat" which has shell style wildcards in it, into /// a regular expression, and return the result in allocated memory. If there -/// is a directory path separator to be matched, then TRUE is put in -/// allow_dirs, otherwise FALSE is put there -- webb. +/// is a directory path separator to be matched, then true is put in +/// allow_dirs, otherwise false is put there -- webb. /// Handle backslashes before special characters, like "\*" and "\ ". /// /// @param pat_end first char after pattern or NULL @@ -5623,7 +5636,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs bool add_dollar = true; if (allow_dirs != NULL) { - *allow_dirs = FALSE; + *allow_dirs = false; } if (pat_end == NULL) { pat_end = pat + STRLEN(pat); @@ -5680,7 +5693,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 '.': @@ -5709,7 +5722,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '/'; reg_pat[i++] = ']'; if (allow_dirs != NULL) { - *allow_dirs = TRUE; + *allow_dirs = true; } break; } @@ -5743,7 +5756,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs && (!no_bslash || *p != '\\') #endif ) { - *allow_dirs = TRUE; + *allow_dirs = true; } reg_pat[i++] = '\\'; reg_pat[i++] = *p; @@ -5756,7 +5769,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '/'; reg_pat[i++] = ']'; if (allow_dirs != NULL) { - *allow_dirs = TRUE; + *allow_dirs = true; } break; #endif @@ -5768,7 +5781,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..ae3c51f1bc 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" @@ -16,6 +15,7 @@ #define READ_KEEP_UNDO 0x20 // keep undo info #define READ_FIFO 0x40 // read from fifo or socket #define READ_NOWINENTER 0x80 // do not trigger BufWinEnter +#define READ_NOFILE 0x100 // do not read a file, do trigger BufReadCmd #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 8f26e03a94..b4ddb3ec08 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. @@ -393,7 +394,7 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha } // Force a redraw to remove the Visual highlighting. if (had_visual) { - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } @@ -720,7 +721,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const emsg(_(e_nofold)); // Force a redraw to remove the Visual highlighting. if (had_visual) { - redraw_buf_later(wp->w_buffer, INVERTED); + redraw_buf_later(wp->w_buffer, UPD_INVERTED); } } else { // Deleting markers may make cursor column invalid @@ -756,7 +757,7 @@ void clearFolding(win_T *win) /// The changes in lines from top to bot (inclusive). void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) { - if (disable_fold_update || compl_busy || State & MODE_INSERT) { + if (disable_fold_update || State & MODE_INSERT) { return; } @@ -818,7 +819,7 @@ void foldUpdateAfterInsert(void) void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } // foldMoveTo() {{{2 @@ -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; @@ -1555,7 +1556,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) } parseMarker(wp); - foldAddMarker(buf, start, wp->w_p_fmr, foldstartmarkerlen); + foldAddMarker(buf, start, (char_u *)wp->w_p_fmr, foldstartmarkerlen); foldAddMarker(buf, end, foldendmarker, foldendmarkerlen); // Update both changes here, to avoid all folds after the start are @@ -1574,9 +1575,9 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) /// Add "marker[markerlen]" in 'commentstring' to position `pos`. static void foldAddMarker(buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) { - char_u *cms = buf->b_p_cms; + char_u *cms = (char_u *)buf->b_p_cms; char_u *newline; - char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s"); + char_u *p = (char_u *)strstr(buf->b_p_cms, "%s"); bool line_is_comment = false; linenr_T lnum = pos.lnum; @@ -1620,7 +1621,7 @@ static void deleteFoldMarkers(win_T *wp, fold_T *fp, int recursive, linenr_T lnu lnum_off + fp->fd_top); } } - foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off, wp->w_p_fmr, + foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off, (char_u *)wp->w_p_fmr, foldstartmarkerlen); foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off + fp->fd_len - 1, foldendmarker, foldendmarkerlen); @@ -1638,7 +1639,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t mark return; } - char_u *cms = buf->b_p_cms; + char_u *cms = (char_u *)buf->b_p_cms; char_u *line = ml_get_buf(buf, lnum, false); for (char_u *p = line; *p != NUL; p++) { if (STRNCMP(p, marker, markerlen) != 0) { @@ -1728,7 +1729,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin emsg_silent++; // handle exceptions, but don't display errors text = - (char_u *)eval_to_string_safe((char *)wp->w_p_fdt, NULL, + (char_u *)eval_to_string_safe(wp->w_p_fdt, NULL, was_set_insecurely(wp, "foldtext", OPT_LOCAL)); emsg_silent--; @@ -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); + char_u *cms_start = (char_u *)skipwhite(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 @@ -2853,7 +2854,7 @@ static void foldlevelIndent(fline_T *flp) // empty line or lines starting with a character in 'foldignore': level // depends on surrounding lines - if (*s == NUL || vim_strchr((char *)flp->wp->w_p_fdi, *s) != NULL) { + if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, *s) != NULL) { // first and last line can't be undefined, use level 0 if (lnum == 1 || lnum == buf->b_ml.ml_line_count) { flp->lvl = 0; @@ -2906,7 +2907,7 @@ static void foldlevelExpr(fline_T *flp) const bool save_keytyped = KeyTyped; int c; - const int n = eval_foldexpr((char *)flp->wp->w_p_fde, &c); + const int n = eval_foldexpr(flp->wp->w_p_fde, &c); KeyTyped = save_keytyped; switch (c) { @@ -2984,8 +2985,8 @@ static void foldlevelExpr(fline_T *flp) /// Relies on the option value to have been checked for correctness already. static void parseMarker(win_T *wp) { - foldendmarker = (char_u *)vim_strchr((char *)wp->w_p_fmr, ','); - foldstartmarkerlen = (size_t)(foldendmarker++ - wp->w_p_fmr); + foldendmarker = (char_u *)vim_strchr(wp->w_p_fmr, ','); + foldstartmarkerlen = (size_t)(foldendmarker++ - (char_u *)wp->w_p_fmr); foldendmarkerlen = STRLEN(foldendmarker); } @@ -3002,7 +3003,7 @@ static void foldlevelMarker(fline_T *flp) int start_lvl = flp->lvl; // cache a few values for speed - char_u *startmarker = flp->wp->w_p_fmr; + char_u *startmarker = (char_u *)flp->wp->w_p_fmr; int cstart = *startmarker; startmarker++; int cend = *foldendmarker; @@ -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, EvalFuncData fptr) +{ + foldclosed_both(argvars, rettv, false); +} + +/// "foldclosedend()" function +void f_foldclosedend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + foldclosed_both(argvars, rettv, true); +} + +/// "foldlevel()" function +void f_foldlevel(typval_T *argvars, typval_T *rettv, EvalFuncData 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, EvalFuncData 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, EvalFuncData 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/garray.c b/src/nvim/garray.c index 7a3c14b1bb..1afabe4e10 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -131,7 +131,7 @@ void ga_remove_duplicate_strings(garray_T *gap) fnames[j - 1] = fnames[j]; } - --gap->ga_len; + gap->ga_len--; } } } diff --git a/src/nvim/garray.h b/src/nvim/garray.h index 56bd5c9130..0281678925 100644 --- a/src/nvim/garray.h +++ b/src/nvim/garray.h @@ -4,7 +4,7 @@ #include <stddef.h> // for size_t #include "nvim/log.h" -#include "nvim/types.h" // for char_u +#include "nvim/types.h" /// Structure used for growing arrays. /// This is used to store information that only grows, is deleted all at diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index 70a7be86b5..d872ffd6a9 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -26,6 +26,7 @@ local c_id = ( local c_void = P('void') local c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + + ((P('Arena') * fill * P('*') * fill) * Cc('arena')) + C((P('const ') ^ -1) * (c_id) * (ws ^ 1) * P('*')) + (C(c_id) * (ws ^ 1)) ) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index b167767f7a..67b8f5f0f5 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -17,6 +17,7 @@ local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path _G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')() +_G.vim.inspect = loadfile(nvimdir..'/../../runtime/lua/vim/inspect.lua')() local hashy = require'generators.hashy' @@ -72,6 +73,11 @@ for i = 6, #arg do -- for specifying errors fn.parameters[#fn.parameters] = nil end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then + -- return value is allocated in an arena + fn.arena_return = true + fn.parameters[#fn.parameters] = nil + end end end input:close() @@ -210,7 +216,7 @@ for i = 1, #functions do if fn.impl_name == nil and fn.remote then local args = {} - output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') + output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Arena* arena, Error *error)') output:write('\n{') output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG') output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke ' @@ -319,6 +325,10 @@ for i = 1, #functions do output:write(call_args) end + if fn.arena_return then + output:write(', arena') + end + if fn.can_fail then -- if the function can fail, also pass a pointer to the local error object if #args > 0 then @@ -355,11 +365,12 @@ local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", vim.t return "method_handlers["..idx.."].name" end) -output:write("static const MsgpackRpcRequestHandler method_handlers[] = {\n") -for _, name in ipairs(hashorder) do +output:write("const MsgpackRpcRequestHandler method_handlers[] = {\n") +for n, name in ipairs(hashorder) do local fn = remote_fns[name] + fn.handler_id = n-1 output:write(' { .name = "'..name..'", .fn = handle_'.. (fn.impl_name or fn.name).. - ', .fast = '..tostring(fn.fast)..'},\n') + ', .fast = '..tostring(fn.fast)..', .arena_return = '..tostring(not not fn.arena_return)..'},\n') end output:write("};\n\n") output:write(hashfun) @@ -400,6 +411,8 @@ output:write([[ #include "nvim/api/private/helpers.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" + ]]) include_headers(output, headers) output:write('\n') @@ -477,6 +490,13 @@ local function process_function(fn) if fn.receives_channel_id then cparams = 'LUA_INTERNAL_CALL, ' .. cparams end + if fn.arena_return then + cparams = cparams .. '&arena, ' + write_shifted_output(output, [[ + Arena arena = ARENA_EMPTY; + ]]) + end + if fn.can_fail then cparams = cparams .. '&err' else @@ -511,15 +531,21 @@ local function process_function(fn) else return_type = fn.return_type end + local free_retval + if fn.arena_return then + free_retval = "arena_mem_free(arena_finish(&arena));" + else + free_retval = "api_free_"..return_type:lower().."(ret);" + end write_shifted_output(output, string.format([[ const %s ret = %s(%s); nlua_push_%s(lstate, ret, true); - api_free_%s(ret); + %s %s %s return 1; - ]], fn.return_type, fn.name, cparams, return_type, return_type:lower(), - free_at_exit_code, err_throw_code)) + ]], fn.return_type, fn.name, cparams, return_type, + free_retval, free_at_exit_code, err_throw_code)) else write_shifted_output(output, string.format([[ %s(%s); diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 93bbaab74c..f9e888c20d 100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -125,7 +125,7 @@ for i = 1, #events do local param = ev.parameters[j] local copy = 'copy_'..param[2] if param[1] == 'String' then - send = send..' String copy_'..param[2]..' = copy_string('..param[2]..');\n' + send = send..' String copy_'..param[2]..' = copy_string('..param[2]..', NULL);\n' argv = argv..', '..copy..'.data, INT2PTR('..copy..'.size)' recv = (recv..' String '..param[2].. ' = (String){.data = argv['..argc..'],'.. @@ -134,7 +134,7 @@ for i = 1, #events do recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' argc = argc+2 elseif param[1] == 'Array' then - send = send..' Array '..copy..' = copy_array('..param[2]..');\n' + send = send..' Array '..copy..' = copy_array('..param[2]..', NULL);\n' argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' recv = (recv..' Array '..param[2].. ' = (Array){.items = argv['..argc..'],'.. @@ -144,7 +144,7 @@ for i = 1, #events do argc = argc+2 elseif param[1] == 'Object' then send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' - send = send..' *'..copy..' = copy_object('..param[2]..');\n' + send = send..' *'..copy..' = copy_object('..param[2]..', NULL);\n' argv = argv..', '..copy recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' recv_argv = recv_argv..', '..param[2] diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index c72249161b..8e6d1f2634 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -28,13 +28,20 @@ local hashy = require'generators.hashy' local hashpipe = io.open(funcsfname, 'wb') local funcs = require('eval').funcs +for _, func in pairs(funcs) do + if func.float_func then + func.func = "float_op_wrapper" + func.data = "{ .float_func = &"..func.float_func.." }" + end +end + local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', - data='&handle_'..fun.name, + data='{ .api_handler = &method_handlers['..fun.handler_id..'] }' } end end @@ -60,12 +67,12 @@ for _, name in ipairs(neworder) do end local base = def.base or "BASE_NONE" local func = def.func or ('f_' .. name) - local data = def.data or "NULL" + local data = def.data or "{ .nullptr = NULL }" local fast = def.fast and 'true' or 'false' - hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, (FunPtr)%s },\n') + hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, %s },\n') :format(name, args[1], args[2], base, fast, func, data)) end -hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, NULL },\n') +hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .nullptr = NULL } },\n') hashpipe:write("};\n\n") hashpipe:write(hashfun) hashpipe:close() 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..0a9457ef35 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,8 +15,12 @@ #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_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" @@ -40,7 +44,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" @@ -78,11 +81,9 @@ static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; static int typeahead_char = 0; // typeahead char that's not flushed -/* - * when block_redo is TRUE redo buffer will not be changed - * used by edit() to repeat insertions and 'V' command for redoing - */ -static int block_redo = FALSE; +// when block_redo is true redo buffer will not be changed +// used by edit() to repeat insertions and 'V' command for redoing +static int block_redo = false; static int KeyNoremap = 0; // remapping flags @@ -159,7 +160,7 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) p2 = p; for (const buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) { - for (const char_u *str = bp->b_str; *str;) { + for (const char_u *str = (char_u *)bp->b_str; *str;) { *p2++ = *str++; } } @@ -176,7 +177,7 @@ char_u *get_recorded(void) char_u *p; size_t len; - p = get_buffcont(&recordbuff, TRUE); + p = get_buffcont(&recordbuff, true); free_buff(&recordbuff); /* @@ -204,7 +205,7 @@ char_u *get_recorded(void) /// K_SPECIAL in the returned string is escaped. char_u *get_inserted(void) { - return get_buffcont(&redobuff, FALSE); + return get_buffcont(&redobuff, false); } /// Add string after the current block of the given buffer @@ -316,7 +317,7 @@ static void add_char_buff(buffheader_T *buf, int c) /// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 /// if that one is empty. -/// If advance == TRUE go to the next char. +/// If advance == true go to the next char. /// No translation is done K_SPECIAL is escaped. static int read_readbuffers(int advance) { @@ -338,7 +339,7 @@ static int read_readbuf(buffheader_T *buf, int advance) } buffblock_T *const curr = buf->bh_first.b_next; - c = curr->b_str[buf->bh_index]; + c = (char_u)curr->b_str[buf->bh_index]; if (advance) { if (curr->b_str[++buf->bh_index] == NUL) { @@ -365,19 +366,15 @@ static void start_stuff(void) } } -/* - * Return TRUE if the stuff buffer is empty. - */ +/// Return true if the stuff buffer is empty. int stuff_empty(void) FUNC_ATTR_PURE { return (readbuf1.bh_first.b_next == NULL && readbuf2.bh_first.b_next == NULL); } -/* - * Return TRUE if readbuf1 is empty. There may still be redo characters in - * redbuf2. - */ +/// Return true if readbuf1 is empty. There may still be redo characters in +/// redbuf2. int readbuf1_empty(void) FUNC_ATTR_PURE { @@ -626,6 +623,34 @@ void stuffnumReadbuff(long n) add_num_buff(&readbuf1, n); } +/// Stuff a string into the typeahead buffer, such that edit() will insert it +/// literally ("literally" true) or interpret is as typed characters. +void stuffescaped(const char *arg, bool literally) +{ + while (*arg != NUL) { + // Stuff a sequence of normal ASCII characters, that's fast. Also + // stuff K_SPECIAL to get the effect of a special key when "literally" + // is true. + const char *const start = arg; + while ((*arg >= ' ' && *arg < DEL) || ((uint8_t)(*arg) == K_SPECIAL + && !literally)) { + arg++; + } + if (arg > start) { + stuffReadbuffLen(start, (arg - start)); + } + + // stuff a single special character + if (*arg != NUL) { + const int c = mb_cptr2char_adv((const char_u **)&arg); + if (literally && ((c < ' ' && c != TAB) || c == DEL)) { + stuffcharReadbuff(Ctrl_V); + } + stuffcharReadbuff(c); + } + } +} + /// Read a character from the redo buffer. Translates K_SPECIAL and /// multibyte characters. /// The redo buffer is left as it is. @@ -646,7 +671,7 @@ static int read_redo(bool init, bool old_redo) if (bp == NULL) { return FAIL; } - p = bp->b_str; + p = (char_u *)bp->b_str; return OK; } if ((c = *p) == NUL) { @@ -667,7 +692,7 @@ static int read_redo(bool init, bool old_redo) } if (*++p == NUL && bp->b_next != NULL) { bp = bp->b_next; - p = bp->b_str; + p = (char_u *)bp->b_str; } buf[i] = (char_u)c; if (i == n - 1) { // last byte of a character @@ -790,7 +815,7 @@ int start_redo_ins(void) void stop_redo_ins(void) { - block_redo = FALSE; + block_redo = false; } /* @@ -929,9 +954,9 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) } else { nrm = noremap; } - for (i = 0; i < addlen; ++i) { + for (i = 0; i < addlen; i++) { typebuf.tb_noremap[typebuf.tb_off + i + offset] = - (char_u)((--nrm >= 0) ? val : RM_YES); + (uint8_t)((--nrm >= 0) ? val : RM_YES); } // tb_maplen and tb_silent only remember the length of mapped and/or @@ -967,7 +992,7 @@ int ins_char_typebuf(int c, int modifiers) return (int)len; } -/// Return TRUE if the typeahead buffer was changed (while waiting for a +/// Return true if the typeahead buffer was changed (while waiting for a /// character to arrive). Happens when a message was received from a client or /// from feedkeys(). /// But check in a more generic way to avoid trouble: When "typebuf.tb_buf" @@ -983,10 +1008,8 @@ bool typebuf_changed(int tb_change_cnt) || typebuf_was_filled); } -/* - * Return TRUE if there are no characters in the typeahead buffer that have - * not been typed (result from a mapping or come from ":normal"). - */ +/// Return true if there are no characters in the typeahead buffer that have +/// not been typed (result from a mapping or come from ":normal"). int typebuf_typed(void) FUNC_ATTR_PURE { @@ -1257,7 +1280,7 @@ void restore_typeahead(tasave_T *tp) /// Open a new script file for the ":source!" command. /// /// @param directly when true execute directly -void openscript(char_u *name, bool directly) +void openscript(char *name, bool directly) { if (curscript + 1 == NSCRIPT) { emsg(_(e_nesting)); @@ -1336,7 +1359,7 @@ static void closescript(void) file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) { - --curscript; + curscript--; } } @@ -1350,9 +1373,7 @@ void close_all_scripts(void) #endif -/* - * Return TRUE when reading keys from a script file. - */ +/// Return true when reading keys from a script file. int using_script(void) FUNC_ATTR_PURE { @@ -1683,7 +1704,7 @@ int vpeekc_any(void) /* * Call vpeekc() without causing anything to be mapped. - * Return TRUE if a character is available, FALSE otherwise. + * Return true if a character is available, false otherwise. */ int char_avail(void) { @@ -1695,6 +1716,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(UPD_NOT_VALID); + } + + 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, EvalFuncData fptr) +{ + getchar_common(argvars, rettv); +} + +/// "getcharstr()" function +void f_getcharstr(typval_T *argvars, typval_T *rettv, EvalFuncData 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, EvalFuncData fptr) +{ + rettv->vval.v_number = mod_mask; +} + typedef enum { map_result_fail, // failed, break loop map_result_get, // get a character from typeahead @@ -1735,7 +1899,7 @@ static bool at_ins_compl_key(void) c = p[3] & 0x1f; } return (ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c)) - || ((compl_cont_status & CONT_LOCAL) && (c == Ctrl_N || c == Ctrl_P)); + || (compl_status_local() && (c == Ctrl_N || c == Ctrl_P)); } /// Check if typebuf.tb_buf[] contains a modifier plus key that can be changed @@ -1901,12 +2065,11 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // - Partly match: mlen == typebuf.tb_len keylen = mp->m_keylen; if (mlen == keylen || (mlen == typebuf.tb_len && typebuf.tb_len < keylen)) { - char_u *s; int n; // If only script-local mappings are allowed, check if the // mapping starts with K_SNR. - s = typebuf.tb_noremap + typebuf.tb_off; + uint8_t *s = typebuf.tb_noremap + typebuf.tb_off; if (*s == RM_SCRIPT && (mp->m_keys[0] != K_SPECIAL || mp->m_keys[1] != KS_EXTRA @@ -1957,7 +2120,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // Check for match with 'pastetoggle' if (*p_pt != NUL && mp == NULL && (State & (MODE_INSERT | MODE_NORMAL))) { - bool match = typebuf_match_len(p_pt, &mlen); + bool match = typebuf_match_len((char_u *)p_pt, &mlen); if (match) { // write chars to script file(s) if (mlen > typebuf.tb_maplen) { @@ -1966,7 +2129,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) } del_typebuf(mlen, 0); // remove the chars - set_option_value("paste", !p_paste, NULL, 0); + set_option_value_give_err("paste", !p_paste, NULL, 0); if (!(State & MODE_INSERT)) { msg_col = 0; msg_row = Rows - 1; @@ -2102,7 +2265,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) save_m_keys = vim_strsave(mp->m_keys); if (save_m_luaref == LUA_NOREF) { - save_m_str = vim_strsave(mp->m_str); + save_m_str = vim_strsave((char_u *)mp->m_str); } map_str = eval_map_expr(mp, NUL); @@ -2134,7 +2297,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) vgetc_busy = save_vgetc_busy; may_garbage_collect = save_may_garbage_collect; } else { - map_str = mp->m_str; + map_str = (char_u *)mp->m_str; } // Insert the 'to' part in the typebuf.tb_buf. @@ -2218,8 +2381,8 @@ void check_end_reg_executing(bool advance) /// /// if "advance" is true (vgetc()): /// Really get the character. -/// KeyTyped is set to TRUE in the case the user typed the key. -/// KeyStuffed is TRUE if the character comes from the stuff buffer. +/// KeyTyped is set to true in the case the user typed the key. +/// KeyStuffed is true if the character comes from the stuff buffer. /// if "advance" is false (vpeekc()): /// Just look whether there is a character available. /// Return NUL if not. @@ -2251,10 +2414,11 @@ static int vgetorpeek(bool advance) return NUL; } - ++vgetc_busy; + vgetc_busy++; if (advance) { - KeyStuffed = FALSE; + KeyStuffed = false; + typebuf_was_empty = false; } init_typebuf(); @@ -2305,7 +2469,7 @@ static int vgetorpeek(bool advance) // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); - // If inchar() returns TRUE (script file was active) or we + // If inchar() returns true (script file was active) or we // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. // As a result typing CTRL-C in insert mode will @@ -2354,7 +2518,7 @@ static int vgetorpeek(bool advance) // write char to script file(s) gotchars(typebuf.tb_buf + typebuf.tb_off, 1); } - KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; + KeyNoremap = (unsigned char)typebuf.tb_noremap[typebuf.tb_off]; del_typebuf(1, 0); } break; // got character, break the for loop @@ -2383,7 +2547,7 @@ static int vgetorpeek(bool advance) && (State & MODE_INSERT) && (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout)) && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { - colnr_T col = 0, vcol; + colnr_T col = 0; char_u *ptr; if (mode_displayed) { @@ -2401,22 +2565,27 @@ static int vgetorpeek(bool advance) // We are expecting to truncate the trailing // white-space, so find the last non-white // character -- webb - col = vcol = curwin->w_wcol = 0; + curwin->w_wcol = 0; ptr = get_cursor_line_ptr(); - while (col < curwin->w_cursor.col) { - if (!ascii_iswhite(ptr[col])) { - curwin->w_wcol = vcol; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, + curwin->w_cursor.lnum, 0, ptr, ptr); + while ((char_u *)cts.cts_ptr < ptr + curwin->w_cursor.col) { + if (!ascii_iswhite(*cts.cts_ptr)) { + curwin->w_wcol = cts.cts_vcol; } - vcol += lbr_chartabsize(ptr, ptr + col, vcol); - col += utfc_ptr2len((char *)ptr + col); + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr += utfc_ptr2len(cts.cts_ptr); } + clear_chartabsize_arg(&cts); + curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); col = 0; // no correction needed } else { - --curwin->w_wcol; + curwin->w_wcol--; col = curwin->w_cursor.col - 1; } } else if (curwin->w_p_wrap && curwin->w_wrow) { @@ -2480,6 +2649,11 @@ static int vgetorpeek(bool advance) } tc = c; + // set a flag to indicate this wasn't a normal char + if (advance) { + typebuf_was_empty = true; + } + // return 0 in normal_check() if (pending_exmode_active) { exmode_active = true; @@ -2638,7 +2812,7 @@ static int vgetorpeek(bool advance) gotchars(nop_buf, 3); } - --vgetc_busy; + vgetc_busy--; return c; } @@ -2714,7 +2888,7 @@ int inchar(char_u *buf, int maxlen, long wait_time) if (read_size <= 0) { // Did not get a character from script. // If we got an interrupt, skip all previously typed characters and - // return TRUE if quit reading script file. + // return true if quit reading script file. // Stop reading typeahead when a single CTRL-C was read, // fill_input_buf() returns this when not able to read from stdin. // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] @@ -2777,7 +2951,7 @@ int fix_input_buffer(char_u *buf, int len) // Two characters are special: NUL and K_SPECIAL. // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER // Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER - for (i = len; --i >= 0; ++p) { + for (i = len; --i >= 0; p++) { if (p[0] == NUL || (p[0] == K_SPECIAL && (i < 2 || p[1] != KS_EXTRA))) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 317423ffa0..060db3b3b1 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 @@ -159,31 +159,10 @@ EXTERN colnr_T dollar_vcol INIT(= -1); // Variables for Insert mode completion. -// Length in bytes of the text being completed (this is deleted to be replaced -// by the match.) -EXTERN int compl_length INIT(= 0); - -// Set when doing something for completion that may call edit() recursively, -// which is not allowed. Also used to disable folding during completion -EXTERN bool compl_busy INIT(= false); - -// List of flags for method of completion. -EXTERN int compl_cont_status INIT(= 0); -#define CONT_ADDING 1 // "normal" or "adding" expansion -#define CONT_INTRPT (2 + 4) // a ^X interrupted the current expansion - // it's set only iff N_ADDS is set -#define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current -#define CONT_S_IPOS 8 // next ^X<> will set initial_pos? - // if so, word-wise-expansion will set SOL -#define CONT_SOL 16 // pattern includes start of line, just for - // word-wise expansion, not set for ^X^L -#define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local - // expansion, (eg use complete=.) - -EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode -EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode -EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode -EXTERN hlf_T edit_submode_highl; // highl. method for extra info +EXTERN char *edit_submode INIT(= NULL); // msg for CTRL-X submode +EXTERN char *edit_submode_pre INIT(= NULL); // prepended to edit_submode +EXTERN char *edit_submode_extra INIT(= NULL); // appended to edit_submode +EXTERN hlf_T edit_submode_highl; // highl. method for extra info // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left @@ -198,7 +177,7 @@ EXTERN bool msg_scrolled_ign INIT(= false); // is reset before the screen is redrawn, so we need to keep track of this. EXTERN bool msg_did_scroll INIT(= false); -EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw +EXTERN char *keep_msg INIT(= NULL); // msg to be shown after redraw EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw EXTERN int msg_scroll INIT(= false); // msg_start() will scroll @@ -248,9 +227,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 @@ -265,9 +241,13 @@ EXTERN int do_profiling INIT(= PROF_NONE); ///< PROF_ values /// Exception currently being thrown. Used to pass an exception to a different /// cstack. Also used for discarding an exception before it is caught or made -/// pending. +/// pending. Only valid when did_throw is true. EXTERN except_T *current_exception; +/// An exception is being thrown. Reset when the exception is caught or as +/// long as it is pending in a finally clause. +EXTERN bool did_throw INIT(= false); + /// Set when a throw that cannot be handled in do_cmdline() must be propagated /// to the cstack of the previously called do_cmdline(). EXTERN bool need_rethrow INIT(= false); @@ -296,7 +276,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 +328,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; @@ -583,6 +563,8 @@ EXTERN bool can_si INIT(= false); // one indent will be removed. EXTERN bool can_si_back INIT(= false); +EXTERN int old_indent INIT(= 0); ///< for ^^D command in insert mode + // w_cursor before formatting text. EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 }); @@ -689,7 +671,7 @@ EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog. EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. -EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names +EXTERN char NameBuff[MAXPATHL]; ///< Buffer for expanding file names EXTERN char msg_buf[MSG_BUF_LEN]; ///< Small buffer for messages EXTERN char os_buf[ ///< Buffer for the os/ layer #if MAXPATHL > IOSIZE @@ -708,6 +690,10 @@ EXTERN int recoverymode INIT(= false); // Set to true for "-r" option // typeahead buffer EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 }); +/// Flag used to indicate that vgetorpeek() returned a char like Esc when the +/// :normal argument was exhausted. +EXTERN bool typebuf_was_empty INIT(= false); + EXTERN int ex_normal_busy INIT(= 0); // recursiveness of ex_normal() EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal() EXTERN int ignore_script INIT(= false); // ignore script input @@ -749,9 +735,9 @@ EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon // including the terminating NUL EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n"); -EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) -EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." -EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline +EXTERN char *last_cmdline INIT(= NULL); // last command line (for ":) +EXTERN char *repeat_cmdline INIT(= NULL); // command line for "." +EXTERN char *new_last_cmdline INIT(= NULL); // new value for last_cmdline EXTERN char *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char *autocmd_match INIT(= NULL); // name for <amatch> on cmdline @@ -775,7 +761,7 @@ EXTERN int keep_help_flag INIT(= false); // doing :ta from help file // When a string option is NULL (which only happens in out-of-memory // situations), it is set to empty_option, to avoid having to check for NULL // everywhere. -EXTERN char_u *empty_option INIT(= (char_u *)""); +EXTERN char *empty_option INIT(= ""); EXTERN bool redir_off INIT(= false); // no redirection for a moment EXTERN FILE *redir_fd INIT(= NULL); // message redirection file @@ -815,7 +801,6 @@ EXTERN char *last_chdir_reason INIT(= NULL); EXTERN bool km_stopsel INIT(= false); EXTERN bool km_startsel INIT(= false); -EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0 EXTERN int cmdwin_result INIT(= 0); ///< result of cmdline window or 0 EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level @@ -939,7 +924,7 @@ EXTERN char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s")); EXTERN char e_positive[] INIT(= N_("E487: Argument must be positive")); EXTERN char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory")); -EXTERN char e_quickfix[] INIT(= N_("E42: No Errors")); +EXTERN char e_no_errors[] INIT(= N_("E42: No Errors")); EXTERN char e_loclist[] INIT(= N_("E776: No location list")); EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string")); EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); @@ -1020,6 +1005,8 @@ EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too lon EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range")); +EXTERN char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name")); + EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); EXTERN char e_undobang_cannot_redo_or_move_branch[] diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 72e85c425d..329adfda12 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" @@ -116,7 +125,7 @@ void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) char buf[MB_MAXBYTES + 1]; buf[utf_char2bytes(c, buf)] = NUL; - grid_puts(grid, (char_u *)buf, row, col, attr); + grid_puts(grid, buf, row, col, attr); } /// get a single character directly from grid.chars into "bytes[]". @@ -139,7 +148,7 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, int *attrp /// attributes 'attr', and update chars[] and attrs[]. /// Note: only outputs within one row, message is truncated at grid boundary! /// Note: if grid, row and/or col is invalid, nothing is done. -void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) +void grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr) { grid_puts_len(grid, text, -1, row, col, attr); } @@ -178,10 +187,10 @@ void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. -void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col, int attr) +void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, int attr) { size_t off; - char_u *ptr = text; + char *ptr = text; int len = textlen; int c; size_t max_off; @@ -228,13 +237,13 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col while (col < grid->cols && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) { - c = *ptr; + c = (unsigned char)(*ptr); // check if this is the first byte of a multibyte mbyte_blen = len > 0 - ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr)) - : utfc_ptr2len((char *)ptr); + ? utfc_ptr2len_len((char_u *)ptr, (int)((text + len) - ptr)) + : utfc_ptr2len(ptr); u8c = len >= 0 - ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)) + ? utfc_ptr2char_len((char_u *)ptr, u8cc, (int)((text + len) - ptr)) : utfc_ptr2char(ptr, u8cc); mbyte_cells = utf_char2cells(u8c); if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { @@ -245,7 +254,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col nc1 = NUL; } else { nc = len >= 0 - ? utfc_ptr2char_len(ptr + mbyte_blen, pcc, (int)((text + len) - ptr - mbyte_blen)) + ? utfc_ptr2char_len((char_u *)ptr + mbyte_blen, pcc, + (int)((text + len) - ptr - mbyte_blen)) : utfc_ptr2char(ptr + mbyte_blen, pcc); nc1 = pcc[0]; } @@ -311,7 +321,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col ptr += mbyte_blen; if (clear_next_cell) { // This only happens at the end, display one space next. - ptr = (char_u *)" "; + ptr = " "; len = -1; } } @@ -384,11 +394,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); + grid_puts_len(grid, " ", 1, row, start_col - 1, 0); } if (end_col < grid->cols && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); + grid_puts_len(grid, " ", 1, row, end_col, 0); } // if grid was resized (in ext_multigrid mode), the UI has no redraw updates @@ -466,9 +476,9 @@ static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_ /// "endcol" gives the columns where valid characters are. /// "clear_width" is the width of the window. It's > 0 if the rest of the line /// needs to be cleared, negative otherwise. -/// "rlflag" is TRUE in a rightleft window: -/// When TRUE and "clear_width" > 0, clear columns 0 to "endcol" -/// When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" +/// "rlflag" is true in a rightleft window: +/// When true and "clear_width" > 0, clear columns 0 to "endcol" +/// When false and "clear_width" > 0, clear columns "endcol" to "clear_width" /// If "wrap" is true, then hint to the UI that "row" contains a line /// which has wrapped into the next row. void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width, @@ -780,3 +790,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..602fa8b71d 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -10,19 +10,16 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif #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,11 +28,13 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/version.h" +#include "nvim/vim.h" /* * To implement printing on a platform, the following functions must be @@ -49,20 +48,20 @@ * * int mch_print_begin(prt_settings_T *settings) * Called to start the print job. - * Return FALSE to abort. + * Return false to abort. * * int mch_print_begin_page(char_u *msg) * Called at the start of each page. * "msg" indicates the progress of the print job, can be NULL. - * Return FALSE to abort. + * Return false to abort. * * int mch_print_end_page() * Called at the end of each page. - * Return FALSE to abort. + * Return false to abort. * * int mch_print_blank_page() * Called to generate a blank page for collated, duplex, multiple copy - * document. Return FALSE to abort. + * document. Return false to abort. * * void mch_print_end(prt_settings_T *psettings) * Called at normal end of print job. @@ -85,36 +84,33 @@ * * mch_print_start_line(int margin, int page_line) * Sets the current position at the start of line "page_line". - * If margin is TRUE start in the left margin (for header and line number). + * If margin is true start in the left margin (for header and line number). * * int mch_print_text_out(char_u *p, size_t len); * Output one character of text p[len] at the current position. - * Return TRUE if there is no room for another character in the same line. + * Return true if there is no room for another character in the same line. * * Note that the generic code has no idea of margins. The machine code should * simply make the page look smaller! The header and the line numbers are * printed in the margin. */ -static option_table_T printer_opts[OPT_PRINT_NUM_OPTIONS] - = - { - { "top", TRUE, 0, NULL, 0, FALSE }, - { "bottom", TRUE, 0, NULL, 0, FALSE }, - { "left", TRUE, 0, NULL, 0, FALSE }, - { "right", TRUE, 0, NULL, 0, FALSE }, - { "header", TRUE, 0, NULL, 0, FALSE }, - { "syntax", FALSE, 0, NULL, 0, FALSE }, - { "number", FALSE, 0, NULL, 0, FALSE }, - { "wrap", FALSE, 0, NULL, 0, FALSE }, - { "duplex", FALSE, 0, NULL, 0, FALSE }, - { "portrait", FALSE, 0, NULL, 0, FALSE }, - { "paper", FALSE, 0, NULL, 0, FALSE }, - { "collate", FALSE, 0, NULL, 0, FALSE }, - { "jobsplit", FALSE, 0, NULL, 0, FALSE }, - { "formfeed", FALSE, 0, NULL, 0, FALSE }, - } -; +static option_table_T printer_opts[OPT_PRINT_NUM_OPTIONS] = { + { "top", true, 0, NULL, 0, false }, + { "bottom", true, 0, NULL, 0, false }, + { "left", true, 0, NULL, 0, false }, + { "right", true, 0, NULL, 0, false }, + { "header", true, 0, NULL, 0, false }, + { "syntax", false, 0, NULL, 0, false }, + { "number", false, 0, NULL, 0, false }, + { "wrap", false, 0, NULL, 0, false }, + { "duplex", false, 0, NULL, 0, false }, + { "portrait", false, 0, NULL, 0, false }, + { "paper", false, 0, NULL, 0, false }, + { "collate", false, 0, NULL, 0, false }, + { "jobsplit", false, 0, NULL, 0, false }, + { "formfeed", false, 0, NULL, 0, false }, +}; static const uint32_t cterm_color_8[8] = { 0x000000, 0xff0000, 0x00ff00, 0xffff00, @@ -150,12 +146,12 @@ static int page_count; static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] = { - { "c", FALSE, 0, NULL, 0, FALSE }, - { "a", FALSE, 0, NULL, 0, FALSE }, - { "r", FALSE, 0, NULL, 0, FALSE }, - { "b", FALSE, 0, NULL, 0, FALSE }, - { "i", FALSE, 0, NULL, 0, FALSE }, - { "o", FALSE, 0, NULL, 0, FALSE }, + { "c", false, 0, NULL, 0, false }, + { "a", false, 0, NULL, 0, false }, + { "r", false, 0, NULL, 0, false }, + { "b", false, 0, NULL, 0, false }, + { "i", false, 0, NULL, 0, false }, + { "o", false, 0, NULL, 0, false }, }; /* @@ -265,7 +261,7 @@ struct prt_resfile_buffer_S { */ char *parse_printoptions(void) { - return parse_list_options(p_popt, printer_opts, OPT_PRINT_NUM_OPTIONS); + return parse_list_options((char_u *)p_popt, printer_opts, OPT_PRINT_NUM_OPTIONS); } /* @@ -274,7 +270,7 @@ char *parse_printoptions(void) */ char *parse_printmbfont(void) { - return parse_list_options(p_pmfn, mbfont_opts, OPT_MBFONT_NUM_OPTIONS); + return parse_list_options((char_u *)p_pmfn, mbfont_opts, OPT_MBFONT_NUM_OPTIONS); } /* @@ -292,8 +288,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,14 +311,14 @@ 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); - for (idx = 0; idx < table_size; ++idx) { + for (idx = 0; idx < table_size; idx++) { if (STRNICMP(stringp, table[idx].name, len) == 0) { break; } @@ -333,8 +329,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 +338,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++; } } @@ -504,9 +500,7 @@ int prt_header_height(void) return 2; } -/* - * Return TRUE if using a line number for printing. - */ +// Return true if using a line number for printing. int prt_use_number(void) { return printer_opts[OPT_PRINT_NUMBER].present @@ -524,7 +518,7 @@ int prt_get_unit(int idx) static char *(units[4]) = PRT_UNIT_NAMES; if (printer_opts[idx].present) { - for (i = 0; i < 4; ++i) { + for (i = 0; i < 4; i++) { if (STRNICMP(printer_opts[idx].string, units[i], 2) == 0) { u = i; break; @@ -550,7 +544,7 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, const if (*p_header != NUL) { linenr_T tmp_lnum, tmp_topline, tmp_botline; - int use_sandbox = FALSE; + int use_sandbox = false; /* * Need to (temporarily) set current line number and first/last line @@ -619,7 +613,7 @@ static void prt_message(char_u *s) { // TODO(bfredl): delete this grid_fill(&default_grid, Rows - 1, Rows, 0, Columns, ' ', ' ', 0); - grid_puts(&default_grid, s, Rows - 1, 0, HL_ATTR(HLF_R)); + grid_puts(&default_grid, (char *)s, Rows - 1, 0, HL_ATTR(HLF_R)); ui_flush(); } @@ -632,8 +626,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; @@ -667,7 +661,7 @@ void ex_hardcopy(exarg_T *eap) settings.modec = 'c'; if (!syntax_present(curwin)) { - settings.do_syntax = FALSE; + settings.do_syntax = false; } else if (printer_opts[OPT_PRINT_SYNTAX].present && TOLOWER_ASC(printer_opts[OPT_PRINT_SYNTAX].string[0]) != 'a') { settings.do_syntax = @@ -734,7 +728,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; @@ -749,11 +743,9 @@ void ex_hardcopy(exarg_T *eap) /* * Loop over all pages in the print job: 1 2 3 ... */ - for (page_count = 0; prtpos.file_line <= eap->line2; ++page_count) { - /* - * Loop over uncollated copies: 1 1 1, 2 2 2, 3 3 3, ... - * For duplex: 12 12 12 34 34 34, ... - */ + for (page_count = 0; prtpos.file_line <= eap->line2; page_count++) { + // Loop over uncollated copies: 1 1 1, 2 2 2, 3 3 3, ... + // For duplex: 12 12 12 34 34 34, ... for (uncollated_copies = 0; uncollated_copies < settings.n_uncollated_copies; uncollated_copies++) { @@ -763,10 +755,8 @@ void ex_hardcopy(exarg_T *eap) /* * Do front and rear side of a page. */ - for (side = 0; side <= settings.duplex; ++side) { - /* - * Print one page. - */ + for (side = 0; side <= settings.duplex; side++) { + // Print one page. // Check for interrupt character every page. os_breakcheck(); @@ -837,7 +827,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. @@ -868,7 +858,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T { colnr_T col; char_u *line; - int need_break = FALSE; + int need_break = false; int outputlen; int tab_spaces; int print_pos; @@ -881,7 +871,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T if (!ppos->ff && prt_use_number()) { prt_line_number(psettings, page_line, ppos->file_line); } - ppos->ff = FALSE; + ppos->ff = false; } else { // left over from wrap halfway through a tab print_pos = ppos->print_pos; @@ -900,7 +890,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 { @@ -944,7 +934,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T && printer_opts[OPT_PRINT_FORMFEED].present && TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0]) == 'y') { - ppos->ff = TRUE; + ppos->ff = true; need_break = 1; } else { need_break = mch_print_text_out(line + col, (size_t)outputlen); @@ -1718,7 +1708,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), @@ -1986,7 +1976,7 @@ void mch_print_cleanup(void) if (prt_do_conv) { convert_setup(&prt_conv, NULL, NULL); - prt_do_conv = FALSE; + prt_do_conv = false; } if (prt_ps_fd != NULL) { fclose(prt_ps_fd); @@ -2118,11 +2108,11 @@ static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap, for (mbenc = 0; mbenc < p_cmap->num_encodings; mbenc++) { if (STRNICMP(p_mbenc->encoding, p_encoding, enc_len) == 0) { *pp_mbenc = p_mbenc; - return TRUE; + return true; } p_mbenc++; } - return FALSE; + return false; } static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, @@ -2141,11 +2131,11 @@ static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, for (mbchar = 0; mbchar < p_cmap->num_charsets; mbchar++) { if (STRNICMP(p_mbchar->charset, p_charset, char_len) == 0) { *pp_mbchar = p_mbchar; - return TRUE; + return true; } p_mbchar++; } - return FALSE; + return false; } int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) @@ -2157,17 +2147,14 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) char_u *p; int props; int cmap = 0; - char_u *p_encoding; struct prt_ps_encoding_S *p_mbenc; struct prt_ps_encoding_S *p_mbenc_first; struct prt_ps_charset_S *p_mbchar = NULL; - /* - * Set up font and encoding. - */ - p_encoding = enc_skip(p_penc); + // Set up font and encoding. + char_u *p_encoding = (char_u *)enc_skip(p_penc); if (*p_encoding == NUL) { - p_encoding = enc_skip(p_enc); + p_encoding = (char_u *)enc_skip(p_enc); } // Look for a multi-byte font that matches the encoding and character set. @@ -2186,7 +2173,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) p_mbenc_first = p_mbenc; effective_cmap = cmap; } - if (prt_match_charset((char *)p_pmcs, &prt_ps_mbfonts[cmap], &p_mbchar)) { + if (prt_match_charset(p_pmcs, &prt_ps_mbfonts[cmap], &p_mbchar)) { break; } } @@ -2279,7 +2266,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_ps_font = &prt_ps_mb_font; } else { - prt_use_courier = FALSE; + prt_use_courier = false; prt_ps_font = &prt_ps_courier_font; } @@ -2296,7 +2283,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) paper_name = "A4"; paper_strlen = 2; } - for (i = 0; i < (int)PRT_MEDIASIZE_LEN; ++i) { + for (i = 0; i < (int)PRT_MEDIASIZE_LEN; i++) { if (STRLEN(prt_mediasize[i].name) == (unsigned)paper_strlen && STRNICMP(prt_mediasize[i].name, paper_name, paper_strlen) == 0) { @@ -2337,7 +2324,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) * Set up the font size. */ fontsize = PRT_PS_DEFAULT_FONTSIZE; - for (p = p_pfn; (p = (char_u *)vim_strchr((char *)p, ':')) != NULL; p++) { + for (p = (char_u *)p_pfn; (p = (char_u *)vim_strchr((char *)p, ':')) != NULL; p++) { if (p[1] == 'h' && ascii_isdigit(p[2])) { fontsize = atoi((char *)p + 2); } @@ -2381,16 +2368,16 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) * Set up printer duplex and tumble based on Duplex option setting - default * is long sided duplex printing (i.e. no tumble). */ - prt_duplex = TRUE; - prt_tumble = FALSE; + prt_duplex = true; + prt_tumble = false; psettings->duplex = 1; if (printer_opts[OPT_PRINT_DUPLEX].present) { if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "off", 3) == 0) { - prt_duplex = FALSE; + prt_duplex = false; psettings->duplex = 0; } else if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "short", 5) == 0) { - prt_tumble = TRUE; + prt_tumble = true; } } @@ -2601,13 +2588,13 @@ bool mch_print_begin(prt_settings_T *psettings) // that cannot be found then default to "latin1". // Note: VIM specific encoding header is always skipped. if (!prt_out_mbyte) { - p_encoding = (char *)enc_skip(p_penc); + p_encoding = enc_skip(p_penc); if (*p_encoding == NUL || !prt_find_resource(p_encoding, &res_encoding)) { // 'printencoding' not set or not supported - find alternate int props; - p_encoding = (char *)enc_skip(p_enc); + p_encoding = enc_skip(p_enc); props = enc_canon_props((char_u *)p_encoding); if (!(props & ENC_8BIT) || !prt_find_resource(p_encoding, &res_encoding)) { @@ -2627,9 +2614,9 @@ bool mch_print_begin(prt_settings_T *psettings) // For the moment there are no checks on encoding resource files to // perform } else { - p_encoding = (char *)enc_skip(p_penc); + p_encoding = enc_skip(p_penc); if (*p_encoding == NUL) { - p_encoding = (char *)enc_skip(p_enc); + p_encoding = enc_skip(p_enc); } if (prt_use_courier) { // Include ASCII range encoding vector @@ -2647,9 +2634,9 @@ bool mch_print_begin(prt_settings_T *psettings) } prt_conv.vc_type = CONV_NONE; - if (!(enc_canon_props(p_enc) & enc_canon_props((char_u *)p_encoding) & ENC_8BIT)) { + if (!(enc_canon_props((char_u *)p_enc) & enc_canon_props((char_u *)p_encoding) & ENC_8BIT)) { // Set up encoding conversion if required - if (convert_setup(&prt_conv, p_enc, (char_u *)p_encoding) == FAIL) { + if (convert_setup(&prt_conv, p_enc, p_encoding) == FAIL) { semsg(_("E620: Unable to convert to print encoding \"%s\""), p_encoding); return false; @@ -2943,7 +2930,7 @@ int mch_print_begin_page(char_u *str) int mch_print_blank_page(void) { - return mch_print_begin_page(NULL) ? (mch_print_end_page()) : FALSE; + return mch_print_begin_page(NULL) ? (mch_print_end_page()) : false; } static double prt_pos_x = 0; @@ -3075,7 +3062,8 @@ int mch_print_text_out(char_u *const textp, size_t len) if (prt_do_conv) { // Convert from multi-byte to 8-bit encoding - tofree = p = string_convert(&prt_conv, p, &len); + p = (char_u *)string_convert(&prt_conv, (char *)p, &len); + tofree = p; if (p == NULL) { p = (char_u *)""; len = 0; diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 95ae7a152c..308f64f011 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; } @@ -67,7 +67,7 @@ void hash_clear(hashtab_T *ht) void hash_clear_all(hashtab_T *ht, unsigned int off) { size_t todo = ht->ht_used; - for (hashitem_T *hi = ht->ht_array; todo > 0; ++hi) { + for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { xfree(hi->hi_key - off); todo--; @@ -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 @@ -354,7 +356,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) hash_T newmask = newsize - 1; size_t todo = ht->ht_used; - for (hashitem_T *olditem = oldarray; todo > 0; ++olditem) { + for (hashitem_T *olditem = oldarray; todo > 0; olditem++) { if (HASHITEM_EMPTY(olditem)) { continue; } diff --git a/src/nvim/help.c b/src/nvim/help.c new file mode 100644 index 0000000000..245d80489f --- /dev/null +++ b/src/nvim/help.c @@ -0,0 +1,1179 @@ +// 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/cmdexpand.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_docmd.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/optionstr.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(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((char *)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_give_err("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 = 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 = strrchr(t1, '.'); + const char *const e2 = 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, + (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 = string_convert(&vc, (char *)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(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..a78b933108 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,51 @@ 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) { + // ns=0 (the default namespace) does not have a provider so stop here + return -1; + } + + 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)))); @@ -212,47 +223,79 @@ 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.attr_id = fallback ? -1 : hl_get_syn_attr(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 +308,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 +319,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 +341,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 +361,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 +379,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 < 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 +486,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 +520,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, @@ -646,7 +729,7 @@ static int hl_cterm2rgb_color(int nr) 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE }; - static char_u ansi_table[16][4] = { + static uint8_t ansi_table[16][4] = { // R G B idx { 0, 0, 0, 1 }, // black { 224, 0, 0, 2 }, // dark red @@ -820,7 +903,7 @@ Dictionary hlattrs2dict(Dictionary *hl_alloc, HlAttrs ae, bool use_rgb) *hl_alloc = hl; return hl; } else { - Dictionary allocated = copy_dictionary(hl); + Dictionary allocated = copy_dictionary(hl, NULL); kv_destroy(hl); return allocated; } @@ -852,7 +935,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 +977,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..ed1f0185b7 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 /// @{ @@ -77,6 +79,8 @@ typedef struct { int sg_rgb_sp_idx; ///< RGB special color index int sg_blend; ///< blend level (0-100 inclusive), -1 if unset + + int sg_parent; ///< parent of @nested.group } HlGroup; enum { @@ -181,6 +185,54 @@ static const char *highlight_init_both[] = { "default link DiagnosticSignWarn DiagnosticWarn", "default link DiagnosticSignInfo DiagnosticInfo", "default link DiagnosticSignHint DiagnosticHint", + + "default link @error Error", + "default link @text.underline Underlined", + "default link @todo Todo", + "default link @debug Debug", + + // Miscs + "default link @comment Comment", + "default link @punctuation Delimiter", + + // Constants + "default link @constant Constant", + "default link @constant.builtin Special", + "default link @constant.macro Define", + "default link @define Define", + "default link @macro Macro", + "default link @string String", + "default link @string.escape SpecialChar", + "default link @character Character", + "default link @character.special SpecialChar", + "default link @number Number", + "default link @boolean Boolean", + "default link @float Float", + + // Functions + "default link @function Function", + "default link @function.builtin Special", + "default link @function.macro Macro", + "default link @parameter Identifier", + "default link @method Function", + "default link @field Identifier", + "default link @property Identifier", + "default link @constructor Special", + + // Keywords + "default link @conditional Conditional", + "default link @repeat Repeat", + "default link @label Label", + "default link @operator Operator", + "default link @keyword Keyword", + "default link @exception Exception", + + "default link @type Type", + "default link @type.definition Typedef", + "default link @storageclass StorageClass", + "default link @structure Structure", + "default link @include Include", + "default link @preproc PreProc", NULL }; @@ -638,7 +690,7 @@ static int color_numbers_8[28] = { 0, 4, 2, 6, // Lookup the "cterm" value to be used for color with index "idx" in // color_names[]. -// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 +// "boldp" will be set to kTrue or kFalse for a foreground color when using 8 // colors, otherwise it will be unchanged. int lookup_color(const int idx, const bool foreground, TriState *const boldp) { @@ -689,14 +741,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 +785,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 +803,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(UPD_NOT_VALID); + } + need_highlight_changed = true; } /// Handle ":highlight" command @@ -794,14 +852,14 @@ void do_highlight(const char *line, const bool forceit, const bool init) } // Isolate the name. - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); // Check for "default" argument. if (strncmp(line, "default", (size_t)(name_end - line)) == 0) { dodefault = true; line = linep; - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); } @@ -833,9 +891,9 @@ void do_highlight(const char *line, const bool forceit, const bool init) int to_id; HlGroup *hlgroup = NULL; - from_end = (const char *)skiptowhite((const char_u *)from_start); + from_end = (const char *)skiptowhite(from_start); to_start = (const char *)skipwhite(from_end); - to_end = (const char *)skiptowhite((const char_u *)to_start); + to_end = (const char *)skiptowhite(to_start); if (ends_excmd((uint8_t)(*from_start)) || ends_excmd((uint8_t)(*to_start))) { @@ -861,7 +919,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 +929,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,10 +940,10 @@ 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); + redraw_all_later(UPD_SOME_VALID); // Only call highlight changed() once after multiple changes need_highlight_changed = true; @@ -908,10 +966,10 @@ void do_highlight(const char *line, const bool forceit, const bool init) } init_highlight(true, true); highlight_changed(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); return; } - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); } @@ -996,7 +1054,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } else { arg_start = linep; - linep = (const char *)skiptowhite((const char_u *)linep); + linep = (const char *)skiptowhite(linep); } if (linep == arg_start) { semsg(_("E417: missing argument: %s"), key_start); @@ -1148,7 +1206,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (dark != -1 && dark != (*p_bg == 'd') && !option_was_set("bg")) { - set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); + set_option_value_give_err("bg", 0L, (dark ? "dark" : "light"), 0); reset_option_was_set("bg"); } } @@ -1262,17 +1320,17 @@ void do_highlight(const char *line, const bool forceit, const bool init) // changed ui_refresh(); } else { - // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // TUI and newer UIs will repaint the screen themselves. UPD_NOT_VALID // redraw below will still handle usages of guibg=fg etc. ui_default_colors_set(); } did_highlight_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } else { 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 @@ -1284,7 +1342,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // redrawing. This may happen when evaluating 'statusline' changes the // StatusLine group. if (!updating_screen) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } need_highlight_changed = true; } @@ -1295,7 +1353,7 @@ void free_highlight(void) { ga_clear(&highlight_ga); map_destroy(cstr_t, int)(&highlight_unames); - arena_mem_free(arena_finish(&highlight_arena), NULL); + arena_mem_free(arena_finish(&highlight_arena)); } #endif @@ -1313,7 +1371,7 @@ void restore_cterm_colors(void) /// @param check_link if true also check for an existing link. /// -/// @return TRUE if highlight group "idx" has any settings. +/// @return true if highlight group "idx" has any settings. static int hl_has_settings(int idx, bool check_link) { return hl_table[idx].sg_cleared == 0 @@ -1363,7 +1421,12 @@ static void highlight_list_one(const int id) const HlGroup *sgp = &hl_table[id - 1]; // index is ID minus one bool didh = false; - if (message_filtered(sgp->sg_name)) { + if (message_filtered((char *)sgp->sg_name)) { + return; + } + + // don't list specialized groups if a parent is used instead + if (sgp->sg_parent && sgp->sg_cleared) { return; } @@ -1653,7 +1716,12 @@ static void set_hl_attr(int idx) int syn_name2id(const char *name) FUNC_ATTR_NONNULL_ALL { - return syn_name2id_len(name, STRLEN(name)); + if (name[0] == '@') { + // if we look up @aaa.bbb, we have to consider @aaa as well + return syn_check_group(name, strlen(name)); + } else { + return syn_name2id_len(name, STRLEN(name)); + } } /// Lookup a highlight group name and return its ID. @@ -1693,7 +1761,7 @@ int syn_name2attr(const char_u *name) return 0; } -/// Return TRUE if highlight group "name" exists. +/// Return true if highlight group "name" exists. int highlight_exists(const char *name) { return syn_name2id(name) > 0; @@ -1742,11 +1810,19 @@ static int syn_add_group(const char *name, size_t len) if (!vim_isprintc(c)) { emsg(_("E669: Unprintable character in group name")); return 0; - } else if (!ASCII_ISALNUM(c) && c != '_') { - // This is an error, but since there previously was no check only give a warning. + } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@') { + // '.' and '@' are allowed characters for use with treesitter capture names. msg_source(HL_ATTR(HLF_W)); - msg(_("W18: Invalid character in group name")); - break; + emsg(_(e_highlight_group_name_invalid_char)); + return 0; + } + } + + int scoped_parent = 0; + if (len > 1 && name[0] == '@') { + char *delim = xmemrchr(name, '.', len); + if (delim) { + scoped_parent = syn_check_group(name, (size_t)(delim - name)); } } @@ -1765,7 +1841,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; @@ -1775,6 +1851,9 @@ static int syn_add_group(const char *name, size_t len) hlgp->sg_rgb_sp_idx = kColorIdxNone; hlgp->sg_blend = -1; hlgp->sg_name_u = arena_memdupz(&highlight_arena, name, len); + hlgp->sg_parent = scoped_parent; + // will get set to false by caller if settings are added + hlgp->sg_cleared = true; vim_strup((char_u *)hlgp->sg_name_u); int id = highlight_ga.ga_len; // ID is index plus one @@ -1788,11 +1867,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 +1887,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 +1904,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) { @@ -1823,10 +1915,13 @@ int syn_get_final_id(int hl_id) continue; } - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { + if (sgp->sg_link > 0 && sgp->sg_link <= highlight_ga.ga_len) { + hl_id = sgp->sg_link; + } else if (sgp->sg_cleared && sgp->sg_parent > 0) { + hl_id = sgp->sg_parent; + } else { break; } - hl_id = sgp->sg_link; } return hl_id; @@ -1863,7 +1958,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 +2008,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 +2031,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 +2046,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++) { @@ -1973,13 +2074,13 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg) // (part of) subcommand already typed if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); + const char *p = (const char *)skiptowhite(arg); if (*p != NUL) { // Past "default" or group name. include_default = 0; if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { arg = (const char *)skipwhite(p); xp->xp_pattern = (char *)arg; - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); } if (*p != NUL) { // past group name include_link = 0; @@ -1989,10 +2090,10 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg) if (strncmp("link", arg, (unsigned)(p - arg)) == 0 || strncmp("clear", arg, (unsigned)(p - arg)) == 0) { xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite((char_u *)xp->xp_pattern); + p = (const char *)skiptowhite(xp->xp_pattern); if (*p != NUL) { // Past first group name. xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite((char_u *)xp->xp_pattern); + p = (const char *)skiptowhite(xp->xp_pattern); } } if (*p != NUL) { // Past group name(s). 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..9413abb2e1 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" @@ -150,10 +153,10 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) // (part of) subcommand already typed if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); + const char *p = (const char *)skiptowhite(arg); if (*p != NUL) { // Past first word. xp->xp_pattern = skipwhite(p); - if (*skiptowhite((char_u *)xp->xp_pattern) != NUL) { + if (*skiptowhite(xp->xp_pattern) != NUL) { xp->xp_context = EXPAND_NOTHING; } else if (STRNICMP(arg, "add", p - arg) == 0) { xp->xp_context = EXPAND_FILES; @@ -200,19 +203,19 @@ static void do_cscope_general(exarg_T *eap, int make_split) /// Implementation of ":cscope" and ":lcscope" void ex_cscope(exarg_T *eap) { - do_cscope_general(eap, FALSE); + do_cscope_general(eap, false); } /// Implementation of ":scscope". Same as ex_cscope(), but splits window, too. void ex_scscope(exarg_T *eap) { - do_cscope_general(eap, TRUE); + do_cscope_general(eap, true); } /// Implementation of ":cstag" void ex_cstag(exarg_T *eap) { - int ret = FALSE; + int ret = false; if (*eap->arg == NUL) { (void)emsg(_("E562: Usage: cstag <ident>")); @@ -275,7 +278,7 @@ void ex_cstag(exarg_T *eap) /// This simulates a vim_fgets(), but for cscope, returns the next line /// from the cscope output. should only be called from find_tags() /// -/// @return true if eof, FALSE otherwise +/// @return true if eof, false otherwise bool cs_fgets(char_u *buf, int size) FUNC_ATTR_NONNULL_ALL { @@ -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); + expand_env(arg1, 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; } @@ -443,7 +445,7 @@ staterr: // get the prepend path (arg2), expand it, and see if it exists if (arg2 != NULL) { ppath = xmalloc(MAXPATHL + 1); - expand_env((char_u *)arg2, (char_u *)ppath, MAXPATHL); + expand_env(arg2, ppath, MAXPATHL); if (!os_path_exists((char_u *)ppath)) { goto staterr; } @@ -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++; } } @@ -755,14 +757,14 @@ err_closing: #endif // expand the cscope exec for env var's prog = xmalloc(MAXPATHL + 1); - expand_env(p_csprg, (char_u *)prog, MAXPATHL); + expand_env(p_csprg, prog, MAXPATHL); // alloc space to hold the cscope command size_t len = strlen(prog) + strlen(csinfo[i].fname) + 32; if (csinfo[i].ppath) { // expand the prepend path for env var's ppath = xmalloc(MAXPATHL + 1); - expand_env((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL); + expand_env(csinfo[i].ppath, ppath, MAXPATHL); len += strlen(ppath); } @@ -837,7 +839,7 @@ err_closing: si.hStdOutput = stdout_wr; si.hStdError = stdout_wr; si.hStdInput = stdin_rd; - created = CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, + created = CreateProcess(NULL, cmd, NULL, NULL, true, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); xfree(prog); xfree(cmd); @@ -873,7 +875,7 @@ err_closing: /// Query cscope using command line interface. Parse the output and use tselect /// to allow choices. Like Nvi, creates a pipe to send to/from query/cscope. /// -/// @return TRUE if we jump to a tag or abort, FALSE if not. +/// @return true if we jump to a tag or abort, false if not. static int cs_find(exarg_T *eap) { char *opt, *pat; @@ -898,7 +900,7 @@ static int cs_find(exarg_T *eap) * Let's replace the NULs written by strtok() with spaces - we need the * spaces to correctly display the quickfix/location list window's title. */ - for (int i = 0; i < eap_arg_len; ++i) { + for (int i = 0; i < eap_arg_len; i++) { if (NUL == eap->arg[i]) { eap->arg[i] = ' '; } @@ -951,7 +953,7 @@ static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool cmdletter = opt[0]; } - qfpos = vim_strchr((char *)p_csqf, cmdletter); + qfpos = vim_strchr(p_csqf, cmdletter); if (qfpos != NULL) { qfpos++; // next symbol must be + or - @@ -1104,7 +1106,7 @@ static int cs_help(exarg_T *eap) cmdp++; } - wait_return(TRUE); + wait_return(true); return CSCOPE_SUCCESS; } @@ -1278,7 +1280,7 @@ static void cs_kill_execute(size_t i, char *cname) (void)smsg_attr(HL_ATTR(HLF_R) | MSG_HIST, _("cscope connection %s closed"), cname); } - cs_release_csp(i, TRUE); + cs_release_csp(i, true); } /// Convert the cscope output into a ctags style entry (as might be found @@ -1836,7 +1838,7 @@ static void cs_release_csp(size_t i, bool freefnpp) // Can't use sigaction(), loop for two seconds. First yield the CPU // to give cscope a chance to exit quickly. sleep(0); - for (waited = 0; waited < 40; ++waited) { + for (waited = 0; waited < 40; waited++) { pid = waitpid(csinfo[i].pid, &pstat, WNOHANG); waitpid_errno = errno; if (pid != 0) { @@ -1930,7 +1932,7 @@ static int cs_reset(exarg_T *eap) pplist[i] = csinfo[i].ppath; fllist[i] = csinfo[i].flags; if (csinfo[i].fname != NULL) { - cs_release_csp(i, FALSE); + cs_release_csp(i, false); } } @@ -2033,7 +2035,7 @@ static int cs_show(exarg_T *eap) } } - wait_return(TRUE); + wait_return(false); return CSCOPE_SUCCESS; } diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 271498d41a..792f4d93c4 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" @@ -24,16 +25,325 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/textformat.h" #include "nvim/undo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # 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 *var, long **array) +{ + long valcount = 1; + int t; + char *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(cp, &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(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) { - return get_indent_str_vtab(get_cursor_line_ptr(), + return get_indent_str_vtab((char *)get_cursor_line_ptr(), curbuf->b_p_ts, curbuf->b_p_vts_array, false); @@ -42,7 +352,7 @@ int get_indent(void) // Count the size (in window cells) of the indent in line "lnum". int get_indent_lnum(linenr_T lnum) { - return get_indent_str_vtab(ml_get(lnum), + return get_indent_str_vtab((char *)ml_get(lnum), curbuf->b_p_ts, curbuf->b_p_vts_array, false); @@ -52,7 +362,7 @@ int get_indent_lnum(linenr_T lnum) // "buf". int get_indent_buf(buf_T *buf, linenr_T lnum) { - return get_indent_str_vtab(ml_get_buf(buf, lnum, false), + return get_indent_str_vtab((char *)ml_get_buf(buf, lnum, false), curbuf->b_p_ts, buf->b_p_vts_array, false); @@ -90,7 +400,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) /// Count the size (in window cells) of the indent in line "ptr", using /// variable tabstops. /// if "list" is true, count only screen size for tabs. -int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list) +int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list) { int count = 0; @@ -402,7 +712,7 @@ int get_number_indent(linenr_T lnum) if ((State & MODE_INSERT) || has_format_option(FO_Q_COMS)) { lead_len = get_leader_len((char *)ml_get(lnum), NULL, false, true); } - regmatch.regprog = vim_regcomp((char *)curbuf->b_p_flp, RE_MAGIC); + regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC); if (regmatch.regprog != NULL) { regmatch.rm_ic = false; @@ -424,6 +734,47 @@ int get_number_indent(linenr_T lnum) return (int)col; } +/// This is called when 'breakindentopt' is changed and when a window is +/// initialized +bool briopt_check(win_T *wp) +{ + int bri_shift = 0; + int bri_min = 20; + bool bri_sbr = false; + int bri_list = 0; + + char *p = wp->w_p_briopt; + while (*p != NUL) { + if (STRNCMP(p, "shift:", 6) == 0 + && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { + p += 6; + bri_shift = getdigits_int(&p, true, 0); + } else if (STRNCMP(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { + p += 4; + bri_min = getdigits_int(&p, true, 0); + } else if (STRNCMP(p, "sbr", 3) == 0) { + p += 3; + bri_sbr = true; + } else if (STRNCMP(p, "list:", 5) == 0) { + p += 5; + bri_list = (int)getdigits(&p, false, 0); + } + if (*p != ',' && *p != NUL) { + return false; + } + if (*p == ',') { + p++; + } + } + + wp->w_briopt_shift = bri_shift; + wp->w_briopt_min = bri_min; + wp->w_briopt_sbr = bri_sbr; + wp->w_briopt_list = bri_list; + + return true; +} + // Return appropriate space number for breakindent, taking influencing // parameters into account. Window must be specified, since it is not // necessarily always the current one. @@ -449,7 +800,7 @@ int get_breakindent_win(win_T *wp, char_u *line) prev_ts = wp->w_buffer->b_p_ts; prev_tick = buf_get_changedtick(wp->w_buffer); prev_vts = wp->w_buffer->b_p_vts_array; - prev_indent = get_indent_str_vtab(line, + prev_indent = get_indent_str_vtab((char *)line, wp->w_buffer->b_p_ts, wp->w_buffer->b_p_vts_array, wp->w_p_list); @@ -462,7 +813,7 @@ int get_breakindent_win(win_T *wp, char_u *line) // add additional indent for numbered lists if (wp->w_briopt_list != 0) { regmatch_T regmatch = { - .regprog = vim_regcomp((char *)curbuf->b_p_flp, + .regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), }; @@ -547,7 +898,7 @@ int get_expr_indent(void) // Need to make a copy, the 'indentexpr' option could be changed while // evaluating it. - char_u *inde_copy = vim_strsave(curbuf->b_p_inde); + char_u *inde_copy = vim_strsave((char_u *)curbuf->b_p_inde); indent = (int)eval_to_number((char *)inde_copy); xfree(inde_copy); @@ -684,13 +1035,15 @@ int get_lisp_indent(void) amount = 2; } else { char_u *line = that; - - amount = 0; - - while (*that && col) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); + while (*cts.cts_ptr != NUL && col > 0) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); col--; } + amount = cts.cts_vcol; + that = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); // Some keywords require "body" indenting rules (the // non-standard-lisp ones are Scheme special forms): @@ -706,10 +1059,15 @@ int get_lisp_indent(void) } firsttry = amount; - while (ascii_iswhite(*that)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - that++; + init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line), + amount, line, that); + while (ascii_iswhite(*cts.cts_ptr)) { + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr++; } + that = (char_u *)cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (*that && (*that != ';')) { // Not a comment line. @@ -722,33 +1080,38 @@ int get_lisp_indent(void) parencount = 0; quotecount = 0; + init_chartabsize_arg(&cts, curwin, + (colnr_T)(that - line), amount, line, that); if (vi_lisp || ((*that != '"') && (*that != '\'') && (*that != '#') && ((*that < '0') || (*that > '9')))) { - while (*that - && (!ascii_iswhite(*that) || quotecount || parencount) - && (!((*that == '(' || *that == '[') + while (*cts.cts_ptr + && (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount) + && (!((*cts.cts_ptr == '(' || *cts.cts_ptr == '[') && !quotecount && !parencount && vi_lisp))) { - if (*that == '"') { + if (*cts.cts_ptr == '"') { quotecount = !quotecount; } - if (((*that == '(') || (*that == '[')) && !quotecount) { + if (((*cts.cts_ptr == '(') || (*cts.cts_ptr == '[')) && !quotecount) { parencount++; } - if (((*that == ')') || (*that == ']')) && !quotecount) { + if (((*cts.cts_ptr == ')') || (*cts.cts_ptr == ']')) && !quotecount) { parencount--; } - if ((*that == '\\') && (*(that + 1) != NUL)) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + if ((*cts.cts_ptr == '\\') && (*(cts.cts_ptr + 1) != NUL)) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + cts.cts_vcol += lbr_chartabsize_adv(&cts); } } - while (ascii_iswhite(*that)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - that++; + while (ascii_iswhite(*cts.cts_ptr)) { + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr++; } + that = (char_u *)cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (!*that || (*that == ';')) { amount = firsttry; @@ -769,10 +1132,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 ? (char_u *)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..166695ef5b 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -212,19 +212,17 @@ int is_pos_in_string(const char_u *line, colnr_T col) * Below "XXX" means that this function may unlock the current line. */ -/* - * Return true if the string "line" starts with a word from 'cinwords'. - */ -bool cin_is_cinword(const char_u *line) +/// @return true if the string "line" starts with a word from 'cinwords'. +bool cin_is_cinword(const char *line) { bool retval = false; size_t cinw_len = STRLEN(curbuf->b_p_cinw) + 1; char_u *cinw_buf = xmalloc(cinw_len); - line = (char_u *)skipwhite((char *)line); + line = 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 = 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; @@ -275,10 +273,8 @@ static const char_u *cin_skipcomment(const char_u *s) return s; } -/* - * Return TRUE if there is no code at *s. White space and comments are - * not considered code. - */ +/// Return true if there is no code at *s. White space and comments are +/// not considered code. static int cin_nocode(const char_u *s) { return *cin_skipcomment(s) == NUL; @@ -317,17 +313,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); @@ -383,7 +379,7 @@ bool cin_islabel(void) // XXX cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; /* * If we're in a comment or raw string now, skip to the start of @@ -403,7 +399,7 @@ bool cin_islabel(void) // XXX } curwin->w_cursor = cursor_save; - if (cin_isterminated(line, TRUE, FALSE) + if (cin_isterminated(line, true, false) || cin_isscopedecl(line) || cin_iscase(line, true) || (cin_islabel_skip(&line) && cin_nocode(line))) { @@ -434,7 +430,7 @@ static int cin_isinit(void) for (;;) { int i, l; - for (i = 0; i < (int)ARRAY_SIZE(skip); ++i) { + for (i = 0; i < (int)ARRAY_SIZE(skip); i++) { l = (int)strlen(skip[i]); if (cin_starts_with(s, skip[i])) { s = cin_skipcomment(s + l); @@ -455,7 +451,7 @@ static int cin_isinit(void) return true; } - return FALSE; + return false; } /// Recognize a switch label: "case .*:" or "default:". @@ -465,7 +461,7 @@ bool cin_iscase(const char_u *s, bool strict) { s = cin_skipcomment(s); if (cin_starts_with(s, "case")) { - for (s += 4; *s; ++s) { + for (s += 4; *s; s++) { s = cin_skipcomment(s); if (*s == NUL) { break; @@ -519,8 +515,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 = 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] != ':') { @@ -588,7 +584,7 @@ static bool cin_is_cpp_namespace(const char_u *s) */ static const char_u *after_label(const char_u *l) { - for (; *l; ++l) { + for (; *l; l++) { if (*l == ':') { if (l[1] == ':') { // skip over "::" for C++ l++; @@ -680,10 +676,10 @@ static int cin_first_id_amount(void) line = get_cursor_line_ptr(); p = (char_u *)skipwhite((char *)line); - len = (int)(skiptowhite(p) - p); + len = (int)((char_u *)skiptowhite((char *)p) - p); if (len == 6 && STRNCMP(p, "static", 6) == 0) { p = (char_u *)skipwhite((char *)p + 6); - len = (int)(skiptowhite(p) - p); + len = (int)((char_u *)skiptowhite((char *)p) - p); } if (len == 6 && STRNCMP(p, "struct", 6) == 0) { p = (char_u *)skipwhite((char *)p + 6); @@ -769,10 +765,10 @@ static int cin_ispreproc(const char_u *s) if (*skipwhite((char *)s) == '#') { return true; } - return FALSE; + return false; } -/// Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a +/// Return true if line "*pp" at "*lnump" is a preprocessor statement or a /// continuation line of a preprocessor statement. Decrease "*lnump" to the /// start and return the line in "*pp". /// Put the amount of indent in "*amount". @@ -789,7 +785,7 @@ static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) for (;;) { if (cin_ispreproc(line)) { - retval = TRUE; + retval = true; *lnump = lnum; break; } @@ -842,7 +838,7 @@ static char_u cin_isterminated(const char_u *s, int incl_open, int incl_comma) { char_u found_start = 0; unsigned n_open = 0; - int is_else = FALSE; + int is_else = false; s = cin_skipcomment(s); @@ -1095,14 +1091,12 @@ probablyFound: return 0; } -/* - * Return TRUE if we are at the end of a do-while. - * do - * nothing; - * while (foo - * && bar); <-- here - * Adjust the cursor to the line with "while". - */ +/// Return true if we are at the end of a do-while. +/// do +/// nothing; +/// while (foo +/// && bar); <-- here +/// Adjust the cursor to the line with "while". static int cin_iswhileofdo_end(int terminated) { const char_u *line; @@ -1133,7 +1127,7 @@ static int cin_iswhileofdo_end(int terminated) } if (cin_starts_with(s, "while")) { curwin->w_cursor.lnum = trypos->lnum; - return TRUE; + return true; } } @@ -1146,7 +1140,7 @@ static int cin_iswhileofdo_end(int terminated) p++; } } - return FALSE; + return false; } static int cin_isbreak(const char_u *p) @@ -1190,7 +1184,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) return false; } - cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + cpp_base_class = lookfor_ctor_init = class_or_struct = false; /* Search for a line starting with '#', empty, ending in ';' or containing * '{' or '}' and start below it. This handles the following situations: @@ -1256,7 +1250,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) if (s[1] == ':') { /* skip double colon. It can't be a constructor * initialization any more */ - lookfor_ctor_init = FALSE; + lookfor_ctor_init = false; s = cin_skipcomment(s + 2); } else if (lookfor_ctor_init || class_or_struct) { /* we have something found, that looks like the start of @@ -1270,8 +1264,8 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } } else if ((STRNCMP(s, "class", 5) == 0 && !vim_isIDc(s[5])) || (STRNCMP(s, "struct", 6) == 0 && !vim_isIDc(s[6]))) { - class_or_struct = TRUE; - lookfor_ctor_init = FALSE; + class_or_struct = true; + lookfor_ctor_init = false; if (*s == 'c') { s = cin_skipcomment(s + 5); @@ -1280,12 +1274,12 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } } else { if (s[0] == '{' || s[0] == '}' || s[0] == ';') { - cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + cpp_base_class = lookfor_ctor_init = class_or_struct = false; } else if (s[0] == ')') { /* Constructor-initialization is assumed if we come across * something like "):" */ - class_or_struct = FALSE; - lookfor_ctor_init = TRUE; + class_or_struct = false; + lookfor_ctor_init = true; } else if (s[0] == '?') { // Avoid seeing '() :' after '?' as constructor init. return false; @@ -1345,11 +1339,9 @@ static int get_baseclass_amount(int col) return amount; } -/* - * Return TRUE if string "s" ends with the string "find", possibly followed by - * white space and comments. Skip strings and comments. - * Ignore "ignore" after "find" if it's not NULL. - */ +/// Return true if string "s" ends with the string "find", possibly followed by +/// white space and comments. Skip strings and comments. +/// Ignore "ignore" after "find" if it's not NULL. static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore) { const char_u *p = s; @@ -1371,12 +1363,10 @@ static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore p++; } } - return FALSE; + return false; } -/* - * Return TRUE when "s" starts with "word" and then a non-ID character. - */ +/// Return true when "s" starts with "word" and then a non-ID character. static int cin_starts_with(const char_u *s, const char *word) { int l = (int)STRLEN(word); @@ -1573,7 +1563,7 @@ static int corr_ind_maxparen(pos_T *startpos) static int find_last_paren(const char_u *l, int start, int end) { int i; - int retval = FALSE; + int retval = false; int open_count = 0; curwin->w_cursor.col = 0; // default is start of line @@ -1588,7 +1578,7 @@ static int find_last_paren(const char_u *l, int start, int end) open_count--; } else { curwin->w_cursor.col = i; - retval = TRUE; + retval = true; } } } @@ -1601,8 +1591,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); @@ -1745,11 +1735,11 @@ void parse_cino(buf_T *buf) 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 +1758,7 @@ void parse_cino(buf_T *buf) n += (sw * fraction + divider / 2) / divider; } } - ++p; + p++; } if (l[1] == '-') { n = -n; @@ -2029,7 +2019,7 @@ int get_c_indent(void) if (trypos == NULL && curwin->w_cursor.lnum > 1) { // There may be a statement before the comment, search from the end // of the line for a comment start. - linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1)); + linecomment_pos.col = check_linecomment((char *)ml_get(curwin->w_cursor.lnum - 1)); if (linecomment_pos.col != MAXCOL) { trypos = &linecomment_pos; trypos->lnum = curwin->w_cursor.lnum - 1; @@ -2052,10 +2042,10 @@ 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; + int done = false; // find how indented the line beginning the comment is getvcol(curwin, comment_pos, &col, NULL, NULL); @@ -2071,11 +2061,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 +2074,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); @@ -2098,7 +2088,7 @@ int get_c_indent(void) * up with the comment opener per the 'comments' option. */ if (STRNCMP(theline, lead_middle, lead_middle_len) == 0 && STRNCMP(theline, lead_end, STRLEN(lead_end)) != 0) { - done = TRUE; + done = true; if (curwin->w_cursor.lnum > 1) { /* If the start comment string matches in the previous * line, use the indent of that line plus offset. If @@ -2330,7 +2320,7 @@ int get_c_indent(void) /* look for opening unmatched paren, indent one level * for each additional level */ n = 1; - for (col = 0; col < our_paren_pos.col; ++col) { + for (col = 0; col < our_paren_pos.col; col++) { switch (l[col]) { case '(': case '{': @@ -2388,7 +2378,7 @@ int get_c_indent(void) * but ignore (void) before the line (ignore_paren_col). */ col = our_paren_pos.col; while ((int)our_paren_pos.col > ignore_paren_col) { - --our_paren_pos.col; + our_paren_pos.col--; switch (*ml_get_pos(&our_paren_pos)) { case '(': amount += curbuf->b_ind_unclosed2; @@ -2562,7 +2552,7 @@ int get_c_indent(void) } } - lookfor_break = FALSE; + lookfor_break = false; if (cin_iscase(theline, false)) { // it's a switch() label lookfor = LOOKFOR_CASE; // find a previous switch() label @@ -2646,7 +2636,7 @@ int get_c_indent(void) continue; } - terminated = cin_isterminated(l, FALSE, TRUE); + terminated = cin_isterminated(l, false, true); /* * If we are at top level and the line looks like a @@ -2660,7 +2650,7 @@ int get_c_indent(void) * don't add extra indent. * TODO: does not work, if a function * declaration is split over multiple lines: - * cin_isfuncdecl returns FALSE then. + * cin_isfuncdecl returns false then. */ if (terminated == ',') { break; @@ -2862,7 +2852,7 @@ int get_c_indent(void) if (n) { amount = n; l = after_label(get_cursor_line_ptr()); - if (l != NULL && cin_is_cinword(l)) { + if (l != NULL && cin_is_cinword((char *)l)) { if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; } else { @@ -2971,7 +2961,7 @@ int get_c_indent(void) * initialisation (not indented) or a variable declaration * (indented). */ - terminated = cin_isterminated(l, FALSE, TRUE); + terminated = cin_isterminated(l, false, true); if (js_cur_has_key) { js_cur_has_key = false; // only check the first line @@ -3116,7 +3106,7 @@ int get_c_indent(void) * Check if we are after an "if", "while", etc. * Also allow " } else". */ - if (cin_is_cinword(l) || cin_iselse((char_u *)skipwhite((char *)l))) { + if (cin_is_cinword((char *)l) || cin_iselse((char_u *)skipwhite((char *)l))) { // Found an unterminated line after an if (), line up // with the last one. // if (cond) @@ -3334,7 +3324,7 @@ int get_c_indent(void) amount += curbuf->b_ind_open_extra; } } - ++whilelevel; + whilelevel++; } /* * We are after a "normal" statement. @@ -3848,7 +3838,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/input.c b/src/nvim/input.c index 37c2903cfd..0aa9feaca3 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -46,7 +46,7 @@ int ask_yesno(const char *const str, const bool direct) int r = ' '; while (r != 'y' && r != 'n') { - // Same highlighting as for wait_return. + // same highlighting as for wait_return() smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); if (direct) { r = get_keystroke(NULL); diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index a64d8e8f00..ba0a36cafe 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -12,11 +12,14 @@ #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.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,14 +36,14 @@ #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" #include "nvim/strings.h" #include "nvim/tag.h" +#include "nvim/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -138,6 +141,21 @@ struct compl_S { int cp_number; ///< sequence number }; +/// state information used for getting the next set of insert completion +/// matches. +typedef struct { + char *e_cpt; ///< current entry in 'complete' + buf_T *ins_buf; ///< buffer being scanned + pos_T *cur_match_pos; ///< current match position + pos_T prev_match_pos; ///< previous match position + bool set_match_pos; ///< save first_match_pos/last_match_pos + pos_T first_match_pos; ///< first match position + pos_T last_match_pos; ///< last match position + bool found_all; ///< found all matches of a certain type. + char_u *dict; ///< dictionary file to search + int dict_f; ///< "dict" is an exact file name or not +} ins_compl_next_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "insexpand.c.generated.h" #endif @@ -160,6 +178,7 @@ static char e_compldel[] = N_("E840: Completion function deleted text"); // "compl_curr_match" points to the currently selected entry. // "compl_shown_match" is different from compl_curr_match during // ins_compl_get_exp(). +// "compl_old_match" points to previous "compl_curr_match". static compl_T *compl_first_match = NULL; static compl_T *compl_curr_match = NULL; @@ -201,12 +220,15 @@ static bool compl_started = false; ///< Which Ctrl-X mode are we in? static int ctrl_x_mode = CTRL_X_NORMAL; -static int compl_matches = 0; +static int compl_matches = 0; ///< number of completion matches static char *compl_pattern = NULL; static Direction compl_direction = FORWARD; static Direction compl_shows_dir = FORWARD; static int compl_pending = 0; ///< > 1 for postponed CTRL-N static pos_T compl_startpos; +/// Length in bytes of the text being completed (this is deleted to be replaced +/// by the match.) +static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed static char_u *compl_orig_text = NULL; ///< text as it was before @@ -214,6 +236,20 @@ static char_u *compl_orig_text = NULL; ///< text as it was before static int compl_cont_mode = 0; static expand_T compl_xp; +// List of flags for method of completion. +static int compl_cont_status = 0; + +#define CONT_ADDING 1 ///< "normal" or "adding" expansion +#define CONT_INTRPT (2 + 4) ///< a ^X interrupted the current expansion + ///< it's set only iff N_ADDS is set +#define CONT_N_ADDS 4 ///< next ^X<> will add-new or expand-current +#define CONT_S_IPOS 8 ///< next ^X<> will set initial_pos? + ///< if so, word-wise-expansion will set SOL +#define CONT_SOL 16 ///< pattern includes start of line, just for + ///< word-wise expansion, not set for ^X^L +#define CONT_LOCAL 32 ///< for ctrl_x_mode 0, ^X^P/^X^N do a local + ///< expansion, (eg use complete=.) + static bool compl_opt_refresh_always = false; static size_t spell_bad_len = 0; // length of located bad word @@ -232,7 +268,7 @@ void ins_ctrl_x(void) } // We're not sure which CTRL-X mode it will be yet ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); edit_submode_pre = NULL; showmode(); } else { @@ -357,9 +393,52 @@ bool ctrl_x_mode_not_defined_yet(void) return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET; } -/// Check that the "dict" or "tsr" option can be used. +/// @return true if currently in "normal" or "adding" insert completion matches state +bool compl_status_adding(void) +{ + return compl_cont_status & CONT_ADDING; +} + +/// @return true if the completion pattern includes start of line, just for +/// word-wise expansion. +bool compl_status_sol(void) +{ + return compl_cont_status & CONT_SOL; +} + +/// @return true if ^X^P/^X^N will do a local completion (i.e. use complete=.) +bool compl_status_local(void) +{ + return compl_cont_status & CONT_LOCAL; +} + +/// Clear the completion status flags +void compl_status_clear(void) +{ + compl_cont_status = 0; +} + +// @return true if completion is using the forward direction matches +static bool compl_dir_forward(void) +{ + return compl_direction == FORWARD; +} + +/// @return true if currently showing forward completion matches +static bool compl_shows_dir_forward(void) +{ + return compl_shows_dir == FORWARD; +} + +/// @return true if currently showing backward completion matches +static bool compl_shows_dir_backward(void) +{ + return compl_shows_dir == BACKWARD; +} + +/// Check that the 'dictionary' or 'thesaurus' option can be used. /// -/// @param dict_opt check "dict" when true, "tsr" when false. +/// @param dict_opt check 'dictionary' when true, 'thesaurus' when false. bool check_compl_option(bool dict_opt) { if (dict_opt @@ -443,6 +522,18 @@ bool vim_is_ctrl_x_key(int c) return false; } +/// @return true if "match" is the original text when the completion began. +static bool match_at_original_text(const compl_T *const match) +{ + return match->cp_flags & CP_ORIGINAL_TEXT; +} + +/// @return true if "match" is the first match in the completion list. +static bool is_first_match(const compl_T *const match) +{ + return match == compl_first_match; +} + /// Check that character "c" is part of the item currently being /// completed. Used to decide whether to abandon complete mode when the menu /// is visible. @@ -477,6 +568,106 @@ bool ins_compl_accept_char(int c) return vim_iswordc(c); } +/// Get the completed text by inferring the case of the originally typed text. +/// If the result is in allocated memory "tofree" is set to it. +static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_char_len, + int min_len, char **tofree) +{ + bool has_lower = false; + bool was_letter = false; + + // Allocate wide character array for the completion and fill it. + int *const wca = xmalloc((size_t)char_len * sizeof(*wca)); + { + const char_u *p = str; + for (int i = 0; i < char_len; i++) { + wca[i] = mb_ptr2char_adv(&p); + } + } + + // Rule 1: Were any chars converted to lower? + { + const char_u *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + has_lower = true; + if (mb_isupper(wca[i])) { + // Rule 1 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_tolower(wca[i]); + } + break; + } + } + } + } + + // Rule 2: No lower case, 2nd consecutive letter converted to + // upper case. + if (!has_lower) { + const char_u *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { + // Rule 2 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_toupper(wca[i]); + } + break; + } + was_letter = mb_islower(c) || mb_isupper(c); + } + } + + // Copy the original case of the part we typed. + { + const char_u *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + wca[i] = mb_tolower(wca[i]); + } else if (mb_isupper(c)) { + wca[i] = mb_toupper(wca[i]); + } + } + } + + // Generate encoding specific output from wide character array. + garray_T gap; + char *p = (char *)IObuff; + int i = 0; + ga_init(&gap, 1, 500); + while (i < char_len) { + if (gap.ga_data != NULL) { + ga_grow(&gap, 10); + assert(gap.ga_data != NULL); // suppress clang "Dereference of NULL pointer" + p = (char *)gap.ga_data + gap.ga_len; + gap.ga_len += utf_char2bytes(wca[i++], p); + } else if ((p - (char *)IObuff) + 6 >= IOSIZE) { + // Multi-byte characters can occupy up to five bytes more than + // ASCII characters, and we also need one byte for NUL, so when + // getting to six bytes from the edge of IObuff switch to using a + // growarray. Add the character in the next round. + ga_grow(&gap, IOSIZE); + *p = NUL; + STRCPY(gap.ga_data, IObuff); + gap.ga_len = (int)STRLEN(IObuff); + } else { + p += utf_char2bytes(wca[i++], p); + } + } + xfree(wca); + + if (gap.ga_data != NULL) { + *tofree = gap.ga_data; + return gap.ga_data; + } + + *p = NUL; + return IObuff; +} + /// This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the /// case of the originally typed text is used, and the case of the completed /// text is inferred, ie this tries to work out what case you probably wanted @@ -488,13 +679,10 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, FUNC_ATTR_NONNULL_ARG(1) { char_u *str = str_arg; - int i, c; - int actual_len; // Take multi-byte characters - int actual_compl_length; // into account. - int min_len; - bool has_lower = false; - bool was_letter = false; + int char_len; // count multi-byte characters + int compl_char_len; int flags = 0; + char *tofree = NULL; if (p_ic && curbuf->b_p_inf && len > 0) { // Infer case of completed part. @@ -502,101 +690,28 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, // Find actual length of completion. { const char_u *p = str; - actual_len = 0; + char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_len++; + char_len++; } } // Find actual length of original text. { const char_u *p = compl_orig_text; - actual_compl_length = 0; + compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_compl_length++; + compl_char_len++; } } - // "actual_len" may be smaller than "actual_compl_length" when using + // "char_len" may be smaller than "compl_char_len" when using // thesaurus, only use the minimum when comparing. - min_len = actual_len < actual_compl_length - ? actual_len : actual_compl_length; - - // Allocate wide character array for the completion and fill it. - int *const wca = xmalloc((size_t)actual_len * sizeof(*wca)); - { - const char_u *p = str; - for (i = 0; i < actual_len; i++) { - wca[i] = mb_ptr2char_adv(&p); - } - } - - // Rule 1: Were any chars converted to lower? - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - has_lower = true; - if (mb_isupper(wca[i])) { - // Rule 1 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_tolower(wca[i]); - } - break; - } - } - } - } - - // Rule 2: No lower case, 2nd consecutive letter converted to - // upper case. - if (!has_lower) { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { - // Rule 2 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_toupper(wca[i]); - } - break; - } - was_letter = mb_islower(c) || mb_isupper(c); - } - } + int min_len = char_len < compl_char_len ? char_len : compl_char_len; - // Copy the original case of the part we typed. - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - wca[i] = mb_tolower(wca[i]); - } else if (mb_isupper(c)) { - wca[i] = mb_toupper(wca[i]); - } - } - } - - // Generate encoding specific output from wide character array. - // Multi-byte characters can occupy up to five bytes more than - // ASCII characters, and we also need one byte for NUL, so stay - // six bytes away from the edge of IObuff. - { - char_u *p = IObuff; - i = 0; - while (i < actual_len && (p - IObuff + 6) < IOSIZE) { - p += utf_char2bytes(wca[i++], (char *)p); - } - *p = NUL; - } - - xfree(wca); - - str = IObuff; + str = ins_compl_infercase_gettext(str, char_len, compl_char_len, min_len, &tofree); } if (cont_s_ipos) { flags |= CP_CONT_S_IPOS; @@ -605,22 +720,30 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, flags |= CP_ICASE; } - return ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); + int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); + xfree(tofree); + return res; } /// Add a match to the list of matches /// -/// @param[in] str Match to add. -/// @param[in] len Match length, -1 to use #STRLEN. -/// @param[in] fname File name match comes from. May be NULL. -/// @param[in] cptext Extra text for popup menu. May be NULL. If not NULL, -/// must have exactly #CPT_COUNT items. +/// @param[in] str text of the match to add +/// @param[in] len length of "str". If -1, then the length of "str" is computed. +/// @param[in] fname file name to associate with this match. May be NULL. +/// @param[in] cptext list of strings to use with this match (for abbr, menu, info +/// and kind). May be NULL. +/// If not NULL, must have exactly #CPT_COUNT items. /// @param[in] cptext_allocated If true, will not copy cptext strings. /// /// @note Will free strings in case of error. /// cptext itself will not be freed. -/// @param[in] cdir Completion direction. -/// @param[in] adup True if duplicate matches are to be accepted. +/// @param[in] user_data user supplied data (any vim type) for this match +/// @param[in] cdir match direction. If 0, use "compl_direction". +/// @param[in] flags_arg match flags (cp_flags) +/// @param[in] adup accept this match even if it is already present. +/// +/// If "cdir" is FORWARD, then the match is added after the current match. +/// Otherwise, it is added before the current match. /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be @@ -659,14 +782,14 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, if (compl_first_match != NULL && !adup) { match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT) + if (!match_at_original_text(match) && STRNCMP(match->cp_str, str, len) == 0 - && match->cp_str[len] == NUL) { + && ((int)STRLEN(match->cp_str) <= len || match->cp_str[len] == NUL)) { FREE_CPTEXT(cptext, cptext_allocated); return NOTDONE; } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } // Remove any popup menu before changing the list of matches. @@ -719,7 +842,8 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, match->cp_user_data = *user_data; } - // Link the new match structure in the list of matches. + // Link the new match structure after (FORWARD) or before (BACKWARD) the + // current match in the list of matches . if (compl_first_match == NULL) { match->cp_next = match->cp_prev = NULL; } else if (dir == FORWARD) { @@ -775,9 +899,10 @@ static void ins_compl_longest_match(compl_T *match) if (compl_leader == NULL) { // First match, use it as a whole. compl_leader = vim_strsave(match->cp_str); + had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_bytes((char *)compl_leader + get_compl_len()); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it @@ -786,40 +911,42 @@ static void ins_compl_longest_match(compl_T *match) ins_compl_delete(); } compl_used_match = false; - } else { - // Reduce the text if this match differs from compl_leader. - p = compl_leader; - s = match->cp_str; - while (*p != NUL) { - c1 = utf_ptr2char((char *)p); - c2 = utf_ptr2char((char *)s); - - if ((match->cp_flags & CP_ICASE) - ? (mb_tolower(c1) != mb_tolower(c2)) - : (c1 != c2)) { - break; - } - MB_PTR_ADV(p); - MB_PTR_ADV(s); - } - if (*p != NUL) { - // Leader was shortened, need to change the inserted text. - *p = NUL; - had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); - ins_redraw(false); + return; + } - // When the match isn't there (to avoid matching itself) remove it - // again after redrawing. - if (!had_match) { - ins_compl_delete(); - } + // Reduce the text if this match differs from compl_leader. + p = compl_leader; + s = match->cp_str; + while (*p != NUL) { + c1 = utf_ptr2char((char *)p); + c2 = utf_ptr2char((char *)s); + + if ((match->cp_flags & CP_ICASE) + ? (mb_tolower(c1) != mb_tolower(c2)) + : (c1 != c2)) { + break; } + MB_PTR_ADV(p); + MB_PTR_ADV(s); + } - compl_used_match = false; + if (*p != NUL) { + // Leader was shortened, need to change the inserted text. + *p = NUL; + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes((char *)compl_leader + get_compl_len()); + ins_redraw(false); + + // When the match isn't there (to avoid matching itself) remove it + // again after redrawing. + if (!had_match) { + ins_compl_delete(); + } } + + compl_used_match = false; } /// Add an array of matches to the list of matches. @@ -844,20 +971,21 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) /// Return the number of matches (excluding the original). static int ins_compl_make_cyclic(void) { - compl_T *match; - int count = 0; + if (compl_first_match == NULL) { + return 0; + } - if (compl_first_match != NULL) { - // Find the end of the list. - match = compl_first_match; - // there's always an entry for the compl_orig_text, it doesn't count. - while (match->cp_next != NULL && match->cp_next != compl_first_match) { - match = match->cp_next; - count++; - } - match->cp_next = compl_first_match; - compl_first_match->cp_prev = match; + // Find the end of the list. + compl_T *match = compl_first_match; + int count = 0; + // there's always an entry for the compl_orig_text, it doesn't count. + while (match->cp_next != NULL && !is_first_match(match->cp_next)) { + match = match->cp_next; + count++; } + match->cp_next = compl_first_match; + compl_first_match->cp_prev = match; + return count; } @@ -880,10 +1008,10 @@ void completeopt_was_set(void) { compl_no_insert = false; compl_no_select = false; - if (strstr((char *)p_cot, "noselect") != NULL) { + if (strstr(p_cot, "noselect") != NULL) { compl_no_select = true; } - if (strstr((char *)p_cot, "noinsert") != NULL) { + if (strstr(p_cot, "noinsert") != NULL) { compl_no_insert = true; } } @@ -896,10 +1024,12 @@ static int compl_match_arraysize; /// Remove any popup menu. static void ins_compl_del_pum(void) { - if (compl_match_array != NULL) { - pum_undisplay(false); - XFREE_CLEAR(compl_match_array); + if (compl_match_array == NULL) { + return; } + + pum_undisplay(false); + XFREE_CLEAR(compl_match_array); } /// Check if the popup menu should be displayed. @@ -907,7 +1037,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return vim_strchr((char *)p_cot, 'm') != NULL; + return vim_strchr(p_cot, 'm') != NULL; } /// Check that there are two or more matches to be shown in the popup menu. @@ -917,17 +1047,16 @@ static bool pum_enough_matches(void) { // Don't display the popup menu if there are no matches or there is only // one (ignoring the original text). - compl_T *comp = compl_first_match; + compl_T *compl = compl_first_match; int i = 0; do { - if (comp == NULL - || ((comp->cp_flags & CP_ORIGINAL_TEXT) == 0 && ++i == 2)) { + if (compl == NULL || (!match_at_original_text(compl) && ++i == 2)) { break; } - comp = comp->cp_next; - } while (comp != compl_first_match); + compl = compl->cp_next; + } while (!is_first_match(compl)); - if (strstr((char *)p_cot, "menuone") != NULL) { + if (strstr(p_cot, "menuone") != NULL) { return i >= 1; } return i >= 2; @@ -951,6 +1080,8 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) return dict; } +/// Trigger the CompleteChanged autocmd event. Invoked each time the Insert mode +/// completion menu is changed. static void trigger_complete_changed_event(int cur) { static bool recursive = false; @@ -979,141 +1110,151 @@ static void trigger_complete_changed_event(int cur) restore_v_event(v_event, &save_v_event); } -/// Show the popup menu for the list of matches. -/// Also adjusts "compl_shown_match" to an entry that is actually displayed. -void ins_compl_show_pum(void) +/// Build a popup menu to show the completion matches. +/// +/// @return the popup menu entry that should be selected, +/// -1 if nothing should be selected. +static int ins_compl_build_pum(void) { - compl_T *compl; - compl_T *shown_compl = NULL; - bool did_find_shown_match = false; - bool shown_match_ok = false; - int i; - int cur = -1; - colnr_T col; - int lead_len = 0; - bool array_changed = false; + // Need to build the popup menu list. + compl_match_arraysize = 0; + compl_T *compl = compl_first_match; - if (!pum_wanted() || !pum_enough_matches()) { - return; + // If it's user complete function and refresh_always, + // do not use "compl_leader" as prefix filter. + if (ins_compl_need_restart()) { + XFREE_CLEAR(compl_leader); } - // Dirty hard-coded hack: remove any matchparen highlighting. - do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); + const int lead_len = compl_leader != NULL ? (int)STRLEN(compl_leader) : 0; - // Update the screen before drawing the popup menu over it. - update_screen(0); - - if (compl_match_array == NULL) { - array_changed = true; - // Need to build the popup menu list. - compl_match_arraysize = 0; - compl = compl_first_match; - // If it's user complete function and refresh_always, - // do not use "compl_leader" as prefix filter. - if (ins_compl_need_restart()) { - XFREE_CLEAR(compl_leader); - } - if (compl_leader != NULL) { - lead_len = (int)STRLEN(compl_leader); - } - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - compl_match_arraysize++; - } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (compl_match_arraysize == 0) { - return; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { + compl_match_arraysize++; } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); - assert(compl_match_arraysize >= 0); - compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - // If the current match is the original text don't find the first - // match after it, don't highlight anything. - if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl_match_arraysize == 0) { + return -1; + } - i = 0; - compl = compl_first_match; - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - if (!shown_match_ok) { - if (compl == compl_shown_match || did_find_shown_match) { - // This item is the shown match or this is the - // first displayed item after the shown match. - compl_shown_match = compl; - did_find_shown_match = true; - shown_match_ok = true; - } else { - // Remember this displayed match for when the - // shown match is just below it. - shown_compl = compl; - } - cur = i; - } + assert(compl_match_arraysize >= 0); + compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - if (compl->cp_text[CPT_ABBR] != NULL) { - compl_match_array[i].pum_text = - compl->cp_text[CPT_ABBR]; - } else { - compl_match_array[i].pum_text = compl->cp_str; - } - compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; - compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; - if (compl->cp_text[CPT_MENU] != NULL) { - compl_match_array[i++].pum_extra = - compl->cp_text[CPT_MENU]; + // If the current match is the original text don't find the first + // match after it, don't highlight anything. + bool shown_match_ok = match_at_original_text(compl_shown_match); + + compl_T *shown_compl = NULL; + bool did_find_shown_match = false; + int cur = -1; + int i = 0; + compl = compl_first_match; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { + if (!shown_match_ok) { + if (compl == compl_shown_match || did_find_shown_match) { + // This item is the shown match or this is the + // first displayed item after the shown match. + compl_shown_match = compl; + did_find_shown_match = true; + shown_match_ok = true; } else { - compl_match_array[i++].pum_extra = compl->cp_fname; + // Remember this displayed match for when the + // shown match is just below it. + shown_compl = compl; } + cur = i; } - if (compl == compl_shown_match) { - did_find_shown_match = true; + if (compl->cp_text[CPT_ABBR] != NULL) { + compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR]; + } else { + compl_match_array[i].pum_text = compl->cp_str; + } + compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; + compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; + if (compl->cp_text[CPT_MENU] != NULL) { + compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU]; + } else { + compl_match_array[i++].pum_extra = compl->cp_fname; + } + } - // When the original text is the shown match don't set - // compl_shown_match. - if (compl->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl == compl_shown_match) { + did_find_shown_match = true; - if (!shown_match_ok && shown_compl != NULL) { - // The shown match isn't displayed, set it to the - // previously displayed match. - compl_shown_match = shown_compl; - shown_match_ok = true; - } + // When the original text is the shown match don't set + // compl_shown_match. + if (match_at_original_text(compl)) { + shown_match_ok = true; } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (!shown_match_ok) { // no displayed match at all - cur = -1; + if (!shown_match_ok && shown_compl != NULL) { + // The shown match isn't displayed, set it to the + // previously displayed match. + compl_shown_match = shown_compl; + shown_match_ok = true; + } } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); + + if (!shown_match_ok) { // no displayed match at all + cur = -1; + } + + return cur; +} + +/// Show the popup menu for the list of matches. +/// Also adjusts "compl_shown_match" to an entry that is actually displayed. +void ins_compl_show_pum(void) +{ + if (!pum_wanted() || !pum_enough_matches()) { + return; + } + + // Dirty hard-coded hack: remove any matchparen highlighting. + do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); + + // Update the screen before drawing the popup menu over it. + update_screen(0); + + int cur = -1; + bool array_changed = false; + + if (compl_match_array == NULL) { + array_changed = true; + // Need to build the popup menu list. + cur = ins_compl_build_pum(); } else { // popup menu already exists, only need to find the current item. - for (i = 0; i < compl_match_arraysize; i++) { + for (int i = 0; i < compl_match_arraysize; i++) { if (compl_match_array[i].pum_text == compl_shown_match->cp_str - || compl_match_array[i].pum_text - == compl_shown_match->cp_text[CPT_ABBR]) { + || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; } } } + if (compl_match_array == NULL) { + return; + } + // In Replace mode when a $ is displayed at the end of the line only // part of the screen would be updated. We do need to redraw here. dollar_vcol = -1; // Compute the screen column of the start of the completed text. // Use the cursor to get all wrapping and other settings right. - col = curwin->w_cursor.col; + const colnr_T col = curwin->w_cursor.col; curwin->w_cursor.col = compl_col; pum_selected_item = cur; pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0); @@ -1127,8 +1268,8 @@ void ins_compl_show_pum(void) #define DICT_FIRST (1) ///< use just first element in "dict" #define DICT_EXACT (2) ///< "dict" is the exact name of a file -/// Add any identifiers that match the given pattern in the list of dictionary -/// files "dict_start" to the list of completions. +/// Add any identifiers that match the given pattern "pat" in the list of +/// dictionary files "dict_start" to the list of completions. /// /// @param flags DICT_FIRST and/or DICT_EXACT /// @param thesaurus Thesaurus completion @@ -1229,6 +1370,54 @@ theend: xfree(buf); } +/// Add all the words in the line "*buf_arg" from the thesaurus file "fname" +/// skipping the word at 'skip_word'. +/// +/// @return OK on success. +static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, char_u *skip_word) +{ + int status = OK; + + // Add the other matches on the line + char_u *ptr = *buf_arg; + while (!got_int) { + // Find start of the next word. Skip white + // space and punctuation. + ptr = find_word_start(ptr); + if (*ptr == NUL || *ptr == NL) { + break; + } + char_u *wstart = ptr; + + // Find end of the word. + // Japanese words may have characters in + // different classes, only separate words + // with single-byte non-word characters. + while (*ptr != NUL) { + const int l = utfc_ptr2len((const char *)ptr); + + if (l < 2 && !vim_iswordc(*ptr)) { + break; + } + ptr += l; + } + + // Add the word. Skip the regexp match. + if (wstart != skip_word) { + status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic, + (char_u *)fname, dir, false); + if (status == FAIL) { + break; + } + } + } + + *buf_arg = ptr; + return status; +} + +/// Process "count" dictionary/thesaurus "files" and add the text matching +/// "regmatch". static void ins_compl_files(int count, char **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, Direction *dir) FUNC_ATTR_NONNULL_ARG(2, 7) @@ -1253,8 +1442,7 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r // Read dictionary file line by line. // Check each line for a match. - while (!got_int && !compl_interrupted - && !vim_fgets(buf, LSIZE, fp)) { + while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) { ptr = buf; while (vim_regexec(regmatch, (char *)buf, (colnr_T)(ptr - buf))) { ptr = regmatch->startp[0]; @@ -1267,38 +1455,10 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r (int)(ptr - regmatch->startp[0]), p_ic, (char_u *)files[i], *dir, false); if (thesaurus) { - char_u *wstart; - - // Add the other matches on the line + // For a thesaurus, add all the words in the line ptr = buf; - while (!got_int) { - // Find start of the next word. Skip white - // space and punctuation. - ptr = find_word_start(ptr); - if (*ptr == NUL || *ptr == NL) { - break; - } - wstart = ptr; - - // Find end of the word. - // Japanese words may have characters in - // different classes, only separate words - // with single-byte non-word characters. - while (*ptr != NUL) { - const int l = utfc_ptr2len((char *)ptr); - - if (l < 2 && !vim_iswordc(*ptr)) { - break; - } - ptr += l; - } - - // Add the word. Skip the regexp match. - if (wstart != regmatch->startp[0]) { - add_r = ins_compl_add_infercase(wstart, (int)(ptr - wstart), - p_ic, (char_u *)files[i], *dir, false); - } - } + add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, + regmatch->startp[0]); } if (add_r == OK) { // if dir was BACKWARD then honor it just once @@ -1389,12 +1549,13 @@ static void ins_compl_free(void) } tv_clear(&match->cp_user_data); xfree(match); - } while (compl_curr_match != NULL && compl_curr_match != compl_first_match); + } while (compl_curr_match != NULL && !is_first_match(compl_curr_match)); compl_first_match = compl_curr_match = NULL; compl_shown_match = NULL; compl_old_match = NULL; } +/// Reset/clear the completion state. void ins_compl_clear(void) { compl_cont_status = 0; @@ -1448,6 +1609,12 @@ colnr_T ins_compl_col(void) return compl_col; } +/// Return the length in bytes of the text being completed +int ins_compl_len(void) +{ + return compl_length; +} + /// Delete one character before the cursor and show the subset of the matches /// that match the word that is now before the cursor. /// Returns the character to be used, NUL if the work is done and another char @@ -1483,12 +1650,12 @@ int ins_compl_bs(void) xfree(compl_leader); compl_leader = vim_strnsave(line + compl_col, (size_t)(p_off - (ptrdiff_t)compl_col)); + ins_compl_new_leader(); if (compl_shown_match != NULL) { // Make sure current match is not a hidden item. compl_curr_match = compl_shown_match; } - return NUL; } @@ -1511,7 +1678,7 @@ static void ins_compl_new_leader(void) { ins_compl_del_pum(); ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_bytes((char *)compl_leader + get_compl_len()); compl_used_match = false; if (compl_started) { @@ -1565,7 +1732,7 @@ void ins_compl_addleader(int c) utf_char2bytes(c, (char *)buf); buf[cc] = NUL; - ins_char_bytes((char_u *)buf, (size_t)cc); + ins_char_bytes(buf, (size_t)cc); } else { ins_char(c); } @@ -1601,13 +1768,13 @@ static void ins_compl_set_original_text(char_u *str) FUNC_ATTR_NONNULL_ALL { // Replace the original text entry. - // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly be - // at the last item for backward completion - if (compl_first_match->cp_flags & CP_ORIGINAL_TEXT) { // safety check + // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly + // be at the last item for backward completion + if (match_at_original_text(compl_first_match)) { // safety check xfree(compl_first_match->cp_str); compl_first_match->cp_str = vim_strsave(str); } else if (compl_first_match->cp_prev != NULL - && (compl_first_match->cp_prev->cp_flags & CP_ORIGINAL_TEXT)) { + && match_at_original_text(compl_first_match->cp_prev)) { xfree(compl_first_match->cp_prev->cp_str); compl_first_match->cp_prev->cp_str = vim_strsave(str); } @@ -1626,20 +1793,20 @@ void ins_compl_addfrommatch(void) if ((int)STRLEN(p) <= len) { // the match is too short // When still at the original match use the first entry that matches // the leader. - if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - p = NULL; - for (cp = compl_shown_match->cp_next; cp != NULL - && cp != compl_first_match; cp = cp->cp_next) { - if (compl_leader == NULL - || ins_compl_equal(cp, compl_leader, STRLEN(compl_leader))) { - p = cp->cp_str; - break; - } - } - if (p == NULL || (int)STRLEN(p) <= len) { - return; + if (!match_at_original_text(compl_shown_match)) { + return; + } + + p = NULL; + for (cp = compl_shown_match->cp_next; cp != NULL + && !is_first_match(cp); cp = cp->cp_next) { + if (compl_leader == NULL + || ins_compl_equal(cp, compl_leader, STRLEN(compl_leader))) { + p = cp->cp_str; + break; } - } else { + } + if (p == NULL || (int)STRLEN(p) <= len) { return; } } @@ -1648,6 +1815,249 @@ void ins_compl_addfrommatch(void) ins_compl_addleader(c); } +/// Set the CTRL-X completion mode based on the key "c" typed after a CTRL-X. +/// Uses the global variables: ctrl_x_mode, edit_submode, edit_submode_pre, +/// compl_cont_mode and compl_cont_status. +/// +/// @return true when the character is not to be inserted. +static bool set_ctrl_x_mode(const int c) +{ + bool retval = false; + + switch (c) { + case Ctrl_E: + case Ctrl_Y: + // scroll the window one line up or down + ctrl_x_mode = CTRL_X_SCROLL; + if (!(State & REPLACE_FLAG)) { + edit_submode = _(" (insert) Scroll (^E/^Y)"); + } else { + edit_submode = _(" (replace) Scroll (^E/^Y)"); + } + edit_submode_pre = NULL; + showmode(); + break; + case Ctrl_L: + // complete whole line + ctrl_x_mode = CTRL_X_WHOLE_LINE; + break; + case Ctrl_F: + // complete filenames + ctrl_x_mode = CTRL_X_FILES; + break; + case Ctrl_K: + // complete words from a dictionary + ctrl_x_mode = CTRL_X_DICTIONARY; + break; + case Ctrl_R: + // Register insertion without exiting CTRL-X mode + // Simply allow ^R to happen without affecting ^X mode + break; + case Ctrl_T: + // complete words from a thesaurus + ctrl_x_mode = CTRL_X_THESAURUS; + break; + case Ctrl_U: + // user defined completion + ctrl_x_mode = CTRL_X_FUNCTION; + break; + case Ctrl_O: + // omni completion + ctrl_x_mode = CTRL_X_OMNI; + break; + case 's': + case Ctrl_S: + // complete spelling suggestions + ctrl_x_mode = CTRL_X_SPELL; + emsg_off++; // Avoid getting the E756 error twice. + spell_back_to_badword(); + emsg_off--; + break; + case Ctrl_RSB: + // complete tag names + ctrl_x_mode = CTRL_X_TAGS; + break; + case Ctrl_I: + case K_S_TAB: + // complete keywords from included files + ctrl_x_mode = CTRL_X_PATH_PATTERNS; + break; + case Ctrl_D: + // complete definitions from included files + ctrl_x_mode = CTRL_X_PATH_DEFINES; + break; + case Ctrl_V: + case Ctrl_Q: + // complete vim commands + ctrl_x_mode = CTRL_X_CMDLINE; + break; + case Ctrl_Z: + // stop completion + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + showmode(); + retval = true; + break; + case Ctrl_P: + case Ctrl_N: + // ^X^P means LOCAL expansion if nothing interrupted (eg we + // just started ^X mode, or there were enough ^X's to cancel + // the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) + // do normal expansion when interrupting a different mode (say + // ^X^F^X^P or ^P^X^X^P, see below) + // nothing changes if interrupting mode 0, (eg, the flag + // doesn't change when going to ADDING mode -- Acevedo + if (!(compl_cont_status & CONT_INTRPT)) { + compl_cont_status |= CONT_LOCAL; + } else if (compl_cont_mode != 0) { + compl_cont_status &= ~CONT_LOCAL; + } + FALLTHROUGH; + default: + // If we have typed at least 2 ^X's... for modes != 0, we set + // compl_cont_status = 0 (eg, as if we had just started ^X + // mode). + // For mode 0, we set "compl_cont_mode" to an impossible + // value, in both cases ^X^X can be used to restart the same + // mode (avoiding ADDING mode). + // Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start + // 'complete' and local ^P expansions respectively. + // In mode 0 an extra ^X is needed since ^X^P goes to ADDING + // mode -- Acevedo + if (c == Ctrl_X) { + if (compl_cont_mode != 0) { + compl_cont_status = 0; + } else { + compl_cont_mode = CTRL_X_NOT_DEFINED_YET; + } + } + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + showmode(); + break; + } + + return retval; +} + +/// Stop insert completion mode +static bool ins_compl_stop(const int c, const int prev_mode, bool retval) +{ + // Get here when we have finished typing a sequence of ^N and + // ^P or other completion characters in CTRL-X mode. Free up + // memory that was used, and make sure we can redo the insert. + if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { + // If any of the original typed text has been changed, eg when + // ignorecase is set, we must add back-spaces to the redo + // buffer. We add as few as necessary to delete just the part + // of the original text that has changed. + // When using the longest match, edited the match or used + // CTRL-E then don't use the current match. + char_u *ptr; + if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { + ptr = compl_curr_match->cp_str; + } else { + ptr = NULL; + } + ins_compl_fixRedoBufForLeader(ptr); + } + + bool want_cindent = (get_can_cindent() && cindent_on()); + + // When completing whole lines: fix indent for 'cindent'. + // Otherwise, break line if it's too long. + if (compl_cont_mode == CTRL_X_WHOLE_LINE) { + // re-indent the current line + if (want_cindent) { + do_c_expr_indent(); + want_cindent = false; // don't do it again + } + } else { + const int prev_col = curwin->w_cursor.col; + + // put the cursor on the last char, for 'tw' formatting + if (prev_col > 0) { + dec_cursor(); + } + + // only format when something was inserted + if (!arrow_used && !ins_need_undo_get() && c != Ctrl_E) { + insertchar(NUL, 0, -1); + } + + if (prev_col > 0 + && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { + inc_cursor(); + } + } + + // If the popup menu is displayed pressing CTRL-Y means accepting + // the selection without inserting anything. When + // compl_enter_selects is set the Enter key does the same. + if ((c == Ctrl_Y || (compl_enter_selects + && (c == CAR || c == K_KENTER || c == NL))) + && pum_visible()) { + retval = true; + } + + // CTRL-E means completion is Ended, go back to the typed text. + // but only do this, if the Popup is still visible + if (c == Ctrl_E) { + ins_compl_delete(); + char_u *p = NULL; + if (compl_leader != NULL) { + p = compl_leader; + } else if (compl_first_match != NULL) { + p = compl_orig_text; + } + if (p != NULL) { + const int compl_len = get_compl_len(); + const int len = (int)STRLEN(p); + if (len > compl_len) { + ins_bytes_len((char *)p + compl_len, (size_t)(len - compl_len)); + } + } + retval = true; + } + + auto_format(false, true); + + // Trigger the CompleteDonePre event to give scripts a chance to + // act upon the completion before clearing the info, and restore + // ctrl_x_mode, so that complete_info() can be used. + ctrl_x_mode = prev_mode; + ins_apply_autocmds(EVENT_COMPLETEDONEPRE); + + ins_compl_free(); + compl_started = false; + compl_matches = 0; + if (!shortmess(SHM_COMPLETIONMENU)) { + msg_clr_cmdline(); // necessary for "noshowmode" + } + ctrl_x_mode = CTRL_X_NORMAL; + compl_enter_selects = false; + if (edit_submode != NULL) { + edit_submode = NULL; + showmode(); + } + + if (c == Ctrl_C && cmdwin_type != 0) { + // Avoid the popup menu remains displayed when leaving the + // command line window. + update_screen(0); + } + + // Indent now if a key was typed that is in 'cinkeys'. + if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) { + do_c_expr_indent(); + } + // Trigger the CompleteDone event to give scripts a chance to act + // upon the end of completion. + ins_apply_autocmds(EVENT_COMPLETEDONE); + + return retval; +} + /// Prepare for Insert mode completion, or stop it. /// Called just after typing a character in Insert mode. /// @@ -1656,7 +2066,6 @@ void ins_compl_addfrommatch(void) /// @return true when the character is not to be inserted; bool ins_compl_prep(int c) { - char_u *ptr; bool retval = false; const int prev_mode = ctrl_x_mode; @@ -1696,111 +2105,14 @@ bool ins_compl_prep(int c) // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = (strstr((char *)p_cot, "longest") != NULL); + compl_get_longest = (strstr(p_cot, "longest") != NULL); compl_used_match = true; } if (ctrl_x_mode_not_defined_yet()) { // We have just typed CTRL-X and aren't quite sure which CTRL-X mode // it will be yet. Now we decide. - switch (c) { - case Ctrl_E: - case Ctrl_Y: - ctrl_x_mode = CTRL_X_SCROLL; - if (!(State & REPLACE_FLAG)) { - edit_submode = (char_u *)_(" (insert) Scroll (^E/^Y)"); - } else { - edit_submode = (char_u *)_(" (replace) Scroll (^E/^Y)"); - } - edit_submode_pre = NULL; - showmode(); - break; - case Ctrl_L: - ctrl_x_mode = CTRL_X_WHOLE_LINE; - break; - case Ctrl_F: - ctrl_x_mode = CTRL_X_FILES; - break; - case Ctrl_K: - ctrl_x_mode = CTRL_X_DICTIONARY; - break; - case Ctrl_R: - // Simply allow ^R to happen without affecting ^X mode - break; - case Ctrl_T: - ctrl_x_mode = CTRL_X_THESAURUS; - break; - case Ctrl_U: - ctrl_x_mode = CTRL_X_FUNCTION; - break; - case Ctrl_O: - ctrl_x_mode = CTRL_X_OMNI; - break; - case 's': - case Ctrl_S: - ctrl_x_mode = CTRL_X_SPELL; - emsg_off++; // Avoid getting the E756 error twice. - spell_back_to_badword(); - emsg_off--; - break; - case Ctrl_RSB: - ctrl_x_mode = CTRL_X_TAGS; - break; - case Ctrl_I: - case K_S_TAB: - ctrl_x_mode = CTRL_X_PATH_PATTERNS; - break; - case Ctrl_D: - ctrl_x_mode = CTRL_X_PATH_DEFINES; - break; - case Ctrl_V: - case Ctrl_Q: - ctrl_x_mode = CTRL_X_CMDLINE; - break; - case Ctrl_Z: - ctrl_x_mode = CTRL_X_NORMAL; - edit_submode = NULL; - showmode(); - retval = true; - break; - case Ctrl_P: - case Ctrl_N: - // ^X^P means LOCAL expansion if nothing interrupted (eg we - // just started ^X mode, or there were enough ^X's to cancel - // the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) - // do normal expansion when interrupting a different mode (say - // ^X^F^X^P or ^P^X^X^P, see below) - // nothing changes if interrupting mode 0, (eg, the flag - // doesn't change when going to ADDING mode -- Acevedo - if (!(compl_cont_status & CONT_INTRPT)) { - compl_cont_status |= CONT_LOCAL; - } else if (compl_cont_mode != 0) { - compl_cont_status &= ~CONT_LOCAL; - } - FALLTHROUGH; - default: - // If we have typed at least 2 ^X's... for modes != 0, we set - // compl_cont_status = 0 (eg, as if we had just started ^X - // mode). - // For mode 0, we set "compl_cont_mode" to an impossible - // value, in both cases ^X^X can be used to restart the same - // mode (avoiding ADDING mode). - // Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start - // 'complete' and local ^P expansions respectively. - // In mode 0 an extra ^X is needed since ^X^P goes to ADDING - // mode -- Acevedo - if (c == Ctrl_X) { - if (compl_cont_mode != 0) { - compl_cont_status = 0; - } else { - compl_cont_mode = CTRL_X_NOT_DEFINED_YET; - } - } - ctrl_x_mode = CTRL_X_NORMAL; - edit_submode = NULL; - showmode(); - break; - } + retval = set_ctrl_x_mode(c); } else if (ctrl_x_mode_not_default()) { // We're already in CTRL-X mode, do we stay in it? if (!vim_is_ctrl_x_key(c)) { @@ -1825,107 +2137,7 @@ bool ins_compl_prep(int c) && c != Ctrl_R && !ins_compl_pum_key(c)) || ctrl_x_mode == CTRL_X_FINISHED) { - // Get here when we have finished typing a sequence of ^N and - // ^P or other completion characters in CTRL-X mode. Free up - // memory that was used, and make sure we can redo the insert. - if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { - // If any of the original typed text has been changed, eg when - // ignorecase is set, we must add back-spaces to the redo - // buffer. We add as few as necessary to delete just the part - // of the original text that has changed. - // When using the longest match, edited the match or used - // CTRL-E then don't use the current match. - if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { - ptr = compl_curr_match->cp_str; - } else { - ptr = NULL; - } - ins_compl_fixRedoBufForLeader(ptr); - } - - bool want_cindent = (can_cindent_get() && cindent_on()); - - // When completing whole lines: fix indent for 'cindent'. - // Otherwise, break line if it's too long. - if (compl_cont_mode == CTRL_X_WHOLE_LINE) { - // re-indent the current line - if (want_cindent) { - do_c_expr_indent(); - want_cindent = false; // don't do it again - } - } else { - int prev_col = curwin->w_cursor.col; - - // put the cursor on the last char, for 'tw' formatting - if (prev_col > 0) { - dec_cursor(); - } - - if (!arrow_used && !ins_need_undo_get() && c != Ctrl_E) { - insertchar(NUL, 0, -1); - } - - if (prev_col > 0 - && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { - inc_cursor(); - } - } - - // If the popup menu is displayed pressing CTRL-Y means accepting - // the selection without inserting anything. When - // compl_enter_selects is set the Enter key does the same. - if ((c == Ctrl_Y || (compl_enter_selects - && (c == CAR || c == K_KENTER || c == NL))) - && pum_visible()) { - retval = true; - } - - // CTRL-E means completion is Ended, go back to the typed text. - // but only do this, if the Popup is still visible - if (c == Ctrl_E) { - ins_compl_delete(); - if (compl_leader != NULL) { - ins_bytes(compl_leader + get_compl_len()); - } else if (compl_first_match != NULL) { - ins_bytes(compl_orig_text + get_compl_len()); - } - retval = true; - } - - auto_format(false, true); - - // Trigger the CompleteDonePre event to give scripts a chance to - // act upon the completion before clearing the info, and restore - // ctrl_x_mode, so that complete_info() can be used. - ctrl_x_mode = prev_mode; - ins_apply_autocmds(EVENT_COMPLETEDONEPRE); - - ins_compl_free(); - compl_started = false; - compl_matches = 0; - if (!shortmess(SHM_COMPLETIONMENU)) { - msg_clr_cmdline(); // necessary for "noshowmode" - } - ctrl_x_mode = CTRL_X_NORMAL; - compl_enter_selects = false; - if (edit_submode != NULL) { - edit_submode = NULL; - showmode(); - } - - // Avoid the popup menu remains displayed when leaving the - // command line window. - if (c == Ctrl_C && cmdwin_type != 0) { - update_screen(0); - } - - // Indent now if a key was typed that is in 'cinkeys'. - if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) { - do_c_expr_indent(); - } - // Trigger the CompleteDone event to give scripts a chance to act - // upon the end of completion. - ins_apply_autocmds(EVENT_COMPLETEDONE); + retval = ins_compl_stop(c, prev_mode, retval); } } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) { // Trigger the CompleteDone event to give scripts a chance to act @@ -2008,16 +2220,16 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) return buf; } -/// Get the user-defined completion function name for completion 'type' +/// Get the user-defined completion function name for completion "type" static char_u *get_complete_funcname(int type) { switch (type) { case CTRL_X_FUNCTION: - return curbuf->b_p_cfu; + return (char_u *)curbuf->b_p_cfu; case CTRL_X_OMNI: - return curbuf->b_p_ofu; + return (char_u *)curbuf->b_p_ofu; case CTRL_X_THESAURUS: - return *curbuf->b_p_tsrfu == NUL ? p_tsrfu : curbuf->b_p_tsrfu; + return *curbuf->b_p_tsrfu == NUL ? (char_u *)p_tsrfu : (char_u *)curbuf->b_p_tsrfu; default: return (char_u *)""; } @@ -2139,16 +2351,21 @@ 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++) { xfree(cptext[i]); } + tv_clear(&user_data); return FAIL; } - return ins_compl_add((char_u *)word, -1, NULL, - (char_u **)cptext, true, &user_data, dir, flags, dup); + int status = ins_compl_add((char_u *)word, -1, NULL, (char_u **)cptext, true, + &user_data, dir, flags, dup); + if (status != OK) { + tv_clear(&user_data); + } + return status; } /// Add completions from a list. @@ -2254,7 +2471,7 @@ static void set_completion(colnr_T startcol, list_T *list) } /// "complete()" function -void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if ((State & MODE_INSERT) == 0) { emsg(_("E785: complete() can only be used in Insert mode")); @@ -2278,13 +2495,13 @@ void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "complete_add()" function -void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } /// "complete_check()" function -void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_check(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int saved = RedrawingDisabled; @@ -2303,18 +2520,19 @@ static char_u *ins_compl_mode(void) return (char_u *)""; } +/// Assign the sequence number to all the completion matches which don't have +/// one assigned yet. static void ins_compl_update_sequence_numbers(void) { int number = 0; compl_T *match; - if (compl_direction == FORWARD) { + if (compl_dir_forward()) { // search backwards for the first valid (!= -1) number. // This should normally succeed already at the first loop // cycle, so it's fast! for (match = compl_curr_match->cp_prev; - match != NULL && match != compl_first_match; - match = match->cp_prev) { + match != NULL && !is_first_match(match); match = match->cp_prev) { if (match->cp_number != -1) { number = match->cp_number; break; @@ -2334,8 +2552,7 @@ static void ins_compl_update_sequence_numbers(void) // number. This should normally succeed already at the // first loop cycle, so it's fast! for (match = compl_curr_match->cp_next; - match != NULL && match != compl_first_match; - match = match->cp_next) { + match != NULL && !is_first_match(match); match = match->cp_next) { if (match->cp_number != -1) { number = match->cp_number; break; @@ -2404,7 +2621,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) if (ret == OK && compl_first_match != NULL) { compl_T *match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT)) { + if (!match_at_original_text(match)) { dict_T *di = tv_dict_alloc(); tv_list_append_dict(li, di); @@ -2420,7 +2637,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) } } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } } @@ -2439,7 +2656,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) } /// "complete_info()" function -void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -2462,6 +2679,481 @@ static bool thesaurus_func_complete(int type) && (*curbuf->b_p_tsrfu != NUL || *p_tsrfu != NUL); } +/// Return value of process_next_cpt_value() +enum { + INS_COMPL_CPT_OK = 1, + INS_COMPL_CPT_CONT, + INS_COMPL_CPT_END, +}; + +/// Process the next 'complete' option value in st->e_cpt. +/// +/// If successful, the arguments are set as below: +/// st->cpt - pointer to the next option value in "st->cpt" +/// compl_type_arg - type of insert mode completion to use +/// st->found_all - all matches of this type are found +/// st->ins_buf - search for completions in this buffer +/// st->first_match_pos - position of the first completion match +/// st->last_match_pos - position of the last completion match +/// st->set_match_pos - true if the first match position should be saved to +/// avoid loops after the search wraps around. +/// st->dict - name of the dictionary or thesaurus file to search +/// st->dict_f - flag specifying whether "dict" is an exact file name or not +/// +/// @return INS_COMPL_CPT_OK if the next value is processed successfully. +/// INS_COMPL_CPT_CONT to skip the current completion source matching +/// the "st->e_cpt" option value and process the next matching source. +/// INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed. +static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_arg, + pos_T *start_match_pos) +{ + int compl_type = -1; + int status = INS_COMPL_CPT_OK; + + st->found_all = false; + + while (*st->e_cpt == ',' || *st->e_cpt == ' ') { + st->e_cpt++; + } + + if (*st->e_cpt == '.' && !curbuf->b_scanned) { + st->ins_buf = curbuf; + st->first_match_pos = *start_match_pos; + // Move the cursor back one character so that ^N can match the + // word immediately after the cursor. + if (ctrl_x_mode_normal() && dec(&st->first_match_pos) < 0) { + // Move the cursor to after the last character in the + // buffer, so that word at start of buffer is found + // correctly. + st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count; + st->first_match_pos.col = (colnr_T)STRLEN(ml_get(st->first_match_pos.lnum)); + } + st->last_match_pos = st->first_match_pos; + compl_type = 0; + + // Remember the first match so that the loop stops when we + // wrap and come back there a second time. + st->set_match_pos = true; + } else if (vim_strchr("buwU", *st->e_cpt) != NULL + && (st->ins_buf = ins_compl_next_buf(st->ins_buf, *st->e_cpt)) != curbuf) { + // Scan a buffer, but not the current one. + if (st->ins_buf->b_ml.ml_mfp != NULL) { // loaded buffer + compl_started = true; + st->first_match_pos.col = st->last_match_pos.col = 0; + st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count + 1; + st->last_match_pos.lnum = 0; + compl_type = 0; + } else { // unloaded buffer, scan like dictionary + st->found_all = true; + if (st->ins_buf->b_fname == NULL) { + status = INS_COMPL_CPT_CONT; + goto done; + } + compl_type = CTRL_X_DICTIONARY; + st->dict = (char_u *)st->ins_buf->b_fname; + st->dict_f = DICT_EXACT; + } + msg_hist_off = true; // reset in msg_trunc_attr() + vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), + st->ins_buf->b_fname == NULL + ? buf_spname(st->ins_buf) + : st->ins_buf->b_sfname == NULL + ? st->ins_buf->b_fname + : st->ins_buf->b_sfname); + (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + } else if (*st->e_cpt == NUL) { + status = INS_COMPL_CPT_END; + } else { + if (ctrl_x_mode_line_or_eval()) { + compl_type = -1; + } else if (*st->e_cpt == 'k' || *st->e_cpt == 's') { + if (*st->e_cpt == 'k') { + compl_type = CTRL_X_DICTIONARY; + } else { + compl_type = CTRL_X_THESAURUS; + } + if (*++st->e_cpt != ',' && *st->e_cpt != NUL) { + st->dict = (char_u *)st->e_cpt; + st->dict_f = DICT_FIRST; + } + } else if (*st->e_cpt == 'i') { + compl_type = CTRL_X_PATH_PATTERNS; + } else if (*st->e_cpt == 'd') { + compl_type = CTRL_X_PATH_DEFINES; + } else if (*st->e_cpt == ']' || *st->e_cpt == 't') { + msg_hist_off = true; // reset in msg_trunc_attr() + compl_type = CTRL_X_TAGS; + vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); + (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + } else { + compl_type = -1; + } + + // in any case e_cpt is advanced to the next entry + (void)copy_option_part(&st->e_cpt, (char *)IObuff, IOSIZE, ","); + + st->found_all = true; + if (compl_type == -1) { + status = INS_COMPL_CPT_CONT; + } + } + +done: + *compl_type_arg = compl_type; + return status; +} + +/// Get the next set of identifiers or defines matching "compl_pattern" in +/// included files. +static void get_next_include_file_completion(int compl_type) +{ + find_pattern_in_path((char_u *)compl_pattern, compl_direction, + STRLEN(compl_pattern), false, false, + ((compl_type == CTRL_X_PATH_DEFINES + && !(compl_cont_status & CONT_SOL)) + ? FIND_DEFINE : FIND_ANY), + 1L, ACTION_EXPAND, 1, MAXLNUM); +} + +/// Get the next set of words matching "compl_pattern" in dictionary or +/// thesaurus files. +static void get_next_dict_tsr_completion(int compl_type, char_u *dict, int dict_f) +{ + if (thesaurus_func_complete(compl_type)) { + expand_by_function(compl_type, (char_u *)compl_pattern); + } else { + ins_compl_dictionaries(dict != NULL ? dict + : (compl_type == CTRL_X_THESAURUS + ? (*curbuf->b_p_tsr == NUL ? p_tsr : (char_u *)curbuf->b_p_tsr) + : (*curbuf->b_p_dict == + NUL ? (char_u *)p_dict : (char_u *)curbuf->b_p_dict)), + (char_u *)compl_pattern, + dict != NULL ? dict_f : 0, + compl_type == CTRL_X_THESAURUS); + } +} + +/// Get the next set of tag names matching "compl_pattern". +static void get_next_tag_completion(void) +{ + // set p_ic according to p_ic, p_scs and pat for find_tags(). + const int save_p_ic = p_ic; + p_ic = ignorecase((char_u *)compl_pattern); + + // Find up to TAG_MANY matches. Avoids that an enormous number + // of matches is found when compl_pattern is empty + g_tag_at_cursor = true; + char **matches; + int num_matches; + if (find_tags(compl_pattern, &num_matches, &matches, + TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP + | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), + TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } + g_tag_at_cursor = false; + p_ic = save_p_ic; +} + +/// Get the next set of filename matching "compl_pattern". +static void get_next_filename_completion(void) +{ + char **matches; + int num_matches; + if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) != OK) { + return; + } + + // May change home directory back to "~". + tilde_replace((char_u *)compl_pattern, num_matches, matches); +#ifdef BACKSLASH_IN_FILENAME + if (curbuf->b_p_csl[0] != NUL) { + for (int i = 0; i < num_matches; i++) { + char_u *ptr = matches[i]; + while (*ptr != NUL) { + if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); +} + +/// Get the next set of command-line completions matching "compl_pattern". +static void get_next_cmdline_completion(void) +{ + char **matches; + int num_matches; + if (expand_cmdline(&compl_xp, (char_u *)compl_pattern, + (int)STRLEN(compl_pattern), + &num_matches, &matches) == EXPAND_OK) { + ins_compl_add_matches(num_matches, matches, false); + } +} + +/// Get the next set of spell suggestions matching "compl_pattern". +static void get_next_spell_completion(linenr_T lnum) +{ + char **matches; + int num_matches = expand_spelling(lnum, (char_u *)compl_pattern, &matches); + if (num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } else { + xfree(matches); + } +} + +/// Return the next word or line from buffer "ins_buf" at position +/// "cur_match_pos" for completion. The length of the match is set in "len". +/// @param ins_buf buffer being scanned +/// @param cur_match_pos current match position +/// @param match_len +/// @param cont_s_ipos next ^X<> will set initial_pos +static char_u *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len, + bool *cont_s_ipos) +{ + *match_len = 0; + char_u *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum, false) + cur_match_pos->col; + int len; + if (ctrl_x_mode_line_or_eval()) { + if (compl_status_adding()) { + if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) { + return NULL; + } + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + if (!p_paste) { + ptr = (char_u *)skipwhite((char *)ptr); + } + } + len = (int)STRLEN(ptr); + } else { + char_u *tmp_ptr = ptr; + + if (compl_status_adding() && compl_length <= (int)STRLEN(tmp_ptr)) { + tmp_ptr += compl_length; + // Skip if already inside a word. + if (vim_iswordp(tmp_ptr)) { + return NULL; + } + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + } + // Find end of this word. + tmp_ptr = find_word_end(tmp_ptr); + len = (int)(tmp_ptr - ptr); + + if (compl_status_adding() && len == compl_length) { + if (cur_match_pos->lnum < ins_buf->b_ml.ml_line_count) { + // Try next line, if any. the new word will be "join" as if the + // normal command "J" was used. IOSIZE is always greater than + // compl_length, so the next STRNCPY always works -- Acevedo + STRNCPY(IObuff, ptr, len); // NOLINT(runtime/printf) + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + tmp_ptr = ptr = (char_u *)skipwhite((char *)ptr); + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + // Find end of next word. + tmp_ptr = find_word_end(tmp_ptr); + if (tmp_ptr > ptr) { + if (*ptr != ')' && IObuff[len - 1] != TAB) { + if (IObuff[len - 1] != ' ') { + IObuff[len++] = ' '; + } + // IObuf =~ "\k.* ", thus len >= 2 + if (p_js + && (IObuff[len - 2] == '.' + || IObuff[len - 2] == '?' + || IObuff[len - 2] == '!')) { + IObuff[len++] = ' '; + } + } + // copy as much as possible of the new word + if (tmp_ptr - ptr >= IOSIZE - len) { + tmp_ptr = ptr + IOSIZE - len - 1; + } + STRLCPY(IObuff + len, ptr, IOSIZE - len); + len += (int)(tmp_ptr - ptr); + *cont_s_ipos = true; + } + IObuff[len] = NUL; + ptr = IObuff; + } + if (len == compl_length) { + return NULL; + } + } + } + + *match_len = len; + return ptr; +} + +/// Get the next set of words matching "compl_pattern" for default completion(s) +/// (normal ^P/^N and ^X^L). +/// Search for "compl_pattern" in the buffer "st->ins_buf" starting from the +/// position "st->start_pos" in the "compl_direction" direction. If +/// "st->set_match_pos" is true, then set the "st->first_match_pos" and +/// "st->last_match_pos". +/// +/// @return OK if a new next match is found, otherwise FAIL. +static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) +{ + // If 'infercase' is set, don't use 'smartcase' here + const int save_p_scs = p_scs; + assert(st->ins_buf); + if (st->ins_buf->b_p_inf) { + p_scs = false; + } + + // Buffers other than curbuf are scanned from the beginning or the + // end but never from the middle, thus setting nowrapscan in this + // buffers is a good idea, on the other hand, we always set + // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb + const int save_p_ws = p_ws; + if (st->ins_buf != curbuf) { + p_ws = false; + } else if (*st->e_cpt == '.') { + p_ws = true; + } + bool looped_around = false; + int found_new_match = FAIL; + for (;;) { + bool cont_s_ipos = false; + + msg_silent++; // Don't want messages for wrapscan. + // ctrl_x_mode_line_or_eval() || word-wise search that + // has added a word that was at the beginning of the line. + if (ctrl_x_mode_line_or_eval() || (compl_cont_status & CONT_SOL)) { + found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, + compl_direction, (char_u *)compl_pattern); + } else { + found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, + NULL, compl_direction, (char_u *)compl_pattern, 1L, + SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); + } + msg_silent--; + if (!compl_started || st->set_match_pos) { + // set "compl_started" even on fail + compl_started = true; + st->first_match_pos = *st->cur_match_pos; + st->last_match_pos = *st->cur_match_pos; + st->set_match_pos = false; + } else if (st->first_match_pos.lnum == st->last_match_pos.lnum + && st->first_match_pos.col == st->last_match_pos.col) { + found_new_match = FAIL; + } else if (compl_dir_forward() + && (st->prev_match_pos.lnum > st->cur_match_pos->lnum + || (st->prev_match_pos.lnum == st->cur_match_pos->lnum + && st->prev_match_pos.col >= st->cur_match_pos->col))) { + if (looped_around) { + found_new_match = FAIL; + } else { + looped_around = true; + } + } else if (!compl_dir_forward() + && (st->prev_match_pos.lnum < st->cur_match_pos->lnum + || (st->prev_match_pos.lnum == st->cur_match_pos->lnum + && st->prev_match_pos.col <= st->cur_match_pos->col))) { + if (looped_around) { + found_new_match = FAIL; + } else { + looped_around = true; + } + } + st->prev_match_pos = *st->cur_match_pos; + if (found_new_match == FAIL) { + break; + } + + // when ADDING, the text before the cursor matches, skip it + if (compl_status_adding() && st->ins_buf == curbuf + && start_pos->lnum == st->cur_match_pos->lnum + && start_pos->col == st->cur_match_pos->col) { + continue; + } + int len; + char_u *ptr = ins_comp_get_next_word_or_line(st->ins_buf, st->cur_match_pos, + &len, &cont_s_ipos); + if (ptr == NULL) { + continue; + } + if (ins_compl_add_infercase(ptr, len, p_ic, + st->ins_buf == curbuf ? NULL : (char_u *)st->ins_buf->b_sfname, + 0, cont_s_ipos) != NOTDONE) { + found_new_match = OK; + break; + } + } + p_scs = save_p_scs; + p_ws = save_p_ws; + + return found_new_match; +} + +/// get the next set of completion matches for "type". +/// @return true if a new match is found, otherwise false. +static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini) +{ + int found_new_match = FAIL; + + switch (type) { + case -1: + break; + case CTRL_X_PATH_PATTERNS: + case CTRL_X_PATH_DEFINES: + get_next_include_file_completion(type); + break; + + case CTRL_X_DICTIONARY: + case CTRL_X_THESAURUS: + get_next_dict_tsr_completion(type, st->dict, st->dict_f); + st->dict = NULL; + break; + + case CTRL_X_TAGS: + get_next_tag_completion(); + break; + + case CTRL_X_FILES: + get_next_filename_completion(); + break; + + case CTRL_X_CMDLINE: + case CTRL_X_CMDLINE_CTRL_X: + get_next_cmdline_completion(); + break; + + case CTRL_X_FUNCTION: + case CTRL_X_OMNI: + expand_by_function(type, (char_u *)compl_pattern); + break; + + case CTRL_X_SPELL: + get_next_spell_completion(st->first_match_pos.lnum); + break; + + default: // normal ^P/^N and ^X^L + found_new_match = get_next_default_completion(st, ini); + if (found_new_match == FAIL && st->ins_buf == curbuf) { + st->found_all = true; + } + } + + // check if compl_curr_match has changed, (e.g. other type of + // expansion added something) + if (type != 0 && compl_curr_match != compl_old_match) { + found_new_match = OK; + } + + return found_new_match; +} + /// Get the next expansion(s), using "compl_pattern". /// The search starts at position "ini" in curbuf and in the direction /// compl_direction. @@ -2471,28 +3163,10 @@ static bool thesaurus_func_complete(int type) /// Return the total number of matches or -1 if still unknown -- Acevedo 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 bool found_all = false; // Found all matches of a - // certain type. - static buf_T *ins_buf = NULL; // buffer being scanned - - pos_T *pos; - char **matches; - int save_p_scs; - bool save_p_ws; - int save_p_ic; + static ins_compl_next_state_T st; int i; - int num_matches; - int len; int found_new_match; int type = ctrl_x_mode; - char_u *ptr; - char_u *dict = NULL; - int dict_f = 0; - bool set_match_pos; - pos_T prev_pos = { 0, 0, 0 }; assert(curbuf != NULL); @@ -2500,112 +3174,34 @@ static int ins_compl_get_exp(pos_T *ini) FOR_ALL_BUFFERS(buf) { buf->b_scanned = false; } - found_all = false; - ins_buf = curbuf; - e_cpt = (compl_cont_status & CONT_LOCAL) - ? (char_u *)"." : 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. + st.found_all = false; + st.ins_buf = curbuf; + st.e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt; + st.last_match_pos = st.first_match_pos = *ini; + } else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) { + st.ins_buf = curbuf; // In case the buffer was wiped out. } - assert(ins_buf != NULL); + assert(st.ins_buf != NULL); compl_old_match = compl_curr_match; // remember the last current match - pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos; + st.cur_match_pos = (compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos); // For ^N/^P loop over all the flags/windows/buffers in 'complete' for (;;) { found_new_match = FAIL; - set_match_pos = false; + st.set_match_pos = false; // For ^N/^P pick a new entry from e_cpt if compl_started is off, // or if found_all says this entry is done. For ^X^L only use the // entries from 'complete' that look in loaded buffers. if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) - && (!compl_started || found_all)) { - found_all = false; - while (*e_cpt == ',' || *e_cpt == ' ') { - e_cpt++; - } - if (*e_cpt == '.' && !curbuf->b_scanned) { - ins_buf = curbuf; - first_match_pos = *ini; - // Move the cursor back one character so that ^N can match the - // word immediately after the cursor. - if (ctrl_x_mode_normal() && dec(&first_match_pos) < 0) { - // Move the cursor to after the last character in the - // buffer, so that word at start of buffer is found - // correctly. - first_match_pos.lnum = ins_buf->b_ml.ml_line_count; - first_match_pos.col = (colnr_T)STRLEN(ml_get(first_match_pos.lnum)); - } - last_match_pos = first_match_pos; - type = 0; - - // Remember the first match so that the loop stops when we - // wrap and come back there a second time. - set_match_pos = true; - } else if (vim_strchr("buwU", *e_cpt) != NULL - && (ins_buf = ins_compl_next_buf(ins_buf, *e_cpt)) != curbuf) { - // Scan a buffer, but not the current one. - if (ins_buf->b_ml.ml_mfp != NULL) { // loaded buffer - compl_started = true; - first_match_pos.col = last_match_pos.col = 0; - first_match_pos.lnum = ins_buf->b_ml.ml_line_count + 1; - last_match_pos.lnum = 0; - type = 0; - } else { // unloaded buffer, scan like dictionary - found_all = true; - if (ins_buf->b_fname == NULL) { - continue; - } - type = CTRL_X_DICTIONARY; - dict = (char_u *)ins_buf->b_fname; - dict_f = DICT_EXACT; - } - msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), - ins_buf->b_fname == NULL - ? buf_spname(ins_buf) - : ins_buf->b_sfname == NULL - ? ins_buf->b_fname - : ins_buf->b_sfname); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); - } else if (*e_cpt == NUL) { + && (!compl_started || st.found_all)) { + int status = process_next_cpt_value(&st, &type, ini); + if (status == INS_COMPL_CPT_END) { break; - } else { - if (ctrl_x_mode_line_or_eval()) { - type = -1; - } else if (*e_cpt == 'k' || *e_cpt == 's') { - if (*e_cpt == 'k') { - type = CTRL_X_DICTIONARY; - } else { - type = CTRL_X_THESAURUS; - } - if (*++e_cpt != ',' && *e_cpt != NUL) { - dict = e_cpt; - dict_f = DICT_FIRST; - } - } else if (*e_cpt == 'i') { - type = CTRL_X_PATH_PATTERNS; - } else if (*e_cpt == 'd') { - type = CTRL_X_PATH_DEFINES; - } else if (*e_cpt == ']' || *e_cpt == 't') { - msg_hist_off = true; // reset in msg_trunc_attr() - type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); - } else { - type = -1; - } - - // in any case e_cpt is advanced to the next entry - (void)copy_option_part((char **)&e_cpt, (char *)IObuff, IOSIZE, ","); - - found_all = true; - if (type == -1) { - continue; - } + } + if (status == INS_COMPL_CPT_CONT) { + continue; } } @@ -2615,271 +3211,12 @@ static int ins_compl_get_exp(pos_T *ini) break; } - switch (type) { - case -1: - break; - case CTRL_X_PATH_PATTERNS: - case CTRL_X_PATH_DEFINES: - find_pattern_in_path((char_u *)compl_pattern, compl_direction, - STRLEN(compl_pattern), false, false, - ((type == CTRL_X_PATH_DEFINES - && !(compl_cont_status & CONT_SOL)) - ? FIND_DEFINE - : FIND_ANY), - 1L, ACTION_EXPAND, 1, MAXLNUM); - break; - - case CTRL_X_DICTIONARY: - case CTRL_X_THESAURUS: - if (thesaurus_func_complete(type)) { - expand_by_function(type, (char_u *)compl_pattern); - } else { - ins_compl_dictionaries(dict != NULL ? dict - : (type == CTRL_X_THESAURUS - ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) - : (*curbuf->b_p_dict == - NUL ? p_dict : curbuf->b_p_dict)), - (char_u *)compl_pattern, - dict != NULL ? dict_f : 0, type == CTRL_X_THESAURUS); - } - dict = NULL; - break; - - case CTRL_X_TAGS: - // set p_ic according to p_ic, p_scs and pat for find_tags(). - save_p_ic = p_ic; - p_ic = ignorecase((char_u *)compl_pattern); - - // Find up to TAG_MANY matches. Avoids that an enormous number - // of matches is found when compl_pattern is empty - g_tag_at_cursor = true; - if (find_tags((char_u *)compl_pattern, &num_matches, &matches, - TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP - | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), - TAG_MANY, (char_u *)curbuf->b_ffname) == OK && num_matches > 0) { - ins_compl_add_matches(num_matches, matches, p_ic); - } - g_tag_at_cursor = false; - p_ic = save_p_ic; - break; - - case CTRL_X_FILES: - if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, - EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) { - // May change home directory back to "~". - tilde_replace((char_u *)compl_pattern, num_matches, matches); -#ifdef BACKSLASH_IN_FILENAME - if (curbuf->b_p_csl[0] != NUL) { - for (int i = 0; i < num_matches; i++) { - char_u *ptr = matches[i]; - while (*ptr != NUL) { - if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { - *ptr = '/'; - } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { - *ptr = '\\'; - } - ptr += utfc_ptr2len(ptr); - } - } - } -#endif - ins_compl_add_matches(num_matches, matches, p_fic || p_wic); - } - break; - - case CTRL_X_CMDLINE: - case CTRL_X_CMDLINE_CTRL_X: - if (expand_cmdline(&compl_xp, (char_u *)compl_pattern, - (int)STRLEN(compl_pattern), - &num_matches, &matches) == EXPAND_OK) { - ins_compl_add_matches(num_matches, matches, false); - } - break; - - case CTRL_X_FUNCTION: - case CTRL_X_OMNI: - expand_by_function(type, (char_u *)compl_pattern); - break; - - case CTRL_X_SPELL: - num_matches = expand_spelling(first_match_pos.lnum, - (char_u *)compl_pattern, &matches); - if (num_matches > 0) { - ins_compl_add_matches(num_matches, matches, p_ic); - } - break; - - default: // normal ^P/^N and ^X^L - // If 'infercase' is set, don't use 'smartcase' here - save_p_scs = p_scs; - assert(ins_buf); - if (ins_buf->b_p_inf) { - p_scs = false; - } - - // Buffers other than curbuf are scanned from the beginning or the - // end but never from the middle, thus setting nowrapscan in this - // buffers is a good idea, on the other hand, we always set - // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb - save_p_ws = p_ws; - if (ins_buf != curbuf) { - p_ws = false; - } else if (*e_cpt == '.') { - p_ws = true; - } - bool looped_around = false; - for (;;) { - bool cont_s_ipos = false; - - msg_silent++; // Don't want messages for wrapscan. - // ctrl_x_mode_line_or_eval() || word-wise search that - // has added a word that was at the beginning of the line. - if (ctrl_x_mode_line_or_eval() - || (compl_cont_status & CONT_SOL)) { - found_new_match = search_for_exact_line(ins_buf, pos, - compl_direction, - (char_u *)compl_pattern); - } else { - found_new_match = searchit(NULL, ins_buf, pos, NULL, - compl_direction, - (char_u *)compl_pattern, 1L, - SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, NULL); - } - msg_silent--; - if (!compl_started || set_match_pos) { - // set "compl_started" even on fail - compl_started = true; - first_match_pos = *pos; - last_match_pos = *pos; - set_match_pos = false; - } else if (first_match_pos.lnum == last_match_pos.lnum - && first_match_pos.col == last_match_pos.col) { - found_new_match = FAIL; - } else if ((compl_direction == FORWARD) - && (prev_pos.lnum > pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col >= pos->col))) { - if (looped_around) { - found_new_match = FAIL; - } else { - looped_around = true; - } - } else if ((compl_direction != FORWARD) - && (prev_pos.lnum < pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col <= pos->col))) { - if (looped_around) { - found_new_match = FAIL; - } else { - looped_around = true; - } - } - prev_pos = *pos; - if (found_new_match == FAIL) { - if (ins_buf == curbuf) { - found_all = true; - } - break; - } - - // when ADDING, the text before the cursor matches, skip it - if ((compl_cont_status & CONT_ADDING) && ins_buf == curbuf - && ini->lnum == pos->lnum - && ini->col == pos->col) { - continue; - } - ptr = ml_get_buf(ins_buf, pos->lnum, false) + pos->col; - if (ctrl_x_mode_line_or_eval()) { - if (compl_cont_status & CONT_ADDING) { - if (pos->lnum >= ins_buf->b_ml.ml_line_count) { - continue; - } - ptr = ml_get_buf(ins_buf, pos->lnum + 1, false); - if (!p_paste) { - ptr = (char_u *)skipwhite((char *)ptr); - } - } - len = (int)STRLEN(ptr); - } else { - char_u *tmp_ptr = ptr; - - if (compl_cont_status & CONT_ADDING) { - tmp_ptr += compl_length; - // Skip if already inside a word. - if (vim_iswordp(tmp_ptr)) { - continue; - } - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - } - // Find end of this word. - tmp_ptr = find_word_end(tmp_ptr); - len = (int)(tmp_ptr - ptr); - - if ((compl_cont_status & CONT_ADDING) - && len == compl_length) { - if (pos->lnum < ins_buf->b_ml.ml_line_count) { - // Try next line, if any. the new word will be "join" as if the - // normal command "J" was used. IOSIZE is always greater than - // compl_length, so the next STRNCPY always works -- Acevedo - STRNCPY(IObuff, ptr, len); // NOLINT(runtime/printf) - ptr = ml_get_buf(ins_buf, pos->lnum + 1, false); - tmp_ptr = ptr = (char_u *)skipwhite((char *)ptr); - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - // Find end of next word. - tmp_ptr = find_word_end(tmp_ptr); - if (tmp_ptr > ptr) { - if (*ptr != ')' && IObuff[len - 1] != TAB) { - if (IObuff[len - 1] != ' ') { - IObuff[len++] = ' '; - } - // IObuf =~ "\k.* ", thus len >= 2 - if (p_js - && (IObuff[len - 2] == '.' - || IObuff[len - 2] == '?' - || IObuff[len - 2] == '!')) { - IObuff[len++] = ' '; - } - } - // copy as much as possible of the new word - if (tmp_ptr - ptr >= IOSIZE - len) { - tmp_ptr = ptr + IOSIZE - len - 1; - } - STRLCPY(IObuff + len, ptr, IOSIZE - len); - len += (int)(tmp_ptr - ptr); - cont_s_ipos = true; - } - IObuff[len] = NUL; - ptr = IObuff; - } - if (len == compl_length) { - continue; - } - } - } - if (ins_compl_add_infercase(ptr, len, p_ic, - ins_buf == curbuf ? NULL : (char_u *)ins_buf->b_sfname, - 0, cont_s_ipos) != NOTDONE) { - found_new_match = OK; - break; - } - } - p_scs = save_p_scs; - p_ws = save_p_ws; - } - - // check if compl_curr_match has changed, (e.g. other type of - // expansion added something) - if (type != 0 && compl_curr_match != compl_old_match) { - found_new_match = OK; - } + // get the next set of completion matches + found_new_match = get_next_completion_match(type, &st, ini); // break the loop for specialized modes (use 'complete' just for the // generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new match - if ((ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval()) + if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval()) || found_new_match != FAIL) { if (got_int) { break; @@ -2889,8 +3226,7 @@ static int ins_compl_get_exp(pos_T *ini) ins_compl_check_keys(0, false); } - if ((ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval()) + if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval()) || compl_interrupted) { break; } @@ -2898,8 +3234,8 @@ static int ins_compl_get_exp(pos_T *ini) } else { // Mark a buffer scanned when it has been scanned completely if (type == 0 || type == CTRL_X_PATH_PATTERNS) { - assert(ins_buf); - ins_buf->b_scanned = true; + assert(st.ins_buf); + st.ins_buf->b_scanned = true; } compl_started = false; @@ -2907,16 +3243,14 @@ static int ins_compl_get_exp(pos_T *ini) } compl_started = true; - if ((ctrl_x_mode_normal() - || ctrl_x_mode_line_or_eval()) - && *e_cpt == NUL) { // Got to end of 'complete' + if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) + && *st.e_cpt == NUL) { // Got to end of 'complete' found_new_match = FAIL; } i = -1; // total of matches, unknown if (found_new_match == FAIL - || (ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval())) { + || (ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())) { i = ins_compl_make_cyclic(); } @@ -2924,7 +3258,7 @@ static int ins_compl_get_exp(pos_T *ini) // If several matches were added (FORWARD) or the search failed and has // just been made cyclic then we have to move compl_curr_match to the // next or previous entry (if any) -- Acevedo - compl_curr_match = compl_direction == FORWARD + compl_curr_match = compl_dir_forward() ? compl_old_match->cp_next : compl_old_match->cp_prev; if (compl_curr_match == NULL) { @@ -2936,6 +3270,31 @@ static int ins_compl_get_exp(pos_T *ini) return i; } +/// Update "compl_shown_match" to the actually shown match, it may differ when +/// "compl_leader" is used to omit some of the matches. +static void ins_compl_update_shown_match(void) +{ + while (!ins_compl_equal(compl_shown_match, + compl_leader, STRLEN(compl_leader)) + && compl_shown_match->cp_next != NULL + && !is_first_match(compl_shown_match->cp_next)) { + compl_shown_match = compl_shown_match->cp_next; + } + + // If we didn't find it searching forward, and compl_shows_dir is + // backward, find the last match. + if (compl_shows_dir_backward() + && !ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) + && (compl_shown_match->cp_next == NULL + || is_first_match(compl_shown_match->cp_next))) { + while (!ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) + && compl_shown_match->cp_prev != NULL + && !is_first_match(compl_shown_match->cp_prev)) { + compl_shown_match = compl_shown_match->cp_prev; + } + } +} + /// Delete the old text being completed. void ins_compl_delete(void) { @@ -2943,7 +3302,7 @@ void ins_compl_delete(void) // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. - col = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); + col = compl_col + (compl_status_adding() ? compl_length : 0); if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { return; @@ -2962,8 +3321,8 @@ void ins_compl_delete(void) /// "in_compl_func" is true when called from complete_check(). void ins_compl_insert(bool in_compl_func) { - ins_bytes(compl_shown_match->cp_str + get_compl_len()); - compl_used_match = !(compl_shown_match->cp_flags & CP_ORIGINAL_TEXT); + ins_bytes((char *)compl_shown_match->cp_str + get_compl_len()); + compl_used_match = !match_at_original_text(compl_shown_match); dict_T *dict = ins_compl_dict_alloc(compl_shown_match); set_vim_var_dict(VV_COMPLETED_ITEM, dict); @@ -2972,97 +3331,69 @@ void ins_compl_insert(bool in_compl_func) } } -/// Fill in the next completion in the current direction. -/// If "allow_get_expansion" is true, then we may call ins_compl_get_exp() to -/// get more completions. If it is false, then we just do nothing when there -/// are no more completions in a given direction. The latter case is used when -/// we are still in the middle of finding completions, to allow browsing -/// through the ones found so far. -/// @return the total number of matches, or -1 if still unknown -- webb. -/// -/// compl_curr_match is currently being used by ins_compl_get_exp(), so we use -/// compl_shown_match here. -/// -/// Note that this function may be called recursively once only. First with -/// "allow_get_expansion" true, which calls ins_compl_get_exp(), which in turn -/// calls this function with "allow_get_expansion" false. -/// -/// @param count Repeat completion this many times; should be at least 1 -/// @param insert_match Insert the newly selected match -/// @param in_compl_func Called from complete_check() -static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match, - bool in_compl_func) +/// show the file name for the completion match (if any). Truncate the file +/// name to avoid a wait for return. +static void ins_compl_show_filename(void) { - int num_matches = -1; - int todo = count; - compl_T *found_compl = NULL; - bool found_end = false; - const bool started = compl_started; - - // When user complete function return -1 for findstart which is next - // time of 'always', compl_shown_match become NULL. - if (compl_shown_match == NULL) { - return -1; + char *const lead = _("match in file"); + int space = sc_col - vim_strsize(lead) - 2; + if (space <= 0) { + return; } - if (compl_leader != NULL - && (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0) { - // Set "compl_shown_match" to the actually shown match, it may differ - // when "compl_leader" is used to omit some of the matches. - while (!ins_compl_equal(compl_shown_match, - compl_leader, STRLEN(compl_leader)) - && compl_shown_match->cp_next != NULL - && compl_shown_match->cp_next != compl_first_match) { - compl_shown_match = compl_shown_match->cp_next; - } - - // If we didn't find it searching forward, and compl_shows_dir is - // backward, find the last match. - if (compl_shows_dir == BACKWARD - && !ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) - && (compl_shown_match->cp_next == NULL - || compl_shown_match->cp_next == compl_first_match)) { - while (!ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) - && compl_shown_match->cp_prev != NULL - && compl_shown_match->cp_prev != compl_first_match) { - compl_shown_match = compl_shown_match->cp_prev; - } + // We need the tail that fits. With double-byte encoding going + // back from the end is very slow, thus go from the start and keep + // the text that fits in "space" between "s" and "e". + char *s; + char *e; + for (s = e = (char *)compl_shown_match->cp_fname; *e != NUL; MB_PTR_ADV(e)) { + space -= ptr2cells(e); + while (space < 0) { + space += ptr2cells(s); + MB_PTR_ADV(s); } } + msg_hist_off = true; + vim_snprintf((char *)IObuff, IOSIZE, "%s %s%s", lead, + (char_u *)s > compl_shown_match->cp_fname ? "<" : "", s); + msg((char *)IObuff); + msg_hist_off = false; + redraw_cmdline = false; // don't overwrite! +} - if (allow_get_expansion && insert_match - && (!(compl_get_longest || compl_restarting) || compl_used_match)) { - // Delete old text to be replaced - ins_compl_delete(); - } - - // When finding the longest common text we stick at the original text, - // don't let CTRL-N or CTRL-P move to the first match. - bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; - - // When restarting the search don't insert the first match either. - if (compl_restarting) { - advance = false; - compl_restarting = false; - } +/// Find the next set of matches for completion. Repeat the completion "todo" +/// times. The number of matches found is returned in 'num_matches'. +/// +/// @param allow_get_expansion If true, then ins_compl_get_exp() may be called to +/// get more completions. +/// If false, then do nothing when there are no more +/// completions in the given direction. +/// @param todo repeat completion this many times +/// @param advance If true, then completion will move to the first match. +/// Otherwise, the original text will be shown. +/// +/// @return OK on success and -1 if the number of matches are unknown. +static int find_next_completion_match(bool allow_get_expansion, int todo, bool advance, + int *num_matches) +{ + bool found_end = false; + compl_T *found_compl = NULL; - // Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap - // around. while (--todo >= 0) { - if (compl_shows_dir == FORWARD && compl_shown_match->cp_next != NULL) { + if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { compl_shown_match = compl_shown_match->cp_next; found_end = (compl_first_match != NULL - && (compl_shown_match->cp_next == compl_first_match - || compl_shown_match == compl_first_match)); - } else if (compl_shows_dir == BACKWARD + && (is_first_match(compl_shown_match->cp_next) + || is_first_match(compl_shown_match))); + } else if (compl_shows_dir_backward() && compl_shown_match->cp_prev != NULL) { - found_end = (compl_shown_match == compl_first_match); + found_end = is_first_match(compl_shown_match); compl_shown_match = compl_shown_match->cp_prev; - found_end |= (compl_shown_match == compl_first_match); + found_end |= is_first_match(compl_shown_match); } else { if (!allow_get_expansion) { if (advance) { - if (compl_shows_dir == BACKWARD) { + if (compl_shows_dir_backward()) { compl_pending -= todo + 1; } else { compl_pending += todo + 1; @@ -3072,7 +3403,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } if (!compl_no_select && advance) { - if (compl_shows_dir == BACKWARD) { + if (compl_shows_dir_backward()) { compl_pending--; } else { compl_pending++; @@ -3080,7 +3411,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Find matches. - num_matches = ins_compl_get_exp(&compl_startpos); + *num_matches = ins_compl_get_exp(&compl_startpos); // handle any pending completions while (compl_pending != 0 && compl_direction == compl_shows_dir @@ -3098,7 +3429,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } found_end = false; } - if ((compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0 + if (!match_at_original_text(compl_shown_match) && compl_leader != NULL && !ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader))) { @@ -3118,15 +3449,77 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } } + return OK; +} + +/// Fill in the next completion in the current direction. +/// If "allow_get_expansion" is true, then we may call ins_compl_get_exp() to +/// get more completions. If it is false, then we just do nothing when there +/// are no more completions in a given direction. The latter case is used when +/// we are still in the middle of finding completions, to allow browsing +/// through the ones found so far. +/// @return the total number of matches, or -1 if still unknown -- webb. +/// +/// compl_curr_match is currently being used by ins_compl_get_exp(), so we use +/// compl_shown_match here. +/// +/// Note that this function may be called recursively once only. First with +/// "allow_get_expansion" true, which calls ins_compl_get_exp(), which in turn +/// calls this function with "allow_get_expansion" false. +/// +/// @param count Repeat completion this many times; should be at least 1 +/// @param insert_match Insert the newly selected match +/// @param in_compl_func Called from complete_check() +static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match, + bool in_compl_func) +{ + int num_matches = -1; + int todo = count; + const bool started = compl_started; + + // When user complete function return -1 for findstart which is next + // time of 'always', compl_shown_match become NULL. + if (compl_shown_match == NULL) { + return -1; + } + + if (compl_leader != NULL && !match_at_original_text(compl_shown_match)) { + // Update "compl_shown_match" to the actually shown match + ins_compl_update_shown_match(); + } + + if (allow_get_expansion && insert_match + && (!(compl_get_longest || compl_restarting) || compl_used_match)) { + // Delete old text to be replaced + ins_compl_delete(); + } + + // When finding the longest common text we stick at the original text, + // don't let CTRL-N or CTRL-P move to the first match. + bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; + + // When restarting the search don't insert the first match either. + if (compl_restarting) { + advance = false; + compl_restarting = false; + } + + // Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap + // around. + if (find_next_completion_match(allow_get_expansion, todo, advance, + &num_matches) == -1) { + return -1; + } + // Insert the text of the new completion, or the compl_leader. if (compl_no_insert && !started) { - ins_bytes(compl_orig_text + get_compl_len()); + ins_bytes((char *)compl_orig_text + get_compl_len()); compl_used_match = false; } else if (insert_match) { if (!compl_get_longest || compl_used_match) { ins_compl_insert(in_compl_func); } else { - ins_bytes(compl_leader + get_compl_len()); + ins_bytes((char *)compl_leader + get_compl_len()); } } else { compl_used_match = false; @@ -3153,31 +3546,8 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Show the file name for the match (if any) - // Truncate the file name to avoid a wait for return. if (compl_shown_match->cp_fname != NULL) { - char *lead = _("match in file"); - int space = sc_col - vim_strsize(lead) - 2; - char *s; - char *e; - - if (space > 0) { - // We need the tail that fits. With double-byte encoding going - // back from the end is very slow, thus go from the start and keep - // the text that fits in "space" between "s" and "e". - for (s = e = (char *)compl_shown_match->cp_fname; *e != NUL; MB_PTR_ADV(e)) { - space -= ptr2cells(e); - while (space < 0) { - space += ptr2cells(s); - MB_PTR_ADV(s); - } - } - msg_hist_off = true; - vim_snprintf((char *)IObuff, IOSIZE, "%s %s%s", lead, - (char_u *)s > compl_shown_match->cp_fname ? "<" : "", s); - msg((char *)IObuff); - msg_hist_off = false; - redraw_cmdline = false; // don't overwrite! - } + ins_compl_show_filename(); } return num_matches; @@ -3330,7 +3700,7 @@ static bool ins_compl_use_match(int c) static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) { if ((compl_cont_status & CONT_SOL) || ctrl_x_mode_path_defines()) { - if (!(compl_cont_status & CONT_ADDING)) { + if (!compl_status_adding()) { while (--startcol >= 0 && vim_isIDc(line[startcol])) {} compl_col += ++startcol; compl_length = curs_col - startcol; @@ -3340,7 +3710,7 @@ static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) } else { compl_pattern = (char *)vim_strnsave(line + compl_col, (size_t)compl_length); } - } else if (compl_cont_status & CONT_ADDING) { + } else if (compl_status_adding()) { char_u *prefix = (char_u *)"\\<"; // we need up to 2 extra chars for the prefix @@ -3397,7 +3767,7 @@ static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) /// Sets the global variables: compl_col, compl_length and compl_pattern. static int get_wholeline_compl_info(char_u *line, colnr_T curs_col) { - compl_col = (colnr_T)getwhitecols(line); + compl_col = (colnr_T)getwhitecols((char *)line); compl_length = (int)curs_col - (int)compl_col; if (compl_length < 0) { // cursor in indent: empty pattern compl_length = 0; @@ -3509,7 +3879,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) return FAIL; } - // Reset extended parameters of completion, when start new + // Reset extended parameters of completion, when starting new // completion. compl_opt_refresh_always = false; @@ -3556,6 +3926,14 @@ static int get_spell_compl_info(int startcol, colnr_T curs_col) } /// Get the completion pattern, column and length. +/// +/// @param startcol start column number of the completion pattern/text +/// @param cur_col current cursor column +/// +/// On return, "line_invalid" is set to true, if the current line may have +/// become invalid and needs to be fetched again. +/// +/// @return OK on success. static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *line_invalid) { if (ctrl_x_mode_normal() @@ -3573,12 +3951,12 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li if (get_userdefined_compl_info(curs_col) == FAIL) { return FAIL; } - *line_invalid = true; // 'line' may have become invalid + *line_invalid = true; // "line" may have become invalid } else if (ctrl_x_mode_spell()) { if (get_spell_compl_info(startcol, curs_col) == FAIL) { return FAIL; } - *line_invalid = true; // 'line' may have become invalid + *line_invalid = true; // "line" may have become invalid } else { internal_error("ins_complete()"); return FAIL; @@ -3587,232 +3965,191 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li return OK; } -/// Do Insert mode completion. -/// Called when character "c" was typed, which has a meaning for completion. -/// Returns OK if completion was done, FAIL if something failed. -int ins_complete(int c, bool enable_pum) +/// Continue an interrupted completion mode search in "line". +/// +/// If this same ctrl_x_mode has been interrupted use the text from +/// "compl_startpos" to the cursor as a pattern to add a new word instead of +/// expand the one before the cursor, in word-wise if "compl_startpos" is not in +/// the same line as the cursor then fix it (the line has been split because it +/// was longer than 'tw'). if SOL is set then skip the previous pattern, a word +/// at the beginning of the line has been inserted, we'll look for that. +static void ins_compl_continue_search(char_u *line) { - char_u *line; - int startcol = 0; // column where searched text starts - colnr_T curs_col; // cursor column - int n; - int save_w_wrow; - int save_w_leftcol; - int insert_match; - const bool save_did_ai = did_ai; - int flags = CP_ORIGINAL_TEXT; - bool line_invalid = false; - - compl_direction = ins_compl_key2dir(c); - insert_match = ins_compl_use_match(c); - - if (!compl_started) { - // First time we hit ^N or ^P (in a row, I mean) - - did_ai = false; - did_si = false; - can_si = false; - can_si_back = false; - if (stop_arrow() == FAIL) { - return FAIL; - } - - line = ml_get(curwin->w_cursor.lnum); - curs_col = curwin->w_cursor.col; - compl_pending = 0; - - // If this same ctrl_x_mode has been interrupted use the text from - // "compl_startpos" to the cursor as a pattern to add a new word - // instead of expand the one before the cursor, in word-wise if - // "compl_startpos" is not in the same line as the cursor then fix it - // (the line has been split because it was longer than 'tw'). if SOL - // is set then skip the previous pattern, a word at the beginning of - // the line has been inserted, we'll look for that -- Acevedo. - if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT - && compl_cont_mode == ctrl_x_mode) { - // it is a continued search - compl_cont_status &= ~CONT_INTRPT; // remove INTRPT - if (ctrl_x_mode_normal() - || ctrl_x_mode_path_patterns() - || ctrl_x_mode_path_defines()) { - if (compl_startpos.lnum != curwin->w_cursor.lnum) { - // line (probably) wrapped, set compl_startpos to the - // first non_blank in the line, if it is not a wordchar - // include it to get a better pattern, but then we don't - // want the "\\<" prefix, check it below. - compl_col = (colnr_T)getwhitecols(line); - compl_startpos.col = compl_col; - compl_startpos.lnum = curwin->w_cursor.lnum; - compl_cont_status &= ~CONT_SOL; // clear SOL if present - } else { - // S_IPOS was set when we inserted a word that was at the - // beginning of the line, which means that we'll go to SOL - // mode but first we need to redefine compl_startpos - if (compl_cont_status & CONT_S_IPOS) { - compl_cont_status |= CONT_SOL; - compl_startpos.col = (colnr_T)((char_u *)skipwhite((char *)line - + compl_length - + compl_startpos.col) - line); - } - compl_col = compl_startpos.col; - } - compl_length = curwin->w_cursor.col - (int)compl_col; - // IObuff is used to add a "word from the next line" would we - // have enough space? just being paranoid -#define MIN_SPACE 75 - if (compl_length > (IOSIZE - MIN_SPACE)) { - compl_cont_status &= ~CONT_SOL; - compl_length = (IOSIZE - MIN_SPACE); - compl_col = curwin->w_cursor.col - compl_length; - } - compl_cont_status |= CONT_ADDING | CONT_N_ADDS; - if (compl_length < 1) { - compl_cont_status &= CONT_LOCAL; - } - } else if (ctrl_x_mode_line_or_eval()) { - compl_cont_status = CONT_ADDING | CONT_N_ADDS; - } else { - compl_cont_status = 0; - } + // it is a continued search + compl_cont_status &= ~CONT_INTRPT; // remove INTRPT + if (ctrl_x_mode_normal() + || ctrl_x_mode_path_patterns() + || ctrl_x_mode_path_defines()) { + if (compl_startpos.lnum != curwin->w_cursor.lnum) { + // line (probably) wrapped, set compl_startpos to the + // first non_blank in the line, if it is not a wordchar + // include it to get a better pattern, but then we don't + // want the "\\<" prefix, check it below. + compl_col = (colnr_T)getwhitecols((char *)line); + compl_startpos.col = compl_col; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_cont_status &= ~CONT_SOL; // clear SOL if present } else { - compl_cont_status &= CONT_LOCAL; - } - - if (!(compl_cont_status & CONT_ADDING)) { // normal expansion - compl_cont_mode = ctrl_x_mode; - if (ctrl_x_mode_not_default()) { - // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL - compl_cont_status = 0; + // S_IPOS was set when we inserted a word that was at the + // beginning of the line, which means that we'll go to SOL + // mode but first we need to redefine compl_startpos + if (compl_cont_status & CONT_S_IPOS) { + compl_cont_status |= CONT_SOL; + compl_startpos.col = (colnr_T)((char_u *)skipwhite((char *)line + + compl_length + + compl_startpos.col) - line); } - compl_cont_status |= CONT_N_ADDS; - compl_startpos = curwin->w_cursor; - startcol = (int)curs_col; - compl_col = 0; - } - - // Work out completion pattern and original text -- webb - if (compl_get_info(line, startcol, curs_col, &line_invalid) == FAIL) { - if (ctrl_x_mode_function() || ctrl_x_mode_omni() - || thesaurus_func_complete(ctrl_x_mode)) { - // restore did_ai, so that adding comment leader works - did_ai = save_did_ai; - } - return FAIL; + compl_col = compl_startpos.col; } - // If "line" was changed while getting completion info get it again. - if (line_invalid) { - line = ml_get(curwin->w_cursor.lnum); + compl_length = curwin->w_cursor.col - (int)compl_col; + // IObuff is used to add a "word from the next line" would we + // have enough space? just being paranoid +#define MIN_SPACE 75 + if (compl_length > (IOSIZE - MIN_SPACE)) { + compl_cont_status &= ~CONT_SOL; + compl_length = (IOSIZE - MIN_SPACE); + compl_col = curwin->w_cursor.col - compl_length; + } + compl_cont_status |= CONT_ADDING | CONT_N_ADDS; + if (compl_length < 1) { + compl_cont_status &= CONT_LOCAL; } + } else if (ctrl_x_mode_line_or_eval()) { + compl_cont_status = CONT_ADDING | CONT_N_ADDS; + } else { + compl_cont_status = 0; + } +} - if (compl_cont_status & CONT_ADDING) { - edit_submode_pre = (char_u *)_(" Adding"); - if (ctrl_x_mode_line_or_eval()) { - // Insert a new line, keep indentation but ignore 'comments'. - char_u *old = curbuf->b_p_com; +/// start insert mode completion +static int ins_compl_start(void) +{ + const bool save_did_ai = did_ai; - curbuf->b_p_com = (char_u *)""; - compl_startpos.lnum = curwin->w_cursor.lnum; - compl_startpos.col = compl_col; - ins_eol('\r'); - curbuf->b_p_com = old; - compl_length = 0; - compl_col = curwin->w_cursor.col; - } - } else { - edit_submode_pre = NULL; - compl_startpos.col = compl_col; - } + // First time we hit ^N or ^P (in a row, I mean) - if (compl_cont_status & CONT_LOCAL) { - edit_submode = (char_u *)_(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); - } else { - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); - } + did_ai = false; + did_si = false; + can_si = false; + can_si_back = false; + if (stop_arrow() == FAIL) { + return FAIL; + } - // If any of the original typed text has been changed we need to fix - // the redo buffer. - ins_compl_fixRedoBufForLeader(NULL); + char_u *line = ml_get(curwin->w_cursor.lnum); + colnr_T curs_col = curwin->w_cursor.col; + compl_pending = 0; + + if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT + && compl_cont_mode == ctrl_x_mode) { + // this same ctrl-x_mode was interrupted previously. Continue the + // completion. + ins_compl_continue_search(line); + } else { + compl_cont_status &= CONT_LOCAL; + } - // Always add completion for the original text. - xfree(compl_orig_text); - compl_orig_text = vim_strnsave(line + compl_col, (size_t)compl_length); - if (p_ic) { - flags |= CP_ICASE; - } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags, false) != OK) { - XFREE_CLEAR(compl_pattern); - XFREE_CLEAR(compl_orig_text); - return FAIL; + int startcol = 0; // column where searched text starts + if (!compl_status_adding()) { // normal expansion + compl_cont_mode = ctrl_x_mode; + if (ctrl_x_mode_not_default()) { + // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL + compl_cont_status = 0; } + compl_cont_status |= CONT_N_ADDS; + compl_startpos = curwin->w_cursor; + startcol = (int)curs_col; + compl_col = 0; + } - // showmode might reset the internal line pointers, so it must - // be called before line = ml_get(), or when this address is no - // longer needed. -- Acevedo. - edit_submode_extra = (char_u *)_("-- Searching..."); - edit_submode_highl = HLF_COUNT; - showmode(); - edit_submode_extra = NULL; - ui_flush(); - } else if (insert_match && stop_arrow() == FAIL) { + // Work out completion pattern and original text -- webb + bool line_invalid = false; + if (compl_get_info(line, startcol, curs_col, &line_invalid) == FAIL) { + if (ctrl_x_mode_function() || ctrl_x_mode_omni() + || thesaurus_func_complete(ctrl_x_mode)) { + // restore did_ai, so that adding comment leader works + did_ai = save_did_ai; + } return FAIL; } + // If "line" was changed while getting completion info get it again. + if (line_invalid) { + line = ml_get(curwin->w_cursor.lnum); + } - compl_shown_match = compl_curr_match; - compl_shows_dir = compl_direction; + if (compl_status_adding()) { + edit_submode_pre = _(" Adding"); + if (ctrl_x_mode_line_or_eval()) { + // Insert a new line, keep indentation but ignore 'comments'. + char *old = curbuf->b_p_com; - // Find next match (and following matches). - save_w_wrow = curwin->w_wrow; - save_w_leftcol = curwin->w_leftcol; - n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); + curbuf->b_p_com = ""; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_startpos.col = compl_col; + ins_eol('\r'); + curbuf->b_p_com = old; + compl_length = 0; + compl_col = curwin->w_cursor.col; + } + } else { + edit_submode_pre = NULL; + compl_startpos.col = compl_col; + } - if (n > 1) { // all matches have been found - compl_matches = n; + if (compl_cont_status & CONT_LOCAL) { + edit_submode = _(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); + } else { + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); } - compl_curr_match = compl_shown_match; - compl_direction = compl_shows_dir; - // Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert - // mode. - if (got_int && !global_busy) { - (void)vgetc(); - got_int = false; + // If any of the original typed text has been changed we need to fix + // the redo buffer. + ins_compl_fixRedoBufForLeader(NULL); + + // Always add completion for the original text. + xfree(compl_orig_text); + compl_orig_text = vim_strnsave(line + compl_col, (size_t)compl_length); + int flags = CP_ORIGINAL_TEXT; + if (p_ic) { + flags |= CP_ICASE; } + if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, + flags, false) != OK) { + XFREE_CLEAR(compl_pattern); + XFREE_CLEAR(compl_orig_text); + return FAIL; + } + + // showmode might reset the internal line pointers, so it must + // be called before line = ml_get(), or when this address is no + // longer needed. -- Acevedo. + edit_submode_extra = _("-- Searching..."); + edit_submode_highl = HLF_COUNT; + showmode(); + edit_submode_extra = NULL; + ui_flush(); + + return OK; +} +/// display the completion status message +static void ins_compl_show_statusmsg(void) +{ // we found no match if the list has only the "compl_orig_text"-entry - if (compl_first_match == compl_first_match->cp_next) { - edit_submode_extra = (compl_cont_status & CONT_ADDING) - && compl_length > 1 - ? (char_u *)_(e_hitend) : (char_u *)_(e_patnotf); + if (is_first_match(compl_first_match->cp_next)) { + edit_submode_extra = compl_status_adding() && compl_length > 1 ? _(e_hitend) : _(e_patnotf); edit_submode_highl = HLF_E; - // remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, - // because we couldn't expand anything at first place, but if we used - // ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word - // (such as M in M'exico) if not tried already. -- Acevedo - if (compl_length > 1 - || (compl_cont_status & CONT_ADDING) - || (ctrl_x_mode_not_default() - && !ctrl_x_mode_path_patterns() - && !ctrl_x_mode_path_defines())) { - compl_cont_status &= ~CONT_N_ADDS; - } - } - - if (compl_curr_match->cp_flags & CP_CONT_S_IPOS) { - compl_cont_status |= CONT_S_IPOS; - } else { - compl_cont_status &= ~CONT_S_IPOS; } if (edit_submode_extra == NULL) { - if (compl_curr_match->cp_flags & CP_ORIGINAL_TEXT) { - edit_submode_extra = (char_u *)_("Back at original"); + if (match_at_original_text(compl_curr_match)) { + edit_submode_extra = _("Back at original"); edit_submode_highl = HLF_W; } else if (compl_cont_status & CONT_S_IPOS) { - edit_submode_extra = (char_u *)_("Word from other line"); + edit_submode_extra = _("Word from other line"); edit_submode_highl = HLF_COUNT; } else if (compl_curr_match->cp_next == compl_curr_match->cp_prev) { - edit_submode_extra = (char_u *)_("The only match"); + edit_submode_extra = _("The only match"); edit_submode_highl = HLF_COUNT; compl_curr_match->cp_number = 1; } else { @@ -3837,7 +4174,7 @@ int ins_complete(int c, bool enable_pum) _("match %d"), compl_curr_match->cp_number); } - edit_submode_extra = match_ref; + edit_submode_extra = (char *)match_ref; edit_submode_highl = HLF_R; if (dollar_vcol >= 0) { curs_columns(curwin, false); @@ -3861,6 +4198,72 @@ int ins_complete(int c, bool enable_pum) msg_clr_cmdline(); // necessary for "noshowmode" } } +} + +/// Do Insert mode completion. +/// Called when character "c" was typed, which has a meaning for completion. +/// Returns OK if completion was done, FAIL if something failed. +int ins_complete(int c, bool enable_pum) +{ + int n; + int save_w_wrow; + int save_w_leftcol; + int insert_match; + + compl_direction = ins_compl_key2dir(c); + insert_match = ins_compl_use_match(c); + + if (!compl_started) { + if (ins_compl_start() == FAIL) { + return FAIL; + } + } else if (insert_match && stop_arrow() == FAIL) { + return FAIL; + } + + compl_shown_match = compl_curr_match; + compl_shows_dir = compl_direction; + + // Find next match (and following matches). + save_w_wrow = curwin->w_wrow; + save_w_leftcol = curwin->w_leftcol; + n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); + + if (n > 1) { // all matches have been found + compl_matches = n; + } + compl_curr_match = compl_shown_match; + compl_direction = compl_shows_dir; + + // Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert + // mode. + if (got_int && !global_busy) { + (void)vgetc(); + got_int = false; + } + + // we found no match if the list has only the "compl_orig_text"-entry + if (is_first_match(compl_first_match->cp_next)) { + // remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, + // because we couldn't expand anything at first place, but if we used + // ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word + // (such as M in M'exico) if not tried already. -- Acevedo + if (compl_length > 1 + || compl_status_adding() + || (ctrl_x_mode_not_default() + && !ctrl_x_mode_path_patterns() + && !ctrl_x_mode_path_defines())) { + compl_cont_status &= ~CONT_N_ADDS; + } + } + + if (compl_curr_match->cp_flags & CP_CONT_S_IPOS) { + compl_cont_status |= CONT_S_IPOS; + } else { + compl_cont_status &= ~CONT_S_IPOS; + } + + ins_compl_show_statusmsg(); // Show the popup menu, unless we got interrupted. if (enable_pum && !compl_interrupted) { @@ -3872,6 +4275,7 @@ int ins_complete(int c, bool enable_pum) return OK; } +/// Remove (if needed) and show the popup menu static void show_pum(int prev_w_wrow, int prev_w_leftcol) { // RedrawingDisabled may be set when invoked through complete(). diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 5a5257efb2..2b00d371f0 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -57,15 +57,15 @@ static char_u modifier_keys_table[] = MOD_MASK_SHIFT, '*', '4', 'k', 'D', // delete char MOD_MASK_SHIFT, '*', '5', 'k', 'L', // delete line MOD_MASK_SHIFT, '*', '7', '@', '7', // end - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7', // end + MOD_MASK_CTRL, KS_EXTRA, KE_C_END, '@', '7', // end MOD_MASK_SHIFT, '*', '9', '@', '9', // exit MOD_MASK_SHIFT, '*', '0', '@', '0', // find MOD_MASK_SHIFT, '#', '1', '%', '1', // help MOD_MASK_SHIFT, '#', '2', 'k', 'h', // home - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h', // home + MOD_MASK_CTRL, KS_EXTRA, KE_C_HOME, 'k', 'h', // home MOD_MASK_SHIFT, '#', '3', 'k', 'I', // insert MOD_MASK_SHIFT, '#', '4', 'k', 'l', // left arrow - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l', // left arrow + MOD_MASK_CTRL, KS_EXTRA, KE_C_LEFT, 'k', 'l', // left arrow MOD_MASK_SHIFT, '%', 'a', '%', '3', // message MOD_MASK_SHIFT, '%', 'b', '%', '4', // move MOD_MASK_SHIFT, '%', 'c', '%', '5', // next @@ -75,63 +75,63 @@ static char_u modifier_keys_table[] = MOD_MASK_SHIFT, '%', 'g', '%', '0', // redo MOD_MASK_SHIFT, '%', 'h', '&', '3', // replace MOD_MASK_SHIFT, '%', 'i', 'k', 'r', // right arr. - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r', // right arr. + MOD_MASK_CTRL, KS_EXTRA, KE_C_RIGHT, 'k', 'r', // right arr. MOD_MASK_SHIFT, '%', 'j', '&', '5', // resume MOD_MASK_SHIFT, '!', '1', '&', '6', // save MOD_MASK_SHIFT, '!', '2', '&', '7', // suspend MOD_MASK_SHIFT, '!', '3', '&', '8', // undo - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u', // up arrow - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd', // down arrow + MOD_MASK_SHIFT, KS_EXTRA, KE_S_UP, 'k', 'u', // up arrow + MOD_MASK_SHIFT, KS_EXTRA, KE_S_DOWN, 'k', 'd', // down arrow // vt100 F1 - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4, - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1', // F1 - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';', // F10 - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A', - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K', - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF1, KS_EXTRA, KE_XF1, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF2, KS_EXTRA, KE_XF2, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF3, KS_EXTRA, KE_XF3, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF4, KS_EXTRA, KE_XF4, + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F1, 'k', '1', // F1 + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F2, 'k', '2', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F3, 'k', '3', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F4, 'k', '4', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F5, 'k', '5', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F6, 'k', '6', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F7, 'k', '7', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F8, 'k', '8', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F9, 'k', '9', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F10, 'k', ';', // F10 + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F11, 'F', '1', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F12, 'F', '2', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F13, 'F', '3', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F14, 'F', '4', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F15, 'F', '5', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F16, 'F', '6', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F17, 'F', '7', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F18, 'F', '8', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F19, 'F', '9', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F20, 'F', 'A', + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F21, 'F', 'B', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F22, 'F', 'C', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F23, 'F', 'D', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F24, 'F', 'E', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F25, 'F', 'F', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F26, 'F', 'G', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F27, 'F', 'H', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F28, 'F', 'I', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F29, 'F', 'J', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F30, 'F', 'K', + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F31, 'F', 'L', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F32, 'F', 'M', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F33, 'F', 'N', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F34, 'F', 'O', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F35, 'F', 'P', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F36, 'F', 'Q', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F37, 'F', 'R', // TAB pseudo code - MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB, + MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, KE_TAB, NUL }; @@ -349,26 +349,26 @@ static struct mousetable { bool is_drag; // Is it a mouse drag event? } mouse_table[] = { - { (int)KE_LEFTMOUSE, MOUSE_LEFT, true, false }, - { (int)KE_LEFTDRAG, MOUSE_LEFT, false, true }, - { (int)KE_LEFTRELEASE, MOUSE_LEFT, false, false }, - { (int)KE_MIDDLEMOUSE, MOUSE_MIDDLE, true, false }, - { (int)KE_MIDDLEDRAG, MOUSE_MIDDLE, false, true }, - { (int)KE_MIDDLERELEASE, MOUSE_MIDDLE, false, false }, - { (int)KE_RIGHTMOUSE, MOUSE_RIGHT, true, false }, - { (int)KE_RIGHTDRAG, MOUSE_RIGHT, false, true }, - { (int)KE_RIGHTRELEASE, MOUSE_RIGHT, false, false }, - { (int)KE_X1MOUSE, MOUSE_X1, true, false }, - { (int)KE_X1DRAG, MOUSE_X1, false, true }, - { (int)KE_X1RELEASE, MOUSE_X1, false, false }, - { (int)KE_X2MOUSE, MOUSE_X2, true, false }, - { (int)KE_X2DRAG, MOUSE_X2, false, true }, - { (int)KE_X2RELEASE, MOUSE_X2, false, false }, + { KE_LEFTMOUSE, MOUSE_LEFT, true, false }, + { KE_LEFTDRAG, MOUSE_LEFT, false, true }, + { KE_LEFTRELEASE, MOUSE_LEFT, false, false }, + { KE_MIDDLEMOUSE, MOUSE_MIDDLE, true, false }, + { KE_MIDDLEDRAG, MOUSE_MIDDLE, false, true }, + { KE_MIDDLERELEASE, MOUSE_MIDDLE, false, false }, + { KE_RIGHTMOUSE, MOUSE_RIGHT, true, false }, + { KE_RIGHTDRAG, MOUSE_RIGHT, false, true }, + { KE_RIGHTRELEASE, MOUSE_RIGHT, false, false }, + { KE_X1MOUSE, MOUSE_X1, true, false }, + { KE_X1DRAG, MOUSE_X1, false, true }, + { KE_X1RELEASE, MOUSE_X1, false, false }, + { KE_X2MOUSE, MOUSE_X2, true, false }, + { KE_X2DRAG, MOUSE_X2, false, true }, + { KE_X2RELEASE, MOUSE_X2, false, false }, // DRAG without CLICK - { (int)KE_MOUSEMOVE, MOUSE_RELEASE, false, true }, + { KE_MOUSEMOVE, MOUSE_RELEASE, false, true }, // RELEASE without CLICK - { (int)KE_IGNORE, MOUSE_RELEASE, false, false }, - { 0, 0, 0, 0 }, + { KE_IGNORE, MOUSE_RELEASE, false, false }, + { 0, 0, 0, 0 }, }; /// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name @@ -926,8 +926,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } else { src += 5; result[dlen++] = K_SPECIAL; - result[dlen++] = (int)KS_EXTRA; - result[dlen++] = (int)KE_SNR; + result[dlen++] = KS_EXTRA; + result[dlen++] = KE_SNR; snprintf((char *)result + dlen, buf_len - dlen, "%" PRId64, (int64_t)current_sctx.sc_sid); dlen += STRLEN(result + dlen); diff --git a/src/nvim/locale.c b/src/nvim/locale.c new file mode 100644 index 0000000000..3e0774c096 --- /dev/null +++ b/src/nvim/locale.c @@ -0,0 +1,369 @@ +// 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 + +// locale.c: functions for language/locale configuration + +#include "auto/config.h" + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/garray.h" +#include "nvim/locale.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/profile.h" +#include "nvim/types.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "locale.c.generated.h" +#endif + +#if defined(HAVE_LOCALE_H) +# define HAVE_GET_LOCALE_VAL + +static char *get_locale_val(int what) +{ + // Obtain the locale value from the libraries. + char *loc = setlocale(what, NULL); + + return loc; +} +#endif + +/// @return true when "lang" starts with a valid language name. +/// Rejects NULL, empty string, "C", "C.UTF-8" and others. +static bool is_valid_mess_lang(char *lang) +{ + return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); +} + +/// Obtain the current messages language. Used to set the default for +/// 'helplang'. May return NULL or an empty string. +char *get_mess_lang(void) +{ + char *p; + +#ifdef HAVE_GET_LOCALE_VAL +# if defined(LC_MESSAGES) + p = get_locale_val(LC_MESSAGES); +# else + // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG + // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME + // and LC_MONETARY may be set differently for a Japanese working in the + // US. + p = get_locale_val(LC_COLLATE); +# endif +#else + p = os_getenv("LC_ALL"); + if (!is_valid_mess_lang(p)) { + p = os_getenv("LC_MESSAGES"); + if (!is_valid_mess_lang(p)) { + p = os_getenv("LANG"); + } + } +#endif + return is_valid_mess_lang(p) ? p : NULL; +} + +// Complicated #if; matches with where get_mess_env() is used below. +#ifdef HAVE_WORKING_LIBINTL +/// Get the language used for messages from the environment. +static char *get_mess_env(void) +{ + char *p; + + p = (char *)os_getenv("LC_ALL"); + if (p == NULL) { + p = (char *)os_getenv("LC_MESSAGES"); + if (p == NULL) { + p = (char *)os_getenv("LANG"); + if (p != NULL && ascii_isdigit(*p)) { + p = NULL; // ignore something like "1043" + } +# ifdef HAVE_GET_LOCALE_VAL + if (p == NULL) { + p = get_locale_val(LC_CTYPE); + } +# endif + } + } + return p; +} +#endif + +/// Set the "v:lang" variable according to the current locale setting. +/// Also do "v:lc_time"and "v:ctype". +void set_lang_var(void) +{ + const char *loc; + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_CTYPE); +#else + // setlocale() not supported: use the default value + loc = "C"; +#endif + set_vim_var_string(VV_CTYPE, loc, -1); + + // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall + // back to LC_CTYPE if it's empty. +#ifdef HAVE_WORKING_LIBINTL + loc = get_mess_env(); +#elif defined(LC_MESSAGES) + loc = get_locale_val(LC_MESSAGES); +#else + // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE + loc = get_locale_val(LC_CTYPE); +#endif + set_vim_var_string(VV_LANG, loc, -1); + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_TIME); +#endif + set_vim_var_string(VV_LC_TIME, loc, -1); + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_COLLATE); +#else + // setlocale() not supported: use the default value + loc = "C"; +#endif + set_vim_var_string(VV_COLLATE, loc, -1); +} + +#if defined(HAVE_LOCALE_H) +/// Setup to use the current locale (for ctype() and many other things). +void init_locale(void) +{ + setlocale(LC_ALL, ""); + +# ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif + + char localepath[MAXPATHL] = { 0 }; + snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); + char *tail = path_tail_with_sep(localepath); + *tail = NUL; + tail = path_tail(localepath); + xstrlcpy(tail, "share/locale", + sizeof(localepath) - (size_t)(tail - localepath)); + bindtextdomain(PROJECT_NAME, localepath); + textdomain(PROJECT_NAME); + TIME_MSG("locale set"); +} +#endif + +#ifdef HAVE_WORKING_LIBINTL + +/// ":language": Set the language (locale). +/// +/// @param eap +void ex_language(exarg_T *eap) +{ + char *loc; + char *p; + char *name; + int what = LC_ALL; + char *whatstr = ""; +# ifdef LC_MESSAGES +# define VIM_LC_MESSAGES LC_MESSAGES +# else +# define VIM_LC_MESSAGES 6789 +# endif + + name = eap->arg; + + // Check for "messages {name}", "ctype {name}" or "time {name}" argument. + // Allow abbreviation, but require at least 3 characters to avoid + // confusion with a two letter language name "me" or "ct". + p = skiptowhite(eap->arg); + if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { + if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { + what = VIM_LC_MESSAGES; + name = skipwhite(p); + whatstr = "messages "; + } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { + what = LC_CTYPE; + name = skipwhite(p); + whatstr = "ctype "; + } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { + what = LC_TIME; + name = skipwhite(p); + whatstr = "time "; + } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { + what = LC_COLLATE; + name = skipwhite(p); + whatstr = "collate "; + } + } + + if (*name == NUL) { + if (what == VIM_LC_MESSAGES) { + p = get_mess_env(); + } else { + p = setlocale(what, NULL); + } + if (p == NULL || *p == NUL) { + p = "Unknown"; + } + smsg(_("Current %slanguage: \"%s\""), whatstr, p); + } else { +# ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) { + loc = ""; + } else { +# endif + loc = setlocale(what, name); +# ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif +# ifndef LC_MESSAGES + } +# endif + if (loc == NULL) { + semsg(_("E197: Cannot set language to \"%s\""), name); + } else { +# ifdef HAVE_NL_MSG_CAT_CNTR + // Need to do this for GNU gettext, otherwise cached translations + // will be used again. + extern int _nl_msg_cat_cntr; + + _nl_msg_cat_cntr++; +# endif + // Reset $LC_ALL, otherwise it would overrule everything. + os_setenv("LC_ALL", "", 1); + + if (what != LC_TIME && what != LC_COLLATE) { + // Tell gettext() what to translate to. It apparently doesn't + // use the currently effective locale. + if (what == LC_ALL) { + os_setenv("LANG", name, 1); + + // Clear $LANGUAGE because GNU gettext uses it. + os_setenv("LANGUAGE", "", 1); + } + if (what != LC_CTYPE) { + os_setenv("LC_MESSAGES", name, 1); + set_helplang_default(name); + } + } + + // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. + set_lang_var(); + maketitle(); + } + } +} + +static char **locales = NULL; // Array of all available locales + +# ifndef WIN32 +static bool did_init_locales = false; + +/// @return an array of strings for all available locales + NULL for the +/// last element or, +/// NULL in case of error. +static char **find_locales(void) +{ + garray_T locales_ga; + char *loc; + char *saveptr = NULL; + + // Find all available locales by running command "locale -a". If this + // doesn't work we won't have completion. + char *locale_a = (char *)get_cmd_output((char_u *)"locale -a", NULL, + kShellOptSilent, NULL); + if (locale_a == NULL) { + return NULL; + } + ga_init(&locales_ga, sizeof(char_u *), 20); + + // Transform locale_a string where each locale is separated by "\n" + // into an array of locale strings. + loc = os_strtok(locale_a, "\n", &saveptr); + + while (loc != NULL) { + loc = xstrdup(loc); + GA_APPEND(char *, &locales_ga, loc); + loc = os_strtok(NULL, "\n", &saveptr); + } + xfree(locale_a); + // Guarantee that .ga_data is NULL terminated + ga_grow(&locales_ga, 1); + ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; + return locales_ga.ga_data; +} +# endif + +/// Lazy initialization of all available locales. +static void init_locales(void) +{ +# ifndef WIN32 + if (!did_init_locales) { + did_init_locales = true; + locales = find_locales(); + } +# endif +} + +# if defined(EXITFREE) +void free_locales(void) +{ + int i; + if (locales != NULL) { + for (i = 0; locales[i] != NULL; i++) { + xfree(locales[i]); + } + XFREE_CLEAR(locales); + } +} +# endif + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":language" command. +char *get_lang_arg(expand_T *xp, int idx) +{ + if (idx == 0) { + return "messages"; + } + if (idx == 1) { + return "ctype"; + } + if (idx == 2) { + return "time"; + } + if (idx == 3) { + return "collate"; + } + + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx - 4]; +} + +/// Function given to ExpandGeneric() to obtain the available locales. +char *get_locales(expand_T *xp, int idx) +{ + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx]; +} + +#endif diff --git a/src/nvim/locale.h b/src/nvim/locale.h new file mode 100644 index 0000000000..39735d371f --- /dev/null +++ b/src/nvim/locale.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LOCALE_H +#define NVIM_LOCALE_H + +#include "nvim/ex_cmds_defs.h" +#include "nvim/types.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "locale.h.generated.h" +#endif +#endif // NVIM_LOCALE_H diff --git a/src/nvim/log.c b/src/nvim/log.c index 99b17a612b..9bdf327430 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -60,16 +60,16 @@ static bool log_try_create(char *fname) static void log_path_init(void) { size_t size = sizeof(log_file_path); - expand_env((char_u *)"$" ENV_LOGFILE, (char_u *)log_file_path, (int)size - 1); + expand_env("$" ENV_LOGFILE, log_file_path, (int)size - 1); if (strequal("$" ENV_LOGFILE, log_file_path) || log_file_path[0] == '\0' - || os_isdir((char_u *)log_file_path) + || os_isdir(log_file_path) || !log_try_create(log_file_path)) { // Make $XDG_STATE_HOME if it does not exist. char *loghome = get_xdg_home(kXDGStateHome); char *failed_dir = NULL; bool log_dir_failure = false; - if (!os_isdir((char_u *)loghome)) { + if (!os_isdir(loghome)) { log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0); } XFREE_CLEAR(loghome); 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..42aa13cfc1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -15,12 +15,14 @@ #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_cmds.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -37,7 +39,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 +449,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) { @@ -988,7 +990,7 @@ int nlua_in_fast_event(lua_State *lstate) static bool viml_func_is_fast(const char *name) { - const EvalFuncDef *const fdef = find_internal_func((const char *)name); + const EvalFuncDef *const fdef = find_internal_func(name); if (fdef) { return fdef->fast; } @@ -1025,7 +1027,7 @@ int nlua_call(lua_State *lstate) // TODO(bfredl): this should be simplified in error handling refactor force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; did_emsg = false; try_start(); @@ -1091,7 +1093,7 @@ static int nlua_rpc(lua_State *lstate, bool request) Object result = rpc_send_call(chan_id, name, args, &res_mem, &err); if (!ERROR_SET(&err)) { nlua_push_Object(lstate, result, false); - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); } } else { if (!rpc_send_event(chan_id, name, args)) { @@ -1313,12 +1315,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 +1332,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); @@ -1577,7 +1578,7 @@ void ex_luado(exarg_T *const eap) } lua_pop(lstate, 1); check_cursor(); - update_screen(NOT_VALID); + update_screen(UPD_NOT_VALID); } /// Run lua file @@ -1661,6 +1662,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_has_language); lua_setfield(lstate, -2, "_ts_has_language"); + lua_pushcfunction(lstate, tslua_remove_lang); + lua_setfield(lstate, -2, "_ts_remove_language"); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); @@ -1906,7 +1910,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; @@ -1939,8 +1943,13 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) // Split args by unescaped whitespace |<f-args>| (nargs dependent) if (cmd->uc_argt & EX_NOSPC) { - // Commands where nargs = 1 or "?" fargs is the same as args - lua_rawseti(lstate, -2, 1); + if ((cmd->uc_argt & EX_NEEDARG) || STRLEN(eap->arg)) { + // For commands where nargs is 1 or "?" and argument is passed, fargs = { args } + lua_rawseti(lstate, -2, 1); + } else { + // if nargs = "?" and no argument is passed, fargs = {} + lua_pop(lstate, 1); // Pop the reference of opts.args + } } else if (eap->args == NULL) { // For commands with more than one possible argument, split if argument list isn't available. lua_pop(lstate, 1); // Pop the reference of opts.args 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..1b874e673a 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" @@ -473,6 +474,52 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } +#if defined(HAVE_ICONV) + +/// Convert string from one encoding to another +static int nlua_iconv(lua_State *lstate) +{ + int narg = lua_gettop(lstate); + + if (narg < 3) { + return luaL_error(lstate, "Expected at least 3 arguments"); + } + + for (int i = 1; i <= 3; i++) { + if (lua_type(lstate, i) != LUA_TSTRING) { + return luaL_argerror(lstate, i, "expected string"); + } + } + + size_t str_len = 0; + const char *str = lua_tolstring(lstate, 1, &str_len); + + char_u *from = (char_u *)enc_canonize(enc_skip((char *)lua_tolstring(lstate, 2, NULL))); + char_u *to = (char_u *)enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL))); + + vimconv_T vimconv; + vimconv.vc_type = CONV_NONE; + convert_setup_ext(&vimconv, from, false, to, false); + + char_u *ret = (char_u *)string_convert(&vimconv, (char *)str, &str_len); + + convert_setup(&vimconv, NULL, NULL); + + xfree(from); + xfree(to); + + if (ret == NULL) { + lua_pushnil(lstate); + } else { + lua_pushlstring(lstate, (char *)ret, str_len); + xfree(ret); + } + + return 1; +} + +#endif + void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) { if (!is_thread) { @@ -518,6 +565,13 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) // vim.spell luaopen_spell(lstate); lua_setfield(lstate, -2, "spell"); + +#if defined(HAVE_ICONV) + // vim.iconv + // depends on p_ambw, p_emoji + lua_pushcfunction(lstate, &nlua_iconv); + lua_setfield(lstate, -2, "iconv"); +#endif } // vim.mpack diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index f0d847e352..7ff4fbbff4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -14,11 +14,14 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> +#include <uv.h> #include "nvim/api/private/helpers.h" #include "nvim/buffer.h" #include "nvim/lib/kvec.h" +#include "nvim/log.h" #include "nvim/lua/treesitter.h" +#include "nvim/map.h" #include "nvim/memline.h" #include "tree_sitter/api.h" @@ -85,6 +88,10 @@ static struct luaL_Reg node_meta[] = { { "prev_sibling", node_prev_sibling }, { "next_named_sibling", node_next_named_sibling }, { "prev_named_sibling", node_prev_named_sibling }, + { "named_children", node_named_children }, + { "root", node_root }, + { "byte_length", node_byte_length }, + { NULL, NULL } }; @@ -145,18 +152,27 @@ int tslua_has_language(lua_State *L) return 1; } +// Creates the language into the internal language map. +// +// Returns true if the language is correctly loaded in the language map int tslua_add_language(lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *lang_name = luaL_checkstring(L, 2); + const char *symbol_name = lang_name; + + if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) { + symbol_name = luaL_checkstring(L, 3); + } if (pmap_has(cstr_t)(&langs, lang_name)) { - return 0; + lua_pushboolean(L, true); + return 1; } #define BUFSIZE 128 char symbol_buf[BUFSIZE]; - snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); + snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", symbol_name); #undef BUFSIZE uv_lib_t lib; @@ -179,6 +195,7 @@ int tslua_add_language(lua_State *L) TSLanguage *lang = lang_parser(); if (lang == NULL) { + uv_dlclose(&lib); return luaL_error(L, "Failed to load parser %s: internal error", path); } @@ -198,6 +215,19 @@ int tslua_add_language(lua_State *L) return 1; } +int tslua_remove_lang(lua_State *L) +{ + const char *lang_name = luaL_checkstring(L, 1); + bool present = pmap_has(cstr_t)(&langs, lang_name); + if (present) { + char *key = (char *)pmap_key(cstr_t)(&langs, lang_name); + pmap_del(cstr_t)(&langs, lang_name); + xfree(key); + } + lua_pushboolean(L, present); + return 1; +} + int tslua_inspect_lang(lua_State *L) { const char *lang_name = luaL_checkstring(L, 1); @@ -593,7 +623,7 @@ void push_tree(lua_State *L, TSTree *tree, bool do_copy) lua_setfenv(L, -2); // [udata] } -static TSTree **tree_check(lua_State *L, uint16_t index) +static TSTree **tree_check(lua_State *L, int index) { TSTree **ud = luaL_checkudata(L, index, TS_META_TREE); return ud; @@ -1036,6 +1066,57 @@ static int node_prev_named_sibling(lua_State *L) return 1; } +static int node_named_children(lua_State *L) +{ + TSNode source; + if (!node_check(L, 1, &source)) { + return 0; + } + TSTreeCursor cursor = ts_tree_cursor_new(source); + + lua_newtable(L); + int curr_index = 0; + + if (ts_tree_cursor_goto_first_child(&cursor)) { + do { + TSNode node = ts_tree_cursor_current_node(&cursor); + if (ts_node_is_named(node)) { + push_node(L, node, 1); + lua_rawseti(L, -2, ++curr_index); + } + } while (ts_tree_cursor_goto_next_sibling(&cursor)); + } + + ts_tree_cursor_delete(&cursor); + return 1; +} + +static int node_root(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + TSNode root = ts_tree_root_node(node.tree); + push_node(L, root, 1); + return 1; +} + +static int node_byte_length(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + uint32_t start_byte = ts_node_start_byte(node); + uint32_t end_byte = ts_node_end_byte(node); + + lua_pushnumber(L, end_byte - start_byte); + return 1; +} + /// assumes the match table being on top of the stack static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) { 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/macros.h b/src/nvim/macros.h index a896a406d1..5601db274b 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -31,7 +31,7 @@ /// @return `s, sizeof(s) - 1` #define S_LEN(s) (s), (sizeof(s) - 1) -/// LINEEMPTY() - return TRUE if the line is empty +/// LINEEMPTY() - return true if the line is empty #define LINEEMPTY(p) (*ml_get(p) == NUL) // toupper() and tolower() that use the current locale. diff --git a/src/nvim/main.c b/src/nvim/main.c index 494ff0b4af..883f946cad 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" @@ -23,23 +25,19 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/grid.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" #include "nvim/insexpand.h" +#include "nvim/locale.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mapping.h" -#include "nvim/ui_client.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif -#include "nvim/garray.h" -#include "nvim/grid.h" -#include "nvim/log.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -50,6 +48,7 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -57,18 +56,20 @@ #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" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" #include "nvim/ui_compositor.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef WIN32 # include "nvim/os/os_win_console.h" @@ -159,6 +160,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"); @@ -475,7 +477,7 @@ int main(int argc, char **argv) if (exmode_active || use_remote_ui || use_builtin_ui) { // Don't clear the screen when starting in Ex mode, or when a UI might have // displayed messages. - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } else { screenclear(); // clear screen TIME_MSG("clearing screen"); @@ -504,7 +506,7 @@ int main(int argc, char **argv) // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { - qf_jump(NULL, 0, 0, FALSE); + qf_jump(NULL, 0, 0, false); TIME_MSG("jump to first error"); } @@ -516,7 +518,7 @@ int main(int argc, char **argv) if (params.diff_mode) { // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - diff_win_options(wp, TRUE); + diff_win_options(wp, true); } } @@ -535,7 +537,7 @@ int main(int argc, char **argv) starting = 0; RedrawingDisabled = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); no_wait_return = false; // 'autochdir' has been postponed. @@ -709,8 +711,8 @@ void getout(int exitval) if (did_emsg) { // give the user a chance to read the (error) message - no_wait_return = FALSE; - wait_return(FALSE); + no_wait_return = false; + wait_return(false); } // Position the cursor again, the autocommands may have moved it @@ -797,30 +799,6 @@ static int get_number_arg(const char *p, int *idx, int def) return def; } -#if defined(HAVE_LOCALE_H) -/// Setup to use the current locale (for ctype() and many other things). -static void init_locale(void) -{ - setlocale(LC_ALL, ""); - -# ifdef LC_NUMERIC - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif - - char localepath[MAXPATHL] = { 0 }; - snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); - char *tail = path_tail_with_sep(localepath); - *tail = NUL; - tail = path_tail(localepath); - xstrlcpy(tail, "share/locale", - sizeof(localepath) - (size_t)(tail - localepath)); - bindtextdomain(PROJECT_NAME, localepath); - textdomain(PROJECT_NAME); - TIME_MSG("locale set"); -} -#endif - static uint64_t server_connect(char *server_addr, const char **errmsg) { if (server_addr == NULL) { @@ -1048,7 +1026,7 @@ static void command_line_scan(mparm_T *parmp) } else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) { parmp->use_vimrc = "NONE"; parmp->clean = true; - set_option_value("shadafile", 0L, "NONE", 0); + set_option_value_give_err("shadafile", 0L, "NONE", 0); } else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) { nlua_disable_preload = true; } else { @@ -1062,7 +1040,7 @@ static void command_line_scan(mparm_T *parmp) } break; case 'A': // "-A" start in Arabic mode. - set_option_value("arabic", 1L, NULL, 0); + set_option_value_give_err("arabic", 1L, NULL, 0); break; case 'b': // "-b" binary mode. // Needs to be effective before expanding file names, because @@ -1093,10 +1071,10 @@ static void command_line_scan(mparm_T *parmp) os_exit(0); case 'H': // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; - set_option_value("rl", 1L, NULL, 0); + set_option_value_give_err("rl", 1L, NULL, 0); break; case 'l': // "-l" lisp mode, 'lisp' and 'showmatch' on. - set_option_value("lisp", 1L, NULL, 0); + set_option_value_give_err("lisp", 1L, NULL, 0); p_sm = true; break; case 'M': // "-M" no changes or writing of files @@ -1177,7 +1155,7 @@ static void command_line_scan(mparm_T *parmp) // default is 10: a little bit verbose p_verbose = get_number_arg(argv[0], &argv_idx, 10); if (argv[0][argv_idx] != NUL) { - set_option_value("verbosefile", 0L, argv[0] + argv_idx, 0); + set_option_value_give_err("verbosefile", 0L, argv[0] + argv_idx, 0); argv_idx = (int)STRLEN(argv[0]); } break; @@ -1185,7 +1163,7 @@ static void command_line_scan(mparm_T *parmp) // "-w {scriptout}" write to script if (ascii_isdigit(((char_u *)argv[0])[argv_idx])) { n = get_number_arg(argv[0], &argv_idx, 10); - set_option_value("window", n, NULL, 0); + set_option_value_give_err("window", n, NULL, 0); break; } want_argument = true; @@ -1242,8 +1220,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]; } @@ -1280,7 +1258,7 @@ static void command_line_scan(mparm_T *parmp) break; case 'i': // "-i {shada}" use for shada - set_option_value("shadafile", 0L, argv[0], 0); + set_option_value_give_err("shadafile", 0L, argv[0], 0); break; case 's': { // "-s {scriptin}" read from script file @@ -1330,7 +1308,7 @@ scripterror: if (ascii_isdigit(*((char_u *)argv[0]))) { argv_idx = 0; n = get_number_arg(argv[0], &argv_idx, 10); - set_option_value("window", n, NULL, 0); + set_option_value_give_err("window", n, NULL, 0); argv_idx = -1; break; } @@ -1364,8 +1342,8 @@ scripterror: ga_grow(&global_alist.al_ga, 1); char *p = xstrdup(argv[0]); - if (parmp->diff_mode && os_isdir((char_u *)p) && GARGCOUNT > 0 - && !os_isdir((char_u *)alist_name(&GARGLIST[0]))) { + if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0 + && !os_isdir(alist_name(&GARGLIST[0]))) { char *r = concat_fnames(p, path_tail(alist_name(&GARGLIST[0])), true); xfree(p); p = r; @@ -1418,7 +1396,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; @@ -1512,7 +1490,7 @@ static void handle_quickfix(mparm_T *paramp) set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); } vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, (char *)p_ef, p_efm, true, (char *)IObuff, (char *)p_menc) < 0) { + if (qf_init(NULL, (char *)p_ef, p_efm, true, (char *)IObuff, p_menc) < 0) { msg_putchar('\n'); os_exit(3); } @@ -1613,9 +1591,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) { @@ -1634,7 +1612,7 @@ static void create_windows(mparm_T *parmp) } curwin = curwin->w_next; } - dorewind = FALSE; + dorewind = false; curbuf = curwin->w_buffer; if (curbuf->b_ml.ml_mfp == NULL) { // Set 'foldlevel' to 'foldlevelstart' if it's not negative.. @@ -1643,15 +1621,15 @@ static void create_windows(mparm_T *parmp) } // When getting the ATTENTION prompt here, use a dialog. swap_exists_action = SEA_DIALOG; - set_buflisted(TRUE); + set_buflisted(true); // create memfile, read file - (void)open_buffer(FALSE, NULL, 0); + (void)open_buffer(false, NULL, 0); if (swap_exists_action == SEA_QUIT) { if (got_int || only_one_window()) { // abort selected or quit and only one window - did_emsg = FALSE; // avoid hit-enter prompt + did_emsg = false; // avoid hit-enter prompt getout(1); } // We can't close the window, it would disturb what @@ -1663,7 +1641,7 @@ static void create_windows(mparm_T *parmp) } else { handle_swap_exists(NULL); } - dorewind = TRUE; // start again + dorewind = true; // start again } os_breakcheck(); if (got_int) { @@ -1677,8 +1655,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 +1673,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) { @@ -1705,7 +1683,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } arg_idx = 1; - for (i = 1; i < parmp->window_count; ++i) { + for (i = 1; i < parmp->window_count; i++) { if (cwd != NULL) { os_chdir((char *)cwd); } @@ -1729,9 +1707,9 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) if (i == 1) { char buf[100]; - p_shm_save = xstrdup((char *)p_shm); + p_shm_save = xstrdup(p_shm); snprintf(buf, sizeof(buf), "F%s", p_shm); - set_option_value("shm", 0L, buf, 0); + set_option_value_give_err("shm", 0L, buf, 0); } } else { if (curwin->w_next == NULL) { // just checking @@ -1756,7 +1734,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) // abort or quit selected if (got_int || only_one_window()) { // abort selected and only one window - did_emsg = FALSE; // avoid hit-enter prompt + did_emsg = false; // avoid hit-enter prompt getout(1); } win_close(curwin, true, false); @@ -1775,14 +1753,14 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } if (p_shm_save != NULL) { - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } 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 +1774,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 +1792,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"); } @@ -1837,11 +1815,11 @@ static void exe_commands(mparm_T *parmp) * pattern on line 1. But don't move the cursor when an autocommand * with g`" was used. */ - msg_scroll = TRUE; + msg_scroll = true; 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,19 +1828,19 @@ 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; } if (!exmode_active) { - msg_scroll = FALSE; + msg_scroll = false; } // When started with "-q errorfile" jump to first error again. if (parmp->edit_type == EDIT_QF) { - qf_jump(NULL, 0, 0, FALSE); + qf_jump(NULL, 0, 0, false); } TIME_MSG("executing command arguments"); } @@ -2059,17 +2037,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..333fb847f6 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" @@ -149,8 +150,8 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) - && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) { + if (message_filtered((char *)mp->m_keys) && message_filtered(mp->m_str) + && (mp->m_desc == NULL || message_filtered(mp->m_desc))) { return; } @@ -202,7 +203,7 @@ static void showmap(mapblock_T *mp, bool local) } else if (mp->m_str[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - msg_outtrans_special(mp->m_str, false, 0); + msg_outtrans_special((char_u *)mp->m_str, false, 0); } if (mp->m_desc != NULL) { @@ -292,7 +293,7 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL, cpo_flags); mapargs->rhs_len = STRLEN(replaced); - // XXX: replace_termcodes may produce an empty string even if orig_rhs is non-empty + // NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty // (e.g. a single ^V, see :h map-empty-rhs) mapargs->rhs_is_noop = orig_rhs_len != 0 && mapargs->rhs_len == 0; mapargs->rhs = (char_u *)replaced; @@ -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. @@ -448,8 +449,8 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, } mp->m_keys = vim_strsave(keys); - mp->m_str = args->rhs; - mp->m_orig_str = args->orig_rhs; + mp->m_str = (char *)args->rhs; + mp->m_orig_str = (char *)args->orig_rhs; mp->m_luaref = args->rhs_lua; if (!simplified) { args->rhs = NULL; @@ -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; @@ -703,7 +704,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, } else { // do we have a match? if (round) { // second round: Try unmap "rhs" string n = (int)STRLEN(mp->m_str); - p = mp->m_str; + p = (char_u *)mp->m_str; } else { n = mp->m_keylen; p = mp->m_keys; @@ -760,8 +761,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, XFREE_CLEAR(mp->m_str); XFREE_CLEAR(mp->m_orig_str); } - mp->m_str = args->rhs; - mp->m_orig_str = args->orig_rhs; + mp->m_str = (char *)args->rhs; + mp->m_orig_str = (char *)args->orig_rhs; mp->m_luaref = args->rhs_lua; if (!keyround1_simplified) { args->rhs = 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) \ @@ -1112,7 +1113,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) mp = maphash[hash]; } for (; mp; mp = mp->m_next) { - if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) { + if ((mp->m_mode & mode) && strstr(mp->m_str, rhs) != NULL) { return true; } } @@ -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) { @@ -1505,7 +1506,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) if (mp->m_expr) { s = eval_map_expr(mp, c); } else { - s = mp->m_str; + s = (char_u *)mp->m_str; } if (s != NULL) { // insert the to string @@ -1541,7 +1542,7 @@ char_u *eval_map_expr(mapblock_T *mp, int c) // Remove escaping of K_SPECIAL, because "str" is in a format to be used as // typeahead. if (mp->m_luaref == LUA_NOREF) { - expr = vim_strsave(mp->m_str); + expr = vim_strsave((char_u *)mp->m_str); vim_unescape_ks(expr); } @@ -1638,7 +1639,7 @@ int makemap(FILE *fd, buf_T *buf) if (mp->m_luaref != LUA_NOREF) { continue; } - for (p = mp->m_str; *p != NUL; p++) { + for (p = (char_u *)mp->m_str; *p != NUL; p++) { if (p[0] == K_SPECIAL && p[1] == KS_EXTRA && p[2] == KE_SNR) { break; @@ -1784,7 +1785,7 @@ int makemap(FILE *fd, buf_T *buf) if (putc(' ', fd) < 0 || put_escstr(fd, mp->m_keys, 0) == FAIL || putc(' ', fd) < 0 - || put_escstr(fd, mp->m_str, 1) == FAIL + || put_escstr(fd, (char_u *)mp->m_str, 1) == FAIL || put_eol(fd) < 0) { return FAIL; } @@ -1955,7 +1956,7 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb *local_ptr = local; } *rhs_lua = mp->m_luaref; - return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL; + return mp->m_luaref == LUA_NOREF ? (char_u *)mp->m_str : NULL; } } } @@ -1966,7 +1967,7 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb } /// "hasmapto()" function -void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *mode; const char *const name = tv_get_string(&argvars[0]); @@ -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) { @@ -2126,7 +2127,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } /// "mapset()" function -void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; const char *which = tv_get_string_buf_chk(&argvars[0], buf); @@ -2194,13 +2195,13 @@ void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "maparg()" function -void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_maparg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_maparg(argvars, rettv, true); } /// "mapcheck()" function -void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_mapcheck(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_maparg(argvars, rettv, false); } @@ -2321,7 +2322,7 @@ void langmap_set(void) ga_clear(&langmap_mapga); // clear the previous map first langmap_init(); // back to one-to-one map - for (p = p_langmap; p[0] != NUL;) { + for (p = (char_u *)p_langmap; p[0] != NUL;) { for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';'; MB_PTR_ADV(p2)) { if (p2[0] == '\\' && p2[1] != NUL) { @@ -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 344d55a473..b430c167ce 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -20,7 +20,6 @@ #include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/mark.h" #include "nvim/mark_defs.h" @@ -36,9 +35,9 @@ #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/quickfix.h" -#include "nvim/search.h" #include "nvim/sign.h" #include "nvim/strings.h" +#include "nvim/textobject.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/map.h" @@ -116,7 +115,7 @@ void clear_fmark(fmark_T *fm) FUNC_ATTR_NONNULL_ALL { free_fmark(*fm); - memset(fm, 0, sizeof(*fm)); + CLEAR_POINTER(fm); } /* @@ -832,7 +831,7 @@ static void fname2fnum(xfmark_T *fm) )) { int len; - expand_env((char_u *)"~/", NameBuff, MAXPATHL); + expand_env("~/", NameBuff, MAXPATHL); len = (int)STRLEN(NameBuff); STRLCPY(NameBuff + len, fm->fname + 2, MAXPATHL - len); } else { @@ -841,7 +840,7 @@ static void fname2fnum(xfmark_T *fm) // Try to shorten the file name. os_dirname(IObuff, IOSIZE); - p = path_shorten_fname(NameBuff, IObuff); + p = path_shorten_fname((char_u *)NameBuff, IObuff); // buflist_new() will call fmarks_check_names() (void)buflist_new((char *)NameBuff, (char *)p, (linenr_T)1, 0); @@ -862,12 +861,12 @@ void fmarks_check_names(buf_T *buf) return; } - for (i = 0; i < NGLOBALMARKS; ++i) { + for (i = 0; i < NGLOBALMARKS; i++) { fmarks_check_one(&namedfm[i], name, buf); } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - for (i = 0; i < wp->w_jumplistlen; ++i) { + for (i = 0; i < wp->w_jumplistlen; i++) { fmarks_check_one(&wp->w_jumplist[i], name, buf); } } @@ -1006,10 +1005,10 @@ void ex_marks(exarg_T *eap) } show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); - for (i = 0; i < NMARKS; ++i) { + for (i = 0; i < NMARKS; i++) { show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); } - for (i = 0; i < NGLOBALMARKS; ++i) { + for (i = 0; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum != 0) { name = fm_getname(&namedfm[i].fmark, 15); } else { @@ -1069,7 +1068,7 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu name = mark_line(p, 15); mustfree = true; } - if (!message_filtered(name)) { + if (!message_filtered((char *)name)) { if (!did_title) { // Highlight title msg_puts_title(_("\nmark line col file/text")); @@ -1080,7 +1079,7 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu snprintf((char *)IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); msg_outtrans((char *)IObuff); if (name != NULL) { - msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); + msg_outtrans_attr((char *)name, current ? HL_ATTR(HLF_D) : 0); } } ui_flush(); // show one line at a time @@ -1133,7 +1132,7 @@ void ex_delmarks(exarg_T *eap) from = to = *p; } - for (i = from; i <= to; ++i) { + for (i = from; i <= to; i++) { if (lower) { curbuf->b_namedm[i - 'a'].mark.lnum = 0; } else { @@ -1185,7 +1184,7 @@ void ex_jumps(exarg_T *eap) cleanup_jumplist(curwin, true); // Highlight title msg_puts_title(_("\n jump line col file/text")); - for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { + for (i = 0; i < curwin->w_jumplistlen && !got_int; i++) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); @@ -1195,7 +1194,7 @@ void ex_jumps(exarg_T *eap) name = vim_strsave((char_u *)"-invalid-"); } // apply :filter /pat/ or file name not available - if (name == NULL || message_filtered(name)) { + if (name == NULL || message_filtered((char *)name)) { xfree(name); continue; } @@ -1210,7 +1209,7 @@ void ex_jumps(exarg_T *eap) i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i, curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col); msg_outtrans((char *)IObuff); - msg_outtrans_attr(name, + msg_outtrans_attr((char *)name, curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); @@ -1241,7 +1240,7 @@ void ex_changes(exarg_T *eap) // Highlight title msg_puts_title(_("\nchange line col text")); - for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) { + for (i = 0; i < curbuf->b_changelistlen && !got_int; i++) { if (curbuf->b_changelist[i].mark.lnum != 0) { msg_putchar('\n'); if (got_int) { @@ -1255,7 +1254,7 @@ void ex_changes(exarg_T *eap) curbuf->b_changelist[i].mark.col); msg_outtrans((char *)IObuff); name = mark_line(&curbuf->b_changelist[i].mark, 17); - msg_outtrans_attr(name, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)name, HL_ATTR(HLF_D)); xfree(name); os_breakcheck(); } @@ -1549,7 +1548,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c */ FOR_ALL_WINDOWS_IN_TAB(win, curtab) { // marks in the jumplist - for (i = 0; i < win->w_jumplistlen; ++i) { + for (i = 0; i < win->w_jumplistlen; i++) { if (win->w_jumplist[i].fmark.fnum == fnum) { COL_ADJUST(&(win->w_jumplist[i].fmark.mark)); } @@ -1651,7 +1650,7 @@ void copy_jumplist(win_T *from, win_T *to) { int i; - for (i = 0; i < from->w_jumplistlen; ++i) { + for (i = 0; i < from->w_jumplistlen; i++) { to->w_jumplist[i] = from->w_jumplist[i]; if (from->w_jumplist[i].fname != NULL) { to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname); @@ -1875,7 +1874,7 @@ void free_jumplist(win_T *wp) { int i; - for (i = 0; i < wp->w_jumplistlen; ++i) { + for (i = 0; i < wp->w_jumplistlen; i++) { free_xfmark(wp->w_jumplist[i]); } wp->w_jumplistlen = 0; @@ -1898,7 +1897,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..48f0d0fc05 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" @@ -42,7 +47,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in matchitem_T *m; int hlg_id; regprog_T *regprog = NULL; - int rtype = SOME_VALID; + int rtype = UPD_SOME_VALID; if (*grp == NUL || (pat != NULL && *pat == NUL)) { return -1; @@ -188,7 +193,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in } m->pos.toplnum = toplnum; m->pos.botlnum = botlnum; - rtype = VALID; + rtype = UPD_VALID; } } @@ -222,7 +227,7 @@ static int match_delete(win_T *wp, int id, bool perr) { matchitem_T *cur = wp->w_match_head; matchitem_T *prev = cur; - int rtype = SOME_VALID; + int rtype = UPD_SOME_VALID; if (id < 1) { if (perr) { @@ -263,7 +268,7 @@ static int match_delete(win_T *wp, int id, bool perr) wp->w_buffer->b_mod_bot = cur->pos.botlnum; wp->w_buffer->b_mod_xlines = 0; } - rtype = VALID; + rtype = UPD_VALID; } xfree(cur); redraw_later(wp, rtype); @@ -282,7 +287,7 @@ void clear_matches(win_T *wp) xfree(wp->w_match_head); wp->w_match_head = m; } - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); } /// Get match from ID 'id' in window 'wp'. @@ -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; @@ -854,7 +859,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **wi } /// "clearmatches()" function -void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_clearmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *win = get_optional_window(argvars, 0); @@ -864,7 +869,7 @@ void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getmatches()" function -void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { matchitem_T *cur; int i; @@ -919,7 +924,7 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setmatches()" function -void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *d; list_T *s = NULL; @@ -1022,7 +1027,7 @@ void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchadd()" function -void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char grpbuf[NUMBUFLEN]; char patbuf[NUMBUFLEN]; @@ -1064,7 +1069,7 @@ void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchaddpo()" function -void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchaddpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -1115,7 +1120,7 @@ void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matcharg()" function -void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matcharg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int id = (int)tv_get_number(&argvars[0]); @@ -1138,7 +1143,7 @@ void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchdelete()" function -void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchdelete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *win = get_optional_window(argvars, 1); if (win == NULL) { @@ -1178,7 +1183,7 @@ void ex_match(exarg_T *eap) && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { end = (char_u *)eap->arg + 4; } else { - p = skiptowhite((char_u *)eap->arg); + p = (char_u *)skiptowhite(eap->arg); if (!eap->skip) { g = vim_strnsave((char_u *)eap->arg, (size_t)(p - (char_u *)eap->arg)); } @@ -1189,7 +1194,7 @@ void ex_match(exarg_T *eap) semsg(_(e_invarg2), eap->arg); return; } - end = skip_regexp(p + 1, *p, true, NULL); + end = (char_u *)skip_regexp((char *)p + 1, *p, true, NULL); if (!eap->skip) { if (*end != NUL && !ends_excmd(*skipwhite((char *)end + 1))) { xfree(g); @@ -1210,5 +1215,5 @@ void ex_match(exarg_T *eap) *end = (char_u)c; } } - eap->nextcmd = (char *)find_nextcmd(end); + eap->nextcmd = find_nextcmd((char *)end); } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 223b4d6845..b874f0dc94 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)) { @@ -736,21 +754,19 @@ bool utf_composinglike(const char_u *p1, const char_u *p2) /// space at least for #MAX_MCO + 1 elements. /// /// @return leading character. -int utfc_ptr2char(const char_u *p, int *pcc) +int utfc_ptr2char(const char *p_in, int *pcc) { - int len; - int c; - int cc; + uint8_t *p = (uint8_t *)p_in; int i = 0; - c = utf_ptr2char((char *)p); - len = utf_ptr2len((char *)p); + int c = utf_ptr2char((char *)p); + int len = utf_ptr2len((char *)p); // Only accept a composing char when the first char isn't illegal. if ((len > 1 || *p < 0x80) && p[len] >= 0x80 && utf_composinglike(p, p + len)) { - cc = utf_ptr2char((char *)p + len); + int cc = utf_ptr2char((char *)p + len); for (;;) { pcc[i++] = cc; if (i == MAX_MCO) { @@ -864,7 +880,7 @@ int utf_ptr2len_len(const char_u *p, int size) } else { m = len; } - for (i = 1; i < m; ++i) { + for (i = 1; i < m; i++) { if ((p[i] & 0xc0) != 0x80) { return 1; } @@ -872,9 +888,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 +1004,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 +1181,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 +1198,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; } @@ -1576,7 +1593,7 @@ void show_utf8(void) } clen = 0; - for (i = 0; i < len; ++i) { + for (i = 0; i < len; i++) { if (clen == 0) { // start of (composing) character, get its length if (i > 0) { @@ -1587,7 +1604,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; @@ -1613,14 +1630,14 @@ int utf_head_off(const char_u *base, const char_u *p) // Skip backwards over trailing bytes: 10xx.xxxx // Skip backwards again if on a composing char. const char_u *q; - for (q = p;; --q) { + for (q = p;; q--) { // Move s to the last byte of this char. const char_u *s; - for (s = q; (s[1] & 0xc0) == 0x80; ++s) {} + for (s = q; (s[1] & 0xc0) == 0x80; s++) {} // 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 +1658,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 +1817,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; @@ -1913,7 +1930,7 @@ void utf_find_illegal(void) char_u *tofree = NULL; vimconv.vc_type = CONV_NONE; - if (enc_canon_props(curbuf->b_p_fenc) & ENC_8BIT) { + if (enc_canon_props((char_u *)curbuf->b_p_fenc) & ENC_8BIT) { // 'encoding' is "utf-8" but we are editing a 8-bit encoded file, // possibly a utf-8 file with illegal bytes. Setup for conversion // from utf-8 to 'fileencoding'. @@ -1925,7 +1942,7 @@ void utf_find_illegal(void) p = get_cursor_pos_ptr(); if (vimconv.vc_type != CONV_NONE) { xfree(tofree); - tofree = string_convert(&vimconv, p, NULL); + tofree = (char_u *)string_convert(&vimconv, (char *)p, NULL); if (tofree == NULL) { break; } @@ -1956,7 +1973,7 @@ void utf_find_illegal(void) if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { break; } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; } @@ -1970,8 +1987,7 @@ theend: } /// @return true if string "s" is a valid utf-8 string. -/// When "end" is NULL stop at the first NUL. -/// When "end" is positive stop there. +/// When "end" is NULL stop at the first NUL. Otherwise stop at "end". bool utf_valid_string(const char_u *s, const char_u *end) { const char_u *p = s; @@ -2128,10 +2144,8 @@ const char *mb_unescape(const char **const pp) return NULL; } -/* - * Skip the Vim specific head of a 'encoding' name. - */ -char_u *enc_skip(char_u *p) +/// Skip the Vim specific head of a 'encoding' name. +char *enc_skip(char *p) { if (STRNCMP(p, "2byte-", 6) == 0) { return p + 6; @@ -2142,27 +2156,25 @@ char_u *enc_skip(char_u *p) return p; } -/* - * Find the canonical name for encoding "enc". - * When the name isn't recognized, returns "enc" itself, but with all lower - * case characters and '_' replaced with '-'. - * Returns an allocated string. - */ -char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET +/// Find the canonical name for encoding "enc". +/// When the name isn't recognized, returns "enc" itself, but with all lower +/// case characters and '_' replaced with '-'. +/// +/// @return an allocated string. +char *enc_canonize(char *enc) + FUNC_ATTR_NONNULL_RET { char_u *p, *s; - int i; - if (STRCMP(enc, "default") == 0) { // Use the default encoding as found by set_init_1(). - return vim_strsave(fenc_default); + return (char *)vim_strsave(fenc_default); } // copy "enc" to allocated memory, with room for two '-' char_u *r = xmalloc(STRLEN(enc) + 3); // Make it all lower case and replace '_' with '-'. p = r; - for (s = enc; *s != NUL; ++s) { + for (s = (char_u *)enc; *s != NUL; s++) { if (*s == '_') { *p++ = '-'; } else { @@ -2172,7 +2184,7 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET *p = NUL; // Skip "2byte-" and "8bit-". - p = enc_skip(r); + p = (char_u *)enc_skip((char *)r); // Change "microsoft-cp" to "cp". Used in some spell files. if (STRNCMP(p, "microsoft-cp", 12) == 0) { @@ -2196,6 +2208,7 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET STRMOVE(p + 5, p + 6); } + int i; if (enc_canon_search(p) >= 0) { // canonical name can be used unmodified if (p != r) { @@ -2206,7 +2219,7 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET xfree(r); r = vim_strsave((char_u *)enc_canon_table[i].name); } - return r; + return (char *)r; } /// Search for an encoding alias of "name". @@ -2215,7 +2228,7 @@ static int enc_alias_search(const char_u *name) { int i; - for (i = 0; enc_alias_table[i].name != NULL; ++i) { + for (i = 0; enc_alias_table[i].name != NULL; i++) { if (STRCMP(name, enc_alias_table[i].name) == 0) { return enc_alias_table[i].canon; } @@ -2291,7 +2304,7 @@ enc_locale_copy_enc: buf[i] = NUL; } - return enc_canonize((char_u *)buf); + return (char_u *)enc_canonize(buf); } #if defined(HAVE_ICONV) @@ -2314,7 +2327,7 @@ void *my_iconv_open(char_u *to, char_u *from) if (iconv_working == kBroken) { return (void *)-1; // detected a broken iconv() previously } - fd = iconv_open((char *)enc_skip(to), (char *)enc_skip(from)); + fd = iconv_open(enc_skip((char *)to), enc_skip((char *)from)); if (fd != (iconv_t)-1 && iconv_working == kUnknown) { /* @@ -2425,18 +2438,17 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen #endif // HAVE_ICONV -/* - * Setup "vcp" for conversion from "from" to "to". - * The names must have been made canonical with enc_canonize(). - * vcp->vc_type must have been initialized to CONV_NONE. - * Note: cannot be used for conversion from/to ucs-2 and ucs-4 (will use utf-8 - * instead). - * Afterwards invoke with "from" and "to" equal to NULL to cleanup. - * Return FAIL when conversion is not supported, OK otherwise. - */ -int convert_setup(vimconv_T *vcp, char_u *from, char_u *to) +/// Setup "vcp" for conversion from "from" to "to". +/// The names must have been made canonical with enc_canonize(). +/// vcp->vc_type must have been initialized to CONV_NONE. +/// Note: cannot be used for conversion from/to ucs-2 and ucs-4 (will use utf-8 +/// instead). +/// Afterwards invoke with "from" and "to" equal to NULL to cleanup. +/// +/// @return FAIL when conversion is not supported, OK otherwise. +int convert_setup(vimconv_T *vcp, char *from, char *to) { - return convert_setup_ext(vcp, from, true, to, true); + return convert_setup_ext(vcp, (char_u *)from, true, (char_u *)to, true); } /// As convert_setup(), but only when from_unicode_is_utf8 is true will all @@ -2509,16 +2521,14 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, c return OK; } -/* - * Convert text "ptr[*lenp]" according to "vcp". - * Returns the result in allocated memory and sets "*lenp". - * When "lenp" is NULL, use NUL terminated strings. - * Illegal chars are often changed to "?", unless vcp->vc_fail is set. - * When something goes wrong, NULL is returned and "*lenp" is unchanged. - */ -char_u *string_convert(const vimconv_T *const vcp, char_u *ptr, size_t *lenp) +/// Convert text "ptr[*lenp]" according to "vcp". +/// Returns the result in allocated memory and sets "*lenp". +/// When "lenp" is NULL, use NUL terminated strings. +/// Illegal chars are often changed to "?", unless vcp->vc_fail is set. +/// When something goes wrong, NULL is returned and "*lenp" is unchanged. +char *string_convert(const vimconv_T *const vcp, char *ptr, size_t *lenp) { - return string_convert_ext(vcp, ptr, lenp, NULL); + return (char *)string_convert_ext(vcp, (char_u *)ptr, lenp, NULL); } /* @@ -2548,7 +2558,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp case CONV_TO_UTF8: // latin1 to utf-8 conversion retval = xmalloc(len * 2 + 1); d = retval; - for (size_t i = 0; i < len; ++i) { + for (size_t i = 0; i < len; i++) { c = ptr[i]; if (c < 0x80) { *d++ = (char_u)c; @@ -2566,7 +2576,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp case CONV_9_TO_UTF8: // latin9 to utf-8 conversion retval = xmalloc(len * 3 + 1); d = retval; - for (size_t i = 0; i < len; ++i) { + for (size_t i = 0; i < len; i++) { c = ptr[i]; switch (c) { case 0xa4: @@ -2678,3 +2688,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, EvalFuncData 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(UPD_NOT_VALID); +} + +void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData 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..9446aaee4f 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -77,7 +77,7 @@ /// /// @return - The open memory file, on success. /// - NULL, on failure (e.g. file does not exist). -memfile_T *mf_open(char_u *fname, int flags) +memfile_T *mf_open(char *fname, int flags) { memfile_T *mfp = xmalloc(sizeof(memfile_T)); @@ -148,7 +148,7 @@ memfile_T *mf_open(char_u *fname, int flags) /// /// @return OK On success. /// FAIL If file could not be opened. -int mf_open_file(memfile_T *mfp, char_u *fname) +int mf_open_file(memfile_T *mfp, char *fname) { if (mf_do_open(mfp, fname, O_RDWR | O_CREAT | O_EXCL)) { mfp->mf_dirty = true; @@ -749,9 +749,9 @@ void mf_free_fnames(memfile_T *mfp) /// /// Only called when creating or renaming the swapfile. Either way it's a new /// name so we must work out the full path name. -void mf_set_fnames(memfile_T *mfp, char_u *fname) +void mf_set_fnames(memfile_T *mfp, char *fname) { - mfp->mf_fname = fname; + mfp->mf_fname = (char_u *)fname; mfp->mf_ffname = (char_u *)FullName_save((char *)mfp->mf_fname, false); } @@ -779,7 +779,7 @@ bool mf_need_trans(memfile_T *mfp) /// /// @param flags Flags for open(). /// @return A bool indicating success of the `open` call. -static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) +static bool mf_do_open(memfile_T *mfp, char *fname, int flags) { // fname cannot be NameBuff, because it must have been allocated. mf_set_fnames(mfp, fname); @@ -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..b0b6b675cb 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. - */ + // Try all directories in the 'directory' option. dirp = 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; @@ -432,7 +430,7 @@ void ml_setname(buf_T *buf) if (vim_rename(mfp->mf_fname, fname) == 0) { success = true; mf_free_fnames(mfp); - mf_set_fnames(mfp, fname); + mf_set_fnames(mfp, (char *)fname); ml_upd_block0(buf, UB_SAME_DIR); 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 @@ -485,15 +483,13 @@ void ml_open_file(buf_T *buf) if (buf->b_spell) { fname = vim_tempname(); if (fname != NULL) { - (void)mf_open_file(mfp, fname); // consumes fname! + (void)mf_open_file(mfp, (char *)fname); // consumes fname! } buf->b_may_swap = false; return; } - /* - * Try all directories in 'directory' option. - */ + // Try all directories in 'directory' option. dirp = p_dir; bool found_existing_dir = false; for (;;) { @@ -503,15 +499,14 @@ 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 } if (fname == NULL) { continue; } - if (mf_open_file(mfp, fname) == OK) { // consumes fname! + if (mf_open_file(mfp, (char *)fname) == OK) { // consumes fname! ml_upd_block0(buf, UB_SAME_DIR); // Flush block zero, so others can read it @@ -528,7 +523,7 @@ void ml_open_file(buf_T *buf) } if (*p_dir != NUL && mfp->mf_fname == NULL) { - need_wait_return = true; // call wait_return later + need_wait_return = true; // call wait_return() later no_wait_return++; (void)semsg(_("E303: Unable to open swap file for \"%s\", recovery impossible"), buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname); @@ -557,7 +552,7 @@ void check_need_swap(bool newfile) /// Close memline for buffer 'buf'. /// -/// @param del_file if TRUE, delete the swap file +/// @param del_file if true, delete the swap file void ml_close(buf_T *buf, int del_file) { if (buf->b_ml.ml_mfp == NULL) { // not open @@ -595,7 +590,7 @@ void ml_close_notmod(void) { FOR_ALL_BUFFERS(buf) { if (!bufIsChanged(buf)) { - ml_close(buf, TRUE); // close all not-modified buffers + ml_close(buf, true); // close all not-modified buffers } } } @@ -726,7 +721,7 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) b0p->b0_flags &= (uint8_t) ~B0_HAS_FENC; } else { memmove((char *)b0p->b0_fname + size - n, - (char *)buf->b_p_fenc, (size_t)n); + buf->b_p_fenc, (size_t)n); *(b0p->b0_fname + size - n - 1) = NUL; b0p->b0_flags |= B0_HAS_FENC; } @@ -769,7 +764,7 @@ void ml_recover(bool checkext) int attr; int orig_file_status = NOTDONE; - recoverymode = TRUE; + recoverymode = true; called_from_main = (curbuf->b_ml.ml_mfp == NULL); attr = HL_ATTR(HLF_E); @@ -790,7 +785,7 @@ void ml_recover(bool checkext) directly = false; // count the number of matching swap files - len = recover_names(fname, FALSE, 0, NULL); + len = recover_names(fname, false, 0, NULL); if (len == 0) { // no swap files found semsg(_("E305: No swap file found for %s"), fname); goto theend; @@ -799,16 +794,16 @@ void ml_recover(bool checkext) i = 1; } else { // several swap files found, choose // list the names of the swap files - (void)recover_names(fname, TRUE, 0, NULL); + (void)recover_names(fname, true, 0, NULL); msg_putchar('\n'); msg_puts(_("Enter number of swap file to use (0 to quit): ")); - i = get_number(FALSE, NULL); + i = get_number(false, NULL); if (i < 1 || i > len) { goto theend; } } // get the swap file name that will be used - (void)recover_names(fname, FALSE, i, &fname_used); + (void)recover_names(fname, false, i, &fname_used); } if (fname_used == NULL) { goto theend; // user chose invalid number. @@ -840,7 +835,7 @@ void ml_recover(bool checkext) */ p = vim_strsave(fname_used); // save "fname_used" for the message: // mf_open() will consume "fname_used"! - mfp = mf_open(fname_used, O_RDONLY); + mfp = mf_open((char *)fname_used, O_RDONLY); fname_used = p; if (mfp == NULL || mfp->mf_fd < 0) { semsg(_("E306: Cannot open %s"), fname_used); @@ -862,7 +857,7 @@ void ml_recover(bool checkext) if ((hp = mf_get(mfp, 0, 1)) == NULL) { msg_start(); msg_puts_attr(_("Unable to read block 0 from "), attr | MSG_HIST); - msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, attr | MSG_HIST); msg_puts_attr(_("\nMaybe no changes were made or Vim did not update the swap file."), attr | MSG_HIST); msg_end(); @@ -871,7 +866,7 @@ void ml_recover(bool checkext) b0p = hp->bh_data; if (STRNCMP(b0p->b0_version, "VIM 3.0", 7) == 0) { msg_start(); - msg_outtrans_attr(mfp->mf_fname, MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, MSG_HIST); msg_puts_attr(_(" cannot be used with this version of Vim.\n"), MSG_HIST); msg_puts_attr(_("Use Vim version 3.0.\n"), MSG_HIST); @@ -884,7 +879,7 @@ void ml_recover(bool checkext) } if (b0_magic_wrong(b0p)) { msg_start(); - msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, attr | MSG_HIST); msg_puts_attr(_(" cannot be used on this computer.\n"), attr | MSG_HIST); msg_puts_attr(_("The file was created on "), attr | MSG_HIST); @@ -906,7 +901,7 @@ void ml_recover(bool checkext) mf_new_page_size(mfp, (unsigned)char_to_long(b0p->b0_page_size)); if (mfp->mf_page_size < previous_page_size) { msg_start(); - msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); + msg_outtrans_attr((char *)mfp->mf_fname, attr | MSG_HIST); msg_puts_attr(_(" has been damaged (page size is smaller than minimum value).\n"), attr | MSG_HIST); msg_end(); @@ -927,11 +922,9 @@ void ml_recover(bool checkext) b0p = hp->bh_data; } - /* - * If .swp file name given directly, use name from swap file for buffer. - */ + // If .swp file name given directly, use name from swap file for buffer. if (directly) { - expand_env(b0p->b0_fname, NameBuff, MAXPATHL); + expand_env((char *)b0p->b0_fname, NameBuff, MAXPATHL); if (setfname(curbuf, (char *)NameBuff, NULL, true) == FAIL) { goto theend; } @@ -998,7 +991,7 @@ void ml_recover(bool checkext) set_fileformat(b0_ff - 1, OPT_LOCAL); } if (b0_fenc != NULL) { - set_option_value("fenc", 0L, (char *)b0_fenc, OPT_LOCAL); + set_option_value_give_err("fenc", 0L, (char *)b0_fenc, OPT_LOCAL); xfree(b0_fenc); } unchanged(curbuf, true, true); @@ -1040,7 +1033,7 @@ void ml_recover(bool checkext) if (pp->pb_id == PTR_ID) { // it is a pointer block // check line count when using pointer block first time if (idx == 0 && line_count != 0) { - for (i = 0; i < (int)pp->pb_count; ++i) { + for (i = 0; i < (int)pp->pb_count; i++) { line_count -= pp->pb_pointer[i].pe_line_count; } if (line_count != 0) { @@ -1076,7 +1069,7 @@ void ml_recover(bool checkext) ml_append(lnum++, _("???LINES MISSING"), (colnr_T)0, true); } - ++idx; // get same block again for next index + idx++; // get same block again for next index continue; } @@ -1136,12 +1129,12 @@ void ml_recover(bool checkext) has_error = true; } - for (i = 0; i < dp->db_line_count; ++i) { + for (i = 0; i < dp->db_line_count; i++) { txt_start = (dp->db_index[i] & DB_INDEX_MASK); 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; } @@ -1182,7 +1175,7 @@ void ml_recover(bool checkext) buf_inc_changedtick(curbuf); } } else { - for (idx = 1; idx <= lnum; ++idx) { + for (idx = 1; idx <= lnum; idx++) { // Need to copy one line, fetching the other one may flush it. p = vim_strsave(ml_get(idx)); i = STRCMP(p, ml_get(idx + lnum)); @@ -1206,14 +1199,14 @@ void ml_recover(bool checkext) curbuf->b_flags |= BF_RECOVERED; check_cursor(); - recoverymode = FALSE; + recoverymode = false; 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 { @@ -1227,11 +1220,11 @@ void ml_recover(bool checkext) msg_puts(_("\nYou may want to delete the .swp file now.\n\n")); cmdline_row = msg_row; } - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); theend: xfree(fname_used); - recoverymode = FALSE; + recoverymode = false; if (mfp != NULL) { if (hp != NULL) { mf_put(mfp, hp, false, false); @@ -1243,7 +1236,7 @@ theend: xfree(buf); } if (serious_error && called_from_main) { - ml_close(curbuf, TRUE); + ml_close(curbuf, true); } else { apply_autocmds(EVENT_BUFREADPOST, NULL, curbuf->b_fname, false, curbuf); apply_autocmds(EVENT_BUFWINENTER, NULL, curbuf->b_fname, false, curbuf); @@ -1260,7 +1253,7 @@ theend: /// - find the name of the n'th swap file when recovering /// /// @param fname base for swap file name -/// @param list when TRUE, list the swap file names +/// @param list when true, list the swap file names /// @param nr when non-zero, return nr'th swap file name /// @param fname_out result when "nr" > 0 int recover_names(char_u *fname, int list, int nr, char_u **fname_out) @@ -1272,7 +1265,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 @@ -1304,7 +1297,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) // 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) { @@ -1315,7 +1308,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) names[2] = xstrdup(".sw?"); num_names = 3; } else { - num_names = recov_file_names(names, fname_res, TRUE); + num_names = recov_file_names(names, fname_res, true); } } else { // check directory dir_name if (fname == NULL) { @@ -1338,7 +1331,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) tail = (char_u *)path_tail((char *)fname_res); tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, true); } - num_names = recov_file_names(names, tail, FALSE); + num_names = recov_file_names(names, tail, false); xfree(tail); } } @@ -1384,7 +1377,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) if (--num_files == 0) { xfree(files); } else { - for (; i < num_files; ++i) { + for (; i < num_files; i++) { files[i] = files[i + 1]; } } @@ -1395,7 +1388,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) { @@ -1411,7 +1404,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) } if (num_files) { - for (int i = 0; i < num_files; ++i) { + for (int i = 0; i < num_files; i++) { // print the swap file name msg_outnum((long)++file_count); msg_puts(". "); @@ -1427,7 +1420,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) file_count += num_files; } - for (int i = 0; i < num_names; ++i) { + for (int i = 0; i < num_names; i++) { xfree(names[i]); } if (num_files > 0) { @@ -1452,7 +1445,7 @@ char *make_percent_swname(const char *dir, const char *name) *d = '%'; } } - d = concat_fnames(dir, s, TRUE); + d = concat_fnames(dir, s, true); xfree(s); xfree(f); } @@ -1660,12 +1653,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; @@ -1673,8 +1666,8 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) /// sync all memlines /// -/// @param check_file if TRUE, check if original file exists and was not changed. -/// @param check_char if TRUE, stop syncing when character becomes available, but +/// @param check_file if true, check if original file exists and was not changed. +/// @param check_char if true, stop syncing when character becomes available, but /// /// always sync at least one block. void ml_sync_all(int check_file, int check_char, bool do_fsync) @@ -1719,7 +1712,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) /// Used for the :preserve command and when the original file has been /// changed or deleted. /// -/// @param message if TRUE, the success of preserving is reported. +/// @param message if true, the success of preserving is reported. void ml_preserve(buf_T *buf, int message, bool do_fsync) { bhdr_T *hp; @@ -1913,7 +1906,7 @@ int ml_line_alloced(void) /// "line" does not need to be allocated, but can't be another line in a /// buffer, unlocking may make it invalid. /// -/// newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum +/// newfile: true when starting to edit a new file, meaning that pe_old_lnum /// will be set for recovery /// Check: The caller of this function should probably also call /// appended_lines(). @@ -1927,7 +1920,7 @@ int ml_line_alloced(void) int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) { // When starting up, we might still need to create the memfile - if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) { + if (curbuf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) { return FAIL; } @@ -1954,7 +1947,7 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool new if (buf->b_ml.ml_line_lnum != 0) { ml_flush_line(buf); } - return ml_append_int(buf, lnum, line, len, newfile, FALSE); + return ml_append_int(buf, lnum, line, len, newfile, false); } /// @param lnum append after this line (can be 0) @@ -2043,7 +2036,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b dp = hp->bh_data; } - ++buf->b_ml.ml_line_count; + buf->b_ml.ml_line_count++; if ((int)dp->db_free >= space_needed) { // enough room in data block /* @@ -2184,7 +2177,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 +2217,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 @@ -2291,7 +2284,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b &pp->pb_pointer[pb_idx + 1], (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN)); } - ++pp->pb_count; + pp->pb_count++; pp->pb_pointer[pb_idx].pe_line_count = line_count_left; pp->pb_pointer[pb_idx].pe_bnum = bnum_left; pp->pb_pointer[pb_idx].pe_page_count = page_count_left; @@ -2458,7 +2451,7 @@ int ml_replace(linenr_T lnum, char *line, bool copy) /// Do not use it after calling ml_replace(). /// /// Check: The caller of this function should probably also call -/// changed_lines(), unless update_screen(NOT_VALID) is used. +/// changed_lines(), unless update_screen(UPD_NOT_VALID) is used. /// /// @return FAIL for failure, OK otherwise int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) @@ -2566,7 +2559,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2; idx = lnum - buf->b_ml.ml_locked_low; - --buf->b_ml.ml_line_count; + buf->b_ml.ml_line_count--; line_start = ((dp->db_index[idx]) & DB_INDEX_MASK); if (idx == 0) { // first line in block, text at the end @@ -2710,7 +2703,7 @@ linenr_T ml_firstmarked(void) dp = hp->bh_data; for (i = lnum - curbuf->b_ml.ml_locked_low; - lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) { + lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { if ((dp->db_index[i]) & DB_MARKED) { (dp->db_index[i]) &= DB_INDEX_MASK; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; @@ -2750,7 +2743,7 @@ void ml_clearmarked(void) dp = hp->bh_data; for (i = lnum - curbuf->b_ml.ml_locked_low; - lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) { + lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { if ((dp->db_index[i]) & DB_MARKED) { (dp->db_index[i]) &= DB_INDEX_MASK; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; @@ -2970,7 +2963,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) high = buf->b_ml.ml_line_count; if (action == ML_FIND) { // first try stack entries - for (top = buf->b_ml.ml_stack_top - 1; top >= 0; --top) { + for (top = buf->b_ml.ml_stack_top - 1; top >= 0; top--) { ip = &(buf->b_ml.ml_stack[top]); if (ip->ip_low <= lnum && ip->ip_high >= lnum) { bnum = ip->ip_bnum; @@ -2998,9 +2991,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; @@ -3026,8 +3019,8 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) ip->ip_high = high; ip->ip_index = -1; // index not known yet - dirty = FALSE; - for (idx = 0; idx < (int)pp->pb_count; ++idx) { + dirty = false; + for (idx = 0; idx < (int)pp->pb_count; idx++) { t = pp->pb_pointer[idx].pe_line_count; CHECK(t == 0, _("pe_line_count is zero")); if ((low += t) > lnum) { @@ -3045,7 +3038,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) if (bnum != bnum2) { bnum = bnum2; pp->pb_pointer[idx].pe_bnum = bnum; - dirty = TRUE; + dirty = true; } } @@ -3063,10 +3056,10 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } if (action == ML_DELETE) { pp->pb_pointer[idx].pe_line_count--; - dirty = TRUE; + dirty = true; } else if (action == ML_INSERT) { pp->pb_pointer[idx].pe_line_count++; - dirty = TRUE; + dirty = true; } mf_put(mfp, hp, dirty, false); } @@ -3124,7 +3117,7 @@ static void ml_lineadd(buf_T *buf, int count) memfile_T *mfp = buf->b_ml.ml_mfp; bhdr_T *hp; - for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; --idx) { + for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; idx--) { ip = &(buf->b_ml.ml_stack[idx]); if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { break; @@ -3208,7 +3201,7 @@ int resolve_symlink(const char_u *fname, char_u *buf) * be consistent even when opening a relative symlink from different * working directories. */ - return vim_FullName((char *)tmp, (char *)buf, MAXPATHL, TRUE); + return vim_FullName((char *)tmp, (char *)buf, MAXPATHL, true); } #endif @@ -3280,7 +3273,7 @@ char_u *get_file_in_dir(char_u *fname, char_u *dname) retval = vim_strsave(fname); } else if (dname[0] == '.' && vim_ispathsep(dname[1])) { if (tail == fname) { // no path before file name - retval = (char_u *)concat_fnames((char *)dname + 2, (char *)tail, TRUE); + retval = (char_u *)concat_fnames((char *)dname + 2, (char *)tail, true); } else { save_char = *tail; *tail = NUL; @@ -3290,7 +3283,7 @@ char_u *get_file_in_dir(char_u *fname, char_u *dname) xfree(t); } } else { - retval = (char_u *)concat_fnames((char *)dname, (char *)tail, TRUE); + retval = (char_u *)concat_fnames((char *)dname, (char *)tail, true); } return retval; @@ -3304,7 +3297,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 +3332,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. @@ -3460,7 +3453,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ && !buf->b_help && !(buf->b_flags & BF_DUMMY)) { int fd; struct block0 b0; - int differ = FALSE; + int differ = false; // Try to read block 0 from the swap file to get the original // file name (and inode number). @@ -3477,19 +3470,19 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // Symlinks may point to the same file even // when the name differs, need to check the // inode too. - expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, + expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL); + if (fnamecmp_ino((char_u *)buf->b_ffname, (char_u *)NameBuff, char_to_long(b0.b0_ino))) { - differ = TRUE; + differ = true; } } } else { // The name in the swap file may be // "~user/path/file". Expand it first. - expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, + expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL); + if (fnamecmp_ino((char_u *)buf->b_ffname, (char_u *)NameBuff, char_to_long(b0.b0_ino))) { - differ = TRUE; + differ = true; } } } @@ -3499,7 +3492,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // give the ATTENTION message when there is an old swap file // for the current file, and the buffer was not recovered. if (differ == false && !(curbuf->b_flags & BF_RECOVERED) - && vim_strchr((char *)p_shm, SHM_ATTENTION) == NULL) { + && vim_strchr(p_shm, SHM_ATTENTION) == NULL) { int choice = 0; process_still_running = false; @@ -3569,7 +3562,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (choice > 0) { switch (choice) { case 1: - buf->b_p_ro = TRUE; + buf->b_p_ro = true; break; case 2: break; @@ -3584,7 +3577,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ break; case 6: swap_exists_action = SEA_QUIT; - got_int = TRUE; + got_int = true; break; } @@ -3615,13 +3608,13 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ XFREE_CLEAR(fname); break; } - --fname[n - 2]; // ".svz", ".suz", etc. + fname[n - 2]--; // ".svz", ".suz", etc. fname[n - 1] = 'z' + 1; } - --fname[n - 1]; // ".swo", ".swn", etc. + fname[n - 1]--; // ".swo", ".swn", etc. } - if (os_isdir((char_u *)dir_name)) { + if (os_isdir(dir_name)) { *found_existing_dir = true; } else if (!*found_existing_dir && **dirp == NUL) { int ret; @@ -3672,18 +3665,18 @@ static int b0_magic_wrong(ZERO_BL *b0p) /// /// current file doesn't exist, file for swap file exist, file name(s) not /// available -> probably different -/// == 0 != 0 FAIL X TRUE -/// == 0 != 0 X FAIL TRUE +/// == 0 != 0 FAIL X true +/// == 0 != 0 X FAIL true /// /// current file exists, inode for swap unknown, file name(s) not /// available -> probably different -/// != 0 == 0 FAIL X TRUE -/// != 0 == 0 X FAIL TRUE +/// != 0 == 0 FAIL X true +/// != 0 == 0 X FAIL true /// /// current file doesn't exist, inode for swap unknown, one file name not /// available -> probably different -/// == 0 == 0 FAIL OK TRUE -/// == 0 == 0 OK FAIL TRUE +/// == 0 == 0 FAIL OK true +/// == 0 == 0 OK FAIL true /// /// current file doesn't exist, inode for swap unknown, both file names not /// available -> compare file names @@ -3727,8 +3720,8 @@ static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0) * One of the inode numbers is unknown, try a forced vim_FullName() and * compare the file names. */ - retval_c = vim_FullName((char *)fname_c, (char *)buf_c, MAXPATHL, TRUE); - retval_s = vim_FullName((char *)fname_s, (char *)buf_s, MAXPATHL, TRUE); + retval_c = vim_FullName((char *)fname_c, (char *)buf_c, MAXPATHL, true); + retval_s = vim_FullName((char *)fname_s, (char *)buf_s, MAXPATHL, true); if (retval_c == OK && retval_s == OK) { return STRCMP(buf_c, buf_s) != 0; } @@ -4168,7 +4161,7 @@ void goto_byte(long cnt) curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (colnr_T)boff; curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } check_cursor(); diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 5ae7f7bbe3..a9785fcb7c 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" @@ -59,6 +60,8 @@ void try_to_free_memory(void) // Try to save all buffers and release as many blocks as possible mf_release_all(); + arena_free_reuse_blks(); + trying_to_free = false; } @@ -527,21 +530,19 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) } #define ARENA_BLOCK_SIZE 4096 +#define REUSE_MAX 4 + +static struct consumed_blk *arena_reuse_blk; +static size_t arena_reuse_blk_count = 0; -void arena_start(Arena *arena, ArenaMem *reuse_blk) +static void arena_free_reuse_blks(void) { - if (reuse_blk && *reuse_blk) { - arena->cur_blk = (char *)(*reuse_blk); - *reuse_blk = NULL; - } else { - arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + while (arena_reuse_blk_count > 0) { + struct consumed_blk *blk = arena_reuse_blk; + arena_reuse_blk = arena_reuse_blk->prev; + xfree(blk); + arena_reuse_blk_count--; } - arena->pos = 0; - arena->size = ARENA_BLOCK_SIZE; - // address is the same as as (struct consumed_blk *)arena->cur_blk - struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); - assert((char *)blk == (char *)arena->cur_blk); - blk->prev = NULL; } /// Finnish the allocations in an arena. @@ -557,17 +558,45 @@ ArenaMem arena_finish(Arena *arena) return res; } +void alloc_block(Arena *arena) +{ + struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; + if (arena_reuse_blk_count > 0) { + arena->cur_blk = (char *)arena_reuse_blk; + arena_reuse_blk = arena_reuse_blk->prev; + arena_reuse_blk_count--; + } else { + arena_alloc_count++; + arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + } + arena->pos = 0; + arena->size = ARENA_BLOCK_SIZE; + struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); + blk->prev = prev_blk; +} + +/// @param arena if NULL, do a global allocation. caller must then free the value! +/// @param size if zero, will still return a non-null pointer, but not a unique one void *arena_alloc(Arena *arena, size_t size, bool align) { + if (!arena) { + return xmalloc(size); + } if (align) { arena->pos = (arena->pos + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1); } - if (arena->pos + size > arena->size) { - if (size > (arena->size - sizeof(struct consumed_blk)) >> 1) { + if (arena->pos + size > arena->size || !arena->cur_blk) { + if (size > (ARENA_BLOCK_SIZE - sizeof(struct consumed_blk)) >> 1) { // if allocation is too big, allocate a large block with the requested // size, but still with block pointer head. We do this even for // arena->size / 2, as there likely is space left for the next // small allocation in the current block. + if (!arena->cur_blk) { + // to simplify free-list management, arena->cur_blk must + // always be a normal, ARENA_BLOCK_SIZE sized, block + alloc_block(arena); + } + arena_alloc_count++; char *alloc = xmalloc(size + sizeof(struct consumed_blk)); struct consumed_blk *cur_blk = (struct consumed_blk *)arena->cur_blk; struct consumed_blk *fix_blk = (struct consumed_blk *)alloc; @@ -575,12 +604,7 @@ void *arena_alloc(Arena *arena, size_t size, bool align) cur_blk->prev = fix_blk; return (alloc + sizeof(struct consumed_blk)); } else { - struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; - arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); - arena->pos = 0; - arena->size = ARENA_BLOCK_SIZE; - struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); - blk->prev = prev_blk; + alloc_block(arena); } } @@ -589,15 +613,17 @@ void *arena_alloc(Arena *arena, size_t size, bool align) return mem; } -void arena_mem_free(ArenaMem mem, ArenaMem *reuse_blk) +void arena_mem_free(ArenaMem mem) { struct consumed_blk *b = mem; // peel of the first block, as it is guaranteed to be ARENA_BLOCK_SIZE, // not a custom fix_blk - if (reuse_blk && *reuse_blk == NULL && b != NULL) { - *reuse_blk = b; + if (arena_reuse_blk_count < REUSE_MAX && b != NULL) { + struct consumed_blk *reuse_blk = b; b = b->prev; - (*reuse_blk)->prev = NULL; + reuse_blk->prev = arena_reuse_blk; + arena_reuse_blk = reuse_blk; + arena_reuse_blk_count++; } while (b) { @@ -617,8 +643,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 +654,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 +668,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" @@ -797,6 +824,9 @@ void free_all_mem(void) nlua_free_all_mem(); ui_free_all_mem(); + + // should be last, in case earlier free functions deallocates arenas + arena_free_reuse_blks(); } #endif diff --git a/src/nvim/memory.h b/src/nvim/memory.h index 63d607c2ce..f407192331 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -6,6 +6,8 @@ #include <stdint.h> // for uint8_t #include <time.h> // for time_t +#include "nvim/macros.h" + /// `malloc()` function signature typedef void *(*MemMalloc)(size_t); @@ -37,6 +39,8 @@ extern MemRealloc mem_realloc; extern bool entered_free_all_mem; #endif +EXTERN size_t arena_alloc_count INIT(=0); + typedef struct consumed_blk { struct consumed_blk *prev; } *ArenaMem; @@ -48,7 +52,7 @@ typedef struct { size_t pos, size; } Arena; -// inits an empty arena. use arena_start() to actually allocate space! +// inits an empty arena. #define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 } #define kv_fixsize_arena(a, v, s) \ diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 16802a4e50..cb6918cd27 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" @@ -419,7 +419,7 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const p = (call_data == NULL) ? NULL : xstrdup(call_data); // loop over all modes, may add more than one - for (i = 0; i < MENU_MODES; ++i) { + for (i = 0; i < MENU_MODES; i++) { if (modes & (1 << i)) { // free any old menu free_menu_string(menu, i); @@ -853,7 +853,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) msg_puts(" "); } // Same highlighting as for directories!? - msg_outtrans_attr((char_u *)menu->name, HL_ATTR(HLF_D)); + msg_outtrans_attr(menu->name, HL_ATTR(HLF_D)); } if (menu != NULL && menu->children == NULL) { @@ -914,7 +914,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) */ static vimmenu_T *expand_menu = NULL; static int expand_modes = 0x0; -static int expand_emenu; // TRUE for ":emenu" command +static int expand_emenu; // true for ":emenu" command /* * Work out what to complete when doing command line completion of menu names. @@ -933,7 +933,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for xp->xp_context = EXPAND_UNSUCCESSFUL; // Check for priority numbers, enable and disable - for (p = arg; *p; ++p) { + for (p = arg; *p; p++) { if (!ascii_isdigit(*p) && *p != '.') { break; } @@ -952,12 +952,12 @@ 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; - for (; *p && !ascii_iswhite(*p); ++p) { + for (; *p && !ascii_iswhite(*p); p++) { if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { p++; } else if (*p == '.') { @@ -1056,7 +1056,7 @@ char *get_menu_name(expand_T *xp, int idx) } else { str = menu->dname; if (menu->en_dname == NULL) { - should_advance = TRUE; + should_advance = true; } } } else { @@ -1163,10 +1163,8 @@ char *menu_name_skip(char *const name) return p; } -/* - * Return TRUE when "name" matches with menu "menu". The name is compared in - * two ways: raw menu name and menu name without '&'. ignore part after a TAB. - */ +/// Return true when "name" matches with menu "menu". The name is compared in +/// two ways: raw menu name and menu name without '&'. ignore part after a TAB. static bool menu_name_equal(const char *const name, const vimmenu_T *const menu) { if (menu->en_name != NULL @@ -1181,7 +1179,7 @@ static bool menu_namecmp(const char *const name, const char *const mname) { int i; - for (i = 0; name[i] != NUL && name[i] != TAB; ++i) { + for (i = 0; name[i] != NUL && name[i] != TAB; i++) { if (name[i] != mname[i]) { break; } @@ -1405,10 +1403,8 @@ bool menu_is_toolbar(const char *const name) return STRNCMP(name, "ToolBar", 7) == 0; } -/* - * Return TRUE if the name is a menu separator identifier: Starts and ends - * with '-' - */ +/// Return true if the name is a menu separator identifier: Starts and ends +/// with '-' int menu_is_separator(char *name) { return name[0] == '-' && name[STRLEN(name) - 1] == '-'; @@ -1659,26 +1655,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 +1803,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; } @@ -1952,7 +1941,7 @@ static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int m /// "menu_info()" function /// Return information about a menu (including all the child menus) -void f_menu_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_menu_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); dict_T *const retdict = rettv->vval.v_dict; 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..80b5f53f4e 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" @@ -53,7 +56,7 @@ typedef struct msgchunk_S msgchunk_T; struct msgchunk_S { msgchunk_T *sb_next; msgchunk_T *sb_prev; - char sb_eol; // TRUE when line ends after this text + char sb_eol; // true when line ends after this text int sb_msg_col; // column in which text starts int sb_attr; // text attributes char_u sb_text[1]; // text to be displayed, actually longer @@ -63,7 +66,7 @@ struct msgchunk_S { #define DLG_BUTTON_SEP '\n' #define DLG_HOTKEY_CHAR '&' -static int confirm_msg_used = FALSE; // displaying confirm_msg +static int confirm_msg_used = false; // displaying confirm_msg #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.c.generated.h" #endif @@ -75,7 +78,7 @@ MessageHistoryEntry *last_msg_hist = NULL; static int msg_hist_len = 0; static FILE *verbose_fd = NULL; -static int verbose_did_open = FALSE; +static int verbose_did_open = false; bool keep_msg_more = false; // keep_msg was set by msgmore() @@ -99,7 +102,7 @@ bool keep_msg_more = false; // keep_msg was set by msgmore() * msg_scrolled How many lines the screen has been scrolled (because of * messages). Used in update_screen() to scroll the screen * back. Incremented each time the screen scrolls a line. - * msg_scrolled_ign TRUE when msg_scrolled is non-zero and msg_puts_attr() + * msg_scrolled_ign true when msg_scrolled is non-zero and msg_puts_attr() * writes something without scrolling should not make * need_wait_return to be set. This is a hack to make ":ts" * work without an extra prompt. @@ -212,7 +215,7 @@ void msg_grid_validate(void) /// Displays the string 's' on the status line /// When terminal not initialized (yet) mch_errmsg(..) is used. /// -/// @return TRUE if wait_return not called +/// @return true if wait_return() not called int msg(char *s) { return msg_attr_keep(s, 0, false, false); @@ -262,7 +265,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea // Print the rest of the message. We know there is no special // character because strpbrk returned NULL if (*s != NUL) { - msg_outtrans_attr((char_u *)s, attr); + msg_outtrans_attr(s, attr); } } @@ -302,7 +305,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) // Skip messages not match ":filter pattern". // Don't filter when there is an error. - if (!emsg_on_display && message_filtered((char_u *)s)) { + if (!emsg_on_display && message_filtered((char *)s)) { return true; } @@ -316,13 +319,13 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) * break this loop, limit the recursiveness to 3 levels. */ if (entered >= 3) { - return TRUE; + return true; } - ++entered; + entered++; // Add message to history (unless it's a repeated kept message or a // truncated message) - if ((const char_u *)s != keep_msg + if (s != keep_msg || (*s != '<' && last_msg_hist != NULL && last_msg_hist->msg != NULL @@ -332,7 +335,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) // Truncate the message if needed. msg_start(); - buf = msg_strtrunc((char_u *)s, FALSE); + buf = msg_strtrunc((char_u *)s, false); if (buf != NULL) { s = (const char *)buf; } @@ -341,7 +344,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) if (multiline) { msg_multiline_attr(s, attr, false, &need_clear); } else { - msg_outtrans_attr((char_u *)s, attr); + msg_outtrans_attr(s, attr); } if (need_clear) { msg_clr_eos(); @@ -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; } @@ -417,7 +420,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) half = room / 2; // First part: Start of the string. - for (e = 0; len < half && e < buflen; ++e) { + for (e = 0; len < half && e < buflen; e++) { if (s[e] == NUL) { // text fits without truncating! buf[e] = NUL; @@ -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,33 +619,33 @@ 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: +/// @return true if not giving error messages right now: /// If "emsg_off" is set: no error messages at the moment. /// If "msg" is in 'debug': do error message but without side effects. /// If "emsg_skip" is set: never do error messages. int emsg_not_now(void) { - if ((emsg_off > 0 && vim_strchr((char *)p_debug, 'm') == NULL - && vim_strchr((char *)p_debug, 't') == NULL) + if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL + && vim_strchr(p_debug, 't') == NULL) || emsg_skip > 0) { - return TRUE; + return true; } - return FALSE; + return false; } static bool emsg_multiline(const char *s, bool multiline) @@ -645,7 +665,7 @@ static bool emsg_multiline(const char *s, bool multiline) bool severe = emsg_severe; emsg_severe = false; - if (!emsg_off || vim_strchr((char *)p_debug, 't') != NULL) { + if (!emsg_off || vim_strchr(p_debug, 't') != NULL) { // Cause a throw of an error exception if appropriate. Don't display // the error message in this case. (If no matching catch clause will // be found, the message will be displayed later on.) "ignore" is set @@ -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,20 +742,18 @@ 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 - } // wait_return has reset need_wait_return + } // wait_return() has reset need_wait_return // and a redraw is expected because // msg_scrolled is non-zero if (msg_ext_kind == NULL) { 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. @@ -748,7 +766,7 @@ static bool emsg_multiline(const char *s, bool multiline) /// Rings the bell, if appropriate, and calls message() to do the real work /// When terminal not initialized (yet) mch_errmsg(..) is used. /// -/// @return true if wait_return not called +/// @return true if wait_return() not called bool emsg(const char *s) { return emsg_multiline(s, false); @@ -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; } @@ -1055,7 +1086,7 @@ void ex_messages(void *const eap_p) HlMessageChunk chunk = kv_A(p->multiattr, i); Array content_entry = ARRAY_DICT_INIT; ADD(content_entry, INTEGER_OBJ(chunk.attr)); - ADD(content_entry, STRING_OBJ(copy_string(chunk.text))); + ADD(content_entry, STRING_OBJ(copy_string(chunk.text, NULL))); ADD(content, ARRAY_OBJ(content_entry)); } } else if (p->msg && p->msg[0]) { @@ -1100,7 +1131,7 @@ void msg_end_prompt(void) /// Wait for the user to hit a key (normally Enter) /// -/// @param redraw if true, redraw the entire screen NOT_VALID +/// @param redraw if true, redraw the entire screen UPD_NOT_VALID /// if false, do a normal redraw /// if -1, don't redraw at all void wait_return(int redraw) @@ -1112,7 +1143,7 @@ void wait_return(int redraw) FILE *save_scriptout; if (redraw == true) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } // If using ":silent cmd", don't wait for a return. Also don't set @@ -1146,12 +1177,12 @@ void wait_return(int redraw) oldState = State; if (quit_more) { c = CAR; // just pretend CR was hit - quit_more = FALSE; - got_int = FALSE; + quit_more = false; + got_int = false; } else if (exmode_active) { msg_puts(" "); // make sure the cursor is on the right line c = CAR; // no need for a return in ex mode - got_int = FALSE; + got_int = false; } else { // Make sure the hit-return prompt is on screen when 'guioptions' was // just changed. @@ -1216,8 +1247,8 @@ void wait_return(int redraw) } if (quit_more) { c = CAR; // just pretend CR was hit - quit_more = FALSE; - got_int = FALSE; + quit_more = false; + got_int = false; } else if (c != K_IGNORE) { c = K_IGNORE; hit_return_msg(); @@ -1276,7 +1307,7 @@ void wait_return(int redraw) emsg_on_display = false; // can delete error message now lines_left = -1; // reset lines_left at next msg_start() reset_last_sourcing(); - if (keep_msg != NULL && vim_strsize((char *)keep_msg) >= + if (keep_msg != NULL && vim_strsize(keep_msg) >= (Rows - cmdline_row - 1) * Columns + sc_col) { XFREE_CLEAR(keep_msg); // don't redisplay message, it's too long } @@ -1285,7 +1316,7 @@ void wait_return(int redraw) ui_refresh(); } else if (!skip_redraw) { if (redraw == true || (msg_scrolled != 0 && redraw != -1)) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } if (ui_has(kUIMessages)) { msg_ext_clear(true); @@ -1319,7 +1350,7 @@ void set_keep_msg(char *s, int attr) { xfree(keep_msg); if (s != NULL && msg_silent == 0) { - keep_msg = vim_strsave((char_u *)s); + keep_msg = xstrdup(s); } else { keep_msg = NULL; } @@ -1479,7 +1510,7 @@ void msg_home_replace_hl(char_u *fname) static void msg_home_replace_attr(char_u *fname, int attr) { char *name = home_replace_save(NULL, (char *)fname); - msg_outtrans_attr((char_u *)name, attr); + msg_outtrans_attr(name, attr); xfree(name); } @@ -1490,29 +1521,29 @@ static void msg_home_replace_attr(char_u *fname, int attr) /// @return the number of characters it takes on the screen. int msg_outtrans(char *str) { - return msg_outtrans_attr((char_u *)str, 0); + return msg_outtrans_attr(str, 0); } -int msg_outtrans_attr(const char_u *str, int attr) +int msg_outtrans_attr(const char *str, int attr) { - return msg_outtrans_len_attr(str, (int)STRLEN(str), attr); + return msg_outtrans_len_attr((char_u *)str, (int)STRLEN(str), attr); } -int msg_outtrans_len(const char_u *str, int len) +int msg_outtrans_len(const char *str, int len) { - return msg_outtrans_len_attr(str, len, 0); + return msg_outtrans_len_attr((char_u *)str, len, 0); } /// Output one character at "p". /// Handles multi-byte characters. /// /// @return pointer to the next character. -char_u *msg_outtrans_one(char_u *p, int attr) +char *msg_outtrans_one(char *p, int attr) { int l; - if ((l = utfc_ptr2len((char *)p)) > 1) { - msg_outtrans_len_attr(p, l, attr); + if ((l = utfc_ptr2len(p)) > 1) { + msg_outtrans_len_attr((char_u *)p, l, attr); return p + l; } msg_puts_attr((const char *)transchar_byte(*p), attr); @@ -1597,12 +1628,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) return retval; } -void msg_make(char_u *arg) +void msg_make(char *arg) { int i; - static char_u *str = (char_u *)"eeffoc", *rs = (char_u *)"Plon#dqg#vxjduB"; + static char *str = "eeffoc"; + static char *rs = "Plon#dqg#vxjduB"; - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); for (i = 5; *arg && i >= 0; i--) { if (*arg++ != str[i]) { break; @@ -1610,7 +1642,7 @@ void msg_make(char_u *arg) } if (i < 0) { msg_putchar('\n'); - for (i = 0; rs[i]; ++i) { + for (i = 0; rs[i]; i++) { msg_putchar(rs[i] - 3); } } @@ -1622,7 +1654,7 @@ void msg_make(char_u *arg) /// If K_SPECIAL is encountered, then it is taken in conjunction with the /// following character and shown as <F1>, <S-Up> etc. Any other character /// which is not printable shown in <> form. -/// If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. +/// If 'from' is true (lhs of a mapping), a space is shown as <Space>. /// If a character is displayed in one of these special ways, is also /// highlighted (its highlight name is '8' in the p_hl variable). /// Otherwise characters are not highlighted. @@ -1945,13 +1977,13 @@ void msg_prt_line(char_u *s, int list) /// Use grid_puts() to output one multi-byte character. /// /// @return the pointer "s" advanced to the next character. -static char_u *screen_puts_mbyte(char_u *s, int l, int attr) +static char *screen_puts_mbyte(char *s, int l, int attr) { int cw; attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); msg_didout = true; // remember that line is not empty - cw = utf_ptr2cells((char *)s); + cw = utf_ptr2cells(s); if (cw > 1 && (cmdmsg_rl ? msg_col <= 1 : msg_col == Columns - 1)) { // Doesn't fit, print a highlighted '>' to fill it up. @@ -1964,13 +1996,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 +2155,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; @@ -2166,7 +2198,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs // ourselves). if (t_col > 0) { // output postponed text - t_puts(&t_col, t_s, s, attr); + t_puts(&t_col, (char *)t_s, (char *)s, attr); } // When no more prompt and no more room, truncate here @@ -2191,7 +2223,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs } else { l = utfc_ptr2len((char *)s); } - s = screen_puts_mbyte((char_u *)s, l, attr); + s = (char_u *)screen_puts_mbyte((char *)s, l, attr); did_last_char = true; } else { did_last_char = false; @@ -2208,11 +2240,11 @@ 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(); - need_wait_return = true; // may need wait_return in main() + need_wait_return = true; // may need wait_return() in main() redraw_cmdline = true; if (cmdline_row > 0 && !exmode_active) { cmdline_row--; @@ -2223,7 +2255,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) { @@ -2250,12 +2282,12 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs if (t_col > 0 && (wrap || *s == '\r' || *s == '\b' || *s == '\t' || *s == BELL)) { // Output any postponed text. - t_puts(&t_col, t_s, s, attr); + t_puts(&t_col, (char *)t_s, (char *)s, attr); } 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 +2304,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 { @@ -2293,7 +2325,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs // characters and draw them all at once later. if (cmdmsg_rl || (cw > 1 && msg_col + t_col >= Columns - 1)) { if (l > 1) { - s = screen_puts_mbyte((char_u *)s, l, attr) - 1; + s = (char_u *)screen_puts_mbyte((char *)s, l, attr) - 1; } else { msg_screen_putchar(*s, attr); } @@ -2306,15 +2338,15 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs s += l - 1; } } - ++s; + s++; } // Output any postponed text. if (t_col > 0) { - t_puts(&t_col, t_s, s, attr); + t_puts(&t_col, (char *)t_s, (char *)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(); @@ -2322,13 +2354,13 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs /// @return true when ":filter pattern" was used and "msg" does not match /// "pattern". -bool message_filtered(char_u *msg) +bool message_filtered(char *msg) { if (cmdmod.cmod_filter_regmatch.regprog == NULL) { return false; } - bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, (char *)msg, (colnr_T)0); + bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, (colnr_T)0); return cmdmod.cmod_filter_force ? match : !match; } @@ -2447,7 +2479,7 @@ void msg_reset_scroll(void) } } } else { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } msg_scrolled = 0; msg_scrolled_at_flush = 0; @@ -2457,7 +2489,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,15 +2500,15 @@ 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); xfree(tofree); } msg_scrolled++; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } @@ -2497,7 +2529,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; @@ -2526,7 +2558,7 @@ static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int } mp->sb_next = NULL; } else if (finish && last_msgchunk != NULL) { - last_msgchunk->sb_eol = TRUE; + last_msgchunk->sb_eol = true; } *sb_str = s; @@ -2583,7 +2615,7 @@ void sb_text_end_cmdline(void) } /// Clear any text remembered for scrolling back. -/// When "all" is FALSE keep the last line. +/// When "all" is false keep the last line. /// Called when redrawing the screen. void clear_sb_text(int all) { @@ -2618,7 +2650,7 @@ void show_sb_text(void) vim_beep(BO_MESS); } else { do_more_prompt('G'); - wait_return(FALSE); + wait_return(false); } } @@ -2637,7 +2669,7 @@ static msgchunk_T *msg_sb_start(msgchunk_T *mps) void msg_sb_eol(void) { if (last_msgchunk != NULL) { - last_msgchunk->sb_eol = TRUE; + last_msgchunk->sb_eol = true; } } @@ -2654,9 +2686,9 @@ 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); + msg_puts_display(p, -1, mp->sb_attr, true); if (mp->sb_eol || mp->sb_next == NULL) { break; } @@ -2667,27 +2699,26 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) } /// Output any postponed text for msg_puts_attr_len(). -static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) +static void t_puts(int *t_col, const char *t_s, const char *s, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); // Output postponed text. msg_didout = true; // Remember that line is not empty. - grid_puts_len(&msg_grid_adj, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col, - attr); + grid_puts_len(&msg_grid_adj, (char *)t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; // If the string starts with a composing character don't increment the // column position for it. - if (utf_iscomposing(utf_ptr2char((char *)t_s))) { + if (utf_iscomposing(utf_ptr2char(t_s))) { msg_col--; } if (msg_col >= Columns) { msg_col = 0; - ++msg_row; + msg_row++; } } -/// @return TRUE when messages should be printed to stdout/stderr: +/// @return true when messages should be printed to stdout/stderr: /// - "batch mode" ("silent mode", -es/-Es) /// - no UI and not embedded int msg_use_printf(void) @@ -2755,14 +2786,14 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) /// When at hit-enter prompt "typed_char" is the already typed character, /// otherwise it's NUL. /// -/// @return TRUE when jumping ahead to "confirm_msg_tail". +/// @return true when jumping ahead to "confirm_msg_tail". static int do_more_prompt(int typed_char) { static bool entered = false; int used_typed_char = typed_char; int oldState = State; int c; - int retval = FALSE; + int retval = false; int toscroll; bool to_redraw = false; msgchunk_T *mp_last = NULL; @@ -2786,7 +2817,7 @@ static int do_more_prompt(int typed_char) // "g<": Find first line on the last page. mp_last = msg_sb_start(last_msgchunk); for (i = 0; i < Rows - 2 && mp_last != NULL - && mp_last->sb_prev != NULL; ++i) { + && mp_last->sb_prev != NULL; i++) { mp_last = msg_sb_start(mp_last->sb_prev); } } @@ -2794,7 +2825,7 @@ static int do_more_prompt(int typed_char) State = MODE_ASKMORE; setmouse(); if (typed_char == NUL) { - msg_moremsg(FALSE); + msg_moremsg(false); } for (;;) { /* @@ -2867,10 +2898,10 @@ static int do_more_prompt(int typed_char) case ESC: if (confirm_msg_used) { // Jump to the choices of the dialog. - retval = TRUE; + retval = true; } else { - got_int = TRUE; - quit_more = TRUE; + got_int = true; + quit_more = true; } // When there is some more output (wrapping line) display that // without another prompt. @@ -2886,7 +2917,7 @@ static int do_more_prompt(int typed_char) break; default: // no valid response - msg_moremsg(TRUE); + msg_moremsg(true); continue; } @@ -2905,8 +2936,7 @@ static int do_more_prompt(int typed_char) } // go to start of line at top of the screen - for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; - ++i) { + for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) { mp = msg_sb_start(mp->sb_prev); } @@ -2938,7 +2968,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 +3067,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++; } } } @@ -3053,10 +3083,9 @@ void msg_moremsg(int full) char_u *s = (char_u *)_("-- More --"); attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); - grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr); + grid_puts(&msg_grid_adj, (char *)s, Rows - 1, 0, attr); if (full) { - grid_puts(&msg_grid_adj, (char_u *) - _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), + grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), Rows - 1, vim_strsize((char *)s), attr); } } @@ -3136,9 +3165,9 @@ void msg_clr_cmdline(void) } /// end putting a message on the screen -/// call wait_return if the message does not fit in the available space +/// call wait_return() if the message does not fit in the available space /// -/// @return TRUE if wait_return not called. +/// @return true if wait_return() not called. int msg_end(void) { /* @@ -3214,8 +3243,8 @@ void msg_ext_clear_later(void) { if (msg_ext_is_visible()) { msg_ext_need_clear = true; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } } @@ -3339,7 +3368,7 @@ int redirecting(void) void verbose_enter(void) { if (*p_vfile != NUL) { - ++msg_silent; + msg_silent++; } } @@ -3358,10 +3387,10 @@ 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; + msg_scroll = true; } } @@ -3384,7 +3413,7 @@ void verbose_stop(void) fclose(verbose_fd); verbose_fd = NULL; } - verbose_did_open = FALSE; + verbose_did_open = false; } /// Open the file 'verbosefile'. @@ -3394,9 +3423,9 @@ int verbose_open(void) { if (verbose_fd == NULL && !verbose_did_open) { // Only give the error message once. - verbose_did_open = TRUE; + verbose_did_open = true; - verbose_fd = os_fopen((char *)p_vfile, "a"); + verbose_fd = os_fopen(p_vfile, "a"); if (verbose_fd == NULL) { semsg(_(e_notopen), p_vfile); return FAIL; @@ -3493,7 +3522,7 @@ void msg_advance(int col) /// different letter. /// /// @param textfiel IObuff for inputdialog(), NULL otherwise -/// @param ex_cmd when TRUE pressing : accepts default and starts Ex command +/// @param ex_cmd when true pressing : accepts default and starts Ex command /// @returns 0 if cancelled, otherwise the nth button (1-indexed). int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd) @@ -3520,7 +3549,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 +3598,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 +3752,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; @@ -3769,7 +3798,7 @@ int vim_dialog_yesno(int type, char_u *title, char_u *message, int dflt) if (do_dialog(type, title == NULL ? (char_u *)_("Question") : title, message, - (char_u *)_("&Yes\n&No"), dflt, NULL, FALSE) == 1) { + (char_u *)_("&Yes\n&No"), dflt, NULL, false) == 1) { return VIM_YES; } return VIM_NO; @@ -3780,7 +3809,7 @@ int vim_dialog_yesnocancel(int type, char_u *title, char_u *message, int dflt) switch (do_dialog(type, title == NULL ? (char_u *)_("Question") : title, message, - (char_u *)_("&Yes\n&No\n&Cancel"), dflt, NULL, FALSE)) { + (char_u *)_("&Yes\n&No\n&Cancel"), dflt, NULL, false)) { case 1: return VIM_YES; case 2: @@ -3795,7 +3824,7 @@ int vim_dialog_yesnoallcancel(int type, char_u *title, char_u *message, int dflt title == NULL ? (char_u *)"Question" : title, message, (char_u *)_("&Yes\n&No\nSave &All\n&Discard All\n&Cancel"), - dflt, NULL, FALSE)) { + dflt, NULL, false)) { case 1: return VIM_YES; case 2: diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index a4a521fa80..73147204a1 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" @@ -178,7 +179,7 @@ retnomove: } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } return IN_BUFFER; } @@ -278,7 +279,7 @@ retnomove: : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (cmdwin_type != 0 && wp != curwin) { // A click outside the command-line window: Use modeless @@ -344,7 +345,7 @@ retnomove: // before moving the cursor for a left click, stop Visual mode if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (grid == 0) { @@ -373,20 +374,20 @@ retnomove: if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { curwin->w_topfill++; } else { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } } check_topfill(curwin, false); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); row = 0; } else if (row >= curwin->w_height_inner) { 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); } @@ -409,7 +410,7 @@ retnomove: } } check_topfill(curwin, false); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); row = curwin->w_height_inner - 1; @@ -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) { @@ -630,14 +631,16 @@ colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { // try to advance to the specified column - char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false); - char_u *const line = ptr; - colnr_T count = 0; - while (count < vcol && *ptr != NUL) { - count += win_lbr_chartabsize(wp, line, ptr, count, NULL); - MB_PTR_ADV(ptr); - } - return (colnr_T)(ptr - line); + char_u *line = ml_get_buf(wp->w_buffer, lnum, false); + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) { + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); + MB_PTR_ADV(cts.cts_ptr); + } + clear_chartabsize_arg(&cts); + + return (colnr_T)((char_u *)cts.cts_ptr - line); } /// Set UI mouse depending on current mode and 'mouse'. @@ -666,7 +669,7 @@ static colnr_T scroll_line_len(linenr_T lnum) char_u *line = ml_get(lnum); if (*line != NUL) { for (;;) { - int numchar = win_chartabsize(curwin, line, col); + int numchar = win_chartabsize(curwin, (char *)line, col); MB_PTR_ADV(line); if (*line == NUL) { // don't count the last character break; @@ -701,8 +704,8 @@ static linenr_T find_longest_lnum(void) max = len; ret = lnum; } else if (len == (colnr_T)max - && abs((int)(lnum - curwin->w_cursor.lnum)) - < abs((int)(ret - curwin->w_cursor.lnum))) { + && abs(lnum - curwin->w_cursor.lnum) + < abs(ret - curwin->w_cursor.lnum)) { ret = lnum; } } @@ -789,7 +792,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // checked for concealed characters. vcol = 0; while (vcol < offset && *ptr != NUL) { - vcol += win_chartabsize(curwin, ptr, vcol); + vcol += win_chartabsize(curwin, (char *)ptr, vcol); ptr += utfc_ptr2len((char *)ptr); } @@ -800,7 +803,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) vcol = offset; ptr_end = ptr_row_offset; while (vcol < col && *ptr_end != NUL) { - vcol += win_chartabsize(curwin, ptr_end, vcol); + vcol += win_chartabsize(curwin, (char *)ptr_end, vcol); ptr_end += utfc_ptr2len((char *)ptr_end); } @@ -815,7 +818,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) #define DECR() nudge--; ptr_end -= utfc_ptr2len((char *)ptr_end) while (ptr < ptr_end && *ptr != NUL) { - cwidth = win_chartabsize(curwin, ptr, vcol); + cwidth = win_chartabsize(curwin, (char *)ptr, vcol); vcol += cwidth; if (cwidth > 1 && *ptr == '\t' && nudge > 0) { // A tab will "absorb" any previous adjustments. diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 08261e4a30..21ff56bbbc 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -36,7 +36,7 @@ #define MOUSE_X2 0x400 // Mouse-button X2 // Direction for nv_mousescroll() and ins_mousescroll() -#define MSCR_DOWN 0 // DOWN must be FALSE +#define MSCR_DOWN 0 // DOWN must be false #define MSCR_UP 1 #define MSCR_LEFT (-1) #define MSCR_RIGHT (-2) diff --git a/src/nvim/move.c b/src/nvim/move.c index 6d4eb8ef49..b3ec3a8e7a 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" @@ -103,7 +105,7 @@ void redraw_for_cursorline(win_T *wp) if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() && (wp->w_p_rnu || win_cursorline_standout(wp))) { // win_line() will redraw the number column and cursorline only. - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } @@ -114,13 +116,13 @@ 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); + // need to redraw with UPD_SOME_VALID. + redraw_later(wp, UPD_SOME_VALID); } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { - // When 'cursorlineopt' contains "screenline" need to redraw with VALID. - redraw_later(wp, VALID); + // When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID. + redraw_later(wp, UPD_VALID); } } // If the cursor moves horizontally when 'concealcursor' is active, then the @@ -182,7 +184,7 @@ void update_topline(win_T *wp) // If the buffer is empty, always set topline to 1. if (buf_is_empty(curbuf)) { // special case - file is empty if (wp->w_topline != 1) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } wp->w_topline = 1; wp->w_botline = 2; @@ -334,9 +336,9 @@ void update_topline(win_T *wp) dollar_vcol = -1; if (wp->w_skipcol != 0) { wp->w_skipcol = 0; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { @@ -448,7 +450,7 @@ void changed_window_setting_win(win_T *wp) wp->w_lines_valid = 0; changed_line_abv_curs_win(wp); wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE); - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } /* @@ -470,7 +472,7 @@ void set_topline(win_T *wp, linenr_T lnum) } wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE); // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } /* @@ -569,7 +571,7 @@ static void curs_rows(win_T *wp) || wp->w_lines[0].wl_lnum > wp->w_topline); int i = 0; wp->w_cline_row = 0; - for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; ++i) { + for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; i++) { bool valid = false; if (!all_invalid && i < wp->w_lines_valid) { if (wp->w_lines[i].wl_lnum < lnum || !wp->w_lines[i].wl_valid) { @@ -585,7 +587,7 @@ static void curs_rows(win_T *wp) valid = true; } } else if (wp->w_lines[i].wl_lnum > lnum) { - --i; // hold at inserted lines + i--; // hold at inserted lines } } if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) { @@ -845,7 +847,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_leftcol = new_leftcol; win_check_anchored_floats(wp); // screen has to be redrawn with new wp->w_leftcol - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } wp->w_wcol -= wp->w_leftcol; @@ -952,7 +954,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_skipcol = 0; } if (prev_skipcol != wp->w_skipcol) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } redraw_for_cursorcolumn(curwin); @@ -1051,12 +1053,12 @@ bool scrolldown(long line_count, int byfold) if (curwin->w_topline == 1) { break; } - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; // 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; } @@ -1066,7 +1068,7 @@ bool scrolldown(long line_count, int byfold) done += plines_win_nofill(curwin, curwin->w_topline, true); } } - --curwin->w_botline; // approximate w_botline + curwin->w_botline--; // approximate w_botline invalidate_botline(); } curwin->w_wrow += done; // keep w_wrow updated @@ -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 { @@ -1128,7 +1130,7 @@ bool scrollup(long line_count, int byfold) linenr_T lnum = curwin->w_topline; while (line_count--) { if (curwin->w_topfill > 0) { - --curwin->w_topfill; + curwin->w_topfill--; } else { if (byfold) { (void)hasFolding(lnum, NULL, &lnum); @@ -1185,7 +1187,7 @@ void check_topfill(win_T *wp, bool down) int n = plines_win_nofill(wp, wp->w_topline, true); if (wp->w_topfill + n > wp->w_height_inner) { if (down && wp->w_topline > 1) { - --wp->w_topline; + wp->w_topline--; wp->w_topfill = 0; } else { wp->w_topfill = wp->w_height_inner - n; @@ -1247,14 +1249,14 @@ void scrolldown_clamp(void) } if (end_row < curwin->w_height_inner - get_scrolloff_value(curwin)) { if (can_fill) { - ++curwin->w_topfill; + curwin->w_topfill++; check_topfill(curwin, true); } else { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); - --curwin->w_botline; // approximate w_botline + curwin->w_botline--; // approximate w_botline curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); } } @@ -1307,7 +1309,7 @@ static void topline_back(win_T *wp, lineoff_T *lp) lp->fill++; lp->height = 1; } else { - --lp->lnum; + lp->lnum--; lp->fill = 0; if (lp->lnum < 1) { lp->height = MAXCOL; @@ -1333,7 +1335,7 @@ static void botline_forw(win_T *wp, lineoff_T *lp) lp->fill++; lp->height = 1; } else { - ++lp->lnum; + lp->lnum++; lp->fill = 0; assert(wp->w_buffer != 0); if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { @@ -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; @@ -1724,9 +1726,9 @@ void scroll_cursor_halfway(int atend) } below += boff.height; } else { - ++below; // count a "~" line + 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) { @@ -1897,7 +1899,7 @@ int onepage(Direction dir, long count) if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { // Vi compatible scrolling if (p_window <= 2) { - ++curwin->w_topline; + curwin->w_topline++; } else { curwin->w_topline += (linenr_T)p_window - 2; } @@ -1933,7 +1935,7 @@ int onepage(Direction dir, long count) if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { // Vi compatible scrolling (sort of) if (p_window <= 2) { - --curwin->w_topline; + curwin->w_topline--; } else { curwin->w_topline -= (linenr_T)p_window - 2; } @@ -1998,7 +2000,7 @@ int onepage(Direction dir, long count) max_topfill(); } if (curwin->w_topfill == loff.fill) { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } comp_botline(curwin); @@ -2038,7 +2040,7 @@ int onepage(Direction dir, long count) } } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); return retval; } @@ -2142,7 +2144,7 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } @@ -2175,7 +2177,7 @@ void halfpage(bool flag, linenr_T Prenum) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { (void)hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum); - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } } else { curwin->w_cursor.lnum += n; @@ -2197,7 +2199,7 @@ void halfpage(bool flag, linenr_T Prenum) if (n < 0 && scrolled > 0) { break; } - --curwin->w_topline; + curwin->w_topline--; (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); curwin->w_topfill = 0; } @@ -2205,7 +2207,7 @@ void halfpage(bool flag, linenr_T Prenum) VALID_BOTLINE|VALID_BOTLINE_AP); scrolled += i; if (curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } } @@ -2216,7 +2218,7 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_cursor.lnum = 1; } else if (hasAnyFolding(curwin)) { while (--n >= 0 && curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); } @@ -2230,7 +2232,7 @@ void halfpage(bool flag, linenr_T Prenum) check_topfill(curwin, !flag); cursor_correct(); beginline(BL_SOL | BL_FIX); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } void do_check_cursorbind(void) @@ -2282,7 +2284,7 @@ void do_check_cursorbind(void) } // Correct cursor for multi-byte character. mb_adjust_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); // Only scroll when 'scrollbind' hasn't done this. if (!curwin->w_p_scb) { diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 1ca68979f4..d22bcb29d5 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -158,7 +158,7 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem } // frame.result was allocated in an arena - arena_mem_free(frame.result_mem, &rpc->unpacker->reuse_blk); + arena_mem_free(frame.result_mem); frame.result_mem = NULL; } @@ -244,7 +244,7 @@ static void parse_msgpack(Channel *channel) ui_client_event_raw_line(p->grid_line_event); } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) { p->ui_handler.fn(p->result.data.array); - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); } } else if (p->type == kMessageTypeResponse) { ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); @@ -295,7 +295,7 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) if (!p->handler.fn) { send_error(channel, p->type, p->request_id, p->unpack_error.msg); api_clear_error(&p->unpack_error); - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); return; } @@ -304,7 +304,8 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) evdata->channel = channel; evdata->handler = p->handler; evdata->args = args; - evdata->used_mem = arena_finish(&p->arena); + evdata->used_mem = p->arena; + p->arena = (Arena)ARENA_EMPTY; evdata->request_id = p->request_id; channel_incref(channel); if (p->handler.fast) { @@ -344,7 +345,8 @@ static void request_event(void **argv) // channel was closed, abort any pending requests goto free_ret; } - Object result = handler.fn(channel->id, e->args, &error); + + Object result = handler.fn(channel->id, e->args, &e->used_mem, &error); if (e->type == kMessageTypeRequest || ERROR_SET(&error)) { // Send the response. msgpack_packer response; @@ -355,13 +357,14 @@ static void request_event(void **argv) &error, result, &out_buffer)); - } else { + } + if (!handler.arena_return) { api_free_object(result); } free_ret: - // e->args is allocated in an arena - arena_mem_free(e->used_mem, &channel->rpc.unpacker->reuse_blk); + // e->args (and possibly result) are allocated in an arena + arena_mem_free(arena_finish(&e->used_mem)); channel_decref(channel); xfree(e); api_clear_error(&error); @@ -624,7 +627,6 @@ static WBuffer *serialize_response(uint64_t channel_id, MessageType type, uint32 1, // responses only go though 1 channel xfree); msgpack_sbuffer_clear(sbuffer); - api_free_object(arg); return rv; } @@ -642,7 +644,7 @@ void rpc_set_client_info(uint64_t id, Dictionary info) Dictionary rpc_client_info(Channel *chan) { - return copy_dictionary(chan->rpc.info); + return copy_dictionary(chan->rpc.info, NULL); } const char *rpc_client_name(Channel *chan) diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h index e622ebddf5..404e68329a 100644 --- a/src/nvim/msgpack_rpc/channel_defs.h +++ b/src/nvim/msgpack_rpc/channel_defs.h @@ -27,7 +27,7 @@ typedef struct { MsgpackRpcRequestHandler handler; Array args; uint32_t request_id; - ArenaMem used_mem; + Arena used_mem; } RequestEvent; typedef struct { diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index b252f0998e..532e684f93 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -216,7 +216,7 @@ bool server_stop(char *endpoint) watchers.ga_len--; // Bump v:servername to the next available server, if any. - if (strequal(addr, (char *)get_vim_var_str(VV_SEND_SERVER))) { + if (strequal(addr, get_vim_var_str(VV_SEND_SERVER))) { set_vservername(&watchers); } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index c8e9fdd4c3..24480835a1 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -181,13 +181,11 @@ void unpacker_init(Unpacker *p) p->unpack_error = (Error)ERROR_INIT; p->arena = (Arena)ARENA_EMPTY; - p->reuse_blk = NULL; } void unpacker_teardown(Unpacker *p) { - arena_mem_free(p->reuse_blk, NULL); - arena_mem_free(arena_finish(&p->arena), NULL); + arena_mem_free(arena_finish(&p->arena)); } bool unpacker_parse_header(Unpacker *p) @@ -308,7 +306,7 @@ bool unpacker_advance(Unpacker *p) p->state = 10; } else { p->state = p->type == kMessageTypeResponse ? 1 : 2; - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; } } @@ -322,7 +320,7 @@ bool unpacker_advance(Unpacker *p) goto done; } else { // unpack other ui events using mpack_parse() - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; } } @@ -416,13 +414,13 @@ redo: if (p->ui_handler.fn != ui_client_event_grid_line) { p->state = 12; if (p->grid_line_event) { - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); p->grid_line_event = NULL; } return true; } else { p->state = 13; - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; p->grid_line_event = arena_alloc(&p->arena, sizeof *p->grid_line_event, true); g = p->grid_line_event; } diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h index f39439be63..35048fb877 100644 --- a/src/nvim/msgpack_rpc/unpacker.h +++ b/src/nvim/msgpack_rpc/unpacker.h @@ -32,8 +32,6 @@ struct Unpacker { Error unpack_error; Arena arena; - // one length free-list of reusable blocks - ArenaMem reuse_blk; int nevents; int ncalls; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2d752eade7..47ad000385 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,15 +55,18 @@ #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" #include "nvim/tag.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -465,7 +473,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" @@ -517,7 +525,7 @@ static bool normal_handle_special_visual_command(NormalState *s) && (nv_cmds[s->idx].cmd_flags & NV_STS) && !(mod_mask & MOD_MASK_SHIFT)) { end_visual_mode(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // Keys that work different when 'keymodel' contains "startsel" @@ -623,16 +631,16 @@ static void normal_redraw_mode_message(NormalState *s) if (must_redraw && keep_msg != NULL && !emsg_on_display) { char_u *kmsg; - kmsg = keep_msg; + kmsg = (char_u *)keep_msg; keep_msg = NULL; // Showmode() will clear keep_msg, but we want to use it anyway. // First update w_topline. setcursor(); update_screen(0); // now reset it, otherwise it's put in the history again - keep_msg = kmsg; + keep_msg = (char *)kmsg; - kmsg = vim_strsave(keep_msg); + kmsg = vim_strsave((char_u *)keep_msg); msg_attr((const char *)kmsg, keep_msg_attr); xfree(kmsg); } @@ -1275,11 +1283,11 @@ static void normal_redraw(NormalState *s) validate_cursor(); if (VIsual_active) { - redraw_curbuf_later(INVERTED); // update inverted part - update_screen(INVERTED); + redraw_curbuf_later(UPD_INVERTED); // update inverted part + 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(); } @@ -1294,7 +1302,7 @@ static void normal_redraw(NormalState *s) // Display message after redraw. If an external message is still visible, // it contains the kept message already. if (keep_msg != NULL && !msg_ext_is_visible()) { - char_u *const p = vim_strsave(keep_msg); + char *const p = xstrdup(keep_msg); // msg_start() will set keep_msg to NULL, make a copy // first. Don't reset keep_msg, msg_attr_keep() uses it to @@ -1316,7 +1324,7 @@ static void normal_redraw(NormalState *s) did_emsg = false; msg_didany = false; // reset lines_left in msg_start() may_clear_sb_text(); // clear scroll-back text on next msg - showruler(false); + show_cursor_info(false); setcursor(); } @@ -1832,7 +1840,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 ? UPD_INVERTED : UPD_VALID); + update_screen(0); setcursor(); ui_flush(); // Update before showing popup menu } @@ -2179,7 +2188,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) curwin->w_set_curswant = true; } if (is_click) { - redraw_curbuf_later(INVERTED); // update the inversion + redraw_curbuf_later(UPD_INVERTED); // update the inversion } } else if (VIsual_active && !old_active) { if (mod_mask & MOD_MASK_ALT) { @@ -2304,7 +2313,7 @@ void reset_VIsual_and_resel(void) { if (VIsual_active) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion later + redraw_curbuf_later(UPD_INVERTED); // delete the inversion later } VIsual_reselect = false; } @@ -2314,7 +2323,7 @@ void reset_VIsual(void) { if (VIsual_active) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion later + redraw_curbuf_later(UPD_INVERTED); // delete the inversion later VIsual_reselect = false; } } @@ -2383,7 +2392,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 +2403,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 +2479,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; } @@ -2645,8 +2654,8 @@ void clear_showcmd(void) lines = bot - top + 1; if (VIsual_mode == Ctrl_V) { - char_u *const saved_sbr = p_sbr; - char_u *const saved_w_sbr = curwin->w_p_sbr; + char *const saved_sbr = p_sbr; + char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; @@ -2709,8 +2718,6 @@ void clear_showcmd(void) /// @return true if output has been written (and setcursor() has been called). bool add_to_showcmd(int c) { - char_u *p; - int i; static int ignore[] = { K_IGNORE, K_LEFTMOUSE, K_LEFTDRAG, K_LEFTRELEASE, K_MOUSEMOVE, @@ -2733,14 +2740,14 @@ bool add_to_showcmd(int c) // Ignore keys that are scrollbar updates and mouse clicks if (IS_SPECIAL(c)) { - for (i = 0; ignore[i] != 0; i++) { + for (int i = 0; ignore[i] != 0; i++) { if (ignore[i] == c) { return false; } } } - p = transchar(c); + char *p = (char *)transchar(c); if (*p == ' ') { STRCPY(p, "<20>"); } @@ -2836,12 +2843,12 @@ static void display_showcmd(void) grid_puts_line_start(&msg_grid_adj, showcmd_row); if (!showcmd_is_clear) { - grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col, + grid_puts(&msg_grid_adj, (char *)showcmd_buf, showcmd_row, sc_col, HL_ATTR(HLF_MSG)); } // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces - grid_puts(&msg_grid_adj, (char_u *)" " + len, showcmd_row, + grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row, sc_col + len, HL_ATTR(HLF_MSG)); grid_puts_line_flush(false); @@ -2949,7 +2956,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff) } } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); cursor_correct(); curwin->w_redr_status = true; } @@ -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) { @@ -3483,7 +3490,7 @@ void scroll_redraw(int up, long count) if (moved) { curwin->w_viewport_invalid = true; } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } /// Get the count specified after a 'z' command. Only the 'z<CR>', 'zl', 'zh', @@ -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); @@ -3649,7 +3656,7 @@ static void nv_zet(cmdarg_T *cap) case 't': scroll_cursor_top(0, true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3660,7 +3667,7 @@ static void nv_zet(cmdarg_T *cap) case 'z': scroll_cursor_halfway(true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3683,7 +3690,7 @@ static void nv_zet(cmdarg_T *cap) case 'b': scroll_cursor_bot(0, true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3735,7 +3742,7 @@ static void nv_zet(cmdarg_T *cap) } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } } break; @@ -3756,7 +3763,7 @@ static void nv_zet(cmdarg_T *cap) } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } } break; @@ -4091,7 +4098,7 @@ static void nv_clear(cmdarg_T *cap) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { wp->w_s->b_syn_slow = false; } - redraw_later(curwin, CLEAR); + redraw_later(curwin, UPD_CLEAR); } } @@ -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; @@ -4268,14 +4275,13 @@ static void nv_ident(cmdarg_T *cap) // Allocate buffer to put the command in. Inserting backslashes can // double the length of the word. p_kp / curbuf->b_p_kp could be added // and some numbers. - char_u *kp = *curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp; // 'keywordprg' - 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) { + char_u *kp = *curbuf->b_p_kp == NUL ? p_kp : (char_u *)curbuf->b_p_kp; // 'keywordprg' + bool kp_help = (*kp == NUL || STRCMP(kp, ":he") == 0 || STRCMP(kp, ":help") == 0); + if (kp_help && *skipwhite(ptr) == NUL) { emsg(_(e_noident)); // found white space only return; } + bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command size_t buf_size = n * 2 + 30 + STRLEN(kp); char *buf = xmalloc(buf_size); buf[0] = NUL; @@ -4288,9 +4294,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 +4333,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 +4370,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,14 +4382,15 @@ 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); + + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0, NULL); } else { g_tag_at_cursor = true; do_cmdline_cmd(buf); @@ -4406,7 +4413,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 +4425,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 +4440,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(); @@ -4557,9 +4564,9 @@ static void nv_right(cmdarg_T *cap) // <Space> wraps to next line if 'whichwrap' has 's'. // 'l' wraps to next line if 'whichwrap' has 'l'. // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. - if (((cap->cmdchar == ' ' && vim_strchr((char *)p_ww, 's') != NULL) - || (cap->cmdchar == 'l' && vim_strchr((char *)p_ww, 'l') != NULL) - || (cap->cmdchar == K_RIGHT && vim_strchr((char *)p_ww, '>') != NULL)) + if (((cap->cmdchar == ' ' && vim_strchr(p_ww, 's') != NULL) + || (cap->cmdchar == 'l' && vim_strchr(p_ww, 'l') != NULL) + || (cap->cmdchar == K_RIGHT && vim_strchr(p_ww, '>') != NULL)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { // When deleting we also count the NL as a character. // Set cap->oap->inclusive when last char in the line is @@ -4627,9 +4634,9 @@ static void nv_left(cmdarg_T *cap) // 'h' wraps to previous line if 'whichwrap' has 'h'. // CURS_LEFT wraps to previous line if 'whichwrap' has '<'. if ((((cap->cmdchar == K_BS || cap->cmdchar == Ctrl_H) - && vim_strchr((char *)p_ww, 'b') != NULL) - || (cap->cmdchar == 'h' && vim_strchr((char *)p_ww, 'h') != NULL) - || (cap->cmdchar == K_LEFT && vim_strchr((char *)p_ww, '<') != NULL)) + && vim_strchr(p_ww, 'b') != NULL) + || (cap->cmdchar == 'h' && vim_strchr(p_ww, 'h') != NULL) + || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL)) && curwin->w_cursor.lnum > 1) { curwin->w_cursor.lnum--; coladvance(MAXCOL); @@ -4796,7 +4803,7 @@ static void nv_search(cmdarg_T *cap) // When using 'incsearch' the cursor may be moved to set a different search // start position. - cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0, true); + cap->searchbuf = (char *)getcmdline(cap->cmdchar, cap->count1, 0, true); if (cap->searchbuf == NULL) { clearop(oap); @@ -4832,9 +4839,8 @@ static void nv_next(cmdarg_T *cap) /// @param opt extra flags for do_search() /// /// @return 0 for failure, 1 for found, 2 for found and line offset added. -static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrapped) +static int normal_search(cmdarg_T *cap, int dir, char *pat, int opt, int *wrapped) { - int i; searchit_arg_T sia; cap->oap->motion_type = kMTCharWise; @@ -4842,9 +4848,9 @@ 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)); - i = do_search(cap->oap, dir, dir, pat, cap->count1, - opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); + CLEAR_FIELD(sia); + int i = do_search(cap->oap, dir, dir, (char_u *)pat, cap->count1, + opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { *wrapped = sia.sa_wrapped; } @@ -5044,15 +5050,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 @@ -5543,7 +5549,7 @@ static void n_swapchar(cmdarg_T *cap) return; } - if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr((char *)p_ww, '~') == NULL) { + if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { clearopbeep(cap->oap); return; } @@ -5559,7 +5565,7 @@ static void n_swapchar(cmdarg_T *cap) did_change |= swapchar(cap->oap->op_type, &curwin->w_cursor); inc_cursor(); if (gchar_cursor() == NUL) { - if (vim_strchr((char *)p_ww, '~') != NULL + if (vim_strchr(p_ww, '~') != NULL && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -5615,7 +5621,7 @@ static MarkMoveRes nv_mark_move_to(cmdarg_T *cap, MarkMove flags, fmark_T *fm) /// Handle commands that are operators in Visual mode. static void v_visop(cmdarg_T *cap) { - static char_u trans[] = "YyDdCcxdXdAAIIrr"; + static char trans[] = "YyDdCcxdXdAAIIrr"; // Uppercase means linewise, except in block mode, then "D" deletes till // the end of the line, and "C" replaces till EOL @@ -5627,7 +5633,7 @@ static void v_visop(cmdarg_T *cap) curwin->w_curswant = MAXCOL; } } - cap->cmdchar = (uint8_t)(*(vim_strchr((char *)trans, cap->cmdchar) + 1)); + cap->cmdchar = (uint8_t)(*(vim_strchr(trans, cap->cmdchar) + 1)); nv_operator(cap); } @@ -5810,7 +5816,7 @@ static void nv_visual(cmdarg_T *cap) showmode(); may_trigger_modechanged(); } - redraw_curbuf_later(INVERTED); // update the inversion + redraw_curbuf_later(UPD_INVERTED); // update the inversion } else { // start Visual mode if (cap->count0 > 0 && resel_VIsual_mode != NUL) { // use previously selected part @@ -5856,7 +5862,7 @@ static void nv_visual(cmdarg_T *cap) } else { curwin->w_set_curswant = true; } - redraw_curbuf_later(INVERTED); // show the inversion + redraw_curbuf_later(UPD_INVERTED); // show the inversion } else { if (!cap->arg) { // start Select mode when 'selectmode' contains "cmd" @@ -5891,7 +5897,7 @@ void start_selection(void) void may_start_select(int c) { VIsual_select = (c == 'o' || (stuff_empty() && typebuf_typed())) - && vim_strchr((char *)p_slm, c) != NULL; + && vim_strchr(p_slm, c) != NULL; } /// Start Visual mode "c". @@ -5922,7 +5928,7 @@ static void n_start_visual_mode(int c) } // Only need to redraw this line, unless still need to redraw an old // Visual area (when 'lazyredraw' is set). - if (curwin->w_redr_type < INVERTED) { + if (curwin->w_redr_type < UPD_INVERTED) { curwin->w_old_cursor_lnum = curwin->w_cursor.lnum; curwin->w_old_visual_lnum = curwin->w_cursor.lnum; } @@ -6009,7 +6015,7 @@ static void nv_gv_cmd(cmdarg_T *cap) may_start_select('c'); } setmouse(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); showmode(); } @@ -6892,7 +6898,7 @@ static void nv_normal(cmdarg_T *cap) } if (VIsual_active) { end_visual_mode(); // stop Visual - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } else { clearopbeep(cap->oap); @@ -6923,6 +6929,10 @@ static void nv_esc(cmdarg_T *cap) } } + if (restart_edit != 0) { + redraw_mode = true; // remove "-- (insert) --" + } + restart_edit = 0; if (cmdwin_type != 0) { @@ -6930,10 +6940,10 @@ static void nv_esc(cmdarg_T *cap) got_int = false; // don't stop executing autocommands et al. return; } - } else if (cmdwin_type != 0 && ex_normal_busy) { + } else if (cmdwin_type != 0 && ex_normal_busy && typebuf_was_empty) { // When :normal runs out of characters while in the command line window - // vgetorpeek() will return ESC. Exit the cmdline window to break the - // loop. + // vgetorpeek() will repeatedly return ESC. Exit the cmdline window to + // break the loop. cmdwin_result = K_IGNORE; return; } @@ -6942,7 +6952,7 @@ static void nv_esc(cmdarg_T *cap) end_visual_mode(); // stop Visual check_cursor_col(); // make sure cursor is not beyond EOL curwin->w_set_curswant = true; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } else if (no_reason) { vim_beep(BO_ESC); } @@ -7062,8 +7072,8 @@ static void nv_object(cmdarg_T *cap) include = true; // "ax" = an object: include white space } // Make sure (), [], {} and <> are in 'matchpairs' - mps_save = curbuf->b_p_mps; - curbuf->b_p_mps = (char_u *)"(:),{:},[:],<:>"; + mps_save = (char_u *)curbuf->b_p_mps; + curbuf->b_p_mps = "(:),{:},[:],<:>"; switch (cap->nchar) { case 'w': // "aw" = a word @@ -7117,7 +7127,7 @@ static void nv_object(cmdarg_T *cap) break; } - curbuf->b_p_mps = mps_save; + curbuf->b_p_mps = (char *)mps_save; if (!flag) { clearopbeep(cap->oap); } diff --git a/src/nvim/normal.h b/src/nvim/normal.h index 9bda70eacd..13ea233658 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -57,7 +57,7 @@ typedef struct oparg_S { * Arguments for Normal mode commands. */ typedef struct cmdarg_S { - oparg_T *oap; // Operator arguments + oparg_T *oap; // Operator arguments int prechar; // prefix character (optional, always 'g') int cmdchar; // command character int nchar; // next command character (optional) @@ -69,7 +69,7 @@ typedef struct cmdarg_S { long count1; // count before command, default 1 int arg; // extra argument from nv_cmds[] int retval; // return: CA_* values - char_u *searchbuf; // return: pointer to search pattern or NULL + char *searchbuf; // return: pointer to search pattern or NULL } cmdarg_T; // values for retval: diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 53612be697..4f2b84d20f 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" @@ -24,7 +25,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" @@ -47,11 +47,11 @@ #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" #include "nvim/terminal.h" +#include "nvim/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -79,9 +79,9 @@ struct block_def { colnr_T textcol; // index of chars (partially) in block colnr_T start_vcol; // start col of 1st char wholly inside block colnr_T end_vcol; // start col of 1st char wholly after block - int is_short; // TRUE if line is too short to fit in block - int is_MAX; // TRUE if curswant==MAXCOL when starting - int is_oneChar; // TRUE if block within one character + int is_short; // true if line is too short to fit in block + int is_MAX; // true if curswant==MAXCOL when starting + int is_oneChar; // true if block within one character int pre_whitesp; // screen cols of ws before block int pre_whitesp_c; // chars of ws before block colnr_T end_char_vcols; // number of vcols of post-block char @@ -182,13 +182,13 @@ int get_op_type(int char1, int char2) return i; } -/// @return TRUE if operator "op" always works on whole lines. +/// @return true if operator "op" always works on whole lines. int op_on_lines(int op) { return opchars[op][2] & OPF_LINES; } -/// @return TRUE if operator "op" changes text. +/// @return true if operator "op" changes text. int op_is_change(int op) { return opchars[op][2] & OPF_CHANGE; @@ -235,7 +235,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) // isn't set or 'cindent' isn't set or '#' isn't in 'cino'. shift_line(oap->op_type == OP_LSHIFT, p_sr, amount, false); } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } if (oap->motion_type == kMTBlockWise) { @@ -245,7 +245,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) curwin->w_cursor.lnum = oap->start.lnum; beginline(BL_SOL | BL_FIX); // shift_line() may have set cursor.col } else { - --curwin->w_cursor.lnum; // put cursor on last line, for ":>" + curwin->w_cursor.lnum--; // put cursor on last line, for ":>" } // The cursor line is not in a closed fold foldOpenCursor(); @@ -289,13 +289,13 @@ void shift_line(int left, int round, int amount, int call_changed_bytes) { int count; int i, j; - int p_sw = (int)get_sw_value_indent(curbuf); + const int sw_val = (int)get_sw_value_indent(curbuf); count = get_indent(); // get current indent if (round) { // round off indent - i = count / p_sw; // number of p_sw rounded down - j = count % p_sw; // extra spaces + i = count / sw_val; // number of 'shiftwidth' rounded down + j = count % sw_val; // extra spaces if (j && left) { // first remove extra spaces amount--; } @@ -307,15 +307,15 @@ void shift_line(int left, int round, int amount, int call_changed_bytes) } else { i += amount; } - count = i * p_sw; + count = i * sw_val; } else { // original vi indent if (left) { - count -= p_sw * amount; + count -= sw_val * amount; if (count < 0) { count = 0; } } else { - count += p_sw * amount; + count += sw_val * amount; } } @@ -335,9 +335,8 @@ static void shift_block(oparg_T *oap, int amount) const int oldstate = State; char_u *newp; const int oldcol = curwin->w_cursor.col; - int p_sw = (int)get_sw_value_indent(curbuf); - long *p_vts = curbuf->b_p_vts_array; - const long p_ts = curbuf->b_p_ts; + const int sw_val = (int)get_sw_value_indent(curbuf); + const int ts_val = (int)curbuf->b_p_ts; struct block_def bd; int incr; int i = 0, j = 0; @@ -352,8 +351,8 @@ static void shift_block(oparg_T *oap, int amount) } // total is number of screen columns to be inserted/removed - int total = (int)((unsigned)amount * (unsigned)p_sw); - if ((total / p_sw) != amount) { + int total = (int)((unsigned)amount * (unsigned)sw_val); + if ((total / sw_val) != amount) { return; // multiplication overflow } @@ -379,16 +378,24 @@ static void shift_block(oparg_T *oap, int amount) bd.startspaces = 0; } } - for (; ascii_iswhite(*bd.textstart);) { - // TODO(fmoralesc): is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); + + // TODO(vim): is passing bd.textstart for start of the line OK? + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, + bd.start_vcol, bd.textstart, bd.textstart); + while (ascii_iswhite(*cts.cts_ptr)) { + incr = lbr_chartabsize_adv(&cts); total += incr; - bd.start_vcol += incr; + cts.cts_vcol += incr; } + bd.textstart = (char_u *)cts.cts_ptr; + bd.start_vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + // OK, now total=all the VWS reqd, and textstart points at the 1st // non-ws char in the block. if (!curbuf->b_p_et) { - tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, p_vts, &i, &j); + tabstop_fromto(ws_vcol, ws_vcol + total, ts_val, curbuf->b_p_vts_array, &i, &j); } else { j = total; } @@ -439,10 +446,16 @@ static void shift_block(oparg_T *oap, int amount) // The character's column is in "bd.start_vcol". colnr_T non_white_col = bd.start_vcol; - while (ascii_iswhite(*non_white)) { - incr = lbr_chartabsize_adv(bd.textstart, &non_white, non_white_col); - non_white_col += incr; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, + non_white_col, bd.textstart, non_white); + while (ascii_iswhite(*cts.cts_ptr)) { + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + non_white_col = cts.cts_vcol; + non_white = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); const colnr_T block_space_width = non_white_col - oap->start_vcol; // We will shift by "total" or "block_space_width", whichever is less. @@ -463,17 +476,19 @@ static void shift_block(oparg_T *oap, int amount) if (bd.startspaces) { verbatim_copy_width -= bd.start_char_vcols; } - while (verbatim_copy_width < destination_col) { - char_u *line = verbatim_copy_end; - - // TODO: is passing verbatim_copy_end for start of the line OK? - incr = lbr_chartabsize(line, verbatim_copy_end, verbatim_copy_width); - if (verbatim_copy_width + incr > destination_col) { + init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width, + bd.textstart, verbatim_copy_end); + while (cts.cts_vcol < destination_col) { + incr = lbr_chartabsize(&cts); + if (cts.cts_vcol + incr > destination_col) { break; } - verbatim_copy_width += incr; - MB_PTR_ADV(verbatim_copy_end); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + verbatim_copy_width = cts.cts_vcol; + verbatim_copy_end = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); // If "destination_col" is different from the width of the initial // part of the line that will be copied, it means we encountered a tab @@ -512,7 +527,7 @@ static void shift_block(oparg_T *oap, int amount) /// Caller must prepare for undo. static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def *bdp) { - int p_ts; + int ts_val; int count = 0; // extra spaces to replace a cut TAB int spaces = 0; // non-zero if cutting a TAB colnr_T offset; // pointer along new line @@ -531,18 +546,18 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def oldp = ml_get(lnum); if (b_insert) { - p_ts = bdp->start_char_vcols; + ts_val = bdp->start_char_vcols; spaces = bdp->startspaces; if (spaces != 0) { - count = p_ts - 1; // we're cutting a TAB + count = ts_val - 1; // we're cutting a TAB } offset = bdp->textcol; } else { // append - p_ts = bdp->end_char_vcols; + ts_val = bdp->end_char_vcols; if (!bdp->is_short) { // spaces = padding after block - spaces = (bdp->endspaces ? p_ts - bdp->endspaces : 0); + spaces = (bdp->endspaces ? ts_val - bdp->endspaces : 0); if (spaces != 0) { - count = p_ts - 1; // we're cutting a TAB + count = ts_val - 1; // we're cutting a TAB } offset = bdp->textcol + bdp->textlen - (spaces != 0); } else { // spaces = padding to block edge @@ -566,7 +581,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def assert(count >= 0); // Make sure the allocated size matches what is actually copied below. newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len - + (spaces > 0 && !bdp->is_short ? (size_t)p_ts - (size_t)spaces : 0) + + (spaces > 0 && !bdp->is_short ? (size_t)ts_val - (size_t)spaces : 0) + (size_t)count + 1); // copy up to shifted part @@ -585,7 +600,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def if (spaces > 0 && !bdp->is_short) { if (*oldp == TAB) { // insert post-padding - memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); + memset(newp + offset + spaces, ' ', (size_t)(ts_val - spaces)); // We're splitting a TAB, don't copy it. oldp++; // We allowed for that TAB, remember this now @@ -684,7 +699,7 @@ void op_reindent(oparg_T *oap, Indenter how) oap->is_VIsual ? start_lnum + (linenr_T)oap->line_count : last_changed + 1, 0L, true); } else if (oap->is_VIsual) { - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } if (oap->line_count > p_report) { @@ -911,7 +926,7 @@ int do_record(int c) if (!ui_has_messages()) { // Enable macro indicator temporarily set_option_value("ch", 1L, NULL, 0); - update_screen(VALID); + update_screen(UPD_VALID); changed_cmdheight = true; } @@ -965,7 +980,7 @@ int do_record(int c) if (changed_cmdheight) { // Restore cmdheight set_option_value("ch", 0L, NULL, 0); - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } } return retval; @@ -998,14 +1013,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); @@ -1112,7 +1127,7 @@ int do_execreg(int regname, int colon, int addcr, int silent) // don't keep the cmdline containing @: XFREE_CLEAR(new_last_cmdline); // Escape all control characters with a CTRL-V - p = vim_strsave_escaped_ext(last_cmdline, + p = vim_strsave_escaped_ext((char_u *)last_cmdline, (char_u *)"\001\002\003\004\005\006\007" "\010\011\012\013\014\015\016\017" "\020\021\022\023\024\025\026\027" @@ -1277,7 +1292,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)) { @@ -1313,34 +1328,6 @@ int insert_reg(int regname, bool literally_arg) return retval; } -/// Stuff a string into the typeahead buffer, such that edit() will insert it -/// literally ("literally" true) or interpret is as typed characters. -static void stuffescaped(const char *arg, bool literally) -{ - while (*arg != NUL) { - // Stuff a sequence of normal ASCII characters, that's fast. Also - // stuff K_SPECIAL to get the effect of a special key when "literally" - // is true. - const char *const start = arg; - while ((*arg >= ' ' && *arg < DEL) || ((uint8_t)(*arg) == K_SPECIAL - && !literally)) { - arg++; - } - if (arg > start) { - stuffReadbuffLen(start, (arg - start)); - } - - // stuff a single special character - if (*arg != NUL) { - const int c = mb_cptr2char_adv((const char_u **)&arg); - if (literally && ((c < ' ' && c != TAB) || c == DEL)) { - stuffcharReadbuff(Ctrl_V); - } - stuffcharReadbuff(c); - } - } -} - /// If "regname" is a special register, return true and store a pointer to its /// value in "argp". /// @@ -1348,7 +1335,7 @@ static void stuffescaped(const char *arg, bool literally) /// @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) +bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) { size_t cnt; @@ -1359,15 +1346,15 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) 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; @@ -1382,11 +1369,11 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) 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)); @@ -1398,8 +1385,9 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) 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; @@ -1411,7 +1399,7 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) 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; @@ -1420,11 +1408,11 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) 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; } @@ -1578,6 +1566,7 @@ int op_delete(oparg_T *oap) // Put deleted text into register 1 and shift number registers if the // delete contains a line break, or when using a specific operator (Vi // compatible) + if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) { shift_delete_registers(is_append_register(oap->regname)); reg = &y_regs[1]; @@ -1927,8 +1916,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; } @@ -2003,7 +1992,7 @@ static int op_replace(oparg_T *oap, int c) curwin->w_cursor.col = 0; oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); if (oap->end.col) { - --oap->end.col; + oap->end.col--; } } else if (!oap->inclusive) { dec(&(oap->end)); @@ -2091,7 +2080,7 @@ void op_tilde(oparg_T *oap) { pos_T pos; struct block_def bd; - int did_change = FALSE; + int did_change = false; if (u_save((linenr_T)(oap->start.lnum - 1), (linenr_T)(oap->end.lnum + 1)) == FAIL) { @@ -2117,7 +2106,7 @@ void op_tilde(oparg_T *oap) pos.col = 0; oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); if (oap->end.col) { - --oap->end.col; + oap->end.col--; } } else if (!oap->inclusive) { dec(&(oap->end)); @@ -2144,7 +2133,7 @@ void op_tilde(oparg_T *oap) if (!did_change && oap->is_VIsual) { // No change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { @@ -2166,7 +2155,7 @@ void op_tilde(oparg_T *oap) /// @param length is rounded up to include the whole last multi-byte character. /// Also works correctly when the number of bytes changes. /// -/// @return TRUE if some character was changed. +/// @return true if some character was changed. static int swapchars(int op_type, pos_T *pos, int length) FUNC_ATTR_NONNULL_ALL { @@ -2263,7 +2252,7 @@ void op_insert(oparg_T *oap, long count1) // vis block is still marked. Get rid of it now. curwin->w_cursor.lnum = oap->start.lnum; - update_screen(INVERTED); + update_screen(UPD_INVERTED); if (oap->motion_type == kMTBlockWise) { // When 'virtualedit' is used, need to insert the extra spaces before @@ -2283,7 +2272,7 @@ void op_insert(oparg_T *oap, long count1) coladvance_force(oap->op_type == OP_APPEND ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } curwin->w_ve_flags = old_ve_flags; } @@ -2304,10 +2293,10 @@ void op_insert(oparg_T *oap, long count1) if (oap->motion_type == kMTBlockWise && curwin->w_cursor.coladd == 0) { // Move the cursor to the character right of the block. - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; while (*get_cursor_pos_ptr() != NUL && (curwin->w_cursor.col < bd.textcol + bd.textlen)) { - ++curwin->w_cursor.col; + curwin->w_cursor.col++; } if (bd.is_short && !bd.is_MAX) { // First line was too short, make it longer and adjust the @@ -2413,7 +2402,7 @@ void op_insert(oparg_T *oap, long count1) if (oap->op_type == OP_APPEND) { pre_textlen += bd2.textlen - bd.textlen; if (bd2.endspaces) { - --bd2.textlen; + bd2.textlen--; } } bd.textcol = bd2.textcol; @@ -2464,7 +2453,7 @@ void op_insert(oparg_T *oap, long count1) /// handle a change operation /// -/// @return TRUE if edit() returns because of a CTRL-O command +/// @return true if edit() returns because of a CTRL-O command int op_change(oparg_T *oap) { colnr_T l; @@ -2490,10 +2479,10 @@ int op_change(oparg_T *oap) // save for undo if (curbuf->b_ml.ml_flags & ML_EMPTY) { if (u_save_cursor() == FAIL) { - return FALSE; + return false; } } else if (op_delete(oap) == FAIL) { - return FALSE; + return false; } if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum) @@ -2511,7 +2500,7 @@ int op_change(oparg_T *oap) } firstline = ml_get(oap->start.lnum); pre_textlen = (long)STRLEN(firstline); - pre_indent = (long)getwhitecols(firstline); + pre_indent = (long)getwhitecols((char *)firstline); bd.textcol = curwin->w_cursor.col; } @@ -2519,7 +2508,7 @@ int op_change(oparg_T *oap) fix_indent(); } - retval = edit(NUL, FALSE, (linenr_T)1); + retval = edit(NUL, false, (linenr_T)1); /* * In Visual block mode, handle copying the new text to all lines of the @@ -2532,7 +2521,7 @@ int op_change(oparg_T *oap) // the indent, exclude that indent change from the inserted text. firstline = ml_get(oap->start.lnum); if (bd.textcol > (colnr_T)pre_indent) { - long new_indent = (long)getwhitecols(firstline); + long new_indent = (long)getwhitecols((char *)firstline); pre_textlen += new_indent - pre_indent; bd.textcol += (colnr_T)(new_indent - pre_indent); @@ -2961,7 +2950,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; @@ -2970,7 +2959,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; @@ -3093,10 +3082,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'); @@ -3104,7 +3093,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; @@ -3115,7 +3104,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 @@ -3132,7 +3121,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) { @@ -3308,12 +3297,19 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // get the old line and advance to the position to insert at oldp = get_cursor_line_ptr(); oldlen = STRLEN(oldp); - for (ptr = oldp; vcol < col && *ptr;) { + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, + oldp, oldp); + + while (cts.cts_vcol < col && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize_adv(oldp, &ptr, vcol); - vcol += incr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + vcol = cts.cts_vcol; + ptr = (char_u *)cts.cts_ptr; bd.textcol = (colnr_T)(ptr - oldp); + clear_chartabsize_arg(&cts); shortline = (vcol < col) || (vcol == col && !*ptr); @@ -3322,7 +3318,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } else if (vcol > col) { bd.endspaces = vcol - col; bd.startspaces = incr - bd.endspaces; - --bd.textcol; + bd.textcol--; delcount = 1; bd.textcol -= utf_head_off(oldp, oldp + bd.textcol); if (oldp[bd.textcol] != TAB) { @@ -3340,9 +3336,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // calculate number of spaces required to fill right side of // block spaces = y_width + 1; + init_chartabsize_arg(&cts, curwin, 0, 0, + (char_u *)y_array[i], (char_u *)y_array[i]); for (int j = 0; j < yanklen; j++) { - spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); + spaces -= lbr_chartabsize(&cts); + cts.cts_ptr++; + cts.cts_vcol = 0; } + clear_chartabsize_arg(&cts); if (spaces < 0) { spaces = 0; } @@ -3395,7 +3396,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, bd.textcol, delcount, (int)totlen + lines_appended, kExtmarkUndo); - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; if (i == 0) { curwin->w_cursor.col += bd.startspaces; } @@ -3442,12 +3443,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; @@ -3580,13 +3578,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; @@ -3670,8 +3668,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; @@ -3702,7 +3700,7 @@ error: // put cursor on first non-blank in first inserted line curwin->w_cursor.col = 0; if (dir == FORWARD) { - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } beginline(BL_WHITE | BL_FIX); } else { // put cursor on first inserted character @@ -3712,7 +3710,7 @@ error: } msgmore(nr_lines); - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; end: if (cmdmod.cmod_flags & CMOD_LOCKMARKS) { @@ -3726,7 +3724,7 @@ end: xfree(y_array); } - VIsual_active = FALSE; + VIsual_active = false; // If the cursor is past the end of the line put it at the end. adjust_cursor_eol(); @@ -3755,7 +3753,7 @@ void adjust_cursor_eol(void) } } -/// @return TRUE if lines starting with '#' should be left aligned. +/// @return true if lines starting with '#' should be left aligned. int preprocs_left(void) { return ((curbuf->b_p_si && !curbuf->b_p_cin) @@ -3841,7 +3839,7 @@ void ex_display(exarg_T *eap) bool do_show = false; for (size_t j = 0; !do_show && j < yb->y_size; j++) { - do_show = !message_filtered((char_u *)yb->y_array[j]); + do_show = !message_filtered(yb->y_array[j]); } if (do_show || yb->y_size == 0) { @@ -3862,7 +3860,7 @@ void ex_display(exarg_T *eap) for (p = (char_u *)yb->y_array[j]; *p != NUL && (n -= ptr2cells((char *)p)) >= 0; p++) { // -V1019 clen = utfc_ptr2len((char *)p); - msg_outtrans_len(p, clen); + msg_outtrans_len((char *)p, clen); p += clen - 1; } } @@ -3878,7 +3876,7 @@ void ex_display(exarg_T *eap) // display last inserted text if ((p = get_last_insert()) != NULL && (arg == NULL || vim_strchr((char *)arg, '.') != NULL) && !got_int - && !message_filtered(p)) { + && !message_filtered((char *)p)) { msg_puts("\n c \". "); dis_msg(p, true); } @@ -3887,13 +3885,13 @@ void ex_display(exarg_T *eap) if (last_cmdline != NULL && (arg == NULL || vim_strchr((char *)arg, ':') != NULL) && !got_int && !message_filtered(last_cmdline)) { msg_puts("\n c \": "); - dis_msg(last_cmdline, false); + dis_msg((char_u *)last_cmdline, false); } // display current file name if (curbuf->b_fname != NULL && (arg == NULL || vim_strchr((char *)arg, '%') != NULL) && !got_int - && !message_filtered((char_u *)curbuf->b_fname)) { + && !message_filtered(curbuf->b_fname)) { msg_puts("\n c \"% "); dis_msg((char_u *)curbuf->b_fname, false); } @@ -3903,7 +3901,7 @@ void ex_display(exarg_T *eap) char *fname; linenr_T dummy; - if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered((char_u *)fname)) { + if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) { msg_puts("\n c \"# "); dis_msg((char_u *)fname, false); } @@ -3912,14 +3910,14 @@ void ex_display(exarg_T *eap) // display last search pattern if (last_search_pat() != NULL && (arg == NULL || vim_strchr((char *)arg, '/') != NULL) && !got_int - && !message_filtered(last_search_pat())) { + && !message_filtered((char *)last_search_pat())) { msg_puts("\n c \"/ "); dis_msg(last_search_pat(), false); } // display last used expression if (expr_line != NULL && (arg == NULL || vim_strchr((char *)arg, '=') != NULL) - && !got_int && !message_filtered(expr_line)) { + && !got_int && !message_filtered((char *)expr_line)) { msg_puts("\n c \"= "); dis_msg(expr_line, false); } @@ -3940,10 +3938,10 @@ static void dis_msg(const char_u *p, bool skip_esc) && !(*p == ESC && skip_esc && *(p + 1) == NUL) && (n -= ptr2cells((char *)p)) >= 0) { if ((l = utfc_ptr2len((char *)p)) > 1) { - msg_outtrans_len(p, l); + msg_outtrans_len((char *)p, l); p += l; } else { - msg_outtrans_len(p++, 1); + msg_outtrans_len((char *)p++, 1); } } os_breakcheck(); @@ -3962,9 +3960,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) { @@ -3986,7 +3984,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; @@ -4001,7 +3999,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 @@ -4015,8 +4013,8 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co } /// @param count number of lines (minimal 2) to join at cursor position. -/// @param save_undo when TRUE, save lines for undo first. -/// @param use_formatoptions set to FALSE when e.g. processing backspace and comment +/// @param save_undo when true, save lines for undo first. +/// @param use_formatoptions set to false when e.g. processing backspace and comment /// leaders should not be removed. /// @param setmark when true, sets the '[ and '] mark, else, the caller is expected /// to set those marks. @@ -4037,7 +4035,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions colnr_T col = 0; int ret = OK; int *comments = NULL; - int remove_comments = (use_formatoptions == TRUE) + int remove_comments = (use_formatoptions == true) && has_format_option(FO_REMOVE_COMS); bool prev_was_comment = false; assert(count >= 1); @@ -4092,11 +4090,11 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions if (endcurr1 == ' ') { endcurr1 = endcurr2; } else { - ++spaces[t]; + spaces[t]++; } // Extra space when 'joinspaces' set and line ends in '.', '?', or '!'. if (p_js && (endcurr1 == '.' || endcurr1 == '?' || endcurr1 == '!')) { - ++spaces[t]; + spaces[t]++; } } } @@ -4212,7 +4210,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; theend: xfree(spaces); @@ -4222,532 +4220,6 @@ theend: return ret; } -/// @return TRUE if the two comment leaders given are the same. -/// -/// @param lnum The first line. White-space is ignored. -/// -/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'. -static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, int leader2_len, - char_u *leader2_flags) -{ - int idx1 = 0, idx2 = 0; - char_u *p; - char_u *line1; - char_u *line2; - - if (leader1_len == 0) { - return leader2_len == 0; - } - - /* - * If first leader has 'f' flag, the lines can be joined only if the - * second line does not have a leader. - * If first leader has 'e' flag, the lines can never be joined. - * If first leader has 's' flag, the lines can only be joined if there is - * some text after it and the second line has the 'm' flag. - */ - if (leader1_flags != NULL) { - for (p = leader1_flags; *p && *p != ':'; ++p) { - if (*p == COM_FIRST) { - return leader2_len == 0; - } - if (*p == COM_END) { - return FALSE; - } - if (*p == COM_START) { - if (*(ml_get(lnum) + leader1_len) == NUL) { - return FALSE; - } - if (leader2_flags == NULL || leader2_len == 0) { - return FALSE; - } - for (p = leader2_flags; *p && *p != ':'; ++p) { - if (*p == COM_MIDDLE) { - return TRUE; - } - } - return FALSE; - } - } - } - - /* - * Get current line and next line, compare the leaders. - * The first line has to be saved, only one line can be locked at a time. - */ - line1 = vim_strsave(ml_get(lnum)); - for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {} - line2 = ml_get(lnum + 1); - for (idx2 = 0; idx2 < leader2_len; ++idx2) { - if (!ascii_iswhite(line2[idx2])) { - if (line1[idx1++] != line2[idx2]) { - break; - } - } else { - while (ascii_iswhite(line1[idx1])) { - ++idx1; - } - } - } - xfree(line1); - - return idx2 == leader2_len && idx1 == leader1_len; -} - -/// Implementation of the format operator 'gq'. -/// -/// @param keep_cursor keep cursor on same text char -static void op_format(oparg_T *oap, int keep_cursor) -{ - linenr_T old_line_count = curbuf->b_ml.ml_line_count; - - // Place the cursor where the "gq" or "gw" command was given, so that "u" - // can put it back there. - curwin->w_cursor = oap->cursor_start; - - if (u_save((linenr_T)(oap->start.lnum - 1), - (linenr_T)(oap->end.lnum + 1)) == FAIL) { - return; - } - curwin->w_cursor = oap->start; - - if (oap->is_VIsual) { - // When there is no change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); - } - - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // Set '[ mark at the start of the formatted area - curbuf->b_op_start = oap->start; - } - - // For "gw" remember the cursor position and put it back below (adjusted - // for joined and split lines). - if (keep_cursor) { - saved_cursor = oap->cursor_start; - } - - format_lines((linenr_T)oap->line_count, keep_cursor); - - /* - * Leave the cursor at the first non-blank of the last formatted line. - * If the cursor was moved one line back (e.g. with "Q}") go to the next - * line, so "." will do the next lines. - */ - if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ++curwin->w_cursor.lnum; - } - beginline(BL_WHITE | BL_FIX); - old_line_count = curbuf->b_ml.ml_line_count - old_line_count; - msgmore(old_line_count); - - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // put '] mark on the end of the formatted area - curbuf->b_op_end = curwin->w_cursor; - } - - if (keep_cursor) { - curwin->w_cursor = saved_cursor; - saved_cursor.lnum = 0; - - // formatting may have made the cursor position invalid - check_cursor(); - } - - if (oap->is_VIsual) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_old_cursor_lnum != 0) { - // When lines have been inserted or deleted, adjust the end of - // the Visual area to be redrawn. - if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { - wp->w_old_cursor_lnum += old_line_count; - } else { - wp->w_old_visual_lnum += old_line_count; - } - } - } - } -} - -/// Implementation of the format operator 'gq' for when using 'formatexpr'. -static void op_formatexpr(oparg_T *oap) -{ - if (oap->is_VIsual) { - // When there is no change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); - } - - if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) { - // As documented: when 'formatexpr' returns non-zero fall back to - // internal formatting. - op_format(oap, false); - } -} - -/// @param c character to be inserted -int fex_format(linenr_T lnum, long count, int c) -{ - int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL); - int r; - char_u *fex; - - /* - * Set v:lnum to the first line number and v:count to the number of lines. - * Set v:char to the character to be inserted (can be NUL). - */ - set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); - set_vim_var_nr(VV_COUNT, (varnumber_T)count); - set_vim_var_char(c); - - // Make a copy, the option could be changed while calling it. - fex = vim_strsave(curbuf->b_p_fex); - // Evaluate the function. - if (use_sandbox) { - sandbox++; - } - r = (int)eval_to_number((char *)fex); - if (use_sandbox) { - sandbox--; - } - - set_vim_var_string(VV_CHAR, NULL, -1); - xfree(fex); - - return r; -} - -/// @param line_count number of lines to format, starting at the cursor position. -/// when negative, format until the end of the paragraph. -/// -/// Lines after the cursor line are saved for undo, caller must have saved the -/// first line. -/// -/// @param avoid_fex don't use 'formatexpr' -void format_lines(linenr_T line_count, int avoid_fex) -{ - bool is_not_par; // current line not part of parag. - bool next_is_not_par; // next line not part of paragraph - bool is_end_par; // at end of paragraph - bool prev_is_end_par = false; // prev. line not part of parag. - bool next_is_start_par = false; - int leader_len = 0; // leader len of current line - int next_leader_len; // leader len of next line - char_u *leader_flags = NULL; // flags for leader of current line - char_u *next_leader_flags = NULL; // flags for leader of next line - bool advance = true; - int second_indent = -1; // indent for second line (comment aware) - bool first_par_line = true; - int smd_save; - long count; - bool need_set_indent = true; // set indent of next paragraph - linenr_T first_line = curwin->w_cursor.lnum; - bool force_format = false; - const int old_State = State; - - // length of a line to force formatting: 3 * 'tw' - const int max_len = comp_textwidth(true) * 3; - - // check for 'q', '2' and '1' in 'formatoptions' - const bool do_comments = has_format_option(FO_Q_COMS); // format comments - int do_comments_list = 0; // format comments with 'n' or '2' - const bool do_second_indent = has_format_option(FO_Q_SECOND); - const bool do_number_indent = has_format_option(FO_Q_NUMBER); - const bool do_trail_white = has_format_option(FO_WHITE_PAR); - - // Get info about the previous and current line. - if (curwin->w_cursor.lnum > 1) { - is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, - &leader_len, &leader_flags, do_comments); - } else { - is_not_par = true; - } - next_is_not_par = fmt_check_par(curwin->w_cursor.lnum, - &next_leader_len, &next_leader_flags, do_comments - ); - is_end_par = (is_not_par || next_is_not_par); - if (!is_end_par && do_trail_white) { - is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); - } - - curwin->w_cursor.lnum--; - for (count = line_count; count != 0 && !got_int; --count) { - /* - * Advance to next paragraph. - */ - if (advance) { - curwin->w_cursor.lnum++; - prev_is_end_par = is_end_par; - is_not_par = next_is_not_par; - leader_len = next_leader_len; - leader_flags = next_leader_flags; - } - - /* - * The last line to be formatted. - */ - if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { - next_is_not_par = true; - next_leader_len = 0; - next_leader_flags = NULL; - } else { - next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1, - &next_leader_len, &next_leader_flags, do_comments - ); - if (do_number_indent) { - next_is_start_par = - (get_number_indent(curwin->w_cursor.lnum + 1) > 0); - } - } - advance = true; - is_end_par = (is_not_par || next_is_not_par || next_is_start_par); - if (!is_end_par && do_trail_white) { - is_end_par = !ends_in_white(curwin->w_cursor.lnum); - } - - /* - * Skip lines that are not in a paragraph. - */ - if (is_not_par) { - if (line_count < 0) { - break; - } - } else { - /* - * For the first line of a paragraph, check indent of second line. - * Don't do this for comments and empty lines. - */ - if (first_par_line - && (do_second_indent || do_number_indent) - && prev_is_end_par - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { - if (leader_len == 0 && next_leader_len == 0) { - // no comment found - second_indent = - get_indent_lnum(curwin->w_cursor.lnum + 1); - } else { - second_indent = next_leader_len; - do_comments_list = 1; - } - } else if (do_number_indent) { - if (leader_len == 0 && next_leader_len == 0) { - // no comment found - second_indent = - get_number_indent(curwin->w_cursor.lnum); - } else { - // get_number_indent() is now "comment aware"... - second_indent = - get_number_indent(curwin->w_cursor.lnum); - do_comments_list = 1; - } - } - } - - /* - * When the comment leader changes, it's the end of the paragraph. - */ - if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count - || !same_leader(curwin->w_cursor.lnum, - leader_len, leader_flags, - next_leader_len, - next_leader_flags)) { - // Special case: If the next line starts with a line comment - // and this line has a line comment after some text, the - // paragraph doesn't really end. - if (next_leader_flags == NULL - || STRNCMP(next_leader_flags, "://", 3) != 0 - || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { - is_end_par = true; - } - } - - /* - * If we have got to the end of a paragraph, or the line is - * getting long, format it. - */ - if (is_end_par || force_format) { - if (need_set_indent) { - int indent = 0; // amount of indent needed - - // Replace indent in first line of a paragraph with minimal - // number of tabs and spaces, according to current options. - // For the very first formatted line keep the current - // indent. - if (curwin->w_cursor.lnum == first_line) { - indent = get_indent(); - } else if (curbuf->b_p_lisp) { - indent = get_lisp_indent(); - } else { - if (cindent_on()) { - indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); - } else { - indent = get_indent(); - } - } - (void)set_indent(indent, SIN_CHANGED); - } - - // put cursor on last non-space - State = MODE_NORMAL; // don't go past end-of-line - coladvance(MAXCOL); - while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { - dec_cursor(); - } - - // do the formatting, without 'showmode' - State = MODE_INSERT; // for open_line() - smd_save = p_smd; - p_smd = FALSE; - insertchar(NUL, INSCHAR_FORMAT - + (do_comments ? INSCHAR_DO_COM : 0) - + (do_comments && do_comments_list - ? INSCHAR_COM_LIST : 0) - + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); - State = old_State; - p_smd = smd_save; - second_indent = -1; - // at end of par.: need to set indent of next par. - need_set_indent = is_end_par; - if (is_end_par) { - // When called with a negative line count, break at the - // end of the paragraph. - if (line_count < 0) { - break; - } - first_par_line = true; - } - force_format = false; - } - - /* - * When still in same paragraph, join the lines together. But - * first delete the leader from the second line. - */ - if (!is_end_par) { - advance = false; - curwin->w_cursor.lnum++; - curwin->w_cursor.col = 0; - if (line_count < 0 && u_save_cursor() == FAIL) { - break; - } - if (next_leader_len > 0) { - (void)del_bytes(next_leader_len, false, false); - mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0); - } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND - int indent = (int)getwhitecols_curline(); - - if (indent > 0) { - (void)del_bytes(indent, false, false); - mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0); - } - } - curwin->w_cursor.lnum--; - if (do_join(2, TRUE, FALSE, FALSE, false) == FAIL) { - beep_flush(); - break; - } - first_par_line = false; - // If the line is getting long, format it next time - if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) { - force_format = true; - } else { - force_format = false; - } - } - } - line_breakcheck(); - } -} - -/// @return TRUE if line "lnum" ends in a white character. -static int ends_in_white(linenr_T lnum) -{ - char_u *s = ml_get(lnum); - size_t l; - - if (*s == NUL) { - return FALSE; - } - l = STRLEN(s) - 1; - return ascii_iswhite(s[l]); -} - -/// Blank lines, and lines containing only the comment leader, are left -/// untouched by the formatting. The function returns TRUE in this -/// case. It also returns TRUE when a line starts with the end of a comment -/// ('e' in comment flags), so that this line is skipped, and not joined to the -/// previous line. A new paragraph starts after a blank line, or when the -/// comment leader changes. -static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, int do_comments) -{ - char_u *flags = NULL; // init for GCC - char_u *ptr; - - ptr = ml_get(lnum); - if (do_comments) { - *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); - } else { - *leader_len = 0; - } - - if (*leader_len > 0) { - /* - * Search for 'e' flag in comment leader flags. - */ - flags = *leader_flags; - while (*flags && *flags != ':' && *flags != COM_END) { - ++flags; - } - } - - return *skipwhite((char *)ptr + *leader_len) == NUL - || (*leader_len > 0 && *flags == COM_END) - || startPS(lnum, NUL, FALSE); -} - -/// Used for auto-formatting. -/// -/// @return TRUE when a paragraph starts in line "lnum". -/// FALSE when the previous line is in the same paragraph. -int paragraph_start(linenr_T lnum) -{ - char_u *p; - int leader_len = 0; // leader len of current line - char_u *leader_flags = NULL; // flags for leader of current line - int next_leader_len = 0; // leader len of next line - char_u *next_leader_flags = NULL; // flags for leader of next line - - if (lnum <= 1) { - return TRUE; // start of the file - } - p = ml_get(lnum - 1); - if (*p == NUL) { - return TRUE; // after empty line - } - const bool do_comments = has_format_option(FO_Q_COMS); // format comments - if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { - return true; // after non-paragraph line - } - - if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { - return true; // "lnum" is not a paragraph line - } - - if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) { - return TRUE; // missing trailing space in previous line. - } - if (has_format_option(FO_Q_NUMBER) && (get_number_indent(lnum) > 0)) { - return TRUE; // numbered item starts in "lnum". - } - if (!same_leader(lnum - 1, leader_len, leader_flags, - next_leader_len, next_leader_flags)) { - return TRUE; // change of comment leader. - } - return FALSE; -} - /// prepare a few things for block mode yank/delete/tilde /// /// for delete: @@ -4784,22 +4256,28 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool bdp->start_char_vcols = 0; line = ml_get(lnum); - pstart = line; prev_pstart = line; - while (bdp->start_vcol < oap->start_vcol && *pstart) { + + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line); + while (cts.cts_vcol < oap->start_vcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize(line, pstart, bdp->start_vcol); - bdp->start_vcol += incr; - if (ascii_iswhite(*pstart)) { + incr = lbr_chartabsize(&cts); + cts.cts_vcol += incr; + if (ascii_iswhite(*cts.cts_ptr)) { bdp->pre_whitesp += incr; bdp->pre_whitesp_c++; } else { bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; } - prev_pstart = pstart; - MB_PTR_ADV(pstart); + prev_pstart = (char_u *)cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); } + bdp->start_vcol = cts.cts_vcol; + pstart = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); + bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) { // line too short bdp->end_vcol = bdp->start_vcol; @@ -4835,13 +4313,19 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool } } } else { + init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, + line, pend); prev_pend = pend; - while (bdp->end_vcol <= oap->end_vcol && *pend != NUL) { + while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - prev_pend = pend; - incr = lbr_chartabsize_adv(line, &pend, bdp->end_vcol); - bdp->end_vcol += incr; + prev_pend = (char_u *)cts.cts_ptr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + bdp->end_vcol = cts.cts_vcol; + pend = (char_u *)cts.cts_ptr; + clear_chartabsize_arg(&cts); + if (bdp->end_vcol <= oap->end_vcol && (!is_del || oap->op_type == OP_APPEND @@ -4966,7 +4450,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) if (!change_cnt && oap->is_VIsual) { // No change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // Set '[ mark if something changed. Keep the last end @@ -5014,12 +4498,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) pos_T endpos; colnr_T save_coladd = 0; - const bool do_hex = vim_strchr((char *)curbuf->b_p_nf, 'x') != NULL; // "heX" - const bool do_oct = vim_strchr((char *)curbuf->b_p_nf, 'o') != NULL; // "Octal" - const bool do_bin = vim_strchr((char *)curbuf->b_p_nf, 'b') != NULL; // "Bin" - const bool do_alpha = vim_strchr((char *)curbuf->b_p_nf, 'p') != NULL; // "alPha" + const bool do_hex = vim_strchr(curbuf->b_p_nf, 'x') != NULL; // "heX" + const bool do_oct = vim_strchr(curbuf->b_p_nf, 'o') != NULL; // "Octal" + const bool do_bin = vim_strchr(curbuf->b_p_nf, 'b') != NULL; // "Bin" + const bool do_alpha = vim_strchr(curbuf->b_p_nf, 'p') != NULL; // "alPha" // "Unsigned" - const bool do_unsigned = vim_strchr((char *)curbuf->b_p_nf, 'u') != NULL; + const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL; if (virtual_active()) { save_coladd = pos->coladd; @@ -5318,7 +4802,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } *ptr = NUL; STRCAT(buf1, buf2); - ins_str(buf1); // insert the new number + ins_str((char *)buf1); // insert the new number endpos = curwin->w_cursor; if (curwin->w_cursor.col) { curwin->w_cursor.col--; @@ -5457,16 +4941,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); @@ -5557,11 +5041,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) { @@ -5583,7 +5067,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); } @@ -5615,7 +5099,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a // Special case: '/' search pattern if (name == '/') { - set_last_search_pat(str, RE_SEARCH, TRUE, TRUE); + set_last_search_pat(str, RE_SEARCH, true, true); return; } @@ -5671,7 +5155,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); } @@ -5685,7 +5169,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 { @@ -5705,18 +5189,18 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str // Count the number of lines within the string if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ++ss) { + for (char_u **ss = (char_u **)str; *ss != NULL; ss++) { newlines++; } } else { newlines = memcnt(str, '\n', len); if (yank_type == kMTCharWise || len == 0 || str[len - 1] != '\n') { extraline = 1; - ++newlines; // count extra newline at the end + newlines++; // count extra newline at the end } if (y_ptr->y_size > 0 && y_ptr->y_type == kMTCharWise) { append = true; - --newlines; // uncount newline when appending first line + newlines--; // uncount newline when appending first line } } @@ -5727,9 +5211,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. @@ -5738,7 +5221,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str // Find the end of each line and save it into the array. if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ++ss, ++lnum) { + for (char_u **ss = (char_u **)str; *ss != NULL; ss++, lnum++) { size_t ss_len = STRLEN(*ss); pp[lnum] = xmemdupz(*ss, ss_len); if (ss_len > maxlen) { @@ -5747,7 +5230,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); @@ -5770,7 +5253,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); @@ -5789,7 +5272,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. @@ -5888,12 +5371,12 @@ void cursor_pos_info(dict_T *dict) max_pos = VIsual; } if (*p_sel == 'e' && max_pos.col > 0) { - --max_pos.col; + max_pos.col--; } if (l_VIsual_mode == Ctrl_V) { - char_u *const saved_sbr = p_sbr; - char_u *const saved_w_sbr = curwin->w_p_sbr; + char *const saved_sbr = p_sbr; + char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; @@ -5901,8 +5384,7 @@ void cursor_pos_info(dict_T *dict) oparg.is_VIsual = true; oparg.motion_type = kMTBlockWise; oparg.op_type = OP_NOP; - getvcols(curwin, &min_pos, &max_pos, - &oparg.start_vcol, &oparg.end_vcol); + getvcols(curwin, &min_pos, &max_pos, &oparg.start_vcol, &oparg.end_vcol); p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; if (curwin->w_curswant == MAXCOL) { @@ -5918,7 +5400,7 @@ void cursor_pos_info(dict_T *dict) line_count_selected = max_pos.lnum - min_pos.lnum + 1; } - for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { // Check for a CTRL-C every 100000 characters. if (byte_count > last_check) { os_breakcheck(); @@ -6065,14 +5547,14 @@ void cursor_pos_info(dict_T *dict) } if (dict == NULL) { // Don't shorten this message, the user asked for it. - p = p_shm; - p_shm = (char_u *)""; + p = (char_u *)p_shm; + p_shm = ""; if (p_ch < 1) { msg_start(); msg_scroll = true; } msg((char *)IObuff); - p_shm = p; + p_shm = (char *)p; } } @@ -6140,6 +5622,23 @@ static void op_colon(oparg_T *oap) // do_cmdline() does the rest } +/// callback function for 'operatorfunc' +static Callback opfunc_cb; + +/// Process the 'operatorfunc' option value. +/// @return OK or FAIL +int set_operatorfunc_option(void) +{ + return option_set_callback_func((char_u *)p_opfunc, &opfunc_cb); +} + +#if defined(EXITFREE) +void free_operatorfunc_option(void) +{ + callback_free(&opfunc_cb); +} +#endif + /// Handle the "g@" operator: call 'operatorfunc'. static void op_function(const oparg_T *oap) FUNC_ATTR_NONNULL_ALL @@ -6177,7 +5676,10 @@ static void op_function(const oparg_T *oap) // Reset finish_op so that mode() returns the right value. finish_op = false; - (void)call_func_retnr((char *)p_opfunc, 1, argv); + typval_T rettv; + if (callback_call(&opfunc_cb, 1, argv, &rettv)) { + tv_clear(&rettv); + } virtual_op = save_virtual_op; finish_op = save_finish_op; @@ -6338,7 +5840,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // If 'cpoptions' does not contain 'r', insert the search // pattern to really repeat the same command. if (vim_strchr(p_cpo, CPO_REDO) == NULL) { - AppendToRedobuffLit((char *)cap->searchbuf, -1); + AppendToRedobuffLit(cap->searchbuf, -1); } AppendToRedobuff(NL_STR); } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { @@ -6348,7 +5850,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (repeat_cmdline == NULL) { ResetRedobuff(); } else { - AppendToRedobuffLit((char *)repeat_cmdline, -1); + AppendToRedobuffLit(repeat_cmdline, -1); AppendToRedobuff(NL_STR); XFREE_CLEAR(repeat_cmdline); } @@ -6581,7 +6083,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) && oap->motion_force == NUL) { // Make sure redrawing is correct. curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } } @@ -6613,7 +6115,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) || oap->op_type == OP_FOLD)) { curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // If the end of an operator is in column one while oap->motion_type diff --git a/src/nvim/option.c b/src/nvim/option.c index 4958c10220..9bc5409ff9 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -27,18 +27,18 @@ #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/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" -#include "nvim/eval/vars.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" @@ -49,9 +49,10 @@ #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" +#include "nvim/locale.h" #include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" @@ -62,16 +63,19 @@ #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" +#include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #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/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" @@ -82,11 +86,25 @@ #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" -#include "nvim/quickfix.h" + +static char e_unknown_option[] + = N_("E518: Unknown option"); +static char e_not_allowed_in_modeline[] + = N_("E520: Not allowed in a modeline"); +static char e_not_allowed_in_modeline_when_modelineexpr_is_off[] + = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); +static char e_key_code_not_set[] + = N_("E846: Key code not set"); +static char e_number_required_after_equal[] + = N_("E521: Number required after ="); +static char e_preview_window_already_exists[] + = N_("E590: A preview window already exists"); /* * The options that are local to a window or buffer have "indir" set to one of @@ -119,74 +137,6 @@ typedef enum { static char *p_term = NULL; static char *p_ttytype = NULL; -/* - * These are the global values for options which are also local to a buffer. - * Only to be used in option.c! - */ -static int p_ai; -static int p_bin; -static int p_bomb; -static char_u *p_bh; -static char_u *p_bt; -static int p_bl; -static long p_channel; -static int p_ci; -static int p_cin; -static char_u *p_cink; -static char_u *p_cino; -static char_u *p_cinw; -static char_u *p_cinsd; -static char_u *p_com; -static char_u *p_cms; -static char_u *p_cpt; -static char_u *p_cfu; -static char_u *p_ofu; -static char_u *p_tfu; -static char_u *p_umf; -static int p_eol; -static int p_fixeol; -static int p_et; -static char_u *p_fenc; -static char_u *p_ff; -static char_u *p_fo; -static char_u *p_flp; -static char_u *p_ft; -static long p_iminsert; -static long p_imsearch; -static char_u *p_inex; -static char_u *p_inde; -static char_u *p_indk; -static char_u *p_fex; -static int p_inf; -static char_u *p_isk; -static int p_lisp; -static int p_ml; -static int p_ma; -static int p_mod; -static char_u *p_mps; -static char_u *p_nf; -static int p_pi; -static char_u *p_qe; -static int p_ro; -static int p_si; -static long p_sts; -static char_u *p_sua; -static long p_sw; -static int p_swf; -static long p_smc; -static char_u *p_syn; -static char_u *p_spc; -static char_u *p_spf; -static char_u *p_spl; -static char_u *p_spo; -static long p_ts; -static long p_tw; -static int p_udf; -static long p_wm; -static char_u *p_vsts; -static char_u *p_vts; -static char_u *p_keymap; - // Saved values for when 'bin' is set. static int p_et_nobin; static int p_ml_nobin; @@ -199,7 +149,7 @@ static int p_et_nopaste; static long p_sts_nopaste; static long p_tw_nopaste; static long p_wm_nopaste; -static char_u *p_vsts_nopaste; +static char *p_vsts_nopaste; typedef struct vimoption { char *fullname; // full option name @@ -215,64 +165,6 @@ typedef struct vimoption { } vimoption_T; /* - * Flags - */ -#define P_BOOL 0x01U // the option is boolean -#define P_NUM 0x02U // the option is numeric -#define P_STRING 0x04U // the option is a string -#define P_ALLOCED 0x08U // the string option is in allocated memory, - // must use free_string_option() when - // assigning new value. Not set if default is - // the same. -#define P_EXPAND 0x10U // environment expansion. NOTE: P_EXPAND can - // never be used for local or hidden options -#define P_NODEFAULT 0x40U // don't set to default value -#define P_DEF_ALLOCED 0x80U // default value is in allocated memory, must - // use free() when assigning new value -#define P_WAS_SET 0x100U // option has been set/reset -#define P_NO_MKRC 0x200U // don't include in :mkvimrc output - -// when option changed, what to display: -#define P_RSTAT 0x1000U ///< redraw status lines -#define P_RWIN 0x2000U ///< redraw current window and recompute text -#define P_RBUF 0x4000U ///< redraw current buffer and recompute text -#define P_RALL 0x6000U ///< redraw all windows -#define P_RCLR 0x7000U ///< clear and redraw all - -#define P_COMMA 0x8000U ///< comma separated list -#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive - ///< commas -#define P_NODUP 0x20000U ///< don't allow duplicate strings -#define P_FLAGLIST 0x40000U ///< list of single-char flags - -#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode -#define P_GETTEXT 0x100000U ///< expand default value with _() -#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc -#define P_NFNAME 0x400000U ///< only normal file name chars allowed -#define P_INSECURE 0x800000U ///< option was set from a modeline -#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option - ///< has side effects) -#define P_NO_ML 0x2000000U ///< not allowed in modeline -#define P_CURSWANT 0x4000000U ///< update curswant required; not needed - ///< when there is a redraw flag -#define P_NO_DEF_EXP 0x8000000U ///< Do not expand default value. - -#define P_RWINONLY 0x10000000U ///< only redraw current window -#define P_NDNAME 0x20000000U ///< only normal dir name chars allowed -#define P_UI_OPTION 0x40000000U ///< send option to remote ui -#define P_MLE 0x80000000U ///< under control of 'modelineexpr' - -#define HIGHLIGHT_INIT \ - "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ - "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ - "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \ - "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ - "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ - "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ - "X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \ - "q:QuickFixLine,0:Whitespace,I:NormalNC" - -/* * options[] is initialized here. * The order of the options MUST be alphabetic for ":set all" and findoption(). * All option names MUST start with a lowercase letter (for findoption()). @@ -287,59 +179,6 @@ typedef struct vimoption { #define OPTION_COUNT ARRAY_SIZE(options) -static char *(p_ambw_values[]) = { "single", "double", NULL }; -static char *(p_bg_values[]) = { "light", "dark", NULL }; -static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", - "unsigned", NULL }; -static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL }; -static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; -static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", - "mac", NULL }; -static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL }; -static char *(p_slm_values[]) = { "mouse", "key", "cmd", NULL }; -static char *(p_km_values[]) = { "startsel", "stopsel", NULL }; -static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL }; -static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL }; -static char *(p_ead_values[]) = { "both", "ver", "hor", NULL }; -static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", - "help", "acwrite", "terminal", - "prompt", NULL }; - -static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", - "wipe", NULL }; -static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL }; -static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", - "syntax", "diff", NULL }; -static char *(p_fcl_values[]) = { "all", NULL }; -static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", - "noinsert", "noselect", NULL }; -#ifdef BACKSLASH_IN_FILENAME -static char *(p_csl_values[]) = { "slash", "backslash", NULL }; -#endif -static char *(p_icm_values[]) = { "nosplit", "split", NULL }; -static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", - "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", - "auto:9", - "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", - "yes:7", "yes:8", - "yes:9", "number", NULL }; -static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", - "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", - "auto:9", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; - -/// All possible flags for 'shm'. -static char_u SHM_ALL[] = { - SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, - SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, - SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, SHM_COMPLETIONMENU, - SHM_RECORDING, SHM_FILEINFO, SHM_SEARCHCOUNT, - 0, -}; - -static char e_unclosed_expression_sequence[] = N_("E540: Unclosed expression sequence"); -static char e_unbalanced_groups[] = N_("E542: unbalanced groups"); - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" #endif @@ -580,7 +419,7 @@ void set_init_1(bool clean_arg) // NOTE: mlterm's author is being asked to 'set' a variable // instead of an environment variable due to inheritance. if (os_env_exists("MLTERM")) { - set_option_value("tbidi", 1L, NULL, 0); + set_option_value_give_err("tbidi", 1L, NULL, 0); } didset_options2(); @@ -600,7 +439,7 @@ void set_init_1(bool clean_arg) #ifdef HAVE_WORKING_LIBINTL // GNU gettext 0.10.37 supports this feature: set the codeset used for // translated messages independently from the current locale. - (void)bind_textdomain_codeset(PROJECT_NAME, (char *)p_enc); + (void)bind_textdomain_codeset(PROJECT_NAME, p_enc); #endif // Set the default for 'helplang'. @@ -616,7 +455,7 @@ static void set_option_default(int opt_idx, int opt_flags) char_u *varp; // pointer to variable for current option int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - varp = get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); + varp = (char_u *)get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); uint32_t flags = options[opt_idx].flags; if (varp != NULL) { // skip hidden option, nothing to do for it if (flags & P_STRING) { @@ -627,7 +466,7 @@ static void set_option_default(int opt_idx, int opt_flags) (char *)options[opt_idx].def_val, opt_flags, 0); } else { if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) { - free_string_option(*(char_u **)(varp)); + free_string_option(*(char **)(varp)); } *(char_u **)varp = options[opt_idx].def_val; options[opt_idx].flags &= ~P_ALLOCED; @@ -766,16 +605,17 @@ void free_all_options(void) if (options[i].indir == PV_NONE) { // global option: free value and default value. if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { - free_string_option(*(char_u **)options[i].var); + free_string_option(*(char **)options[i].var); } if (options[i].flags & P_DEF_ALLOCED) { - free_string_option(options[i].def_val); + free_string_option((char *)options[i].def_val); } } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) { // buffer-local option: free global value - clear_string_option((char_u **)options[i].var); + clear_string_option((char **)options[i].var); } } + free_operatorfunc_option(); } #endif @@ -835,12 +675,12 @@ void set_init_3(void) if (FNAMECMP(p, "csh") == 0 || FNAMECMP(p, "tcsh") == 0) { if (do_sp) { - p_sp = (char_u *)"|& tee"; - options[idx_sp].def_val = p_sp; + p_sp = "|& tee"; + options[idx_sp].def_val = (char_u *)p_sp; } if (do_srr) { - p_srr = (char_u *)">&"; - options[idx_srr].def_val = p_srr; + p_srr = ">&"; + options[idx_srr].def_val = (char_u *)p_srr; } } else if (FNAMECMP(p, "sh") == 0 || FNAMECMP(p, "ksh") == 0 @@ -854,12 +694,12 @@ void set_init_3(void) || FNAMECMP(p, "dash") == 0) { // Always use POSIX shell style redirection if we reach this if (do_sp) { - p_sp = (char_u *)"2>&1| tee"; - options[idx_sp].def_val = p_sp; + p_sp = "2>&1| tee"; + options[idx_sp].def_val = (char_u *)p_sp; } if (do_srr) { - p_srr = (char_u *)">%s 2>&1"; - options[idx_srr].def_val = p_srr; + p_srr = ">%s 2>&1"; + options[idx_srr].def_val = (char_u *)p_srr; } } xfree(p); @@ -892,7 +732,7 @@ void set_helplang_default(const char *lang) int idx = findoption("hlg"); if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { if (options[idx].flags & P_ALLOCED) { - free_string_option(p_hlg); + free_string_option((char *)p_hlg); } p_hlg = (char_u *)xmemdupz(lang, lang_len); // zh_CN becomes "cn", zh_TW becomes "tw". @@ -970,7 +810,7 @@ int do_set(char *arg, int opt_flags) int opt_idx; char *errmsg; char errbuf[80]; - char_u *startarg; + char *startarg; int prefix; // 1: nothing, 0: "no", 2: "inv" in front of name char_u nextchar; // next non-white char after option name int afterchar; // character just after option name @@ -979,7 +819,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" @@ -993,7 +833,7 @@ int do_set(char *arg, int opt_flags) while (*arg != NUL) { // loop to process all options errmsg = NULL; - startarg = (char_u *)arg; // remember for error message + startarg = arg; // remember for error message if (STRNCMP(arg, "all", 3) == 0 && !isalpha(arg[3]) && !(opt_flags & OPT_MODELINE)) { @@ -1009,7 +849,7 @@ int do_set(char *arg, int opt_flags) didset_options(); didset_options2(); ui_refresh_options(); - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } else { showoptions(1, opt_flags); did_show = true; @@ -1090,7 +930,7 @@ int do_set(char *arg, int opt_flags) nextchar = (uint8_t)arg[len]; if (opt_idx == -1 && key == 0) { // found a mismatch: skip - errmsg = N_("E518: Unknown option"); + errmsg = e_unknown_option; goto skip; } @@ -1101,7 +941,7 @@ int do_set(char *arg, int opt_flags) if (vim_strchr("=:!&<", nextchar) == NULL && (!(options[opt_idx].flags & P_BOOL) || nextchar == '?')) { - errmsg = _(e_unsupportedoption); + errmsg = e_unsupportedoption; } goto skip; } @@ -1128,11 +968,11 @@ int do_set(char *arg, int opt_flags) // Disallow changing some options from modelines. if (opt_flags & OPT_MODELINE) { if (flags & (P_SECURE | P_NO_ML)) { - errmsg = N_("E520: Not allowed in a modeline"); + errmsg = e_not_allowed_in_modeline; goto skip; } if ((flags & P_MLE) && !p_mle) { - errmsg = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); + errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off; goto skip; } // In diff mode some options are overruled. This avoids that @@ -1189,7 +1029,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[ @@ -1200,7 +1040,7 @@ int do_set(char *arg, int opt_flags) } } } else { - errmsg = N_("E846: Key code not set"); + errmsg = e_key_code_not_set; goto skip; } if (nextchar != '?' @@ -1251,8 +1091,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) { @@ -1294,11 +1133,11 @@ int do_set(char *arg, int opt_flags) // Allow negative, octal and hex numbers. vim_str2nr((char_u *)arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true); if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) { - errmsg = N_("E521: Number required after ="); + errmsg = e_number_required_after_equal; goto skip; } } else { - errmsg = N_("E521: Number required after ="); + errmsg = e_number_required_after_equal; goto skip; } @@ -1311,7 +1150,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. @@ -1334,7 +1173,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 @@ -1348,7 +1187,7 @@ int do_set(char *arg, int opt_flags) // A global-local string option might have an empty // option as value to indicate that the global // value should be used. - if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == empty_option) { + if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == (char_u *)empty_option) { origval_l = origval_g; } } @@ -1368,7 +1207,7 @@ int do_set(char *arg, int opt_flags) // required when an environment variable was set // later if (newval == NULL) { - newval = empty_option; + newval = (char_u *)empty_option; } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { s = option_expand(opt_idx, newval); if (s == NULL) { @@ -1383,27 +1222,20 @@ 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: - *(char_u **)varp = empty_option; + *(char **)varp = empty_option; break; case 1: *(char_u **)varp = vim_strsave((char_u *)"indent,eol"); @@ -1426,14 +1258,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) { @@ -1453,14 +1281,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++; } @@ -1658,7 +1483,7 @@ int do_set(char *arg, int opt_flags) // for ":set" on local options. Note: when setting // 'syntax' or 'filetype' autocommands may be // triggered that can cause havoc. - errmsg = did_set_string_option(opt_idx, (char_u **)varp, oldval, + errmsg = did_set_string_option(opt_idx, (char **)varp, (char *)oldval, errbuf, sizeof(errbuf), opt_flags, &value_checked); @@ -1718,17 +1543,17 @@ skip: if (errmsg != NULL) { STRLCPY(IObuff, _(errmsg), IOSIZE); i = (int)STRLEN(IObuff) + 2; - if (i + ((char_u *)arg - startarg) < IOSIZE) { + if (i + (arg - startarg) < IOSIZE) { // append the argument with the error STRCAT(IObuff, ": "); - assert((char_u *)arg >= startarg); - memmove(IObuff + i, startarg, (size_t)((char_u *)arg - startarg)); - IObuff[i + ((char_u *)arg - startarg)] = NUL; + assert(arg >= startarg); + memmove(IObuff + i, startarg, (size_t)(arg - startarg)); + IObuff[i + (arg - startarg)] = NUL; } // make sure all characters are printable trans_characters((char *)IObuff, IOSIZE); - no_wait_return++; // wait_return done later + no_wait_return++; // wait_return() done later emsg((char *)IObuff); // show error highlighted no_wait_return--; @@ -1758,7 +1583,7 @@ theend: /// @param opt_flags possibly with OPT_MODELINE /// @param new_value value was replaced completely /// @param value_checked value was checked to be safe, no need to set P_INSECURE -static void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked) +void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked) { options[opt_idx].flags |= P_WAS_SET; @@ -1775,19 +1600,9 @@ static void did_set_option(int opt_idx, int opt_flags, int new_value, int value_ } } -static char *illegal_char(char *errbuf, size_t errbuflen, int c) -{ - if (errbuf == NULL) { - return ""; - } - vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), - (char *)transchar(c)); - return errbuf; -} - /// Convert a key name or string into a key value. /// Used for 'wildchar' and 'cedit' options. -static int string_to_key(char_u *arg) +int string_to_key(char_u *arg) { if (*arg == '<') { return find_key_option(arg + 1, true); @@ -1798,29 +1613,11 @@ static int string_to_key(char_u *arg) return *arg; } -/// Check value of 'cedit' and set cedit_key. -/// Returns NULL if value is OK, error message otherwise. -static char *check_cedit(void) -{ - int n; - - if (*p_cedit == NUL) { - cedit_key = -1; - } else { - n = string_to_key(p_cedit); - if (vim_isprintc(n)) { - return e_invarg; - } - cedit_key = n; - } - return NULL; -} - // When changing 'title', 'titlestring', 'icon' or 'iconstring', call // maketitle() to create and display it. // When switching the title or icon off, call ui_set_{icon,title}(NULL) to get // the old value back. -static void did_set_title(void) +void did_set_title(void) { if (starting != NO_SCREEN) { maketitle(); @@ -1902,16 +1699,14 @@ int get_shada_parameter(int type) /// Return NULL if the parameter is not specified in the string. char_u *find_shada_parameter(int type) { - char_u *p; - - for (p = p_shada; *p; p++) { + for (char *p = p_shada; *p; p++) { if (*p == type) { - return p + 1; + return (char_u *)p + 1; } if (*p == 'n') { // 'n' is always the last one break; } - p = (char_u *)vim_strchr((char *)p, ','); // skip until next ',' + p = vim_strchr(p, ','); // skip until next ',' if (p == NULL) { // hit the end without finding parameter break; } @@ -1946,35 +1741,15 @@ static char_u *option_expand(int opt_idx, char_u *val) * names. * For 'spellsuggest' expand after "file:". */ - expand_env_esc(val, NameBuff, MAXPATHL, + expand_env_esc(val, (char_u *)NameBuff, MAXPATHL, (char_u **)options[opt_idx].var == &p_tags, false, - (char_u **)options[opt_idx].var == &p_sps ? (char_u *)"file:" : + (char_u **)options[opt_idx].var == (char_u **)&p_sps ? (char_u *)"file:" : NULL); if (STRCMP(NameBuff, val) == 0) { // they are the same return NULL; } - return NameBuff; -} - -/// After setting various option values: recompute variables that depend on -/// option values. -static void didset_string_options(void) -{ - (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); - (void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true); - (void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, true); - (void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true); - (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); - (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); - (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); - (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); - (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); - (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); - (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); - (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); - (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); - (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); + return (char_u *)NameBuff; } /// After setting various option values: recompute variables that depend on @@ -1994,7 +1769,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. @@ -2003,9 +1778,6 @@ static void didset_options2(void) // Initialize the highlight_attr[] table. highlight_changed(); - // Parse default for 'clipboard'. - (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); - // Parse default for 'fillchars'. (void)set_chars_option(curwin, &curwin->w_p_fcs, true); @@ -2027,96 +1799,11 @@ void check_options(void) for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) { if ((options[opt_idx].flags & P_STRING) && options[opt_idx].var != NULL) { - check_string_option((char_u **)get_varp(&(options[opt_idx]))); + check_string_option((char **)get_varp(&(options[opt_idx]))); } } } -/// Check string options in a buffer for NULL value. -void check_buf_options(buf_T *buf) -{ - check_string_option(&buf->b_p_bh); - check_string_option(&buf->b_p_bt); - check_string_option(&buf->b_p_fenc); - check_string_option(&buf->b_p_ff); - check_string_option(&buf->b_p_def); - check_string_option(&buf->b_p_inc); - check_string_option(&buf->b_p_inex); - check_string_option(&buf->b_p_inde); - check_string_option(&buf->b_p_indk); - check_string_option(&buf->b_p_fp); - check_string_option(&buf->b_p_fex); - check_string_option(&buf->b_p_kp); - check_string_option(&buf->b_p_mps); - check_string_option(&buf->b_p_fo); - check_string_option(&buf->b_p_flp); - check_string_option(&buf->b_p_isk); - check_string_option(&buf->b_p_com); - check_string_option(&buf->b_p_cms); - check_string_option(&buf->b_p_nf); - check_string_option(&buf->b_p_qe); - check_string_option(&buf->b_p_syn); - check_string_option(&buf->b_s.b_syn_isk); - check_string_option(&buf->b_s.b_p_spc); - check_string_option(&buf->b_s.b_p_spf); - check_string_option(&buf->b_s.b_p_spl); - check_string_option(&buf->b_s.b_p_spo); - check_string_option(&buf->b_p_sua); - check_string_option(&buf->b_p_cink); - check_string_option(&buf->b_p_cino); - parse_cino(buf); - check_string_option(&buf->b_p_ft); - check_string_option(&buf->b_p_cinw); - check_string_option(&buf->b_p_cinsd); - 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_keymap); - check_string_option(&buf->b_p_gp); - check_string_option(&buf->b_p_mp); - check_string_option(&buf->b_p_efm); - check_string_option(&buf->b_p_ep); - check_string_option(&buf->b_p_path); - check_string_option(&buf->b_p_tags); - check_string_option(&buf->b_p_tfu); - check_string_option(&buf->b_p_tc); - check_string_option(&buf->b_p_dict); - check_string_option(&buf->b_p_tsr); - check_string_option(&buf->b_p_tsrfu); - check_string_option(&buf->b_p_lw); - check_string_option(&buf->b_p_bkc); - check_string_option(&buf->b_p_menc); - check_string_option(&buf->b_p_vsts); - check_string_option(&buf->b_p_vts); -} - -/// Free the string allocated for an option. -/// Checks for the string being empty_option. This may happen if we're out of -/// memory, vim_strsave() returned NULL, which was replaced by empty_option by -/// check_options(). -/// Does NOT check for P_ALLOCED flag! -void free_string_option(char_u *p) -{ - if (p != empty_option) { - xfree(p); - } -} - -void clear_string_option(char_u **pp) -{ - if (*pp != empty_option) { - xfree(*pp); - } - *pp = empty_option; -} - -static void check_string_option(char_u **pp) -{ - if (*pp == NULL) { - *pp = empty_option; - } -} - /// Return true when option "opt" was set from a modeline or in secure mode. /// Return false when it wasn't. /// Return -1 for an unknown option. @@ -2163,172 +1850,18 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) } /// Redraw the window title and/or tab page text later. -static void redraw_titles(void) +void redraw_titles(void) { need_maketitle = true; redraw_tabline = true; } -static int shada_idx = -1; - -/// Set a string option to a new value (without checking the effect). -/// The string is copied into allocated memory. -/// if ("opt_idx" == -1) "name" is used, otherwise "opt_idx" is used. -/// When "set_sid" is zero set the scriptID to current_sctx.sc_sid. When -/// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to -/// "set_sid". -/// -/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL -void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags, - int set_sid) -{ - char *s; - char **varp; - int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - int idx = opt_idx; - - if (idx == -1) { // Use name. - idx = findoption(name); - if (idx < 0) { // Not found (should not happen). - internal_error("set_string_option_direct()"); - siemsg(_("For option %s"), name); - return; - } - } - - if (options[idx].var == NULL) { // can't set hidden option - return; - } - - assert((void *)options[idx].var != (void *)&p_shada); - - s = xstrdup(val); - { - varp = (char **)get_varp_scope(&(options[idx]), both ? OPT_LOCAL : opt_flags); - if ((opt_flags & OPT_FREE) && (options[idx].flags & P_ALLOCED)) { - free_string_option((char_u *)(*varp)); - } - *varp = s; - - // For buffer/window local option may also set the global value. - if (both) { - set_string_option_global(idx, (char_u **)varp); - } - - options[idx].flags |= P_ALLOCED; - - /* When setting both values of a global option with a local value, - * make the local value empty, so that the global value is used. */ - if (((int)options[idx].indir & PV_BOTH) && both) { - free_string_option((char_u *)(*varp)); - *varp = (char *)empty_option; - } - if (set_sid != SID_NONE) { - sctx_T script_ctx; - - if (set_sid == 0) { - script_ctx = current_sctx; - } else { - script_ctx.sc_sid = set_sid; - script_ctx.sc_seq = 0; - script_ctx.sc_lnum = 0; - } - set_option_sctx_idx(idx, opt_flags, script_ctx); - } - } -} - -/// Set global value for string option when it's a local option. -/// -/// @param opt_idx option index -/// @param varp pointer to option variable -static void set_string_option_global(int opt_idx, char_u **varp) -{ - char_u **p, *s; - - // the global value is always allocated - if (options[opt_idx].var == VAR_WIN) { - p = (char_u **)GLOBAL_WO(varp); - } else { - p = (char_u **)options[opt_idx].var; - } - if (options[opt_idx].indir != PV_NONE && p != varp) { - s = vim_strsave(*varp); - free_string_option(*p); - *p = s; - } -} - -/// Set a string option to a new value, handling the effects -/// -/// @param[in] opt_idx Option to set. -/// @param[in] value New value. -/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or -/// #OPT_GLOBAL. -/// -/// @return NULL on success, error message on error. -static char *set_string_option(const int opt_idx, const char *const value, const int opt_flags) - FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (options[opt_idx].var == NULL) { // don't set hidden option - return NULL; - } - - char *const s = xstrdup(value); - char **const varp = (char **)get_varp_scope(&(options[opt_idx]), - ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - ? (((int)options[opt_idx].indir & PV_BOTH) - ? OPT_GLOBAL : OPT_LOCAL) - : opt_flags)); - char *const oldval = *varp; - char *oldval_l = NULL; - char *oldval_g = NULL; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - oldval_l = *(char **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); - oldval_g = *(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - - *varp = s; - - char *const saved_oldval = xstrdup(oldval); - char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; - char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; - char *const saved_newval = xstrdup(s); - - int value_checked = false; - char *const r = did_set_string_option(opt_idx, (char_u **)varp, (char_u *)oldval, - NULL, 0, - opt_flags, &value_checked); - if (r == NULL) { - did_set_option(opt_idx, opt_flags, true, value_checked); - } - - // call autocommand after handling side effects - if (r == NULL) { - if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, - saved_newval); - } - if (options[opt_idx].flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - STRING_OBJ(cstr_as_string(saved_newval))); - } - } - xfree(saved_oldval); - xfree(saved_oldval_l); - xfree(saved_oldval_g); - xfree(saved_newval); - - return r; -} - /// 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 *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++) { + for (const char_u *s = (char_u *)val; *s != NUL; s++) { if (!ASCII_ISALNUM(*s) && vim_strchr(allowed, *s) == NULL) { return false; @@ -2337,1632 +1870,36 @@ static bool valid_name(const char_u *val, const char *allowed) return true; } -/// Return true if "val" is a valid 'filetype' name. -/// Also used for 'syntax' and 'keymap'. -static bool valid_filetype(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 '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) -{ - long vertical = -1; - long horizontal = -1; - - for (;;) { - char *end = vim_strchr(string, ','); - size_t length = end ? (size_t)(end - string) : STRLEN(string); - - // Both "ver:" and "hor:" are 4 bytes long. - // They should be followed by at least one digit. - if (length <= 4) { - return e_invarg; - } - - long *direction; - - if (memcmp(string, "ver:", 4) == 0) { - direction = &vertical; - } else if (memcmp(string, "hor:", 4) == 0) { - direction = &horizontal; - } else { - return e_invarg; - } - - // If the direction has already been set, this is a duplicate. - if (*direction != -1) { - return e_invarg; - } - - // Verify that only digits follow the colon. - for (size_t i = 4; i < length; i++) { - if (!ascii_isdigit(string[i])) { - return N_("E548: digit expected"); - } - } - - string += 4; - *direction = getdigits_int(&string, false, -1); - - // Num options are generally kept within the signed int range. - // We know this number won't be negative because we've already checked for - // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. - if (*direction == -1) { - return e_invarg; - } - - if (!end) { - break; - } - - string = end + 1; - } - - // If a direction wasn't set, fallback to the default value. - p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; - p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; - - return NULL; -} - -/// Handle string options that need some action to perform when changed. -/// The new value must be allocated. -/// Returns NULL for success, or an error message for an error. -/// -/// @param opt_idx index in options[] table -/// @param varp pointer to the option variable -/// @param oldval previous value of the option -/// @param errbuf buffer for errors, or NULL -/// @param errbuflen length of errors buffer -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL -/// @param value_checked value was checked to be safe, no need to set P_INSECURE -static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char *errbuf, - size_t errbuflen, int opt_flags, int *value_checked) -{ - char *errmsg = NULL; - char_u *s, *p; - int did_chartab = false; - char_u **gvarp; - bool free_oldval = (options[opt_idx].flags & P_ALLOCED); - bool value_changed = false; - - /* Get the global option to compare with, otherwise we would have to check - * two values for all local options. */ - gvarp = (char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - - // Disallow changing some options from secure mode - if ((secure || sandbox != 0) - && (options[opt_idx].flags & P_SECURE)) { - errmsg = e_secure; - } else if (((options[opt_idx].flags & P_NFNAME) - && strpbrk((char *)(*varp), - (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) - || ((options[opt_idx].flags & P_NDNAME) - && strpbrk((char *)(*varp), "*?[|;&<>\r\n") != NULL)) { - // Check for a "normal" directory or file name in some options. Disallow a - // path separator (slash and/or backslash), wildcards and characters that - // are often illegal in a file name. Be more permissive if "secure" is off. - errmsg = e_invarg; - } else if (gvarp == &p_bkc) { // 'backupcopy' - char_u *bkc = p_bkc; - unsigned int *flags = &bkc_flags; - - if (opt_flags & OPT_LOCAL) { - bkc = curbuf->b_p_bkc; - flags = &curbuf->b_bkc_flags; - } - - if ((opt_flags & OPT_LOCAL) && *bkc == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else { - if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { - errmsg = e_invarg; - } - - if (((*flags & BKC_AUTO) != 0) - + ((*flags & BKC_YES) != 0) - + ((*flags & BKC_NO) != 0) != 1) { - // Must have exactly one of "auto", "yes" and "no". - (void)opt_strings_flags(oldval, p_bkc_values, flags, true); - errmsg = e_invarg; - } - } - } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode' - if (STRCMP(*p_bex == '.' ? p_bex + 1 : p_bex, - *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { - errmsg = N_("E589: 'backupext' and 'patchmode' are equal"); - } - } else if (varp == &curwin->w_p_briopt) { // 'breakindentopt' - if (briopt_check(curwin) == FAIL) { - errmsg = e_invarg; - } - } else if (varp == &p_isi - || varp == &(curbuf->b_p_isk) - || varp == &p_isp - || varp == &p_isf) { - // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] - // If the new option is invalid, use old value. 'lisp' option: refill - // g_chartab[] for '-' char - if (init_chartab() == FAIL) { - did_chartab = true; // need to restore it below - errmsg = e_invarg; // error in value - } - } else if (varp == &p_hf) { // 'helpfile' - // May compute new values for $VIM and $VIMRUNTIME - if (didset_vim) { - os_setenv("VIM", "", 1); - didset_vim = false; - } - if (didset_vimruntime) { - os_setenv("VIMRUNTIME", "", 1); - didset_vimruntime = false; - } - } else if (varp == &p_rtp || varp == &p_pp) { // 'runtimepath' 'packpath' - runtime_search_path_invalidate(); - } else if (varp == &curwin->w_p_culopt - || gvarp == &curwin->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' - if (**varp == NUL || fill_culopt_flags(*varp, curwin) != OK) { - errmsg = e_invarg; - } - } else if (varp == &curwin->w_p_cc) { // 'colorcolumn' - errmsg = check_colorcolumn(curwin); - } else if (varp == &p_hlg) { // 'helplang' - // Check for "", "ab", "ab,cd", etc. - for (s = p_hlg; *s != NUL; s += 3) { - if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { - errmsg = e_invarg; - break; - } - if (s[2] == NUL) { - break; - } - } - } else if (varp == &p_hl) { - // 'highlight' - if (STRCMP(*varp, HIGHLIGHT_INIT) != 0) { - errmsg = e_unsupportedoption; - } - } else if (varp == &p_jop) { // 'jumpoptions' - if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_nf) { // 'nrformats' - if (check_opt_strings(*varp, p_nf_values, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_ssop) { // 'sessionoptions' - if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { - errmsg = e_invarg; - } - if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { - // Don't allow both "sesdir" and "curdir". - (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); - errmsg = e_invarg; - } - } else if (varp == &p_vop) { // 'viewoptions' - if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_rdb) { // 'redrawdebug' - if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == (char_u **)&p_sbo) { // 'scrollopt' - if (check_opt_strings((char_u *)p_sbo, p_scbopt_values, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_ambw || (int *)varp == &p_emoji) { - // 'ambiwidth' - 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 - } - } else if (varp == &p_bg) { // 'background' - if (check_opt_strings(p_bg, p_bg_values, false) == OK) { - int dark = (*p_bg == 'd'); - - init_highlight(false, false); - - if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { - // The color scheme must have set 'background' back to another - // value, that's not what we want here. Disable the color - // scheme and set the colors again. - do_unlet(S_LEN("g:colors_name"), true); - free_string_option(p_bg); - p_bg = vim_strsave((char_u *)(dark ? "dark" : "light")); - check_string_option(&p_bg); - init_highlight(false, false); - } - } else { - errmsg = e_invarg; - } - } else if (varp == &p_wim) { // 'wildmode' - if (check_opt_wim() == FAIL) { - errmsg = e_invarg; - } - // 'wildoptions' - } else if (varp == &p_wop) { - if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_wak) { // 'winaltkeys' - if (*p_wak == NUL - || check_opt_strings(p_wak, p_wak_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_ei) { // 'eventignore' - if (check_ei() == FAIL) { - errmsg = e_invarg; - } - // 'encoding', 'fileencoding' and 'makeencoding' - } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { - if (gvarp == &p_fenc) { - if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { - errmsg = e_modifiable; - } else if (vim_strchr((char *)(*varp), ',') != NULL) { - // No comma allowed in 'fileencoding'; catches confusing it - // with 'fileencodings'. - errmsg = e_invarg; - } else { - // May show a "+" in the title now. - redraw_titles(); - // Add 'fileencoding' to the swap file. - ml_setflags(curbuf); - } - } - - if (errmsg == NULL) { - // canonize the value, so that STRCMP() can be used on it - p = enc_canonize(*varp); - xfree(*varp); - *varp = p; - if (varp == &p_enc) { - // only encoding=utf-8 allowed - if (STRCMP(p_enc, "utf-8") != 0) { - errmsg = e_unsupportedoption; - } else { - spell_reload(); - } - } - } - } else if (varp == &p_penc) { - // Canonize printencoding if VIM standard one - p = enc_canonize(p_penc); - xfree(p_penc); - p_penc = p; - } else if (varp == &curbuf->b_p_keymap) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - int secure_save = secure; - - // Reset the secure flag, since the value of 'keymap' has - // been checked to be safe. - secure = 0; - - // load or unload key mapping tables - errmsg = keymap_init(); - - secure = secure_save; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - - if (errmsg == NULL) { - if (*curbuf->b_p_keymap != NUL) { - // Installed a new keymap, switch on using it. - curbuf->b_p_iminsert = B_IMODE_LMAP; - if (curbuf->b_p_imsearch != B_IMODE_USE_INSERT) { - curbuf->b_p_imsearch = B_IMODE_LMAP; - } - } else { - // Cleared the keymap, may reset 'iminsert' and 'imsearch'. - if (curbuf->b_p_iminsert == B_IMODE_LMAP) { - curbuf->b_p_iminsert = B_IMODE_NONE; - } - if (curbuf->b_p_imsearch == B_IMODE_LMAP) { - curbuf->b_p_imsearch = B_IMODE_USE_INSERT; - } - } - if ((opt_flags & OPT_LOCAL) == 0) { - set_iminsert_global(); - set_imsearch_global(); - } - status_redraw_curbuf(); - } - } else if (gvarp == &p_ff) { // 'fileformat' - if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL)) { - errmsg = e_modifiable; - } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { - errmsg = e_invarg; - } else { - redraw_titles(); - // update flag in swap file - ml_setflags(curbuf); - /* Redraw needed when switching to/from "mac": a CR in the text - * will be displayed differently. */ - if (get_fileformat(curbuf) == EOL_MAC || *oldval == 'm') { - redraw_curbuf_later(NOT_VALID); - } - } - } else if (varp == (char_u **)&p_ffs) { // 'fileformats' - if (check_opt_strings((char_u *)p_ffs, p_ff_values, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_mps) { // 'matchpairs' - for (p = *varp; *p != NUL; p++) { - int x2 = -1; - int x3 = -1; - - p += utfc_ptr2len((char *)p); - if (*p != NUL) { - x2 = *p++; - } - if (*p != NUL) { - x3 = utf_ptr2char((char *)p); - p += utfc_ptr2len((char *)p); - } - if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { - errmsg = e_invarg; - break; - } - if (*p == NUL) { - break; - } - } - } else if (gvarp == &p_com) { // 'comments' - for (s = *varp; *s;) { - while (*s && *s != ':') { - if (vim_strchr(COM_ALL, *s) == NULL - && !ascii_isdigit(*s) && *s != '-') { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - s++; - } - if (*s++ == NUL) { - errmsg = N_("E524: Missing colon"); - } else if (*s == ',' || *s == NUL) { - errmsg = N_("E525: Zero length string"); - } - if (errmsg != NULL) { - break; - } - while (*s && *s != ',') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - s = skip_to_option_part(s); - } - } else if (varp == &p_lcs) { // global 'listchars' - errmsg = set_chars_option(curwin, varp, false); - if (errmsg == NULL) { - // The current window is set to use the global 'listchars' value. - // So clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(&curwin->w_p_lcs); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - (void)set_chars_option(wp, &wp->w_p_lcs, true); - } - redraw_all_later(NOT_VALID); - } - } else if (varp == &curwin->w_p_lcs) { // local 'listchars' - errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_fcs) { // global 'fillchars' - errmsg = set_chars_option(curwin, varp, false); - if (errmsg == NULL) { - // The current window is set to use the global 'fillchars' value. - // So clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(&curwin->w_p_fcs); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - (void)set_chars_option(wp, &wp->w_p_fcs, true); - } - redraw_all_later(NOT_VALID); - } - } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' - errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_cedit) { // 'cedit' - errmsg = check_cedit(); - } else if (varp == &p_vfile) { // 'verbosefile' - verbose_stop(); - if (*p_vfile != NUL && verbose_open() == FAIL) { - errmsg = e_invarg; - } - // 'shada' - } else if (varp == &p_shada) { - // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo - // option. - opt_idx = ((options[opt_idx].fullname[0] == 'v') - ? (shada_idx == -1 - ? ((shada_idx = findoption("shada"))) - : shada_idx) - : opt_idx); - // Update free_oldval now that we have the opt_idx for 'shada', otherwise - // 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;) { - // Check it's a valid character - if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - if (*s == 'n') { // name is always last one - break; - } else if (*s == 'r') { // skip until next ',' - while (*++s && *s != ',') {} - } else if (*s == '%') { - // optional number - while (ascii_isdigit(*++s)) {} - } else if (*s == '!' || *s == 'h' || *s == 'c') { - s++; // no extra chars - } else { // must have a number - while (ascii_isdigit(*++s)) {} - - if (!ascii_isdigit(*(s - 1))) { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E526: Missing number after <%s>"), - transchar_byte(*(s - 1))); - errmsg = errbuf; - } else { - errmsg = ""; - } - break; - } - } - if (*s == ',') { - s++; - } else if (*s) { - if (errbuf != NULL) { - errmsg = N_("E527: Missing comma"); - } else { - errmsg = ""; - } - break; - } - } - if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) { - errmsg = N_("E528: Must specify a ' value"); - } - } else if (gvarp == &p_sbr) { // 'showbreak' - for (s = *varp; *s;) { - if (ptr2cells((char *)s) != 1) { - errmsg = N_("E595: 'showbreak' contains unprintable or wide character"); - } - MB_PTR_ADV(s); - } - } else if (varp == &p_guicursor) { // 'guicursor' - errmsg = parse_shape_opt(SHAPE_CURSOR); - } else if (varp == &p_popt) { - errmsg = parse_printoptions(); - } else if (varp == &p_pmfn) { - errmsg = parse_printmbfont(); - } else if (varp == &p_langmap) { // 'langmap' - langmap_set(); - } else if (varp == &p_breakat) { // 'breakat' - fill_breakat_flags(); - } else if (varp == &p_titlestring || varp == &p_iconstring) { - // 'titlestring' and 'iconstring' - int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; - - // NULL => statusline syntax - if (vim_strchr((char *)(*varp), '%') && check_stl_option((char *)(*varp)) == NULL) { - stl_syntax |= flagval; - } else { - stl_syntax &= ~flagval; - } - did_set_title(); - } else if (varp == &p_sel) { // 'selection' - if (*p_sel == NUL - || check_opt_strings(p_sel, p_sel_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_slm) { // 'selectmode' - if (check_opt_strings(p_slm, p_slm_values, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_km) { // 'keymodel' - if (check_opt_strings(p_km, p_km_values, true) != OK) { - errmsg = e_invarg; - } else { - km_stopsel = (vim_strchr((char *)p_km, 'o') != NULL); - km_startsel = (vim_strchr((char *)p_km, 'a') != NULL); - } - } else if (varp == &p_mousem) { // 'mousemodel' - if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_mousescroll) { // 'mousescroll' - errmsg = check_mousescroll((char *)p_mousescroll); - } else if (varp == &p_swb) { // 'switchbuf' - if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_debug) { // 'debug' - if (check_opt_strings(p_debug, p_debug_values, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_dy) { // 'display' - if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { - errmsg = e_invarg; - } else { - (void)init_chartab(); - msg_grid_validate(); - } - } else if (varp == &p_ead) { // 'eadirection' - if (check_opt_strings(p_ead, p_ead_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_cb) { // 'clipboard' - if (opt_strings_flags(p_cb, p_cb_values, &cb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &(curwin->w_s->b_p_spl) // 'spell' - || varp == &(curwin->w_s->b_p_spf)) { - // When 'spelllang' or 'spellfile' is set and there is a window for this - // buffer in which 'spell' is set load the wordlists. - const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); - - if ((is_spellfile && !valid_spellfile(*varp)) - || (!is_spellfile && !valid_spelllang(*varp))) { - errmsg = e_invarg; - } else { - errmsg = did_set_spell_option(is_spellfile); - } - } else if (varp == &(curwin->w_s->b_p_spc)) { - // When 'spellcapcheck' is set compile the regexp program. - errmsg = compile_cap_prog(curwin->w_s); - } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions' - if (**varp != NUL && STRCMP("camel", *varp) != 0) { - errmsg = e_invarg; - } - } else if (varp == &p_sps) { // 'spellsuggest' - if (spell_check_sps() != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_msm) { // 'mkspellmem' - if (spell_check_msm() != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_bh) { - // When 'bufhidden' is set, check for valid value. - if (check_opt_strings(curbuf->b_p_bh, p_bufhidden_values, false) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_bt) { - // When 'buftype' is set, check for valid value. - if ((curbuf->terminal && curbuf->b_p_bt[0] != 't') - || (!curbuf->terminal && curbuf->b_p_bt[0] == 't') - || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { - errmsg = e_invarg; - } else { - if (curwin->w_status_height || global_stl_height()) { - curwin->w_redr_status = true; - redraw_later(curwin, VALID); - } - curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); - redraw_titles(); - } - } else if (gvarp == &p_stl || gvarp == (char_u **)&p_wbr || varp == &p_tal || varp == &p_ruf) { - // 'statusline', 'winbar', 'tabline' or 'rulerformat' - int wid; - - if (varp == &p_ruf) { // reset ru_wid first - ru_wid = 0; - } - s = *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); - if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) { - ru_wid = wid; - } else { - errmsg = check_stl_option((char *)p_ruf); - } - } 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); - } - if (varp == &p_ruf && errmsg == NULL) { - comp_col(); - } - // add / remove window bars for 'winbar' - if (gvarp == (char_u **)&p_wbr) { - set_winbar(true); - } - } else if (gvarp == &p_cpt) { - // check if it is a valid value for 'complete' -- Acevedo - for (s = *varp; *s;) { - while (*s == ',' || *s == ' ') { - s++; - } - if (!*s) { - break; - } - if (vim_strchr(".wbuksid]tU", *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - if (*++s != NUL && *s != ',' && *s != ' ') { - if (s[-1] == 'k' || s[-1] == 's') { - // skip optional filename after 'k' and 's' - while (*s && *s != ',' && *s != ' ') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - } else { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E535: Illegal character after <%c>"), - *--s); - errmsg = errbuf; - } else { - errmsg = ""; - } - break; - } - } - } - } else if (varp == &p_cot) { // 'completeopt' - if (check_opt_strings(p_cot, p_cot_values, true) != OK) { - errmsg = e_invarg; - } else { - completeopt_was_set(); - } -#ifdef BACKSLASH_IN_FILENAME - } else if (gvarp == &p_csl) { // 'completeslash' - if (check_opt_strings(p_csl, p_csl_values, false) != OK - || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) { - errmsg = e_invarg; - } -#endif - } else if (varp == &curwin->w_p_scl) { - // 'signcolumn' - if (check_signcolumn(*varp) != OK) { - errmsg = e_invarg; - } - // When changing the 'signcolumn' to or from 'number', recompute the - // width of the number column if 'number' or 'relativenumber' is set. - if (((*oldval == 'n' && *(oldval + 1) == 'u') - || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) == 'u')) - && (curwin->w_p_nu || curwin->w_p_rnu)) { - curwin->w_nrwidth_line_count = 0; - } - } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { - // 'foldcolumn' - if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_pt) { - // 'pastetoggle': translate key codes like in a mapping - if (*p_pt) { - p = NULL; - (void)replace_termcodes((char *)p_pt, - STRLEN(p_pt), - (char **)&p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, - CPO_TO_CPO_FLAGS); - if (p != NULL) { - free_string_option(p_pt); - p_pt = p; - } - } - } else if (varp == &p_bs) { // 'backspace' - if (ascii_isdigit(*p_bs)) { - if (*p_bs > '3' || p_bs[1] != NUL) { - errmsg = e_invarg; - } - } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_bo) { - if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_tc) { // 'tagcase' - unsigned int *flags; - - if (opt_flags & OPT_LOCAL) { - p = curbuf->b_p_tc; - flags = &curbuf->b_tc_flags; - } else { - p = p_tc; - flags = &tc_flags; - } - - if ((opt_flags & OPT_LOCAL) && *p == NUL) { - // 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) { - errmsg = e_invarg; - } - } else if (varp == &p_cmp) { // 'casemap' - if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_dip) { // 'diffopt' - if (diffopt_changed() == FAIL) { - errmsg = e_invarg; - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fdm) { // 'foldmethod' - if (check_opt_strings(*varp, p_fdm_values, false) != OK - || *curwin->w_p_fdm == NUL) { - errmsg = e_invarg; - } else { - foldUpdateAll(curwin); - if (foldmethodIsDiff(curwin)) { - newFoldLevel(); - } - } - } else if (varp == &curwin->w_p_fde) { // 'foldexpr' - if (foldmethodIsExpr(curwin)) { - foldUpdateAll(curwin); - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' - p = (char_u *)vim_strchr((char *)(*varp), ','); - if (p == NULL) { - errmsg = N_("E536: comma required"); - } else if (p == *varp || p[1] == NUL) { - errmsg = e_invarg; - } else if (foldmethodIsMarker(curwin)) { - foldUpdateAll(curwin); - } - } else if (gvarp == &p_cms) { // 'commentstring' - if (**varp != NUL && strstr((char *)(*varp), "%s") == NULL) { - errmsg = N_("E537: 'commentstring' must be empty or contain %s"); - } - } else if (varp == &p_fdo) { // 'foldopen' - if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_fcl) { // 'foldclose' - if (check_opt_strings(p_fcl, p_fcl_values, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fdi) { // 'foldignore' - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } - } else if (gvarp == &p_ve) { // 'virtualedit' - char_u *ve = p_ve; - unsigned int *flags = &ve_flags; - - if (opt_flags & OPT_LOCAL) { - ve = curwin->w_p_ve; - flags = &curwin->w_ve_flags; - } - - if ((opt_flags & OPT_LOCAL) && *ve == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else { - if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { - errmsg = e_invarg; - } else if (STRCMP(p_ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol(); - coladvance(curwin->w_virtcol); - } - } - } else if (varp == &p_csqf) { - if (p_csqf != NULL) { - p = p_csqf; - while (*p != NUL) { - if (vim_strchr(CSQF_CMDS, *p) == NULL - || p[1] == NUL - || vim_strchr(CSQF_FLAGS, p[1]) == NULL - || (p[2] != NUL && p[2] != ',')) { - errmsg = e_invarg; - break; - } else if (p[2] == NUL) { - break; - } else { - p += 3; - } - } - } - } else if (gvarp == &p_cino) { // 'cinoptions' - // TODO(vim): recognize errors - parse_cino(curbuf); - // inccommand - } else if (varp == &p_icm) { - if (check_opt_strings(p_icm, p_icm_values, false) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_ft) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - value_changed = STRCMP(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - } else if (gvarp == &p_syn) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - value_changed = STRCMP(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - } else if (varp == &curwin->w_p_winhl) { - if (!parse_winhl_opt(curwin)) { - errmsg = e_invarg; - } - } else if (varp == &p_tpf) { - if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop' - char_u *cp; - - if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - XFREE_CLEAR(curbuf->b_p_vsts_array); - } else { - for (cp = *varp; *cp; cp++) { - if (ascii_isdigit(*cp)) { - continue; - } - if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { - continue; - } - errmsg = e_invarg; - break; - } - if (errmsg == NULL) { - long *oldarray = curbuf->b_p_vsts_array; - if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) { - xfree(oldarray); - } else { - errmsg = e_invarg; - } - } - } - } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop' - char_u *cp; - - if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - XFREE_CLEAR(curbuf->b_p_vts_array); - } else { - for (cp = *varp; *cp; cp++) { - if (ascii_isdigit(*cp)) { - continue; - } - if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { - continue; - } - errmsg = e_invarg; - break; - } - if (errmsg == NULL) { - long *oldarray = curbuf->b_p_vts_array; - if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) { - xfree(oldarray); - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } - } else { - errmsg = e_invarg; - } - } - } - } else if (varp == &p_qftf) { - if (!qf_process_qftf_option()) { - errmsg = e_invarg; - } - } else { - // Options that are a list of flags. - p = NULL; - if (varp == &p_ww) { // 'whichwrap' - p = (char_u *)WW_ALL; - } - if (varp == &p_shm) { // 'shortmess' - p = (char_u *)SHM_ALL; - } else if (varp == (char_u **)&(p_cpo)) { // 'cpoptions' - p = (char_u *)CPO_VI; - } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' - p = (char_u *)FO_ALL; - } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' - p = (char_u *)COCU_ALL; - } else if (varp == &p_mouse) { // 'mouse' - p = (char_u *)MOUSE_ALL; - } - if (p != NULL) { - for (s = *varp; *s; s++) { - if (vim_strchr((char *)p, *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - } - } - } - - /* - * If error detected, restore the previous value. - */ - if (errmsg != NULL) { - free_string_option(*varp); - *varp = oldval; - /* - * When resetting some values, need to act on it. - */ - if (did_chartab) { - (void)init_chartab(); - } - } else { - // Remember where the option was set. - set_option_sctx_idx(opt_idx, opt_flags, current_sctx); - // Free string options that are in allocated memory. - // Use "free_oldval", because recursiveness may change the flags under - // our fingers (esp. init_highlight()). - if (free_oldval) { - free_string_option(oldval); - } - options[opt_idx].flags |= P_ALLOCED; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && ((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); - free_string_option(*(char_u **)p); - *(char_u **)p = empty_option; - } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { - // May set global value for local option. - set_string_option_global(opt_idx, varp); - } - - /* - * Trigger the autocommand only after setting the flags. - */ - // When 'syntax' is set, load the syntax of that name - if (varp == &(curbuf->b_p_syn)) { - static int syn_recursive = 0; - - syn_recursive++; - // Only pass true for "force" when the value changed or not used - // recursively, to avoid endless recurrence. - apply_autocmds(EVENT_SYNTAX, (char *)curbuf->b_p_syn, curbuf->b_fname, - value_changed || syn_recursive == 1, curbuf); - curbuf->b_flags |= BF_SYN_SET; - syn_recursive--; - } else if (varp == &(curbuf->b_p_ft)) { - // 'filetype' is set, trigger the FileType autocommand - // Skip this when called from a modeline and the filetype was - // already set to this value. - if (!(opt_flags & OPT_MODELINE) || value_changed) { - static int ft_recursive = 0; - int secure_save = secure; - - // Reset the secure flag, since the value of 'filetype' has - // been checked to be safe. - secure = 0; - - ft_recursive++; - did_filetype = true; - // Only pass true for "force" when the value changed or not - // used recursively, to avoid endless recurrence. - apply_autocmds(EVENT_FILETYPE, (char *)curbuf->b_p_ft, curbuf->b_fname, - value_changed || ft_recursive == 1, curbuf); - ft_recursive--; - // Just in case the old "curbuf" is now invalid - if (varp != &(curbuf->b_p_ft)) { - varp = NULL; - } - secure = secure_save; - } - } - if (varp == &(curwin->w_s->b_p_spl)) { - char_u fname[200]; - char_u *q = curwin->w_s->b_p_spl; - - // Skip the first name if it is "cjk". - if (STRNCMP(q, "cjk,", 4) == 0) { - q += 4; - } - - /* - * Source the spell/LANG.vim in 'runtimepath'. - * They could set 'spellcapcheck' depending on the language. - * Use the first name in 'spelllang' up to '_region' or - * '.encoding'. - */ - for (p = q; *p != NUL; p++) { - if (!ASCII_ISALNUM(*p) && *p != '-') { - break; - } - } - if (p > q) { - vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - q), q); - source_runtime((char *)fname, DIP_ALL); - } - } - } - - if (varp == &p_mouse) { - setmouse(); // in case 'mouse' changed - } - - if (curwin->w_curswant != MAXCOL - && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) { - curwin->w_set_curswant = true; - } - - check_redraw(options[opt_idx].flags); - - 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 -int check_signcolumn(char_u *val) -{ - if (*val == NUL) { - return FAIL; - } - // check for basic match - if (check_opt_strings(val, p_scl_values, false) == OK) { - return OK; - } - - // check for 'auto:<NUMBER>-<NUMBER>' - if (STRLEN(val) == 8 - && !STRNCMP(val, "auto:", 5) - && ascii_isdigit(val[5]) - && val[6] == '-' - && ascii_isdigit(val[7])) { - int min = val[5] - '0'; - int max = val[7] - '0'; - if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) { - return FAIL; - } - return OK; - } - - 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.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) -{ - int groupdepth = 0; - static char errbuf[80]; - - while (*s) { - // Check for valid keys after % sequences - while (*s && *s != '%') { - s++; - } - if (!*s) { - break; - } - s++; - if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) { - s++; - continue; - } - if (*s == ')') { - s++; - if (--groupdepth < 0) { - break; - } - continue; - } - if (*s == '-') { - s++; - } - while (ascii_isdigit(*s)) { - s++; - } - if (*s == STL_USER_HL) { - continue; - } - if (*s == '.') { - s++; - while (*s && ascii_isdigit(*s)) { - s++; - } - } - if (*s == '(') { - groupdepth++; - continue; - } - if (vim_strchr(STL_ALL, *s) == NULL) { - return illegal_char(errbuf, sizeof(errbuf), *s); - } - if (*s == '{') { - bool reevaluate = (*++s == '%'); - - if (reevaluate && *++s == '}') { - // "}" is not allowed immediately after "%{%" - return illegal_char(errbuf, sizeof(errbuf), '}'); - } - while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) { - s++; - } - if (*s != '}') { - return e_unclosed_expression_sequence; - } - } - } - if (groupdepth != 0) { - return e_unbalanced_groups; - } - 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,34 +1910,22 @@ 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; } -// Set the script_ctx for an option, taking care of setting the buffer- or -// window-local value. -static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) +/// Set the script_ctx for an option, taking care of setting the buffer- or +/// window-local value. +void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) { int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int indir = (int)options[opt_idx].indir; @@ -4009,7 +1934,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 }; @@ -4041,6 +1966,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va { int old_value = *(int *)varp; int old_global_value = 0; + char *errmsg = NULL; // Disallow changing some options from secure mode if ((secure || sandbox != 0) @@ -4145,7 +2071,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va paste_option_changed(); } else if ((int *)varp == &p_ic && p_hls) { // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else if ((int *)varp == &p_hls) { // when 'hlsearch' is set or reset: reset no_hlsearch set_no_hlsearch(false); @@ -4162,7 +2088,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (win->w_p_pvw && win != curwin) { curwin->w_p_pvw = false; - return N_("E590: A preview window already exists"); + return e_preview_window_already_exists; } } } @@ -4221,10 +2147,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } } else if ((int *)varp == &curwin->w_p_spell) { // 'spell' if (curwin->w_p_spell) { - char *errmsg = did_set_spelllang(curwin); - if (errmsg != NULL) { - emsg(_(errmsg)); - } + errmsg = did_set_spelllang(curwin); } } @@ -4243,7 +2166,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va // Enable Arabic shaping (major part of what Arabic requires) if (!p_arshape) { p_arshape = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } @@ -4261,7 +2184,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va p_deco = true; // Force-set the necessary keymap for arabic. - set_option_value("keymap", 0L, "arabic", OPT_LOCAL); + errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL); } else { /* * 'arabic' is reset, handle various sub-settings. @@ -4341,7 +2264,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } check_redraw(options[opt_idx].flags); - return NULL; + return errmsg; } /// Set the value of a number option, taking care of side effects @@ -4785,51 +2708,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, return errmsg; } -/// Trigger the OptionSet autocommand. -/// "opt_idx" is the index of the option being set. -/// "opt_flags" can be OPT_LOCAL etc. -/// "oldval" the old value -/// "oldval_l" the old local value (only non-NULL if global and local value are set) -/// "oldval_g" the old global value (only non-NULL if global and local value are set) -/// "newval" the new value -static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, - char *oldval_g, char *newval) -{ - // Don't do this recursively. - if (oldval != NULL - && newval != NULL - && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { - char buf_type[7]; - - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_OLD, oldval, -1); - set_vim_var_string(VV_OPTION_NEW, newval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - if (opt_flags & OPT_LOCAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); - } - if (opt_flags & OPT_GLOBAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); - } - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - set_vim_var_string(VV_OPTION_COMMAND, "set", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); - } - if (opt_flags & OPT_MODELINE) { - set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); - } - apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); - reset_v_option_vars(); - } -} - /// Called after an option changed: check if something needs to be redrawn. -static void check_redraw(uint32_t flags) +void check_redraw(uint32_t flags) { // Careful: P_RCLR and P_RALL are a combination of other P_ flags bool doclear = (flags & P_RCLR) == P_RCLR; @@ -4843,15 +2723,15 @@ static void check_redraw(uint32_t flags) changed_window_setting(); } if (flags & P_RBUF) { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } if (flags & P_RWINONLY) { - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } if (doclear) { - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } else if (all) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } @@ -5001,7 +2881,7 @@ bool set_tty_option(const char *name, char *value) void set_tty_background(const char *value) { - if (option_was_set("bg") || strequal((char *)p_bg, value)) { + if (option_was_set("bg") || strequal(p_bg, value)) { // background is already set... ignore return; } @@ -5011,7 +2891,7 @@ void set_tty_background(const char *value) ? "autocmd VimEnter * ++once ++nested set bg=light" : "autocmd VimEnter * ++once ++nested set bg=dark"); } else { - set_option_value("bg", 0L, value, 0); + set_option_value_give_err("bg", 0L, value, 0); reset_option_was_set("bg"); } } @@ -5021,7 +2901,7 @@ void set_tty_background(const char *value) /// @param[in] arg Option name. /// /// @return Option index or -1 if option was not found. -static int findoption(const char *const arg) +int findoption(const char *const arg) FUNC_ATTR_NONNULL_ALL { return findoption_len(arg, strlen(arg)); @@ -5050,14 +2930,14 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, i return gov_unknown; } - char_u *varp = get_varp_scope(&(options[opt_idx]), opt_flags); + char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags); if (options[opt_idx].flags & P_STRING) { if (varp == NULL) { // hidden option return gov_hidden_string; } if (stringval != NULL) { - if ((char_u **)varp == &p_pt) { // 'pastetoggle' + if ((char **)varp == &p_pt) { // 'pastetoggle' *stringval = str2special_save(*(char **)(varp), false, false); } else { *stringval = xstrdup(*(char **)(varp)); @@ -5176,7 +3056,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o // only getting a pointer, no need to use aucmd_prepbuf() curbuf = (buf_T *)from; curwin->w_buffer = curbuf; - varp = get_varp_scope(p, OPT_LOCAL); + varp = (char_u *)get_varp_scope(p, OPT_LOCAL); curbuf = save_curbuf; curwin->w_buffer = curbuf; } @@ -5184,7 +3064,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o win_T *save_curwin = curwin; curwin = (win_T *)from; curbuf = curwin->w_buffer; - varp = get_varp_scope(p, OPT_LOCAL); + varp = (char_u *)get_varp_scope(p, OPT_LOCAL); curwin = save_curwin; curbuf = curwin->w_buffer; } @@ -5207,6 +3087,49 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return rv; } +/// Return the flags for the option at 'opt_idx'. +uint32_t get_option_flags(int opt_idx) +{ + return options[opt_idx].flags; +} + +/// Set a flag for the option at 'opt_idx'. +void set_option_flag(int opt_idx, uint32_t flag) +{ + options[opt_idx].flags |= flag; +} + +/// Clear a flag for the option at 'opt_idx'. +void clear_option_flag(int opt_idx, uint32_t flag) +{ + options[opt_idx].flags &= ~flag; +} + +/// Returns true if the option at 'opt_idx' is a global option +bool is_global_option(int opt_idx) +{ + return options[opt_idx].indir == PV_NONE; +} + +/// Returns true if the option at 'opt_idx' is a global option which also has a +/// local value. +int is_global_local_option(int opt_idx) +{ + return options[opt_idx].indir & PV_BOTH; +} + +/// Returns true if the option at 'opt_idx' is a window-local option +bool is_window_local_option(int opt_idx) +{ + return options[opt_idx].var == VAR_WIN; +} + +/// Returns true if the option at 'opt_idx' is a hidden option +bool is_hidden_option(int opt_idx) +{ + return options[opt_idx].var == NULL; +} + /// Set the value of an option /// /// @param[in] name Option name. @@ -5217,7 +3140,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o /// is cleared (the exact semantics of this depend /// on the option). /// -/// @return NULL on success, error message on error. +/// @return NULL on success, an untranslated error message on error. char *set_option_value(const char *const name, const long number, const char *const string, const int opt_flags) FUNC_ATTR_NONNULL_ARG(1) @@ -5247,7 +3170,7 @@ char *set_option_value(const char *const name, const long number, const char *co return set_string_option(opt_idx, s, opt_flags); } - varp = get_varp_scope(&(options[opt_idx]), opt_flags); + varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags); if (varp != NULL) { // hidden option is not changed if (number == 0 && string != NULL) { int idx; @@ -5287,6 +3210,18 @@ char *set_option_value(const char *const name, const long number, const char *co return NULL; } +/// Call set_option_value() and when an error is returned report it. +/// +/// @param opt_flags OPT_LOCAL or 0 (both) +void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags) +{ + char *errmsg = set_option_value(name, number, string, opt_flags); + + if (errmsg != NULL) { + emsg(_(errmsg)); + } +} + /// Return true if "name" is a string option. /// Returns false if option "name" does not exist. bool is_string_option(const char *name) @@ -5364,14 +3299,14 @@ static void showoptions(int all, int opt_flags) item_count = 0; for (p = &options[0]; p->fullname != NULL; p++) { // apply :filter /pat/ - if (message_filtered((char_u *)p->fullname)) { + if (message_filtered(p->fullname)) { continue; } varp = NULL; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { - varp = get_varp_scope(p, opt_flags); + varp = (char_u *)get_varp_scope(p, opt_flags); } } else { varp = get_varp(p); @@ -5475,13 +3410,12 @@ void ui_refresh_options(void) /// @param opt_flags OPT_LOCAL or OPT_GLOBAL static void showoneopt(vimoption_T *p, int opt_flags) { - char_u *varp; int save_silent = silent_mode; silent_mode = false; info_message = true; // use mch_msg(), not mch_errmsg() - varp = get_varp_scope(p, opt_flags); + char_u *varp = (char_u *)get_varp_scope(p, opt_flags); // for 'modified' we also need to check if 'ff' or 'fenc' changed. if ((p->flags & P_BOOL) && ((int *)varp == &curbuf->b_changed @@ -5527,7 +3461,7 @@ static void showoneopt(vimoption_T *p, int opt_flags) int makeset(FILE *fd, int opt_flags, int local_only) { vimoption_T *p; - char_u *varp; // currently used value + char *varp; // currently used value char_u *varp_fresh; // local value char_u *varp_local = NULL; // fresh value char *cmd; @@ -5564,7 +3498,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) continue; } // Global values are only written when not at the default value. - if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) { + if ((opt_flags & OPT_GLOBAL) && optval_default(p, (char_u *)varp)) { continue; } @@ -5583,11 +3517,11 @@ int makeset(FILE *fd, int opt_flags, int local_only) // When fresh value of window-local option is not at the // default, need to write it too. if (!(opt_flags & OPT_GLOBAL) && !local_only) { - varp_fresh = get_varp_scope(p, OPT_GLOBAL); + varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); if (!optval_default(p, varp_fresh)) { round = 1; - varp_local = varp; - varp = varp_fresh; + varp_local = (char_u *)varp; + varp = (char *)varp_fresh; } } } @@ -5595,7 +3529,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) // Round 1: fresh value for window-local options. // Round 2: other values - for (; round <= 2; varp = varp_local, round++) { + for (; round <= 2; varp = (char *)varp_local, round++) { if (round == 1 || (opt_flags & OPT_GLOBAL)) { cmd = "set"; } else { @@ -5623,8 +3557,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) } do_endif = true; } - if (put_setstring(fd, cmd, p->fullname, (char_u **)varp, - p->flags) == FAIL) { + if (put_setstring(fd, cmd, p->fullname, (char **)varp, p->flags) == FAIL) { return FAIL; } if (do_endif) { @@ -5645,29 +3578,25 @@ int makeset(FILE *fd, int opt_flags, int local_only) int makefoldset(FILE *fd) { if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, 0) == FAIL - || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) - == FAIL - || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) - == FAIL - || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) - == FAIL + || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) == FAIL + || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) == FAIL + || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) == FAIL || put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL || put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL || put_setnum(fd, "setlocal", "fdn", &curwin->w_p_fdn) == FAIL - || put_setbool(fd, "setlocal", "fen", - curwin->w_p_fen) == FAIL) { + || put_setbool(fd, "setlocal", "fen", curwin->w_p_fen) == FAIL) { return FAIL; } return OK; } -static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint64_t flags) +static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_t flags) { 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; @@ -5677,7 +3606,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 // options some characters have to be escaped with // CTRL-V or backslash if (valuep == &p_pt) { - s = *valuep; + s = (char_u *)(*valuep); while (*s != NUL) { if (put_escstr(fd, (char_u *)str2special((const char **)&s, false, false), 2) @@ -5690,27 +3619,27 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 // replace home directory in the whole option value into "buf" buf = xmalloc(size); - home_replace(NULL, (char *)(*valuep), (char *)buf, size, false); + home_replace(NULL, *valuep, (char *)buf, size, false); // If the option value is longer than MAXPATHL, we need to append // each comma separated part of the option separately, so that it // can be expanded when read back. if (size >= MAXPATHL && (flags & P_COMMA) != 0 - && vim_strchr((char *)(*valuep), ',') != NULL) { + && vim_strchr(*valuep, ',') != NULL) { part = xmalloc(size); // write line break to clear the option, e.g. ':set rtp=' 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; } @@ -5724,7 +3653,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 return FAIL; } xfree(buf); - } else if (put_escstr(fd, *valuep, 2) == FAIL) { + } else if (put_escstr(fd, (char_u *)(*valuep), 2) == FAIL) { return FAIL; } } @@ -5771,47 +3700,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) { @@ -5891,7 +3779,7 @@ void unset_global_local_option(char *name, void *from) clear_string_option(&((win_T *)from)->w_p_stl); break; case PV_WBR: - clear_string_option((char_u **)&((win_T *)from)->w_p_wbr); + clear_string_option(&((win_T *)from)->w_p_wbr); break; case PV_UL: buf->b_p_ul = NO_LOCAL_UNDOLEVEL; @@ -5905,12 +3793,12 @@ void unset_global_local_option(char *name, void *from) case PV_LCS: clear_string_option(&((win_T *)from)->w_p_lcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); - redraw_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, UPD_NOT_VALID); break; case PV_FCS: clear_string_option(&((win_T *)from)->w_p_fcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); - redraw_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, UPD_NOT_VALID); break; case PV_VE: clear_string_option(&((win_T *)from)->w_p_ve); @@ -5920,76 +3808,83 @@ void unset_global_local_option(char *name, void *from) } /// Get pointer to option variable, depending on local or global scope. -static char_u *get_varp_scope(vimoption_T *p, int opt_flags) +static char *get_varp_scope(vimoption_T *p, int opt_flags) { if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { if (p->var == VAR_WIN) { - return (char_u *)GLOBAL_WO(get_varp(p)); + return GLOBAL_WO(get_varp(p)); } - return p->var; + return (char *)p->var; } if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { switch ((int)p->indir) { case PV_FP: - return (char_u *)&(curbuf->b_p_fp); + return (char *)&(curbuf->b_p_fp); case PV_EFM: - return (char_u *)&(curbuf->b_p_efm); + return (char *)&(curbuf->b_p_efm); case PV_GP: - return (char_u *)&(curbuf->b_p_gp); + return (char *)&(curbuf->b_p_gp); case PV_MP: - return (char_u *)&(curbuf->b_p_mp); + return (char *)&(curbuf->b_p_mp); case PV_EP: - return (char_u *)&(curbuf->b_p_ep); + return (char *)&(curbuf->b_p_ep); case PV_KP: - return (char_u *)&(curbuf->b_p_kp); + return (char *)&(curbuf->b_p_kp); case PV_PATH: - return (char_u *)&(curbuf->b_p_path); + return (char *)&(curbuf->b_p_path); case PV_AR: - return (char_u *)&(curbuf->b_p_ar); + return (char *)&(curbuf->b_p_ar); case PV_TAGS: - return (char_u *)&(curbuf->b_p_tags); + return (char *)&(curbuf->b_p_tags); case PV_TC: - return (char_u *)&(curbuf->b_p_tc); + return (char *)&(curbuf->b_p_tc); case PV_SISO: - return (char_u *)&(curwin->w_p_siso); + return (char *)&(curwin->w_p_siso); case PV_SO: - return (char_u *)&(curwin->w_p_so); + return (char *)&(curwin->w_p_so); case PV_DEF: - return (char_u *)&(curbuf->b_p_def); + return (char *)&(curbuf->b_p_def); case PV_INC: - return (char_u *)&(curbuf->b_p_inc); + return (char *)&(curbuf->b_p_inc); case PV_DICT: - return (char_u *)&(curbuf->b_p_dict); + return (char *)&(curbuf->b_p_dict); case PV_TSR: - return (char_u *)&(curbuf->b_p_tsr); + return (char *)&(curbuf->b_p_tsr); case PV_TSRFU: - return (char_u *)&(curbuf->b_p_tsrfu); + return (char *)&(curbuf->b_p_tsrfu); case PV_TFU: - return (char_u *)&(curbuf->b_p_tfu); + return (char *)&(curbuf->b_p_tfu); case PV_SBR: - return (char_u *)&(curwin->w_p_sbr); + return (char *)&(curwin->w_p_sbr); case PV_STL: - return (char_u *)&(curwin->w_p_stl); + return (char *)&(curwin->w_p_stl); case PV_WBR: - return (char_u *)&(curwin->w_p_wbr); + return (char *)&(curwin->w_p_wbr); case PV_UL: - return (char_u *)&(curbuf->b_p_ul); + return (char *)&(curbuf->b_p_ul); case PV_LW: - return (char_u *)&(curbuf->b_p_lw); + return (char *)&(curbuf->b_p_lw); case PV_BKC: - return (char_u *)&(curbuf->b_p_bkc); + return (char *)&(curbuf->b_p_bkc); case PV_MENC: - return (char_u *)&(curbuf->b_p_menc); + return (char *)&(curbuf->b_p_menc); case PV_FCS: - return (char_u *)&(curwin->w_p_fcs); + return (char *)&(curwin->w_p_fcs); case PV_LCS: - return (char_u *)&(curwin->w_p_lcs); + return (char *)&(curwin->w_p_lcs); case PV_VE: - return (char_u *)&(curwin->w_p_ve); + return (char *)&(curwin->w_p_ve); } return NULL; // "cannot happen" } - return get_varp(p); + return (char *)get_varp(p); +} + +/// Get pointer to option variable at 'opt_idx', depending on local or global +/// scope. +char *get_option_varp_scope(int opt_idx, int opt_flags) +{ + return get_varp_scope(&(options[opt_idx]), opt_flags); } /// Get pointer to option variable. @@ -6303,13 +4198,25 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_wm); } +/// Return a pointer to the variable for option at 'opt_idx' +char_u *get_option_var(int opt_idx) +{ + return options[opt_idx].var; +} + +/// Return the full name of the option at 'opt_idx' +char *get_option_fullname(int opt_idx) +{ + return options[opt_idx].fullname; +} + /// Get the value of 'equalprg', either the buffer-local one or the global one. char_u *get_equalprg(void) { if (*curbuf->b_p_ep == NUL) { return p_ep; } - return curbuf->b_p_ep; + return (char_u *)curbuf->b_p_ep; } /// Copy options from one window to another. @@ -6318,7 +4225,15 @@ 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); +} + +static char *copy_option_val(const char *val) +{ + if (val == empty_option) { + return empty_option; // no need to allocate memory + } + return xstrdup(val); } /// Copy the options from one winopt_T to another. @@ -6329,21 +4244,23 @@ void copy_winopt(winopt_T *from, winopt_T *to) { to->wo_arab = from->wo_arab; to->wo_list = from->wo_list; + to->wo_lcs = copy_option_val(from->wo_lcs); + to->wo_fcs = copy_option_val(from->wo_fcs); to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; - to->wo_ve = vim_strsave(from->wo_ve); + to->wo_ve = copy_option_val(from->wo_ve); to->wo_ve_flags = from->wo_ve_flags; to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; - to->wo_rlc = vim_strsave(from->wo_rlc); - to->wo_sbr = vim_strsave(from->wo_sbr); - to->wo_stl = vim_strsave(from->wo_stl); - to->wo_wbr = xstrdup(from->wo_wbr); + to->wo_rlc = copy_option_val(from->wo_rlc); + to->wo_sbr = copy_option_val(from->wo_sbr); + to->wo_stl = copy_option_val(from->wo_stl); + to->wo_wbr = copy_option_val(from->wo_wbr); to->wo_wrap = from->wo_wrap; to->wo_wrap_save = from->wo_wrap_save; to->wo_lbr = from->wo_lbr; to->wo_bri = from->wo_bri; - to->wo_briopt = vim_strsave(from->wo_briopt); + to->wo_briopt = copy_option_val(from->wo_briopt); to->wo_scb = from->wo_scb; to->wo_scb_save = from->wo_scb_save; to->wo_crb = from->wo_crb; @@ -6351,32 +4268,28 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_spell = from->wo_spell; to->wo_cuc = from->wo_cuc; to->wo_cul = from->wo_cul; - to->wo_culopt = vim_strsave(from->wo_culopt); - to->wo_cc = vim_strsave(from->wo_cc); + to->wo_culopt = copy_option_val(from->wo_culopt); + to->wo_cc = copy_option_val(from->wo_cc); to->wo_diff = from->wo_diff; to->wo_diff_saved = from->wo_diff_saved; - to->wo_cocu = vim_strsave(from->wo_cocu); + to->wo_cocu = copy_option_val(from->wo_cocu); to->wo_cole = from->wo_cole; - to->wo_fdc = vim_strsave(from->wo_fdc); - to->wo_fdc_save = from->wo_diff_saved - ? vim_strsave(from->wo_fdc_save) : empty_option; + to->wo_fdc = copy_option_val(from->wo_fdc); + to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_option; to->wo_fen = from->wo_fen; to->wo_fen_save = from->wo_fen_save; - to->wo_fdi = vim_strsave(from->wo_fdi); + to->wo_fdi = copy_option_val(from->wo_fdi); to->wo_fml = from->wo_fml; to->wo_fdl = from->wo_fdl; to->wo_fdl_save = from->wo_fdl_save; - to->wo_fdm = vim_strsave(from->wo_fdm); - to->wo_fdm_save = from->wo_diff_saved - ? vim_strsave(from->wo_fdm_save) : empty_option; + to->wo_fdm = copy_option_val(from->wo_fdm); + to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_option; to->wo_fdn = from->wo_fdn; - to->wo_fde = vim_strsave(from->wo_fde); - to->wo_fdt = vim_strsave(from->wo_fdt); - to->wo_fmr = vim_strsave(from->wo_fmr); - to->wo_scl = vim_strsave(from->wo_scl); - to->wo_winhl = vim_strsave(from->wo_winhl); - to->wo_fcs = vim_strsave(from->wo_fcs); - to->wo_lcs = vim_strsave(from->wo_lcs); + to->wo_fde = copy_option_val(from->wo_fde); + to->wo_fdt = copy_option_val(from->wo_fdt); + to->wo_fmr = copy_option_val(from->wo_fmr); + to->wo_scl = copy_option_val(from->wo_scl); + to->wo_winhl = copy_option_val(from->wo_winhl); to->wo_winbl = from->wo_winbl; // Copy the script context so that we know were the value was last set. @@ -6411,10 +4324,10 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_cocu); check_string_option(&wop->wo_briopt); check_string_option(&wop->wo_winhl); - check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_lcs); + check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_ve); - check_string_option((char_u **)&wop->wo_wbr); + check_string_option(&wop->wo_wbr); } /// Free the allocated memory inside a winopt_T. @@ -6437,13 +4350,13 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_cocu); clear_string_option(&wop->wo_briopt); clear_string_option(&wop->wo_winhl); - clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_lcs); + clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_ve); - clear_string_option((char_u **)&wop->wo_wbr); + clear_string_option(&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); @@ -6452,7 +4365,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; } @@ -6515,14 +4428,14 @@ 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 // (jumping back to a help file with CTRL-T or CTRL-O) dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized; if (dont_do_help) { // don't free b_p_isk - save_p_isk = buf->b_p_isk; + save_p_isk = (char_u *)buf->b_p_isk; buf->b_p_isk = NULL; } // Always free the allocated strings. If not already initialized, @@ -6530,19 +4443,19 @@ void buf_copy_options(buf_T *buf, int flags) if (!buf->b_p_initialized) { free_buf_options(buf, true); buf->b_p_ro = false; // don't copy readonly - buf->b_p_fenc = vim_strsave(p_fenc); + buf->b_p_fenc = xstrdup(p_fenc); switch (*p_ffs) { case 'm': - buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + buf->b_p_ff = xstrdup(FF_MAC); break; case 'd': - buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + buf->b_p_ff = xstrdup(FF_DOS); break; case 'u': - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + buf->b_p_ff = xstrdup(FF_UNIX); break; default: - buf->b_p_ff = vim_strsave(p_ff); + buf->b_p_ff = xstrdup(p_ff); break; } buf->b_p_bh = empty_option; @@ -6587,44 +4500,42 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_swf = p_swf; COPY_OPT_SCTX(buf, BV_SWF); } - buf->b_p_cpt = vim_strsave(p_cpt); + buf->b_p_cpt = xstrdup(p_cpt); COPY_OPT_SCTX(buf, BV_CPT); #ifdef BACKSLASH_IN_FILENAME buf->b_p_csl = vim_strsave(p_csl); COPY_OPT_SCTX(buf, BV_CSL); #endif - buf->b_p_cfu = vim_strsave(p_cfu); + buf->b_p_cfu = xstrdup(p_cfu); COPY_OPT_SCTX(buf, BV_CFU); - buf->b_p_ofu = vim_strsave(p_ofu); + buf->b_p_ofu = xstrdup(p_ofu); COPY_OPT_SCTX(buf, BV_OFU); - buf->b_p_tfu = vim_strsave(p_tfu); + buf->b_p_tfu = xstrdup(p_tfu); COPY_OPT_SCTX(buf, BV_TFU); - buf->b_p_umf = vim_strsave(p_umf); + buf->b_p_umf = xstrdup(p_umf); COPY_OPT_SCTX(buf, BV_UMF); buf->b_p_sts = p_sts; COPY_OPT_SCTX(buf, BV_STS); buf->b_p_sts_nopaste = p_sts_nopaste; - buf->b_p_vsts = vim_strsave(p_vsts); + buf->b_p_vsts = xstrdup(p_vsts); COPY_OPT_SCTX(buf, BV_VSTS); if (p_vsts && p_vsts != empty_option) { (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { buf->b_p_vsts_array = NULL; } - buf->b_p_vsts_nopaste = p_vsts_nopaste - ? vim_strsave(p_vsts_nopaste) - : NULL; - buf->b_p_com = vim_strsave(p_com); + buf->b_p_vsts_nopaste = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : NULL; + buf->b_p_com = xstrdup(p_com); COPY_OPT_SCTX(buf, BV_COM); - buf->b_p_cms = vim_strsave(p_cms); + buf->b_p_cms = xstrdup(p_cms); COPY_OPT_SCTX(buf, BV_CMS); - buf->b_p_fo = vim_strsave(p_fo); + buf->b_p_fo = xstrdup(p_fo); COPY_OPT_SCTX(buf, BV_FO); - buf->b_p_flp = vim_strsave(p_flp); + buf->b_p_flp = xstrdup(p_flp); COPY_OPT_SCTX(buf, BV_FLP); - buf->b_p_nf = vim_strsave(p_nf); + buf->b_p_nf = xstrdup(p_nf); COPY_OPT_SCTX(buf, BV_NF); - buf->b_p_mps = vim_strsave(p_mps); + buf->b_p_mps = xstrdup(p_mps); COPY_OPT_SCTX(buf, BV_MPS); buf->b_p_si = p_si; COPY_OPT_SCTX(buf, BV_SI); @@ -6634,18 +4545,18 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_CI); buf->b_p_cin = p_cin; COPY_OPT_SCTX(buf, BV_CIN); - buf->b_p_cink = vim_strsave(p_cink); + buf->b_p_cink = xstrdup(p_cink); COPY_OPT_SCTX(buf, BV_CINK); - buf->b_p_cino = vim_strsave(p_cino); + buf->b_p_cino = xstrdup(p_cino); COPY_OPT_SCTX(buf, BV_CINO); - buf->b_p_cinsd = vim_strsave(p_cinsd); + buf->b_p_cinsd = xstrdup(p_cinsd); COPY_OPT_SCTX(buf, BV_CINSD); // Don't copy 'filetype', it must be detected buf->b_p_ft = empty_option; buf->b_p_pi = p_pi; COPY_OPT_SCTX(buf, BV_PI); - buf->b_p_cinw = vim_strsave(p_cinw); + buf->b_p_cinw = xstrdup(p_cinw); COPY_OPT_SCTX(buf, BV_CINW); buf->b_p_lisp = p_lisp; COPY_OPT_SCTX(buf, BV_LISP); @@ -6654,25 +4565,25 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_smc = p_smc; COPY_OPT_SCTX(buf, BV_SMC); buf->b_s.b_syn_isk = empty_option; - buf->b_s.b_p_spc = vim_strsave(p_spc); + buf->b_s.b_p_spc = xstrdup(p_spc); COPY_OPT_SCTX(buf, BV_SPC); (void)compile_cap_prog(&buf->b_s); - buf->b_s.b_p_spf = vim_strsave(p_spf); + buf->b_s.b_p_spf = xstrdup(p_spf); COPY_OPT_SCTX(buf, BV_SPF); - buf->b_s.b_p_spl = vim_strsave(p_spl); + buf->b_s.b_p_spl = xstrdup(p_spl); COPY_OPT_SCTX(buf, BV_SPL); - buf->b_s.b_p_spo = vim_strsave(p_spo); + buf->b_s.b_p_spo = xstrdup(p_spo); COPY_OPT_SCTX(buf, BV_SPO); - buf->b_p_inde = vim_strsave(p_inde); + buf->b_p_inde = xstrdup(p_inde); COPY_OPT_SCTX(buf, BV_INDE); - buf->b_p_indk = vim_strsave(p_indk); + buf->b_p_indk = xstrdup(p_indk); COPY_OPT_SCTX(buf, BV_INDK); buf->b_p_fp = empty_option; - buf->b_p_fex = vim_strsave(p_fex); + buf->b_p_fex = xstrdup(p_fex); COPY_OPT_SCTX(buf, BV_FEX); - buf->b_p_sua = vim_strsave(p_sua); + buf->b_p_sua = xstrdup(p_sua); COPY_OPT_SCTX(buf, BV_SUA); - buf->b_p_keymap = vim_strsave(p_keymap); + buf->b_p_keymap = xstrdup(p_keymap); COPY_OPT_SCTX(buf, BV_KMAP); buf->b_kmap_state |= KEYMAP_INIT; // This isn't really an option, but copying the langmap and IME @@ -6699,12 +4610,12 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_tc_flags = 0; buf->b_p_def = empty_option; buf->b_p_inc = empty_option; - buf->b_p_inex = vim_strsave(p_inex); + buf->b_p_inex = xstrdup(p_inex); COPY_OPT_SCTX(buf, BV_INEX); buf->b_p_dict = empty_option; buf->b_p_tsr = empty_option; buf->b_p_tsrfu = empty_option; - buf->b_p_qe = vim_strsave(p_qe); + buf->b_p_qe = xstrdup(p_qe); COPY_OPT_SCTX(buf, BV_QE); buf->b_p_udf = p_udf; COPY_OPT_SCTX(buf, BV_UDF); @@ -6718,19 +4629,19 @@ void buf_copy_options(buf_T *buf, int flags) * or to a help buffer. */ if (dont_do_help) { - buf->b_p_isk = save_p_isk; + buf->b_p_isk = (char *)save_p_isk; if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; } } else { - buf->b_p_isk = vim_strsave(p_isk); + buf->b_p_isk = xstrdup(p_isk); COPY_OPT_SCTX(buf, BV_ISK); did_isk = true; buf->b_p_ts = p_ts; COPY_OPT_SCTX(buf, BV_TS); - buf->b_p_vts = vim_strsave(p_vts); + buf->b_p_vts = xstrdup(p_vts); COPY_OPT_SCTX(buf, BV_VTS); if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); @@ -7051,7 +4962,7 @@ void ExpandOldSetting(int *num_file, char ***file) if (expand_option_idx >= 0) { // Put string of option value in NameBuff. option_value2string(&options[expand_option_idx], expand_option_flags); - var = NameBuff; + var = (char_u *)NameBuff; } else { var = (char_u *)""; } @@ -7084,9 +4995,7 @@ void ExpandOldSetting(int *num_file, char ***file) /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL static void option_value2string(vimoption_T *opp, int opt_flags) { - char_u *varp; - - varp = get_varp_scope(opp, opt_flags); + char_u *varp = (char_u *)get_varp_scope(opp, opt_flags); if (opp->flags & P_NUM) { long wc = 0; @@ -7108,7 +5017,7 @@ static void option_value2string(vimoption_T *opp, int opt_flags) } else if (opp->flags & P_EXPAND) { home_replace(NULL, (char *)varp, (char *)NameBuff, MAXPATHL, false); // Translate 'pastetoggle' into special key names. - } else if ((char_u **)opp->var == &p_pt) { + } else if ((char **)opp->var == &p_pt) { str2specialbuf((const char *)p_pt, (char *)NameBuff, MAXPATHL); } else { STRLCPY(NameBuff, varp, MAXPATHL); @@ -7130,25 +5039,14 @@ static int wc_use_keyname(char_u *varp, long *wcp) return false; } -/// Return true if format option 'x' is in effect. -/// Take care of no formatting when 'paste' is set. -bool has_format_option(int x) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (p_paste) { - return false; - } - return vim_strchr((char *)curbuf->b_p_fo, x) != NULL; -} - /// @returns true if "x" is present in 'shortmess' option, or /// 'shortmess' contains 'a' and "x" is present in SHM_ALL_ABBREVIATIONS. bool shortmess(int x) { return (p_shm != NULL - && (vim_strchr((char *)p_shm, x) != NULL - || (vim_strchr((char *)p_shm, 'a') != NULL - && vim_strchr((char *)SHM_ALL_ABBREVIATIONS, x) != NULL))); + && (vim_strchr(p_shm, x) != NULL + || (vim_strchr(p_shm, 'a') != NULL + && vim_strchr(SHM_ALL_ABBREVIATIONS, x) != NULL))); } /// paste_option_changed() - Called after p_paste was set or reset. @@ -7178,7 +5076,7 @@ static void paste_option_changed(void) xfree(buf->b_p_vsts_nopaste); } buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option - ? vim_strsave(buf->b_p_vsts) + ? xstrdup(buf->b_p_vsts) : NULL; } @@ -7197,9 +5095,7 @@ static void paste_option_changed(void) if (p_vsts_nopaste) { xfree(p_vsts_nopaste); } - p_vsts_nopaste = p_vsts && p_vsts != empty_option - ? vim_strsave(p_vsts) - : NULL; + p_vsts_nopaste = p_vsts && p_vsts != empty_option ? xstrdup(p_vsts) : NULL; } // Always set the option values, also when 'paste' is set when it is @@ -7249,9 +5145,7 @@ static void paste_option_changed(void) if (buf->b_p_vsts) { free_string_option(buf->b_p_vsts); } - buf->b_p_vsts = buf->b_p_vsts_nopaste - ? vim_strsave(buf->b_p_vsts_nopaste) - : empty_option; + buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_option; xfree(buf->b_p_vsts_array); if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); @@ -7278,7 +5172,7 @@ static void paste_option_changed(void) if (p_vsts) { free_string_option(p_vsts); } - p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option; + p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_option; } old_p_paste = p_paste; @@ -7336,7 +5230,7 @@ void reset_option_was_set(const char *name) } /// fill_breakat_flags() -- called when 'breakat' changes value. -static void fill_breakat_flags(void) +void fill_breakat_flags(void) { char_u *p; int i; @@ -7346,16 +5240,16 @@ static void fill_breakat_flags(void) } if (p_breakat != NULL) { - for (p = p_breakat; *p; p++) { + for (p = (char_u *)p_breakat; *p; p++) { breakat_flags[*p] = true; } } } /// fill_culopt_flags() -- called when 'culopt' changes value -static int fill_culopt_flags(char_u *val, win_T *wp) +int fill_culopt_flags(char *val, win_T *wp) { - char_u *p; + char *p; char_u culopt_flags_new = 0; if (val == NULL) { @@ -7395,101 +5289,41 @@ static int fill_culopt_flags(char_u *val, win_T *wp) return OK; } -/// Check an option that can be a range of string values. -/// -/// @param list when true: accept a list of values -/// -/// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char_u *val, char **values, int list) -{ - return opt_strings_flags(val, values, NULL, list); -} - -/// Handle an option that can be a range of string values. -/// Set a flag in "*flagp" for each string present. -/// -/// @param val new value -/// @param values array of valid string values -/// @param list when true: accept a list of values -/// -/// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int opt_strings_flags(char_u *val, char **values, unsigned *flagp, bool list) -{ - unsigned int new_flags = 0; - - while (*val) { - for (unsigned int i = 0;; i++) { - if (values[i] == NULL) { // val not found in values[] - return FAIL; - } - - size_t len = STRLEN(values[i]); - if (STRNCMP(values[i], val, len) == 0 - && ((list && val[len] == ',') || val[len] == NUL)) { - val += len + (val[len] == ','); - assert(i < sizeof(1U) * 8); - new_flags |= (1U << i); - break; // check next item in val list - } - } - } - if (flagp != NULL) { - *flagp = new_flags; - } - - return OK; -} - -/// Read the 'wildmode' option, fill wim_flags[]. -static int check_opt_wim(void) +/// Set the callback function value for an option that accepts a function name, +/// lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.) +/// @return OK if the option is successfully set to a function, otherwise FAIL +int option_set_callback_func(char_u *optval, Callback *optcb) { - char_u new_wim_flags[4]; - char_u *p; - int i; - int idx = 0; - - for (i = 0; i < 4; i++) { - new_wim_flags[i] = 0; + if (optval == NULL || *optval == NUL) { + callback_free(optcb); + return OK; } - for (p = p_wim; *p; p++) { - for (i = 0; ASCII_ISALPHA(p[i]); i++) {} - if (p[i] != NUL && p[i] != ',' && p[i] != ':') { - return FAIL; - } - if (i == 7 && STRNCMP(p, "longest", 7) == 0) { - new_wim_flags[idx] |= WIM_LONGEST; - } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { - new_wim_flags[idx] |= WIM_FULL; - } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { - new_wim_flags[idx] |= WIM_LIST; - } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { - new_wim_flags[idx] |= WIM_BUFLASTUSED; - } else { + typval_T *tv; + if (*optval == '{' + || (STRNCMP(optval, "function(", 9) == 0) + || (STRNCMP(optval, "funcref(", 8) == 0)) { + // Lambda expression or a funcref + tv = eval_expr((char *)optval); + if (tv == NULL) { return FAIL; } - p += i; - if (*p == NUL) { - break; - } - if (*p == ',') { - if (idx == 3) { - return FAIL; - } - idx++; - } + } else { + // treat everything else as a function name string + tv = xcalloc(1, sizeof(*tv)); + tv->v_type = VAR_STRING; + tv->vval.v_string = (char *)vim_strsave(optval); } - // fill remaining entries with last flag - while (idx < 3) { - new_wim_flags[idx + 1] = new_wim_flags[idx]; - idx++; + Callback cb; + if (!callback_from_typval(&cb, tv)) { + tv_free(tv); + return FAIL; } - // only when there are no errors, wim_flags[] is changed - for (i = 0; i < 4; i++) { - wim_flags[i] = new_wim_flags[i]; - } + callback_free(optcb); + *optcb = cb; + tv_free(tv); return OK; } @@ -7510,412 +5344,7 @@ bool can_bs(int what) case '0': return false; } - return vim_strchr((char *)p_bs, what) != NULL; -} - -/// Save the current values of 'fileformat' and 'fileencoding', so that we know -/// the file must be considered changed when the value is different. -void save_file_ff(buf_T *buf) -{ - buf->b_start_ffc = *buf->b_p_ff; - buf->b_start_eol = buf->b_p_eol; - buf->b_start_bomb = buf->b_p_bomb; - - // Only use free/alloc when necessary, they take time. - if (buf->b_start_fenc == NULL - || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { - xfree(buf->b_start_fenc); - buf->b_start_fenc = (char *)vim_strsave(buf->b_p_fenc); - } -} - -/// Return true if 'fileformat' and/or 'fileencoding' has a different value -/// from when editing started (save_file_ff() called). -/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was -/// changed and 'binary' is not set. -/// Also when 'endofline' was changed and 'fixeol' is not set. -/// When "ignore_empty" is true don't consider a new, empty buffer to be -/// changed. -bool file_ff_differs(buf_T *buf, bool ignore_empty) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - // In a buffer that was never loaded the options are not valid. - if (buf->b_flags & BF_NEVERLOADED) { - return false; - } - if (ignore_empty - && (buf->b_flags & BF_NEW) - && buf->b_ml.ml_line_count == 1 - && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { - return false; - } - if (buf->b_start_ffc != *buf->b_p_ff) { - return true; - } - if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) { - return true; - } - if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { - return true; - } - if (buf->b_start_fenc == NULL) { - return *buf->b_p_fenc != NUL; - } - return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; -} - -/// return OK if "p" is a valid fileformat name, FAIL otherwise. -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) -{ - int bri_shift = 0; - int bri_min = 20; - bool bri_sbr = false; - int bri_list = 0; - - char *p = (char *)wp->w_p_briopt; - while (*p != NUL) { - if (STRNCMP(p, "shift:", 6) == 0 - && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { - p += 6; - bri_shift = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { - p += 4; - bri_min = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "sbr", 3) == 0) { - p += 3; - bri_sbr = true; - } else if (STRNCMP(p, "list:", 5) == 0) { - p += 5; - bri_list = (int)getdigits(&p, false, 0); - } - if (*p != ',' && *p != NUL) { - return false; - } - if (*p == ',') { - p++; - } - } - - wp->w_briopt_shift = bri_shift; - wp->w_briopt_min = bri_min; - wp->w_briopt_sbr = bri_sbr; - wp->w_briopt_list = bri_list; - - return true; + return vim_strchr(p_bs, what) != NULL; } /// Get the local or global value of 'backupcopy'. @@ -7940,19 +5369,19 @@ char_u *get_showbreak_value(win_T *const win) FUNC_ATTR_WARN_UNUSED_RESULT { if (win->w_p_sbr == NULL || *win->w_p_sbr == NUL) { - return p_sbr; + return (char_u *)p_sbr; } if (STRCMP(win->w_p_sbr, "NONE") == 0) { - return empty_option; + return (char_u *)empty_option; } - return win->w_p_sbr; + return (char_u *)win->w_p_sbr; } /// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC. int get_fileformat(const buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int c = *buf->b_p_ff; + int c = (unsigned char)(*buf->b_p_ff); if (buf->b_p_bin || c == 'u') { return EOL_UNIX; @@ -7979,7 +5408,7 @@ int get_fileformat_force(const buf_T *buf, const exarg_T *eap) ? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) { return EOL_UNIX; } - c = *buf->b_p_ff; + c = (unsigned char)(*buf->b_p_ff); } if (c == 'u') { return EOL_UNIX; @@ -8036,7 +5465,7 @@ void set_fileformat(int eol_style, int opt_flags) } /// Skip to next part of an option argument: skip space and comma -char_u *skip_to_option_part(const char_u *p) +char *skip_to_option_part(const char *p) { if (*p == ',') { p++; @@ -8044,7 +5473,7 @@ char_u *skip_to_option_part(const char_u *p) while (*p == ' ') { p++; } - return (char_u *)p; + return (char *)p; } /// Isolate one part of a string option separated by `sep_chars`. @@ -8079,7 +5508,7 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars if (*p != NUL && *p != ',') { // skip non-standard separator p++; } - p = (char *)skip_to_option_part((char_u *)p); // p points to next file name + p = skip_to_option_part(p); // p points to next file name *option = p; return len; diff --git a/src/nvim/option.h b/src/nvim/option.h index a5a57cc66d..c65d2ee182 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -19,23 +19,6 @@ typedef enum { #define BCO_ALWAYS 2 // always copy the options #define BCO_NOHELP 4 // don't touch the help related options -/// Flags for option-setting functions -/// -/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global -/// values, get local value. -typedef enum { - OPT_FREE = 0x01, ///< Free old value if it was allocated. - OPT_GLOBAL = 0x02, ///< Use global value. - OPT_LOCAL = 0x04, ///< Use local value. - OPT_MODELINE = 0x08, ///< Option in modeline. - OPT_WINONLY = 0x10, ///< Only set window-local options. - OPT_NOWIN = 0x20, ///< Don’t set window-local options. - OPT_ONECOLUMN = 0x40, ///< list options one per line - OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option - OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' - OPT_CLEAR = 0x200, ///< Clear local value of an option. -} OptionFlags; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.h.generated.h" #endif diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 92ab7489bb..78cb324fe4 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -7,6 +7,69 @@ // option_defs.h: definition of global variables for settable options +// Flags +#define P_BOOL 0x01U ///< the option is boolean +#define P_NUM 0x02U ///< the option is numeric +#define P_STRING 0x04U ///< the option is a string +#define P_ALLOCED 0x08U ///< the string option is in allocated memory, + ///< must use free_string_option() when + ///< assigning new value. Not set if default is + ///< the same. +#define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can + ///< never be used for local or hidden options +#define P_NODEFAULT 0x40U ///< don't set to default value +#define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must + ///< use free() when assigning new value +#define P_WAS_SET 0x100U ///< option has been set/reset +#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output + +// when option changed, what to display: +#define P_RSTAT 0x1000U ///< redraw status lines +#define P_RWIN 0x2000U ///< redraw current window and recompute text +#define P_RBUF 0x4000U ///< redraw current buffer and recompute text +#define P_RALL 0x6000U ///< redraw all windows +#define P_RCLR 0x7000U ///< clear and redraw all + +#define P_COMMA 0x8000U ///< comma separated list +#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive + ///< commas +#define P_NODUP 0x20000U ///< don't allow duplicate strings +#define P_FLAGLIST 0x40000U ///< list of single-char flags + +#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode +#define P_GETTEXT 0x100000U ///< expand default value with _() +#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc +#define P_NFNAME 0x400000U ///< only normal file name chars allowed +#define P_INSECURE 0x800000U ///< option was set from a modeline +#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option + ///< has side effects) +#define P_NO_ML 0x2000000U ///< not allowed in modeline +#define P_CURSWANT 0x4000000U ///< update curswant required; not needed + ///< when there is a redraw flag +#define P_NDNAME 0x8000000U ///< only normal dir name chars allowed +#define P_RWINONLY 0x10000000U ///< only redraw current window +#define P_MLE 0x20000000U ///< under control of 'modelineexpr' + +#define P_NO_DEF_EXP 0x40000000U ///< Do not expand default value. +#define P_UI_OPTION 0x80000000U ///< send option to remote ui + +/// Flags for option-setting functions +/// +/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global +/// values, get local value. +typedef enum { + OPT_FREE = 0x01, ///< Free old value if it was allocated. + OPT_GLOBAL = 0x02, ///< Use global value. + OPT_LOCAL = 0x04, ///< Use local value. + OPT_MODELINE = 0x08, ///< Option in modeline. + OPT_WINONLY = 0x10, ///< Only set window-local options. + OPT_NOWIN = 0x20, ///< Don’t set window-local options. + OPT_ONECOLUMN = 0x40, ///< list options one per line + OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option + OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' + OPT_CLEAR = 0x200, ///< Clear local value of an option. +} OptionFlags; + // Return value from get_option_value_strict #define SOPT_BOOL 0x01 // Boolean option #define SOPT_NUM 0x02 // Number option @@ -21,6 +84,16 @@ #define SREQ_WIN 1 // Request window-local option value #define SREQ_BUF 2 // Request buffer-local option value +#define HIGHLIGHT_INIT \ + "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ + "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ + "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \ + "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ + "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ + "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ + "X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \ + "q:QuickFixLine,0:Whitespace,I:NormalNC" + // Default values for 'errorformat'. // The "%f|%l| %m" one is used for when the contents of the quickfix window is // written to a file. @@ -185,7 +258,7 @@ enum { SHM_SEARCHCOUNT = 'S', ///< Search sats: '[1/10]' }; /// Represented by 'a' flag. -#define SHM_ALL_ABBREVIATIONS ((char_u[]) { \ +#define SHM_ALL_ABBREVIATIONS ((char[]) { \ SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ 0, \ }) @@ -311,37 +384,39 @@ enum { */ EXTERN long p_aleph; // 'aleph' -EXTERN int p_acd; // 'autochdir' -EXTERN char_u *p_ambw; // 'ambiwidth' +EXTERN char *p_ambw; ///< 'ambiwidth' +EXTERN int p_acd; ///< 'autochdir' +EXTERN int p_ai; ///< 'autoindent' +EXTERN int p_bin; ///< 'binary' +EXTERN int p_bomb; ///< 'bomb' +EXTERN int p_bl; ///< 'buflisted' +EXTERN int p_cin; ///< 'cindent' +EXTERN long p_channel; ///< 'channel' +EXTERN char *p_cink; ///< 'cinkeys' +EXTERN char *p_cinsd; ///< 'cinscopedecls' +EXTERN char *p_cinw; ///< 'cinwords' +EXTERN char *p_cfu; ///< 'completefunc' +EXTERN char *p_ofu; ///< 'omnifunc' +EXTERN char *p_tsrfu; ///< 'thesaurusfunc' +EXTERN int p_ci; ///< 'copyindent' EXTERN int p_ar; // 'autoread' EXTERN int p_aw; // 'autowrite' EXTERN int p_awa; // 'autowriteall' -EXTERN char_u *p_bs; // 'backspace' -EXTERN char_u *p_bg; // 'background' +EXTERN char *p_bs; // 'backspace' +EXTERN char *p_bg; // 'background' EXTERN int p_bk; // 'backup' -EXTERN char_u *p_bkc; // 'backupcopy' +EXTERN char *p_bkc; // 'backupcopy' EXTERN unsigned int bkc_flags; ///< flags from 'backupcopy' -#ifdef IN_OPTION_C -static char *(p_bkc_values[]) = -{ "yes", "auto", "no", "breaksymlink", "breakhardlink", NULL }; -#endif #define BKC_YES 0x001 #define BKC_AUTO 0x002 #define BKC_NO 0x004 #define BKC_BREAKSYMLINK 0x008 #define BKC_BREAKHARDLINK 0x010 -EXTERN char_u *p_bdir; // 'backupdir' -EXTERN char_u *p_bex; // 'backupext' -EXTERN char_u *p_bo; // 'belloff' +EXTERN char *p_bdir; // 'backupdir' +EXTERN char *p_bex; // 'backupext' +EXTERN char *p_bo; // 'belloff' EXTERN char breakat_flags[256]; // which characters are in 'breakat' EXTERN unsigned bo_flags; -#ifdef IN_OPTION_C -static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", - "copy", "ctrlg", "error", "esc", "ex", - "hangul", "lang", "mess", "showmatch", - "operator", "register", "shell", "spell", - "wildmode", NULL }; -#endif // values for the 'belloff' option #define BO_ALL 0x0001 @@ -365,90 +440,88 @@ static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", #define BO_WILD 0x40000 EXTERN char_u *p_bsk; // 'backupskip' -EXTERN char_u *p_breakat; // 'breakat' -EXTERN char_u *p_cmp; // 'casemap' +EXTERN char *p_breakat; // 'breakat' +EXTERN char *p_bh; ///< 'bufhidden' +EXTERN char *p_bt; ///< 'buftype' +EXTERN char *p_cmp; // 'casemap' EXTERN unsigned cmp_flags; -#ifdef IN_OPTION_C -static char *(p_cmp_values[]) = { "internal", "keepascii", NULL }; -#endif #define CMP_INTERNAL 0x001 #define CMP_KEEPASCII 0x002 -EXTERN char_u *p_enc; // 'encoding' -EXTERN int p_deco; // 'delcombine' -EXTERN char_u *p_ccv; // 'charconvert' -EXTERN char_u *p_cedit; // 'cedit' -EXTERN char_u *p_cb; // 'clipboard' +EXTERN char *p_enc; // 'encoding' +EXTERN int p_deco; // 'delcombine' +EXTERN char *p_ccv; // 'charconvert' +EXTERN char *p_cino; ///< 'cinoptions' +EXTERN char *p_cedit; // 'cedit' +EXTERN char *p_cb; // 'clipboard' EXTERN unsigned cb_flags; -#ifdef IN_OPTION_C -static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; -#endif #define CB_UNNAMED 0x001 #define CB_UNNAMEDPLUS 0x002 #define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS) EXTERN long p_cwh; // 'cmdwinheight' EXTERN long p_ch; // 'cmdheight' +EXTERN char *p_cms; ///< 'commentstring' +EXTERN char *p_cpt; ///< 'complete' EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' -EXTERN char_u *p_cot; // 'completeopt' +EXTERN char *p_cot; // 'completeopt' #ifdef BACKSLASH_IN_FILENAME EXTERN char_u *p_csl; // 'completeslash' #endif EXTERN long p_pb; // 'pumblend' EXTERN long p_ph; // 'pumheight' EXTERN long p_pw; // 'pumwidth' +EXTERN char *p_com; ///< 'comments' EXTERN char *p_cpo; // 'cpoptions' -EXTERN char_u *p_csprg; // 'cscopeprg' +EXTERN char *p_csprg; // 'cscopeprg' EXTERN int p_csre; // 'cscoperelative' -EXTERN char_u *p_csqf; // 'cscopequickfix' +EXTERN char *p_csqf; // 'cscopequickfix' #define CSQF_CMDS "sgdctefia" #define CSQF_FLAGS "+-0" EXTERN int p_cst; // 'cscopetag' EXTERN long p_csto; // 'cscopetagorder' EXTERN long p_cspc; // 'cscopepathcomp' EXTERN int p_csverbose; // 'cscopeverbose' -EXTERN char_u *p_debug; // 'debug' -EXTERN char_u *p_def; // 'define' -EXTERN char_u *p_inc; -EXTERN char_u *p_dip; // 'diffopt' -EXTERN char_u *p_dex; // 'diffexpr' -EXTERN char_u *p_dict; // 'dictionary' +EXTERN char *p_debug; // 'debug' +EXTERN char *p_def; // 'define' +EXTERN char *p_inc; +EXTERN char *p_dip; // 'diffopt' +EXTERN char *p_dex; // 'diffexpr' +EXTERN char *p_dict; // 'dictionary' EXTERN int p_dg; // 'digraph' -EXTERN char_u *p_dir; // 'directory' -EXTERN char_u *p_dy; // 'display' +EXTERN char *p_dir; // 'directory' +EXTERN char *p_dy; // 'display' EXTERN unsigned dy_flags; -#ifdef IN_OPTION_C -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", - NULL }; -#endif #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 // code should use msg_use_msgsep() to check if msgsep is active #define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' +EXTERN char *p_ead; // 'eadirection' EXTERN int p_emoji; // 'emoji' -EXTERN char_u *p_ead; // 'eadirection' EXTERN int p_ea; // 'equalalways' -EXTERN char_u *p_ep; // 'equalprg' +EXTERN char_u *p_ep; // 'equalprg' EXTERN int p_eb; // 'errorbells' -EXTERN char_u *p_ef; // 'errorfile' -EXTERN char *p_efm; // 'errorformat' -EXTERN char *p_gefm; // 'grepformat' -EXTERN char_u *p_gp; // 'grepprg' -EXTERN char_u *p_ei; // 'eventignore' +EXTERN char_u *p_ef; // 'errorfile' +EXTERN char *p_efm; // 'errorformat' +EXTERN char *p_gefm; // 'grepformat' +EXTERN char_u *p_gp; // 'grepprg' +EXTERN int p_eol; ///< 'endofline' +EXTERN char *p_ei; // 'eventignore' +EXTERN int p_et; ///< 'expandtab' EXTERN int p_exrc; // 'exrc' -EXTERN char_u *p_fencs; // 'fileencodings' -EXTERN char *p_ffs; // 'fileformats' +EXTERN char *p_fenc; ///< 'fileencoding' +EXTERN char *p_fencs; // 'fileencodings' +EXTERN char *p_ff; ///< 'fileformat' +EXTERN char *p_ffs; // 'fileformats' EXTERN int p_fic; // 'fileignorecase' -EXTERN char_u *p_fcl; // 'foldclose' +EXTERN char *p_ft; ///< 'filetype' +EXTERN char *p_fcs; ///< 'fillchar' +EXTERN int p_fixeol; ///< 'fixendofline' +EXTERN char *p_fcl; // 'foldclose' EXTERN long p_fdls; // 'foldlevelstart' -EXTERN char_u *p_fdo; // 'foldopen' +EXTERN char *p_fdo; // 'foldopen' EXTERN unsigned fdo_flags; -#ifdef IN_OPTION_C -static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", - "quickfix", "search", "tag", "insert", - "undo", "jump", NULL }; -#endif #define FDO_ALL 0x001 #define FDO_BLOCK 0x002 #define FDO_HOR 0x004 @@ -460,66 +533,76 @@ static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", #define FDO_INSERT 0x100 #define FDO_UNDO 0x200 #define FDO_JUMP 0x400 -EXTERN char_u *p_fp; // 'formatprg' +EXTERN char *p_fex; ///< 'formatexpr' +EXTERN char *p_flp; ///< 'formatlistpat' +EXTERN char *p_fo; ///< 'formatoptions' +EXTERN char_u *p_fp; // 'formatprg' EXTERN int p_fs; // 'fsync' EXTERN int p_gd; // 'gdefault' -EXTERN char_u *p_pdev; // 'printdevice' -EXTERN char_u *p_penc; // 'printencoding' -EXTERN char_u *p_pexpr; // 'printexpr' -EXTERN char_u *p_pmfn; // 'printmbfont' -EXTERN char_u *p_pmcs; // 'printmbcharset' -EXTERN char_u *p_pfn; // 'printfont' -EXTERN char_u *p_popt; // 'printoptions' -EXTERN char_u *p_header; // 'printheader' -EXTERN char_u *p_guicursor; // 'guicursor' -EXTERN char_u *p_guifont; // 'guifont' -EXTERN char_u *p_guifontwide; // 'guifontwide' -EXTERN char_u *p_hf; // 'helpfile' +EXTERN char_u *p_pdev; // 'printdevice' +EXTERN char *p_penc; // 'printencoding' +EXTERN char *p_pexpr; // 'printexpr' +EXTERN char *p_pmfn; // 'printmbfont' +EXTERN char *p_pmcs; // 'printmbcharset' +EXTERN char *p_pfn; // 'printfont' +EXTERN char *p_popt; // 'printoptions' +EXTERN char_u *p_header; // 'printheader' +EXTERN char *p_guicursor; // 'guicursor' +EXTERN char_u *p_guifont; // 'guifont' +EXTERN char_u *p_guifontwide; // 'guifontwide' +EXTERN char *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' -EXTERN char_u *p_hlg; // 'helplang' +EXTERN char_u *p_hlg; // 'helplang' EXTERN int p_hid; // 'hidden' -EXTERN char_u *p_hl; // 'highlight' +EXTERN char *p_hl; // 'highlight' EXTERN int p_hls; // 'hlsearch' EXTERN long p_hi; // 'history' EXTERN int p_hkmap; // 'hkmap' EXTERN int p_hkmapp; // 'hkmapp' EXTERN int p_arshape; // 'arabicshape' EXTERN int p_icon; // 'icon' -EXTERN char_u *p_iconstring; // 'iconstring' +EXTERN char *p_iconstring; // 'iconstring' EXTERN int p_ic; // 'ignorecase' +EXTERN long p_iminsert; ///< 'iminsert' +EXTERN long p_imsearch; ///< 'imsearch' +EXTERN int p_inf; ///< 'infercase' +EXTERN char *p_inex; ///< 'includeexpr' EXTERN int p_is; // 'incsearch' -EXTERN char_u *p_icm; // 'inccommand' -EXTERN char_u *p_isf; // 'isfname' -EXTERN char_u *p_isi; // 'isident' -EXTERN char_u *p_isp; // 'isprint' +EXTERN char *p_inde; ///< 'indentexpr' +EXTERN char *p_indk; ///< 'indentkeys' +EXTERN char *p_icm; // 'inccommand' +EXTERN char *p_isf; // 'isfname' +EXTERN char *p_isi; // 'isident' +EXTERN char *p_isk; ///< 'iskeyword' +EXTERN char *p_isp; // 'isprint' EXTERN int p_js; // 'joinspaces' -EXTERN char_u *p_jop; // 'jumpooptions' +EXTERN char *p_jop; // 'jumpooptions' EXTERN unsigned jop_flags; -#ifdef IN_OPTION_C -static char *(p_jop_values[]) = { "stack", "view", NULL }; -#endif #define JOP_STACK 0x01 #define JOP_VIEW 0x02 -EXTERN char_u *p_kp; // 'keywordprg' -EXTERN char_u *p_km; // 'keymodel' -EXTERN char_u *p_langmap; // 'langmap' +EXTERN char *p_keymap; ///< 'keymap' +EXTERN char_u *p_kp; // 'keywordprg' +EXTERN char *p_km; // 'keymodel' +EXTERN char *p_langmap; // 'langmap' EXTERN int p_lnr; // 'langnoremap' EXTERN int p_lrm; // 'langremap' -EXTERN char_u *p_lm; // 'langmenu' -EXTERN long p_lines; // 'lines' -EXTERN long p_linespace; // 'linespace' -EXTERN char_u *p_lispwords; // 'lispwords' +EXTERN char_u *p_lm; // 'langmenu' +EXTERN long p_lines; // 'lines' +EXTERN long p_linespace; // 'linespace' +EXTERN int p_lisp; ///< 'lisp' +EXTERN char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' -EXTERN char_u *p_lcs; // 'listchars' +EXTERN char *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' -EXTERN char_u *p_menc; // 'makeencoding' -EXTERN char *p_mef; // 'makeef' -EXTERN char_u *p_mp; // 'makeprg' -EXTERN char_u *p_cc; // 'colorcolumn' +EXTERN char *p_menc; // 'makeencoding' +EXTERN char *p_mef; // 'makeef' +EXTERN char_u *p_mp; // 'makeprg' +EXTERN char *p_mps; ///< 'matchpairs' +EXTERN char_u *p_cc; // 'colorcolumn' EXTERN int p_cc_cols[256]; // array for 'colorcolumn' columns EXTERN long p_mat; // 'matchtime' EXTERN long p_mco; // 'maxcombine' @@ -527,72 +610,62 @@ EXTERN long p_mfd; // 'maxfuncdepth' EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' -EXTERN char_u *p_msm; // 'mkspellmem' -EXTERN int p_mle; // 'modelineexpr' +EXTERN char *p_msm; // 'mkspellmem' +EXTERN int p_ml; ///< 'modeline' +EXTERN int p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' -EXTERN char_u *p_mouse; // 'mouse' -EXTERN char_u *p_mousem; // 'mousemodel' -EXTERN int p_mousef; // 'mousefocus' -EXTERN char_u *p_mousescroll; // 'mousescroll' +EXTERN int p_ma; ///< 'modifiable' +EXTERN int p_mod; ///< 'modified' +EXTERN char *p_mouse; // 'mouse' +EXTERN char *p_mousem; // 'mousemodel' +EXTERN int p_mousef; // 'mousefocus' +EXTERN char *p_mousescroll; // 'mousescroll' EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT); EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT); EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' -EXTERN char_u *p_opfunc; // 'operatorfunc' -EXTERN char_u *p_para; // 'paragraphs' +EXTERN char *p_nf; ///< 'nrformats' +EXTERN char *p_opfunc; // 'operatorfunc' +EXTERN char_u *p_para; // 'paragraphs' EXTERN int p_paste; // 'paste' -EXTERN char_u *p_pt; // 'pastetoggle' -EXTERN char_u *p_pex; // 'patchexpr' -EXTERN char_u *p_pm; // 'patchmode' -EXTERN char_u *p_path; // 'path' -EXTERN char_u *p_cdpath; // 'cdpath' +EXTERN char *p_pt; // 'pastetoggle' +EXTERN char_u *p_pex; // 'patchexpr' +EXTERN char *p_pm; // 'patchmode' +EXTERN char_u *p_path; // 'path' +EXTERN char_u *p_cdpath; // 'cdpath' +EXTERN int p_pi; ///< 'preserveindent' EXTERN long p_pyx; // 'pyxversion' -EXTERN char_u *p_rdb; // 'redrawdebug' +EXTERN char *p_qe; ///< 'quoteescape' +EXTERN int p_ro; ///< 'readonly' +EXTERN char *p_rdb; // 'redrawdebug' EXTERN unsigned rdb_flags; -#ifdef IN_OPTION_C -static char *(p_rdb_values[]) = { - "compositor", - "nothrottle", - "invalid", - "nodelta", - NULL -}; -#endif #define RDB_COMPOSITOR 0x001 #define RDB_NOTHROTTLE 0x002 #define RDB_INVALID 0x004 #define RDB_NODELTA 0x008 -EXTERN long p_rdt; // 'redrawtime' -EXTERN long p_re; // 'regexpengine' -EXTERN long p_report; // 'report' -EXTERN long p_pvh; // 'previewheight' -EXTERN int p_ari; // 'allowrevins' -EXTERN int p_ri; // 'revins' -EXTERN int p_ru; // 'ruler' -EXTERN char_u *p_ruf; // 'rulerformat' -EXTERN char_u *p_pp; // 'packpath' -EXTERN char_u *p_qftf; // 'quickfixtextfunc' -EXTERN char_u *p_rtp; // 'runtimepath' -EXTERN long p_scbk; // 'scrollback' -EXTERN long p_sj; // 'scrolljump' -EXTERN long p_so; // 'scrolloff' -EXTERN char *p_sbo; // 'scrollopt' -EXTERN char_u *p_sections; // 'sections' -EXTERN int p_secure; // 'secure' -EXTERN char_u *p_sel; // 'selection' -EXTERN char_u *p_slm; // 'selectmode' -EXTERN char_u *p_ssop; // 'sessionoptions' +EXTERN long p_rdt; // 'redrawtime' +EXTERN long p_re; // 'regexpengine' +EXTERN long p_report; // 'report' +EXTERN long p_pvh; // 'previewheight' +EXTERN int p_ari; // 'allowrevins' +EXTERN int p_ri; // 'revins' +EXTERN int p_ru; // 'ruler' +EXTERN char *p_ruf; // 'rulerformat' +EXTERN char *p_pp; // 'packpath' +EXTERN char *p_qftf; // 'quickfixtextfunc' +EXTERN char *p_rtp; // 'runtimepath' +EXTERN long p_scbk; // 'scrollback' +EXTERN long p_sj; // 'scrolljump' +EXTERN long p_so; // 'scrolloff' +EXTERN char *p_sbo; // 'scrollopt' +EXTERN char *p_sections; // 'sections' +EXTERN int p_secure; // 'secure' +EXTERN char *p_sel; // 'selection' +EXTERN char *p_slm; // 'selectmode' +EXTERN char *p_ssop; // 'sessionoptions' EXTERN unsigned ssop_flags; -#ifdef IN_OPTION_C -// Also used for 'viewoptions'! Keep in sync with SSOP_ flags. -static char *(p_ssop_values[]) = { - "buffers", "winpos", "resize", "winsize", - "localoptions", "options", "help", "blank", "globals", "slash", "unix", - "sesdir", "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp", - NULL -}; -#endif + #define SSOP_BUFFERS 0x001 #define SSOP_WINPOS 0x002 #define SSOP_RESIZE 0x004 @@ -612,22 +685,23 @@ static char *(p_ssop_values[]) = { #define SSOP_TERMINAL 0x10000 #define SSOP_SKIP_RTP 0x20000 -EXTERN char_u *p_sh; // 'shell' -EXTERN char_u *p_shcf; // 'shellcmdflag' -EXTERN char_u *p_sp; // 'shellpipe' -EXTERN char_u *p_shq; // 'shellquote' -EXTERN char_u *p_sxq; // 'shellxquote' -EXTERN char_u *p_sxe; // 'shellxescape' -EXTERN char_u *p_srr; // 'shellredir' +EXTERN char_u *p_sh; // 'shell' +EXTERN char_u *p_shcf; // 'shellcmdflag' +EXTERN char *p_sp; // 'shellpipe' +EXTERN char_u *p_shq; // 'shellquote' +EXTERN char_u *p_sxq; // 'shellxquote' +EXTERN char_u *p_sxe; // 'shellxescape' +EXTERN char *p_srr; // 'shellredir' EXTERN int p_stmp; // 'shelltemp' #ifdef BACKSLASH_IN_FILENAME EXTERN int p_ssl; // 'shellslash' #endif -EXTERN char_u *p_stl; // 'statusline' -EXTERN char *p_wbr; // 'winbar' +EXTERN char *p_stl; // 'statusline' +EXTERN char *p_wbr; // 'winbar' EXTERN int p_sr; // 'shiftround' -EXTERN char_u *p_shm; // 'shortmess' -EXTERN char_u *p_sbr; // 'showbreak' +EXTERN long p_sw; ///< 'shiftwidth' +EXTERN char *p_shm; // 'shortmess' +EXTERN char *p_sbr; // 'showbreak' EXTERN int p_sc; // 'showcmd' EXTERN int p_sft; // 'showfulltag' EXTERN int p_sm; // 'showmatch' @@ -635,16 +709,17 @@ EXTERN int p_smd; // 'showmode' EXTERN long p_ss; // 'sidescroll' EXTERN long p_siso; // 'sidescrolloff' EXTERN int p_scs; // 'smartcase' +EXTERN int p_si; ///< 'smartindent' EXTERN int p_sta; // 'smarttab' +EXTERN long p_sts; ///< 'softtabstop' EXTERN int p_sb; // 'splitbelow' +EXTERN char *p_sua; ///< 'suffixesadd' +EXTERN int p_swf; ///< 'swapfile' +EXTERN long p_smc; ///< 'synmaxcol' EXTERN long p_tpm; // 'tabpagemax' -EXTERN char_u *p_tal; // 'tabline' -EXTERN char_u *p_tpf; // 'termpastefilter' +EXTERN char *p_tal; // 'tabline' +EXTERN char *p_tpf; // 'termpastefilter' EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter' -#ifdef IN_OPTION_C -static char *(p_tpf_values[]) = -{ "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; -#endif #define TPF_BS 0x001 #define TPF_HT 0x002 #define TPF_FF 0x004 @@ -652,29 +727,30 @@ static char *(p_tpf_values[]) = #define TPF_DEL 0x010 #define TPF_C0 0x020 #define TPF_C1 0x040 -EXTERN char_u *p_sps; // 'spellsuggest' +EXTERN char *p_tfu; ///< 'tagfunc' +EXTERN char *p_umf; ///< 'usermarkfunc' +EXTERN char *p_spc; ///< 'spellcapcheck' +EXTERN char *p_spf; ///< 'spellfile' +EXTERN char *p_spl; ///< 'spelllang' +EXTERN char *p_spo; // 'spelloptions' +EXTERN char *p_sps; // 'spellsuggest' EXTERN int p_spr; // 'splitright' EXTERN int p_sol; // 'startofline' -EXTERN char_u *p_su; // 'suffixes' -EXTERN char_u *p_swb; // 'switchbuf' +EXTERN char_u *p_su; // 'suffixes' +EXTERN char *p_swb; // 'switchbuf' EXTERN unsigned swb_flags; -#ifdef IN_OPTION_C -static char *(p_swb_values[]) = -{ "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL }; -#endif +// Keep in sync with p_swb_values in optionstr.c #define SWB_USEOPEN 0x001 #define SWB_USETAB 0x002 #define SWB_SPLIT 0x004 #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 #define SWB_USELAST 0x020 +EXTERN char *p_syn; ///< 'syntax' +EXTERN long p_ts; ///< 'tabstop' EXTERN int p_tbs; ///< 'tagbsearch' -EXTERN char_u *p_tc; ///< 'tagcase' +EXTERN char *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' -#ifdef IN_OPTION_C -static char *(p_tc_values[]) = -{ "followic", "ignore", "match", "followscs", "smart", NULL }; -#endif #define TC_FOLLOWIC 0x01 #define TC_IGNORE 0x02 #define TC_MATCH 0x04 @@ -685,35 +761,34 @@ EXTERN int p_tr; ///< 'tagrelative' EXTERN char_u *p_tags; ///< 'tags' EXTERN int p_tgst; ///< 'tagstack' EXTERN int p_tbidi; ///< 'termbidi' +EXTERN long p_tw; ///< 'textwidth' EXTERN int p_to; ///< 'tildeop' EXTERN int p_timeout; ///< 'timeout' EXTERN long p_tm; ///< 'timeoutlen' EXTERN int p_title; ///< 'title' EXTERN long p_titlelen; ///< 'titlelen' EXTERN char_u *p_titleold; ///< 'titleold' -EXTERN char_u *p_titlestring; ///< 'titlestring' +EXTERN char *p_titlestring; ///< 'titlestring' EXTERN char_u *p_tsr; ///< 'thesaurus' -EXTERN char_u *p_tsrfu; ///< 'thesaurusfunc' EXTERN int p_tgc; ///< 'termguicolors' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' EXTERN char_u *p_udir; ///< 'undodir' +EXTERN int p_udf; ///< 'undofile' EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' -EXTERN char_u *p_fcs; ///< 'fillchar' -EXTERN char_u *p_shada; ///< 'shada' +EXTERN char *p_shada; ///< 'shada' EXTERN char *p_shadafile; ///< 'shadafile' +EXTERN char *p_vsts; ///< 'varsofttabstop' +EXTERN char *p_vts; ///< 'vartabstop' EXTERN char_u *p_vdir; ///< 'viewdir' -EXTERN char_u *p_vop; ///< 'viewoptions' +EXTERN char *p_vop; ///< 'viewoptions' EXTERN unsigned vop_flags; ///< uses SSOP_ flags EXTERN int p_vb; ///< 'visualbell' -EXTERN char_u *p_ve; ///< 'virtualedit' +EXTERN char *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; -#ifdef IN_OPTION_C -static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; -#endif #define VE_BLOCK 5U // includes "all" #define VE_INSERT 6U // includes "all" #define VE_ALL 4U @@ -724,29 +799,27 @@ EXTERN long p_verbose; // 'verbose' #ifdef IN_OPTION_C char_u *p_vfile = (char_u *)""; // used before options are initialized #else -extern char_u *p_vfile; // 'verbosefile' +extern char *p_vfile; // 'verbosefile' #endif EXTERN int p_warn; // 'warn' -EXTERN char_u *p_wop; // 'wildoptions' +EXTERN char *p_wop; // 'wildoptions' EXTERN unsigned wop_flags; -#ifdef IN_OPTION_C -static char *(p_wop_values[]) = { "tagfile", "pum", NULL }; -#endif #define WOP_TAGFILE 0x01 #define WOP_PUM 0x02 EXTERN long p_window; // 'window' -EXTERN char_u *p_wak; // 'winaltkeys' -EXTERN char_u *p_wig; // 'wildignore' -EXTERN char_u *p_ww; // 'whichwrap' +EXTERN char *p_wak; // 'winaltkeys' +EXTERN char *p_wig; // 'wildignore' +EXTERN char *p_ww; // 'whichwrap' EXTERN long p_wc; // 'wildchar' EXTERN long p_wcm; // 'wildcharm' EXTERN int p_wic; // 'wildignorecase' -EXTERN char_u *p_wim; // 'wildmode' +EXTERN char *p_wim; // 'wildmode' EXTERN int p_wmnu; // 'wildmenu' EXTERN long p_wh; // 'winheight' EXTERN long p_wmh; // 'winminheight' EXTERN long p_wmw; // 'winminwidth' EXTERN long p_wiw; // 'winwidth' +EXTERN long p_wm; ///< 'wrapmargin' EXTERN int p_ws; // 'wrapscan' EXTERN int p_write; // 'write' EXTERN int p_wa; // 'writeany' @@ -901,8 +974,8 @@ enum { WV_WRAP, WV_SCL, WV_WINHL, - WV_FCS, WV_LCS, + WV_FCS, WV_WINBL, WV_WBR, WV_COUNT, // must be the last one diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c new file mode 100644 index 0000000000..1bdfcbecb9 --- /dev/null +++ b/src/nvim/optionstr.c @@ -0,0 +1,1663 @@ +// 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 + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/cursor_shape.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_getln.h" +#include "nvim/hardcopy.h" +#include "nvim/highlight_group.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/mapping.h" +#include "nvim/memline.h" +#include "nvim/mouse.h" +#include "nvim/move.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/quickfix.h" +#include "nvim/runtime.h" +#include "nvim/spell.h" +#include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" +#include "nvim/strings.h" +#include "nvim/ui.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "optionstr.c.generated.h" +#endif + +static char e_unclosed_expression_sequence[] + = N_("E540: Unclosed expression sequence"); +static char e_unbalanced_groups[] + = N_("E542: unbalanced groups"); +static char e_backupext_and_patchmode_are_equal[] + = N_("E589: 'backupext' and 'patchmode' are equal"); +static char e_showbreak_contains_unprintable_or_wide_character[] + = N_("E595: 'showbreak' contains unprintable or wide character"); + +static char *(p_ambw_values[]) = { "single", "double", NULL }; +static char *(p_bg_values[]) = { "light", "dark", NULL }; +static char *(p_bkc_values[]) = { "yes", "auto", "no", "breaksymlink", "breakhardlink", NULL }; +static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", "copy", "ctrlg", "error", + "esc", "ex", "hangul", "lang", "mess", "showmatch", "operator", + "register", "shell", "spell", "wildmode", NULL }; +static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", NULL }; +static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL }; +static char *(p_cmp_values[]) = { "internal", "keepascii", NULL }; +static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", NULL }; +static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", "quickfix", "search", + "tag", "insert", "undo", "jump", NULL }; +/// Also used for 'viewoptions'! Keep in sync with SSOP_ flags. +static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "localoptions", + "options", "help", "blank", "globals", "slash", "unix", "sesdir", + "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp", + NULL }; +// Keep in sync with SWB_ flags in option_defs.h +static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", + NULL }; +static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL }; +static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; +static char *(p_wop_values[]) = { "tagfile", "pum", NULL }; +static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; +static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL }; +static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL }; +static char *(p_slm_values[]) = { "mouse", "key", "cmd", NULL }; +static char *(p_km_values[]) = { "startsel", "stopsel", NULL }; +static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL }; +static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL }; +static char *(p_ead_values[]) = { "both", "ver", "hor", NULL }; +static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", "help", "acwrite", + "terminal", "prompt", NULL }; +static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", "wipe", NULL }; +static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL }; +static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", + "syntax", "diff", NULL }; +static char *(p_fcl_values[]) = { "all", NULL }; +static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "noinsert", "noselect", + NULL }; +#ifdef BACKSLASH_IN_FILENAME +static char *(p_csl_values[]) = { "slash", "backslash", NULL }; +#endif + +static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", + "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", + "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", + "yes:9", "number", NULL }; +static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", + "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9", NULL }; +static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; +static char *(p_icm_values[]) = { "nosplit", "split", NULL }; +static char *(p_jop_values[]) = { "stack", "view", NULL }; +static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; +static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", NULL }; + +/// All possible flags for 'shm'. +static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, + SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, + SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, + SHM_COMPLETIONMENU, SHM_RECORDING, SHM_FILEINFO, SHM_SEARCHCOUNT, 0, }; + +/// After setting various option values: recompute variables that depend on +/// option values. +void didset_string_options(void) +{ + (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); + (void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true); + (void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, true); + (void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true); + (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); + (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); + (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); + (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); + (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); + (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); + (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); + (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); + (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); + (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); +} + +/// Trigger the OptionSet autocommand. +/// "opt_idx" is the index of the option being set. +/// "opt_flags" can be OPT_LOCAL etc. +/// "oldval" the old value +/// "oldval_l" the old local value (only non-NULL if global and local value are set) +/// "oldval_g" the old global value (only non-NULL if global and local value are set) +/// "newval" the new value +void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, + char *oldval_g, char *newval) +{ + // Don't do this recursively. + if (oldval != NULL + && newval != NULL + && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { + char buf_type[7]; + + vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", + (opt_flags & OPT_LOCAL) ? "local" : "global"); + set_vim_var_string(VV_OPTION_OLD, oldval, -1); + set_vim_var_string(VV_OPTION_NEW, newval, -1); + set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + apply_autocmds(EVENT_OPTIONSET, get_option_fullname(opt_idx), NULL, false, NULL); + reset_v_option_vars(); + } +} + +static char *illegal_char(char *errbuf, size_t errbuflen, int c) +{ + if (errbuf == NULL) { + return ""; + } + vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), + (char *)transchar(c)); + return errbuf; +} + +/// Check string options in a buffer for NULL value. +void check_buf_options(buf_T *buf) +{ + check_string_option(&buf->b_p_bh); + check_string_option(&buf->b_p_bt); + check_string_option(&buf->b_p_fenc); + check_string_option(&buf->b_p_ff); + check_string_option(&buf->b_p_def); + check_string_option(&buf->b_p_inc); + check_string_option(&buf->b_p_inex); + check_string_option(&buf->b_p_inde); + check_string_option(&buf->b_p_indk); + check_string_option(&buf->b_p_fp); + check_string_option(&buf->b_p_fex); + check_string_option(&buf->b_p_kp); + check_string_option(&buf->b_p_mps); + check_string_option(&buf->b_p_fo); + check_string_option(&buf->b_p_flp); + check_string_option(&buf->b_p_isk); + check_string_option(&buf->b_p_com); + check_string_option(&buf->b_p_cms); + check_string_option(&buf->b_p_nf); + check_string_option(&buf->b_p_qe); + check_string_option(&buf->b_p_syn); + check_string_option(&buf->b_s.b_syn_isk); + check_string_option(&buf->b_s.b_p_spc); + check_string_option(&buf->b_s.b_p_spf); + check_string_option(&buf->b_s.b_p_spl); + check_string_option(&buf->b_s.b_p_spo); + check_string_option(&buf->b_p_sua); + check_string_option(&buf->b_p_cink); + check_string_option(&buf->b_p_cino); + parse_cino(buf); + check_string_option(&buf->b_p_ft); + check_string_option(&buf->b_p_cinw); + check_string_option(&buf->b_p_cinsd); + 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_keymap); + check_string_option(&buf->b_p_gp); + check_string_option(&buf->b_p_mp); + check_string_option(&buf->b_p_efm); + check_string_option(&buf->b_p_ep); + check_string_option(&buf->b_p_path); + check_string_option(&buf->b_p_tags); + check_string_option(&buf->b_p_tfu); + check_string_option(&buf->b_p_tc); + check_string_option(&buf->b_p_dict); + check_string_option(&buf->b_p_tsr); + check_string_option(&buf->b_p_tsrfu); + check_string_option(&buf->b_p_lw); + check_string_option(&buf->b_p_bkc); + check_string_option(&buf->b_p_menc); + check_string_option(&buf->b_p_vsts); + check_string_option(&buf->b_p_vts); +} + +/// Free the string allocated for an option. +/// Checks for the string being empty_option. This may happen if we're out of +/// memory, vim_strsave() returned NULL, which was replaced by empty_option by +/// check_options(). +/// Does NOT check for P_ALLOCED flag! +void free_string_option(char *p) +{ + if (p != empty_option) { + xfree(p); + } +} + +void clear_string_option(char **pp) +{ + if (*pp != empty_option) { + xfree(*pp); + } + *pp = empty_option; +} + +void check_string_option(char **pp) +{ + if (*pp == NULL) { + *pp = empty_option; + } +} + +/// Set global value for string option when it's a local option. +/// +/// @param opt_idx option index +/// @param varp pointer to option variable +static void set_string_option_global(int opt_idx, char **varp) +{ + char **p; + + // the global value is always allocated + if (is_window_local_option(opt_idx)) { + p = (char **)GLOBAL_WO(varp); + } else { + p = (char **)get_option_var(opt_idx); + } + if (!is_global_option(opt_idx) && p != varp) { + char *s = xstrdup(*varp); + free_string_option(*p); + *p = s; + } +} + +/// Set a string option to a new value (without checking the effect). +/// The string is copied into allocated memory. +/// if ("opt_idx" == -1) "name" is used, otherwise "opt_idx" is used. +/// When "set_sid" is zero set the scriptID to current_sctx.sc_sid. When +/// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to +/// "set_sid". +/// +/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL +void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags, + int set_sid) +{ + char *s; + char **varp; + int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; + int idx = opt_idx; + + if (idx == -1) { // Use name. + idx = findoption(name); + if (idx < 0) { // Not found (should not happen). + internal_error("set_string_option_direct()"); + siemsg(_("For option %s"), name); + return; + } + } + + if (is_hidden_option(idx)) { // can't set hidden option + return; + } + + assert((void *)get_option_var(idx) != (void *)&p_shada); + + s = xstrdup(val); + { + varp = (char **)get_option_varp_scope(idx, both ? OPT_LOCAL : opt_flags); + if ((opt_flags & OPT_FREE) && (get_option_flags(idx) & P_ALLOCED)) { + free_string_option(*varp); + } + *varp = s; + + // For buffer/window local option may also set the global value. + if (both) { + set_string_option_global(idx, varp); + } + + set_option_flag(idx, P_ALLOCED); + + // When setting both values of a global option with a local value, + // make the local value empty, so that the global value is used. + if (is_global_local_option(idx) && both) { + free_string_option(*varp); + *varp = empty_option; + } + if (set_sid != SID_NONE) { + sctx_T script_ctx; + + if (set_sid == 0) { + script_ctx = current_sctx; + } else { + script_ctx.sc_sid = set_sid; + script_ctx.sc_seq = 0; + script_ctx.sc_lnum = 0; + } + set_option_sctx_idx(idx, opt_flags, script_ctx); + } + } +} + +/// Like set_string_option_direct(), but for a window-local option in "wp". +/// Blocks autocommands to avoid the old curwin becoming invalid. +void set_string_option_direct_in_win(win_T *wp, const char *name, int opt_idx, const char *val, + int opt_flags, int set_sid) +{ + win_T *save_curwin = curwin; + + block_autocmds(); + curwin = wp; + curbuf = curwin->w_buffer; + set_string_option_direct(name, opt_idx, val, opt_flags, set_sid); + curwin = save_curwin; + curbuf = curwin->w_buffer; + unblock_autocmds(); +} + +/// Set a string option to a new value, handling the effects +/// +/// @param[in] opt_idx Option to set. +/// @param[in] value New value. +/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or +/// #OPT_GLOBAL. +/// +/// @return NULL on success, an untranslated error message on error. +char *set_string_option(const int opt_idx, const char *const value, const int opt_flags) + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (is_hidden_option(opt_idx)) { // don't set hidden option + return NULL; + } + + char *const s = xstrdup(value); + char **const varp + = (char **)get_option_varp_scope(opt_idx, + (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 + ? (is_global_local_option(opt_idx) + ? OPT_GLOBAL : OPT_LOCAL) + : opt_flags); + char *const oldval = *varp; + char *oldval_l = NULL; + char *oldval_g = NULL; + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + oldval_l = *(char **)get_option_varp_scope(opt_idx, OPT_LOCAL); + oldval_g = *(char **)get_option_varp_scope(opt_idx, OPT_GLOBAL); + } + + *varp = s; + + char *const saved_oldval = xstrdup(oldval); + char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; + char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; + char *const saved_newval = xstrdup(s); + + int value_checked = false; + char *const errmsg = did_set_string_option(opt_idx, varp, oldval, + NULL, 0, + opt_flags, &value_checked); + if (errmsg == NULL) { + did_set_option(opt_idx, opt_flags, true, value_checked); + } + + // call autocommand after handling side effects + if (errmsg == NULL) { + if (!starting) { + trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, + saved_newval); + } + if (get_option_flags(opt_idx) & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(get_option_fullname(opt_idx)), + STRING_OBJ(cstr_as_string(saved_newval))); + } + } + xfree(saved_oldval); + xfree(saved_oldval_l); + xfree(saved_oldval_g); + xfree(saved_newval); + + return errmsg; +} + +/// Return true if "val" is a valid 'filetype' name. +/// Also used for 'syntax' and 'keymap'. +static bool valid_filetype(const char *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_"); +} + +/// Handle setting 'mousescroll'. +/// @return error message, NULL if it's OK. +static char *check_mousescroll(char *string) +{ + long vertical = -1; + long horizontal = -1; + + for (;;) { + char *end = vim_strchr(string, ','); + size_t length = end ? (size_t)(end - string) : STRLEN(string); + + // Both "ver:" and "hor:" are 4 bytes long. + // They should be followed by at least one digit. + if (length <= 4) { + return e_invarg; + } + + long *direction; + + if (memcmp(string, "ver:", 4) == 0) { + direction = &vertical; + } else if (memcmp(string, "hor:", 4) == 0) { + direction = &horizontal; + } else { + return e_invarg; + } + + // If the direction has already been set, this is a duplicate. + if (*direction != -1) { + return e_invarg; + } + + // Verify that only digits follow the colon. + for (size_t i = 4; i < length; i++) { + if (!ascii_isdigit(string[i])) { + return N_("E548: digit expected"); + } + } + + string += 4; + *direction = getdigits_int(&string, false, -1); + + // Num options are generally kept within the signed int range. + // We know this number won't be negative because we've already checked for + // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. + if (*direction == -1) { + return e_invarg; + } + + if (!end) { + break; + } + + string = end + 1; + } + + // If a direction wasn't set, fallback to the default value. + p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; + p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; + + return NULL; +} + +/// Handle setting 'signcolumn' for value 'val' +/// +/// @return OK when the value is valid, FAIL otherwise +static int check_signcolumn(char *val) +{ + if (*val == NUL) { + return FAIL; + } + // check for basic match + if (check_opt_strings(val, p_scl_values, false) == OK) { + return OK; + } + + // check for 'auto:<NUMBER>-<NUMBER>' + if (STRLEN(val) == 8 + && !STRNCMP(val, "auto:", 5) + && ascii_isdigit(val[5]) + && val[6] == '-' + && ascii_isdigit(val[7])) { + int min = val[5] - '0'; + int max = val[7] - '0'; + if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) { + return FAIL; + } + return OK; + } + + return FAIL; +} + +/// Check validity of options with the 'statusline' format. +/// Return an untranslated error message or NULL. +char *check_stl_option(char *s) +{ + int groupdepth = 0; + static char errbuf[80]; + + while (*s) { + // Check for valid keys after % sequences + while (*s && *s != '%') { + s++; + } + if (!*s) { + break; + } + s++; + if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) { + s++; + continue; + } + if (*s == ')') { + s++; + if (--groupdepth < 0) { + break; + } + continue; + } + if (*s == '-') { + s++; + } + while (ascii_isdigit(*s)) { + s++; + } + if (*s == STL_USER_HL) { + continue; + } + if (*s == '.') { + s++; + while (*s && ascii_isdigit(*s)) { + s++; + } + } + if (*s == '(') { + groupdepth++; + continue; + } + if (vim_strchr(STL_ALL, *s) == NULL) { + return illegal_char(errbuf, sizeof(errbuf), *s); + } + if (*s == '{') { + bool reevaluate = (*++s == '%'); + + if (reevaluate && *++s == '}') { + // "}" is not allowed immediately after "%{%" + return illegal_char(errbuf, sizeof(errbuf), '}'); + } + while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) { + s++; + } + if (*s != '}') { + return e_unclosed_expression_sequence; + } + } + } + if (groupdepth != 0) { + return e_unbalanced_groups; + } + return NULL; +} + +static int shada_idx = -1; + +/// Handle string options that need some action to perform when changed. +/// The new value must be allocated. +/// +/// @param opt_idx index in options[] table +/// @param varp pointer to the option variable +/// @param oldval previous value of the option +/// @param errbuf buffer for errors, or NULL +/// @param errbuflen length of errors buffer +/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @param value_checked value was checked to be safe, no need to set P_INSECURE +/// +/// @return NULL for success, or an untranslated error message for an error +char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen, + int opt_flags, int *value_checked) +{ + char *errmsg = NULL; + char *s, *p; + int did_chartab = false; + bool free_oldval = (get_option_flags(opt_idx) & P_ALLOCED); + bool value_changed = false; + + // Get the global option to compare with, otherwise we would have to check + // two values for all local options. + char **gvarp = (char **)get_option_varp_scope(opt_idx, OPT_GLOBAL); + + // Disallow changing some options from secure mode + if ((secure || sandbox != 0) + && (get_option_flags(opt_idx) & P_SECURE)) { + errmsg = e_secure; + } else if (((get_option_flags(opt_idx) & P_NFNAME) + && strpbrk(*varp, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) + || ((get_option_flags(opt_idx) & P_NDNAME) + && strpbrk(*varp, "*?[|;&<>\r\n") != NULL)) { + // Check for a "normal" directory or file name in some options. Disallow a + // path separator (slash and/or backslash), wildcards and characters that + // are often illegal in a file name. Be more permissive if "secure" is off. + errmsg = e_invarg; + } else if (gvarp == &p_bkc) { // 'backupcopy' + char *bkc = p_bkc; + unsigned int *flags = &bkc_flags; + + if (opt_flags & OPT_LOCAL) { + bkc = curbuf->b_p_bkc; + flags = &curbuf->b_bkc_flags; + } + + if ((opt_flags & OPT_LOCAL) && *bkc == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { + errmsg = e_invarg; + } + + if (((*flags & BKC_AUTO) != 0) + + ((*flags & BKC_YES) != 0) + + ((*flags & BKC_NO) != 0) != 1) { + // Must have exactly one of "auto", "yes" and "no". + (void)opt_strings_flags(oldval, p_bkc_values, flags, true); + errmsg = e_invarg; + } + } + } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode' + if (STRCMP(*p_bex == '.' ? p_bex + 1 : p_bex, + *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { + errmsg = e_backupext_and_patchmode_are_equal; + } + } else if (varp == &curwin->w_p_briopt) { // 'breakindentopt' + if (briopt_check(curwin) == FAIL) { + errmsg = e_invarg; + } + } else if (varp == &p_isi + || varp == &(curbuf->b_p_isk) + || varp == &p_isp + || varp == &p_isf) { + // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] + // If the new option is invalid, use old value. 'lisp' option: refill + // g_chartab[] for '-' char + if (init_chartab() == FAIL) { + did_chartab = true; // need to restore it below + errmsg = e_invarg; // error in value + } + } else if (varp == &p_hf) { // 'helpfile' + // May compute new values for $VIM and $VIMRUNTIME + if (didset_vim) { + vim_unsetenv_ext("VIM"); + } + if (didset_vimruntime) { + vim_unsetenv_ext("VIMRUNTIME"); + } + } else if (varp == &p_rtp || varp == &p_pp) { // 'runtimepath' 'packpath' + runtime_search_path_invalidate(); + } else if (varp == &curwin->w_p_culopt + || gvarp == &curwin->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' + if (**varp == NUL || fill_culopt_flags(*varp, curwin) != OK) { + errmsg = e_invarg; + } + } else if (varp == &curwin->w_p_cc) { // 'colorcolumn' + errmsg = check_colorcolumn(curwin); + } else if (varp == (char **)&p_hlg) { // 'helplang' + // Check for "", "ab", "ab,cd", etc. + 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; + } + if (s[2] == NUL) { + break; + } + } + } else if (varp == &p_hl) { // 'highlight' + if (STRCMP(*varp, HIGHLIGHT_INIT) != 0) { + errmsg = e_unsupportedoption; + } + } else if (varp == &p_jop) { // 'jumpoptions' + if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_nf) { // 'nrformats' + if (check_opt_strings(*varp, p_nf_values, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_ssop) { // 'sessionoptions' + if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { + errmsg = e_invarg; + } + if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { + // Don't allow both "sesdir" and "curdir". + (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); + errmsg = e_invarg; + } + } else if (varp == &p_vop) { // 'viewoptions' + if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_rdb) { // 'redrawdebug' + if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_sbo) { // 'scrollopt' + if (check_opt_strings(p_sbo, p_scbopt_values, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_ambw || (int *)varp == &p_emoji) { // 'ambiwidth' + if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { + errmsg = e_invarg; + } else { + errmsg = check_chars_options(); + } + } else if (varp == &p_bg) { // 'background' + if (check_opt_strings(p_bg, p_bg_values, false) == OK) { + int dark = (*p_bg == 'd'); + + init_highlight(false, false); + + if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { + // The color scheme must have set 'background' back to another + // value, that's not what we want here. Disable the color + // scheme and set the colors again. + do_unlet(S_LEN("g:colors_name"), true); + free_string_option(p_bg); + p_bg = xstrdup((dark ? "dark" : "light")); + check_string_option(&p_bg); + init_highlight(false, false); + } + } else { + errmsg = e_invarg; + } + } else if (varp == &p_wim) { // 'wildmode' + if (check_opt_wim() == FAIL) { + errmsg = e_invarg; + } + } else if (varp == &p_wop) { // 'wildoptions' + if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_wak) { // 'winaltkeys' + if (*p_wak == NUL + || check_opt_strings(p_wak, p_wak_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_ei) { // 'eventignore' + if (check_ei() == FAIL) { + errmsg = e_invarg; + } + } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { + // 'encoding', 'fileencoding' and 'makeencoding' + if (gvarp == &p_fenc) { + if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { + errmsg = e_modifiable; + } else if (vim_strchr(*varp, ',') != NULL) { + // No comma allowed in 'fileencoding'; catches confusing it + // with 'fileencodings'. + errmsg = e_invarg; + } else { + // May show a "+" in the title now. + redraw_titles(); + // Add 'fileencoding' to the swap file. + ml_setflags(curbuf); + } + } + + if (errmsg == NULL) { + // canonize the value, so that STRCMP() can be used on it + p = enc_canonize(*varp); + xfree(*varp); + *varp = p; + if (varp == &p_enc) { + // only encoding=utf-8 allowed + if (STRCMP(p_enc, "utf-8") != 0) { + errmsg = e_unsupportedoption; + } else { + spell_reload(); + } + } + } + } else if (varp == &p_penc) { + // Canonize printencoding if VIM standard one + p = enc_canonize(p_penc); + xfree(p_penc); + p_penc = p; + } else if (varp == &curbuf->b_p_keymap) { + if (!valid_filetype(*varp)) { + errmsg = e_invarg; + } else { + int secure_save = secure; + + // Reset the secure flag, since the value of 'keymap' has + // been checked to be safe. + secure = 0; + + // load or unload key mapping tables + errmsg = keymap_init(); + + secure = secure_save; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; + } + + if (errmsg == NULL) { + if (*curbuf->b_p_keymap != NUL) { + // Installed a new keymap, switch on using it. + curbuf->b_p_iminsert = B_IMODE_LMAP; + if (curbuf->b_p_imsearch != B_IMODE_USE_INSERT) { + curbuf->b_p_imsearch = B_IMODE_LMAP; + } + } else { + // Cleared the keymap, may reset 'iminsert' and 'imsearch'. + if (curbuf->b_p_iminsert == B_IMODE_LMAP) { + curbuf->b_p_iminsert = B_IMODE_NONE; + } + if (curbuf->b_p_imsearch == B_IMODE_LMAP) { + curbuf->b_p_imsearch = B_IMODE_USE_INSERT; + } + } + if ((opt_flags & OPT_LOCAL) == 0) { + set_iminsert_global(); + set_imsearch_global(); + } + status_redraw_curbuf(); + } + } else if (gvarp == &p_ff) { // 'fileformat' + if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL)) { + errmsg = e_modifiable; + } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { + errmsg = e_invarg; + } else { + redraw_titles(); + // update flag in swap file + ml_setflags(curbuf); + // Redraw needed when switching to/from "mac": a CR in the text + // will be displayed differently. + if (get_fileformat(curbuf) == EOL_MAC || *oldval == 'm') { + redraw_curbuf_later(UPD_NOT_VALID); + } + } + } else if (varp == &p_ffs) { // 'fileformats' + if (check_opt_strings(p_ffs, p_ff_values, true) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_mps) { // 'matchpairs' + for (p = *varp; *p != NUL; p++) { + int x2 = -1; + int x3 = -1; + + p += utfc_ptr2len(p); + if (*p != NUL) { + x2 = (unsigned char)(*p++); + } + if (*p != NUL) { + x3 = utf_ptr2char(p); + p += utfc_ptr2len(p); + } + if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { + errmsg = e_invarg; + break; + } + if (*p == NUL) { + break; + } + } + } else if (gvarp == &p_com) { // 'comments' + for (s = *varp; *s;) { + while (*s && *s != ':') { + if (vim_strchr(COM_ALL, *s) == NULL + && !ascii_isdigit(*s) && *s != '-') { + errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + s++; + } + if (*s++ == NUL) { + errmsg = N_("E524: Missing colon"); + } else if (*s == ',' || *s == NUL) { + errmsg = N_("E525: Zero length string"); + } + if (errmsg != NULL) { + break; + } + while (*s && *s != ',') { + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; + } + s = skip_to_option_part(s); + } + } else if (varp == &p_lcs || varp == &p_fcs) { // global 'listchars' or 'fillchars' + char **local_ptr = varp == &p_lcs ? &curwin->w_p_lcs : &curwin->w_p_fcs; + // only apply the global value to "curwin" when it does not have a local value + errmsg = + set_chars_option(curwin, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); + if (errmsg == NULL) { + // If the current window is set to use the global + // 'listchars'/'fillchars' value, clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(local_ptr); + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + // If the current window has a local value need to apply it + // again, it was changed when setting the global value. + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs; + if (**local_ptr == NUL) { + (void)set_chars_option(wp, local_ptr, true); + } + } + redraw_all_later(UPD_NOT_VALID); + } + } else if (varp == &curwin->w_p_lcs) { // local 'listchars' + errmsg = set_chars_option(curwin, varp, true); + } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' + errmsg = set_chars_option(curwin, varp, true); + } else if (varp == &p_cedit) { // 'cedit' + errmsg = check_cedit(); + } else if (varp == &p_vfile) { // 'verbosefile' + verbose_stop(); + if (*p_vfile != NUL && verbose_open() == FAIL) { + errmsg = e_invarg; + } + } else if (varp == &p_shada) { // 'shada' + // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo + // option. + opt_idx = ((get_option_fullname(opt_idx)[0] == 'v') + ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) + : opt_idx); + // Update free_oldval now that we have the opt_idx for 'shada', otherwise + // 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 = (get_option_flags(opt_idx) & P_ALLOCED); + for (s = p_shada; *s;) { + // Check it's a valid character + if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) { + errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + if (*s == 'n') { // name is always last one + break; + } else if (*s == 'r') { // skip until next ',' + while (*++s && *s != ',') {} + } else if (*s == '%') { + // optional number + while (ascii_isdigit(*++s)) {} + } else if (*s == '!' || *s == 'h' || *s == 'c') { + s++; // no extra chars + } else { // must have a number + while (ascii_isdigit(*++s)) {} + + if (!ascii_isdigit(*(s - 1))) { + if (errbuf != NULL) { + vim_snprintf(errbuf, errbuflen, + _("E526: Missing number after <%s>"), + transchar_byte(*(s - 1))); + errmsg = errbuf; + } else { + errmsg = ""; + } + break; + } + } + if (*s == ',') { + s++; + } else if (*s) { + if (errbuf != NULL) { + errmsg = N_("E527: Missing comma"); + } else { + errmsg = ""; + } + break; + } + } + if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) { + errmsg = N_("E528: Must specify a ' value"); + } + } else if (gvarp == &p_sbr) { // 'showbreak' + for (s = *varp; *s;) { + if (ptr2cells(s) != 1) { + errmsg = e_showbreak_contains_unprintable_or_wide_character; + } + MB_PTR_ADV(s); + } + } else if (varp == &p_guicursor) { // 'guicursor' + errmsg = parse_shape_opt(SHAPE_CURSOR); + } else if (varp == &p_popt) { + errmsg = parse_printoptions(); + } else if (varp == &p_pmfn) { + errmsg = parse_printmbfont(); + } else if (varp == &p_langmap) { // 'langmap' + langmap_set(); + } else if (varp == &p_breakat) { // 'breakat' + fill_breakat_flags(); + } else if (varp == &p_titlestring || varp == &p_iconstring) { + // 'titlestring' and 'iconstring' + int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; + + // NULL => statusline syntax + if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { + stl_syntax |= flagval; + } else { + stl_syntax &= ~flagval; + } + did_set_title(); + } else if (varp == &p_sel) { // 'selection' + if (*p_sel == NUL + || check_opt_strings(p_sel, p_sel_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_slm) { // 'selectmode' + if (check_opt_strings(p_slm, p_slm_values, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_km) { // 'keymodel' + if (check_opt_strings(p_km, p_km_values, true) != OK) { + errmsg = e_invarg; + } else { + km_stopsel = (vim_strchr(p_km, 'o') != NULL); + km_startsel = (vim_strchr(p_km, 'a') != NULL); + } + } else if (varp == &p_mousem) { // 'mousemodel' + if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_mousescroll) { // 'mousescroll' + errmsg = check_mousescroll(p_mousescroll); + } else if (varp == &p_swb) { // 'switchbuf' + if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_debug) { // 'debug' + if (check_opt_strings(p_debug, p_debug_values, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_dy) { // 'display' + if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { + errmsg = e_invarg; + } else { + (void)init_chartab(); + msg_grid_validate(); + } + } else if (varp == &p_ead) { // 'eadirection' + if (check_opt_strings(p_ead, p_ead_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_cb) { // 'clipboard' + if (opt_strings_flags(p_cb, p_cb_values, &cb_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &(curwin->w_s->b_p_spl) // 'spell' + || varp == &(curwin->w_s->b_p_spf)) { + // When 'spelllang' or 'spellfile' is set and there is a window for this + // buffer in which 'spell' is set load the wordlists. + const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); + + if ((is_spellfile && !valid_spellfile(*varp)) + || (!is_spellfile && !valid_spelllang(*varp))) { + errmsg = e_invarg; + } else { + errmsg = did_set_spell_option(is_spellfile); + } + } else if (varp == &(curwin->w_s->b_p_spc)) { + // When 'spellcapcheck' is set compile the regexp program. + errmsg = compile_cap_prog(curwin->w_s); + } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions' + if (**varp != NUL && STRCMP("camel", *varp) != 0) { + errmsg = e_invarg; + } + } else if (varp == &p_sps) { // 'spellsuggest' + if (spell_check_sps() != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_msm) { // 'mkspellmem' + if (spell_check_msm() != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_bh) { + // When 'bufhidden' is set, check for valid value. + if (check_opt_strings(curbuf->b_p_bh, p_bufhidden_values, false) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_bt) { + // When 'buftype' is set, check for valid value. + if ((curbuf->terminal && curbuf->b_p_bt[0] != 't') + || (!curbuf->terminal && curbuf->b_p_bt[0] == 't') + || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { + errmsg = e_invarg; + } else { + if (curwin->w_status_height || global_stl_height()) { + curwin->w_redr_status = true; + redraw_later(curwin, UPD_VALID); + } + curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); + redraw_titles(); + } + } else if (gvarp == &p_stl || gvarp == &p_wbr || varp == &p_tal + || varp == &p_ruf) { + // 'statusline', 'winbar', 'tabline' or 'rulerformat' + int wid; + + if (varp == &p_ruf) { // reset ru_wid first + ru_wid = 0; + } + s = *varp; + if (varp == &p_ruf && *s == '%') { + // set ru_wid if 'ruf' starts with "%99(" + if (*++s == '-') { // ignore a '-' + s++; + } + wid = getdigits_int(&s, true, 0); + if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) { + ru_wid = wid; + } else { + errmsg = check_stl_option(p_ruf); + } + } 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(s); + } + if (varp == &p_ruf && errmsg == NULL) { + comp_col(); + } + // add / remove window bars for 'winbar' + if (gvarp == &p_wbr) { + set_winbar(true); + } + } else if (gvarp == &p_cpt) { + // check if it is a valid value for 'complete' -- Acevedo + for (s = *varp; *s;) { + while (*s == ',' || *s == ' ') { + s++; + } + if (!*s) { + break; + } + if (vim_strchr(".wbuksid]tU", *s) == NULL) { + errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + if (*++s != NUL && *s != ',' && *s != ' ') { + if (s[-1] == 'k' || s[-1] == 's') { + // skip optional filename after 'k' and 's' + while (*s && *s != ',' && *s != ' ') { + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; + } + } else { + if (errbuf != NULL) { + vim_snprintf(errbuf, errbuflen, + _("E535: Illegal character after <%c>"), + *--s); + errmsg = errbuf; + } else { + errmsg = ""; + } + break; + } + } + } + } else if (varp == &p_cot) { // 'completeopt' + if (check_opt_strings(p_cot, p_cot_values, true) != OK) { + errmsg = e_invarg; + } else { + completeopt_was_set(); + } +#ifdef BACKSLASH_IN_FILENAME + } else if (gvarp == &p_csl) { // 'completeslash' + if (check_opt_strings(p_csl, p_csl_values, false) != OK + || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) { + errmsg = e_invarg; + } +#endif + } else if (varp == &curwin->w_p_scl) { // 'signcolumn' + if (check_signcolumn(*varp) != OK) { + errmsg = e_invarg; + } + // When changing the 'signcolumn' to or from 'number', recompute the + // width of the number column if 'number' or 'relativenumber' is set. + if (((*oldval == 'n' && *(oldval + 1) == 'u') + || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) == 'u')) + && (curwin->w_p_nu || curwin->w_p_rnu)) { + curwin->w_nrwidth_line_count = 0; + } + } else if (varp == &curwin->w_p_fdc + || varp == &curwin->w_allbuf_opt.wo_fdc) { + // 'foldcolumn' + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_pt) { + // 'pastetoggle': translate key codes like in a mapping + if (*p_pt) { + p = NULL; + (void)replace_termcodes(p_pt, + STRLEN(p_pt), + &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, + CPO_TO_CPO_FLAGS); + if (p != NULL) { + free_string_option(p_pt); + p_pt = p; + } + } + } else if (varp == &p_bs) { // 'backspace' + if (ascii_isdigit(*p_bs)) { + if (*p_bs > '3' || p_bs[1] != NUL) { + errmsg = e_invarg; + } + } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_bo) { + if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_tc) { // 'tagcase' + unsigned int *flags; + + if (opt_flags & OPT_LOCAL) { + p = curbuf->b_p_tc; + flags = &curbuf->b_tc_flags; + } else { + p = p_tc; + flags = &tc_flags; + } + + if ((opt_flags & OPT_LOCAL) && *p == NUL) { + // 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) { + errmsg = e_invarg; + } + } else if (varp == &p_cmp) { // 'casemap' + if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_dip) { // 'diffopt' + if (diffopt_changed() == FAIL) { + errmsg = e_invarg; + } + } else if (gvarp == &curwin->w_allbuf_opt.wo_fdm) { // 'foldmethod' + if (check_opt_strings(*varp, p_fdm_values, false) != OK + || *curwin->w_p_fdm == NUL) { + errmsg = e_invarg; + } else { + foldUpdateAll(curwin); + if (foldmethodIsDiff(curwin)) { + newFoldLevel(); + } + } + } else if (varp == &curwin->w_p_fde) { // 'foldexpr' + if (foldmethodIsExpr(curwin)) { + foldUpdateAll(curwin); + } + } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' + p = vim_strchr(*varp, ','); + if (p == NULL) { + errmsg = N_("E536: comma required"); + } else if (p == *varp || p[1] == NUL) { + errmsg = e_invarg; + } else if (foldmethodIsMarker(curwin)) { + foldUpdateAll(curwin); + } + } else if (gvarp == &p_cms) { // 'commentstring' + if (**varp != NUL && strstr(*varp, "%s") == NULL) { + errmsg = N_("E537: 'commentstring' must be empty or contain %s"); + } + } else if (varp == &p_fdo) { // 'foldopen' + if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &p_fcl) { // 'foldclose' + if (check_opt_strings(p_fcl, p_fcl_values, true) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &curwin->w_allbuf_opt.wo_fdi) { // 'foldignore' + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else if (gvarp == &p_ve) { // 'virtualedit' + char *ve = p_ve; + unsigned int *flags = &ve_flags; + + if (opt_flags & OPT_LOCAL) { + ve = curwin->w_p_ve; + flags = &curwin->w_ve_flags; + } + + if ((opt_flags & OPT_LOCAL) && *ve == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + errmsg = e_invarg; + } else if (STRCMP(p_ve, oldval) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. + validate_virtcol(); + coladvance(curwin->w_virtcol); + } + } + } else if (varp == &p_csqf) { + if (p_csqf != NULL) { + p = p_csqf; + while (*p != NUL) { + if (vim_strchr(CSQF_CMDS, *p) == NULL + || p[1] == NUL + || vim_strchr(CSQF_FLAGS, p[1]) == NULL + || (p[2] != NUL && p[2] != ',')) { + errmsg = e_invarg; + break; + } else if (p[2] == NUL) { + break; + } else { + p += 3; + } + } + } + } else if (gvarp == &p_cino) { // 'cinoptions' + // TODO(vim): recognize errors + parse_cino(curbuf); + } else if (varp == &p_icm) { // 'inccommand' + if (check_opt_strings(p_icm, p_icm_values, false) != OK) { + errmsg = e_invarg; + } + } else if (gvarp == &p_ft) { + if (!valid_filetype(*varp)) { + errmsg = e_invarg; + } else { + value_changed = STRCMP(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; + } + } else if (gvarp == &p_syn) { + if (!valid_filetype(*varp)) { + errmsg = e_invarg; + } else { + value_changed = STRCMP(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; + } + } else if (varp == &curwin->w_p_winhl) { + if (!parse_winhl_opt(curwin)) { + errmsg = e_invarg; + } + } else if (varp == &p_tpf) { + if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { + errmsg = e_invarg; + } + } else if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop' + char *cp; + + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + XFREE_CLEAR(curbuf->b_p_vsts_array); + } else { + for (cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + errmsg = e_invarg; + break; + } + if (errmsg == NULL) { + long *oldarray = curbuf->b_p_vsts_array; + if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) { + xfree(oldarray); + } else { + errmsg = e_invarg; + } + } + } + } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop' + char *cp; + + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + XFREE_CLEAR(curbuf->b_p_vts_array); + } else { + for (cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + errmsg = e_invarg; + break; + } + if (errmsg == NULL) { + long *oldarray = curbuf->b_p_vts_array; + if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) { + xfree(oldarray); + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else { + errmsg = e_invarg; + } + } + } + } else if (varp == &p_opfunc) { // 'operatorfunc' + if (set_operatorfunc_option() == FAIL) { + errmsg = e_invarg; + } + } else if (varp == &p_qftf) { // 'quickfixtextfunc' + if (qf_process_qftf_option() == FAIL) { + errmsg = e_invarg; + } + } else { + // Options that are a list of flags. + p = NULL; + if (varp == &p_ww) { // 'whichwrap' + p = WW_ALL; + } + if (varp == &p_shm) { // 'shortmess' + p = SHM_ALL; + } else if (varp == &(p_cpo)) { // 'cpoptions' + p = CPO_VI; + } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' + p = FO_ALL; + } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' + p = COCU_ALL; + } else if (varp == &p_mouse) { // 'mouse' + p = MOUSE_ALL; + } + if (p != NULL) { + for (s = *varp; *s; s++) { + if (vim_strchr(p, *s) == NULL) { + errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + } + } + } + + // If error detected, restore the previous value. + if (errmsg != NULL) { + free_string_option(*varp); + *varp = oldval; + // When resetting some values, need to act on it. + if (did_chartab) { + (void)init_chartab(); + } + } else { + // Remember where the option was set. + set_option_sctx_idx(opt_idx, opt_flags, current_sctx); + // Free string options that are in allocated memory. + // Use "free_oldval", because recursiveness may change the flags under + // our fingers (esp. init_highlight()). + if (free_oldval) { + free_string_option(oldval); + } + set_option_flag(opt_idx, P_ALLOCED); + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 + && is_global_local_option(opt_idx)) { + // global option with local value set to use global value; free + // the local value and make it empty + p = get_option_varp_scope(opt_idx, OPT_LOCAL); + free_string_option(*(char **)p); + *(char **)p = empty_option; + } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { + // May set global value for local option. + set_string_option_global(opt_idx, varp); + } + + // Trigger the autocommand only after setting the flags. + // When 'syntax' is set, load the syntax of that name + if (varp == &(curbuf->b_p_syn)) { + static int syn_recursive = 0; + + syn_recursive++; + // Only pass true for "force" when the value changed or not used + // recursively, to avoid endless recurrence. + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, + value_changed || syn_recursive == 1, curbuf); + curbuf->b_flags |= BF_SYN_SET; + syn_recursive--; + } else if (varp == &(curbuf->b_p_ft)) { + // 'filetype' is set, trigger the FileType autocommand + // Skip this when called from a modeline and the filetype was + // already set to this value. + if (!(opt_flags & OPT_MODELINE) || value_changed) { + static int ft_recursive = 0; + int secure_save = secure; + + // Reset the secure flag, since the value of 'filetype' has + // been checked to be safe. + secure = 0; + + ft_recursive++; + did_filetype = true; + // Only pass true for "force" when the value changed or not + // used recursively, to avoid endless recurrence. + apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname, + value_changed || ft_recursive == 1, curbuf); + ft_recursive--; + // Just in case the old "curbuf" is now invalid + if (varp != &(curbuf->b_p_ft)) { + varp = NULL; + } + secure = secure_save; + } + } + if (varp == &(curwin->w_s->b_p_spl)) { + char fname[200]; + char *q = curwin->w_s->b_p_spl; + + // Skip the first name if it is "cjk". + if (STRNCMP(q, "cjk,", 4) == 0) { + q += 4; + } + + // Source the spell/LANG.vim in 'runtimepath'. + // They could set 'spellcapcheck' depending on the language. + // Use the first name in 'spelllang' up to '_region' or + // '.encoding'. + for (p = q; *p != NUL; p++) { + if (!ASCII_ISALNUM(*p) && *p != '-') { + break; + } + } + if (p > q) { + vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q); + source_runtime(fname, DIP_ALL); + } + } + } + + if (varp == &p_mouse) { + setmouse(); // in case 'mouse' changed + } + + if (curwin->w_curswant != MAXCOL + && (get_option_flags(opt_idx) & (P_CURSWANT | P_RALL)) != 0) { + curwin->w_set_curswant = true; + } + + check_redraw(get_option_flags(opt_idx)); + + return errmsg; +} + +/// Check an option that can be a range of string values. +/// +/// @param list when true: accept a list of values +/// +/// @return OK for correct value, FAIL otherwise. Empty is always OK. +static int check_opt_strings(char *val, char **values, int list) +{ + return opt_strings_flags(val, values, NULL, list); +} + +/// Handle an option that can be a range of string values. +/// Set a flag in "*flagp" for each string present. +/// +/// @param val new value +/// @param values array of valid string values +/// @param list when true: accept a list of values +/// +/// @return OK for correct value, FAIL otherwise. Empty is always OK. +static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool list) +{ + unsigned int new_flags = 0; + + while (*val) { + for (unsigned int i = 0;; i++) { + if (values[i] == NULL) { // val not found in values[] + return FAIL; + } + + size_t len = STRLEN(values[i]); + if (STRNCMP(values[i], val, len) == 0 + && ((list && val[len] == ',') || val[len] == NUL)) { + val += len + (val[len] == ','); + assert(i < sizeof(1U) * 8); + new_flags |= (1U << i); + break; // check next item in val list + } + } + } + if (flagp != NULL) { + *flagp = new_flags; + } + + return OK; +} + +/// @return OK if "p" is a valid fileformat name, FAIL otherwise. +int check_ff_value(char *p) +{ + return check_opt_strings(p, p_ff_values, false); +} diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h new file mode 100644 index 0000000000..ac8d90e10e --- /dev/null +++ b/src/nvim/optionstr.h @@ -0,0 +1,10 @@ +#ifndef NVIM_OPTIONSTR_H +#define NVIM_OPTIONSTR_H + +#include "nvim/buffer_defs.h" // for buf_T, win_T +#include "nvim/option_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "optionstr.h.generated.h" +#endif +#endif // NVIM_OPTIONSTR_H diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index eaa56ffe63..c940c86675 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -8,9 +8,8 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.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" @@ -554,9 +553,9 @@ char_u *expand_env_save_opt(char_u *src, bool one) /// @param src Input string e.g. "$HOME/vim.hlp" /// @param dst[out] Where to put the result /// @param dstlen Maximum length of the result -void expand_env(char_u *src, char_u *dst, int dstlen) +void expand_env(char *src, char *dst, int dstlen) { - expand_env_esc(src, dst, dstlen, false, false, NULL); + expand_env_esc((char_u *)src, (char_u *)dst, dstlen, false, false, NULL); } /// Expand environment variable with path name and escaping. @@ -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; } @@ -766,12 +765,12 @@ static char *vim_version_dir(const char *vimdir) return NULL; } char *p = concat_fnames(vimdir, VIM_VERSION_NODOT, true); - if (os_isdir((char_u *)p)) { + if (os_isdir(p)) { return p; } xfree(p); p = concat_fnames(vimdir, RUNTIME_DIRNAME, true); - if (os_isdir((char_u *)p)) { + if (os_isdir(p)) { return p; } xfree(p); @@ -938,8 +937,8 @@ char *vim_getenv(const char *name) // - the directory name from 'helpfile' (unless it contains '$') // - the executable name from argv[0] if (vim_path == NULL) { - if (p_hf != NULL && vim_strchr((char *)p_hf, '$') == NULL) { - vim_path = (char *)p_hf; + if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) { + vim_path = p_hf; } char exe_name[MAXPATHL]; @@ -958,7 +957,7 @@ char *vim_getenv(const char *name) char *vim_path_end = path_tail(vim_path); // remove "doc/" from 'helpfile', if present - if (vim_path == (char *)p_hf) { + if (vim_path == p_hf) { vim_path_end = remove_tail(vim_path, vim_path_end, "doc"); } @@ -977,7 +976,7 @@ char *vim_getenv(const char *name) assert(vim_path_end >= vim_path); vim_path = xstrndup(vim_path, (size_t)(vim_path_end - vim_path)); - if (!os_isdir((char_u *)vim_path)) { + if (!os_isdir(vim_path)) { xfree(vim_path); vim_path = NULL; } @@ -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])) { @@ -1158,15 +1156,12 @@ char *home_replace_save(buf_T *buf, const char *src) /// Function given to ExpandGeneric() to obtain an environment variable name. char *get_env_name(expand_T *xp, int idx) { -#define ENVNAMELEN 100 - // this static buffer is needed to avoid a memory leak in ExpandGeneric - static char_u name[ENVNAMELEN]; assert(idx >= 0); char *envname = os_getenvname_at_index((size_t)idx); if (envname) { - STRLCPY(name, envname, ENVNAMELEN); + STRLCPY(xp->xp_buf, envname, EXPAND_BUF_LEN); xfree(envname); - return (char *)name; + return xp->xp_buf; } return NULL; } @@ -1231,3 +1226,29 @@ bool os_shell_is_cmdexe(const char *sh) } return striequal("cmd.exe", path_tail(sh)); } + +/// Removes environment variable "name" and take care of side effects. +void vim_unsetenv_ext(const char *var) +{ + os_unsetenv(var); + + // "homedir" is not cleared, keep using the old value until $HOME is set. + if (STRICMP(var, "VIM") == 0) { + didset_vim = false; + } else if (STRICMP(var, "VIMRUNTIME") == 0) { + didset_vimruntime = false; + } +} + +/// Set environment variable "name" and take care of side effects. +void vim_setenv_ext(const char *name, const char *val) +{ + os_setenv(name, val, 1); + if (STRICMP(name, "HOME") == 0) { + init_homedir(); + } else if (didset_vim && STRICMP(name, "VIM") == 0) { + didset_vim = false; + } else if (didset_vimruntime && STRICMP(name, "VIMRUNTIME") == 0) { + didset_vimruntime = false; + } +} diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 901a1bc5a6..c0d5616666 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -129,10 +129,10 @@ bool os_isrealdir(const char *name) /// Check if the given path exists and is a directory. /// /// @return `true` if `name` is a directory. -bool os_isdir(const char_u *name) +bool os_isdir(const char *name) FUNC_ATTR_NONNULL_ALL { - int32_t mode = os_getperm((const char *)name); + int32_t mode = os_getperm(name); if (mode < 0) { return false; } @@ -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 @@ -884,7 +865,7 @@ int os_file_is_writable(const char *name) int r; RUN_UV_FS_FUNC(r, uv_fs_access, name, W_OK, NULL); if (r == 0) { - return os_isdir((char_u *)name) ? 2 : 1; + return os_isdir(name) ? 2 : 1; } return 0; } @@ -930,11 +911,11 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di // We're done when it's "/" or "c:/". const size_t dirlen = strlen(dir); char *const curdir = xmemdupz(dir, dirlen); - char *const past_head = (char *)get_past_head((char_u *)curdir); + char *const past_head = get_past_head(curdir); char *e = curdir + dirlen; const char *const real_end = e; const char past_head_save = *past_head; - while (!os_isdir((char_u *)curdir)) { + while (!os_isdir(curdir)) { e = path_tail_with_sep(curdir); if (e <= past_head) { *past_head = NUL; @@ -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..461a79c37b 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" @@ -54,7 +55,7 @@ static void save_patterns(int num_pat, char **pat, int *num_file, char ***file) char_u *s = vim_strsave((char_u *)pat[i]); // Be compatible with expand_filename(): halve the number of // backslashes. - backslash_halve(s); + backslash_halve((char *)s); (*file)[i] = (char *)s; } *num_file = num_pat; @@ -73,7 +74,7 @@ static bool have_wildcard(int num, char **file) static bool have_dollars(int num, char **file) { for (int i = 0; i < num; i++) { - if (vim_strchr((char *)file[i], '$') != NULL) { + if (vim_strchr(file[i], '$') != NULL) { return true; } } @@ -248,10 +249,16 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } STRCAT(command, ">"); } else { - if (flags & EW_NOTFOUND) { - STRCPY(command, "set nonomatch; "); - } else { - STRCPY(command, "unset nonomatch; "); + STRCPY(command, ""); + if (shell_style == STYLE_GLOB) { + // Assume the nonomatch option is valid only for csh like shells, + // otherwise, this may set the positional parameters for the shell, + // e.g. "$*". + if (flags & EW_NOTFOUND) { + STRCAT(command, "set nonomatch; "); + } else { + STRCAT(command, "unset nonomatch; "); + } } if (shell_style == STYLE_GLOB) { STRCAT(command, "glob >"); @@ -501,7 +508,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // check if this entry should be included - dir = (os_isdir((char_u *)(*file)[i])); + dir = (os_isdir((*file)[i])); if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { continue; } @@ -904,7 +911,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu out_data_ring(NULL, SIZE_MAX); } if (forward_output) { - // caller should decide if wait_return is invoked + // caller should decide if wait_return() is invoked no_wait_return++; msg_end(); no_wait_return--; 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..a5cec6772f 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -8,9 +8,9 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/garray.h" @@ -33,7 +33,7 @@ #include "nvim/vim.h" #include "nvim/window.h" -#define URL_SLASH 1 // path_is_url() has found "://" +#define URL_SLASH 1 // path_is_url() has found ":/" #define URL_BACKSLASH 2 // path_is_url() has found ":\\" #ifdef gen_expand_wildcards @@ -62,7 +62,7 @@ FileComparison path_full_compare(char *const s1, char *const s2, const bool chec FileID file_id_1, file_id_2; if (expandenv) { - expand_env((char_u *)s1, (char_u *)exp1, MAXPATHL); + expand_env(s1, exp1, MAXPATHL); } else { STRLCPY(exp1, s1, MAXPATHL); } @@ -104,7 +104,7 @@ char *path_tail(const char *fname) return ""; } - const char *tail = (char *)get_past_head((char_u *)fname); + const char *tail = get_past_head(fname); const char *p = tail; // Find last part of path. while (*p != NUL) { @@ -130,7 +130,7 @@ char *path_tail_with_sep(char *fname) assert(fname != NULL); // Don't remove the '/' from "c:/file". - char *past_head = (char *)get_past_head((char_u *)fname); + char *past_head = get_past_head(fname); char *tail = path_tail(fname); while (tail > past_head && after_pathsep(fname, tail)) { tail--; @@ -150,7 +150,7 @@ char *path_tail_with_sep(char *fname) const char_u *invocation_path_tail(const char_u *invocation, size_t *len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) { - const char_u *tail = get_past_head((char_u *)invocation); + const char_u *tail = (char_u *)get_past_head((char *)invocation); const char_u *p = tail; while (*p != NUL && *p != ' ') { bool was_sep = vim_ispathsep_nocolon(*p); @@ -215,28 +215,26 @@ bool is_path_head(const char_u *path) /// Get a pointer to one character past the head of a path name. /// Unix: after "/"; Win: after "c:\" /// If there is no head, path is returned. -char_u *get_past_head(const char_u *path) +char *get_past_head(const char *path) { - const char_u *retval = path; + const char *retval = path; #ifdef WIN32 // May skip "c:" - if (is_path_head(path)) { + if (is_path_head((char_u *)path)) { retval = path + 2; } #endif while (vim_ispathsep(*retval)) { - ++retval; + retval++; } - return (char_u *)retval; + return (char *)retval; } -/* - * Return TRUE if 'c' is a path separator. - * Note that for MS-Windows this includes the colon. - */ +/// Return true if 'c' is a path separator. +/// Note that for MS-Windows this includes the colon. int vim_ispathsep(int c) { #ifdef UNIX @@ -262,9 +260,7 @@ int vim_ispathsep_nocolon(int c) ; } -/* - * return TRUE if 'c' is a path list separator. - */ +/// return true if 'c' is a path list separator. int vim_ispathlistsep(int c) { #ifdef UNIX @@ -314,16 +310,14 @@ void shorten_dir_len(char_u *str, int trim_len) /// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" /// It's done in-place. -void shorten_dir(char_u *str) +void shorten_dir(char *str) { - shorten_dir_len(str, 1); + shorten_dir_len((char_u *)str, 1); } -/* - * Return TRUE if the directory of "fname" exists, FALSE otherwise. - * Also returns TRUE if there is no directory name. - * "fname" must be writable!. - */ +/// Return true if the directory of "fname" exists, false otherwise. +/// Also returns true if there is no directory name. +/// "fname" must be writable!. bool dir_of_file_exists(char_u *fname) { char *p = path_tail_with_sep((char *)fname); @@ -332,7 +326,7 @@ bool dir_of_file_exists(char_u *fname) } char c = *p; *p = NUL; - bool retval = os_isdir(fname); + bool retval = os_isdir((char *)fname); *p = c; return retval; } @@ -376,16 +370,16 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, size_t le const char *p1 = fname1; const char *p2 = fname2; while (len > 0) { - c1 = utf_ptr2char((const char_u *)p1); - c2 = utf_ptr2char((const char_u *)p2); + c1 = utf_ptr2char(p1); + c2 = utf_ptr2char(p2); if ((c1 == NUL || c2 == NUL || (!((c1 == '/' || c1 == '\\') && (c2 == '\\' || c2 == '/')))) && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { break; } - len -= (size_t)utfc_ptr2len((const char_u *)p1); - p1 += utfc_ptr2len((const char_u *)p1); - p2 += utfc_ptr2len((const char_u *)p2); + len -= (size_t)utfc_ptr2len(p1); + p1 += utfc_ptr2len(p1); + p2 += utfc_ptr2len(p2); } return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2; #else @@ -637,7 +631,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, while (*path_end != NUL) { /* May ignore a wildcard that has a backslash before it; it will * be removed by rem_backslash() or file_pat_to_reg_pat() below. */ - if (path_end >= path + wildoff && rem_backslash(path_end)) { + if (path_end >= path + wildoff && rem_backslash((char *)path_end)) { *p++ = *path_end++; } else if (vim_ispathsep_nocolon(*path_end)) { if (e != NULL) { @@ -663,16 +657,16 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // Now we have one wildcard component between "s" and "e". /* Remove backslashes between "wildoff" and the start of the wildcard * component. */ - for (p = buf + wildoff; p < s; ++p) { - if (rem_backslash(p)) { + for (p = buf + wildoff; p < s; p++) { + if (rem_backslash((char *)p)) { STRMOVE(p, p + 1); - --e; - --s; + e--; + s--; } } // Check for "**" between "s" and "e". - for (p = s; p < e; ++p) { + for (p = s; p < e; p++) { if (p[0] == '*' && p[1] == '*') { starstar = true; } @@ -695,11 +689,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); @@ -725,7 +719,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // Find all matching entries. char_u *name; scandir_next_with_dots(NULL); // initialize - while ((name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { + while (!got_int && (name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { if ((name[0] != '.' || starts_with_dot || ((flags & EW_DODOT) @@ -742,9 +736,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); @@ -758,7 +752,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // no more wildcards, check if there is a match // remove backslashes for the remaining components only if (*path_end != NUL) { - backslash_halve(buf + len + 1); + backslash_halve((char *)buf + len + 1); } // add existing file or symbolic link if ((flags & EW_ALLLINKS) ? os_fileinfo_link((char *)buf, &file_info) @@ -774,8 +768,10 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, xfree(buf); vim_regfree(regmatch.regprog); + // When interrupted the matches probably won't be used and sorting can be + // slow, thus skip it. size_t matches = (size_t)(gap->ga_len - start_len); - if (matches > 0) { + if (matches > 0 && !got_int) { qsort(((char_u **)gap->ga_data) + start_len, matches, sizeof(char_u *), pstrcmp); } @@ -804,10 +800,8 @@ static int find_previous_pathsep(char_u *path, char_u **psep) return FAIL; } -/* - * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap". - * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". - */ +/// Returns true if "maybe_unique" is unique wrt other_paths in "gap". +/// "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) { char_u **other_paths = (char_u **)gap->ga_data; @@ -841,7 +835,7 @@ static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) */ static void expand_path_option(char_u *curdir, garray_T *gap) { - char_u *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + char_u *path_option = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path; char_u *buf = xmalloc(MAXPATHL); while (*path_option != NUL) { @@ -1141,16 +1135,14 @@ 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, (char *)pattern, gap, glob_flags); xfree(paths); return gap->ga_len; } -/* - * Return TRUE if "p" contains what looks like an environment variable. - * Allowing for escaping. - */ +/// Return true if "p" contains what looks like an environment variable. +/// Allowing for escaping. static bool has_env_var(char_u *p) { for (; *p; MB_PTR_ADV(p)) { @@ -1165,7 +1157,7 @@ static bool has_env_var(char_u *p) #ifdef SPECIAL_WILDCHAR -// Return TRUE if "p" contains a special wildcard character, one that Vim +// Return true if "p" contains a special wildcard character, one that Vim // cannot expand, requires using a shell. static bool has_special_wildchar(char_u *p) { @@ -1254,7 +1246,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i */ ga_init(&ga, (int)sizeof(char_u *), 30); - for (int i = 0; i < num_pat; ++i) { + for (int i = 0; i < num_pat && !got_int; i++) { add_pat = -1; p = (char_u *)pat[i]; @@ -1320,7 +1312,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i } if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND))) { - char_u *t = backslash_halve_save(p); + char_u *t = (char_u *)backslash_halve_save((char *)p); /* When EW_NOTFOUND is used, always add files and dirs. Makes * "vim c:/" work. */ @@ -1363,9 +1355,7 @@ void FreeWild(int count, char **files) xfree(files); } -/* - * Return TRUE if we can expand this backtick thing here. - */ +/// Return true if we can expand this backtick thing here. static int vim_backtick(char_u *p) { return *p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`'; @@ -1401,7 +1391,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 +1399,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++; } } @@ -1482,7 +1472,7 @@ void addfile(garray_T *gap, char_u *f, int flags) } #endif - isdir = os_isdir(f); + isdir = os_isdir((char *)f); if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE))) { return; } @@ -1656,12 +1646,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 "../" @@ -1682,7 +1672,7 @@ void simplify_filename(char_u *filename) static char *eval_includeexpr(const char *const ptr, const size_t len) { set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); - char *res = eval_to_string_safe((char *)curbuf->b_p_inex, NULL, + char *res = eval_to_string_safe(curbuf->b_p_inex, NULL, was_set_insecurely(curwin, "includeexpr", OPT_LOCAL)); set_vim_var_string(VV_FNAME, NULL, 0); return res; @@ -1724,7 +1714,7 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, ptr = tofree; len = STRLEN(ptr); file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, - TRUE, rel_fname); + true, rel_fname); } } if (file_name == NULL && (options & FNAME_MESS)) { @@ -1738,7 +1728,7 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, * appears several times in the path. */ while (file_name != NULL && --count > 0) { xfree(file_name); - file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname); + file_name = find_file_in_path(ptr, len, options, false, rel_fname); } } else { file_name = vim_strnsave(ptr, len); @@ -1749,12 +1739,26 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, return file_name; } -// Check if the "://" of a URL is at the pointer, return URL_SLASH. +/// Checks for a Windows drive letter ("C:/") at the start of the path. +/// +/// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter +bool path_has_drive_letter(const char *p) + FUNC_ATTR_NONNULL_ALL +{ + return strlen(p) >= 2 + && ASCII_ISALPHA(p[0]) + && (p[1] == ':' || p[1] == '|') + && (strlen(p) == 2 || ((p[2] == '/') | (p[2] == '\\') | (p[2] == '?') | (p[2] == '#'))); +} + +// Check if the ":/" of a URL is at the pointer, return URL_SLASH. // Also check for ":\\", which MS Internet Explorer accepts, return // URL_BACKSLASH. int path_is_url(const char *p) { - if (strncmp(p, "://", 3) == 0) { + // In the spec ':' is enough to recognize a scheme + // https://url.spec.whatwg.org/#scheme-state + if (strncmp(p, ":/", 2) == 0) { return URL_SLASH; } else if (strncmp(p, ":\\\\", 3) == 0) { return URL_BACKSLASH; @@ -1779,6 +1783,10 @@ int path_with_url(const char *fname) return 0; } + if (path_has_drive_letter(fname)) { + return 0; + } + // check body: alpha or dash for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {} @@ -1787,7 +1795,7 @@ int path_with_url(const char *fname) return 0; } - // "://" or ":\\" must follow + // ":/" or ":\\" must follow return path_is_url(p); } @@ -1800,9 +1808,7 @@ bool path_with_extension(const char *path, const char *extension) return strcmp(last_dot + 1, extension) == 0; } -/* - * Return TRUE if "name" is a full (absolute) path name or URL. - */ +/// Return true if "name" is a full (absolute) path name or URL. bool vim_isAbsName(char_u *name) { return path_with_url((char *)name) != 0 || path_is_absolute(name); @@ -1839,7 +1845,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) return OK; } - int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); + int rv = path_to_absolute(fname, buf, len, force); if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } @@ -1898,7 +1904,7 @@ void path_fix_case(char *name) } // Open the directory where the file is located. - char *slash = (char *)STRRCHR(name, '/'); + char *slash = strrchr(name, '/'); char *tail; Directory dir; bool ok; @@ -1939,21 +1945,17 @@ void path_fix_case(char *name) os_closedir(&dir); } -/* - * Return TRUE if "p" points to just after a path separator. - * Takes care of multi-byte characters. - * "b" must point to the start of the file name - */ +/// Return true if "p" points to just after a path separator. +/// Takes care of multi-byte characters. +/// "b" must point to the start of the file name int after_pathsep(const char *b, const char *p) { return p > b && vim_ispathsep(p[-1]) && utf_head_off((char_u *)b, (char_u *)p - 1) == 0; } -/* - * Return TRUE if file names "f1" and "f2" are in the same directory. - * "f1" may be a short name, "f2" must be a full path. - */ +/// Return true if file names "f1" and "f2" are in the same directory. +/// "f1" may be a short name, "f2" must be a full path. bool same_directory(char_u *f1, char_u *f2) { char ffname[MAXPATHL]; @@ -1965,7 +1967,7 @@ bool same_directory(char_u *f1, char_u *f2) return false; } - (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, FALSE); + (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, false); t1 = path_tail_with_sep(ffname); t2 = path_tail_with_sep((char *)f2); return t1 - ffname == (char_u *)t2 - f2 @@ -2133,10 +2135,11 @@ int expand_wildcards_eval(char_u **pat, int *num_file, char ***file, int flags) if (*exp_pat == '%' || *exp_pat == '#' || *exp_pat == '<') { emsg_off++; - eval_pat = eval_vars((char_u *)exp_pat, (char_u *)exp_pat, &usedlen, NULL, &ignored_msg, NULL); + eval_pat = eval_vars((char_u *)exp_pat, (char_u *)exp_pat, &usedlen, NULL, &ignored_msg, NULL, + true); emsg_off--; if (eval_pat != NULL) { - exp_pat = (char *)concat_str(eval_pat, (char_u *)exp_pat + usedlen); + exp_pat = concat_str((char *)eval_pat, exp_pat + usedlen); } } @@ -2192,7 +2195,7 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int ffname = (char_u *)FullName_save((*files)[i], false); assert((*files)[i] != NULL); assert(ffname != NULL); - if (match_file_list(p_wig, (char_u *)(*files)[i], ffname)) { + if (match_file_list((char_u *)p_wig, (char_u *)(*files)[i], ffname)) { // remove this matching file from the list xfree((*files)[i]); for (j = i; j + 1 < *num_files; j++) { @@ -2205,17 +2208,14 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int } } - // // Move the names where 'suffixes' match to the end. - // + // Skip when interrupted, the result probably won't be used. assert(*num_files == 0 || *files != NULL); - if (*num_files > 1) { + if (*num_files > 1 && !got_int) { non_suf_match = 0; for (i = 0; i < *num_files; i++) { if (!match_suffix((char_u *)(*files)[i])) { - // // Move the name without matching suffix to the front of the list. - // p = (char_u *)(*files)[i]; for (j = i; j > non_suf_match; j--) { (*files)[j] = (*files)[j - 1]; @@ -2234,9 +2234,7 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int return retval; } -/* - * Return TRUE if "fname" matches with an entry in 'suffixes'. - */ +/// Return true if "fname" matches with an entry in 'suffixes'. int match_suffix(char_u *fname) { #define MAXSUFLEN 30 // maximum length of a file suffix @@ -2355,20 +2353,20 @@ int append_path(char *path, const char *to_append, size_t max_len) /// @param force also expand when "fname" is already absolute. /// /// @return FAIL for failure, OK for success. -static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int force) +static int path_to_absolute(const char *fname, char *buf, size_t len, int force) { - char_u *p; + char *p; *buf = NUL; char *relative_directory = xmalloc(len); char *end_of_path = (char *)fname; // expand it if forced or not an absolute path - if (force || !path_is_absolute(fname)) { - p = STRRCHR(fname, '/'); + if (force || !path_is_absolute((char_u *)fname)) { + p = strrchr(fname, '/'); #ifdef WIN32 if (p == NULL) { - p = STRRCHR(fname, '\\'); + p = strrchr(fname, '\\'); } #endif if (p != NULL) { @@ -2382,24 +2380,24 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo memcpy(relative_directory, fname, (size_t)(p - fname)); relative_directory[p - fname] = NUL; } - end_of_path = (char *)(p + 1); + end_of_path = p + 1; } else { relative_directory[0] = NUL; end_of_path = (char *)fname; } - if (FAIL == path_full_dir_name(relative_directory, (char *)buf, len)) { + if (FAIL == path_full_dir_name(relative_directory, buf, len)) { xfree(relative_directory); return FAIL; } } xfree(relative_directory); - return append_path((char *)buf, end_of_path, len); + return append_path(buf, end_of_path, len); } /// Check if file `fname` is a full (absolute) path. /// -/// @return `TRUE` if "fname" is absolute. +/// @return `true` if "fname" is absolute. int path_is_absolute(const char_u *fname) { #ifdef WIN32 diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 70bdbd8b1d..cc730ba307 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -98,15 +98,15 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) /// "wp". Does not care about folding, 'wrap' or 'diff'. int plines_win_nofold(win_T *wp, linenr_T lnum) { - char_u *s; + char *s; unsigned int col; int width; - s = ml_get_buf(wp->w_buffer, lnum, false); + s = (char *)ml_get_buf(wp->w_buffer, lnum, false); if (*s == NUL) { // empty line return 1; } - col = win_linetabsize(wp, s, MAXCOL); + col = win_linetabsize(wp, lnum, (char_u *)s, MAXCOL); // If list mode is on, then the '$' at the end of the line may take up one // extra column. @@ -145,23 +145,27 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) } char_u *line = ml_get_buf(wp->w_buffer, lnum, false); - char_u *s = line; colnr_T col = 0; - while (*s != NUL && --column >= 0) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); - MB_PTR_ADV(s); + chartabsize_T cts; + + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + while (*cts.cts_ptr != NUL && --column >= 0) { + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); + MB_PTR_ADV(cts.cts_ptr); } - // If *s is a TAB, and the TAB is not displayed as ^I, and we're not in - // MODE_INSERT state, then col must be adjusted so that it represents the + // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not + // in MODE_INSERT state, then col must be adjusted so that it represents the // last screen position of the TAB. This only fixes an error when the TAB // wraps from one screen line to the next (when 'columns' is not a multiple // of 'ts') -- webb. - if (*s == TAB && (State & MODE_NORMAL) + col = cts.cts_vcol; + if (*cts.cts_ptr == TAB && (State & MODE_NORMAL) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - col += win_lbr_chartabsize(wp, line, s, col, NULL) - 1; + col += win_lbr_chartabsize(&cts, NULL) - 1; } + clear_chartabsize_arg(&cts); // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. int width = wp->w_width_inner - win_col_off(wp); @@ -223,13 +227,13 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last) /// @param col /// /// @return Number of characters. -int win_chartabsize(win_T *wp, char_u *p, colnr_T col) +int win_chartabsize(win_T *wp, char *p, colnr_T col) { buf_T *buf = wp->w_buffer; if (*p == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { return tabstop_padding(col, buf->b_p_ts, buf->b_p_vts_array); } else { - return ptr2cells((char *)p); + return ptr2cells(p); } } @@ -241,24 +245,24 @@ int win_chartabsize(win_T *wp, char_u *p, colnr_T col) /// @return Number of characters the string will take on the screen. int linetabsize(char_u *s) { - return linetabsize_col(0, s); + return linetabsize_col(0, (char *)s); } -/// Like linetabsize(), but starting at column "startcol". +/// Like linetabsize(), but "s" starts at column "startcol". /// /// @param startcol /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize_col(int startcol, char_u *s) +int linetabsize_col(int startcol, char *s) { - colnr_T col = startcol; - char_u *line = s; // pointer to start of line, for breakindent - - while (*s != NUL) { - col += lbr_chartabsize_adv(line, &s, col); + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, 0, startcol, (char_u *)s, (char_u *)s); + while (*cts.cts_ptr != NUL) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - return (int)col; + clear_chartabsize_arg(&cts); + return cts.cts_vcol; } /// Like linetabsize(), but for a given window instead of the current one. @@ -268,19 +272,39 @@ int linetabsize_col(int startcol, char_u *s) /// @param len /// /// @return Number of characters the string will take on the screen. -unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) +unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len) { - colnr_T col = 0; - - for (char_u *s = line; - *s != NUL && (len == MAXCOL || s < line + len); - MB_PTR_ADV(s)) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < (char *)line + len); + MB_PTR_ADV(cts.cts_ptr)) { + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); } + clear_chartabsize_arg(&cts); + return (unsigned int)cts.cts_vcol; +} - return (unsigned int)col; +/// Prepare the structure passed to chartabsize functions. +/// +/// "line" is the start of the line, "ptr" is the first relevant character. +/// When "lnum" is zero do not use text properties that insert text. +void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char_u *line, + char_u *ptr) +{ + cts->cts_win = wp; + cts->cts_lnum = lnum; + cts->cts_vcol = col; + cts->cts_line = (char *)line; + cts->cts_ptr = (char *)ptr; + cts->cts_cur_text_width = 0; + // TODO(bfredl): actually lookup inline virtual text here + cts->cts_has_virt_text = false; } +/// Free any allocated item in "cts". +void clear_chartabsize_arg(chartabsize_T *cts) +{} + /// like win_chartabsize(), but also check for line breaks on the screen /// /// @param line @@ -288,16 +312,16 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) /// @param col /// /// @return The number of characters taken up on the screen. -int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) +int lbr_chartabsize(chartabsize_T *cts) { if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri) { + && !curwin->w_p_bri && !cts->cts_has_virt_text) { if (curwin->w_p_wrap) { - return win_nolbr_chartabsize(curwin, s, col, NULL); + return win_nolbr_chartabsize(cts, NULL); } - return win_chartabsize(curwin, s, col); + return win_chartabsize(curwin, cts->cts_ptr, cts->cts_vcol); } - return win_lbr_chartabsize(curwin, line == NULL ? s: line, s, col, NULL); + return win_lbr_chartabsize(cts, NULL); } /// Call lbr_chartabsize() and advance the pointer. @@ -307,12 +331,12 @@ int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) /// @param col /// /// @return The number of characters take up on the screen. -int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) +int lbr_chartabsize_adv(chartabsize_T *cts) { int retval; - retval = lbr_chartabsize(line, *s, col); - MB_PTR_ADV(*s); + retval = lbr_chartabsize(cts); + MB_PTR_ADV(cts->cts_ptr); return retval; } @@ -322,17 +346,19 @@ int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) /// string at start of line. Warning: *headp is only set if it's a non-zero /// value, init to 0 before calling. /// -/// @param wp -/// @param line -/// @param s -/// @param col +/// @param cts /// @param headp /// /// @return The number of characters taken up on the screen. -int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *headp) +int win_lbr_chartabsize(chartabsize_T *cts, int *headp) { + win_T *wp = cts->cts_win; + char *line = cts->cts_line; // start of the line + char_u *s = (char_u *)cts->cts_ptr; + colnr_T vcol = cts->cts_vcol; + colnr_T col2; - colnr_T col_adj = 0; // col + screen size of tab + colnr_T col_adj = 0; // vcol + screen size of tab colnr_T colmax; int added; int mb_added = 0; @@ -340,16 +366,23 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he char_u *ps; int n; + cts->cts_cur_text_width = 0; + // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL) { + if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL + && !cts->cts_has_virt_text) { if (wp->w_p_wrap) { - return win_nolbr_chartabsize(wp, s, col, headp); + return win_nolbr_chartabsize(cts, headp); } - return win_chartabsize(wp, s, col); + return win_chartabsize(wp, (char *)s, vcol); + } + + // First get normal size, without 'linebreak' or virtual text + int size = win_chartabsize(wp, (char *)s, vcol); + if (cts->cts_has_virt_text) { + // TODO(bfredl): inline virtual text } - // First get normal size, without 'linebreak' - int size = win_chartabsize(wp, s, col); int c = *s; if (*s == TAB) { col_adj = size - 1; @@ -365,15 +398,15 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he // Count all characters from first non-blank after a blank up to next // non-blank after a blank. numberextra = win_col_off(wp); - col2 = col; + col2 = vcol; colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); - if (col >= colmax) { + if (vcol >= colmax) { colmax += col_adj; n = colmax + win_col_off2(wp); if (n > 0) { - colmax += (((col - colmax) / n) + 1) * n - col_adj; + colmax += (((vcol - colmax) / n) + 1) * n - col_adj; } } @@ -383,21 +416,21 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he c = *s; if (!(c != NUL - && (vim_isbreak(c) || col2 == col || !vim_isbreak((int)(*ps))))) { + && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((int)(*ps))))) { break; } - col2 += win_chartabsize(wp, s, col2); + col2 += win_chartabsize(wp, (char *)s, col2); if (col2 >= colmax) { // doesn't fit - size = colmax - col + col_adj; + size = colmax - vcol + col_adj; break; } } } else if ((size == 2) && (MB_BYTE2LEN(*s) > 1) && wp->w_p_wrap - && in_win_border(wp, col)) { + && in_win_border(wp, vcol)) { // Count the ">" in the last column. size++; mb_added = 1; @@ -409,40 +442,40 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he added = 0; char *const sbr = (char *)get_showbreak_value(wp); - if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && col != 0) { + if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); numberextra = numberwidth; - col += numberextra + mb_added; + vcol += numberextra + mb_added; - if (col >= (colnr_T)wp->w_width_inner) { - col -= wp->w_width_inner; + if (vcol >= (colnr_T)wp->w_width_inner) { + vcol -= wp->w_width_inner; numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp)); - if (col >= numberextra && numberextra > 0) { - col %= numberextra; + if (vcol >= numberextra && numberextra > 0) { + vcol %= numberextra; } if (*sbr != NUL) { sbrlen = (colnr_T)mb_charlen((char_u *)sbr); - if (col >= sbrlen) { - col -= sbrlen; + if (vcol >= sbrlen) { + vcol -= sbrlen; } } - if (col >= numberextra && numberextra > 0) { - col %= numberextra; - } else if (col > 0 && numberextra > 0) { - col += numberwidth - win_col_off2(wp); + if (vcol >= numberextra && numberextra > 0) { + vcol %= numberextra; + } else if (vcol > 0 && numberextra > 0) { + vcol += numberwidth - win_col_off2(wp); } numberwidth -= win_col_off2(wp); } - if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { + if (vcol == 0 || (vcol + size + sbrlen > (colnr_T)wp->w_width_inner)) { if (*sbr != NUL) { if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { // Calculate effective window width. int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; - int prev_width = col ? ((colnr_T)wp->w_width_inner - (sbrlen + col)) + int prev_width = vcol ? ((colnr_T)wp->w_width_inner - (sbrlen + vcol)) : 0; if (width <= 0) { @@ -459,11 +492,11 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he } if (wp->w_p_bri) { - added += get_breakindent_win(wp, line); + added += get_breakindent_win(wp, (char_u *)line); } size += added; - if (col != 0) { + if (vcol != 0) { added = 0; } } @@ -485,8 +518,11 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he /// @param headp /// /// @return The number of characters take up on the screen. -static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) +static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) { + win_T *wp = cts->cts_win; + char *s = cts->cts_ptr; + colnr_T col = cts->cts_vcol; int n; if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { @@ -494,11 +530,11 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) wp->w_buffer->b_p_ts, wp->w_buffer->b_p_vts_array); } - n = ptr2cells((char *)s); + n = ptr2cells(s); // Add one cell for a double-width character in the last column of the // window, displayed with a ">". - if ((n == 2) && (MB_BYTE2LEN(*s) > 1) && in_win_border(wp, col)) { + if ((n == 2) && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && in_win_border(wp, col)) { if (headp != NULL) { *headp = 1; } diff --git a/src/nvim/plines.h b/src/nvim/plines.h index 32778b69f1..7b228f3e91 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -3,6 +3,20 @@ #include "nvim/vim.h" +// Argument for lbr_chartabsize(). +typedef struct { + win_T *cts_win; + linenr_T cts_lnum; // zero when not using text properties + char *cts_line; // start of the line + char *cts_ptr; // current position in line + + bool cts_has_virt_text; // true if if a property inserts text + int cts_cur_text_width; // width of current inserted text + // TODO(bfredl): iterator in to the marktree for scanning virt text + + int cts_vcol; // virtual column at current position +} chartabsize_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.h.generated.h" #endif diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 82345f8a46..d64789660e 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -5320,8 +5320,8 @@ msgstr "E424: Te veel verskillende uitlig-eienskappe in gebruik" msgid "E669: Unprintable character in group name" msgstr "E669: Onvertoonbare karakter in groepnaam" -msgid "W18: Invalid character in group name" -msgstr "W18: Ongeldige karakter groepnaam" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ongeldige karakter groepnaam" #~ msgid "E849: Too many highlight and syntax groups" #~ msgstr "" diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po index 6c4d6ddd22..5869e6567c 100644 --- a/src/nvim/po/ca.po +++ b/src/nvim/po/ca.po @@ -5983,9 +5983,9 @@ msgstr "E424: Hi ha massa atributs de ressalt diferents en ús" msgid "E669: Unprintable character in group name" msgstr "E669: Caràcter no imprimible en el nom del grup" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Hi ha un caràcter no vàlid en el nom del grup" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Hi ha un caràcter no vàlid en el nom del grup" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po index 859039eb87..bce5c0fa76 100644 --- a/src/nvim/po/cs.cp1250.po +++ b/src/nvim/po/cs.cp1250.po @@ -6072,10 +6072,10 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 +#: ../highlight_group.c:1756 #, fuzzy -msgid "W18: Invalid character in group name" -msgstr "E182: Chybné jméno pøíkazu" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybné jméno pøíkazu" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po index 4d9ad58836..f4eab3f0d4 100644 --- a/src/nvim/po/cs.po +++ b/src/nvim/po/cs.po @@ -6072,10 +6072,10 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 +#: ../highlight_group.c:1756 #, fuzzy -msgid "W18: Invalid character in group name" -msgstr "E182: Chybné jméno pøíkazu" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybné jméno pøíkazu" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index dfcc052288..1c3284f867 100644 --- a/src/nvim/po/da.po +++ b/src/nvim/po/da.po @@ -5599,8 +5599,8 @@ msgstr "E424: For mange forskellige fremhævningsattributter i brug" msgid "E669: Unprintable character in group name" msgstr "E669: Tegn som ikke kan udskrives i gruppenavn" -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldige tegn i gruppenavn" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldige tegn i gruppenavn" msgid "E849: Too many highlight and syntax groups" msgstr "E849: For mange fremhævnings- og syntaksgrupper" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index 2dde77e9f7..ce3fd77ede 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -5408,8 +5408,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Nicht druckbare Zeichen im Namen der Gruppe" #: ../syntax.c:7304 -msgid "W18: Invalid character in group name" -msgstr "W18: Ungültiges Zeichen im Namen der Gruppe" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ungültiges Zeichen im Namen der Gruppe" #: ../syntax.c:7318 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po index 66cdba6f92..81ee9ed6a0 100644 --- a/src/nvim/po/en_GB.po +++ b/src/nvim/po/en_GB.po @@ -5730,8 +5730,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" msgstr "" #: ../syntax.c:7448 diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index 1c503d0a84..263fb61b18 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -5378,8 +5378,8 @@ msgstr "E424: Tro da malsamaj atributoj de emfazo uzataj" msgid "E669: Unprintable character in group name" msgstr "E669: Nepresebla signo en nomo de grupo" -msgid "W18: Invalid character in group name" -msgstr "W18: Nevalida signo en nomo de grupo" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Nevalida signo en nomo de grupo" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Tro da emfazaj kaj sintaksaj grupoj" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index adea651b7c..8a44f6a534 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -6053,9 +6053,9 @@ msgstr "E669: Carácter no imprimible en el nombre del grupo" # This is an error, but since there previously was no check only # * give a warning. -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Hay un carácter no válido en el nombre del grupo" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Hay un carácter no válido en el nombre del grupo" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index f10d4ce977..1c0da244ba 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -5369,8 +5369,8 @@ msgstr "E424: Liikaa eri korostusattribuutteja" msgid "E669: Unprintable character in group name" msgstr "E669: Tulostuskelvoton merkki ryhmän nimessä" -msgid "W18: Invalid character in group name" -msgstr "W18: Virheellinen merkki ryhmän nimessä" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Virheellinen merkki ryhmän nimessä" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Liikaa korostuksia ja syntaksiryhmiä" diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index 614ba013e6..be2141cd6d 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -2040,8 +2040,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "E669: Caractère non imprimable dans un nom de groupe" -msgid "W18: Invalid character in group name" -msgstr "W18: Caractère invalide dans un nom de groupe" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Caractère invalide dans un nom de groupe" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Trop de groupes de surbrillance et de syntaxe" diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index 1c25ee481c..346f0faa84 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -5668,8 +5668,8 @@ msgstr "E424: An iomarca tréithe aibhsithe in úsáid" msgid "E669: Unprintable character in group name" msgstr "E669: Carachtar neamhghrafach in ainm grúpa" -msgid "W18: Invalid character in group name" -msgstr "W18: Carachtar neamhbhailí in ainm grúpa" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Carachtar neamhbhailí in ainm grúpa" msgid "E849: Too many highlight and syntax groups" msgstr "E849: An iomarca grúpaí aibhsithe agus comhréire" diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index 313280c807..152ed2cbe3 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -6046,9 +6046,9 @@ msgstr "E424: Troppi gruppi evidenziazione differenti in uso" msgid "E669: Unprintable character in group name" msgstr "E669: Carattere non stampabile in un nome di gruppo" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Carattere non ammesso in un nome di gruppo" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Carattere non ammesso in un nome di gruppo" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po index b99c22caeb..09be710374 100644 --- a/src/nvim/po/ko.UTF-8.po +++ b/src/nvim/po/ko.UTF-8.po @@ -5913,9 +5913,9 @@ msgstr "E424: 너무 ë§Žì€ ë‹¤ë¥¸ 하ì´ë¼ì´íЏ ì†ì„±ì´ 사용ë˜ê³ 있습 msgid "E669: Unprintable character in group name" msgstr "E669: 그룹 ì´ë¦„ì— ì¶œë ¥í• ìˆ˜ 없는 문ìžê°€ 있습니다" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 그룹 ì´ë¦„ì— ì´ìƒí•œ 문ìž" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 그룹 ì´ë¦„ì— ì´ìƒí•œ 문ìž" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po index 2285d755cf..9bc730ae71 100644 --- a/src/nvim/po/nb.po +++ b/src/nvim/po/nb.po @@ -5930,9 +5930,9 @@ msgstr "E424: For mange forskjellige uthevingsattributter i bruk" msgid "E669: Unprintable character in group name" msgstr "E669: Ikke-skrivbart tegn i gruppenavn" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldig tegn i gruppenavn" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldig tegn i gruppenavn" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po index 00d113c83c..4d2e55adc6 100644 --- a/src/nvim/po/nl.po +++ b/src/nvim/po/nl.po @@ -5913,8 +5913,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" msgstr "" #: ../syntax.c:7448 diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po index 2285d755cf..9bc730ae71 100644 --- a/src/nvim/po/no.po +++ b/src/nvim/po/no.po @@ -5930,9 +5930,9 @@ msgstr "E424: For mange forskjellige uthevingsattributter i bruk" msgid "E669: Unprintable character in group name" msgstr "E669: Ikke-skrivbart tegn i gruppenavn" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldig tegn i gruppenavn" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldig tegn i gruppenavn" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po index 5f1779d1bd..7e978113f6 100644 --- a/src/nvim/po/pl.UTF-8.po +++ b/src/nvim/po/pl.UTF-8.po @@ -5908,9 +5908,9 @@ msgstr "E424: Zbyt wiele różnych atrybutów podkreÅ›lania w użyciu" msgid "E669: Unprintable character in group name" msgstr "E669: Niedrukowalny znak w nazwie grupy" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: nieprawidÅ‚owy znak w nazwie grupy" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: nieprawidÅ‚owy znak w nazwie grupy" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po index 533d916de1..bfd64b4d28 100644 --- a/src/nvim/po/pt_BR.po +++ b/src/nvim/po/pt_BR.po @@ -6070,8 +6070,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Caractere não-imprimível no nome do grupo" #: ../syntax.c:7320 -msgid "W18: Invalid character in group name" -msgstr "W18: Caractere inválido no nome do grupo" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Caractere inválido no nome do grupo" #: ../syntax.c:7334 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index 7566036d3e..da356770d7 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -5977,9 +5977,9 @@ msgstr "E424: ИÑпользуетÑÑ Ñлишком много разных а msgid "E669: Unprintable character in group name" msgstr "E669: Ðепечатный Ñимвол в имени группы" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: ÐедопуÑтимый Ñимвол в имени группы" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: ÐедопуÑтимый Ñимвол в имени группы" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po index ff95c68a12..cb8b8c6abb 100644 --- a/src/nvim/po/sk.cp1250.po +++ b/src/nvim/po/sk.cp1250.po @@ -5940,9 +5940,9 @@ msgstr "E424: Používaných príliš ve¾a odlišných zvýrazòovacích vlastností" msgid "E669: Unprintable character in group name" msgstr "E669: Netlaèitelný znak v mene skupiny" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Chybný znak v mene skupiny" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybný znak v mene skupiny" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po index d35622726f..4f9e1fe185 100644 --- a/src/nvim/po/sk.po +++ b/src/nvim/po/sk.po @@ -5940,9 +5940,9 @@ msgstr "E424: Pou¾ívaných príli¹ veµa odli¹ných zvýrazòovacích vlastností" msgid "E669: Unprintable character in group name" msgstr "E669: Netlaèitelný znak v mene skupiny" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Chybný znak v mene skupiny" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybný znak v mene skupiny" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index 3c45e1bf80..cbdf736d0f 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -6050,8 +6050,8 @@ msgstr "E424: У употреби је превише различитих Ð°Ñ‚Ñ msgid "E669: Unprintable character in group name" msgstr "E669: У имену групе је карактер који не може да Ñе штампа" -msgid "W18: Invalid character in group name" -msgstr "W18: Ðеважећи карактер у имену групе" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ðеважећи карактер у имену групе" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Превише ÑинтакÑних и група иÑтицања" diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po index d50c9d695d..406900f7b2 100644 --- a/src/nvim/po/sv.po +++ b/src/nvim/po/sv.po @@ -2056,8 +2056,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Outskrivbart tecken i gruppnamn" #: ../syntax.c:7292 -msgid "W18: Invalid character in group name" -msgstr "W18: Ogiltigt tecken i gruppnamn" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ogiltigt tecken i gruppnamn" #: ../syntax.c:7306 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po index fae2fd4967..20b667d198 100644 --- a/src/nvim/po/tr.po +++ b/src/nvim/po/tr.po @@ -3054,8 +3054,8 @@ msgstr "E423: İzin verilmeyen argüman: %s" msgid "E669: Unprintable character in group name" msgstr "E669: Grup adında yazdırılamayan karakter" -msgid "W18: Invalid character in group name" -msgstr "W18: Grup adında geçersiz karakter" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Grup adında geçersiz karakter" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Çok fazla vurgulama ve sözdizim grupları" diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index da87d50683..427abd9b77 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -3058,8 +3058,8 @@ msgstr "E423: Ðеправильний аргумент: %s" msgid "E669: Unprintable character in group name" msgstr "E669: Ðедруковний Ñимвол у назві групи" -msgid "W18: Invalid character in group name" -msgstr "W18: Ðекоректний Ñимвол у назві групи" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ðекоректний Ñимвол у назві групи" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Забагато груп підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñ– ÑинтакÑиÑу" diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po index c693f910d8..ad59718a30 100644 --- a/src/nvim/po/vi.po +++ b/src/nvim/po/vi.po @@ -5975,9 +5975,9 @@ msgstr "E424: Sá» dụng quá nhiá»u thuá»™c tÃnh chiếu sáng cú pháp" msgid "E669: Unprintable character in group name" msgstr "E669: Ký tá»± không thể tin ra trong tên nhóm" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ký tá»± không cho phép trong tên nhóm" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ký tá»± không cho phép trong tên nhóm" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index 70c1389d7f..afa2f29029 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -734,9 +734,9 @@ msgid "E120: Using <SID> not in a script context: %s" msgstr "E120: <SID> ä¸èƒ½åœ¨ script 上下文外使用: %s" #: ../eval.c:7391 -#, fuzzy, c-format +#, c-format msgid "E725: Calling dict function without Dictionary: %s" -msgstr "E720: Dictionary ä¸ç¼ºå°‘冒å·: %s" +msgstr "E725: 调用å—典函数但是没有å—典:%s" #: ../eval.c:7453 #, fuzzy @@ -766,19 +766,16 @@ msgid "E737: Key already exists: %s" msgstr "E737: 键已å˜åœ¨: %s" #: ../eval.c:8692 -#, fuzzy msgid "extend() argument" -msgstr "--cmd 傿•°" +msgstr "extend() 傿•°" #: ../eval.c:8915 -#, fuzzy msgid "map() argument" -msgstr "-c 傿•°" +msgstr "map() 傿•°" #: ../eval.c:8916 -#, fuzzy msgid "filter() argument" -msgstr "-c 傿•°" +msgstr "filter() 傿•°" #: ../eval.c:9229 #, c-format @@ -849,9 +846,8 @@ msgid "E702: Sort compare function failed" msgstr "E702: Sort 比较函数失败" #: ../eval.c:13806 -#, fuzzy msgid "E882: Uniq compare function failed" -msgstr "E702: Sort 比较函数失败" +msgstr "E882: Uniq 比较函数失败" #: ../eval.c:14085 msgid "(Invalid)" @@ -864,31 +860,31 @@ msgstr "E677: 写临时文件出错" #: ../eval.c:16159 #, fuzzy msgid "E805: Using a Float as a Number" -msgstr "E745: å°† List 作数å—使用" +msgstr "E805: å°†æµ®ç‚¹æ•°å½“åšæ•°å—使用" #: ../eval.c:16162 msgid "E703: Using a Funcref as a Number" -msgstr "E703: å°† Funcref 作数å—使用" +msgstr "E703: å°†å‡½æ•°å½“åšæ•°å—使用" #: ../eval.c:16170 msgid "E745: Using a List as a Number" -msgstr "E745: å°† List 作数å—使用" +msgstr "E745: å°†åˆ—è¡¨å½“åšæ•°å—使用" #: ../eval.c:16173 msgid "E728: Using a Dictionary as a Number" -msgstr "E728: å°† Dictionary 作数å—使用" +msgstr "E728: å°†å—å…¸å½“åšæ•°å—使用" #: ../eval.c:16259 msgid "E729: using Funcref as a String" -msgstr "E729: å°† Funcref 作 String 使用" +msgstr "E729: 将函数当åšå—符串使用" #: ../eval.c:16262 msgid "E730: using List as a String" -msgstr "E730: å°† List 作 String 使用" +msgstr "E730: 将列表当åšå—符串使用" #: ../eval.c:16265 msgid "E731: using Dictionary as a String" -msgstr "E731: å°† Dictionary 作 String 使用" +msgstr "E731: å°†å—典当åšå—符串使用" #: ../eval.c:16619 #, c-format @@ -3053,11 +3049,11 @@ msgstr "E673: ä¸å…¼å®¹çš„多å—节编ç å’Œå—符集。" #: ../hardcopy.c:2238 msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset 在多å—节编ç 下ä¸èƒ½ä¸ºç©ºã€‚" +msgstr "E674: printmbcharset 在多å—节编ç 下ä¸èƒ½ä¸ºç©º" #: ../hardcopy.c:2254 msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 没有指定多å—节打å°çš„默认å—体。" +msgstr "E675: 没有指定多å—节打å°çš„默认å—体" #: ../hardcopy.c:2426 msgid "E324: Can't open PostScript output file" @@ -4204,9 +4200,8 @@ msgstr "E329: 没有èœå• \"%s\"" #. Only a mnemonic or accelerator is not valid. #: ../menu.c:329 -#, fuzzy msgid "E792: Empty menu name" -msgstr "E749: 空的缓冲区" +msgstr "E792: 空的èœå•åç§°" #: ../menu.c:340 msgid "E330: Menu path must not lead to a sub-menu" @@ -4329,9 +4324,8 @@ msgid "E766: Insufficient arguments for printf()" msgstr "E766: printf() çš„å‚æ•°ä¸è¶³" #: ../message.c:3119 -#, fuzzy msgid "E807: Expected Float argument for printf()" -msgstr "E766: printf() çš„å‚æ•°ä¸è¶³" +msgstr "E807: 期盼浮点数作为printf()傿•°" #: ../message.c:3873 msgid "E767: Too many arguments to printf()" @@ -5675,9 +5669,9 @@ msgid "E781: .sug file doesn't match .spl file: %s" msgstr "E781: .sug 文件ä¸èƒ½åŒ¹é… .spl 文件: %s" #: ../spell.c:9305 -#, fuzzy, c-format +#, c-format msgid "E782: error while reading .sug file: %s" -msgstr "E47: 读å–错误文件失败" +msgstr "E782: 当读å–.sug 文件时错误" #. This should have been checked when generating the .spl #. file. @@ -5867,6 +5861,7 @@ msgstr "E410: 䏿£ç¡®çš„ :syntax å命令: %s" msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" +" 总 计 计 æ•° 匹 é… æœ€ æ…¢ çš„ å¹³ å‡ å å— æ¨¡ å¼" #: ../syntax.c:6146 msgid "E679: recursive loop loading syncolor.vim" @@ -5942,9 +5937,9 @@ msgstr "E424: 使用了太多ä¸åŒçš„高亮度属性" msgid "E669: Unprintable character in group name" msgstr "E669: 组åä¸å˜åœ¨ä¸å¯æ˜¾ç¤ºå—符" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 组åä¸å«æœ‰æ— 效å—符" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 组åä¸å«æœ‰æ— 效å—符" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" @@ -6103,14 +6098,13 @@ msgstr "Vim: 读错误,退出ä¸...\n" #. This happens when the FileChangedRO autocommand changes the #. * file in a way it becomes shorter. #: ../undo.c:379 -#, fuzzy msgid "E881: Line count changed unexpectedly" -msgstr "E787: æ„外地改å˜äº†ç¼“冲区" +msgstr "E881: 行数æ„外地改å˜äº†" #: ../undo.c:627 -#, fuzzy, c-format +#, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E212: æ— æ³•æ‰“å¼€å¹¶å†™å…¥æ–‡ä»¶" +msgstr "E828: æ— æ³•æ‰“å¼€æ’¤é”€æ–‡ä»¶åŽ»å†™å…¥" #: ../undo.c:717 #, c-format diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index e2fb2d39d4..e95b1e2cad 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -59,19 +59,19 @@ msgstr "無法傳é€å›žæ‡‰è¨Šæ¯" #: ../api/private/helpers.c:204 msgid "internal error: unknown option type" -msgstr "" +msgstr "內部錯誤: 未知的é¸é …類型" #: ../buffer.c:92 msgid "[Location List]" -msgstr "" +msgstr "[Location 列表]" #: ../buffer.c:93 msgid "[Quickfix List]" -msgstr "" +msgstr "[Quickfix 列表]" #: ../buffer.c:94 msgid "E855: Autocommands caused command to abort" -msgstr "" +msgstr "E855: è‡ªå‹•å‘½ä»¤å°Žè‡´å‘½ä»¤è¢«åœæ¢" #: ../buffer.c:135 msgid "E82: Cannot allocate any buffer, exiting..." @@ -349,7 +349,7 @@ msgstr "E103: ç·©è¡å€ \"%s\" 䏿˜¯åœ¨ diff 模å¼" #: ../diff.c:2193 msgid "E787: Buffer changed unexpectedly" -msgstr "" +msgstr "E787: æ„外地改變了緩è¡å€" #: ../digraph.c:1598 msgid "E104: Escape not allowed in digraph" @@ -365,7 +365,7 @@ msgstr "E105: 使用 :loadkeymap " #: ../digraph.c:1821 msgid "E791: Empty keymap entry" -msgstr "" +msgstr "E791: 空的éµä½æ˜ å°„é …" #: ../edit.c:82 msgid " Keyword completion (^N^P)" @@ -434,11 +434,11 @@ msgstr "已到段è½çµå°¾" #: ../edit.c:101 msgid "E839: Completion function changed window" -msgstr "" +msgstr "E839: è£œå…¨å‡½å¼æ›´æ”¹äº†çª—å£" #: ../edit.c:102 msgid "E840: Completion function deleted text" -msgstr "" +msgstr "E840: 補全函å¼åˆªé™¤äº†æ–‡æœ¬" #: ../edit.c:1847 msgid "'dictionary' option is empty" @@ -556,7 +556,7 @@ msgstr "E118: å‡½å¼ %s 的引數éŽå¤š" #: ../eval.c:148 #, c-format msgid "E716: Key not present in Dictionary: %s" -msgstr "" +msgstr "E716: éµåœ¨å—å…¸ä¸ä¸å˜åœ¨: %s" #: ../eval.c:150 #, c-format @@ -581,7 +581,7 @@ msgstr "E360: ä¸èƒ½ç”¨ -f é¸é …執行 shell" #: ../eval.c:154 #, c-format msgid "E734: Wrong variable type for %s=" -msgstr "" +msgstr "E734: 錯誤的變數類型: %s=" #: ../eval.c:155 #, fuzzy, c-format @@ -595,19 +595,19 @@ msgstr "E461: ä¸åˆæ³•的變數å稱: %s" #: ../eval.c:157 msgid "E806: using Float as a String" -msgstr "" +msgstr "E806: 使用浮點數作為å—串" #: ../eval.c:1830 msgid "E687: Less targets than List items" -msgstr "" +msgstr "E687: ç›®æ¨™æ¯”åˆ—è¡¨é …æ•¸å°‘" #: ../eval.c:1834 msgid "E688: More targets than List items" -msgstr "" +msgstr "E688: ç›®æ¨™æ¯”åˆ—è¡¨é …æ•¸å¤š" #: ../eval.c:1906 msgid "Double ; in list of variables" -msgstr "" +msgstr "變數列表出ç¾å…©å€‹ ;" #: ../eval.c:2078 #, fuzzy, c-format @@ -616,23 +616,23 @@ msgstr "E138: 無法寫入 viminfo 檔案 %s !" #: ../eval.c:2391 msgid "E689: Can only index a List or Dictionary" -msgstr "" +msgstr "E689: åªèƒ½ç´¢å¼•一個列表或者å—å…¸" #: ../eval.c:2396 msgid "E708: [:] must come last" -msgstr "" +msgstr "E708: [:] å¿…é ˆåœ¨æœ€å¾Œ" #: ../eval.c:2439 msgid "E709: [:] requires a List value" -msgstr "" +msgstr "E709: [:] 需è¦ä¸€å€‹åˆ—表值" #: ../eval.c:2674 msgid "E710: List value has more items than target" -msgstr "" +msgstr "E710: åˆ—è¡¨å€¼çš„é …æ¯”ç›®æ¨™å¤š" #: ../eval.c:2678 msgid "E711: List value has not enough items" -msgstr "" +msgstr "E711: åˆ—è¡¨å€¼æ²’æœ‰è¶³å¤ å¤šçš„é …" #: ../eval.c:2867 #, fuzzy @@ -651,7 +651,7 @@ msgstr "E108: ç„¡æ¤è®Šæ•¸: \"%s\"" #: ../eval.c:3333 msgid "E743: variable nested too deep for (un)lock" -msgstr "" +msgstr "E743: (un)lock çš„è®Šæ•¸åµŒå¥—éŽæ·±" #: ../eval.c:3630 msgid "E109: Missing ':' after '?'" @@ -659,7 +659,7 @@ msgstr "E109: '?' 後缺少 ':'" #: ../eval.c:3893 msgid "E691: Can only compare List with List" -msgstr "" +msgstr "E691: åªèƒ½æ¯”較列表和列表" #: ../eval.c:3895 #, fuzzy @@ -668,7 +668,7 @@ msgstr "E449: æ”¶åˆ°ä¸æ£ç¢ºçš„é‹ç®—å¼" #: ../eval.c:3915 msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "" +msgstr "E735: åªèƒ½æ¯”較å—典和å—å…¸" #: ../eval.c:3917 #, fuzzy @@ -677,7 +677,7 @@ msgstr "E116: å‡½å¼ %s çš„å¼•æ•¸ä¸æ£ç¢º" #: ../eval.c:3932 msgid "E693: Can only compare Funcref with Funcref" -msgstr "" +msgstr "E693: åªèƒ½æ¯”較Funcref å’Œ Funcref" #: ../eval.c:3934 #, fuzzy @@ -736,7 +736,7 @@ msgstr "E242: 找ä¸åˆ°é¡è‰²: %s" #: ../eval.c:6499 #, c-format msgid "E721: Duplicate key in Dictionary: \"%s\"" -msgstr "" +msgstr "E721: Dictionary ä¸å‡ºç¾é‡è¤‡çš„éµ: \"%s\"" #: ../eval.c:6517 #, fuzzy, c-format @@ -781,7 +781,7 @@ msgstr "E120: <SID> ä¸èƒ½åœ¨ script 本文外使用: %s" #: ../eval.c:7391 #, c-format msgid "E725: Calling dict function without Dictionary: %s" -msgstr "" +msgstr "E725: 調用å—典函å¼ä½†æ˜¯æ²’有å—å…¸: %s" #: ../eval.c:7453 #, fuzzy @@ -814,16 +814,15 @@ msgstr "E227: %s çš„ mapping 已經å˜åœ¨" #: ../eval.c:8692 msgid "extend() argument" -msgstr "" +msgstr "extend() åƒæ•¸" #: ../eval.c:8915 -#, fuzzy msgid "map() argument" -msgstr "vim [åƒæ•¸] " +msgstr "map() åƒæ•¸" #: ../eval.c:8916 msgid "filter() argument" -msgstr "" +msgstr "filter() åƒæ•¸" #: ../eval.c:9229 #, c-format @@ -857,19 +856,19 @@ msgstr "E596: 䏿£ç¢ºçš„å—åž‹" #: ../eval.c:11980 msgid "E726: Stride is zero" -msgstr "" +msgstr "E726: æ¥é•·ç‚ºé›¶" #: ../eval.c:11982 msgid "E727: Start past end" -msgstr "" +msgstr "E727: 起始值在終æ¢å€¼å¾Œ" #: ../eval.c:12024 ../eval.c:15297 msgid "<empty>" -msgstr "" +msgstr "<空>" #: ../eval.c:12282 msgid "remove() argument" -msgstr "" +msgstr "remove() åƒæ•¸" #: ../eval.c:12466 msgid "E655: Too many symbolic links (cycle?)" @@ -877,11 +876,11 @@ msgstr "E655: 太多層的符號éˆçµ(symlink) (循環?)" #: ../eval.c:12593 msgid "reverse() argument" -msgstr "" +msgstr "reverse() åƒæ•¸" #: ../eval.c:13721 msgid "sort() argument" -msgstr "" +msgstr "sort() åƒæ•¸" #: ../eval.c:13721 #, fuzzy @@ -895,7 +894,7 @@ msgstr "E237: ç„¡æ³•é¸æ“‡æ¤å°è¡¨æ©Ÿ" #: ../eval.c:13806 msgid "E882: Uniq compare function failed" -msgstr "" +msgstr "E882: Uniq 比較函å¼å¤±æ•—" #: ../eval.c:14085 msgid "(Invalid)" @@ -908,32 +907,31 @@ msgstr "E208: 寫入檔案 \"%s\" 錯誤" #: ../eval.c:16159 msgid "E805: Using a Float as a Number" -msgstr "" +msgstr "E805: å°‡æµ®é»žæ•¸ç•¶åšæ•¸å—使用" #: ../eval.c:16162 msgid "E703: Using a Funcref as a Number" -msgstr "" +msgstr "E703: 將函å¼ç•¶å𿕏å—使用" #: ../eval.c:16170 msgid "E745: Using a List as a Number" -msgstr "" +msgstr "E745: å°‡åˆ—è¡¨ç•¶åšæ•¸å—使用" #: ../eval.c:16173 msgid "E728: Using a Dictionary as a Number" -msgstr "" +msgstr "E728: å°‡å—å…¸ç•¶åšæ•¸å—使用" #: ../eval.c:16259 msgid "E729: using Funcref as a String" -msgstr "" +msgstr "E729: 將函å¼ç•¶åšå—串使用" #: ../eval.c:16262 -#, fuzzy msgid "E730: using List as a String" -msgstr "E374: æ ¼å¼åŒ–å—串裡少了 ]" +msgstr "E730: 將列表當åšå—串使用" #: ../eval.c:16265 msgid "E731: using Dictionary as a String" -msgstr "" +msgstr "E731: å°‡å—典當åšå—串使用" #: ../eval.c:16619 #, fuzzy, c-format @@ -953,12 +951,12 @@ msgstr "E128: 函å¼åç¨±ç¬¬ä¸€å€‹å—æ¯å¿…é ˆå¤§å¯«: %s" #: ../eval.c:16732 #, c-format msgid "E705: Variable name conflicts with existing function: %s" -msgstr "" +msgstr "E705: 變數å與已有函å¼åè¡çª: %s" #: ../eval.c:16763 #, c-format msgid "E741: Value is locked: %s" -msgstr "" +msgstr "E741: 值已鎖定: %s" #: ../eval.c:16764 ../eval.c:16769 ../message.c:1839 msgid "Unknown" @@ -971,7 +969,7 @@ msgstr "E284: ä¸èƒ½è¨å®š IC 數值" #: ../eval.c:16838 msgid "E698: variable nested too deep for making a copy" -msgstr "" +msgstr "E698: è®Šæ•¸åµŒå¥—éŽæ·±ç„¡æ³•複製" #: ../eval.c:17249 #, c-format @@ -1216,7 +1214,7 @@ msgstr "è¦è¦†å¯«å·²å˜åœ¨çš„æª”案 \"%.*s\"?" #: ../ex_cmds.c:2317 #, c-format msgid "Swap file \"%s\" exists, overwrite anyway?" -msgstr "" +msgstr "äº¤æ›æ–‡ä»¶ \"%s\" å·²å˜åœ¨ï¼Œç¢ºå¯¦éœ€è¦è¦†è“‹å—Žï¼Ÿ" #: ../ex_cmds.c:2326 #, fuzzy, c-format @@ -1456,7 +1454,7 @@ msgstr "%3d %s %s 第 %<PRId64> 行 " #: ../ex_cmds2.c:942 msgid "E750: First use \":profile start {fname}\"" -msgstr "" +msgstr "E750: 請先使用 :profile start <fname>" #: ../ex_cmds2.c:1269 #, fuzzy, c-format @@ -1555,7 +1553,7 @@ msgstr "vim [åƒæ•¸] " #: ../ex_cmds2.c:2771 msgid "environment variable" -msgstr "" +msgstr "環境變數" #: ../ex_cmds2.c:2773 #, fuzzy @@ -2047,7 +2045,7 @@ msgstr "E199: 已刪除掉作用ä¸çš„視窗或暫å˜å€" #: ../file_search.c:203 msgid "E854: path too long for completion" -msgstr "" +msgstr "E854: 補全用的路徑太長了" #: ../file_search.c:446 #, c-format @@ -2099,11 +2097,11 @@ msgstr "[未命å]" #: ../fileio.c:511 msgid "[New DIRECTORY]" -msgstr "" +msgstr "[新目錄]" #: ../fileio.c:529 ../fileio.c:532 msgid "[File too big]" -msgstr "" +msgstr "[文件太大]" #: ../fileio.c:534 msgid "[Permission Denied]" @@ -2265,7 +2263,7 @@ msgstr "E513: 無法寫入 -- 轉æ›å¤±æ•—" msgid "" "E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty to " "override)" -msgstr "" +msgstr "E513: 寫入錯誤,轉æ›å¤±æ•— (è«‹å°‡ 'fenc' 置空以強制執行)" #: ../fileio.c:3448 msgid "E514: write error (file system full?)" @@ -2720,7 +2718,7 @@ msgstr "E49: 錯誤的æ²å‹•大å°" #: ../globals.h:1021 msgid "E901: Job table is full" -msgstr "" +msgstr "E901: 任務表已經滿" #: ../globals.h:1024 #, c-format @@ -2877,7 +2875,7 @@ msgstr "E42: 沒有錯誤" #: ../globals.h:1067 msgid "E776: No location list" -msgstr "" +msgstr "E776: 沒有ä½ç½®åˆ—表" #: ../globals.h:1068 msgid "E43: Damaged match string" @@ -2992,7 +2990,7 @@ msgstr "E473: 內部錯誤" #: ../globals.h:1104 msgid "E363: pattern uses more memory than 'maxmempattern'" -msgstr "" +msgstr "E363: 表é”å¼çš„å…§å˜è¶…出 'maxmempattern'" #: ../globals.h:1105 #, fuzzy @@ -3097,15 +3095,15 @@ msgstr "E621: \"%s\" è³‡æºæª”版本錯誤" #: ../hardcopy.c:2225 msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" +msgstr "E673: ä¸å…¼å®¹çš„多å—節編碼和å—元集" #: ../hardcopy.c:2238 msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" +msgstr "E674: printmbcharset 在多å—節編碼下ä¸èƒ½ç‚ºç©º" #: ../hardcopy.c:2254 msgid "E675: No default font specified for multi-byte printing." -msgstr "" +msgstr "E675: 沒有指定多å—節打å°çš„默èªå—åž‹" #: ../hardcopy.c:2426 msgid "E324: Can't open PostScript output file" @@ -3267,6 +3265,7 @@ msgstr "%-5s: %-30s (用法: %s)" #: ../if_cscope.c:1155 msgid "" "\n" +" a: Find assignments to this symbol\n" " c: Find functions calling this function\n" " d: Find functions called by this function\n" " e: Find this egrep pattern\n" @@ -3276,6 +3275,16 @@ msgid "" " s: Find this C symbol\n" " t: Find this text string\n" msgstr "" +"\n" +" a: æœç´¢å°æ¤ç¬¦è™Ÿçš„賦值\n" +" c: æœç´¢èª¿ç”¨æ¤å‡½å¼çš„函å¼\n" +" d: æœç´¢æ¤å‡½å¼èª¿ç”¨çš„函å¼\n" +" e: æœç´¢æ¤ egrep 模å¼\n" +" f: æœç´¢æ¤æ–‡ä»¶\n" +" g: æœç´¢æ¤å®šç¾©\n" +" i: æœç´¢åŒ…嫿¤æ–‡ä»¶çš„æ–‡ä»¶\n" +" s: æœç´¢æ¤ C 符å·\n" +" t: æœç´¢æ¤æ–‡æœ¬å—串\n" #: ../if_cscope.c:1226 msgid "E568: duplicate cscope database not added" @@ -3509,7 +3518,7 @@ msgstr "-N\t\t\t'nocompatible' ä¸å®Œå…¨èˆ‡å‚³çµ± Vi 相容,å¯ä½¿ç”¨ Vim åŠ å #: ../main.c:2215 msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" -msgstr "" +msgstr "-V[N][fname]\t\t詳細 [level N] [log messages to fname]" #: ../main.c:2216 msgid "-D\t\t\tDebugging mode" @@ -3602,7 +3611,7 @@ msgstr "-W <scriptout>\tå°æª”案 <scriptout> 寫入所有輸入的命令" #: ../main.c:2240 msgid "--startuptime <file>\tWrite startup timing messages to <file>" -msgstr "" +msgstr "--startuptime <file>\t將啟動時間寫入到文件 <file>" #: ../main.c:2242 msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" @@ -3794,7 +3803,7 @@ msgstr "" #: ../memline.c:945 msgid " has been damaged (page size is smaller than minimum value).\n" -msgstr "" +msgstr "å·²æŸå(é é¢å¤§å°å°æ–¼æœ€å°å€¼ï¼‰ã€‚\n" #: ../memline.c:974 #, c-format @@ -4082,7 +4091,7 @@ msgstr "E317: 指標å€å¡Š id 錯 2" #: ../memline.c:3070 #, c-format msgid "E773: Symlink loop for \"%s\"" -msgstr "" +msgstr "E773: \"%s\" ç¬¦è™ŸéˆæŽ¥å‡ºç¾å¾ªç’°" #: ../memline.c:3221 msgid "E325: ATTENTION" @@ -4231,7 +4240,7 @@ msgstr "E329: 沒有那樣的é¸å–®" #. Only a mnemonic or accelerator is not valid. #: ../menu.c:329 msgid "E792: Empty menu name" -msgstr "" +msgstr "E792: 空的èœå–®å稱" #: ../menu.c:340 msgid "E330: Menu path must not lead to a sub-menu" @@ -4312,7 +4321,7 @@ msgstr "-- 尚有 --" #: ../message.c:2398 msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit " -msgstr "" +msgstr " ç©ºæ ¼/d/j: å±å¹•/é /行 下翻,b/u/k: 上翻,q: 退出 " #: ../message.c:3021 ../message.c:3031 msgid "Question" @@ -4357,7 +4366,7 @@ msgstr "E116: å‡½å¼ %s çš„å¼•æ•¸ä¸æ£ç¢º" #: ../message.c:3119 msgid "E807: Expected Float argument for printf()" -msgstr "" +msgstr "E807: 期盼浮點數作為printf()åƒæ•¸" #: ../message.c:3873 #, fuzzy @@ -4370,11 +4379,11 @@ msgstr "W10: 注æ„: ä½ æ£åœ¨ä¿®æ”¹ä¸€å€‹å”¯è®€æª”" #: ../misc1.c:2537 msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "" +msgstr "請輸入數å—並<Enter>æˆ–é»žæ“Šé¼ æ¨™ï¼ˆç©ºç™½å–æ¶ˆï¼‰: " #: ../misc1.c:2539 msgid "Type number and <Enter> (empty cancels): " -msgstr "" +msgstr "è«‹é¸æ“‡æ•¸å—並(<Enter> å–æ¶ˆ): " #: ../misc1.c:2585 msgid "1 more line" @@ -4400,7 +4409,7 @@ msgstr " (已䏿–·)" #: ../misc1.c:2635 msgid "Beep!" -msgstr "" +msgstr "Beep!" #: ../misc2.c:738 #, c-format @@ -4617,7 +4626,7 @@ msgstr "E520: ä¸èƒ½åœ¨ Modeline 裡出ç¾" #: ../option.c:2815 msgid "E846: Key code not set" -msgstr "" +msgstr "E846: 未è¨ç½®éµä½ä»£ç¢¼" #: ../option.c:2924 msgid "E521: Number required after =" @@ -4642,11 +4651,11 @@ msgstr "E589: 'backupext' è·Ÿ 'patchmode' 是一樣的" #: ../option.c:3964 msgid "E834: Conflicts with value of 'listchars'" -msgstr "" +msgstr "E834: 與'listchars'ä¸çš„值發生è¡çª" #: ../option.c:3966 msgid "E835: Conflicts with value of 'fillchars'" -msgstr "" +msgstr "E835: 與'fillchars'ä¸çš„值發生è¡çª" #: ../option.c:4163 msgid "E524: Missing colon" @@ -4884,7 +4893,7 @@ msgstr "E382: 無法寫入,'buftype' é¸é …å·²è¨å®š" #: ../quickfix.c:2812 msgid "E683: File name missing or invalid pattern" -msgstr "" +msgstr "E683: ç¼ºå°‘æ–‡ä»¶åæˆ–模å¼ç„¡æ•ˆ" #: ../quickfix.c:2911 #, fuzzy, c-format @@ -5022,25 +5031,26 @@ msgid "" "E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " "used " msgstr "" +"E864: \\%#= 後é¢åªèƒ½æ˜¯0,1,或者2。自動引擎將會被使用" #: ../regexp_nfa.c:239 msgid "E865: (NFA) Regexp end encountered prematurely" -msgstr "" +msgstr "E865: (NFA) éŽæ—©çš„é‡åˆ°äº†æ£å‰‡è¡¨é”å¼çš„çµå°¾" #: ../regexp_nfa.c:240 #, c-format msgid "E866: (NFA regexp) Misplaced %c" -msgstr "" +msgstr "E866: (NFA regexp) %c 放錯了ä½ç½®" #: ../regexp_nfa.c:242 #, c-format msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" -msgstr "" +msgstr "E877: (NFA regexp) ä¸å¯ç”¨çš„å—元類: %<PRId64>" #: ../regexp_nfa.c:1261 #, c-format msgid "E867: (NFA) Unknown operator '\\z%c'" -msgstr "" +msgstr "E867: (NFA) 未知的æ“作符 '\\z%c'" #: ../regexp_nfa.c:1387 #, c-format @@ -5050,21 +5060,21 @@ msgstr "" #: ../regexp_nfa.c:1802 #, c-format msgid "E869: (NFA) Unknown operator '\\@%c'" -msgstr "" +msgstr "E869: (NFA) 未知的æ“作符 '\\%%%c'" #: ../regexp_nfa.c:1831 msgid "E870: (NFA regexp) Error reading repetition limits" -msgstr "" +msgstr "E870: (NFA regexp) 读å–é‡å¤é™åˆ¶æ—¶å‡ºé”™" #. Can't have a multi follow a multi. #: ../regexp_nfa.c:1895 msgid "E871: (NFA regexp) Can't have a multi follow a multi !" -msgstr "" +msgstr "E871: (NFA regexp) ä¸èƒ½å¤šä¸ªè·Ÿå¤šä¸ªï¼" #. Too many `(' #: ../regexp_nfa.c:2037 msgid "E872: (NFA regexp) Too many '('" -msgstr "" +msgstr "E872: (NFA regexp) 太多 '('" #: ../regexp_nfa.c:2042 #, fuzzy @@ -5073,31 +5083,32 @@ msgstr "E50: 太多 \\z(" #: ../regexp_nfa.c:2066 msgid "E873: (NFA regexp) proper termination error" -msgstr "" +msgstr "E873: (NFA regexp) 未é©ç•¶çµ‚æ¢" #: ../regexp_nfa.c:2599 msgid "E874: (NFA) Could not pop the stack !" -msgstr "" +msgstr "E874: (NFA) 無法出棧ï¼" #: ../regexp_nfa.c:3298 msgid "" "E875: (NFA regexp) (While converting from postfix to NFA), too many states " "left on stack" -msgstr "" +msgstr "E875: (NFA regexp) (從後綴轉到 NFA 时),棧上éºç•™äº†å¤ªå¤šç‹€æ…‹" #: ../regexp_nfa.c:3302 msgid "E876: (NFA regexp) Not enough space to store the whole NFA " -msgstr "" +msgstr "E876: (NFA regexp) æ²’æœ‰è¶³å¤ çš„ç©ºé–“å˜å„²NFA " #: ../regexp_nfa.c:4571 ../regexp_nfa.c:4869 msgid "" "Could not open temporary log file for writing, displaying on stderr ... " msgstr "" +"無法打開臨時日志文件進行寫入,顯示在stderrä¸..." #: ../regexp_nfa.c:4840 #, c-format msgid "(NFA) COULD NOT OPEN %s !" -msgstr "" +msgstr "(NFA) ä¸èƒ½æ‰“å¼€ %s !" #: ../regexp_nfa.c:6049 #, fuzzy @@ -5270,17 +5281,17 @@ msgstr "E297: æš«å˜æª”寫入錯誤" #: ../spell.c:952 msgid "E758: Truncated spell file" -msgstr "" +msgstr "E758: 已截斷的拼寫文件" #: ../spell.c:953 #, c-format msgid "Trailing text in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,多餘的後續文本: %s" #: ../spell.c:954 #, c-format msgid "Affix name too long in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d è¡Œï¼Œé™„åŠ é …åå—太長: %s" #: ../spell.c:955 #, fuzzy @@ -5289,20 +5300,20 @@ msgstr "E431: Tag 檔 \"%s\" æ ¼å¼éŒ¯èª¤" #: ../spell.c:957 msgid "E762: Character in FOL, LOW or UPP is out of range" -msgstr "" +msgstr "E762: FOLã€LOW 或 UPP ä¸å—元超出範åœ" #: ../spell.c:958 msgid "Compressing word tree..." -msgstr "" +msgstr "壓縮單詞樹……" #: ../spell.c:1951 msgid "E756: Spell checking is not enabled" -msgstr "" +msgstr "E756: 拼寫檢查未啟用" #: ../spell.c:2249 #, c-format msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" -msgstr "" +msgstr "è¦å‘Š: 找ä¸åˆ°å–®è©žåˆ—表 \"%s.%s.spl\" or \"%s.ascii.spl\"" #: ../spell.c:2473 #, fuzzy, c-format @@ -5316,11 +5327,11 @@ msgstr "E307: %s 看起來ä¸åƒæ˜¯ Vim æš«å˜æª”" #: ../spell.c:2501 msgid "E771: Old spell file, needs to be updated" -msgstr "" +msgstr "E771: èˆŠçš„æ‹¼å¯«æ–‡ä»¶ï¼Œéœ€è¦æ›´æ–°" #: ../spell.c:2504 msgid "E772: Spell file is for newer version of Vim" -msgstr "" +msgstr "E772: 為更高版本的 Vim 所使用的拼寫文件" #: ../spell.c:2602 #, fuzzy @@ -5340,66 +5351,68 @@ msgstr "æœå°‹ tag 檔案 \"%s\"" #: ../spell.c:4589 ../spell.c:5635 ../spell.c:6140 #, c-format msgid "Conversion failure for word in %s line %d: %s" -msgstr "" +msgstr "單詞 %s 轉æ›å¤±æ•—,第 %d 行: %s" #: ../spell.c:4630 ../spell.c:6170 #, c-format msgid "Conversion in %s not supported: from %s to %s" -msgstr "" +msgstr "䏿”¯æŒ %s ä¸çš„轉æ›: 从 %s 到 %s" #: ../spell.c:4642 #, c-format msgid "Invalid value for FLAG in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,FLAG çš„å€¼æ— æ•ˆ: %s" #: ../spell.c:4655 #, c-format msgid "FLAG after using flags in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d è¡Œï¼Œåœ¨ä½¿ç”¨æ¨™å¿—å¾Œå‡ºç¾ FLAG: %s" #: ../spell.c:4723 #, c-format msgid "" "Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "" +msgstr "在 PFX é …ä¹‹å¾Œå®šç¾© COMPOUNDFORBIDFLAG (%s 第%d行)å¯èƒ½æœƒçµ¦å‡ºçš„éŒ¯èª¤çµæžœ" +"%d" #: ../spell.c:4731 #, c-format msgid "" "Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "" +msgstr "在 PFX é …ä¹‹å¾Œå®šç¾© COMPOUNDFORBIDFLAG (%s 第%d行)å¯èƒ½æœƒçµ¦å‡ºçš„éŒ¯èª¤çµæžœ" +"%d" #: ../spell.c:4747 #, c-format msgid "Wrong COMPOUNDRULES value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" #: ../spell.c:4771 #, c-format msgid "Wrong COMPOUNDWORDMAX value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDWORDMAX 值: %s" #: ../spell.c:4777 #, c-format msgid "Wrong COMPOUNDMIN value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" #: ../spell.c:4783 #, c-format msgid "Wrong COMPOUNDSYLMAX value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDSYLMAX 值: %s" #: ../spell.c:4795 #, c-format msgid "Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 CHECKCOMPOUNDPATTERN 值: %s" #: ../spell.c:4847 #, c-format msgid "Different combining flag in continued affix block in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d è¡Œï¼Œåœ¨é€£çºŒçš„é™„åŠ å¡Šç¨®å‡ºç¾ä¸åŒçš„çµ„åˆæ¨™èªŒ: %s" #: ../spell.c:4850 #, fuzzy, c-format @@ -5412,45 +5425,47 @@ msgid "" "Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s " "line %d: %s" msgstr "" +"%s 第 %d è¡Œï¼Œé™„åŠ é …è¢« BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST 使" +"用: %s" #: ../spell.c:4893 #, c-format msgid "Expected Y or N in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,æ¤è™•éœ€è¦ Y 或 N: %s" #: ../spell.c:4968 #, c-format msgid "Broken condition in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,錯誤的æ¢ä»¶: %s" #: ../spell.c:5091 #, c-format msgid "Expected REP(SAL) count in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,æ¤è™•éœ€è¦ REP(SAL) 計數" #: ../spell.c:5120 #, c-format msgid "Expected MAP count in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,æ¤è™•éœ€è¦ MAP 計數" #: ../spell.c:5132 #, c-format msgid "Duplicate character in MAP in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,MAP ä¸å˜åœ¨é‡è¤‡çš„å—å…ƒ" #: ../spell.c:5176 #, c-format msgid "Unrecognized or duplicate item in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,無法è˜åˆ¥æˆ–é‡è¤‡çš„é …: %s" #: ../spell.c:5197 #, c-format msgid "Missing FOL/LOW/UPP line in %s" -msgstr "" +msgstr "%s ä¸ç¼ºå°‘ FOL/LOW/UPP 行" #: ../spell.c:5220 msgid "COMPOUNDSYLMAX used without SYLLABLE" -msgstr "" +msgstr "在没有 SYLLABLE 的情æ³ä¸‹ä½¿ç”¨äº† COMPOUNDSYLMAX" #: ../spell.c:5236 #, fuzzy @@ -5464,32 +5479,32 @@ msgstr "å¤ªå¤šç·¨è¼¯åƒæ•¸" #: ../spell.c:5240 msgid "Too many postponed prefixes and/or compound flags" -msgstr "" +msgstr "太多延é²å‰ç¶´å’Œ/æˆ–çµ„åˆæ¨™èªŒ" #: ../spell.c:5250 #, c-format msgid "Missing SOFO%s line in %s" -msgstr "" +msgstr "%s ä¸ç¼ºå°‘ SOFO%s 行" #: ../spell.c:5253 #, c-format msgid "Both SAL and SOFO lines in %s" -msgstr "" +msgstr "%s åŒæ™‚å‡ºç¾ SAL å’Œ SOFO 行" #: ../spell.c:5331 #, c-format msgid "Flag is not a number in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d è¡Œï¼Œæ¨™èªŒä¸æ˜¯æ•¸å—: %s" #: ../spell.c:5334 #, c-format msgid "Illegal flag in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,無效的標誌: %s" #: ../spell.c:5493 ../spell.c:5501 #, c-format msgid "%s value differs from what is used in another .aff file" -msgstr "" +msgstr "%s 的值與å¦ä¸€å€‹ .aff 文件ä¸ä½¿ç”¨çš„值ä¸ç›¸åŒ" #: ../spell.c:5602 #, fuzzy, c-format @@ -5499,12 +5514,12 @@ msgstr "掃瞄å—å…¸: %s" #: ../spell.c:5611 #, c-format msgid "E760: No word count in %s" -msgstr "" +msgstr "E760: %s 䏿²¡æœ‰å–®è©žè¨ˆæ•¸" #: ../spell.c:5669 #, c-format msgid "line %6d, word %6d - %s" -msgstr "" +msgstr "第 %6d 行,第 %6d 个單詞 - %s" #: ../spell.c:5691 #, fuzzy, c-format @@ -5514,17 +5529,17 @@ msgstr "æ¯ä¸€è¡Œéƒ½æ‰¾ä¸åˆ°: %s" #: ../spell.c:5694 #, c-format msgid "First duplicate word in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,首次出ç¾é‡è¤‡çš„單詞: %s" #: ../spell.c:5746 #, c-format msgid "%d duplicate word(s) in %s" -msgstr "" +msgstr "å˜åœ¨ %d 个é‡è¤‡çš„單詞,在 %s ä¸" #: ../spell.c:5748 #, c-format msgid "Ignored %d word(s) with non-ASCII characters in %s" -msgstr "" +msgstr "å¿½ç•¥äº†å«æœ‰éž ASCII å—元的 %d 个單詞,在 %s ä¸" #: ../spell.c:6115 #, fuzzy, c-format @@ -5534,42 +5549,42 @@ msgstr "從標準輸入讀å–..." #: ../spell.c:6155 #, c-format msgid "Duplicate /encoding= line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %ld 行,é‡å¤çš„ /encoding= 行已被忽略: %s" #: ../spell.c:6159 #, c-format msgid "/encoding= line after word ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,å•è¯åŽçš„ /encoding= 行已被忽略: %s" #: ../spell.c:6180 #, c-format msgid "Duplicate /regions= line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,é‡å¤çš„ /regions= 行已被忽略: %s" #: ../spell.c:6185 #, c-format msgid "Too many regions in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,太多å€åŸŸ: %s" #: ../spell.c:6198 #, c-format msgid "/ line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,/ 行已被忽略: %s" #: ../spell.c:6224 #, fuzzy, c-format msgid "Invalid region nr in %s line %d: %s" -msgstr "E573: 䏿£ç¢ºçš„伺æœå™¨ id : %s" +msgstr "%s 第 %d 行,無效的å€åŸŸè™Ÿ: %s" #: ../spell.c:6230 #, c-format msgid "Unrecognized flags in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,ä¸å¯è˜åˆ¥çš„æ¨™èªŒ: %s" #: ../spell.c:6257 #, c-format msgid "Ignored %d words with non-ASCII characters" -msgstr "" +msgstr "å¿½ç•¥äº†å«æœ‰éž ASCII å—元的 %d 个單詞" #: ../spell.c:6656 #, c-format @@ -5578,23 +5593,23 @@ msgstr "" #: ../spell.c:7340 msgid "Reading back spell file..." -msgstr "" +msgstr "è¯»å–æ‹¼å¯«æ–‡ä»¶â€¦â€¦" #. Go through the trie of good words, soundfold each word and add it to #. the soundfold trie. #: ../spell.c:7357 msgid "Performing soundfolding..." -msgstr "" +msgstr "æ£åœ¨ soundfolding……" #: ../spell.c:7368 #, c-format msgid "Number of words after soundfolding: %<PRId64>" -msgstr "" +msgstr "soundfolding åŽçš„單詞数: %<PRId64>" #: ../spell.c:7476 #, c-format msgid "Total number of words: %d" -msgstr "" +msgstr "單詞总数: %d" #: ../spell.c:7655 #, fuzzy, c-format @@ -5604,11 +5619,11 @@ msgstr "寫入 viminfo 檔案 \"%s\" ä¸" #: ../spell.c:7707 ../spell.c:7927 #, c-format msgid "Estimated runtime memory use: %d bytes" -msgstr "" +msgstr "估計é‹è¡Œæ™‚的內å˜ç”¨é‡: %d ä½å…ƒ" #: ../spell.c:7820 msgid "E751: Output file name must not have region name" -msgstr "" +msgstr "E751: 輸出文件ä¸èƒ½å«æœ‰å€åŸŸå" #: ../spell.c:7822 #, fuzzy @@ -5622,7 +5637,7 @@ msgstr "E15: 䏿£ç¢ºçš„é‹ç®—å¼: %s" #: ../spell.c:7907 msgid "Warning: both compounding and NOBREAK specified" -msgstr "" +msgstr "è¦å‘Š: åŒæ™‚指定了 compounding å’Œ NOBREAK" #: ../spell.c:7920 #, fuzzy, c-format @@ -5631,30 +5646,30 @@ msgstr "寫入 viminfo 檔案 \"%s\" ä¸" #: ../spell.c:7925 msgid "Done!" -msgstr "" +msgstr "完æˆï¼" #: ../spell.c:8034 #, c-format msgid "E765: 'spellfile' does not have %<PRId64> entries" -msgstr "" +msgstr "E765: 'spellfile' 没有 %<PRId64> é …" #: ../spell.c:8074 #, c-format msgid "Word '%.*s' removed from %s" -msgstr "" +msgstr "从 %s ä¸åˆ 除了單詞" #: ../spell.c:8117 #, c-format msgid "Word '%.*s' added to %s" -msgstr "" +msgstr "å‘ %s 䏿·»åŠ äº†å–®è©ž" #: ../spell.c:8381 msgid "E763: Word characters differ between spell files" -msgstr "" +msgstr "E763: 拼寫文件之間的å—å…ƒä¸ç›¸åŒ" #: ../spell.c:8684 msgid "Sorry, no suggestions" -msgstr "" +msgstr "抱æ‰ï¼Œæ²¡æœ‰å»ºè®®" #: ../spell.c:8687 #, fuzzy, c-format @@ -5671,7 +5686,7 @@ msgstr "將變動å˜å„²è‡³ \"%.*s\"?" #: ../spell.c:8737 #, c-format msgid " < \"%.*s\"" -msgstr "" +msgstr " < \"%.*s\"" #: ../spell.c:8882 #, fuzzy @@ -5691,28 +5706,28 @@ msgstr "E307: %s 看起來ä¸åƒæ˜¯ Vim æš«å˜æª”" #: ../spell.c:9282 #, c-format msgid "E779: Old .sug file, needs to be updated: %s" -msgstr "" +msgstr "E779: 舊的.sug æ–‡ä»¶ï¼Œéœ€è¦æ›´æ–°: %s" #: ../spell.c:9286 #, c-format msgid "E780: .sug file is for newer version of Vim: %s" -msgstr "" +msgstr "E780: .sug 文件é©ç”¨æ–¼è¼ƒæ–°çš„ Vim 版本: %s" #: ../spell.c:9295 #, c-format msgid "E781: .sug file doesn't match .spl file: %s" -msgstr "" +msgstr "E781: .sug 文件ä¸èƒ½åŒ¹é… .spl 文件: %s" #: ../spell.c:9305 #, fuzzy, c-format msgid "E782: error while reading .sug file: %s" -msgstr "E47: 讀å–錯誤檔案失敗" +msgstr "E782: 當讀å–.sug 文件時錯誤" #. This should have been checked when generating the .spl #. file. #: ../spell.c:11575 msgid "E783: duplicate char in MAP entry" -msgstr "" +msgstr "E783: MAP æ¢ç›®ä¸æœ‰é‡è¤‡çš„å—å…ƒ" #: ../syntax.c:266 msgid "No Syntax items defined for this buffer" @@ -5894,10 +5909,11 @@ msgstr "E410: 䏿£ç¢ºçš„ :syntax å命令: %s" msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" +" 總 計 計 數 匹 é… æœ€ æ…¢ çš„ å¹³ å‡ å å— æ¨¡ å¼" #: ../syntax.c:6146 msgid "E679: recursive loop loading syncolor.vim" -msgstr "" +msgstr "E679: åŠ è¼‰ syncolor.vim 时出ç¾åµŒå¥—循環" #: ../syntax.c:6256 #, c-format @@ -5969,13 +5985,13 @@ msgstr "E424: 使用了éŽå¤šç›¸ç•°çš„高亮度屬性" msgid "E669: Unprintable character in group name" msgstr "E669: 群組åç¨±ä¸æœ‰ç„¡æ³•列å°çš„å—å…ƒ" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 群組åç¨±ä¸æœ‰ä¸æ£ç¢ºçš„å—å…ƒ" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 群組åç¨±ä¸æœ‰ä¸æ£ç¢ºçš„å—å…ƒ" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" -msgstr "" +msgstr "E849: 高亮和語法組éŽå¤š" #: ../tag.c:104 msgid "E555: at bottom of tag stack" @@ -6083,7 +6099,7 @@ msgstr "E435: 找ä¸åˆ° tag, 用猜的!" #: ../tag.c:2797 #, c-format msgid "Duplicate field name: %s" -msgstr "" +msgstr "é‡è¤‡çš„å—æ®µå: %s" #: ../term.c:1442 msgid "' not known. Available builtin terminals are:" @@ -6131,35 +6147,35 @@ msgstr "Vim: 讀å–輸入錯誤,離開ä¸...\n" #. * file in a way it becomes shorter. #: ../undo.c:379 msgid "E881: Line count changed unexpectedly" -msgstr "" +msgstr "E881: 行數æ„外地改變了" #: ../undo.c:627 -#, fuzzy, c-format +#, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E212: 無法以寫入模å¼é–‹å•Ÿ" +msgstr "E828: 無法打開撤銷文件去寫入" #: ../undo.c:717 #, c-format msgid "E825: Corrupted undo file (%s): %s" -msgstr "" +msgstr "E825: å·²æå£žçš„æ’¤éŠ·æ–‡ä»¶ (%s): %s" #: ../undo.c:1039 msgid "Cannot write undo file in any directory in 'undodir'" -msgstr "" +msgstr "ä¸èƒ½å¯«å…¥æ’¤éŠ·æ–‡ä»¶åˆ° 'undodir' ä¸çš„任何文件夾" #: ../undo.c:1074 #, c-format msgid "Will not overwrite with undo file, cannot read: %s" -msgstr "" +msgstr "ä¸èƒ½å¯«å…¥æ’¤éŠ·æ–‡ä»¶ï¼Œä¸å¯è®€å–: %s" #: ../undo.c:1092 #, c-format msgid "Will not overwrite, this is not an undo file: %s" -msgstr "" +msgstr "䏿œƒè¦†è“‹ï¼Œé€™ä¸æ˜¯æ’¤éŠ·æ–‡ä»¶: %s" #: ../undo.c:1108 msgid "Skipping undo file write, nothing to undo" -msgstr "" +msgstr "è·³éŽæ’¤æ¶ˆæ–‡ä»¶å¯«å…¥ï¼Œæ²’æœ‰å¯æ’¤æ¶ˆçš„內容" #: ../undo.c:1121 #, fuzzy, c-format @@ -6174,7 +6190,7 @@ msgstr "E297: æš«å˜æª”寫入錯誤" #: ../undo.c:1280 #, c-format msgid "Not reading undo file, owner differs: %s" -msgstr "" +msgstr "ä¸èƒ½è®€å–æ’¤éŠ·æ–‡ä»¶ï¼Œæ“æœ‰è€…ä¸åŒ: %s" #: ../undo.c:1292 #, fuzzy, c-format @@ -6198,7 +6214,7 @@ msgstr "E484: 無法開啟檔案 %s" #: ../undo.c:1328 msgid "File contents changed, cannot use undo info" -msgstr "" +msgstr "文件內容已經改變,ä¸èƒ½ä½¿ç”¨æ’¤éŠ·ä¿¡æ¯" #: ../undo.c:1497 #, fuzzy, c-format @@ -6207,11 +6223,11 @@ msgstr "çµæŸåŸ·è¡Œ %s" #: ../undo.c:1586 ../undo.c:1812 msgid "Already at oldest change" -msgstr "" +msgstr "已經在最早的改變" #: ../undo.c:1597 ../undo.c:1814 msgid "Already at newest change" -msgstr "" +msgstr "已經在最新的改變" #: ../undo.c:1806 #, fuzzy, c-format @@ -6259,11 +6275,11 @@ msgstr "%<PRId64> 行 %s éŽ %d 次" #: ../undo.c:2228 msgid "before" -msgstr "" +msgstr "之å‰" #: ../undo.c:2228 msgid "after" -msgstr "" +msgstr "之後" #: ../undo.c:2325 #, fuzzy @@ -6272,7 +6288,7 @@ msgstr "沒有這個 mapping å°æ‡‰" #: ../undo.c:2330 msgid "number changes when saved" -msgstr "" +msgstr " 編號 改變 時間 ä¿å˜" #: ../undo.c:2360 #, fuzzy, c-format diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmenu.c index 746429e5d5..0d9080ceb7 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmenu.c @@ -1,7 +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 -/// @file popupmnu.c +/// @file popupmenu.c /// /// Popup menu (PUM) @@ -13,17 +13,19 @@ #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" #include "nvim/memory.h" #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/ui.h" @@ -55,7 +57,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 @@ -154,7 +156,6 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_external) { if (array_changed) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array arr = arena_array(&arena, (size_t)size); for (int i = 0; i < size; i++) { Array item = arena_array(&arena, 4); @@ -166,7 +167,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col, pum_anchor_grid); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } else { ui_call_popupmenu_select(selected); return; @@ -463,7 +464,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; @@ -483,7 +484,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; @@ -523,7 +524,7 @@ void pum_redraw(void) } if (pum_rl) { - char *rt = (char *)reverse_text(st); + char *rt = reverse_text((char *)st); char *rt_start = rt; int size = vim_strsize(rt); @@ -541,14 +542,13 @@ void pum_redraw(void) size++; } } - grid_puts_len(&pum_grid, (char_u *)rt, (int)STRLEN(rt), row, - grid_col - size + 1, attr); + grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, grid_col - size + 1, attr); xfree(rt_start); xfree(st); grid_col -= width; } else { // use grid_puts_len() to truncate the text - grid_puts(&pum_grid, st, row, grid_col, attr); + grid_puts(&pum_grid, (char *)st, row, grid_col, attr); xfree(st); grid_col += width; } @@ -559,11 +559,11 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col - 1, + grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1, attr); grid_col -= 2; } else { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col, attr); + grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr); grid_col += 2; } totwidth += 2; @@ -639,11 +639,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; @@ -702,7 +702,7 @@ static int pum_set_selected(int n, int repeat) if ((pum_array[pum_selected].pum_info != NULL) && (Rows > 10) && (repeat <= 1) - && (vim_strchr((char *)p_cot, 'p') != NULL)) { + && (vim_strchr(p_cot, 'p') != NULL)) { win_T *curwin_save = curwin; tabpage_T *curtab_save = curtab; int res = OK; @@ -742,11 +742,11 @@ static int pum_set_selected(int n, int repeat) if (res == OK) { // Edit a new, empty buffer. Set options for a "wipeout" // buffer. - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bl", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - set_option_value("diff", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bl", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL); + set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL); + set_option_value_give_err("diff", 0L, NULL, OPT_LOCAL); } } @@ -776,12 +776,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; @@ -795,12 +795,12 @@ 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 validate_cursor(); - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); // When the preview window was resized we need to // update the view on the buffer. Only go back to @@ -904,7 +904,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) { @@ -999,7 +999,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..cded231c85 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 = skiptowhite(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 = (char_u *)skiptowhite(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 = 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(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..f9c4892b91 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" @@ -31,13 +34,13 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os_unix.h" #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" @@ -64,9 +67,9 @@ struct qfline_S { char *qf_module; ///< module name for this error char *qf_pattern; ///< search pattern for the error char *qf_text; ///< description of the error - char qf_viscol; ///< set to TRUE if qf_col and qf_end_col is + char qf_viscol; ///< set to true if qf_col and qf_end_col is // screen column - char qf_cleared; ///< set to TRUE if line has been deleted + char qf_cleared; ///< set to true if line has been deleted char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep char qf_valid; ///< valid error message detected }; @@ -97,7 +100,7 @@ typedef struct qf_list_S { qfline_T *qf_ptr; ///< pointer to the current error int qf_count; ///< number of errors (0 means empty list) int qf_index; ///< current index in the error list - int qf_nonevalid; ///< TRUE if not a single valid entry found + int qf_nonevalid; ///< true if not a single valid entry found char *qf_title; ///< title derived from the command that created ///< the error list or set by setqflist typval_T *qf_ctx; ///< context set by setqflist/setloclist @@ -604,7 +607,7 @@ static efm_T *parse_efm_option(char *efm) goto parse_efm_error; } // Advance to next part - efm = (char *)skip_to_option_part((char_u *)efm + len); // skip comma and spaces + efm = skip_to_option_part(efm + len); // skip comma and spaces } if (fmt_first == NULL) { // nothing found @@ -796,7 +799,7 @@ retry: // Convert a line if it contains a non-ASCII character if (state->vc.vc_type != CONV_NONE && has_non_ascii((char_u *)state->linebuf)) { - char *line = (char *)string_convert(&state->vc, (char_u *)state->linebuf, &state->linelen); + char *line = string_convert(&state->vc, state->linebuf, &state->linelen); if (line != NULL) { if (state->linelen < IOSIZE) { STRLCPY(state->linebuf, line, state->linelen + 1); @@ -997,7 +1000,7 @@ static int qf_setup_state(qfstate_T *pstate, char *restrict enc, const char *res { pstate->vc.vc_type = CONV_NONE; if (enc != NULL && *enc != NUL) { - convert_setup(&pstate->vc, (char_u *)enc, p_enc); + convert_setup(&pstate->vc, enc, p_enc); } if (efile != NULL @@ -1088,7 +1091,7 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { - efm = (char *)buf->b_p_efm; + efm = buf->b_p_efm; } else { efm = errorformat; } @@ -1224,7 +1227,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; @@ -1241,7 +1244,7 @@ static int qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int pre // Expand ~/file and $HOME/file to full path. char c = (char)(*rmp->endp[midx]); *rmp->endp[midx] = NUL; - expand_env(rmp->startp[midx], (char_u *)fields->namebuf, CMDBUFFSIZE); + expand_env((char *)rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); *rmp->endp[midx] = (char_u)c; // For separate filename patterns (%O, %P and %Q), the specified file @@ -1888,7 +1891,7 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) /// Get the quickfix/location list stack to use for the specified Ex command. /// For a location list command, returns the stack for the current window. If /// the location list is not found, then returns NULL and prints an error -/// message if 'print_emsg' is TRUE. +/// message if 'print_emsg' is true. static qf_info_T *qf_cmd_get_stack(exarg_T *eap, int print_emsg) { qf_info_T *qi = &ql_info; @@ -2126,7 +2129,7 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi while (ds_new) { xfree((*stackptr)->dirname); (*stackptr)->dirname = concat_fnames(ds_new->dirname, dirbuf, true); - if (os_isdir((char_u *)(*stackptr)->dirname)) { + if (os_isdir((*stackptr)->dirname)) { break; } @@ -2508,7 +2511,7 @@ static win_T *qf_find_win_with_normal_buf(void) } // Go to a window in any tabpage containing the specified file. Returns true -// if successfully jumped to the window. Otherwise returns FALSE. +// if successfully jumped to the window. Otherwise returns false. static bool qf_goto_tabwin_with_file(int fnum) { FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -2920,7 +2923,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) // If 'newwin' is true, then open the file in a new window. static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, bool newwin) { - char *old_swb = (char *)p_swb; + char *old_swb = p_swb; unsigned old_swb_flags = swb_flags; const bool old_KeyTyped = KeyTyped; // getting file may reset it @@ -2929,7 +2932,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -2991,10 +2994,10 @@ theend: qfl->qf_ptr = qf_ptr; qfl->qf_index = qf_index; } - if (p_swb != (char_u *)old_swb && p_swb == empty_option) { + if (p_swb != old_swb && p_swb == empty_option) { // Restore old 'switchbuf' value, but not when an autocommand or // modeline has changed the value. - p_swb = (char_u *)old_swb; + p_swb = old_swb; swb_flags = old_swb_flags; } decr_quickfix_busy(); @@ -3035,23 +3038,23 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) // text of the entry. bool filter_entry = true; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - filter_entry &= message_filtered((char_u *)qfp->qf_module); + filter_entry &= message_filtered(qfp->qf_module); } if (filter_entry && fname != NULL) { - filter_entry &= message_filtered((char_u *)fname); + filter_entry &= message_filtered(fname); } if (filter_entry && qfp->qf_pattern != NULL) { - filter_entry &= message_filtered((char_u *)qfp->qf_pattern); + filter_entry &= message_filtered(qfp->qf_pattern); } if (filter_entry) { - filter_entry &= message_filtered((char_u *)qfp->qf_text); + filter_entry &= message_filtered(qfp->qf_text); } if (filter_entry) { return; } msg_putchar('\n'); - msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); + msg_outtrans_attr((char *)IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); if (qfp->qf_lnum != 0) { msg_puts_attr(":", qfSepAttr); @@ -3108,7 +3111,7 @@ void qf_list(exarg_T *eap) } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -3119,7 +3122,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; } @@ -3177,7 +3180,7 @@ static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsi int i; const char *p = (char *)text; - for (i = 0; *p != NUL && i < bufsize - 1; ++i) { + for (i = 0; *p != NUL && i < bufsize - 1; i++) { if (*p == '\n') { buf[i] = ' '; while (*++p != NUL) { @@ -3260,13 +3263,13 @@ void qf_age(exarg_T *eap) emsg(_("E380: At bottom of quickfix stack")); break; } - --qi->qf_curlist; + qi->qf_curlist--; } else { if (qi->qf_curlist >= qi->qf_listcount - 1) { emsg(_("E381: At top of quickfix stack")); break; } - ++qi->qf_curlist; + qi->qf_curlist++; } } qf_msg(qi, qi->qf_curlist, ""); @@ -3390,7 +3393,7 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount, found_one = true; if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) { if (amount == MAXLNUM) { - qfp->qf_cleared = TRUE; + qfp->qf_cleared = true; } else { qfp->qf_lnum += amount; } @@ -3464,7 +3467,7 @@ void qf_view_result(bool split) qi = GET_LOC_LIST(curwin); } if (qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -3555,12 +3558,12 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp static void qf_set_cwindow_options(void) { // switch off 'swapfile' - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bt", 0L, "quickfix", OPT_LOCAL); + set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL); RESET_BINDING(curwin); curwin->w_p_diff = false; - set_option_value("fdm", 0L, "manual", OPT_LOCAL); + set_option_value_give_err("fdm", 0L, "manual", OPT_LOCAL); } // Open a new quickfix or location list window, load the quickfix buffer and @@ -3707,7 +3710,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curwin->w_cursor.coladd = 0; curwin->w_curswant = 0; update_topline(curwin); // scroll to show the line - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); curwin->w_redr_status = true; // update ruler curwin = old_curwin; curbuf = curwin->w_buffer; @@ -3744,7 +3747,7 @@ linenr_T qf_current_entry(win_T *wp) } /// Update the cursor position in the quickfix window to the current error. -/// Return TRUE if there is a quickfix window. +/// Return true if there is a quickfix window. /// /// @param old_qf_index previous qf_index or zero static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index) @@ -3827,38 +3830,11 @@ static buf_T *qf_find_buf(qf_info_T *qi) return NULL; } -// Process the 'quickfixtextfunc' option value. -bool qf_process_qftf_option(void) +/// Process the 'quickfixtextfunc' option value. +/// @return OK or FAIL +int qf_process_qftf_option(void) { - if (p_qftf == NULL || *p_qftf == NUL) { - callback_free(&qftf_cb); - return true; - } - - typval_T *tv; - if (*p_qftf == '{') { - // Lambda expression - tv = eval_expr((char *)p_qftf); - if (tv == NULL) { - return false; - } - } else { - // treat everything else as a function name string - tv = xcalloc(1, sizeof(*tv)); - tv->v_type = VAR_STRING; - tv->vval.v_string = (char *)vim_strsave(p_qftf); - } - - Callback cb; - if (!callback_from_typval(&cb, tv)) { - tv_free(tv); - return false; - } - - callback_free(&qftf_cb); - qftf_cb = cb; - tv_free(tv); - return true; + return option_set_callback_func((char_u *)p_qftf, &qftf_cb); } /// Update the w:quickfix_title variable in the quickfix/location list window in @@ -3922,7 +3898,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) // Only redraw when added lines are visible. This avoids flickering when // the added lines are not visible. if ((win = qf_find_win(qi)) != NULL && old_line_count < win->w_botline) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } } } @@ -4132,7 +4108,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q // resembles reading a file into a buffer, it's more logical when using // autocommands. curbuf->b_ro_locked++; - set_option_value("ft", 0L, "qf", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "qf", OPT_LOCAL); curbuf->b_p_ma = false; keep_filetype = true; // don't detect 'filetype' @@ -4142,7 +4118,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q curbuf->b_ro_locked--; // make sure it will be redrawn - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } // Restore KeyTyped, setting 'filetype' may reset it. @@ -4199,7 +4175,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) } } -// Return TRUE when using ":vimgrep" for ":grep". +// Return true when using ":vimgrep" for ":grep". int grep_internal(cmdidx_T cmdidx) { return (cmdidx == CMD_grep @@ -4207,7 +4183,7 @@ int grep_internal(cmdidx_T cmdidx) || cmdidx == CMD_grepadd || cmdidx == CMD_lgrepadd) && STRCMP("internal", - *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0; + *curbuf->b_p_gp == NUL ? p_gp : (char_u *)curbuf->b_p_gp) == 0; } // Return the make/grep autocmd name. @@ -4246,7 +4222,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname) // If 'shellpipe' empty: don't redirect to 'errorfile'. if (*p_sp != NUL) { - append_redir(cmd, len, (char *)p_sp, (char *)fname); + append_redir(cmd, len, p_sp, (char *)fname); } // Display the fully formed command. Output a newline if there's something @@ -4265,7 +4241,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname) // Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" void ex_make(exarg_T *eap) { - char *enc = (*curbuf->b_p_menc != NUL) ? (char *)curbuf->b_p_menc : (char *)p_menc; + char *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". if (grep_internal(eap->cmdidx)) { @@ -4352,7 +4328,7 @@ static char *get_mef_name(void) char *p; - for (p = p_mef; *p; ++p) { + for (p = p_mef; *p; p++) { if (p[0] == '#' && p[1] == '#') { break; } @@ -4856,7 +4832,7 @@ static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewis } /// Get the nth quickfix entry above the specified entry. Searches backwards in -/// the list. If linewise is TRUE, then treat multiple entries on a single line +/// the list. If linewise is true, then treat multiple entries on a single line /// as one. static void qf_get_nth_above_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr) FUNC_ATTR_NONNULL_ALL @@ -4922,7 +4898,7 @@ void ex_cbelow(exarg_T *eap) || eap->cmdidx == CMD_cafter) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; if (!(curbuf->b_has_qf_entry & buf_has_flag)) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -4934,7 +4910,7 @@ void ex_cbelow(exarg_T *eap) qf_list_T *qfl = qf_get_curlist(qi); // check if the list has valid errors if (!qf_list_has_valid_entries(qfl)) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -5005,7 +4981,7 @@ void ex_cfile(exarg_T *eap) set_string_option_direct("ef", -1, eap->arg, OPT_FREE, 0); } - char *enc = (*curbuf->b_p_menc != NUL) ? (char *)curbuf->b_p_menc : (char *)p_menc; + char *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; @@ -5164,6 +5140,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6) { bool found_match = false; + const size_t pat_len = STRLEN(spat); for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) { colnr_T col = 0; @@ -5205,7 +5182,6 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp } } } else { - const size_t pat_len = STRLEN(spat); char *const str = (char *)ml_get_buf(buf, lnum, false); int score; uint32_t matches[MAX_FUZZY_MATCHES]; @@ -5299,7 +5275,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)); @@ -5329,7 +5305,7 @@ static int vgr_process_args(exarg_T *eap, vgr_args_T *args) } // Parse the list of arguments, wildcards have already been expanded. - if (get_arglist_exp((char_u *)p, &args->fcount, &args->fnames, true) == FAIL) { + if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) { return FAIL; } if (args->fcount == 0) { @@ -5453,7 +5429,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // options! aco_save_T aco; aucmd_prepbuf(&aco, buf); - apply_autocmds(EVENT_FILETYPE, (char *)buf->b_p_ft, buf->b_fname, true, buf); + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf); do_modelines(OPT_NOWIN); aucmd_restbuf(&aco); } @@ -5633,7 +5609,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin if (readfile_result == OK && !got_int && !(curbuf->b_flags & BF_NEW)) { - failed = FALSE; + failed = false; if (curbuf != newbuf) { // Bloody autocommands changed the buffer! Can happen when // using netrw and editing a remote file. Use the current @@ -5701,7 +5677,7 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start) cleanup_T cs; // Reset the error/interrupt/exception state here so that aborting() - // returns FALSE when wiping out the buffer. Otherwise it doesn't + // returns false when wiping out the buffer. Otherwise it doesn't // work when got_int is set. enter_cleanup(&cs); @@ -5779,7 +5755,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 +6125,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; @@ -7057,7 +7033,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char FUNC_ATTR_NONNULL_ARG(1, 2) { // Go through all directories in 'runtimepath' - char *p = (char *)p_rtp; + char *p = p_rtp; while (*p != NUL && !got_int) { copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); @@ -7086,9 +7062,10 @@ void ex_helpgrep(exarg_T *eap) } } + bool updated = false; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *const save_cpo = p_cpo; - p_cpo = (char *)empty_option; + p_cpo = empty_option; bool new_qi = false; if (is_loclist_cmd(eap->cmdidx)) { @@ -7116,14 +7093,24 @@ void ex_helpgrep(exarg_T *eap) qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; qf_list_changed(qfl); - qf_update_buffer(qi, NULL); + updated = true; } - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { - // Darn, some plugin changed the value. - free_string_option((char_u *)save_cpo); + // Darn, some plugin changed the value. If it's still empty it was + // changed and restored, need to restore in the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); + } + + if (updated) { + // This may open a window and source scripts, do this after 'cpo' was + // restored. + qf_update_buffer(qi, NULL); } if (au_name != NULL) { @@ -7157,3 +7144,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, EvalFuncData 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, EvalFuncData 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, EvalFuncData 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, EvalFuncData fptr) +{ + set_qf_ll_list(NULL, argvars, rettv); +} diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index fbbf904f8b..a52343e28b 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,19 +481,17 @@ 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 *skip_regexp(char *startp, int dirc, int magic, char **newp) { int mymagic; - char_u *p = startp; + char *p = startp; if (magic) { mymagic = MAGIC_ON; @@ -508,7 +506,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) } if ((p[0] == '[' && mymagic >= MAGIC_ON) || (p[0] == '\\' && p[1] == '[' && mymagic <= MAGIC_OFF)) { - p = skip_anyof((char *)p + 1); + p = (char *)skip_anyof(p + 1); if (p[0] == NUL) { break; } @@ -516,7 +514,7 @@ 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); + *newp = xstrdup(startp); p = *newp + (p - startp); } STRMOVE(p, p + 1); @@ -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) { @@ -1167,7 +1165,7 @@ static bool reg_match_visual(void) rex.line = reg_getline(rex.lnum); rex.input = rex.line + col; - unsigned int cols_u = win_linetabsize(wp, rex.line, col); + unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; if (cols < start || cols > end - (*p_sel == 'e')) { @@ -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..88f0d781af 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; @@ -3764,6 +3764,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case RE_VCOL: if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win, + rex.reg_firstlnum + rex.lnum, rex.line, (colnr_T)(rex.input - rex.line)) + 1, scan)) { @@ -4369,7 +4370,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case BRACE_COMPLEX + 8: case BRACE_COMPLEX + 9: no = op - BRACE_COMPLEX; - ++brace_count[no]; + brace_count[no]++; // If not matched enough times yet, try one more if (brace_count[no] <= (brace_min[no] <= brace_max[no] 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..d4db710d93 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++; } } @@ -6910,7 +6910,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm result = col > t->state->val * ts; } if (!result) { - uintmax_t lts = win_linetabsize(wp, rex.line, col); + uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); assert(t->state->val >= 0); result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1); } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 5d28d624fe..e85167f8fd 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -7,20 +7,176 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.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/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>, ESTACK_STACK for <stack> or +/// ESTACK_SCRIPT for <script>. +char *estack_sfile(estack_arg_T which) +{ + const 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); + } + + // If evaluated in a function or autocommand, return the path of the script + // where it is defined, at script level the current script path is returned + // instead. + if (which == ESTACK_SCRIPT) { + // Walk the stack backwards, starting from the current frame. + for (int idx = exestack.ga_len - 1; idx >= 0; idx--, entry--) { + if (entry->es_type == ETYPE_UFUNC || entry->es_type == ETYPE_AUCMD) { + const sctx_T *const def_ctx = (entry->es_type == ETYPE_UFUNC + ? &entry->es_info.ufunc->uf_script_ctx + : &entry->es_info.aucmd->script_ctx); + return def_ctx->sc_sid > 0 + ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid).sn_name)) + : NULL; + } else if (entry->es_type == ETYPE_SCRIPT) { + return xstrdup(entry->es_name); + } + } + return NULL; + } + + // 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; @@ -36,7 +192,7 @@ void runtime_init(void) void ex_runtime(exarg_T *eap) { char *arg = eap->arg; - char *p = (char *)skiptowhite((char_u *)arg); + char *p = skiptowhite(arg); ptrdiff_t len = p - arg; int flags = eap->forceit ? DIP_ALL : 0; @@ -70,9 +226,9 @@ static void source_callback(char *fname, void *cookie) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) +int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char *tail; int num_files; char **files; int i; @@ -80,20 +236,20 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, // Make a copy of 'runtimepath'. Invoking the callback may change the // value. - char_u *rtp_copy = vim_strsave(path); + char *rtp_copy = xstrdup(path); char *buf = xmallocz(MAXPATHL); { if (p_verbose > 10 && name != NULL) { verbose_enter(); - smsg(_("Searching for \"%s\" in \"%s\""), name, (char *)path); + smsg(_("Searching for \"%s\" in \"%s\""), name, path); verbose_leave(); } // Loop over all entries in 'runtimepath'. - char_u *rtp = rtp_copy; + char *rtp = 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. @@ -111,15 +267,14 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, did_one = true; } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { add_pathsep(buf); - tail = (char_u *)buf + STRLEN(buf); + tail = 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 "); + assert(MAXPATHL >= (tail - buf)); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -208,19 +363,19 @@ void runtime_search_path_unref(RuntimeSearchPath path, int *ref) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie) +int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char *tail; int num_files; char **files; int i; bool did_one = false; - char_u buf[MAXPATHL]; + char buf[MAXPATHL]; if (p_verbose > 10 && name != NULL) { verbose_enter(); - smsg(_("Searching for \"%s\" in runtime path"), (char *)name); + smsg(_("Searching for \"%s\" in runtime path"), name); verbose_leave(); } @@ -244,15 +399,15 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void (*callback)(item.path, cookie); } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { STRCPY(buf, item.path); - add_pathsep((char *)buf); + add_pathsep(buf); tail = buf + STRLEN(buf); // Loop over all patterns in "name" - char_u *np = name; + char *np = 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, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -264,7 +419,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0; // Expand wildcards, invoke the callback for each match. - char *(pat[]) = { (char *)buf }; + char *(pat[]) = { buf }; if (gen_expand_wildcards(1, pat, &num_files, &files, ew_flags) == OK) { for (i = 0; i < num_files; i++) { (*callback)(files[i], cookie); @@ -344,7 +499,7 @@ ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, if (lua) { if (item->has_lua == kNone) { size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path); - item->has_lua = (size < buf_len && os_isdir((char_u *)buf)); + item->has_lua = (size < buf_len && os_isdir(buf)); } if (item->has_lua == kFalse) { continue; @@ -379,13 +534,13 @@ done: /// If "name" is NULL calls callback for each entry in "path". Cookie is /// passed by reference in this case, setting it to NULL indicates that callback /// has done its job. -int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) +int do_in_path_and_pp(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) { int done = FAIL; if ((flags & DIP_NORTP) == 0) { - done |= do_in_path(path, (char *)((name && !*name) ? NULL : name), flags, callback, cookie); + done |= do_in_path(path, (name && !*name) ? NULL : name, flags, callback, + cookie); } if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { @@ -470,7 +625,7 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_ } static void expand_pack_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used, - CharVec *after_path, char_u *pack_entry, size_t pack_entry_len) + CharVec *after_path, char *pack_entry, size_t pack_entry_len) { static char buf[MAXPATHL]; char *(start_pat[]) = { "/pack/*/start/*", "/start/*" }; // NOLINT @@ -509,10 +664,10 @@ RuntimeSearchPath runtime_search_path_build(void) RuntimeSearchPath search_path = KV_INITIAL_VALUE; CharVec after_path = KV_INITIAL_VALUE; - static char_u buf[MAXPATHL]; - for (char *entry = (char *)p_pp; *entry != NUL;) { + static char buf[MAXPATHL]; + for (char *entry = p_pp; *entry != NUL;) { char *cur_entry = entry; - copy_option_part(&entry, (char *)buf, MAXPATHL, ","); + copy_option_part(&entry, buf, MAXPATHL, ","); String the_entry = { .data = cur_entry, .size = STRLEN(buf) }; @@ -521,20 +676,20 @@ RuntimeSearchPath runtime_search_path_build(void) } char *rtp_entry; - for (rtp_entry = (char *)p_rtp; *rtp_entry != NUL;) { + for (rtp_entry = p_rtp; *rtp_entry != NUL;) { char *cur_entry = rtp_entry; - copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); + copy_option_part(&rtp_entry, buf, MAXPATHL, ","); size_t buflen = STRLEN(buf); - if (path_is_after((char *)buf, buflen)) { + if (path_is_after(buf, buflen)) { rtp_entry = cur_entry; break; } // fact: &rtp entries can contain wild chars - expand_rtp_entry(&search_path, &rtp_used, (char *)buf, false); + expand_rtp_entry(&search_path, &rtp_used, buf, false); - handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string((char *)buf), false); + handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string(buf), false); if (h) { (*h)++; expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen); @@ -545,7 +700,7 @@ RuntimeSearchPath runtime_search_path_build(void) String item = kv_A(pack_entries, i); handle_T h = map_get(String, handle_T)(&pack_used, item); if (h == 0) { - expand_pack_entry(&search_path, &rtp_used, &after_path, (char_u *)item.data, item.size); + expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size); } } @@ -557,8 +712,8 @@ RuntimeSearchPath runtime_search_path_build(void) // "after" dirs in rtp for (; *rtp_entry != NUL;) { - copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); - expand_rtp_entry(&search_path, &rtp_used, (char *)buf, path_is_after((char *)buf, STRLEN(buf))); + copy_option_part(&rtp_entry, buf, MAXPATHL, ","); + expand_rtp_entry(&search_path, &rtp_used, buf, path_is_after(buf, STRLEN(buf))); } // strings are not owned @@ -612,13 +767,13 @@ int do_in_runtimepath(char *name, int flags, DoInRuntimepathCB callback, void *c { int success = FAIL; if (!(flags & DIP_NORTP)) { - success |= do_in_cached_path((name && !*name) ? NULL : (char_u *)name, flags, callback, cookie); + success |= do_in_cached_path((name && !*name) ? NULL : name, flags, callback, cookie); flags = (flags & ~DIP_START) | DIP_NORTP; } // TODO(bfredl): we could integrate disabled OPT dirs into the cached path // which would effectivize ":packadd myoptpack" as well if ((flags & (DIP_START|DIP_OPT)) && (success == FAIL || (flags & DIP_ALL))) { - success |= do_in_path_and_pp(p_rtp, (char_u *)name, flags, callback, cookie); + success |= do_in_path_and_pp(p_rtp, name, flags, callback, cookie); } return success; } @@ -634,7 +789,7 @@ int source_runtime(char *name, int flags) } /// Just like source_runtime(), but use "path" instead of 'runtimepath'. -int source_in_path(char_u *path, char_u *name, int flags) +int source_in_path(char *path, char *name, int flags) { return do_in_path_and_pp(path, name, flags, source_callback, NULL); } @@ -658,17 +813,23 @@ static void source_all_matches(char *pat) /// /// @param fname the package path /// @param is_pack whether the added dir is a "pack/*/start/*/" style package -static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) +static int add_pack_dir_to_rtp(char *fname, bool is_pack) { - char_u *p4, *p3, *p2, *p1, *p; - char_u *buf = NULL; + char *p; + char *buf = NULL; char *afterdir = NULL; int retval = FAIL; - p4 = p3 = p2 = p1 = get_past_head(fname); + char *p1 = get_past_head(fname); + char *p2 = p1; + char *p3 = p1; + char *p4 = p1; for (p = p1; *p; MB_PTR_ADV(p)) { if (vim_ispathsep_nocolon(*p)) { - p4 = p3; p3 = p2; p2 = p1; p1 = p; + p4 = p3; + p3 = p2; + p2 = p1; + p1 = p; } } @@ -678,9 +839,9 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) // // find the part up to "pack" in 'runtimepath' p4++; // append pathsep in order to expand symlink - char_u c = *p4; + char c = *p4; *p4 = NUL; - char *const ffname = fix_fname((char *)fname); + char *const ffname = fix_fname(fname); *p4 = c; if (ffname == NULL) { @@ -699,10 +860,10 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) for (const char *entry = (const char *)p_rtp; *entry != NUL;) { const char *cur_entry = entry; - copy_option_part((char **)&entry, (char *)buf, MAXPATHL, ","); + copy_option_part((char **)&entry, buf, MAXPATHL, ","); if (insp == NULL) { - add_pathsep((char *)buf); - char *const rtp_ffname = fix_fname((char *)buf); + add_pathsep(buf); + char *const rtp_ffname = fix_fname(buf); if (rtp_ffname == NULL) { goto theend; } @@ -714,7 +875,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) } } - if ((p = (char_u *)strstr((char *)buf, "after")) != NULL + if ((p = strstr(buf, "after")) != NULL && p > buf && vim_ispathsep(p[-1]) && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { @@ -734,9 +895,9 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) } // check if rtp/pack/name/start/name/after exists - afterdir = concat_fnames((char *)fname, "after", true); + afterdir = concat_fnames(fname, "after", true); size_t afterlen = 0; - if (is_pack ? pack_has_entries((char_u *)afterdir) : os_isdir((char_u *)afterdir)) { + if (is_pack ? pack_has_entries(afterdir) : os_isdir(afterdir)) { afterlen = strlen(afterdir) + 1; // add one for comma } @@ -789,7 +950,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) xstrlcat(new_rtp, afterdir, new_rtp_capacity); } - set_option_value("rtp", 0L, new_rtp, 0); + set_option_value_give_err("rtp", 0L, new_rtp, 0); xfree(new_rtp); retval = OK; @@ -803,29 +964,29 @@ theend: /// Load scripts in "plugin" directory of the package. /// For opt packages, also load scripts in "ftdetect" (start packages already /// load these from filetype.vim) -static int load_pack_plugin(bool opt, char_u *fname) +static int load_pack_plugin(bool opt, char *fname) { static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT - char *const ffname = fix_fname((char *)fname); + char *const ffname = fix_fname(fname); size_t len = strlen(ffname) + STRLEN(ftpat); - char_u *pat = xmallocz(len); + char *pat = xmallocz(len); - vim_snprintf((char *)pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT - source_all_matches((char *)pat); - vim_snprintf((char *)pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT - source_all_matches((char *)pat); + vim_snprintf(pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT + source_all_matches(pat); + vim_snprintf(pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT + source_all_matches(pat); - char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); + char *cmd = xstrdup("g:did_load_filetypes"); // If runtime/filetype.vim wasn't loaded yet, the scripts will be // found when it loads. - if (opt && eval_to_number((char *)cmd) > 0) { + if (opt && eval_to_number(cmd) > 0) { do_cmdline_cmd("augroup filetypedetect"); - vim_snprintf((char *)pat, len, ftpat, ffname); - source_all_matches((char *)pat); + vim_snprintf(pat, len, ftpat, ffname); + source_all_matches(pat); vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT - source_all_matches((char *)pat); + source_all_matches(pat); do_cmdline_cmd("augroup END"); } xfree(cmd); @@ -840,7 +1001,7 @@ static int APP_ADD_DIR; static int APP_LOAD; static int APP_BOTH; -static void add_pack_plugin(bool opt, char_u *fname, void *cookie) +static void add_pack_plugin(bool opt, char *fname, void *cookie) { if (cookie != &APP_LOAD) { char *buf = xmalloc(MAXPATHL); @@ -849,7 +1010,7 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) const char *p = (const char *)p_rtp; while (*p != NUL) { copy_option_part((char **)&p, buf, MAXPATHL, ","); - if (path_fnamecmp(buf, (char *)fname) == 0) { + if (path_fnamecmp(buf, fname) == 0) { found = true; break; } @@ -870,12 +1031,12 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) static void add_start_pack_plugin(char *fname, void *cookie) { - add_pack_plugin(false, (char_u *)fname, cookie); + add_pack_plugin(false, fname, cookie); } static void add_opt_pack_plugin(char *fname, void *cookie) { - add_pack_plugin(true, (char_u *)fname, cookie); + add_pack_plugin(true, fname, cookie); } /// Add all packages in the "start" directory to 'runtimepath'. @@ -884,11 +1045,11 @@ void add_pack_start_dirs(void) do_in_path(p_pp, NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL); } -static bool pack_has_entries(char_u *buf) +static bool pack_has_entries(char *buf) { int num_files; char **files; - char *(pat[]) = { (char *)buf }; + char *(pat[]) = { buf }; if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR) == OK) { FreeWild(num_files, files); } @@ -897,7 +1058,7 @@ static bool pack_has_entries(char_u *buf) static void add_pack_start_dir(char *fname, void *cookie) { - static char_u buf[MAXPATHL]; + static char buf[MAXPATHL]; char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT for (int i = 0; i < 2; i++) { if (STRLEN(fname) + STRLEN(start_pat[i]) + 1 > MAXPATHL) { @@ -938,12 +1099,12 @@ void ex_packloadall(exarg_T *eap) void load_plugins(void) { if (p_lpl) { - char_u *rtp_copy = p_rtp; - char_u *const plugin_pattern_vim = (char_u *)"plugin/**/*.vim"; // NOLINT - char_u *const plugin_pattern_lua = (char_u *)"plugin/**/*.lua"; // NOLINT + char *rtp_copy = p_rtp; + char *const plugin_pattern_vim = "plugin/**/*.vim"; // NOLINT + char *const plugin_pattern_lua = "plugin/**/*.lua"; // NOLINT if (!did_source_packages) { - rtp_copy = vim_strsave(p_rtp); + rtp_copy = xstrdup(p_rtp); add_pack_start_dirs(); } @@ -960,8 +1121,8 @@ void load_plugins(void) } TIME_MSG("loading packages"); - source_runtime((char *)plugin_pattern_vim, DIP_ALL | DIP_AFTER); - source_runtime((char *)plugin_pattern_lua, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER); TIME_MSG("loading after plugins"); } } @@ -984,13 +1145,168 @@ void ex_packadd(exarg_T *eap) vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg); // The first round don't give a "not found" error, in the second round // only when nothing was found in the first round. - res = do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), - round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, - eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + res = + do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), + round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, + eap->forceit ? &APP_ADD_DIR : &APP_BOTH); xfree(pat); } } +/// 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 *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 *s = xmalloc(size); + snprintf(s, size, "%s/%s*.vim", dirnames[i], pat); + globpath(p_rtp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf(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 *s = xmalloc(size); + snprintf(s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf(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 *s = xmalloc(size); + snprintf(s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf(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 *s = xmalloc(size); + snprintf(s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf(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 *s = xmalloc(size); + snprintf(s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf(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 *match = ((char **)ga.ga_data)[i]; + char *s = match; + char *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 *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 *s = xmalloc(buflen); + snprintf(s, buflen, "pack/*/opt/%s*", pat); // NOLINT + globpath(p_pp, s, &ga, 0); + snprintf(s, buflen, "opt/%s*", pat); // NOLINT + globpath(p_pp, s, &ga, 0); + xfree(s); + + for (int i = 0; i < ga.ga_len; i++) { + char *match = ((char **)ga.ga_data)[i]; + s = path_tail(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 @@ -1163,7 +1479,7 @@ char *get_lib_dir(void) // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty // in an appimage build if (strlen(default_lib_dir) != 0 - && os_isdir((const char_u *)default_lib_dir)) { + && os_isdir(default_lib_dir)) { return xstrdup(default_lib_dir); } @@ -1282,3 +1598,900 @@ 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(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 *const p, + size_t len) + FUNC_ATTR_NONNULL_ALL +{ + const char *const line = skipwhite_len((char *)p, len); + len -= (size_t)(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 = skip_to_newline(line); + garray_T ga; + ga_init(&ga, sizeof(char), 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 *const next_eol = skip_to_newline(line); + if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - 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 = 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), 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 *)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(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, 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, 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, "utf-8", p_enc); + p = string_convert(&cookie.conv, (char *)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(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 = 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, SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true); + vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); + if (!message_filtered((char *)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 *get_scriptname(LastSet last_set, bool *should_free) +{ + *should_free = false; + + switch (last_set.script_ctx.sc_sid) { + case SID_MODELINE: + return _("modeline"); + case SID_CMDARG: + return _("--cmd argument"); + case SID_CARG: + return _("-c argument"); + case SID_ENV: + return _("environment variable"); + case SID_ERROR: + return _("error handler"); + case SID_WINLAYOUT: + return _("changed window size"); + case SID_LUA: + return _("Lua"); + case SID_API_CLIENT: + snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); + return (char *)IObuff; + case SID_STR: + return _("anonymous :source"); + default: { + char *const sname = 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 (char *)IObuff; + } + + *should_free = true; + return 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, 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), 400); + ga_concat(&ga, line); + while (sp->nextline != NULL + && concat_continued_line(&ga, 400, 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 = string_convert(&sp->conv, line, NULL); + if (s != NULL) { + xfree(line); + line = s; + } + } + + // Did we encounter a breakpoint? + if (sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) { + dbg_breakpoint(sp->fname, SOURCING_LNUM); + // Find next breakpoint. + sp->breakpoint = dbg_find_breakpoint(true, 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 = enc_canonize(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, 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..053c71212e 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -3,7 +3,77 @@ #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_SCRIPT, +} estack_arg_T; + +typedef struct scriptitem_S { + char *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 0d3b936c48..d268dde845 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1,629 +1,55 @@ // 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/cmdexpand.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/drawscreen.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/optionstr.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/statusline.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; - } +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'"); - // 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); - } - - 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 { @@ -643,21 +69,7 @@ bool conceal_cursor_line(const win_T *wp) } else { return false; } - 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); - } + return vim_strchr(wp->w_p_cocu, c) != NULL; } /// Whether cursorline is drawn in a special way @@ -669,1081 +81,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 +120,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 +145,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 +159,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 +173,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 +182,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,2751 +237,22 @@ 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; - } - - 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) -{ - 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(); - } -} - -/* - * Redraw all status lines at the bottom of frame "frp". - */ -void win_redraw_last_status(const frame_T *frp) - FUNC_ATTR_NONNULL_ARG(1) -{ - if (frp->fr_layout == FR_LEAF) { - frp->fr_win->w_redr_status = true; - } else if (frp->fr_layout == FR_ROW) { - FOR_ALL_FRAMES(frp, frp->fr_child) { - win_redraw_last_status(frp); - } - } else { - assert(frp->fr_layout == FR_COL); - frp = frp->fr_child; - while (frp->fr_next != NULL) { - frp = frp->fr_next; - } - win_redraw_last_status(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) +static int wildmenu_match_len(expand_T *xp, char_u *s) { int len = 0; @@ -4725,7 +265,7 @@ static int status_match_len(expand_T *xp, char_u *s) } while (*s != NUL) { - s += skip_status_match_char(xp, s); + s += skip_wildmenu_char(xp, s); len += ptr2cells((char *)s); MB_PTR_ADV(s); } @@ -4733,18 +273,20 @@ static int status_match_len(expand_T *xp, char_u *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. - */ -static int skip_status_match_char(expand_T *xp, char_u *s) +/// Return the number of characters that should be skipped in the wildmenu +/// These are backslashes used for escaping. Do show backslashes in help tags. +static int skip_wildmenu_char(expand_T *xp, char_u *s) { - if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) + if ((rem_backslash((char *)s) && xp->xp_context != EXPAND_HELP) || ((xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES) && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) { #ifndef BACKSLASH_IN_FILENAME + // TODO(bfredl): Why in the actual fuck are we special casing the + // shell variety deep in the redraw logic? Shell special snowflakiness + // should already be eliminated multiple layers before reaching the + // screen infracstructure. if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { return 2; } @@ -4761,7 +303,7 @@ static int skip_status_match_char(expand_T *xp, char_u *s) /// If inversion is possible we use it. Else '=' characters are used. /// /// @param matches list of matches -void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int match, int showtail) +void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) { #define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m]) int row; @@ -4792,7 +334,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int highlight = false; } // count 1 for the ending ">" - clen = status_match_len(xp, (char_u *)L_MATCH(match)) + 3; + clen = wildmenu_match_len(xp, (char_u *)L_MATCH(match)) + 3; if (match == 0) { first_match = 0; } else if (match < first_match) { @@ -4802,7 +344,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int } else { // check if match fits on the screen for (i = first_match; i < match; i++) { - clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; } if (first_match > 0) { clen += 2; @@ -4813,7 +355,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int // if showing the last match, we can add some on the left clen = 2; for (i = match; i < num_matches; i++) { - clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; if ((long)clen >= Columns) { break; } @@ -4825,7 +367,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int } if (add_left) { while (first_match > 0) { - clen += status_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; if ((long)clen >= Columns) { break; } @@ -4845,7 +387,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int clen = len; i = first_match; - while (clen + status_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { + while (clen + wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { if (i == match) { selstart = buf + len; selstart_col = clen; @@ -4861,8 +403,8 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int len += l; clen += l; } else { - for (; *s != NUL; ++s) { - s += skip_status_match_char(xp, s); + for (; *s != NUL; s++) { + s += skip_wildmenu_char(xp, s); clen += ptr2cells((char *)s); if ((l = utfc_ptr2len((char *)s)) > 1) { STRNCPY(buf + len, s, l); // NOLINT(runtime/printf) @@ -4888,7 +430,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; @@ -4927,10 +469,10 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_grid; - grid_puts(grid, buf, row, 0, attr); + grid_puts(grid, (char *)buf, row, 0, attr); if (selstart != NULL && highlight) { *selend = NUL; - grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + grid_puts(grid, (char *)selstart, row, selstart_col, HL_ATTR(HLF_WM)); } grid_fill(grid, row, row + 1, clen, Columns, @@ -4941,190 +483,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 @@ -5149,77 +507,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 @@ -5248,7 +535,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) curwin = old_curwin; if (p == NULL || *p == NUL) { if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { - p = (char *)wp->w_buffer->b_p_keymap; + p = wp->w_buffer->b_p_keymap; } else { p = "lang"; } @@ -5261,305 +548,23 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) return buf[0] != NUL; } -/// 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) -{ - static bool entered = false; - int attr; - int curattr; - int row; - int col = 0; - int maxwidth; - int width; - int n; - int len; - int fillchar; - char buf[MAXPATHL]; - char_u *stl; - char *p; - stl_hlrec_t *hltab; - StlClickRecord *tabtab; - int use_sandbox = false; - win_T *ewp; - int p_crb_save; - bool is_stl_global = global_stl_height() > 0; - - 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. */ - if (entered) { - return; - } - entered = true; - - // setup environment for the task at hand - if (wp == NULL) { - // Use 'tabline'. Always at the first line of the screen. - stl = p_tal; - row = 0; - fillchar = ' '; - attr = HL_ATTR(HLF_TPF); - maxwidth = Columns; - use_sandbox = was_set_insecurely(wp, "tabline", 0); - } else if (draw_winbar) { - stl = (char_u *)((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); - row = -1; // row zero is first row of text - col = 0; - grid = &wp->w_grid; - grid_adjust(&grid, &row, &col); - - if (row < 0) { - return; - } - - fillchar = wp->w_p_fcs_chars.wbr; - attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); - maxwidth = wp->w_width_inner; - use_sandbox = was_set_insecurely(wp, "winbar", 0); - - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); - // Allocate / resize the click definitions array for winbar if needed. - if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) { - xfree(wp->w_winbar_click_defs); - wp->w_winbar_click_defs_size = (size_t)maxwidth; - wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord)); - } - } else { - row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); - fillchar = fillchar_status(&attr, wp); - maxwidth = is_stl_global ? Columns : wp->w_width; - - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); - // Allocate / resize the click definitions array for statusline if needed. - if (wp->w_status_click_defs_size < (size_t)maxwidth) { - xfree(wp->w_status_click_defs); - wp->w_status_click_defs_size = (size_t)maxwidth; - wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord)); - } - - if (draw_ruler) { - stl = p_ruf; - // advance past any leading group spec - implicit in ru_col - if (*stl == '%') { - if (*++stl == '-') { - stl++; - } - if (atoi((char *)stl)) { - while (ascii_isdigit(*stl)) { - stl++; - } - } - if (*stl++ != '(') { - stl = p_ruf; - } - } - col = ru_col - (Columns - maxwidth); - if (col < (maxwidth + 1) / 2) { - col = (maxwidth + 1) / 2; - } - maxwidth = maxwidth - col; - if (!wp->w_status_height && !is_stl_global) { - grid = &msg_grid_adj; - row = Rows - 1; - maxwidth--; // writing in last column may cause scrolling - fillchar = ' '; - attr = HL_ATTR(HLF_MSG); - } - - use_sandbox = was_set_insecurely(wp, "rulerformat", 0); - } else { - if (*wp->w_p_stl != NUL) { - stl = wp->w_p_stl; - } else { - stl = p_stl; - } - use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); - } - - col += is_stl_global ? 0 : wp->w_wincol; - } - - if (maxwidth <= 0) { - goto theend; - } - - /* 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; - - /* 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, - fillchar, maxwidth, &hltab, &tabtab); - xfree(stl); - ewp->w_p_crb = p_crb_save; - - // Make all characters printable. - p = transstr(buf, true); - len = (int)STRLCPY(buf, p, sizeof(buf)); - len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; - xfree(p); - - // fill up with "fillchar" - while (width < maxwidth && len < (int)sizeof(buf) - 1) { - len += utf_char2bytes(fillchar, buf + len); - width++; - } - buf[len] = NUL; - - /* - * Draw each snippet with the specified highlighting. - */ - grid_puts_line_start(grid, row); - - curattr = attr; - p = buf; - for (n = 0; hltab[n].start != NULL; n++) { - int textlen = (int)(hltab[n].start - p); - grid_puts_len(grid, (char_u *)p, textlen, row, col, curattr); - col += vim_strnsize((char_u *)p, textlen); - p = hltab[n].start; - - if (hltab[n].userhl == 0) { - curattr = attr; - } else if (hltab[n].userhl < 0) { - curattr = syn_id2attr(-hltab[n].userhl); - } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { - curattr = highlight_stlnc[hltab[n].userhl - 1]; - } else { - curattr = highlight_user[hltab[n].userhl - 1]; - } - } - // Make sure to use an empty string instead of p, if p is beyond buf + len. - grid_puts(grid, p >= buf + len ? (char_u *)"" : (char_u *)p, row, col, - curattr); - - grid_puts_line_flush(false); - - // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking - // in the tab page line, status line or window bar - StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs - : draw_winbar ? wp->w_winbar_click_defs - : wp->w_status_click_defs; - - if (click_defs == NULL) { - goto theend; - } - - col = 0; - len = 0; - p = buf; - StlClickDefinition cur_click_def = { - .type = kStlClickDisabled, - }; - for (n = 0; tabtab[n].start != NULL; n++) { - len += vim_strnsize((char_u *)p, (int)(tabtab[n].start - p)); - while (col < len) { - click_defs[col++] = cur_click_def; - } - p = (char *)tabtab[n].start; - cur_click_def = tabtab[n].def; - if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled - || cur_click_def.type == kStlClickFuncRun)) { - // window bar and status line only support click functions - cur_click_def.type = kStlClickDisabled; - } - } - while (col < maxwidth) { - click_defs[col++] = cur_click_def; - } - -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; - - 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++) { - grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); - } - 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; } } @@ -5579,105 +584,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. @@ -5694,67 +600,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) { @@ -5784,9 +629,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) { @@ -5808,126 +653,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; @@ -5948,12 +696,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; @@ -5990,13 +734,13 @@ int showmode(void) length = (Rows - msg_row) * Columns - 3; } if (edit_submode_extra != NULL) { - length -= vim_strsize((char *)edit_submode_extra); + length -= vim_strsize(edit_submode_extra); } if (length > 0) { if (edit_submode_pre != NULL) { - length -= vim_strsize((char *)edit_submode_pre); + length -= vim_strsize(edit_submode_pre); } - if (length - vim_strsize((char *)edit_submode) > 0) { + if (length - vim_strsize(edit_submode) > 0) { if (edit_submode_pre != NULL) { msg_puts_attr((const char *)edit_submode_pre, attr); } @@ -6077,7 +821,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 @@ -6087,7 +831,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 @@ -6099,6 +843,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 @@ -6116,14 +863,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; @@ -6171,9 +917,7 @@ static void recording_mode(int 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; @@ -6191,8 +935,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; @@ -6226,7 +969,7 @@ void draw_tabline(void) did_emsg |= saved_did_emsg; } else { FOR_ALL_TABS(tp) { - ++tabcount; + tabcount++; } if (tabcount > 0) { @@ -6270,7 +1013,7 @@ void draw_tabline(void) modified = false; - for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount) { + for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { if (bufIsChanged(wp->w_buffer)) { modified = true; } @@ -6288,7 +1031,7 @@ void draw_tabline(void) col += len; } if (modified) { - grid_puts_len(&default_grid, (char_u *)"+", 1, 0, col++, attr); + grid_puts_len(&default_grid, "+", 1, 0, col++, attr); } grid_putchar(&default_grid, ' ', 0, col++, attr); } @@ -6299,7 +1042,7 @@ void draw_tabline(void) get_trans_bufname(cwp->w_buffer); shorten_dir(NameBuff); len = vim_strsize((char *)NameBuff); - p = NameBuff; + p = (char_u *)NameBuff; while (len > room) { len -= ptr2cells((char *)p); MB_PTR_ADV(p); @@ -6308,7 +1051,7 @@ void draw_tabline(void) len = Columns - col - 1; } - grid_puts_len(&default_grid, p, (int)STRLEN(p), 0, col, attr); + grid_puts_len(&default_grid, (char *)p, (int)STRLEN(p), 0, col, attr); col += len; } grid_putchar(&default_grid, ' ', 0, col++, attr); @@ -6348,10 +1091,9 @@ 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); size_t n_tabs = 0; FOR_ALL_TABS(tp) { @@ -6392,13 +1134,9 @@ void ui_ext_tabline_update(void) } ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } -/* - * 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) { @@ -6409,37 +1147,9 @@ 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". - */ -int fillchar_status(int *attr, win_T *wp) -{ - int fill; - bool is_curwin = (wp == curwin); - if (is_curwin) { - *attr = win_hl_attr(wp, HLF_S); - fill = wp->w_p_fcs_chars.stl; - } else { - *attr = win_hl_attr(wp, HLF_SNC); - fill = wp->w_p_fcs_chars.stlnc; - } - // Use fill when there is highlighting, and highlighting of current - // window differs, or the fillchars differ, or this is not the - // current window - if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || ONE_WINDOW) - || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { - return fill; - } - if (is_curwin) { - return '^'; - } - return '='; -} - /// 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; @@ -6447,240 +1157,68 @@ 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(); - } -} +#define COL_RULER 17 // columns needed by standard ruler -static void win_redr_ruler(win_T *wp, bool always) +/// 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) { - bool is_stl_global = global_stl_height() > 0; - static bool did_show_ext_ruler = false; - - // If 'ruler' off, don't do anything - if (!p_ru) { - return; - } - - /* - * 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; - } + int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); - // Don't draw the ruler while doing insert-completion, it might overwrite - // the (long) mode message. - if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { - if (edit_submode != NULL) { - return; + 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_ruf && p_ch > 0 && !ui_has(kUIMessages)) { - const int called_emsg_before = called_emsg; - win_redr_custom(wp, false, true); - if (called_emsg > called_emsg_before) { - set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); + if (p_sc) { + sc_col += SHOWCMD_COLS; + if (!p_ru || last_has_status) { // no need for separating space + sc_col++; } - return; } - - // Check if not in Insert mode and the line is empty (will show "0-1"). - int empty_line = false; - if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) { - empty_line = true; + 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; } - - /* - * Only draw the ruler when something changed. - */ - validate_virtcol_win(wp); - if (redraw_cmdline - || always - || wp->w_cursor.lnum != wp->w_ru_cursor.lnum - || wp->w_cursor.col != wp->w_ru_cursor.col - || wp->w_virtcol != wp->w_ru_virtcol - || wp->w_cursor.coladd != wp->w_ru_cursor.coladd - || wp->w_topline != wp->w_ru_topline - || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count - || wp->w_topfill != wp->w_ru_topfill - || empty_line != wp->w_ru_empty) { - int width; - int row; - int fillchar; - int attr; - int off; - bool part_of_status = false; - - if (wp->w_status_height) { - row = W_ENDROW(wp); - fillchar = fillchar_status(&attr, wp); - off = wp->w_wincol; - width = wp->w_width; - part_of_status = true; - } else if (is_stl_global) { - row = Rows - (int)p_ch - 1; - fillchar = fillchar_status(&attr, wp); - off = 0; - width = Columns; - part_of_status = true; - } else { - row = Rows - 1; - fillchar = ' '; - attr = HL_ATTR(HLF_MSG); - width = Columns; - off = 0; - } - - if (!part_of_status && !ui_has_messages()) { - return; - } - - // In list mode virtcol needs to be recomputed - colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { - wp->w_p_list = false; - getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = true; - } - -#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. - */ - 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); - 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). - */ - 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); - if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen - o++; - } - int this_ru_col = ru_col - (Columns - width); - if (this_ru_col < 0) { - this_ru_col = 0; - } - // Never use more than half the window/screen width, leave the other half - // for the filename. - if (this_ru_col < (width + 1) / 2) { - this_ru_col = (width + 1) / 2; - } - if (this_ru_col + o < width) { - // Need at least 3 chars left for get_rel_pos() + NUL. - while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { - i += utf_char2bytes(fillchar, buffer + i); - o++; - } - get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); - } - - if (ui_has(kUIMessages) && !part_of_status) { - MAXSIZE_TEMP_ARRAY(content, 1); - MAXSIZE_TEMP_ARRAY(chunk, 2); - ADD_C(chunk, INTEGER_OBJ(attr)); - ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer))); - ADD_C(content, ARRAY_OBJ(chunk)); - ui_call_msg_ruler(content); - did_show_ext_ruler = true; - } else { - if (did_show_ext_ruler) { - ui_call_msg_ruler((Array)ARRAY_DICT_INIT); - did_show_ext_ruler = false; - } - // Truncate at window boundary. - o = 0; - for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { - o += utf_ptr2cells(buffer + i); - if (this_ru_col + o > width) { - buffer[i] = NUL; - break; - } - } - - ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; - grid_puts(grid, (char_u *)buffer, row, this_ru_col + off, attr); - grid_fill(grid, row, row + 1, - this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, - fillchar, attr); - } - - wp->w_ru_cursor = wp->w_cursor; - wp->w_ru_virtcol = wp->w_virtcol; - wp->w_ru_empty = (char)empty_line; - wp->w_ru_topline = wp->w_topline; - wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; - wp->w_ru_topfill = wp->w_topfill; + 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. - */ +/// 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; @@ -6702,7 +1240,7 @@ int number_width(win_T *wp) n = 0; do { lnum /= 10; - ++n; + n++; } while (lnum > 0); // 'numberwidth' gives the minimal width plus one @@ -6721,155 +1259,282 @@ 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(const 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); + const char_u *s = *p; - *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; + if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { + int64_t num = 0; + for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { + *p += 2; + int n = hexhex2nr((char *)(*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((const char *)s); + int c = mb_cptr2char_adv(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 the global or the window-local value. +/// @param apply if false, do not store the flags, only check for errors. +/// @return error message, NULL if it's OK. +char *set_chars_option(win_T *wp, char **varp, bool apply) +{ + const char_u *last_multispace = NULL; // Last occurrence of "multispace:" + const 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 + const bool is_listchars = (varp == &p_lcs || varp == &wp->w_p_lcs); + + struct chars_tab { + int *cp; ///< char value + char *name; ///< char id + int def; ///< default value + }; - if (width < 0 || height < 0) { // just checking... - return; - } + // 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", '~' }, + }; - if (State == MODE_HITRETURN || State == MODE_SETWSIZE) { - // postpone the resizing - State = MODE_SETWSIZE; - return; - } + 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 }, + }; - /* 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; + struct chars_tab *tab; + int entries; + const char_u *value = (char_u *)(*varp); + if (is_listchars) { + tab = lcs_tab; + entries = ARRAY_SIZE(lcs_tab); + if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { + value = (char_u *)p_lcs; // local value is empty, use the global value + } + } else { + tab = fcs_tab; + entries = ARRAY_SIZE(fcs_tab); + if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { + value = (char_u *)p_fcs; // local value is empty, use the global value + } } - resizing_screen = true; + // first round: check for valid value, second round: assign values + for (int round = 0; round <= (apply ? 1 : 0); round++) { + if (round > 0) { + // After checking that the value is valid: set defaults + for (int i = 0; i < entries; i++) { + if (tab[i].cp != NULL) { + *(tab[i].cp) = tab[i].def; + } + } + if (is_listchars) { + wp->w_p_lcs_chars.tab1 = NUL; + wp->w_p_lcs_chars.tab3 = NUL; - 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); + 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; + } - send_grid_resize = 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; + } + } + } + const char_u *p = value; + while (*p) { + int i; + for (i = 0; i < entries; i++) { + const size_t len = STRLEN(tab[i].name); + if (STRNCMP(p, tab[i].name, len) == 0 + && p[len] == ':' + && p[len + 1] != NUL) { + const char_u *s = p + len + 1; + int c1 = get_encoded_char_adv(&s); + if (c1 == 0 || char2cells(c1) > 1) { + return e_invarg; + } + int c2 = 0, c3 = 0; + 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; + } + } + } - /// 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 (i == entries) { + const size_t len = STRLEN("multispace"); + const size_t len2 = STRLEN("leadmultispace"); + if (is_listchars + && STRNCMP(p, "multispace", len) == 0 + && p[len] == ':' + && p[len + 1] != NUL) { + const char_u *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 != ',') { + int 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 != ',') { + int c1 = get_encoded_char_adv(&s); + if (p == last_multispace) { + wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; + } + } + p = s; + } + } else if (is_listchars + && STRNCMP(p, "leadmultispace", len2) == 0 + && p[len2] == ':' + && p[len2 + 1] != NUL) { + const char_u *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 != ',') { + int 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 != ',') { + int 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 (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { - screenclear(); + if (*p == ',') { + p++; + } + } } - 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(); - } - } + return NULL; // no error +} + +/// 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 (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. @@ -6890,13 +1555,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..e995081df8 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" @@ -168,7 +170,7 @@ int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatc } if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - mr_pattern = reverse_text(pat); + mr_pattern = (char_u *)reverse_text((char *)pat); mr_pattern_alloced = true; } else { mr_pattern = pat; @@ -218,7 +220,7 @@ void save_re_pat(int idx, char_u *pat, int magic) last_idx = idx; // If 'hlsearch' set and search pat changed: need redraw. if (p_hls) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } set_no_hlsearch(false); } @@ -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); @@ -352,10 +354,8 @@ char_u *last_search_pattern(void) return spats[RE_SEARCH].pat; } -/* - * Return TRUE when case should be ignored for search pattern "pat". - * Uses the 'ignorecase' and 'smartcase' options. - */ +/// Return true when case should be ignored for search pattern "pat". +/// Uses the 'ignorecase' and 'smartcase' options. int ignorecase(char_u *pat) { return ignorecase_opt(pat, p_ic, p_scs); @@ -421,7 +421,7 @@ int last_csearch_forward(void) int last_csearch_until(void) { - return last_t_cmd == TRUE; + return last_t_cmd == true; } void set_last_csearch(int c, char_u *s, int len) @@ -431,7 +431,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); } } @@ -478,8 +478,8 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) spats[idx].no_scs = false; spats[idx].off.dir = '/'; set_vv_searchforward(); - spats[idx].off.line = FALSE; - spats[idx].off.end = FALSE; + spats[idx].off.line = false; + spats[idx].off.end = false; spats[idx].off.off = 0; if (setlast) { last_idx = idx; @@ -496,7 +496,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) } // If 'hlsearch' set and search pat changed: need redraw. if (p_hls && idx == last_idx && !no_hlsearch) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } } @@ -511,9 +511,9 @@ void last_pat_prog(regmmatch_T *regmatch) regmatch->regprog = NULL; return; } - ++emsg_off; // So it doesn't beep if bad expr + 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. @@ -608,11 +608,11 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, start_pos = *pos; // remember start pos for detecting no match found = 0; // default: not found - at_first_line = TRUE; // default: start in first line + at_first_line = true; // default: start in first line if (pos->lnum == 0) { // correct lnum for when starting in line 0 pos->lnum = 1; pos->col = 0; - at_first_line = FALSE; // not in first line now + at_first_line = false; // not in first line now } /* @@ -625,14 +625,14 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, if (dir == BACKWARD && start_pos.col == 0 && (options & SEARCH_START) == 0) { lnum = pos->lnum - 1; - at_first_line = FALSE; + at_first_line = false; } else { lnum = pos->lnum; } - for (loop = 0; loop <= 1; ++loop) { // loop twice if 'wrapscan' set + for (loop = 0; loop <= 1; loop++) { // loop twice if 'wrapscan' set for (; lnum > 0 && lnum <= buf->b_ml.ml_line_count; - lnum += dir, at_first_line = FALSE) { + lnum += dir, at_first_line = false) { // Stop after checking "stop_lnum", if it's set. if (stop_lnum != 0 && (dir == FORWARD ? lnum > stop_lnum : lnum < stop_lnum)) { @@ -899,7 +899,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; // if second loop, stop where started } } - at_first_line = FALSE; + at_first_line = false; // vim_regexec_multi() may clear "regprog" if (regmatch.regprog == NULL) { @@ -989,7 +989,7 @@ static int first_submatch(regmmatch_T *rp) { int submatch; - for (submatch = 1;; ++submatch) { + for (submatch = 1;; submatch++) { if (rp->startpos[submatch].lnum >= 0) { break; } @@ -1004,22 +1004,22 @@ static int first_submatch(regmmatch_T *rp) /// Highest level string search function. /// Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc' /// -/// Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this +/// Careful: If spats[0].off.line == true and spats[0].off.off == 0 this /// makes the movement linewise without moving the match position. /// /// @param dirc if 0: use previous dir. /// @param pat NULL or empty : use previous string. -/// @param options if TRUE and -/// SEARCH_REV == TRUE : go in reverse of previous dir. -/// SEARCH_ECHO == TRUE : echo the search command and handle options -/// SEARCH_MSG == TRUE : may give error message -/// SEARCH_OPT == TRUE : interpret optional flags -/// SEARCH_HIS == TRUE : put search pattern in history -/// SEARCH_NOOF == TRUE : don't add offset to position -/// SEARCH_MARK == TRUE : set previous context mark -/// SEARCH_KEEP == TRUE : keep previous search pattern -/// SEARCH_START == TRUE : accept match at curpos itself -/// SEARCH_PEEK == TRUE : check for typed char, cancel search +/// @param options if true and +/// SEARCH_REV == true : go in reverse of previous dir. +/// SEARCH_ECHO == true : echo the search command and handle options +/// SEARCH_MSG == true : may give error message +/// SEARCH_OPT == true : interpret optional flags +/// SEARCH_HIS == true : put search pattern in history +/// SEARCH_NOOF == true : don't add offset to position +/// SEARCH_MARK == true : set previous context mark +/// SEARCH_KEEP == true : keep previous search pattern +/// SEARCH_START == true : accept match at curpos itself +/// SEARCH_PEEK == true : check for typed char, cancel search /// @param oap can be NULL /// @param dirc '/' or '?' /// @param search_delim delimiter for search, e.g. '%' in s%regex%replacement @@ -1036,7 +1036,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; @@ -1091,7 +1091,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, * Turn 'hlsearch' highlighting back on. */ if (no_hlsearch && !(options & SEARCH_KEEP)) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(false); } @@ -1123,20 +1123,20 @@ 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; - p = skip_regexp(pat, search_delim, p_magic, &strcopy); - if (strcopy != ps) { + ps = (char_u *)strcopy; + p = (char_u *)skip_regexp((char *)pat, search_delim, p_magic, &strcopy); + 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 *p++ = NUL; } - spats[0].off.line = FALSE; - spats[0].off.end = FALSE; + spats[0].off.line = false; + spats[0].off.end = false; spats[0].off.off = 0; // Check for a line offset or a character offset. // For get_address (echo off) we don't check for a character @@ -1160,9 +1160,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++; } } @@ -1261,7 +1261,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // it would be blanked out again very soon. Show it on the // left, but do reverse the text. if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - char_u *r = reverse_text(trunc != NULL ? trunc : msgbuf); + char_u *r = (char_u *)reverse_text(trunc != NULL ? (char *)trunc : (char *)msgbuf); xfree(msgbuf); msgbuf = r; // move reversed text to beginning of buffer @@ -1296,7 +1296,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, */ if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2) { if (spats[0].off.off > 0) { - for (c = spats[0].off.off; c; --c) { + for (c = spats[0].off.off; c; c--) { if (decl(&pos) == -1) { break; } @@ -1306,7 +1306,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, pos.col = MAXCOL; } } else { - for (c = spats[0].off.off; c; ++c) { + for (c = spats[0].off.off; c; c++) { if (incl(&pos) == -1) { break; } @@ -1426,14 +1426,14 @@ 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) { setpcmark(); } curwin->w_cursor = pos; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; end_do_search: if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS)) { @@ -1497,16 +1497,15 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) // when adding lines the matching line may be empty but it is not // ignored because we are interested in the next line -- Acevedo - if ((compl_cont_status & CONT_ADDING) - && !(compl_cont_status & CONT_SOL)) { + if (compl_status_adding() && !compl_status_sol()) { if (mb_strcmp_ic((bool)p_ic, (const char *)p, (const char *)pat) == 0) { return OK; } } else if (*p != NUL) { // Ignore empty lines. // Expanding lines or words. - assert(compl_length >= 0); - if ((p_ic ? mb_strnicmp(p, pat, (size_t)compl_length) - : STRNCMP(p, pat, compl_length)) == 0) { + assert(ins_compl_len() >= 0); + if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len()) + : STRNCMP(p, pat, ins_compl_len())) == 0) { return OK; } } @@ -1518,12 +1517,10 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) * Character Searches */ -/* - * Search for a character in a line. If "t_cmd" is FALSE, move to the - * position of the character, otherwise move to just before the char. - * Do this "cap->count1" times. - * Return FAIL or OK. - */ +/// Search for a character in a line. If "t_cmd" is false, move to the +/// position of the character, otherwise move to just before the char. +/// Do this "cap->count1" times. +/// Return FAIL or OK. int searchc(cmdarg_T *cap, int t_cmd) FUNC_ATTR_NONNULL_ALL { @@ -1695,34 +1692,34 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) static void find_mps_values(int *initc, int *findc, bool *backwards, bool switchit) FUNC_ATTR_NONNULL_ALL { - char_u *ptr = curbuf->b_p_mps; + char *ptr = curbuf->b_p_mps; while (*ptr != NUL) { - if (utf_ptr2char((char *)ptr) == *initc) { + if (utf_ptr2char(ptr) == *initc) { if (switchit) { *findc = *initc; - *initc = utf_ptr2char((char *)ptr + utfc_ptr2len((char *)ptr) + 1); + *initc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); *backwards = true; } else { - *findc = utf_ptr2char((char *)ptr + utfc_ptr2len((char *)ptr) + 1); + *findc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); *backwards = false; } return; } - char_u *prev = ptr; - ptr += utfc_ptr2len((char *)ptr) + 1; - if (utf_ptr2char((char *)ptr) == *initc) { + char *prev = ptr; + ptr += utfc_ptr2len(ptr) + 1; + if (utf_ptr2char(ptr) == *initc) { if (switchit) { *findc = *initc; - *initc = utf_ptr2char((char *)prev); + *initc = utf_ptr2char(prev); *backwards = false; } else { - *findc = utf_ptr2char((char *)prev); + *findc = utf_ptr2char(prev); *backwards = true; } return; } - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); if (*ptr == ',') { ptr++; } @@ -1866,7 +1863,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) * the line. */ if (linep[pos.col] == NUL && pos.col) { - --pos.col; + pos.col--; } for (;;) { initc = utf_ptr2char((char *)linep + pos.col); @@ -1980,7 +1977,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) // backward search: Check if this line contains a single-line comment if ((backwards && comment_dir) || lisp) { - comment_col = check_linecomment(linep); + comment_col = check_linecomment((char *)linep); } if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col) { lispcomm = true; // find match inside this comment @@ -2000,7 +1997,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (pos.lnum == 1) { // start of file break; } - --pos.lnum; + pos.lnum--; if (maxtravel > 0 && ++traveled > maxtravel) { break; @@ -2013,7 +2010,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) // Check if this line contains a single-line comment if (comment_dir || lisp) { - comment_col = check_linecomment(linep); + comment_col = check_linecomment((char *)linep); } // skip comment if (lisp && comment_col != MAXCOL) { @@ -2035,7 +2032,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) || lispcomm) { break; } - ++pos.lnum; + pos.lnum++; if (maxtravel && traveled++ > maxtravel) { break; @@ -2046,7 +2043,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) do_quotes = -1; line_breakcheck(); if (lisp) { // find comment pos in new line - comment_col = check_linecomment(linep); + comment_col = check_linecomment((char *)linep); } } else { pos.col += utfc_ptr2len((char *)linep + pos.col); @@ -2130,16 +2127,16 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) * Watch out for "\\". */ at_start = do_quotes; - for (ptr = linep; *ptr; ++ptr) { + for (ptr = linep; *ptr; ptr++) { if (ptr == linep + pos.col + backwards) { at_start = (do_quotes & 1); } 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 @@ -2210,7 +2207,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (do_quotes) { int col; - for (col = pos.col - 1; col >= 0; --col) { + for (col = pos.col - 1; col >= 0; col--) { if (linep[col] != '\\') { break; } @@ -2304,15 +2301,15 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) /// Check if line[] contains a / / comment. /// @returns MAXCOL if not, otherwise return the column. -int check_linecomment(const char_u *line) +int check_linecomment(const char *line) { - const char_u *p = line; // scan from start + const char *p = line; // scan from start // skip Lispish one-line comments if (curbuf->b_p_lisp) { if (vim_strchr((char *)p, ';') != NULL) { // there may be comments bool in_str = false; // inside of string - while ((p = (char_u *)strpbrk((char *)p, "\";")) != NULL) { + while ((p = strpbrk((char *)p, "\";")) != NULL) { if (*p == '"') { if (in_str) { if (*(p - 1) != '\\') { // skip escaped quote @@ -2325,7 +2322,7 @@ int check_linecomment(const char_u *line) } } else if (!in_str && ((p - line) < 2 || (*(p - 1) != '\\' && *(p - 2) != '#')) - && !is_pos_in_string(line, (colnr_T)(p - line))) { + && !is_pos_in_string((char_u *)line, (colnr_T)(p - line))) { break; // found! } p++; @@ -2334,15 +2331,15 @@ int check_linecomment(const char_u *line) p = NULL; } } else { - while ((p = (char_u *)vim_strchr((char *)p, '/')) != NULL) { + while ((p = vim_strchr((char *)p, '/')) != NULL) { // Accept a double /, unless it's preceded with * and followed by *, // because * / / * is an end and start of a C comment. Only // accept the position if it is not inside a string. if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*') - && !is_pos_in_string(line, (colnr_T)(p - line))) { + && !is_pos_in_string((char_u *)line, (colnr_T)(p - line))) { break; } - ++p; + p++; } } @@ -2375,7 +2372,7 @@ void showmatch(int c) * Only show match for chars in the 'matchpairs' option. */ // 'matchpairs' is "x:y,x:y" - for (p = curbuf->b_p_mps; *p != NUL; p++) { + for (p = (char_u *)curbuf->b_p_mps; *p != NUL; p++) { if (utf_ptr2char((char *)p) == c && (curwin->w_p_rl ^ p_ri)) { break; } @@ -2412,7 +2409,7 @@ void showmatch(int c) dollar_vcol = -1; } curwin->w_virtcol++; // do display ')' just before "$" - update_screen(VALID); // show the new char first + update_screen(UPD_VALID); // show the new char first save_dollar_vcol = dollar_vcol; save_state = State; @@ -2421,7 +2418,7 @@ void showmatch(int c) curwin->w_cursor = mpos; // move to matching char *so = 0; // don't use 'scrolloff' here *siso = 0; // don't use 'sidescrolloff' here - showruler(false); + show_cursor_info(false); setcursor(); ui_flush(); // Restore dollar_vcol(), because setcursor() may call curs_rows() @@ -2447,1816 +2444,6 @@ void showmatch(int c) } } -// Find the start of the next sentence, searching in the direction specified -// by the "dir" argument. The cursor is positioned on the start of the next -// sentence when found. If the next sentence is found, return OK. Return FAIL -// otherwise. See ":h sentence" for the precise definition of a "sentence" -// text object. -int findsent(Direction dir, long count) -{ - pos_T pos, tpos; - int c; - int (*func)(pos_T *); - bool noskip = false; // do not skip blanks - - pos = curwin->w_cursor; - if (dir == FORWARD) { - func = incl; - } else { - func = decl; - } - - while (count--) { - const pos_T prev_pos = pos; - - // if on an empty line, skip up to a non-empty line - if (gchar_pos(&pos) == NUL) { - do { - if ((*func)(&pos) == -1) { - break; - } - } while (gchar_pos(&pos) == NUL); - if (dir == FORWARD) { - goto found; - } - // if on the start of a paragraph or a section and searching forward, - // go to the next line - } else if (dir == FORWARD && pos.col == 0 - && startPS(pos.lnum, NUL, false)) { - if (pos.lnum == curbuf->b_ml.ml_line_count) { - return FAIL; - } - pos.lnum++; - goto found; - } else if (dir == BACKWARD) { - decl(&pos); - } - - // go back to the previous non-white non-punctuation character - bool found_dot = false; - while (c = gchar_pos(&pos), ascii_iswhite(c) - || vim_strchr(".!?)]\"'", c) != NULL) { - tpos = pos; - if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { - break; - } - if (found_dot) { - break; - } - if (vim_strchr(".!?", c) != NULL) { - found_dot = true; - } - if (vim_strchr(")]\"'", c) != NULL - && vim_strchr(".!?)]\"'", gchar_pos(&tpos)) == NULL) { - break; - } - decl(&pos); - } - - // remember the line where the search started - const int startlnum = pos.lnum; - const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; - - for (;;) { // find end of sentence - c = gchar_pos(&pos); - if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) { - if (dir == BACKWARD && pos.lnum != startlnum) { - ++pos.lnum; - } - break; - } - if (c == '.' || c == '!' || c == '?') { - tpos = pos; - do { - if ((c = inc(&tpos)) == -1) { - break; - } - } while (vim_strchr(")]\"'", c = gchar_pos(&tpos)) - != NULL); - if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL - || (cpo_J && (c == ' ' && inc(&tpos) >= 0 - && gchar_pos(&tpos) == ' '))) { - pos = tpos; - if (gchar_pos(&pos) == NUL) { // skip NUL at EOL - inc(&pos); - } - break; - } - } - if ((*func)(&pos) == -1) { - if (count) { - return FAIL; - } - noskip = true; - break; - } - } -found: - // skip white space - while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) { - if (incl(&pos) == -1) { - break; - } - } - - if (equalpos(prev_pos, pos)) { - // didn't actually move, advance one character and try again - if ((*func)(&pos) == -1) { - if (count) { - return FAIL; - } - break; - } - count++; - } - } - - setpcmark(); - curwin->w_cursor = pos; - return OK; -} - -/// Find the next paragraph or section in direction 'dir'. -/// Paragraphs are currently supposed to be separated by empty lines. -/// If 'what' is NUL we go to the next paragraph. -/// If 'what' is '{' or '}' we go to the next section. -/// If 'both' is TRUE also stop at '}'. -/// -/// @param pincl Return: true if last char is to be included -/// -/// @return TRUE if the next paragraph or section was found. -bool findpar(bool *pincl, int dir, long count, int what, int both) -{ - linenr_T curr; - bool did_skip; // true after separating lines have been skipped - bool first; // true on first line - linenr_T fold_first; // first line of a closed fold - linenr_T fold_last; // last line of a closed fold - bool fold_skipped; // true if a closed fold was skipped this - // iteration - - curr = curwin->w_cursor.lnum; - - while (count--) { - did_skip = false; - for (first = true;; first = false) { - if (*ml_get(curr) != NUL) { - did_skip = true; - } - - // skip folded lines - fold_skipped = false; - if (first && hasFolding(curr, &fold_first, &fold_last)) { - curr = ((dir > 0) ? fold_last : fold_first) + dir; - fold_skipped = true; - } - - if (!first && did_skip && startPS(curr, what, both)) { - break; - } - - if (fold_skipped) { - curr -= dir; - } - if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { - if (count) { - return false; - } - curr -= dir; - break; - } - } - } - setpcmark(); - if (both && *ml_get(curr) == '}') { // include line with '}' - ++curr; - } - curwin->w_cursor.lnum = curr; - if (curr == curbuf->b_ml.ml_line_count && what != '}') { - char_u *line = ml_get(curr); - - // Put the cursor on the last character in the last line and make the - // motion inclusive. - if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) { - curwin->w_cursor.col--; - curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); - *pincl = true; - } - } else { - curwin->w_cursor.col = 0; - } - return true; -} - -/* - * check if the string 's' is a nroff macro that is in option 'opt' - */ -static int inmacro(char_u *opt, char_u *s) -{ - char_u *macro; - - for (macro = opt; macro[0]; macro++) { - // Accept two characters in the option being equal to two characters - // in the line. A space in the option matches with a space in the - // line or the line having ended. - if ((macro[0] == s[0] - || (macro[0] == ' ' - && (s[0] == NUL || s[0] == ' '))) - && (macro[1] == s[1] - || ((macro[1] == NUL || macro[1] == ' ') - && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { - break; - } - ++macro; - if (macro[0] == NUL) { - break; - } - } - return macro[0] != NUL; -} - -/* - * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. - * If 'para' is '{' or '}' only check for sections. - * If 'both' is TRUE also stop at '}' - */ -int startPS(linenr_T lnum, int para, int both) -{ - char_u *s; - - s = ml_get(lnum); - if (*s == para || *s == '\f' || (both && *s == '}')) { - return true; - } - if (*s == '.' && (inmacro(p_sections, s + 1) - || (!para && inmacro(p_para, s + 1)))) { - return true; - } - return false; -} - -/* - * The following routines do the word searches performed by the 'w', 'W', - * 'b', 'B', 'e', and 'E' commands. - */ - -/* - * To perform these searches, characters are placed into one of three - * classes, and transitions between classes determine word boundaries. - * - * The classes are: - * - * 0 - white space - * 1 - punctuation - * 2 or higher - keyword characters (letters, digits and underscore) - */ - -static int cls_bigword; // TRUE for "W", "B" or "E" - -/* - * cls() - returns the class of character at curwin->w_cursor - * - * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars - * from class 2 and higher are reported as class 1 since only white space - * boundaries are of interest. - */ -static int cls(void) -{ - int c; - - c = gchar_cursor(); - if (c == ' ' || c == '\t' || c == NUL) { - return 0; - } - - c = utf_class(c); - - // If cls_bigword is TRUE, report all non-blanks as class 1. - if (c != 0 && cls_bigword) { - return 1; - } - return c; -} - -/// fwd_word(count, type, eol) - move forward one word -/// -/// @return FAIL if the cursor was already at the end of the file. -/// If eol is TRUE, last word stops at end of line (for operators). -/// -/// @param bigword "W", "E" or "B" -int fwd_word(long count, int bigword, int eol) -{ - int sclass; // starting class - int i; - int last_line; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - // When inside a range of folded lines, move to the last char of the - // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); - } - sclass = cls(); - - /* - * We always move at least one character, unless on the last - * character in the buffer. - */ - last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); - i = inc_cursor(); - if (i == -1 || (i >= 1 && last_line)) { // started at last char in file - return FAIL; - } - if (i >= 1 && eol && count == 0) { // started at last char in line - return OK; - } - - /* - * Go one char past end of current word (if any) - */ - if (sclass != 0) { - while (cls() == sclass) { - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) { - return OK; - } - } - } - - /* - * go to next non-white - */ - while (cls() == 0) { - /* - * We'll stop if we land on a blank line - */ - if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) { - break; - } - - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) { - return OK; - } - } - } - return OK; -} - -/* - * bck_word() - move backward 'count' words - * - * If stop is TRUE and we are already on the start of a word, move one less. - * - * Returns FAIL if top of the file was reached. - */ -int bck_word(long count, int bigword, int stop) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - /* When inside a range of folded lines, move to the first char of the - * first line. */ - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { - curwin->w_cursor.col = 0; - } - sclass = cls(); - if (dec_cursor() == -1) { // started at start of file - return FAIL; - } - - if (!stop || sclass == cls() || sclass == 0) { - /* - * Skip white space before the word. - * Stop on an empty line. - */ - while (cls() == 0) { - if (curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) { - goto finished; - } - if (dec_cursor() == -1) { // hit start of file, stop here - return OK; - } - } - - /* - * Move backward to start of this word. - */ - if (skip_chars(cls(), BACKWARD)) { - return OK; - } - } - - inc_cursor(); // overshot - forward one -finished: - stop = FALSE; - } - return OK; -} - -/* - * end_word() - move to the end of the word - * - * There is an apparent bug in the 'e' motion of the real vi. At least on the - * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' - * motion crosses blank lines. When the real vi crosses a blank line in an - * 'e' motion, the cursor is placed on the FIRST character of the next - * non-blank line. The 'E' command, however, works correctly. Since this - * appears to be a bug, I have not duplicated it here. - * - * Returns FAIL if end of the file was reached. - * - * If stop is TRUE and we are already on the end of a word, move one less. - * If empty is TRUE stop on an empty line. - */ -int end_word(long count, int bigword, int stop, int empty) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - /* When inside a range of folded lines, move to the last char of the - * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); - } - sclass = cls(); - if (inc_cursor() == -1) { - return FAIL; - } - - /* - * If we're in the middle of a word, we just have to move to the end - * of it. - */ - if (cls() == sclass && sclass != 0) { - /* - * Move forward to end of the current word - */ - if (skip_chars(sclass, FORWARD)) { - return FAIL; - } - } else if (!stop || sclass == 0) { - /* - * We were at the end of a word. Go to the end of the next word. - * First skip white space, if 'empty' is TRUE, stop at empty line. - */ - while (cls() == 0) { - if (empty && curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) { - goto finished; - } - if (inc_cursor() == -1) { // hit end of file, stop here - return FAIL; - } - } - - /* - * Move forward to the end of this word. - */ - if (skip_chars(cls(), FORWARD)) { - return FAIL; - } - } - dec_cursor(); // overshot - one char backward -finished: - stop = FALSE; // we move only one word less - } - return OK; -} - -/// Move back to the end of the word. -/// -/// @param bigword TRUE for "B" -/// @param eol if true, then stop at end of line. -/// -/// @return FAIL if start of the file was reached. -int bckend_word(long count, int bigword, bool eol) -{ - int sclass; // starting class - int i; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - sclass = cls(); - if ((i = dec_cursor()) == -1) { - return FAIL; - } - if (eol && i == 1) { - return OK; - } - - /* - * Move backward to before the start of this word. - */ - if (sclass != 0) { - while (cls() == sclass) { - if ((i = dec_cursor()) == -1 || (eol && i == 1)) { - return OK; - } - } - } - - /* - * Move backward to end of the previous word - */ - while (cls() == 0) { - if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { - break; - } - if ((i = dec_cursor()) == -1 || (eol && i == 1)) { - return OK; - } - } - } - return OK; -} - -/// Skip a row of characters of the same class. -/// -/// @return true when end-of-file reached, false otherwise. -static bool skip_chars(int cclass, int dir) -{ - while (cls() == cclass) { - if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) { - return true; - } - } - return false; -} - -/* - * Go back to the start of the word or the start of white space - */ -static void back_in_line(void) -{ - int sclass; // starting class - - sclass = cls(); - for (;;) { - if (curwin->w_cursor.col == 0) { // stop at start of line - break; - } - dec_cursor(); - if (cls() != sclass) { // stop at start of word - inc_cursor(); - break; - } - } -} - -static void find_first_blank(pos_T *posp) -{ - int c; - - while (decl(posp) != -1) { - c = gchar_pos(posp); - if (!ascii_iswhite(c)) { - incl(posp); - break; - } - } -} - -/// Skip count/2 sentences and count/2 separating white spaces. -/// -/// @param at_start_sent cursor is at start of sentence -static void findsent_forward(long count, bool at_start_sent) -{ - while (count--) { - findsent(FORWARD, 1L); - if (at_start_sent) { - find_first_blank(&curwin->w_cursor); - } - if (count == 0 || at_start_sent) { - decl(&curwin->w_cursor); - } - at_start_sent = !at_start_sent; - } -} - -/// Find word under cursor, cursor at end. -/// Used while an operator is pending, and in Visual mode. -/// -/// @param include TRUE: include word and white space -/// @param bigword FALSE == word, TRUE == WORD -int current_word(oparg_T *oap, long count, int include, int bigword) -{ - pos_T start_pos; - pos_T pos; - bool inclusive = true; - int include_white = FALSE; - - cls_bigword = bigword; - clearpos(&start_pos); - - // Correct cursor when 'selection' is exclusive - if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) { - dec_cursor(); - } - - /* - * When Visual mode is not active, or when the VIsual area is only one - * character, select the word and/or white space under the cursor. - */ - if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { - /* - * Go to start of current word or white space. - */ - back_in_line(); - start_pos = curwin->w_cursor; - - /* - * If the start is on white space, and white space should be included - * (" word"), or start is not on white space, and white space should - * not be included ("word"), find end of word. - */ - if ((cls() == 0) == include) { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) { - return FAIL; - } - } else { - /* - * If the start is not on white space, and white space should be - * included ("word "), or start is on white space and white - * space should not be included (" "), find start of word. - * If we end up in the first column of the next line (single char - * word) back up to end of the line. - */ - fwd_word(1L, bigword, TRUE); - if (curwin->w_cursor.col == 0) { - decl(&curwin->w_cursor); - } else { - oneleft(); - } - - if (include) { - include_white = TRUE; - } - } - - if (VIsual_active) { - // should do something when inclusive == false ! - VIsual = start_pos; - redraw_curbuf_later(INVERTED); // update the inversion - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - } - --count; - } - - /* - * When count is still > 0, extend with more objects. - */ - while (count > 0) { - inclusive = true; - if (VIsual_active && lt(curwin->w_cursor, VIsual)) { - /* - * In Visual mode, with cursor at start: move cursor back. - */ - if (decl(&curwin->w_cursor) == -1) { - return FAIL; - } - if (include != (cls() != 0)) { - if (bck_word(1L, bigword, TRUE) == FAIL) { - return FAIL; - } - } else { - if (bckend_word(1L, bigword, true) == FAIL) { - return FAIL; - } - (void)incl(&curwin->w_cursor); - } - } else { - /* - * Move cursor forward one word and/or white area. - */ - if (incl(&curwin->w_cursor) == -1) { - return FAIL; - } - if (include != (cls() == 0)) { - if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) { - return FAIL; - } - /* - * If end is just past a new-line, we don't want to include - * the first character on the line. - * Put cursor on last char of white. - */ - if (oneleft() == FAIL) { - inclusive = false; - } - } else { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) { - return FAIL; - } - } - } - --count; - } - - if (include_white && (cls() != 0 - || (curwin->w_cursor.col == 0 && !inclusive))) { - /* - * If we don't include white space at the end, move the start - * to include some white space there. This makes "daw" work - * better on the last word in a sentence (and "2daw" on last-but-one - * word). Also when "2daw" deletes "word." at the end of the line - * (cursor is at start of next line). - * But don't delete white space at start of line (indent). - */ - pos = curwin->w_cursor; // save cursor position - curwin->w_cursor = start_pos; - if (oneleft() == OK) { - back_in_line(); - if (cls() == 0 && curwin->w_cursor.col > 0) { - if (VIsual_active) { - VIsual = curwin->w_cursor; - } else { - oap->start = curwin->w_cursor; - } - } - } - curwin->w_cursor = pos; // put cursor back at end - } - - if (VIsual_active) { - if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) { - inc_cursor(); - } - if (VIsual_mode == 'V') { - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - } - } else { - oap->inclusive = inclusive; - } - - return OK; -} - -/* - * Find sentence(s) under the cursor, cursor at end. - * When Visual active, extend it by one or more sentences. - */ -int current_sent(oparg_T *oap, long count, int include) -{ - pos_T start_pos; - pos_T pos; - bool start_blank; - int c; - bool at_start_sent; - long ncount; - - start_pos = curwin->w_cursor; - pos = start_pos; - findsent(FORWARD, 1L); // Find start of next sentence. - - /* - * When the Visual area is bigger than one character: Extend it. - */ - if (VIsual_active && !equalpos(start_pos, VIsual)) { -extend: - if (lt(start_pos, VIsual)) { - /* - * Cursor at start of Visual area. - * Find out where we are: - * - in the white space before a sentence - * - in a sentence or just after it - * - at the start of a sentence - */ - at_start_sent = true; - decl(&pos); - while (lt(pos, curwin->w_cursor)) { - c = gchar_pos(&pos); - if (!ascii_iswhite(c)) { - at_start_sent = false; - break; - } - incl(&pos); - } - if (!at_start_sent) { - findsent(BACKWARD, 1L); - if (equalpos(curwin->w_cursor, start_pos)) { - at_start_sent = true; // exactly at start of sentence - } else { - // inside a sentence, go to its end (start of next) - findsent(FORWARD, 1L); - } - } - if (include) { // "as" gets twice as much as "is" - count *= 2; - } - while (count--) { - if (at_start_sent) { - find_first_blank(&curwin->w_cursor); - } - c = gchar_cursor(); - if (!at_start_sent || (!include && !ascii_iswhite(c))) { - findsent(BACKWARD, 1L); - } - at_start_sent = !at_start_sent; - } - } else { - /* - * Cursor at end of Visual area. - * Find out where we are: - * - just before a sentence - * - just before or in the white space before a sentence - * - in a sentence - */ - incl(&pos); - at_start_sent = true; - if (!equalpos(pos, curwin->w_cursor)) { // not just before a sentence - at_start_sent = false; - while (lt(pos, curwin->w_cursor)) { - c = gchar_pos(&pos); - if (!ascii_iswhite(c)) { - at_start_sent = true; - break; - } - incl(&pos); - } - if (at_start_sent) { // in the sentence - findsent(BACKWARD, 1L); - } else { // in/before white before a sentence - curwin->w_cursor = start_pos; - } - } - - if (include) { // "as" gets twice as much as "is" - count *= 2; - } - findsent_forward(count, at_start_sent); - if (*p_sel == 'e') { - ++curwin->w_cursor.col; - } - } - return OK; - } - - /* - * If the cursor started on a blank, check if it is just before the start - * of the next sentence. - */ - while (c = gchar_pos(&pos), ascii_iswhite(c)) { - incl(&pos); - } - if (equalpos(pos, curwin->w_cursor)) { - start_blank = true; - find_first_blank(&start_pos); // go back to first blank - } else { - start_blank = false; - findsent(BACKWARD, 1L); - start_pos = curwin->w_cursor; - } - if (include) { - ncount = count * 2; - } else { - ncount = count; - if (start_blank) { - --ncount; - } - } - if (ncount > 0) { - findsent_forward(ncount, true); - } else { - decl(&curwin->w_cursor); - } - - if (include) { - /* - * If the blank in front of the sentence is included, exclude the - * blanks at the end of the sentence, go back to the first blank. - * If there are no trailing blanks, try to include leading blanks. - */ - if (start_blank) { - find_first_blank(&curwin->w_cursor); - c = gchar_pos(&curwin->w_cursor); - if (ascii_iswhite(c)) { - decl(&curwin->w_cursor); - } - } else if (c = gchar_cursor(), !ascii_iswhite(c)) { - find_first_blank(&start_pos); - } - } - - if (VIsual_active) { - // Avoid getting stuck with "is" on a single space before a sentence. - if (equalpos(start_pos, curwin->w_cursor)) { - goto extend; - } - if (*p_sel == 'e') { - ++curwin->w_cursor.col; - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - redraw_curbuf_later(INVERTED); // update the inversion - } else { - // include a newline after the sentence, if there is one - if (incl(&curwin->w_cursor) == -1) { - oap->inclusive = true; - } else { - oap->inclusive = false; - } - oap->start = start_pos; - oap->motion_type = kMTCharWise; - } - return OK; -} - -/// Find block under the cursor, cursor at end. -/// "what" and "other" are two matching parenthesis/brace/etc. -/// -/// @param include TRUE == include white space -/// @param what '(', '{', etc. -/// @param other ')', '}', etc. -int current_block(oparg_T *oap, long count, int include, int what, int other) -{ - pos_T old_pos; - pos_T *pos = NULL; - pos_T start_pos; - pos_T *end_pos; - pos_T old_start, old_end; - char *save_cpo; - bool sol = false; // '{' at start of line - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - - /* - * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. - */ - if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { - setpcmark(); - if (what == '{') { // ignore indent - while (inindent(1)) { - if (inc_cursor() != 0) { - break; - } - } - } - if (gchar_cursor() == what) { - // cursor on '(' or '{', move cursor just after it - ++curwin->w_cursor.col; - } - } else if (lt(VIsual, curwin->w_cursor)) { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } else { - old_end = VIsual; - } - - // Search backwards for unclosed '(', '{', etc.. - // Put this position in start_pos. - // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the - // user wants. - save_cpo = p_cpo; - p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"; - if ((pos = findmatch(NULL, what)) != NULL) { - while (count-- > 0) { - if ((pos = findmatch(NULL, what)) == NULL) { - break; - } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } else { - while (count-- > 0) { - if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { - break; - } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } - p_cpo = save_cpo; - - /* - * Search for matching ')', '}', etc. - * Put this position in curwin->w_cursor. - */ - if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - - // Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. - // If the ending '}', ')' or ']' is only preceded by indent, skip that - // indent. But only if the resulting area is not smaller than what we - // started with. - while (!include) { - incl(&start_pos); - sol = (curwin->w_cursor.col == 0); - decl(&curwin->w_cursor); - while (inindent(1)) { - sol = true; - if (decl(&curwin->w_cursor) != 0) { - break; - } - } - - // In Visual mode, when the resulting area is not bigger than what we - // started with, extend it to the next block, and then exclude again. - // Don't try to expand the area if the area is empty. - if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) - && !equalpos(start_pos, curwin->w_cursor) - && VIsual_active) { - curwin->w_cursor = old_start; - decl(&curwin->w_cursor); - if ((pos = findmatch(NULL, what)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - start_pos = *pos; - curwin->w_cursor = *pos; - if ((end_pos = findmatch(NULL, other)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - } else { - break; - } - } - - if (VIsual_active) { - if (*p_sel == 'e') { - inc(&curwin->w_cursor); - } - if (sol && gchar_cursor() != NUL) { - inc(&curwin->w_cursor); // include the line break - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - oap->inclusive = false; - if (sol) { - incl(&curwin->w_cursor); - } else if (ltoreq(start_pos, curwin->w_cursor)) { - // Include the character under the cursor. - oap->inclusive = true; - } else { - // End is before the start (no text in between <>, [], etc.): don't - // operate on any text. - curwin->w_cursor = start_pos; - } - } - - return OK; -} - -/// @param end_tag when true, return true if the cursor is on "</aaa>". -/// -/// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". -static bool in_html_tag(bool end_tag) -{ - char_u *line = get_cursor_line_ptr(); - char_u *p; - int c; - int lc = NUL; - pos_T pos; - - for (p = line + curwin->w_cursor.col; p > line;) { - if (*p == '<') { // find '<' under/before cursor - break; - } - MB_PTR_BACK(line, p); - if (*p == '>') { // find '>' before cursor - break; - } - } - if (*p != '<') { - return false; - } - - pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)(p - line); - - MB_PTR_ADV(p); - if (end_tag) { - // check that there is a '/' after the '<' - return *p == '/'; - } - - // check that there is no '/' after the '<' - if (*p == '/') { - return false; - } - - // check that the matching '>' is not preceded by '/' - for (;;) { - if (inc(&pos) < 0) { - return false; - } - c = *ml_get_pos(&pos); - if (c == '>') { - break; - } - lc = c; - } - return lc != '/'; -} - -/// Find tag block under the cursor, cursor at end. -/// -/// @param include true == include white space -int current_tagblock(oparg_T *oap, long count_arg, bool include) -{ - long count = count_arg; - pos_T old_pos; - pos_T start_pos; - pos_T end_pos; - pos_T old_start, old_end; - char_u *p; - char_u *cp; - int len; - bool do_include = include; - bool save_p_ws = p_ws; - int retval = FAIL; - int is_inclusive = true; - - p_ws = false; - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - if (!VIsual_active || *p_sel == 'e') { - decl(&old_end); // old_end is inclusive - } - /* - * If we start on "<aaa>" select that block. - */ - if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { - setpcmark(); - - // ignore indent - while (inindent(1)) { - if (inc_cursor() != 0) { - break; - } - } - - if (in_html_tag(false)) { - // cursor on start tag, move to its '>' - while (*get_cursor_pos_ptr() != '>') { - if (inc_cursor() < 0) { - break; - } - } - } else if (in_html_tag(true)) { - // cursor on end tag, move to just before it - while (*get_cursor_pos_ptr() != '<') { - if (dec_cursor() < 0) { - break; - } - } - dec_cursor(); - old_end = curwin->w_cursor; - } - } else if (lt(VIsual, curwin->w_cursor)) { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } else { - old_end = VIsual; - } - -again: - /* - * Search backwards for unclosed "<aaa>". - * Put this position in start_pos. - */ - for (long n = 0; n < count; n++) { - if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - "", - "</[^>]*>", BACKWARD, NULL, 0, - NULL, (linenr_T)0, 0L) <= 0) { - curwin->w_cursor = old_pos; - goto theend; - } - } - start_pos = curwin->w_cursor; - - /* - * Search for matching "</aaa>". First isolate the "aaa". - */ - inc_cursor(); - p = get_cursor_pos_ptr(); - for (cp = p; - *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); - MB_PTR_ADV(cp)) {} - len = (int)(cp - p); - if (len == 0) { - curwin->w_cursor = old_pos; - goto theend; - } - const size_t spat_len = (size_t)len + 39; - char *const spat = xmalloc(spat_len); - const size_t epat_len = (size_t)len + 9; - char *const epat = xmalloc(epat_len); - snprintf(spat, spat_len, - "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); - snprintf(epat, epat_len, "</%.*s>\\c", len, p); - - const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); - - xfree(spat); - xfree(epat); - - if (r < 1 || lt(curwin->w_cursor, old_end)) { - // Can't find other end or it's before the previous end. Could be a - // HTML tag that doesn't have a matching end. Search backwards for - // another starting tag. - count = 1; - curwin->w_cursor = start_pos; - goto again; - } - - if (do_include) { - // Include up to the '>'. - while (*get_cursor_pos_ptr() != '>') { - if (inc_cursor() < 0) { - break; - } - } - } else { - char_u *c = get_cursor_pos_ptr(); - // Exclude the '<' of the end tag. - // If the closing tag is on new line, do not decrement cursor, but make - // operation exclusive, so that the linefeed will be selected - if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { - // do not decrement cursor - is_inclusive = false; - } else if (*c == '<') { - dec_cursor(); - } - } - end_pos = curwin->w_cursor; - - if (!do_include) { - // Exclude the start tag. - curwin->w_cursor = start_pos; - while (inc_cursor() >= 0) { - if (*get_cursor_pos_ptr() == '>') { - inc_cursor(); - start_pos = curwin->w_cursor; - break; - } - } - curwin->w_cursor = end_pos; - - // If we are in Visual mode and now have the same text as before set - // "do_include" and try again. - if (VIsual_active - && equalpos(start_pos, old_start) - && equalpos(end_pos, old_end)) { - do_include = true; - curwin->w_cursor = old_start; - count = count_arg; - goto again; - } - } - - if (VIsual_active) { - // If the end is before the start there is no text between tags, select - // the char under the cursor. - if (lt(end_pos, start_pos)) { - curwin->w_cursor = start_pos; - } else if (*p_sel == 'e') { - inc_cursor(); - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - if (lt(end_pos, start_pos)) { - // End is before the start: there is no text between tags; operate - // on an empty area. - curwin->w_cursor = start_pos; - oap->inclusive = false; - } else { - oap->inclusive = is_inclusive; - } - } - retval = OK; - -theend: - p_ws = save_p_ws; - return retval; -} - -/// @param include TRUE == include white space -/// @param type 'p' for paragraph, 'S' for section -int current_par(oparg_T *oap, long count, int include, int type) -{ - linenr_T start_lnum; - linenr_T end_lnum; - int white_in_front; - int dir; - int start_is_white; - int prev_start_is_white; - int retval = OK; - int do_white = FALSE; - int t; - int i; - - if (type == 'S') { // not implemented yet - return FAIL; - } - - start_lnum = curwin->w_cursor.lnum; - - /* - * When visual area is more than one line: extend it. - */ - if (VIsual_active && start_lnum != VIsual.lnum) { -extend: - if (start_lnum < VIsual.lnum) { - dir = BACKWARD; - } else { - dir = FORWARD; - } - for (i = (int)count; --i >= 0;) { - if (start_lnum == - (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { - retval = FAIL; - break; - } - - prev_start_is_white = -1; - for (t = 0; t < 2; ++t) { - start_lnum += dir; - start_is_white = linewhite(start_lnum); - if (prev_start_is_white == start_is_white) { - start_lnum -= dir; - break; - } - for (;;) { - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) { - break; - } - if (start_is_white != linewhite(start_lnum + dir) - || (!start_is_white - && startPS(start_lnum + (dir > 0 - ? 1 : 0), 0, 0))) { - break; - } - start_lnum += dir; - } - if (!include) { - break; - } - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) { - break; - } - prev_start_is_white = start_is_white; - } - } - curwin->w_cursor.lnum = start_lnum; - curwin->w_cursor.col = 0; - return retval; - } - - /* - * First move back to the start_lnum of the paragraph or white lines - */ - white_in_front = linewhite(start_lnum); - while (start_lnum > 1) { - if (white_in_front) { // stop at first white line - if (!linewhite(start_lnum - 1)) { - break; - } - } else { // stop at first non-white line of start of paragraph - if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) { - break; - } - } - --start_lnum; - } - - /* - * Move past the end of any white lines. - */ - end_lnum = start_lnum; - while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { - ++end_lnum; - } - - end_lnum--; - i = (int)count; - if (!include && white_in_front) { - --i; - } - while (i--) { - if (end_lnum == curbuf->b_ml.ml_line_count) { - return FAIL; - } - - if (!include) { - do_white = linewhite(end_lnum + 1); - } - - if (include || !do_white) { - ++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; - } - } - - if (i == 0 && white_in_front && include) { - break; - } - - /* - * skip to end of white lines after paragraph - */ - if (include || do_white) { - while (end_lnum < curbuf->b_ml.ml_line_count - && linewhite(end_lnum + 1)) { - ++end_lnum; - } - } - } - - /* - * If there are no empty lines at the end, try to find some empty lines at - * the start (unless that has been done already). - */ - if (!white_in_front && !linewhite(end_lnum) && include) { - while (start_lnum > 1 && linewhite(start_lnum - 1)) { - --start_lnum; - } - } - - if (VIsual_active) { - // Problem: when doing "Vipipip" nothing happens in a single white - // line, we get stuck there. Trap this here. - if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { - goto extend; - } - if (VIsual.lnum != start_lnum) { - VIsual.lnum = start_lnum; - VIsual.col = 0; - } - VIsual_mode = 'V'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start.lnum = start_lnum; - oap->start.col = 0; - oap->motion_type = kMTLineWise; - } - curwin->w_cursor.lnum = end_lnum; - curwin->w_cursor.col = 0; - - return OK; -} - -/// Search quote char from string line[col]. -/// Quote character escaped by one of the characters in "escape" is not counted -/// as a quote. -/// -/// @param escape escape characters, can be NULL -/// -/// @return column number of "quotechar" or -1 when not found. -static int find_next_quote(char_u *line, int col, int quotechar, char_u *escape) -{ - int c; - - for (;;) { - c = line[col]; - if (c == NUL) { - return -1; - } else if (escape != NULL && vim_strchr((char *)escape, c)) { - col++; - if (line[col] == NUL) { - return -1; - } - } else if (c == quotechar) { - break; - } - col += utfc_ptr2len((char *)line + col); - } - return col; -} - -/// Search backwards in "line" from column "col_start" to find "quotechar". -/// Quote character escaped by one of the characters in "escape" is not counted -/// as a quote. -/// -/// @param escape escape characters, can be NULL -/// -/// @return the found column or zero. -static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *escape) -{ - int n; - - while (col_start > 0) { - col_start--; - col_start -= utf_head_off(line, line + col_start); - n = 0; - if (escape != NULL) { - while (col_start - n > 0 && vim_strchr((char *)escape, - line[col_start - n - 1]) != NULL) { - ++n; - } - } - if (n & 1) { - col_start -= n; // uneven number of escape chars, skip it - } else if (line[col_start] == quotechar) { - break; - } - } - return col_start; -} - -/// Find quote under the cursor, cursor at end. -/// -/// @param include true == include quote char -/// @param quotechar Quote character -/// -/// @return true if found, else false. -bool current_quote(oparg_T *oap, long count, bool include, int quotechar) - FUNC_ATTR_NONNULL_ALL -{ - char_u *line = get_cursor_line_ptr(); - int col_end; - int col_start = curwin->w_cursor.col; - bool inclusive = false; - bool vis_empty = true; // Visual selection <= 1 char - bool vis_bef_curs = false; // Visual starts before cursor - bool did_exclusive_adj = false; // adjusted pos for 'selection' - bool inside_quotes = false; // Looks like "i'" done before - bool selected_quote = false; // Has quote inside selection - int i; - bool restore_vis_bef = false; // resotre VIsual on abort - - // When 'selection' is "exclusive" move the cursor to where it would be - // with 'selection' "inclusive", so that the logic is the same for both. - // The cursor then is moved forward after adjusting the area. - if (VIsual_active) { - // this only works within one line - if (VIsual.lnum != curwin->w_cursor.lnum) { - return false; - } - - vis_bef_curs = lt(VIsual, curwin->w_cursor); - vis_empty = equalpos(VIsual, curwin->w_cursor); - if (*p_sel == 'e') { - if (vis_bef_curs) { - dec_cursor(); - did_exclusive_adj = true; - } else if (!vis_empty) { - dec(&VIsual); - did_exclusive_adj = true; - } - vis_empty = equalpos(VIsual, curwin->w_cursor); - if (!vis_bef_curs && !vis_empty) { - // VIsual needs to be start of Visual selection. - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - vis_bef_curs = true; - restore_vis_bef = true; - } - } - } - - if (!vis_empty) { - // Check if the existing selection exactly spans the text inside - // quotes. - if (vis_bef_curs) { - inside_quotes = VIsual.col > 0 - && line[VIsual.col - 1] == quotechar - && line[curwin->w_cursor.col] != NUL - && line[curwin->w_cursor.col + 1] == quotechar; - i = VIsual.col; - col_end = curwin->w_cursor.col; - } else { - inside_quotes = curwin->w_cursor.col > 0 - && line[curwin->w_cursor.col - 1] == quotechar - && line[VIsual.col] != NUL - && line[VIsual.col + 1] == quotechar; - i = curwin->w_cursor.col; - col_end = VIsual.col; - } - - // Find out if we have a quote in the selection. - while (i <= col_end) { - // check for going over the end of the line, which can happen if - // the line was changed after the Visual area was selected. - if (line[i] == NUL) { - break; - } - if (line[i++] == quotechar) { - selected_quote = true; - break; - } - } - } - - if (!vis_empty && line[col_start] == quotechar) { - // Already selecting something and on a quote character. Find the - // next quoted string. - if (vis_bef_curs) { - // Assume we are on a closing quote: move to after the next - // opening quote. - col_start = find_next_quote(line, col_start + 1, quotechar, NULL); - if (col_start < 0) { - goto abort_search; - } - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - // We were on a starting quote perhaps? - col_end = col_start; - col_start = curwin->w_cursor.col; - } - } else { - col_end = find_prev_quote(line, col_start, quotechar, NULL); - if (line[col_end] != quotechar) { - goto abort_search; - } - col_start = find_prev_quote(line, col_end, quotechar, - curbuf->b_p_qe); - if (line[col_start] != quotechar) { - // We were on an ending quote perhaps? - col_start = col_end; - col_end = curwin->w_cursor.col; - } - } - } else if (line[col_start] == quotechar || !vis_empty) { - int first_col = col_start; - - if (!vis_empty) { - if (vis_bef_curs) { - first_col = find_next_quote(line, col_start, quotechar, NULL); - } else { - first_col = find_prev_quote(line, col_start, quotechar, NULL); - } - } - // The cursor is on a quote, we don't know if it's the opening or - // closing quote. Search from the start of the line to find out. - // Also do this when there is a Visual area, a' may leave the cursor - // in between two strings. - col_start = 0; - for (;;) { - // Find open quote character. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0 || col_start > first_col) { - goto abort_search; - } - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - goto abort_search; - } - // If is cursor between start and end quote character, it is - // target text object. - if (col_start <= first_col && first_col <= col_end) { - break; - } - col_start = col_end + 1; - } - } else { - // Search backward for a starting quote. - col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); - if (line[col_start] != quotechar) { - // No quote before the cursor, look after the cursor. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0) { - goto abort_search; - } - } - - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - goto abort_search; - } - } - - // When "include" is true, include spaces after closing quote or before - // the starting quote. - if (include) { - if (ascii_iswhite(line[col_end + 1])) { - while (ascii_iswhite(line[col_end + 1])) { - ++col_end; - } - } else { - while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { - --col_start; - } - } - } - - // Set start position. After vi" another i" must include the ". - // For v2i" include the quotes. - if (!include && count < 2 && (vis_empty || !inside_quotes)) { - col_start++; - } - curwin->w_cursor.col = col_start; - if (VIsual_active) { - // Set the start of the Visual area when the Visual area was empty, we - // were just inside quotes or the Visual area didn't start at a quote - // and didn't include a quote. - if (vis_empty - || (vis_bef_curs - && !selected_quote - && (inside_quotes - || (line[VIsual.col] != quotechar - && (VIsual.col == 0 - || line[VIsual.col - 1] != quotechar))))) { - VIsual = curwin->w_cursor; - redraw_curbuf_later(INVERTED); - } - } else { - oap->start = curwin->w_cursor; - oap->motion_type = kMTCharWise; - } - - // Set end position. - curwin->w_cursor.col = col_end; - if ((include || count > 1 - // After vi" another i" must include the ". - || (!vis_empty && inside_quotes) - ) && inc_cursor() == 2) { - inclusive = true; - } - if (VIsual_active) { - if (vis_empty || vis_bef_curs) { - // decrement cursor when 'selection' is not exclusive - if (*p_sel != 'e') { - dec_cursor(); - } - } else { - // Cursor is at start of Visual area. Set the end of the Visual - // area when it was just inside quotes or it didn't end at a - // quote. - if (inside_quotes - || (!selected_quote - && line[VIsual.col] != quotechar - && (line[VIsual.col] == NUL - || line[VIsual.col + 1] != quotechar))) { - dec_cursor(); - VIsual = curwin->w_cursor; - } - curwin->w_cursor.col = col_start; - } - if (VIsual_mode == 'V') { - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - } - } else { - // Set inclusive and other oap's flags. - oap->inclusive = inclusive; - } - - return true; - -abort_search: - if (VIsual_active && *p_sel == 'e') { - if (did_exclusive_adj) { - inc_cursor(); - } - if (restore_vis_bef) { - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - } - } - return false; -} - /// Find next search match under cursor, cursor at end. /// Used while an operator is pending, and in Visual mode. /// @@ -4389,7 +2576,7 @@ int current_search(long count, bool forward) may_start_select('c'); setmouse(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); showmode(); return OK; @@ -4399,7 +2586,7 @@ int current_search(long count, bool forward) /// If move is true, check from the beginning of the buffer, /// else from position "cur". /// "direction" is FORWARD or BACKWARD. -/// Returns TRUE, FALSE or -1 for failure. +/// Returns true, false or -1 for failure. static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) { regmmatch_T regmatch; @@ -4456,9 +2643,7 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct return result; } -/* - * return TRUE if line 'lnum' is empty or has white chars only. - */ +/// return true if line 'lnum' is empty or has white chars only. int linewhite(linenr_T lnum) { char_u *p; @@ -4551,7 +2736,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; @@ -4637,7 +2822,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst } // "searchcount()" function -void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T pos = curwin->w_cursor; char_u *pattern = NULL; @@ -4869,7 +3054,7 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz, if (currIdx > 0) { // Camel case const char_u *p = str; - int neighbor; + int neighbor = ' '; for (uint32_t sidx = 0; sidx < currIdx; sidx++) { neighbor = utf_ptr2char((char *)p); @@ -5287,13 +3472,13 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, } /// "matchfuzzy()" function -void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchfuzzy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_fuzzymatch(argvars, rettv, false); } /// "matchfuzzypos()" function -void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_fuzzymatch(argvars, rettv, true); } @@ -5309,7 +3494,7 @@ static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) } /// Find identifiers or defines in included files. -/// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. +/// If p_ic && compl_status_sol() then ptr must be in lowercase. /// /// @param ptr pointer to search pattern /// @param dir direction of expansion @@ -5351,7 +3536,6 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo int i; char_u *already = NULL; char_u *startp = NULL; - char_u *inc_opt = NULL; win_T *curwin_save = NULL; const int l_g_do_tagpreview = g_do_tagpreview; @@ -5362,9 +3546,9 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo file_line = xmalloc(LSIZE); if (type != CHECK_PATH && type != FIND_DEFINE - // when CONT_SOL is set compare "ptr" with the beginning of the line - // is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo - && !(compl_cont_status & CONT_SOL)) { + // when CONT_SOL is set compare "ptr" with the beginning of the + // line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo + && !compl_status_sol()) { pat = xmalloc(len + 5); assert(len <= INT_MAX); sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr); @@ -5376,22 +3560,22 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo goto fpip_end; } } - inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; + char *inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; if (*inc_opt != NUL) { - incl_regmatch.regprog = vim_regcomp((char *)inc_opt, p_magic ? RE_MAGIC : 0); + incl_regmatch.regprog = vim_regcomp(inc_opt, p_magic ? RE_MAGIC : 0); if (incl_regmatch.regprog == NULL) { goto fpip_end; } - incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat. + incl_regmatch.rm_ic = false; // don't ignore case in incl. pat. } if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) { def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL - ? (char *)p_def : (char *)curbuf->b_p_def, + ? p_def : curbuf->b_p_def, p_magic ? RE_MAGIC : 0); if (def_regmatch.regprog == NULL) { goto fpip_end; } - def_regmatch.rm_ic = FALSE; // don't ignore case in define pat. + def_regmatch.rm_ic = false; // don't ignore case in define pat. } files = xcalloc((size_t)max_path_depth, sizeof(SearchedFile)); old_files = max_path_depth; @@ -5412,7 +3596,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo char_u *p_fname = (curr_fname == (char_u *)curbuf->b_fname) ? (char_u *)curbuf->b_ffname : curr_fname; - if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL) { + if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) { // Use text from '\zs' to '\ze' (or end) of 'include'. new_fname = find_file_name_in_path(incl_regmatch.startp[0], (size_t)(incl_regmatch.endp[0] @@ -5424,7 +3608,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo new_fname = file_name_in_line(incl_regmatch.endp[0], 0, FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL); } - already_searched = FALSE; + already_searched = false; if (new_fname != NULL) { // Check whether we have already searched in this file for (i = 0;; i++) { @@ -5467,7 +3651,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(" "); } @@ -5482,14 +3666,14 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (new_fname != NULL) { // using "new_fname" is more reliable, e.g., when // 'includeexpr' is set. - msg_outtrans_attr(new_fname, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)new_fname, HL_ATTR(HLF_D)); } else { /* * Isolate the file name. * Include the surrounding "" or <> if present. */ if (inc_opt != NULL - && strstr((char *)inc_opt, "\\zs") != NULL) { + && strstr(inc_opt, "\\zs") != NULL) { // pattern contains \zs, use the match p = incl_regmatch.startp[0]; i = (int)(incl_regmatch.endp[0] @@ -5509,16 +3693,16 @@ 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]; p[i] = NUL; - msg_outtrans_attr(p, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); p[i] = save_char; } @@ -5544,7 +3728,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo bigger[i].fp = NULL; bigger[i].name = NULL; bigger[i].lnum = 0; - bigger[i].matched = FALSE; + bigger[i].matched = false; } for (i = old_files; i < max_path_depth; i++) { bigger[i + max_path_depth] = files[i]; @@ -5561,11 +3745,11 @@ 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; - files[depth].matched = FALSE; + files[depth].matched = false; if (action == ACTION_EXPAND) { msg_hist_off = true; // reset in msg_trunc_attr() vim_snprintf((char *)IObuff, IOSIZE, @@ -5604,8 +3788,7 @@ search_line: * define and this line didn't match define_prog above. */ if (def_regmatch.regprog == NULL || define_matched) { - if (define_matched - || (compl_cont_status & CONT_SOL)) { + if (define_matched || compl_status_sol()) { // compare the first "len" chars from "ptr" startp = (char_u *)skipwhite((char *)p); if (p_ic) { @@ -5640,7 +3823,7 @@ search_line: p = (char_u *)skipwhite((char *)line); if (matched || (p[0] == '/' && p[1] == '*') || p[0] == '*') { - for (p = line; *p && p < startp; ++p) { + for (p = line; *p && p < startp; p++) { if (matched && p[0] == '/' && (p[1] == '*' || p[1] == '/')) { @@ -5671,8 +3854,8 @@ search_line: } found = true; aux = p = startp; - if (compl_cont_status & CONT_ADDING) { - p += compl_length; + if (compl_status_adding()) { + p += ins_compl_len(); if (vim_iswordp(p)) { goto exit_matched; } @@ -5681,7 +3864,7 @@ search_line: p = find_word_end(p); i = (int)(p - aux); - if ((compl_cont_status & CONT_ADDING) && i == compl_length) { + if (compl_status_adding() && i == ins_compl_len()) { // IOSIZE > compl_length, so the STRNCPY works STRNCPY(IObuff, aux, i); @@ -5728,7 +3911,7 @@ search_line: IObuff[i] = NUL; aux = IObuff; - if (i == compl_length) { + if (i == ins_compl_len()) { goto exit_matched; } } @@ -5820,14 +4003,14 @@ search_line: } if (action != ACTION_SHOW) { curwin->w_cursor.col = (colnr_T)(startp - line); - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } if (l_g_do_tagpreview != 0 && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } break; @@ -5838,7 +4021,7 @@ exit_matched: // are not at the end of it already if (def_regmatch.regprog == NULL && action == ACTION_EXPAND - && !(compl_cont_status & CONT_SOL) + && !compl_status_sol() && *startp != NUL && *(p = startp + utfc_ptr2len((char *)startp)) != NUL) { goto search_line; @@ -5860,7 +4043,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 +4131,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; } @@ -5963,7 +4146,7 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); msg_puts(" "); } - msg_prt_line(line, FALSE); + msg_prt_line(line, false); ui_flush(); // show one line at a time // Definition continues until line that doesn't end with '\' @@ -5996,7 +4179,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 +4195,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 6e80b550d8..d2f1b39ca4 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" @@ -1476,7 +1477,7 @@ static char *shada_filename(const char *file) // because various expansions must have already be done by the shell. // If shell is not performing them then they should be done in main.c // where arguments are parsed, *not here*. - expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL); + expand_env((char *)file, &(NameBuff[0]), MAXPATHL); file = (const char *)&(NameBuff[0]); } } @@ -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; @@ -3001,7 +3023,7 @@ shada_write_file_nomerge: {} if (tail != fname) { const char tail_save = *tail; *tail = NUL; - if (!os_isdir((char_u *)fname)) { + if (!os_isdir(fname)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) { @@ -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; } @@ -3974,7 +3996,7 @@ static bool shada_removable(const char *name) bool retval = false; char *new_name = home_replace_save(NULL, (char *)name); - for (p = (char *)p_shada; *p;) { + for (p = p_shada; *p;) { (void)copy_option_part(&p, part, ARRAY_SIZE(part), ", "); if (part[0] == 'r') { home_replace(NULL, part + 1, (char *)NameBuff, MAXPATHL, true); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 7b6b55fede..ed7f53d3ba 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" @@ -203,7 +203,7 @@ static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int // When adding first sign need to redraw the windows to create the // column for signs. if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); changed_line_abv_curs(); } @@ -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'. @@ -577,7 +576,7 @@ static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) // When deleting the last sign the cursor position may change, because the // sign columns no longer shows. And the 'signcolumn' may be hidden. if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); changed_line_abv_curs(); } @@ -859,7 +858,7 @@ static void sign_define_init_icon(sign_T *sp, char_u *icon) { xfree(sp->sn_icon); sp->sn_icon = vim_strsave(icon); - backslash_halve(sp->sn_icon); + backslash_halve((char *)sp->sn_icon); } /// Initialize the text for a new sign @@ -935,7 +934,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ // non-empty sign list. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer->b_signlist != NULL) { - redraw_buf_later(wp->w_buffer, NOT_VALID); + redraw_buf_later(wp->w_buffer, UPD_NOT_VALID); } } } @@ -1085,7 +1084,7 @@ static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T at } if (sign_id == 0) { // Delete all the signs in the specified buffer - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); buf_delete_signs(buf, (char *)sign_group); } else { linenr_T lnum; @@ -1354,7 +1353,7 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si if (STRNCMP(arg, "line=", 5) == 0) { arg += 5; *lnum = atoi((char *)arg); - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); lnum_arg = true; } else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { if (*signid != -1) { @@ -1362,11 +1361,11 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si return FAIL; } *signid = -2; - arg = skiptowhite(arg + 1); + arg = (char_u *)skiptowhite((char *)arg + 1); } else if (STRNCMP(arg, "name=", 5) == 0) { arg += 5; name = arg; - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); if (*arg != NUL) { *arg++ = NUL; } @@ -1377,14 +1376,14 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si } else if (STRNCMP(arg, "group=", 6) == 0) { arg += 6; *group = arg; - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); if (*arg != NUL) { *arg++ = NUL; } } else if (STRNCMP(arg, "priority=", 9) == 0) { arg += 9; *prio = atoi((char *)arg); - arg = skiptowhite(arg); + arg = (char_u *)skiptowhite((char *)arg); } else if (STRNCMP(arg, "file=", 5) == 0) { arg += 5; filename = arg; @@ -1428,7 +1427,7 @@ void ex_sign(exarg_T *eap) sign_T *sp; // Parse the subcommand. - p = skiptowhite(arg); + p = (char_u *)skiptowhite((char *)arg); idx = sign_cmd_idx(arg, p); if (idx == SIGNCMD_LAST) { semsg(_("E160: Unknown sign command: %s"), arg); @@ -1450,7 +1449,7 @@ void ex_sign(exarg_T *eap) // Isolate the sign name. If it's a number skip leading zeroes, // so that "099" and "99" are the same sign. But keep "0". - p = skiptowhite(arg); + p = (char_u *)skiptowhite((char *)arg); if (*p != NUL) { *p++ = NUL; } @@ -1790,7 +1789,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) expand_what = EXP_SUBCMD; xp->xp_pattern = (char *)arg; - end_subcmd = skiptowhite(arg); + end_subcmd = (char_u *)skiptowhite((char *)arg); if (*end_subcmd == NUL) { // expand subcmd name // :sign {subcmd}<CTRL-D> @@ -1815,7 +1814,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) do { p = (char_u *)skipwhite((char *)p); last = p; - p = skiptowhite(p); + p = (char_u *)skiptowhite((char *)p); } while (*p != NUL); p = (char_u *)vim_strchr((char *)last, '='); @@ -1965,7 +1964,7 @@ static void sign_define_multiple(list_T *l, list_T *retlist) } /// "sign_define()" function -void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name; @@ -1996,7 +1995,7 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_getdefined()" function -void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name = NULL; @@ -2010,7 +2009,7 @@ void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_getplaced()" function -void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf = NULL; dict_T *dict; @@ -2068,7 +2067,7 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_jump()" function -void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int sign_id; char *sign_group = NULL; @@ -2226,7 +2225,7 @@ cleanup: } /// "sign_place()" function -void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *dict = NULL; @@ -2244,7 +2243,7 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_placelist()" function. Place multiple signs. -void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int sign_id; @@ -2284,7 +2283,7 @@ static void sign_undefine_multiple(list_T *l, list_T *retlist) } /// "sign_undefine()" function -void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name; @@ -2374,7 +2373,7 @@ cleanup: } /// "sign_unplace()" function -void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *dict = NULL; @@ -2397,7 +2396,7 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_unplacelist()" function -void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int retval; 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..1259736e0e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -56,71 +56,59 @@ // 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> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <wctype.h> - -// for offsetof() -#include <stddef.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/change.h" -#include "nvim/charset.h" -#include "nvim/cursor.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" -#include "nvim/mark.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/normal.h" -#include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#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/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) +#include <assert.h> // for assert +#include <inttypes.h> // for uint32_t, uint16_t, uint8_t +#include <limits.h> // for INT_MAX +#include <stdbool.h> // for false, true, bool +#include <stddef.h> // for NULL, size_t, ptrdiff_t +#include <stdio.h> // for snprintf +#include <string.h> // for memmove, strstr, memcpy, memset + +#include "nvim/ascii.h" // for NUL, ascii_isdigit, ascii_iswhite +#include "nvim/autocmd.h" // for apply_autocmds +#include "nvim/buffer.h" // for bufref_valid, set_bufref, buf_is_empty +#include "nvim/buffer_defs.h" // for win_T, synblock_T, buf_T, w_p_... +#include "nvim/change.h" // for changed_bytes +#include "nvim/charset.h" // for skipwhite, getwhitecols, skipbin +#include "nvim/cursor.h" // for get_cursor_line_ptr +#include "nvim/drawscreen.h" // for NOT_VALID, redraw_later +#include "nvim/eval/typval.h" // for semsg +#include "nvim/ex_cmds.h" // for do_sub_msg +#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_docmd.h" // for do_cmdline_cmd +#include "nvim/garray.h" // for garray_T, GA_EMPTY, GA_APPEND_... +#include "nvim/gettext.h" // for _, N_ +#include "nvim/hashtab.h" // for hash_clear_all, hash_init, has... +#include "nvim/highlight_defs.h" // for HLF_COUNT, hlf_T, HLF_SPB, HLF... +#include "nvim/insexpand.h" // for ins_compl_add_infercase, ins_c... +#include "nvim/log.h" // for ELOG +#include "nvim/macros.h" // for MB_PTR_ADV, MB_PTR_BACK, ASCII... +#include "nvim/mark.h" // for clearpos +#include "nvim/mbyte.h" // for utf_ptr2char, utf_char2bytes +#include "nvim/memline.h" // for ml_append, ml_get_buf, ml_close +#include "nvim/memline_defs.h" // for memline_T +#include "nvim/memory.h" // for xfree, xmalloc, xcalloc, xstrdup +#include "nvim/message.h" // for emsg, msg_puts, give_warning +#include "nvim/option.h" // for copy_option_part, set_option_v... +#include "nvim/option_defs.h" // for p_ws, OPT_LOCAL, p_enc, SHM_SE... +#include "nvim/os/fs.h" // for os_remove +#include "nvim/os/input.h" // for line_breakcheck +#include "nvim/os/os_defs.h" // for MAXPATHL +#include "nvim/path.h" // for path_full_compare, path_tail... +#include "nvim/pos.h" // for pos_T, colnr_T, linenr_T +#include "nvim/regexp.h" // for vim_regfree, vim_regexec, vim_... +#include "nvim/regexp_defs.h" // for regmatch_T, regprog_T +#include "nvim/runtime.h" // for DIP_ALL, do_in_runtimepath +#include "nvim/search.h" // for SEARCH_KEEP, for do_search +#include "nvim/spell.h" // for FUNC_ATTR_NONNULL_ALL, FUNC_AT... +#include "nvim/spell_defs.h" // for slang_T, langp_T, MAXWLEN, sal... +#include "nvim/spellfile.h" // for spell_load_file +#include "nvim/spellsuggest.h" // for spell_suggest_list +#include "nvim/strings.h" // for vim_strchr, vim_snprintf, conc... +#include "nvim/syntax.h" // for syn_get_id, syntax_present +#include "nvim/types.h" // for char_u +#include "nvim/undo.h" // for u_save_cursor +#include "nvim/vim.h" // for curwin, STRLEN, STRLCPY, STRNCMP // Result values. Lower number is accepted over higher one. #define SP_BANNED (-1) @@ -136,104 +124,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 +179,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 +193,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. @@ -356,9 +218,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou matchinf_T mi; // Most things are put in "mi" so that it can // be passed to functions quickly. size_t nrlen = 0; // found a number first - int c; size_t wrongcaplen = 0; - int lpi; bool count_word = docount; bool use_camel_case = *wp->w_s->b_p_spo != NUL; bool camel_case = false; @@ -374,7 +234,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 @@ -383,7 +243,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou if (*ptr == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) { mi.mi_end = (char_u *)skipbin((char *)ptr + 2); } else if (*ptr == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) { - mi.mi_end = skiphex(ptr + 2); + mi.mi_end = (char_u *)skiphex((char *)ptr + 2); } else { mi.mi_end = (char_u *)skipdigits((char *)ptr); } @@ -397,7 +257,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou bool this_upper = false; // init for gcc if (use_camel_case) { - c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char((char *)mi.mi_fend); this_upper = SPELL_ISUPPER(c); } @@ -405,7 +265,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou MB_PTR_ADV(mi.mi_fend); if (use_camel_case) { const bool prev_upper = this_upper; - c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char((char *)mi.mi_fend); this_upper = SPELL_ISUPPER(c); camel_case = !prev_upper && this_upper; } @@ -414,7 +274,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) { // Check word starting with capital letter. - c = utf_ptr2char((char *)ptr); + int c = utf_ptr2char((char *)ptr); if (!SPELL_ISUPPER(c)) { wrongcaplen = (size_t)(mi.mi_fend - ptr); } @@ -455,7 +315,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // Loop over the languages specified in 'spelllang'. // We check them all, because a word may be matched longer in another // language. - for (lpi = 0; lpi < wp->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < wp->w_s->b_langp.ga_len; lpi++) { mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, lpi); // If reloading fails the language is still in the list but everything @@ -615,7 +475,6 @@ static void find_word(matchinf_T *mip, int mode) int endlen[MAXWLEN]; // length at possible word endings idx_T endidx[MAXWLEN]; // possible word endings int endidxcnt = 0; - int len; int c; // Repeat advancing in the tree until: @@ -627,7 +486,7 @@ static void find_word(matchinf_T *mip, int mode) flen = fold_more(mip); } - len = byts[arridx++]; + int len = byts[arridx++]; // If the first possible byte is a zero the word could end here. // Remember this index, we first check for the longest word. @@ -639,13 +498,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 +542,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 +555,8 @@ static void find_word(matchinf_T *mip, int mode) if (ptr[wlen] != ' ' && ptr[wlen] != TAB) { break; } - ++wlen; - --flen; + wlen++; + flen--; } } } @@ -708,7 +567,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]; @@ -744,7 +603,7 @@ static void find_word(matchinf_T *mip, int mode) // prefix ID. // Repeat this if there are more flags/region alternatives until there // is a match. - for (len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; len--, arridx++) { + for (int len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; len--, arridx++) { uint32_t flags = (uint32_t)idxs[arridx]; // For the fold-case tree check that the case of the checked word @@ -763,11 +622,10 @@ static void find_word(matchinf_T *mip, int mode) || !spell_valid_case(mip->mi_capflags, (int)flags)) { continue; } - } - // When mode is FIND_PREFIX the word must support the prefix: - // check the prefix ID and the condition. Do that for the list at - // mip->mi_prefarridx that find_prefix() filled. - else if (mode == FIND_PREFIX && !prefix_found) { + } else if (mode == FIND_PREFIX && !prefix_found) { + // When mode is FIND_PREFIX the word must support the prefix: + // check the prefix ID and the condition. Do that for the list at + // mip->mi_prefarridx that find_prefix() filled. c = valid_word_prefix(mip->mi_prefcnt, mip->mi_prefarridx, (int)flags, mip->mi_word + mip->mi_cprefixlen, slang, @@ -899,9 +757,8 @@ static void find_word(matchinf_T *mip, int mode) // COMPOUNDRULE, discard the compounded word. continue; } - } - // Check NEEDCOMPOUND: can't use word without compounding. - else if (flags & WF_NEEDCOMP) { + } else if (flags & WF_NEEDCOMP) { + // skip if word is only valid in a compound continue; } @@ -939,14 +796,14 @@ static void find_word(matchinf_T *mip, int mode) #if 0 c = mip->mi_compoff; #endif - ++mip->mi_complen; + mip->mi_complen++; if (flags & WF_COMPROOT) { - ++mip->mi_compextra; + mip->mi_compextra++; } // For NOBREAK we need to try all NOBREAK languages, at least // to find the ".add" file(s). - for (int lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; lpi++) { if (slang->sl_nobreak) { mip->mi_lp = LANGP_ENTRY(mip->mi_win->w_s->b_langp, lpi); if (mip->mi_lp->lp_slang->sl_fidxs == NULL @@ -980,9 +837,9 @@ static void find_word(matchinf_T *mip, int mode) break; } } - --mip->mi_complen; + mip->mi_complen--; if (flags & WF_COMPROOT) { - --mip->mi_compextra; + mip->mi_compextra--; } mip->mi_lp = save_lp; @@ -1050,18 +907,15 @@ 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; - for (int i = 0; i + 1 < gap->ga_len; i += 2) { - p = ((char_u **)gap->ga_data)[i + 1]; + char_u *p = ((char_u **)gap->ga_data)[i + 1]; if (STRNCMP(ptr + wlen, p, STRLEN(p)) == 0) { // Second part matches at start of following compound word, now // check if first part matches at end of previous word. p = ((char_u **)gap->ga_data)[i]; - len = (int)STRLEN(p); + int len = (int)STRLEN(p); if (len <= wlen && STRNCMP(ptr + wlen - len, p, len) == 0) { return true; } @@ -1072,7 +926,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,48 +955,18 @@ 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; - int c; - // loop over all the COMPOUNDRULE entries - for (p = slang->sl_comprules; *p != NUL; ++p) { + for (char_u *p = slang->sl_comprules; *p != NUL; p++) { // loop over the flags in the compound word we have made, match // them against the current rule entry - for (i = 0;; ++i) { - c = compflags[i]; + for (int i = 0;; i++) { + int c = compflags[i]; if (c == NUL) { // found a rule that matches for the flags we have so far return true; @@ -1154,7 +978,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 +990,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,15 +1012,12 @@ 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; - int prefid = (int)((unsigned)flags >> 24); - for (prefcnt = totprefcnt - 1; prefcnt >= 0; prefcnt--) { - pidx = slang->sl_pidxs[arridx + prefcnt]; + for (int prefcnt = totprefcnt - 1; prefcnt >= 0; prefcnt--) { + int pidx = slang->sl_pidxs[arridx + prefcnt]; // Check the prefix ID. if (prefid != (pidx & 0xff)) { @@ -1236,30 +1057,23 @@ static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word static void find_prefix(matchinf_T *mip, int mode) { idx_T arridx = 0; - int len; int wlen = 0; - int flen; - int c; - char_u *ptr; - idx_T lo, hi, m; slang_T *slang = mip->mi_lp->lp_slang; - char_u *byts; - idx_T *idxs; - byts = slang->sl_pbyts; + char_u *byts = slang->sl_pbyts; if (byts == NULL) { return; // array is empty } // We use the case-folded word here, since prefixes are always // case-folded. - ptr = mip->mi_fword; - flen = mip->mi_fwordlen; // available case-folded bytes + char_u *ptr = mip->mi_fword; + int flen = mip->mi_fwordlen; // available case-folded bytes if (mode == FIND_COMPOUND) { // Skip over the previously found word(s). ptr += mip->mi_compoff; flen -= mip->mi_compoff; } - idxs = slang->sl_pidxs; + idx_T *idxs = slang->sl_pidxs; // Repeat advancing in the tree until: // - there is a byte that doesn't match, @@ -1270,7 +1084,7 @@ static void find_prefix(matchinf_T *mip, int mode) flen = fold_more(mip); } - len = byts[arridx++]; + int len = byts[arridx++]; // If the first possible byte is a zero the prefix could end here. // Check if the following word matches and supports the prefix. @@ -1282,8 +1096,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; @@ -1310,11 +1124,11 @@ static void find_prefix(matchinf_T *mip, int mode) } // Perform a binary search in the list of accepted bytes. - c = ptr[wlen]; - lo = arridx; - hi = arridx + len - 1; + int c = ptr[wlen]; + idx_T lo = arridx; + idx_T hi = arridx + len - 1; while (lo < hi) { - m = (lo + hi) / 2; + idx_T m = (lo + hi) / 2; if (byts[m] > c) { hi = m - 1; } else if (byts[m] < c) { @@ -1332,8 +1146,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--; } } @@ -1342,10 +1156,7 @@ static void find_prefix(matchinf_T *mip, int mode) // Return the length of the folded chars in bytes. static int fold_more(matchinf_T *mip) { - int flen; - char_u *p; - - p = mip->mi_fend; + char_u *p = mip->mi_fend; do { MB_PTR_ADV(mip->mi_fend); } while (*mip->mi_fend != NUL && spell_iswordp(mip->mi_fend, mip->mi_win)); @@ -1358,7 +1169,7 @@ static int fold_more(matchinf_T *mip) (void)spell_casefold(mip->mi_win, p, (int)(mip->mi_fend - p), mip->mi_fword + mip->mi_fwordlen, MAXWLEN - mip->mi_fwordlen); - flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen); + int flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen); mip->mi_fwordlen += flen; return flen; } @@ -1368,7 +1179,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 +1188,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)) { @@ -1400,17 +1211,12 @@ static bool no_spell_checking(win_T *wp) /// @return 0 if not found, length of the badly spelled word otherwise. size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *attrp) { - linenr_T lnum; pos_T found_pos; size_t found_len = 0; - char_u *line; - char_u *p; - char_u *endp; hlf_T attr = HLF_COUNT; size_t len; int has_syntax = syntax_present(wp); int col; - bool can_spell; char_u *buf = NULL; size_t buflen = 0; int skip = 0; @@ -1431,11 +1237,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // We concatenate the start of the next line, so that wrapped words work // (e.g. "et<line-break>cetera"). Doesn't work when searching backwards // though... - lnum = wp->w_cursor.lnum; + linenr_T lnum = wp->w_cursor.lnum; clearpos(&found_pos); while (!got_int) { - line = ml_get_buf(wp->w_buffer, lnum, false); + char_u *line = ml_get_buf(wp->w_buffer, lnum, false); len = STRLEN(line); if (buflen < len + MAXWLEN + 2) { @@ -1452,10 +1258,10 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // For checking first word with a capital skip white space. if (capcol == 0) { - capcol = (int)getwhitecols(line); + capcol = (int)getwhitecols((char *)line); } else if (curline && wp == curwin) { // For spellbadword(): check if first word needs a capital. - col = (int)getwhitecols(line); + col = (int)getwhitecols((char *)line); if (check_need_cap(lnum, col)) { capcol = col; } @@ -1475,8 +1281,8 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att ml_get_buf(wp->w_buffer, lnum + 1, false), MAXWLEN); } - p = buf + skip; - endp = buf + len; + char_u *p = buf + skip; + char_u *endp = buf + len; while (p < endp) { // When searching backward don't search after the cursor. Unless // we wrapped around the end of the buffer. @@ -1502,10 +1308,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att || ((colnr_T)(curline ? p - buf + (ptrdiff_t)len : p - buf) > wp->w_cursor.col)) { + bool can_spell; if (has_syntax) { col = (int)(p - buf); (void)syn_get_id(wp, lnum, (colnr_T)col, - FALSE, &can_spell, FALSE); + false, &can_spell, false); if (!can_spell) { attr = HLF_COUNT; } @@ -1515,9 +1322,11 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att if (can_spell) { found_one = true; - found_pos.lnum = lnum; - found_pos.col = (int)(p - buf); - found_pos.coladd = 0; + found_pos = (pos_T) { + .lnum = lnum, + .col = (int)(p - buf), + .coladd = 0 + }; if (dir == FORWARD) { // No need to search further. wp->w_cursor = found_pos; @@ -1581,7 +1390,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 +1418,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) { @@ -1630,10 +1439,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // to skip those bytes if the word was OK. void spell_cat_line(char_u *buf, char_u *line, int maxlen) { - char_u *p; - int n; - - p = (char_u *)skipwhite((char *)line); + char_u *p = (char_u *)skipwhite((char *)line); while (vim_strchr("*#/\"\t", *p) != NULL) { p = (char_u *)skipwhite((char *)p + 1); } @@ -1641,7 +1447,7 @@ void spell_cat_line(char_u *buf, char_u *line, int maxlen) if (*p != NUL) { // Only worth concatenating if there is something else than spaces to // concatenate. - n = (int)(p - line) + 1; + int n = (int)(p - line) + 1; if (n < maxlen - 1) { memset(buf, ' ', (size_t)n); STRLCPY(buf + n, p, maxlen - n); @@ -1656,7 +1462,6 @@ static void spell_load_lang(char_u *lang) char fname_enc[85]; int r; spelload_T sl; - int round; // Copy the language name to pass it to spell_load_cb() as a cookie. // It's truncated when an error is detected. @@ -1666,7 +1471,7 @@ static void spell_load_lang(char_u *lang) // We may retry when no spell file is found for the language, an // autocommand may load it then. - for (round = 1; round <= 2; ++round) { + for (int round = 1; round <= 2; round++) { // Find the first spell file for "lang" in 'runtimepath' and load it. vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, "spell/%s.%s.spl", lang, spell_enc()); @@ -1713,7 +1518,7 @@ static void spell_load_lang(char_u *lang) char_u *spell_enc(void) { if (STRLEN(p_enc) < 60 && STRCMP(p_enc, "iso-8859-15") != 0) { - return p_enc; + return (char_u *)p_enc; } return (char_u *)"latin1"; } @@ -1797,7 +1602,7 @@ void slang_clear(slang_T *lp) GA_DEEP_CLEAR(gap, salitem_T, free_salitem); } - for (int i = 0; i < lp->sl_prefixcnt; ++i) { + for (int i = 0; i < lp->sl_prefixcnt; i++) { vim_regfree(lp->sl_prefprog[i]); } lp->sl_prefixcnt = 0; @@ -1846,9 +1651,7 @@ void slang_clear_sug(slang_T *lp) static void spell_load_cb(char *fname, void *cookie) { spelload_T *slp = (spelload_T *)cookie; - slang_T *slang; - - slang = spell_load_file((char_u *)fname, slp->sl_lang, NULL, false); + slang_T *slang = spell_load_file((char_u *)fname, slp->sl_lang, NULL, false); if (slang != NULL) { // When a previously loaded file has NOBREAK also use it for the // ".add" files. @@ -1871,9 +1674,6 @@ static void spell_load_cb(char *fname, void *cookie) /// @param[in] count 1 to count once, 10 to init void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) { - hash_T hash; - hashitem_T *hi; - wordcount_T *wc; char_u buf[MAXWLEN]; char_u *p; @@ -1886,9 +1686,10 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) p = buf; } - hash = hash_hash(p); + wordcount_T *wc; + hash_T hash = hash_hash(p); const size_t p_len = STRLEN(p); - hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); + hashitem_T *hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); if (HASHITEM_EMPTY(hi)) { wc = xmalloc(sizeof(wordcount_T) + p_len); memcpy(wc->wc_word, p, p_len + 1); @@ -1903,45 +1704,11 @@ 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) { - char_u *p; - - for (p = str; *p != NUL; ++p) { + for (char_u *p = str; *p != NUL; p++) { if (*p == n) { return true; } @@ -1953,19 +1720,16 @@ bool byte_in_str(char_u *str, int n) // in "slang->sl_syl_items". int init_syl_tab(slang_T *slang) { - char_u *p; - char_u *s; - int l; - ga_init(&slang->sl_syl_items, sizeof(syl_item_T), 4); - p = (char_u *)vim_strchr((char *)slang->sl_syllable, '/'); + char_u *p = (char_u *)vim_strchr((char *)slang->sl_syllable, '/'); while (p != NULL) { *p++ = NUL; if (*p == NUL) { // trailing slash break; } - s = p; + char_u *s = p; p = (char_u *)vim_strchr((char *)p, '/'); + int l; if (p == NULL) { l = (int)STRLEN(s); } else { @@ -1991,8 +1755,6 @@ static int count_syllables(slang_T *slang, const char_u *word) int cnt = 0; bool skip = false; int len; - syl_item_T *syl; - int c; if (slang->sl_syllable == NULL) { return 0; @@ -2008,24 +1770,24 @@ static int count_syllables(slang_T *slang, const char_u *word) // Find longest match of syllable items. len = 0; - for (int i = 0; i < slang->sl_syl_items.ga_len; ++i) { - syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; + for (int i = 0; i < slang->sl_syl_items.ga_len; i++) { + syl_item_T *syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; if (syl->sy_len > len && STRNCMP(p, syl->sy_chars, syl->sy_len) == 0) { len = syl->sy_len; } } if (len != 0) { // found a match, count syllable - ++cnt; + cnt++; skip = false; } else { // No recognized syllable item, at least a syllable char then? - c = utf_ptr2char((char *)p); + int c = utf_ptr2char((char *)p); len = utfc_ptr2len((char *)p); if (vim_strchr((char *)slang->sl_syllable, c) == NULL) { skip = false; // No, search for next syllable } else if (!skip) { - ++cnt; // Yes, count it + cnt++; // Yes, count it skip = true; // don't count following syllable chars } } @@ -2033,12 +1795,12 @@ static int count_syllables(slang_T *slang, const char_u *word) return cnt; } -// Parse 'spelllang' and set w_s->b_langp accordingly. -// Returns NULL if it's OK, an error message otherwise. +/// Parse 'spelllang' and set w_s->b_langp accordingly. +/// @return NULL if it's OK, an untranslated error message otherwise. 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 +1812,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; @@ -2075,18 +1837,18 @@ char *did_set_spelllang(win_T *wp) // Make a copy of 'spelllang', the SpellFileMissing autocommands may change // it under our fingers. - spl_copy = vim_strsave(wp->w_s->b_p_spl); + spl_copy = vim_strsave((char_u *)wp->w_s->b_p_spl); 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); - if (!valid_spelllang(lang)) { + if (!valid_spelllang((char *)lang)) { continue; } @@ -2114,7 +1876,7 @@ char *did_set_spelllang(win_T *wp) // Check if we loaded this language before. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)lang, (char *)slang->sl_fname, false, true) + if (path_full_compare((char *)lang, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -2163,7 +1925,7 @@ char *did_set_spelllang(win_T *wp) // Loop over the languages, there can be several files for "lang". for (slang = first_lang; slang != NULL; slang = slang->sl_next) { if (filename - ? path_full_compare((char *)lang, (char *)slang->sl_fname, false, true) == kEqualFiles + ? path_full_compare((char *)lang, slang->sl_fname, false, true) == kEqualFiles : STRICMP(lang, slang->sl_name) == 0) { region_mask = REGION_ALL; if (!filename && region != NULL) { @@ -2205,7 +1967,7 @@ char *did_set_spelllang(win_T *wp) // round 2: load second name in 'spellfile. // etc. spf = curwin->w_s->b_p_spf; - for (round = 0; round == 0 || *spf != NUL; ++round) { + for (round = 0; round == 0 || *spf != NUL; round++) { if (round == 0) { // Internal wordlist, if there is one. if (int_wordlist == NULL) { @@ -2214,12 +1976,12 @@ 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. - for (c = 0; c < ga.ga_len; ++c) { - p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname; + for (c = 0; c < ga.ga_len; c++) { + p = (char_u *)LANGP_ENTRY(ga, c)->lp_slang->sl_fname; if (p != NULL && path_full_compare((char *)spf_name, (char *)p, false, true) == kEqualFiles) { break; @@ -2232,7 +1994,7 @@ char *did_set_spelllang(win_T *wp) // Check if it was loaded already. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)spf_name, (char *)slang->sl_fname, false, true) + if (path_full_compare((char *)spf_name, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -2290,7 +2052,7 @@ char *did_set_spelllang(win_T *wp) // For each language figure out what language to use for sound folding and // REP items. If the language doesn't support it itself use another one // with the same name. E.g. for "en-math" use "en". - for (int i = 0; i < ga.ga_len; ++i) { + for (int i = 0; i < ga.ga_len; i++) { lp = LANGP_ENTRY(ga, i); // sound folding @@ -2299,7 +2061,7 @@ char *did_set_spelllang(win_T *wp) lp->lp_sallang = lp->lp_slang; } else { // find first similar language that does sound folding - for (int j = 0; j < ga.ga_len; ++j) { + for (int j = 0; j < ga.ga_len; j++) { lp2 = LANGP_ENTRY(ga, j); if (!GA_EMPTY(&lp2->lp_slang->sl_sal) && STRNCMP(lp->lp_slang->sl_name, @@ -2316,7 +2078,7 @@ char *did_set_spelllang(win_T *wp) lp->lp_replang = lp->lp_slang; } else { // find first similar language that has REP items - for (int j = 0; j < ga.ga_len; ++j) { + for (int j = 0; j < ga.ga_len; j++) { lp2 = LANGP_ENTRY(ga, j); if (!GA_EMPTY(&lp2->lp_slang->sl_rep) && STRNCMP(lp->lp_slang->sl_name, @@ -2327,7 +2089,7 @@ char *did_set_spelllang(win_T *wp) } } } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); theend: xfree(spl_copy); @@ -2338,7 +2100,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); } @@ -2358,13 +2120,13 @@ static void use_midword(slang_T *lp, win_T *wp) wp->w_s->b_spell_ismw[c] = true; } else if (wp->w_s->b_spell_ismw_mb == NULL) { // First multi-byte char in "b_spell_ismw_mb". - wp->w_s->b_spell_ismw_mb = vim_strnsave(p, (size_t)l); + wp->w_s->b_spell_ismw_mb = (char *)vim_strnsave(p, (size_t)l); } else { // Append multi-byte chars to "b_spell_ismw_mb". const int n = (int)STRLEN(wp->w_s->b_spell_ismw_mb); - char_u *bp = vim_strnsave(wp->w_s->b_spell_ismw_mb, (size_t)n + (size_t)l); + char_u *bp = vim_strnsave((char_u *)wp->w_s->b_spell_ismw_mb, (size_t)n + (size_t)l); xfree(wp->w_s->b_spell_ismw_mb); - wp->w_s->b_spell_ismw_mb = bp; + wp->w_s->b_spell_ismw_mb = (char *)bp; STRLCPY(bp + n, p, l + 1); } p += l; @@ -2403,9 +2165,6 @@ int captype(char_u *word, char_u *end) FUNC_ATTR_NONNULL_ARG(1) { char_u *p; - int firstcap; - bool allcap; - bool past_second = false; // past second word char // find first letter for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) { @@ -2414,7 +2173,9 @@ int captype(char_u *word, char_u *end) } } int c = mb_ptr2char_adv((const char_u **)&p); - firstcap = allcap = SPELL_ISUPPER(c); + bool allcap; + bool firstcap = allcap = SPELL_ISUPPER(c); + bool past_second = false; // past second word char // Need to check all letters to find a word with mixed upper/lower. // But a word with an upper char only at start is a ONECAP. @@ -2444,57 +2205,11 @@ 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) { - char_u fname[MAXPATHL] = { 0 }; - if (int_wordlist != NULL) { + char_u fname[MAXPATHL] = { 0 }; os_remove((char *)int_wordlist); int_wordlist_spl(fname); os_remove((char *)fname); @@ -2505,15 +2220,13 @@ void spell_delete_wordlist(void) // Free all languages. void spell_free_all(void) { - slang_T *slang; - // Go through all buffers and handle 'spelllang'. <VN> FOR_ALL_BUFFERS(buf) { ga_clear(&buf->b_s.b_langp); } while (first_lang != NULL) { - slang = first_lang; + slang_T *slang = first_lang; first_lang = slang->sl_next; slang_free(slang); } @@ -2547,36 +2260,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. @@ -2600,7 +2283,7 @@ buf_T *open_spellbuf(void) void close_spellbuf(buf_T *buf) { if (buf != NULL) { - ml_close(buf, TRUE); + ml_close(buf, true); xfree(buf); } } @@ -2608,28 +2291,26 @@ void close_spellbuf(buf_T *buf) // Init the chartab used for spelling for ASCII. void clear_spell_chartab(spelltab_T *sp) { - int i; + // Init everything to false (zero). + CLEAR_FIELD(sp->st_isw); + CLEAR_FIELD(sp->st_isu); - // Init everything to false. - memset(sp->st_isw, false, sizeof(sp->st_isw)); - memset(sp->st_isu, false, sizeof(sp->st_isu)); - - for (i = 0; i < 256; i++) { + for (int i = 0; i < 256; i++) { sp->st_fold[i] = (char_u)i; sp->st_upper[i] = (char_u)i; } // We include digits. A word shouldn't start with a digit, but handling // that is done separately. - for (i = '0'; i <= '9'; ++i) { + for (int i = '0'; i <= '9'; i++) { sp->st_isw[i] = true; } - for (i = 'A'; i <= 'Z'; ++i) { + for (int i = 'A'; i <= 'Z'; i++) { sp->st_isw[i] = true; sp->st_isu[i] = true; sp->st_fold[i] = (char_u)(i + 0x20); } - for (i = 'a'; i <= 'z'; ++i) { + for (int i = 'a'; i <= 'z'; i++) { sp->st_isw[i] = true; sp->st_upper[i] = (char_u)(i - 0x20); } @@ -2641,11 +2322,9 @@ void clear_spell_chartab(spelltab_T *sp) // locale. For utf-8 we don't use isalpha() but our own functions. void init_spell_chartab(void) { - int i; - did_set_spelltab = false; clear_spell_chartab(&spelltab); - for (i = 128; i < 256; i++) { + for (int i = 128; i < 256; i++) { int f = utf_fold(i); int u = mb_toupper(i); @@ -2665,11 +2344,9 @@ 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; - const int l = utfc_ptr2len((char *)p); const char_u *s = p; if (l == 1) { @@ -2678,16 +2355,16 @@ static bool spell_iswordp(const char_u *p, const win_T *wp) s = p + 1; // skip a mid-word character } } else { - c = utf_ptr2char((char *)p); + int c = utf_ptr2char((char *)p); if (c < 256 ? wp->w_s->b_spell_ismw[c] : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr((char *)wp->w_s->b_spell_ismw_mb, c) != NULL)) { + && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) { s = p + l; } } - c = utf_ptr2char((char *)s); + int c = utf_ptr2char((char *)s); if (c > 255) { return spell_mb_isword_class(mb_get_class(s), wp); } @@ -2728,7 +2405,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp) if (*p < 256 ? wp->w_s->b_spell_ismw[*p] : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr((char *)wp->w_s->b_spell_ismw_mb, + && vim_strchr(wp->w_s->b_spell_ismw_mb, *p) != NULL)) { s = p + 1; } else { @@ -2783,313 +2460,20 @@ 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; - char_u *line_copy = NULL; - char_u *p; - colnr_T endcol; - regmatch_T regmatch; if (curwin->w_s->b_cap_prog == NULL) { return false; } - line = get_cursor_line_ptr(); - endcol = 0; - if (getwhitecols(line) >= (int)col) { + char_u *line = get_cursor_line_ptr(); + char_u *line_copy = NULL; + colnr_T endcol = 0; + if (getwhitecols((char *)line) >= (int)col) { // At start of line, check if previous line is empty or sentence // ends there. if (lnum == 1) { @@ -3100,7 +2484,7 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) need_cap = true; } else { // Append a space in place of the line break. - line_copy = concat_str(line, (char_u *)" "); + line_copy = (char_u *)concat_str((char *)line, " "); line = line_copy; endcol = (colnr_T)STRLEN(line); } @@ -3111,9 +2495,11 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) if (endcol > 0) { // Check if sentence ends before the bad word. - regmatch.regprog = curwin->w_s->b_cap_prog; - regmatch.rm_ic = FALSE; - p = line + endcol; + regmatch_T regmatch = { + .regprog = curwin->w_s->b_cap_prog, + .rm_ic = false + }; + char_u *p = line + endcol; for (;;) { MB_PTR_BACK(line, p); if (p == line || spell_iswordp_nmw(p, curwin)) { @@ -3137,10 +2523,6 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) void ex_spellrepall(exarg_T *eap) { pos_T pos = curwin->w_cursor; - char_u *frompat; - int addlen; - char_u *line; - char_u *p; bool save_ws = p_ws; linenr_T prev_lnum = 0; @@ -3148,10 +2530,11 @@ void ex_spellrepall(exarg_T *eap) emsg(_("E752: No previous spell replacement")); return; } - addlen = (int)(STRLEN(repl_to) - STRLEN(repl_from)); + int addlen = (int)(STRLEN(repl_to) - STRLEN(repl_from)); - frompat = xmalloc(STRLEN(repl_from) + 7); - sprintf((char *)frompat, "\\V\\<%s\\>", repl_from); + size_t frompatlen = STRLEN(repl_from) + 7; + char_u *frompat = xmalloc(frompatlen); + snprintf((char *)frompat, frompatlen, "\\V\\<%s\\>", repl_from); p_ws = false; sub_nsubs = 0; @@ -3165,10 +2548,10 @@ void ex_spellrepall(exarg_T *eap) // Only replace when the right word isn't there yet. This happens // when changing "etc" to "etc.". - line = get_cursor_line_ptr(); + char_u *line = get_cursor_line_ptr(); if (addlen <= 0 || STRNCMP(line + curwin->w_cursor.col, repl_to, STRLEN(repl_to)) != 0) { - p = xmalloc(STRLEN(line) + (size_t)addlen + 1); + char_u *p = xmalloc(STRLEN(line) + (size_t)addlen + 1); memmove(p, line, (size_t)curwin->w_cursor.col); STRCPY(p + curwin->w_cursor.col, repl_to); STRCAT(p, line + curwin->w_cursor.col + STRLEN(repl_from)); @@ -3176,10 +2559,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 +2578,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 +2600,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 +2624,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 +2640,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 +2655,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. @@ -5974,13 +2699,12 @@ char *eval_soundfold(const char *const word) /// @param[in,out] res destination for soundfolded word void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) { - char_u fword[MAXWLEN]; - char_u *word; - if (slang->sl_sofo) { // SOFOFROM and SOFOTO used spell_soundfold_sofo(slang, inword, res); } else { + char_u fword[MAXWLEN]; + char_u *word; // SAL items used. Requires the word to be case-folded. if (folded) { word = inword; @@ -6047,29 +2771,26 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; int word[MAXWLEN] = { 0 }; int wres[MAXWLEN] = { 0 }; - int l; int *ws; int *pf; - int i, j, z; + int j, z; int reslen; - int n, k = 0; + int k = 0; int z0; int k0; int n0; - int c; int pri; int p0 = -333; int c0; bool did_white = false; - int wordlen; // Convert the multi-byte string to a wide-character string. // Remove accents, if wanted. We actually remove all non-word characters. // But keep white space. - wordlen = 0; + int wordlen = 0; for (const char_u *s = inword; *s != NUL;) { const char_u *t = s; - c = mb_cptr2char_adv(&s); + int c = mb_cptr2char_adv(&s); if (slang->sl_rem_accents) { if (utf_class(c) == 0) { if (did_white) { @@ -6088,13 +2809,14 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } word[wordlen] = NUL; + int c; // This algorithm comes from Aspell phonet.cpp. // Converted from C++ to C. Added support for multi-byte chars. // Changed to keep spaces. - i = reslen = z = 0; + int i = reslen = z = 0; while ((c = word[i]) != NUL) { // Start with the first rule that has the character in the word. - n = slang->sl_sal_first[c & 0xff]; + int n = slang->sl_sal_first[c & 0xff]; z0 = 0; if (n >= 0) { @@ -6102,7 +2824,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // If c is 0x300 need extra check for the end of the array, as // (c & 0xff) is NUL. for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff) - && ws[0] != NUL; ++n) { + && ws[0] != NUL; n++) { // Quickly skip entries that don't match the word. Most // entries are less than three chars, optimize for that. if (c != ws[0]) { @@ -6114,7 +2836,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) continue; } if (k > 2) { - for (j = 2; j < k; ++j) { + for (j = 2; j < k; j++) { if (word[i + j] != ws[j]) { break; } @@ -6128,12 +2850,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 @@ -6175,7 +2897,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Test follow-up rule for "word[i + k]"; loop over // all entries with the same index byte. for (; ((ws = smp[n0].sm_lead_w)[0] & 0xff) - == (c0 & 0xff); ++n0) { + == (c0 & 0xff); n0++) { // Quickly skip entries that don't match the word. if (c0 != ws[0]) { continue; @@ -6187,7 +2909,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } if (k0 > 2) { pf = word + i + k + 1; - for (j = 2; j < k0; ++j) { + for (j = 2; j < k0; j++) { if (*pf++ != ws[j]) { break; } @@ -6203,12 +2925,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; @@ -6329,8 +3051,8 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } // Convert wide characters in "wres" to a multi-byte string in "res". - l = 0; - for (n = 0; n < reslen; n++) { + int l = 0; + for (int n = 0; n < reslen; n++) { l += utf_char2bytes(wres[n], (char *)res + l); if (l + MB_MAXBYTES > MAXWLEN) { break; @@ -6339,499 +3061,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) { @@ -6863,20 +3092,19 @@ void ex_spellinfo(exarg_T *eap) // ":spelldump" void ex_spelldump(exarg_T *eap) { - char *spl; - long dummy; - if (no_spell_checking(curwin)) { return; } + char *spl; + long dummy; (void)get_option_value("spl", &dummy, &spl, OPT_LOCAL); // Create a new empty buffer in a new window. do_cmdline_cmd("new"); // enable spelling locally in the new window - set_option_value("spell", true, "", OPT_LOCAL); - set_option_value("spl", dummy, spl, OPT_LOCAL); + set_option_value_give_err("spell", true, "", OPT_LOCAL); + set_option_value_give_err("spl", dummy, spl, OPT_LOCAL); xfree(spl); if (!buf_is_empty(curbuf)) { @@ -6889,7 +3117,7 @@ void ex_spelldump(exarg_T *eap) if (curbuf->b_ml.ml_line_count > 1) { ml_delete(curbuf->b_ml.ml_line_count, false); } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Go through all possible words and: @@ -6912,7 +3140,6 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) char_u *byts; idx_T *idxs; linenr_T lnum = 0; - int round; int depth; int n; int flags; @@ -6940,7 +3167,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) // Find out if we can support regions: All languages must support the same // regions or none at all. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); p = lp->lp_slang->sl_regions; if (p[0] != 0) { @@ -6963,7 +3190,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) } // Loop over all files loaded for the entries in 'spelllang'. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + 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 (slang->sl_fbyts == NULL) { // reloading failed @@ -6985,7 +3212,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) // round 1: case-folded tree // round 2: keep-case tree - for (round = 1; round <= 2; ++round) { + for (int round = 1; round <= 2; round++) { if (round == 1) { dumpflags &= ~DUMPFLAG_KEEPCASE; byts = slang->sl_fbyts; @@ -7005,13 +3232,13 @@ 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 { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0 || depth >= MAXWLEN - 1) { // End of word or reached maximum length, deal with the @@ -7038,7 +3265,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 +3290,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--; } } } @@ -7079,10 +3306,8 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, { bool keepcap = false; char_u *p; - char_u *tw; char_u cword[MAXWLEN]; char_u badword[MAXWLEN + 10]; - int i; int flags = wordflags; if (dumpflags & DUMPFLAG_ONECAP) { @@ -7104,7 +3329,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, keepcap = true; } } - tw = p; + char_u *tw = p; if (pat == NULL) { // Add flags and regions after a slash. @@ -7120,7 +3345,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, STRCAT(badword, "?"); } if (flags & WF_REGION) { - for (i = 0; i < 7; i++) { + for (int i = 0; i < 7; i++) { if (flags & (0x10000 << i)) { const size_t badword_len = STRLEN(badword); snprintf((char *)badword + badword_len, @@ -7171,46 +3396,40 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi char_u prefix[MAXWLEN]; char_u word_up[MAXWLEN]; bool has_word_up = false; - int c; - char_u *byts; - idx_T *idxs; linenr_T lnum = startlnum; - int depth; - int n; - int len; - int i; // If the word starts with a lower-case letter make the word with an // upper-case letter in word_up[]. - c = utf_ptr2char((char *)word); + int c = utf_ptr2char((char *)word); if (SPELL_TOUPPER(c) != c) { onecap_copy(word, word_up, true); has_word_up = true; } - byts = slang->sl_pbyts; - idxs = slang->sl_pidxs; + char_u *byts = slang->sl_pbyts; + idx_T *idxs = slang->sl_pidxs; if (byts != NULL) { // array not is empty // Loop over all prefixes, building them byte-by-byte in prefix[]. // When at the end of a prefix check that it supports "flags". - depth = 0; + int depth = 0; arridx[0] = 0; curi[0] = 1; while (depth >= 0 && !got_int) { - n = arridx[depth]; - len = byts[n]; + int n = arridx[depth]; + int 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. n += curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // End of prefix, find out how many IDs there are. - for (i = 1; i < len; ++i) { + int i; + for (i = 1; i < len; i++) { if (byts[n + i] != 0) { break; } @@ -7221,10 +3440,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi if (c != 0) { STRLCPY(prefix + depth, word, MAXWLEN - depth); dump_word(slang, prefix, pat, dir, dumpflags, - (c & WF_RAREPFX) ? (flags | WF_RARE) - : flags, lnum); + (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } @@ -7237,10 +3455,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi if (c != 0) { STRLCPY(prefix + depth, word_up, MAXWLEN - depth); dump_word(slang, prefix, pat, dir, dumpflags, - (c & WF_RAREPFX) ? (flags | WF_RARE) - : flags, lnum); + (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { - ++lnum; + lnum++; } } } @@ -7276,16 +3493,14 @@ char_u *spell_to_word_end(char_u *start, win_T *win) // Returns the column number of the word. int spell_word_start(int startcol) { - char_u *line; - char_u *p; - int col = 0; - if (no_spell_checking(curwin)) { return startcol; } + char_u *line = get_cursor_line_ptr(); + char_u *p; + // Find a word character before "startcol". - line = get_cursor_line_ptr(); for (p = line + startcol; p > line;) { MB_PTR_BACK(line, p); if (spell_iswordp_nmw(p, curwin)) { @@ -7293,6 +3508,8 @@ int spell_word_start(int startcol) } } + int col = 0; + // Go back to start of the word. while (p > line) { col = (int)(p - line); @@ -7327,3 +3544,70 @@ 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 *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 *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (const char_u *s = (char_u *)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; + + 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 + char *re = concat_str("^", synblock->b_p_spc); + synblock->b_cap_prog = vim_regcomp(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..27b9777dc2 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 @@ -113,9 +115,9 @@ typedef struct slang_S slang_T; struct slang_S { slang_T *sl_next; // next language - char_u *sl_name; // language name "en", "en.rare", "nl", etc. - char_u *sl_fname; // name of .spl file - bool sl_add; // true if it's a .add file. + char_u *sl_name; // language name "en", "en.rare", "nl", etc. + char *sl_fname; // name of .spl file + bool sl_add; // true if it's a .add file. char_u *sl_fbyts; // case-folded word bytes long sl_fbyts_len; // length of sl_fbyts @@ -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..0f8034312e 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" @@ -438,7 +440,7 @@ typedef struct spellinfo_S { sblock_T *si_blocks; // memory blocks used long si_blocks_cnt; // memory blocks allocated - int si_did_emsg; // TRUE when ran out of memory + int si_did_emsg; // true when ran out of memory long si_compress_cnt; // words to add before lowering // compression limit @@ -452,7 +454,7 @@ typedef struct spellinfo_S { int si_ascii; // handling only ASCII words int si_add; // addition file - int si_clear_chartab; // when TRUE clear char tables + int si_clear_chartab; // when true clear char tables int si_region; // region mask vimconv_T si_conv; // for conversion to 'encoding' int si_memtot; // runtime memory used @@ -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) { @@ -603,7 +604,7 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile lp = slang_alloc(lang); // Remember the file name, used to reload the file when it's updated. - lp->sl_fname = vim_strsave(fname); + lp->sl_fname = (char *)vim_strsave(fname); // Check for .add.spl. lp->sl_add = strstr(path_tail((char *)fname), SPL_FNAME_ADD) != 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,27 +840,27 @@ 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. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // End of word, count it. - ++wordcount[depth]; + wordcount[depth]++; // 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; @@ -867,12 +869,12 @@ static void tree_count_words(char_u *byts, idx_T *idxs) } } -// Load the .sug files for languages that have one and weren't loaded yet. +/// Load the .sug files for languages that have one and weren't loaded yet. void suggest_load_files(void) { langp_T *lp; slang_T *slang; - char_u *dotp; + char *dotp; FILE *fd; char_u buf[MAXWLEN]; int i; @@ -883,7 +885,7 @@ void suggest_load_files(void) int c; // Do this for all languages that support sound folding. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + 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 (slang->sl_sugtime != 0 && !slang->sl_sugloaded) { @@ -892,12 +894,12 @@ void suggest_load_files(void) // don't try again and again. slang->sl_sugloaded = true; - dotp = STRRCHR(slang->sl_fname, '.'); + dotp = strrchr(slang->sl_fname, '.'); if (dotp == NULL || FNAMECMP(dotp, ".spl") != 0) { continue; } STRCPY(dotp, ".sug"); - fd = os_fopen((char *)slang->sl_fname, "r"); + fd = os_fopen(slang->sl_fname, "r"); if (fd == NULL) { goto nextone; } @@ -958,7 +960,7 @@ someerror: // Read all the wordnr lists into the buffer, one NUL terminated // list per line. ga_init(&ga, 1, 100); - for (wordnr = 0; wordnr < wcount; ++wordnr) { + for (wordnr = 0; wordnr < wcount; wordnr++) { ga.ga_len = 0; for (;;) { c = getc(fd); // <sugline> @@ -1140,10 +1142,10 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first) } // Fill the first-index table. - for (int i = 0; i < 256; ++i) { + for (int i = 0; i < 256; i++) { first[i] = -1; } - for (int i = 0; i < gap->ga_len; ++i) { + for (int i = 0; i < gap->ga_len; i++) { ftp = &((fromto_T *)gap->ga_data)[i]; if (first[*ftp->ft_from] == -1) { first[*ftp->ft_from] = (int16_t)i; @@ -1198,7 +1200,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) // Read up to the first special char into sm_lead. int i = 0; - for (; i < ccnt; ++i) { + for (; i < ccnt; i++) { c = getc(fd); // <salfrom> if (vim_strchr("0123456789(-<^$", c) != NULL) { break; @@ -1211,7 +1213,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) // Put (abc) chars in sm_oneof, if any. if (c == '(') { smp->sm_oneof = p; - for (++i; i < ccnt; ++i) { + for (++i; i < ccnt; i++) { c = getc(fd); // <salfrom> if (c == ')') { break; @@ -1297,7 +1299,7 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) while (done < len) { // Read one word at a time. - for (i = 0;; ++i) { + for (i = 0;; i++) { c = getc(fd); if (c == EOF) { return SP_TRUNCERROR; @@ -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; @@ -1596,7 +1597,7 @@ static void set_sal_first(slang_T *lp) garray_T *gap = &lp->sl_sal; sfirst = lp->sl_sal_first; - for (int i = 0; i < 256; ++i) { + for (int i = 0; i < 256; i++) { sfirst[i] = -1; } smp = (salitem_T *)gap->ga_data; @@ -1731,7 +1732,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx byts[idx++] = (char_u)len; // Read the byte values, flag/region bytes and shared indexes. - for (i = 1; i <= len; ++i) { + for (i = 1; i <= len; i++) { c = getc(fd); // <byte> if (c < 0) { return SP_TRUNCERROR; @@ -1794,7 +1795,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx // Recursively read the children for non-shared siblings. // Skip the end-of-word ones (zero byte value) and the shared ones (and // remove SHARED_MASK) - for (i = 1; i <= len; ++i) { + for (i = 1; i <= len; i++) { if (byts[startidx + i] != 0) { if (idxs[startidx + i] & SHARED_MASK) { idxs[startidx + i] &= ~SHARED_MASK; @@ -1821,13 +1822,13 @@ static void spell_reload_one(char_u *fname, bool added_word) bool didit = false; for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)fname, (char *)slang->sl_fname, false, true) == kEqualFiles) { + if (path_full_compare((char *)fname, slang->sl_fname, false, true) == kEqualFiles) { slang_clear(slang); if (spell_load_file(fname, NULL, slang, false) == NULL) { // reloading failed, clear the language slang_clear(slang); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); didit = true; } } @@ -1862,7 +1863,7 @@ static long compress_added = 500000; // word count // Sets "sps_flags". int spell_check_msm(void) { - char *p = (char *)p_msm; + char *p = p_msm; long start = 0; long incr = 0; long added = 0; @@ -1923,7 +1924,7 @@ static void spell_clear_flags(wordnode_T *node) wordnode_T *np; for (np = node; np != NULL; np = np->wn_sibling) { - np->wn_u1.index = FALSE; + np->wn_u1.index = false; spell_clear_flags(np->wn_child); } } @@ -1939,7 +1940,7 @@ static void spell_print_node(wordnode_T *node, int depth) msg((char_u *)line2); msg((char_u *)line3); } else { - node->wn_u1.index = TRUE; + node->wn_u1.index = true; if (node->wn_byte != NUL) { if (node->wn_child != NULL) { @@ -2041,7 +2042,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } vim_snprintf((char *)IObuff, IOSIZE, _("Reading affix file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // Only do REP lines when not done in another .aff file already. do_rep = GA_EMPTY(&spin->si_rep); @@ -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 == '#') { @@ -2074,7 +2075,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Convert from "SET" to 'encoding' when needed. xfree(pc); if (spin->si_conv.vc_type != CONV_NONE) { - pc = string_convert(&spin->si_conv, rline, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %d: %s"), fname, lnum, 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) { @@ -2120,10 +2121,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (itemcnt > 0) { if (is_aff_rule(items, itemcnt, "SET", 2) && aff->af_enc == NULL) { // Setup for conversion from "ENC" to 'encoding'. - aff->af_enc = enc_canonize(items[1]); + aff->af_enc = (char_u *)enc_canonize((char *)items[1]); if (!spin->si_ascii - && convert_setup(&spin->si_conv, aff->af_enc, - p_enc) == FAIL) { + && convert_setup(&spin->si_conv, (char *)aff->af_enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, aff->af_enc, p_enc); } @@ -2167,9 +2167,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) STRCAT(p, " "); STRCAT(p, items[1]); spin->si_info = p; - } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) - && midword == NULL) { - midword = getroom_save(spin, items[1]); + } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) && midword == NULL) { + midword = (char_u *)getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "TRY", 2)) { // ignored, we look in the tree for what chars may appear } @@ -2300,22 +2299,19 @@ 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++] = getroom_save(spin, items[1]); + ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, items[2]); } } else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2) && syllable == NULL) { - syllable = getroom_save(spin, items[1]); + syllable = (char_u *)getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "NOBREAK", 1)) { spin->si_nobreak = true; } else if (is_aff_rule(items, itemcnt, "NOSPLITSUGS", 1)) { @@ -2387,7 +2383,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; @@ -2448,10 +2444,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) aff_entry = getroom(spin, sizeof(*aff_entry), true); if (STRCMP(items[2], "0") != 0) { - aff_entry->ae_chop = getroom_save(spin, items[2]); + aff_entry->ae_chop = (char_u *)getroom_save(spin, items[2]); } if (STRCMP(items[3], "0") != 0) { - aff_entry->ae_add = getroom_save(spin, items[3]); + aff_entry->ae_add = (char_u *)getroom_save(spin, items[3]); // Recognize flags on the affix: abcd/XYZ aff_entry->ae_flags = (char_u *)vim_strchr((char *)aff_entry->ae_add, '/'); @@ -2471,7 +2467,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (STRCMP(items[4], ".") != 0) { char_u buf[MAXLINELEN]; - aff_entry->ae_cond = getroom_save(spin, items[4]); + aff_entry->ae_cond = (char_u *)getroom_save(spin, items[4]); if (*items[0] == 'P') { sprintf((char *)buf, "^%s", items[4]); } else { @@ -2520,7 +2516,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (aff_entry->ae_cond != NULL) { char_u buf[MAXLINELEN]; onecap_copy(items[4], buf, true); - aff_entry->ae_cond = getroom_save(spin, buf); + aff_entry->ae_cond = (char_u *)getroom_save(spin, buf); if (aff_entry->ae_cond != NULL) { sprintf((char *)buf, "^%s", aff_entry->ae_cond); @@ -2550,7 +2546,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) idx = spin->si_prefcond.ga_len; pp = GA_APPEND_VIA_PTR(char_u *, &spin->si_prefcond); *pp = (aff_entry->ae_cond == NULL) ? - NULL : getroom_save(spin, aff_entry->ae_cond); + NULL : (char_u *)getroom_save(spin, aff_entry->ae_cond); } // Add the prefix to the prefix tree. @@ -2582,7 +2578,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Didn't actually use ah_newID, backup si_newprefID. if (aff_todo == 0 && !did_postpone_prefix) { - --spin->si_newprefID; + spin->si_newprefID--; cur_aff->ah_newID = 0; } } @@ -2676,10 +2672,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } } else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2) && sofofrom == NULL) { - sofofrom = getroom_save(spin, items[1]); + sofofrom = (char_u *)getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "SOFOTO", 2) && sofoto == NULL) { - sofoto = getroom_save(spin, items[1]); + sofoto = (char_u *)getroom_save(spin, items[1]); } else if (STRCMP(items[0], "COMMON") == 0) { int i; @@ -2809,7 +2805,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 +2941,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 +2964,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 +2973,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 +2986,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) { @@ -3045,9 +3041,9 @@ static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *t fromto_T *ftp = GA_APPEND_VIA_PTR(fromto_T, gap); (void)spell_casefold(curwin, from, (int)STRLEN(from), word, MAXWLEN); - ftp->ft_from = getroom_save(spin, word); + ftp->ft_from = (char_u *)getroom_save(spin, word); (void)spell_casefold(curwin, to, (int)STRLEN(to), word, MAXWLEN); - ftp->ft_to = getroom_save(spin, word); + ftp->ft_to = (char_u *)getroom_save(spin, word); } // Converts a boolean argument in a SAL line to true or false; @@ -3070,9 +3066,9 @@ static void spell_free_aff(afffile_T *aff) // All this trouble to free the "ae_prog" items... for (ht = &aff->af_pref;; ht = &aff->af_suff) { todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + 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); @@ -3127,7 +3123,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) vim_snprintf((char *)IObuff, IOSIZE, _("Reading dictionary file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // start with a message for the first line spin->si_msg_count = 999999; @@ -3142,7 +3138,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 +3146,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 @@ -3159,7 +3155,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // Convert from "SET" to 'encoding' when needed. if (spin->si_conv.vc_type != CONV_NONE) { - pc = string_convert(&spin->si_conv, line, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)line, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %d: %s"), fname, lnum, line); @@ -3186,7 +3182,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; } @@ -3210,7 +3206,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Store the word in the hashtable to be able to find duplicates. - dw = getroom_save(spin, w); + dw = (char_u *)getroom_save(spin, w); if (dw == NULL) { retval = FAIL; xfree(pc); @@ -3227,7 +3223,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 +3358,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 +3388,7 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl } } if (affile->af_flagtype == AFT_NUM && *p == ',') { - ++p; + p++; } } @@ -3436,9 +3432,9 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff int use_condit; todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0 && retval == OK; ++hi) { + 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 @@ -3542,8 +3538,8 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Combine the prefix IDs. Avoid adding the // same ID twice. - for (i = 0; i < pfxlen; ++i) { - for (j = 0; j < use_pfxlen; ++j) { + for (i = 0; i < pfxlen; i++) { + for (j = 0; j < use_pfxlen; j++) { if (pfxlist[i] == use_pfxlist[j]) { break; } @@ -3564,9 +3560,8 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Combine the list of compound flags. // Concatenate them to the prefix IDs list. // Avoid adding the same ID twice. - for (i = pfxlen; pfxlist[i] != NUL; ++i) { - for (j = use_pfxlen; - use_pfxlist[j] != NUL; ++j) { + for (i = pfxlen; pfxlist[i] != NUL; i++) { + for (j = use_pfxlen; use_pfxlist[j] != NUL; j++) { if (pfxlist[i] == use_pfxlist[j]) { break; } @@ -3681,12 +3676,12 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) } vim_snprintf((char *)IObuff, IOSIZE, _("Reading word file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // 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 +3691,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 @@ -3706,7 +3701,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) // Convert from "/encoding={encoding}" to 'encoding' when needed. xfree(pc); if (spin->si_conv.vc_type != CONV_NONE) { - pc = string_convert(&spin->si_conv, rline, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %ld: %s"), fname, lnum, rline); @@ -3719,7 +3714,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"), @@ -3728,14 +3723,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) smsg(_("/encoding= line after word ignored in %s line %ld: %s"), fname, lnum, line - 1); } else { - char_u *enc; + char *enc; // Setup for conversion to 'encoding'. line += 9; - enc = enc_canonize(line); + enc = enc_canonize((char *)line); if (!spin->si_ascii - && convert_setup(&spin->si_conv, enc, - p_enc) == FAIL) { + && convert_setup(&spin->si_conv, enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, line, p_enc); } @@ -3802,13 +3796,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; } @@ -3826,7 +3820,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) if (spin->si_ascii && non_ascii > 0) { vim_snprintf((char *)IObuff, IOSIZE, _("Ignored %d words with non-ASCII characters"), non_ascii); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); } return retval; @@ -3860,7 +3854,7 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) bl->sb_next = spin->si_blocks; spin->si_blocks = bl; bl->sb_used = 0; - ++spin->si_blocks_cnt; + spin->si_blocks_cnt++; } p = bl->sb_data + bl->sb_used; @@ -3869,9 +3863,10 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) return p; } -// Make a copy of a string into memory allocated with getroom(). -// Returns NULL when out of memory. -static char_u *getroom_save(spellinfo_T *spin, char_u *s) +/// Make a copy of a string into memory allocated with getroom(). +/// +/// @return NULL when out of memory. +static char *getroom_save(spellinfo_T *spin, char_u *s) { const size_t s_size = STRLEN(s) + 1; return memcpy(getroom(spin, s_size, false), s, s_size); @@ -3899,13 +3894,13 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin) /// Return true if "word" contains valid word characters. /// Control characters and trailing '/' are invalid. Space is OK. -static bool valid_spell_word(const char_u *word, const char_u *end) +static bool valid_spell_word(const char *word, const char *end) { - if (!utf_valid_string(word, end)) { + if (!utf_valid_string((char_u *)word, (char_u *)end)) { return false; } - for (const char_u *p = word; *p != NUL && p < end; p += utfc_ptr2len((const char *)p)) { - if (*p < ' ' || (p[0] == '/' && p[1] == NUL)) { + for (const char *p = word; *p != NUL && p < end; p += utfc_ptr2len(p)) { + if ((uint8_t)(*p) < ' ' || (p[0] == '/' && p[1] == NUL)) { return false; } } @@ -3933,7 +3928,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co int res = OK; // Avoid adding illegal bytes to the word tree. - if (!valid_spell_word(word, word + len)) { + if (!valid_spell_word((char *)word, (char *)word + len)) { return FAIL; } @@ -3947,7 +3942,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co break; } } - ++spin->si_foldwcount; + spin->si_foldwcount++; if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) { for (const char_u *p = pfxlist; res == OK; p++) { @@ -3959,7 +3954,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co break; } } - ++spin->si_keepwcount; + spin->si_keepwcount++; } return res; } @@ -3978,12 +3973,12 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int int i; // Add each byte of the word to the tree, including the NUL at the end. - for (i = 0;; ++i) { + for (i = 0;; i++) { // When there is more than one reference to this node we need to make // a copy, so that we can modify it. Copy the whole list of siblings // (we don't optimize for a partly shared list of siblings). if (node != NULL && node->wn_refs > 1) { - --node->wn_refs; + node->wn_refs--; copyprev = prev; for (copyp = node; copyp != NULL; copyp = copyp->wn_sibling) { // Allocate a new node and copy the info. @@ -3993,7 +3988,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int } np->wn_child = copyp->wn_child; if (np->wn_child != NULL) { - ++np->wn_child->wn_refs; // child gets extra ref + np->wn_child->wn_refs++; // child gets extra ref } np->wn_byte = copyp->wn_byte; if (np->wn_byte == NUL) { @@ -4080,7 +4075,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int #endif // count nr of words added since last message - ++spin->si_msg_count; + spin->si_msg_count++; if (spin->si_compress_cnt > 1) { if (--spin->si_compress_cnt == 1) { @@ -4146,8 +4141,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,9 +4168,9 @@ 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 + cnt++; // length field } return cnt; } @@ -4187,7 +4182,7 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) { n->wn_child = spin->si_first_free; spin->si_first_free = n; - ++spin->si_free_count; + spin->si_free_count++; } // Compress a tree: find tails that are identical and can be shared. @@ -4218,7 +4213,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n vim_snprintf((char *)IObuff, IOSIZE, _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), name, tot, tot - n, perc); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); } #ifdef SPELL_PRINTTREE spell_print_tree(root->wn_sibling); @@ -4248,7 +4243,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); @@ -4266,7 +4261,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo // Found one! Now use that child in place of the // current one. This means the current child and all // its siblings is unlinked from the tree. - ++tp->wn_refs; + tp->wn_refs++; compressed += deref_wordnode(spin, child); np->wn_child = tp; break; @@ -4352,14 +4347,15 @@ static int rep_compare(const void *s1, const void *s2) return STRCMP(p1->ft_from, p2->ft_from); } -// Write the Vim .spl file "fname". -// Return OK/FAIL. -static int write_vim_spell(spellinfo_T *spin, char_u *fname) +/// Write the Vim .spl file "fname". +/// +/// @return OK/FAIL. +static int write_vim_spell(spellinfo_T *spin, char *fname) { int retval = OK; int regionmask; - FILE *fd = os_fopen((char *)fname, "w"); + FILE *fd = os_fopen(fname, "w"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; @@ -4424,7 +4420,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) put_bytes(fd, 1 + 128 + 2 + l, 4); // <sectionlen> fputc(128, fd); // <charflagslen> - for (size_t i = 128; i < 256; ++i) { + for (size_t i = 128; i < 256; i++) { flags = 0; if (spelltab.st_isw[i]) { flags |= CF_WORD; @@ -4468,7 +4464,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // round 1: SN_REP section // round 2: SN_SAL section (unless SN_SOFO is used) // round 3: SN_REPSAL section - for (unsigned int round = 1; round <= 3; ++round) { + for (unsigned int round = 1; round <= 3; round++) { garray_T *gap; if (round == 1) { gap = &spin->si_rep; @@ -4502,13 +4498,13 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // Compute the length of what follows. size_t l = 2; // count <repcount> or <salcount> assert(gap->ga_len >= 0); - for (size_t i = 0; i < (size_t)gap->ga_len; ++i) { + for (size_t i = 0; i < (size_t)gap->ga_len; i++) { fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; l += 1 + STRLEN(ftp->ft_from); // count <*fromlen> and <*from> l += 1 + STRLEN(ftp->ft_to); // count <*tolen> and <*to> } if (round == 2) { - ++l; // count <salflags> + l++; // count <salflags> } put_bytes(fd, l, 4); // <sectionlen> @@ -4527,11 +4523,11 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) } put_bytes(fd, (uintmax_t)gap->ga_len, 2); // <repcount> or <salcount> - for (size_t i = 0; i < (size_t)gap->ga_len; ++i) { + for (size_t i = 0; i < (size_t)gap->ga_len; i++) { // <rep> : <repfromlen> <repfrom> <reptolen> <repto> // <sal> : <salfromlen> <salfrom> <saltolen> <salto> fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; - for (unsigned int rr = 1; rr <= 2; ++rr) { + for (unsigned int rr = 1; rr <= 2; rr++) { char_u *p = rr == 1 ? ftp->ft_from : ftp->ft_to; l = STRLEN(p); assert(l < INT_MAX); @@ -4568,20 +4564,20 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // round 1: count the bytes // round 2: write the bytes - for (unsigned int round = 1; round <= 2; ++round) { + for (unsigned int round = 1; round <= 2; round++) { size_t todo; size_t len = 0; hashitem_T *hi; todo = spin->si_commonwords.ht_used; - for (hi = spin->si_commonwords.ht_array; todo > 0; ++hi) { + for (hi = spin->si_commonwords.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { size_t l = STRLEN(hi->hi_key) + 1; len += l; if (round == 2) { // <word> fwv &= fwrite(hi->hi_key, l, 1, fd); } - --todo; + todo--; } } if (round == 1) { @@ -4644,8 +4640,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 +4651,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> @@ -4691,7 +4687,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // <LWORDTREE> <KWORDTREE> <PREFIXTREE> spin->si_memtot = 0; - for (unsigned int round = 1; round <= 3; ++round) { + for (unsigned int round = 1; round <= 3; round++) { wordnode_T *tree; if (round == 1) { tree = spin->si_foldroot->wn_sibling; @@ -4782,7 +4778,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. @@ -4880,12 +4876,12 @@ void ex_mkspell(exarg_T *eap) { int fcount; char **fnames; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; bool ascii = false; if (STRNCMP(arg, "-ascii", 6) == 0) { ascii = true; - arg = (char_u *)skipwhite((char *)arg + 6); + arg = skipwhite(arg + 6); } // Expand all the remaining arguments (e.g., $VIMRUNTIME). @@ -4898,7 +4894,7 @@ void ex_mkspell(exarg_T *eap) // Create the .sug file. // Uses the soundfold info in "spin". // Writes the file with the name "wfname", with ".spl" changed to ".sug". -static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) +static void spell_make_sugfile(spellinfo_T *spin, char *wfname) { char_u *fname = NULL; int len; @@ -4911,14 +4907,14 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // of the code for the soundfolding stuff. // It might have been done already by spell_reload_one(). for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)wfname, (char *)slang->sl_fname, false, true) + if (path_full_compare(wfname, slang->sl_fname, false, true) == kEqualFiles) { break; } } if (slang == NULL) { - spell_message(spin, (char_u *)_("Reading back spell file...")); - slang = spell_load_file(wfname, NULL, NULL, false); + spell_message(spin, _("Reading back spell file...")); + slang = spell_load_file((char_u *)wfname, NULL, NULL, false); if (slang == NULL) { return; } @@ -4935,7 +4931,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // Go through the trie of good words, soundfold each word and add it to // the soundfold trie. - spell_message(spin, (char_u *)_("Performing soundfolding...")); + spell_message(spin, _("Performing soundfolding...")); if (sug_filltree(spin, slang) == FAIL) { goto theend; } @@ -4952,7 +4948,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) (int64_t)spin->si_spellbuf->b_ml.ml_line_count); // Compress the soundfold trie. - spell_message(spin, (char_u *)_(msg_compressing)); + spell_message(spin, _(msg_compressing)); wordtree_compress(spin, spin->si_foldroot, "case-folded"); // Write the .sug file. @@ -5012,12 +5008,12 @@ 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. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { @@ -5033,8 +5029,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. @@ -5198,7 +5194,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) vim_snprintf((char *)IObuff, IOSIZE, _("Writing suggestion file %s..."), fname); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); // <SUGHEADER>: <fileID> <versionnr> <timestamp> if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, (size_t)1, fd) != 1) { // <fileID> @@ -5235,7 +5231,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) assert(wcount >= 0); put_bytes(fd, (uintmax_t)wcount, 4); // <sugwcount> - for (linenr_T lnum = 1; lnum <= wcount; ++lnum) { + for (linenr_T lnum = 1; lnum <= wcount; lnum++) { // <sugline>: <sugnr> ... NUL char_u *line = ml_get_buf(spin->si_spellbuf, lnum, false); size_t len = STRLEN(line) + 1; @@ -5254,7 +5250,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) vim_snprintf((char *)IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin->si_memtot); - spell_message(spin, IObuff); + spell_message(spin, (char *)IObuff); theend: // close the file @@ -5273,7 +5269,6 @@ theend: static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool added_word) { char_u *fname = NULL; - char_u *wfname; char **innames; int incount; afffile_T *(afile[MAXREGIONS]); @@ -5282,7 +5277,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; @@ -5301,7 +5296,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool innames = &fnames[fcount == 1 ? 0 : 1]; incount = fcount - 1; - wfname = xmalloc(MAXPATHL); + char *wfname = xmalloc(MAXPATHL); if (fcount >= 1) { len = (int)STRLEN(fnames[0]); @@ -5309,42 +5304,42 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // For ":mkspell path/en.latin1.add" output file is // "path/en.latin1.add.spl". incount = 1; - vim_snprintf((char *)wfname, MAXPATHL, "%s.spl", fnames[0]); + vim_snprintf(wfname, MAXPATHL, "%s.spl", fnames[0]); } else if (fcount == 1) { // For ":mkspell path/vim" output file is "path/vim.latin1.spl". incount = 1; - vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, + vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); } else if (len > 4 && STRCMP(fnames[0] + len - 4, ".spl") == 0) { // Name ends in ".spl", use as the file name. STRLCPY(wfname, fnames[0], MAXPATHL); } else { // Name should be language, make the file name from it. - vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, + vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); } // Check for .ascii.spl. - if (strstr(path_tail((char *)wfname), SPL_FNAME_ASCII) != NULL) { + if (strstr(path_tail(wfname), SPL_FNAME_ASCII) != NULL) { spin.si_ascii = true; } // Check for .add.spl. - if (strstr(path_tail((char *)wfname), SPL_FNAME_ADD) != NULL) { + if (strstr(path_tail(wfname), SPL_FNAME_ADD) != NULL) { spin.si_add = true; } } if (incount <= 0) { emsg(_(e_invarg)); // need at least output and input names - } else if (vim_strchr(path_tail((char *)wfname), '_') != NULL) { + } else if (vim_strchr(path_tail(wfname), '_') != NULL) { emsg(_("E751: Output file name must not have region name")); } else if (incount > MAXREGIONS) { semsg(_("E754: Only up to %d regions supported"), MAXREGIONS); } else { // Check for overwriting before doing things that may take a lot of // time. - if (!over_write && os_path_exists(wfname)) { + if (!over_write && os_path_exists((char_u *)wfname)) { emsg(_(e_exists)); goto theend; } @@ -5357,7 +5352,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Init the aff and dic pointers. // Get the region names if there are more than 2 arguments. - for (i = 0; i < incount; ++i) { + for (i = 0; i < incount; i++) { afile[i] = NULL; if (incount > 1) { @@ -5389,7 +5384,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Read all the .aff and .dic files. // Text is converted to 'encoding'. // Words are stored in the case-folded and keep-case trees. - for (i = 0; i < incount && !error; ++i) { + for (i = 0; i < incount && !error; i++) { spin.si_conv.vc_type = CONV_NONE; spin.si_region = 1 << i; @@ -5426,7 +5421,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool if (!error && !got_int) { // Combine tails in the tree. - spell_message(&spin, (char_u *)_(msg_compressing)); + spell_message(&spin, _(msg_compressing)); wordtree_compress(&spin, spin.si_foldroot, "case-folded"); wordtree_compress(&spin, spin.si_keeproot, "keep-case"); wordtree_compress(&spin, spin.si_prefroot, "prefixes"); @@ -5436,18 +5431,18 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Write the info in the spell file. vim_snprintf((char *)IObuff, IOSIZE, _("Writing spell file %s..."), wfname); - spell_message(&spin, IObuff); + spell_message(&spin, (char *)IObuff); error = write_vim_spell(&spin, wfname) == FAIL; - spell_message(&spin, (char_u *)_("Done!")); + spell_message(&spin, _("Done!")); vim_snprintf((char *)IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin.si_memtot); - spell_message(&spin, IObuff); + spell_message(&spin, (char *)IObuff); // If the file is loaded need to reload it. if (!error) { - spell_reload_one(wfname, added_word); + spell_reload_one((char_u *)wfname, added_word); } } @@ -5461,7 +5456,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool hash_clear_all(&spin.si_commonwords, 0); // Free the .aff file structures. - for (i = 0; i < incount; ++i) { + for (i = 0; i < incount; i++) { if (afile[i] != NULL) { spell_free_aff(afile[i]); } @@ -5484,14 +5479,14 @@ theend: // Display a message for spell file processing when 'verbose' is set or using // ":mkspell". "str" can be IObuff. -static void spell_message(const spellinfo_T *spin, char_u *str) +static void spell_message(const spellinfo_T *spin, char *str) FUNC_ATTR_NONNULL_ALL { if (spin->si_verbose || p_verbose > 2) { if (!spin->si_verbose) { verbose_enter(); } - msg((char *)str); + msg(str); ui_flush(); if (!spin->si_verbose) { verbose_leave(); @@ -5529,7 +5524,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo int i; char_u *spf; - if (!valid_spell_word(word, word + len)) { + if (!valid_spell_word((char *)word, (char *)word + len)) { emsg(_(e_illegal_character_in_word)); return; } @@ -5555,7 +5550,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } fnamebuf = xmalloc(MAXPATHL); - for (spf = curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { + for (spf = (char_u *)curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { copy_option_part((char **)&spf, (char *)fnamebuf, MAXPATHL, ","); if (i == idx) { break; @@ -5669,7 +5664,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo buf_reload(buf, buf->b_orig_mode, false); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } xfree(fnamebuf); } @@ -5683,14 +5678,14 @@ static void init_spellfile(void) char_u *rtp; char_u *lend; bool aspath = false; - char_u *lstart = curbuf->b_s.b_p_spl; + char_u *lstart = (char_u *)curbuf->b_s.b_p_spl; if (*curwin->w_s->b_p_spl != NUL && !GA_EMPTY(&curwin->w_s->b_langp)) { buf = xmalloc(MAXPATHL); // Find the end of the language name. Exclude the region. If there // is a path separator remember the start of the tail. - for (lend = curwin->w_s->b_p_spl; *lend != NUL + for (lend = (char_u *)curwin->w_s->b_p_spl; *lend != NUL && vim_strchr(",._", *lend) == NULL; lend++) { if (vim_ispathsep(*lend)) { aspath = true; @@ -5700,13 +5695,12 @@ static void init_spellfile(void) // Loop over all entries in 'runtimepath'. Use the first one where we // are allowed to write. - rtp = p_rtp; + rtp = (char_u *)p_rtp; while (*rtp != NUL) { if (aspath) { // Use directory of an entry with path, e.g., for // "/dir/lg.utf-8.spl" use "/dir". - STRLCPY(buf, curbuf->b_s.b_p_spl, - lstart - curbuf->b_s.b_p_spl); + STRLCPY(buf, curbuf->b_s.b_p_spl, lstart - (char_u *)curbuf->b_s.b_p_spl); } else { // Copy the path from 'runtimepath' to buf[]. copy_option_part((char **)&rtp, (char *)buf, MAXPATHL, ","); @@ -5715,8 +5709,7 @@ static void init_spellfile(void) // Use the first language name from 'spelllang' and the // encoding used in the first loaded .spl file. if (aspath) { - STRLCPY(buf, curbuf->b_s.b_p_spl, - lend - curbuf->b_s.b_p_spl + 1); + STRLCPY(buf, curbuf->b_s.b_p_spl, lend - (char_u *)curbuf->b_s.b_p_spl + 1); } else { // Create the "spell" directory if it doesn't exist yet. l = (int)STRLEN(buf); @@ -5730,14 +5723,14 @@ static void init_spellfile(void) "/%.*s", (int)(lend - lstart), lstart); } l = (int)STRLEN(buf); - fname = LANGP_ENTRY(curwin->w_s->b_langp, 0) + fname = (char_u *)LANGP_ENTRY(curwin->w_s->b_langp, 0) ->lp_slang->sl_fname; vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, ".%s.add", ((fname != NULL && strstr(path_tail((char *)fname), ".ascii.") != NULL) ? "ascii" : (const char *)spell_enc())); - set_option_value("spellfile", 0L, (const char *)buf, OPT_LOCAL); + set_option_value_give_err("spellfile", 0L, (const char *)buf, OPT_LOCAL); break; } aspath = false; @@ -5761,7 +5754,7 @@ static void set_spell_charflags(char_u *flags, int cnt, char_u *fol) clear_spell_chartab(&new_st); - for (i = 0; i < 128; ++i) { + for (i = 0; i < 128; i++) { if (i < cnt) { new_st.st_isw[i + 128] = (flags[i] & CF_WORD) != 0; new_st.st_isu[i + 128] = (flags[i] & CF_UPPER) != 0; @@ -5785,7 +5778,7 @@ static int set_spell_finish(spelltab_T *new_st) if (did_set_spelltab) { // check that it's the same table - for (i = 0; i < 256; ++i) { + for (i = 0; i < 256; i++) { if (spelltab.st_isw[i] != new_st->st_isw[i] || spelltab.st_isu[i] != new_st->st_isu[i] || spelltab.st_fold[i] != new_st->st_fold[i] @@ -5815,7 +5808,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) { @@ -5848,7 +5841,7 @@ static void set_map_str(slang_T *lp, char_u *map) lp->sl_has_map = true; // Init the array and hash tables empty. - for (i = 0; i < 256; ++i) { + for (i = 0; i < 256; i++) { lp->sl_map_array[i] = 0; } hash_init(&lp->sl_map_hash); diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c new file mode 100644 index 0000000000..23c8c621b5 --- /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 = 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((char_u *)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 = (char_u *)skiptowhite((char *)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((char *)p) != NUL) { + p = (char_u *)skipwhite(skiptowhite((char *)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((char *)p) != NUL) { + p = (char_u *)skipwhite(skiptowhite((char *)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((char *)stp->st_word) == NUL) { + for (p = fword; *(p = (char_u *)skiptowhite((char *)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/statusline.c b/src/nvim/statusline.c new file mode 100644 index 0000000000..9b441ba0a9 --- /dev/null +++ b/src/nvim/statusline.c @@ -0,0 +1,1807 @@ +// 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 +// + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/assert.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/vars.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/statusline.h" +#include "nvim/ui.h" +#include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +// Determines how deeply nested %{} blocks will be evaluated in statusline. +#define MAX_STL_EVAL_DEPTH 100 + +/// Enumeration specifying the valid numeric bases that can +/// be used when printing numbers in the status line. +typedef enum { + kNumBaseDecimal = 10, + kNumBaseHexadecimal = 16, +} NumberBase; + +/// Redraw the status line of window `wp`. +/// +/// If inversion is possible we use it. Else '=' characters are used. +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 = (char_u *)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, (char *)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; +} + +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; +} + +void win_redr_ruler(win_T *wp, bool always) +{ + bool is_stl_global = global_stl_height() > 0; + static bool did_show_ext_ruler = false; + + // If 'ruler' off, don't do anything + if (!p_ru) { + return; + } + + // 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; + } + + // Don't draw the ruler while doing insert-completion, it might overwrite + // the (long) mode message. + if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { + if (edit_submode != NULL) { + return; + } + } + + if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) { + const int called_emsg_before = called_emsg; + win_redr_custom(wp, false, true); + if (called_emsg > called_emsg_before) { + set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); + } + return; + } + + // Check if not in Insert mode and the line is empty (will show "0-1"). + int empty_line = false; + if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) { + empty_line = true; + } + + // Only draw the ruler when something changed. + validate_virtcol_win(wp); + if (redraw_cmdline + || always + || wp->w_cursor.lnum != wp->w_ru_cursor.lnum + || wp->w_cursor.col != wp->w_ru_cursor.col + || wp->w_virtcol != wp->w_ru_virtcol + || wp->w_cursor.coladd != wp->w_ru_cursor.coladd + || wp->w_topline != wp->w_ru_topline + || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count + || wp->w_topfill != wp->w_ru_topfill + || empty_line != wp->w_ru_empty) { + int width; + int row; + int fillchar; + int attr; + int off; + bool part_of_status = false; + + if (wp->w_status_height) { + row = W_ENDROW(wp); + fillchar = fillchar_status(&attr, wp); + off = wp->w_wincol; + width = wp->w_width; + part_of_status = true; + } else if (is_stl_global) { + row = Rows - (int)p_ch - 1; + fillchar = fillchar_status(&attr, wp); + off = 0; + width = Columns; + part_of_status = true; + } else { + row = Rows - 1; + fillchar = ' '; + attr = HL_ATTR(HLF_MSG); + width = Columns; + off = 0; + } + + if (!part_of_status && !ui_has_messages()) { + return; + } + + // In list mode virtcol needs to be recomputed + colnr_T virtcol = wp->w_virtcol; + if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { + wp->w_p_list = false; + getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); + wp->w_p_list = true; + } + +#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. + 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); + 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). + 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); + if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen + o++; + } + int this_ru_col = ru_col - (Columns - width); + if (this_ru_col < 0) { + this_ru_col = 0; + } + // Never use more than half the window/screen width, leave the other half + // for the filename. + if (this_ru_col < (width + 1) / 2) { + this_ru_col = (width + 1) / 2; + } + if (this_ru_col + o < width) { + // Need at least 3 chars left for get_rel_pos() + NUL. + while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { + i += utf_char2bytes(fillchar, buffer + i); + o++; + } + get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); + } + + if (ui_has(kUIMessages) && !part_of_status) { + MAXSIZE_TEMP_ARRAY(content, 1); + MAXSIZE_TEMP_ARRAY(chunk, 2); + ADD_C(chunk, INTEGER_OBJ(attr)); + ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer))); + ADD_C(content, ARRAY_OBJ(chunk)); + ui_call_msg_ruler(content); + did_show_ext_ruler = true; + } else { + if (did_show_ext_ruler) { + ui_call_msg_ruler((Array)ARRAY_DICT_INIT); + did_show_ext_ruler = false; + } + // Truncate at window boundary. + o = 0; + for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { + o += utf_ptr2cells(buffer + i); + if (this_ru_col + o > width) { + buffer[i] = NUL; + break; + } + } + + ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; + grid_puts(grid, buffer, row, this_ru_col + off, attr); + grid_fill(grid, row, row + 1, + this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, + fillchar, attr); + } + + wp->w_ru_cursor = wp->w_cursor; + wp->w_ru_virtcol = wp->w_virtcol; + wp->w_ru_empty = (char)empty_line; + wp->w_ru_topline = wp->w_topline; + wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; + wp->w_ru_topfill = wp->w_topfill; + } +} + +/// Get the character to use in a status line. Get its attributes in "*attr". +int fillchar_status(int *attr, win_T *wp) +{ + int fill; + bool is_curwin = (wp == curwin); + if (is_curwin) { + *attr = win_hl_attr(wp, HLF_S); + fill = wp->w_p_fcs_chars.stl; + } else { + *attr = win_hl_attr(wp, HLF_SNC); + fill = wp->w_p_fcs_chars.stlnc; + } + // Use fill when there is highlighting, and highlighting of current + // window differs, or the fillchars differ, or this is not the + // current window + if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) + || !is_curwin || ONE_WINDOW) + || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { + return fill; + } + if (is_curwin) { + return '^'; + } + return '='; +} + +/// Redraw the status line according to 'statusline' and take care of any +/// errors encountered. +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; +} + +/// Redraw the status line, window bar or ruler of window "wp". +/// When "wp" is NULL redraw the tab pages line from 'tabline'. +void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +{ + static bool entered = false; + int attr; + int curattr; + int row; + int col = 0; + int maxwidth; + int width; + int n; + int len; + int fillchar; + char buf[MAXPATHL]; + char_u *stl; + char *p; + stl_hlrec_t *hltab; + StlClickRecord *tabtab; + int use_sandbox = false; + win_T *ewp; + int p_crb_save; + bool is_stl_global = global_stl_height() > 0; + + 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. + if (entered) { + return; + } + entered = true; + + // setup environment for the task at hand + if (wp == NULL) { + // Use 'tabline'. Always at the first line of the screen. + stl = (char_u *)p_tal; + row = 0; + fillchar = ' '; + attr = HL_ATTR(HLF_TPF); + maxwidth = Columns; + use_sandbox = was_set_insecurely(wp, "tabline", 0); + } else if (draw_winbar) { + stl = (char_u *)((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); + row = -1; // row zero is first row of text + col = 0; + grid = &wp->w_grid; + grid_adjust(&grid, &row, &col); + + if (row < 0) { + return; + } + + fillchar = wp->w_p_fcs_chars.wbr; + attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); + maxwidth = wp->w_width_inner; + use_sandbox = was_set_insecurely(wp, "winbar", 0); + + stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); + // Allocate / resize the click definitions array for winbar if needed. + if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) { + xfree(wp->w_winbar_click_defs); + wp->w_winbar_click_defs_size = (size_t)maxwidth; + wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord)); + } + } else { + row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); + fillchar = fillchar_status(&attr, wp); + maxwidth = is_stl_global ? Columns : wp->w_width; + + stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); + // Allocate / resize the click definitions array for statusline if needed. + if (wp->w_status_click_defs_size < (size_t)maxwidth) { + xfree(wp->w_status_click_defs); + wp->w_status_click_defs_size = (size_t)maxwidth; + wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord)); + } + + if (draw_ruler) { + stl = (char_u *)p_ruf; + // advance past any leading group spec - implicit in ru_col + if (*stl == '%') { + if (*++stl == '-') { + stl++; + } + if (atoi((char *)stl)) { + while (ascii_isdigit(*stl)) { + stl++; + } + } + if (*stl++ != '(') { + stl = (char_u *)p_ruf; + } + } + col = ru_col - (Columns - maxwidth); + if (col < (maxwidth + 1) / 2) { + col = (maxwidth + 1) / 2; + } + maxwidth = maxwidth - col; + if (!wp->w_status_height && !is_stl_global) { + grid = &msg_grid_adj; + row = Rows - 1; + maxwidth--; // writing in last column may cause scrolling + fillchar = ' '; + attr = HL_ATTR(HLF_MSG); + } + + use_sandbox = was_set_insecurely(wp, "rulerformat", 0); + } else { + if (*wp->w_p_stl != NUL) { + stl = (char_u *)wp->w_p_stl; + } else { + stl = (char_u *)p_stl; + } + use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); + } + + col += is_stl_global ? 0 : wp->w_wincol; + } + + if (maxwidth <= 0) { + goto theend; + } + + // 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; + + // 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, + fillchar, maxwidth, &hltab, &tabtab); + xfree(stl); + ewp->w_p_crb = p_crb_save; + + // Make all characters printable. + p = transstr(buf, true); + len = (int)STRLCPY(buf, p, sizeof(buf)); + len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; + xfree(p); + + // fill up with "fillchar" + while (width < maxwidth && len < (int)sizeof(buf) - 1) { + len += utf_char2bytes(fillchar, buf + len); + width++; + } + buf[len] = NUL; + + // Draw each snippet with the specified highlighting. + grid_puts_line_start(grid, row); + + curattr = attr; + p = buf; + for (n = 0; hltab[n].start != NULL; n++) { + int textlen = (int)(hltab[n].start - p); + grid_puts_len(grid, p, textlen, row, col, curattr); + col += vim_strnsize(p, textlen); + p = hltab[n].start; + + if (hltab[n].userhl == 0) { + curattr = attr; + } else if (hltab[n].userhl < 0) { + curattr = syn_id2attr(-hltab[n].userhl); + } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { + curattr = highlight_stlnc[hltab[n].userhl - 1]; + } else { + curattr = highlight_user[hltab[n].userhl - 1]; + } + } + // Make sure to use an empty string instead of p, if p is beyond buf + len. + grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); + + grid_puts_line_flush(false); + + // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking + // in the tab page line, status line or window bar + StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs + : draw_winbar ? wp->w_winbar_click_defs + : wp->w_status_click_defs; + + if (click_defs == NULL) { + goto theend; + } + + col = 0; + len = 0; + p = buf; + StlClickDefinition cur_click_def = { + .type = kStlClickDisabled, + }; + for (n = 0; tabtab[n].start != NULL; n++) { + len += vim_strnsize(p, (int)(tabtab[n].start - p)); + while (col < len) { + click_defs[col++] = cur_click_def; + } + p = (char *)tabtab[n].start; + cur_click_def = tabtab[n].def; + if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled + || cur_click_def.type == kStlClickFuncRun)) { + // window bar and status line only support click functions + cur_click_def.type = kStlClickDisabled; + } + } + while (col < maxwidth) { + click_defs[col++] = cur_click_def; + } + +theend: + entered = false; +} + +/// Build a string from the status line items in "fmt". +/// Return length of string in screen cells. +/// +/// Normally works for window "wp", except when working for 'tabline' then it +/// is "curwin". +/// +/// Items are drawn interspersed with the text that surrounds it +/// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation +/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional +/// +/// If maxwidth is not zero, the string will be filled at any middle marker +/// or truncated if too long, fillchar is used for all whitespace. +/// +/// @param wp The window to build a statusline for +/// @param out The output buffer to write the statusline to +/// Note: This should not be NameBuff +/// @param outlen The length of the output buffer +/// @param fmt The statusline format string +/// @param use_sandbox Use a sandboxed environment when evaluating fmt +/// @param fillchar Character to use when filling empty space in the statusline +/// @param maxwidth The maximum width to make the statusline +/// @param hltab HL attributes (can be NULL) +/// @param tabtab Tab clicks definition (can be NULL). +/// +/// @return The final width of the statusline +int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar, + int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) +{ + static size_t stl_items_len = 20; // Initial value, grows as needed. + static stl_item_t *stl_items = NULL; + static int *stl_groupitems = NULL; + static stl_hlrec_t *stl_hltab = NULL; + static StlClickRecord *stl_tabtab = NULL; + static int *stl_separator_locations = NULL; + +#define TMPLEN 70 + char buf_tmp[TMPLEN]; + char win_tmp[TMPLEN]; + char *usefmt = fmt; + const int save_must_redraw = must_redraw; + const int save_redr_type = curwin->w_redr_type; + + if (stl_items == NULL) { + stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); + stl_groupitems = xmalloc(sizeof(int) * stl_items_len); + + // Allocate one more, because the last element is used to indicate the + // end of the list. + stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); + stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); + + stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); + } + + // When the format starts with "%!" then evaluate it as an expression and + // use the result as the actual format string. + if (fmt[0] == '%' && fmt[1] == '!') { + typval_T tv = { + .v_type = VAR_NUMBER, + .vval.v_number = wp->handle, + }; + set_var(S_LEN("g:statusline_winid"), &tv, false); + + usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); + if (usefmt == NULL) { + usefmt = fmt; + } + + do_unlet(S_LEN("g:statusline_winid"), true); + } + + if (fillchar == 0) { + fillchar = ' '; + } + + // The cursor in windows other than the current one isn't always + // up-to-date, esp. because of autocommands and timers. + linenr_T lnum = wp->w_cursor.lnum; + if (lnum > wp->w_buffer->b_ml.ml_line_count) { + lnum = wp->w_buffer->b_ml.ml_line_count; + wp->w_cursor.lnum = lnum; + } + + // Get line & check if empty (cursorpos will show "0-1"). + const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false); + bool empty_line = (*line_ptr == NUL); + + // Get the byte value now, in case we need it below. This is more + // efficient than making a copy of the line. + int byteval; + const size_t len = STRLEN(line_ptr); + if (wp->w_cursor.col > (colnr_T)len) { + // Line may have changed since checking the cursor column, or the lnum + // was adjusted above. + wp->w_cursor.col = (colnr_T)len; + wp->w_cursor.coladd = 0; + byteval = 0; + } else { + byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); + } + + int groupdepth = 0; + int evaldepth = 0; + + int curitem = 0; + bool prevchar_isflag = true; + bool prevchar_isitem = false; + + // out_p is the current position in the output buffer + char *out_p = out; + + // out_end_p is the last valid character in the output buffer + // Note: The null termination character must occur here or earlier, + // so any user-visible characters must occur before here. + char *out_end_p = (out + outlen) - 1; + + // Proceed character by character through the statusline format string + // fmt_p is the current position in the input buffer + for (char *fmt_p = usefmt; *fmt_p != NUL;) { + if (curitem == (int)stl_items_len) { + size_t new_len = stl_items_len * 3 / 2; + + stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); + stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); + stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); + stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); + stl_separator_locations = + xrealloc(stl_separator_locations, sizeof(int) * new_len); + + stl_items_len = new_len; + } + + if (*fmt_p != '%') { + prevchar_isflag = prevchar_isitem = false; + } + + // Copy the formatting verbatim until we reach the end of the string + // or find a formatting item (denoted by `%`) + // or run out of room in our output buffer. + while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) { + *out_p++ = *fmt_p++; + } + + // If we have processed the entire format string or run out of + // room in our output buffer, exit the loop. + if (*fmt_p == NUL || out_p >= out_end_p) { + break; + } + + // The rest of this loop will handle a single `%` item. + // Note: We increment here to skip over the `%` character we are currently + // on so we can process the item's contents. + fmt_p++; + + // Ignore `%` at the end of the format string + if (*fmt_p == NUL) { + break; + } + + // Two `%` in a row is the escape sequence to print a + // single `%` in the output buffer. + if (*fmt_p == '%') { + *out_p++ = *fmt_p++; + prevchar_isflag = prevchar_isitem = false; + continue; + } + + // STL_SEPARATE: Separation place between left and right aligned items. + if (*fmt_p == STL_SEPARATE) { + fmt_p++; + // Ignored when we are inside of a grouping + if (groupdepth > 0) { + continue; + } + stl_items[curitem].type = Separate; + stl_items[curitem++].start = out_p; + continue; + } + + // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. + if (*fmt_p == STL_TRUNCMARK) { + fmt_p++; + stl_items[curitem].type = Trunc; + stl_items[curitem++].start = out_p; + continue; + } + + // The end of a grouping + if (*fmt_p == ')') { + fmt_p++; + // Ignore if we are not actually inside a group currently + if (groupdepth < 1) { + continue; + } + groupdepth--; + + // Determine how long the group is. + // Note: We set the current output position to null + // so `vim_strsize` will work. + char *t = stl_items[stl_groupitems[groupdepth]].start; + *out_p = NUL; + long group_len = vim_strsize(t); + + // If the group contained internal items + // and the group did not have a minimum width, + // and if there were no normal items in the group, + // move the output pointer back to where the group started. + // Note: This erases any non-item characters that were in the group. + // Otherwise there would be no reason to do this step. + if (curitem > stl_groupitems[groupdepth] + 1 + && stl_items[stl_groupitems[groupdepth]].minwid == 0) { + // remove group if all items are empty and highlight group + // doesn't change + int group_start_userhl = 0; + int group_end_userhl = 0; + int n; + for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { + if (stl_items[n].type == Highlight) { + group_start_userhl = group_end_userhl = stl_items[n].minwid; + break; + } + } + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + if (stl_items[n].type == Normal) { + break; + } + if (stl_items[n].type == Highlight) { + group_end_userhl = stl_items[n].minwid; + } + } + if (n == curitem && group_start_userhl == group_end_userhl) { + // empty group + out_p = t; + group_len = 0; + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + // do not use the highlighting from the removed group + if (stl_items[n].type == Highlight) { + stl_items[n].type = Empty; + } + // adjust the start position of TabPage to the next + // item position + if (stl_items[n].type == TabPage) { + stl_items[n].start = out_p; + } + } + } + } + + // If the group is longer than it is allowed to be + // truncate by removing bytes from the start of the group text. + if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { + // { Determine the number of bytes to remove + + // Find the first character that should be included. + long n = 0; + while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { + group_len -= ptr2cells(t + n); + n += utfc_ptr2len(t + n); + } + // } + + // Prepend the `<` to indicate that the output was truncated. + *t = '<'; + + // { Move the truncated output + memmove(t + 1, t + n, (size_t)(out_p - (t + n))); + out_p = out_p - n + 1; + // Fill up space left over by half a double-wide char. + while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { + MB_CHAR2BYTES(fillchar, out_p); + } + // } + + // correct the start of the items for the truncation + for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { + // Shift everything back by the number of removed bytes + // Minus one for the leading '<' added above. + stl_items[idx].start -= n - 1; + + // If the item was partially or completely truncated, set its + // start to the start of the group + if (stl_items[idx].start < t) { + stl_items[idx].start = t; + } + } + // If the group is shorter than the minimum width, add padding characters. + } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { + long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; + // If the group is left-aligned, add characters to the right. + if (min_group_width < 0) { + min_group_width = 0 - min_group_width; + while (group_len++ < min_group_width && out_p < out_end_p) { + MB_CHAR2BYTES(fillchar, out_p); + } + // If the group is right-aligned, shift everything to the right and + // prepend with filler characters. + } else { + // { Move the group to the right + group_len = (min_group_width - group_len) * utf_char2len(fillchar); + memmove(t + group_len, t, (size_t)(out_p - t)); + if (out_p + group_len >= (out_end_p + 1)) { + group_len = (long)(out_end_p - out_p); + } + out_p += group_len; + // } + + // Adjust item start positions + for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + stl_items[n].start += group_len; + } + + // Prepend the fill characters + for (; group_len > 0; group_len--) { + MB_CHAR2BYTES(fillchar, t); + } + } + } + continue; + } + int minwid = 0; + int maxwid = 9999; + bool left_align = false; + + // Denotes that numbers should be left-padded with zeros + bool zeropad = (*fmt_p == '0'); + if (zeropad) { + fmt_p++; + } + + // Denotes that the item should be left-aligned. + // This is tracked by using a negative length. + if (*fmt_p == '-') { + fmt_p++; + left_align = true; + } + + // The first digit group is the item's min width + if (ascii_isdigit(*fmt_p)) { + minwid = getdigits_int(&fmt_p, false, 0); + } + + // User highlight groups override the min width field + // to denote the styling to use. + if (*fmt_p == STL_USER_HL) { + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; + fmt_p++; + curitem++; + continue; + } + + // TABPAGE pairs are used to denote a region that when clicked will + // either switch to or close a tab. + // + // Ex: tabline=%0Ttab\ zero%X + // This tabline has a TABPAGENR item with minwid `0`, + // which is then closed with a TABCLOSENR item. + // Clicking on this region with mouse enabled will switch to tab 0. + // Setting the minwid to a different value will switch + // to that tab, if it exists + // + // Ex: tabline=%1Xtab\ one%X + // This tabline has a TABCLOSENR item with minwid `1`, + // which is then closed with a TABCLOSENR item. + // Clicking on this region with mouse enabled will close tab 0. + // This is determined by the following formula: + // tab to close = (1 - minwid) + // This is because for TABPAGENR we use `minwid` = `tab number`. + // For TABCLOSENR we store the tab number as a negative value. + // Because 0 is a valid TABPAGENR value, we have to + // start our numbering at `-1`. + // So, `-1` corresponds to us wanting to close tab `0` + // + // Note: These options are only valid when creating a tabline. + if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { + if (*fmt_p == STL_TABCLOSENR) { + if (minwid == 0) { + // %X ends the close label, go back to the previous tab label nr. + for (long n = curitem - 1; n >= 0; n--) { + if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { + minwid = stl_items[n].minwid; + break; + } + } + } else { + // close nrs are stored as negative values + minwid = -minwid; + } + } + stl_items[curitem].type = TabPage; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; + fmt_p++; + curitem++; + continue; + } + + if (*fmt_p == STL_CLICK_FUNC) { + fmt_p++; + char *t = fmt_p; + while (*fmt_p != STL_CLICK_FUNC && *fmt_p) { + fmt_p++; + } + if (*fmt_p != STL_CLICK_FUNC) { + break; + } + stl_items[curitem].type = ClickFunc; + stl_items[curitem].start = out_p; + stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t)); + stl_items[curitem].minwid = minwid; + fmt_p++; + curitem++; + continue; + } + + // Denotes the end of the minwid + // the maxwid may follow immediately after + if (*fmt_p == '.') { + fmt_p++; + if (ascii_isdigit(*fmt_p)) { + maxwid = getdigits_int(&fmt_p, false, 50); + } + } + + // Bound the minimum width at 50. + // Make the number negative to denote left alignment of the item + minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1); + + // Denotes the start of a new group + if (*fmt_p == '(') { + stl_groupitems[groupdepth++] = curitem; + stl_items[curitem].type = Group; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; + stl_items[curitem].maxwid = maxwid; + fmt_p++; + curitem++; + continue; + } + + // Denotes end of expanded %{} block + if (*fmt_p == '}' && evaldepth > 0) { + fmt_p++; + evaldepth--; + continue; + } + + // An invalid item was specified. + // Continue processing on the next character of the format string. + if (vim_strchr(STL_ALL, *fmt_p) == NULL) { + fmt_p++; + continue; + } + + // The status line item type + char opt = *fmt_p++; + + // OK - now for the real work + NumberBase base = kNumBaseDecimal; + bool itemisflag = false; + bool fillable = true; + long num = -1; + char *str = NULL; + switch (opt) { + case STL_FILEPATH: + case STL_FULLPATH: + case STL_FILENAME: + // Set fillable to false so that ' ' in the filename will not + // get replaced with the fillchar + fillable = false; + if (buf_spname(wp->w_buffer) != NULL) { + STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); + } else { + char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname + : wp->w_buffer->b_fname; + home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true); + } + trans_characters((char *)NameBuff, MAXPATHL); + if (opt != STL_FILENAME) { + str = (char *)NameBuff; + } else { + str = path_tail((char *)NameBuff); + } + break; + case STL_VIM_EXPR: // '{' + { + char *block_start = fmt_p - 1; + int reevaluate = (*fmt_p == '%'); + itemisflag = true; + + if (reevaluate) { + fmt_p++; + } + + // Attempt to copy the expression to evaluate into + // the output buffer as a null-terminated string. + char *t = out_p; + while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) + && *fmt_p != NUL && out_p < out_end_p) { + *out_p++ = *fmt_p++; + } + if (*fmt_p != '}') { // missing '}' or out of space + break; + } + fmt_p++; + if (reevaluate) { + out_p[-1] = 0; // remove the % at the end of %{% expr %} + } else { + *out_p = 0; + } + + // Move our position in the output buffer + // to the beginning of the expression + out_p = t; + + // { Evaluate the expression + + // Store the current buffer number as a string variable + vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); + set_internal_string_var("g:actual_curbuf", buf_tmp); + vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); + set_internal_string_var("g:actual_curwin", (char *)win_tmp); + + buf_T *const save_curbuf = curbuf; + win_T *const save_curwin = curwin; + const int save_VIsual_active = VIsual_active; + curwin = wp; + curbuf = wp->w_buffer; + // Visual mode is only valid in the current window. + if (curwin != save_curwin) { + VIsual_active = false; + } + + // Note: The result stored in `t` is unused. + str = eval_to_string_safe(out_p, &t, use_sandbox); + + curwin = save_curwin; + curbuf = save_curbuf; + VIsual_active = save_VIsual_active; + + // Remove the variable we just stored + do_unlet(S_LEN("g:actual_curbuf"), true); + do_unlet(S_LEN("g:actual_curwin"), true); + + // } + + // Check if the evaluated result is a number. + // If so, convert the number to an int and free the string. + if (str != NULL && *str != 0) { + if (*skipdigits(str) == NUL) { + num = atoi(str); + XFREE_CLEAR(str); + itemisflag = false; + } + } + + // If the output of the expression needs to be evaluated + // replace the %{} block with the result of evaluation + if (reevaluate && str != NULL && *str != 0 + && strchr((const char *)str, '%') != NULL + && evaldepth < MAX_STL_EVAL_DEPTH) { + size_t parsed_usefmt = (size_t)(block_start - usefmt); + size_t str_length = STRLEN(str); + size_t fmt_length = STRLEN(fmt_p); + size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; + char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); + char *new_fmt_p = new_fmt; + + new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt; + new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length; + new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2; + new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length; + *new_fmt_p = 0; + new_fmt_p = NULL; + + if (usefmt != fmt) { + xfree(usefmt); + } + XFREE_CLEAR(str); + usefmt = new_fmt; + fmt_p = usefmt + parsed_usefmt; + evaldepth++; + continue; + } + break; + } + + case STL_LINE: + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0L : (long)(wp->w_cursor.lnum); + break; + + case STL_NUMLINES: + num = wp->w_buffer->b_ml.ml_line_count; + break; + + case STL_COLUMN: + num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1; + break; + + case STL_VIRTCOL: + case STL_VIRTCOL_ALT: { + colnr_T virtcol = wp->w_virtcol + 1; + // Don't display %V if it's the same as %c. + if (opt == STL_VIRTCOL_ALT + && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line + ? 0 : (int)wp->w_cursor.col + 1))) { + break; + } + num = (long)virtcol; + break; + } + + case STL_PERCENTAGE: + num = (int)(((long)wp->w_cursor.lnum * 100L) / + (long)wp->w_buffer->b_ml.ml_line_count); + break; + + case STL_ALTPERCENT: + // Store the position percentage in our temporary buffer. + // Note: We cannot store the value in `num` because + // `get_rel_pos` can return a named position. Ex: "Top" + get_rel_pos(wp, buf_tmp, TMPLEN); + str = buf_tmp; + break; + + case STL_ARGLISTSTAT: + fillable = false; + + // Note: This is important because `append_arg_number` starts appending + // at the end of the null-terminated string. + // Setting the first byte to null means it will place the argument + // number string at the beginning of the buffer. + buf_tmp[0] = 0; + + // Note: The call will only return true if it actually + // appended data to the `buf_tmp` buffer. + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { + str = buf_tmp; + } + break; + + case STL_KEYMAP: + fillable = false; + if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) { + str = buf_tmp; + } + break; + case STL_PAGENUM: + num = printer_page_num; + break; + + case STL_BUFNO: + num = wp->w_buffer->b_fnum; + break; + + case STL_OFFSET_X: + base = kNumBaseHexadecimal; + FALLTHROUGH; + case STL_OFFSET: { + long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, + false); + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? + 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ? + 0 : (int)wp->w_cursor.col); + break; + } + case STL_BYTEVAL_X: + base = kNumBaseHexadecimal; + FALLTHROUGH; + case STL_BYTEVAL: + num = byteval; + if (num == NL) { + num = 0; + } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { + num = NL; + } + break; + + case STL_ROFLAG: + case STL_ROFLAG_ALT: + itemisflag = true; + if (wp->w_buffer->b_p_ro) { + str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + } + break; + + case STL_HELPFLAG: + case STL_HELPFLAG_ALT: + itemisflag = true; + if (wp->w_buffer->b_help) { + str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]"); + } + break; + + case STL_FILETYPE: + // Copy the filetype if it is not null and the formatted string will fit + // in the temporary buffer + // (including the brackets and null terminating character) + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { + vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", + wp->w_buffer->b_p_ft); + str = buf_tmp; + } + break; + + case STL_FILETYPE_ALT: + itemisflag = true; + // Copy the filetype if it is not null and the formatted string will fit + // in the temporary buffer + // (including the comma and null terminating character) + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { + vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft); + // Uppercase the file extension + for (char *t = buf_tmp; *t != 0; t++) { + *t = (char)TOUPPER_LOC(*t); + } + str = buf_tmp; + } + break; + case STL_PREVIEWFLAG: + case STL_PREVIEWFLAG_ALT: + itemisflag = true; + if (wp->w_p_pvw) { + str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]"); + } + break; + + case STL_QUICKFIX: + if (bt_quickfix(wp->w_buffer)) { + str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist); + } + break; + + case STL_MODIFIED: + case STL_MODIFIED_ALT: + itemisflag = true; + switch ((opt == STL_MODIFIED_ALT) + + bufIsChanged(wp->w_buffer) * 2 + + (!MODIFIABLE(wp->w_buffer)) * 4) { + case 2: + str = "[+]"; break; + case 3: + str = ",+"; break; + case 4: + str = "[-]"; break; + case 5: + str = ",-"; break; + case 6: + str = "[+-]"; break; + case 7: + str = ",+-"; break; + } + break; + + case STL_HIGHLIGHT: { + // { The name of the highlight is surrounded by `#` + char *t = fmt_p; + while (*fmt_p != '#' && *fmt_p != NUL) { + fmt_p++; + } + // } + + // Create a highlight item based on the name + if (*fmt_p == '#') { + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t)); + curitem++; + fmt_p++; + } + continue; + } + } + + // If we made it this far, the item is normal and starts at + // our current position in the output buffer. + // Non-normal items would have `continued`. + stl_items[curitem].start = out_p; + stl_items[curitem].type = Normal; + + // Copy the item string into the output buffer + if (str != NULL && *str) { + // { Skip the leading `,` or ` ` if the item is a flag + // and the proper conditions are met + char *t = str; + if (itemisflag) { + if ((t[0] && t[1]) + && ((!prevchar_isitem && *t == ',') + || (prevchar_isflag && *t == ' '))) { + t++; + } + prevchar_isflag = true; + } + // } + + long l = vim_strsize(t); + + // If this item is non-empty, record that the last thing + // we put in the output buffer was an item + if (l > 0) { + prevchar_isitem = true; + } + + // If the item is too wide, truncate it from the beginning + if (l > maxwid) { + while (l >= maxwid) { + l -= ptr2cells(t); + t += utfc_ptr2len(t); + } + + // Early out if there isn't enough room for the truncation marker + if (out_p >= out_end_p) { + break; + } + + // Add the truncation marker + *out_p++ = '<'; + } + + // If the item is right aligned and not wide enough, + // pad with fill characters. + if (minwid > 0) { + for (; l < minwid && out_p < out_end_p; l++) { + // Don't put a "-" in front of a digit. + if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { + *out_p++ = ' '; + } else { + MB_CHAR2BYTES(fillchar, out_p); + } + } + minwid = 0; + } else { + // Note: The negative value denotes a left aligned item. + // Here we switch the minimum width back to a positive value. + minwid *= -1; + } + + // { Copy the string text into the output buffer + for (; *t && out_p < out_end_p; t++) { + // Change a space by fillchar, unless fillchar is '-' and a + // digit follows. + if (fillable && *t == ' ' + && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { + MB_CHAR2BYTES(fillchar, out_p); + } else { + *out_p++ = *t; + } + } + // } + + // For left-aligned items, fill any remaining space with the fillchar + for (; l < minwid && out_p < out_end_p; l++) { + MB_CHAR2BYTES(fillchar, out_p); + } + + // Otherwise if the item is a number, copy that to the output buffer. + } else if (num >= 0) { + if (out_p + 20 > out_end_p) { + break; // not sufficient space + } + prevchar_isitem = true; + + // { Build the formatting string + char nstr[20]; + char *t = nstr; + if (opt == STL_VIRTCOL_ALT) { + *t++ = '-'; + minwid--; + } + *t++ = '%'; + if (zeropad) { + *t++ = '0'; + } + + // Note: The `*` means we take the width as one of the arguments + *t++ = '*'; + *t++ = base == kNumBaseHexadecimal ? 'X' : 'd'; + *t = 0; + // } + + // { Determine how many characters the number will take up when printed + // Note: We have to cast the base because the compiler uses + // unsigned ints for the enum values. + long num_chars = 1; + for (long n = num; n >= (int)base; n /= (int)base) { + num_chars++; + } + + // VIRTCOL_ALT takes up an extra character because + // of the `-` we added above. + if (opt == STL_VIRTCOL_ALT) { + num_chars++; + } + // } + + assert(out_end_p >= out_p); + size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; + + // If the number is going to take up too much room + // Figure out the approximate number in "scientific" type notation. + // Ex: 14532 with maxwid of 4 -> '14>3' + if (num_chars > maxwid) { + // Add two to the width because the power piece will take + // two extra characters + num_chars += 2; + + // How many extra characters there are + long n = num_chars - maxwid; + + // { Reduce the number by base^n + while (num_chars-- > maxwid) { + num /= (long)base; + } + // } + + // { Add the format string for the exponent bit + *t++ = '>'; + *t++ = '%'; + // Use the same base as the first number + *t = t[-3]; + *++t = 0; + // } + + vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n); + } else { + vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num); + } + + // Advance the output buffer position to the end of the + // number we just printed + out_p += STRLEN(out_p); + + // Otherwise, there was nothing to print so mark the item as empty + } else { + stl_items[curitem].type = Empty; + } + + // Only free the string buffer if we allocated it. + // Note: This is not needed if `str` is pointing at `tmp` + if (opt == STL_VIM_EXPR) { + XFREE_CLEAR(str); + } + + if (num >= 0 || (!itemisflag && str && *str)) { + prevchar_isflag = false; // Item not NULL, but not a flag + } + + // Item processed, move to the next + curitem++; + } + + *out_p = NUL; + int itemcnt = curitem; + + // Free the format buffer if we allocated it internally + if (usefmt != fmt) { + xfree(usefmt); + } + + // We have now processed the entire statusline format string. + // What follows is post-processing to handle alignment and highlighting. + + int width = vim_strsize(out); + if (maxwidth > 0 && width > maxwidth) { + // Result is too long, must truncate somewhere. + int item_idx = 0; + char *trunc_p; + + // If there are no items, truncate from beginning + if (itemcnt == 0) { + trunc_p = out; + + // Otherwise, look for the truncation item + } else { + // Default to truncating at the first item + trunc_p = stl_items[0].start; + item_idx = 0; + + for (int i = 0; i < itemcnt; i++) { + if (stl_items[i].type == Trunc) { + // Truncate at %< stl_items. + trunc_p = stl_items[i].start; + item_idx = i; + break; + } + } + } + + // If the truncation point we found is beyond the maximum + // length of the string, truncate the end of the string. + if (width - vim_strsize(trunc_p) >= maxwidth) { + // Walk from the beginning of the + // string to find the last character that will fit. + trunc_p = out; + width = 0; + for (;;) { + width += ptr2cells(trunc_p); + if (width >= maxwidth) { + break; + } + + // Note: Only advance the pointer if the next + // character will fit in the available output space + trunc_p += utfc_ptr2len(trunc_p); + } + + // Ignore any items in the statusline that occur after + // the truncation point + for (int i = 0; i < itemcnt; i++) { + if (stl_items[i].start > trunc_p) { + itemcnt = i; + break; + } + } + + // Truncate the output + *trunc_p++ = '>'; + *trunc_p = 0; + + // Truncate at the truncation point we found + } else { + // { Determine how many bytes to remove + long trunc_len = 0; + while (width >= maxwidth) { + width -= ptr2cells(trunc_p + trunc_len); + trunc_len += utfc_ptr2len(trunc_p + trunc_len); + } + // } + + // { Truncate the string + char *trunc_end_p = trunc_p + trunc_len; + STRMOVE(trunc_p + 1, trunc_end_p); + + // Put a `<` to mark where we truncated at + *trunc_p = '<'; + + if (width + 1 < maxwidth) { + // Advance the pointer to the end of the string + trunc_p = trunc_p + STRLEN(trunc_p); + } + + // Fill up for half a double-wide character. + while (++width < maxwidth) { + MB_CHAR2BYTES(fillchar, trunc_p); + *trunc_p = NUL; + } + // } + + // { Change the start point for items based on + // their position relative to our truncation point + + // Note: The offset is one less than the truncation length because + // the truncation marker `<` is not counted. + long item_offset = trunc_len - 1; + + for (int i = item_idx; i < itemcnt; i++) { + // Items starting at or after the end of the truncated section need + // to be moved backwards. + if (stl_items[i].start >= trunc_end_p) { + stl_items[i].start -= item_offset; + // Anything inside the truncated area is set to start + // at the `<` truncation character. + } else { + stl_items[i].start = trunc_p; + } + } + // } + } + width = maxwidth; + + // If there is room left in our statusline, and room left in our buffer, + // add characters at the separate marker (if there is one) to + // fill up the available space. + } else if (width < maxwidth + && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) { + // Find how many separators there are, which we will use when + // figuring out how many groups there are. + int num_separators = 0; + for (int i = 0; i < itemcnt; i++) { + if (stl_items[i].type == Separate) { + // Create an array of the start location for each + // separator mark. + stl_separator_locations[num_separators] = i; + num_separators++; + } + } + + // If we have separated groups, then we deal with it now + if (num_separators) { + int standard_spaces = (maxwidth - width) / num_separators; + int final_spaces = (maxwidth - width) - + standard_spaces * (num_separators - 1); + + for (int i = 0; i < num_separators; i++) { + int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; + dislocation *= utf_char2len(fillchar); + char *start = stl_items[stl_separator_locations[i]].start; + char *seploc = start + dislocation; + STRMOVE(seploc, start); + for (char *s = start; s < seploc;) { + MB_CHAR2BYTES(fillchar, s); + } + + for (int item_idx = stl_separator_locations[i] + 1; + item_idx < itemcnt; + item_idx++) { + stl_items[item_idx].start += dislocation; + } + } + + width = maxwidth; + } + } + + // Store the info about highlighting. + if (hltab != NULL) { + *hltab = stl_hltab; + stl_hlrec_t *sp = stl_hltab; + for (long l = 0; l < itemcnt; l++) { + if (stl_items[l].type == Highlight) { + sp->start = stl_items[l].start; + sp->userhl = stl_items[l].minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + // Store the info about tab pages labels. + if (tabtab != NULL) { + *tabtab = stl_tabtab; + StlClickRecord *cur_tab_rec = stl_tabtab; + for (long l = 0; l < itemcnt; l++) { + if (stl_items[l].type == TabPage) { + cur_tab_rec->start = stl_items[l].start; + if (stl_items[l].minwid == 0) { + cur_tab_rec->def.type = kStlClickDisabled; + cur_tab_rec->def.tabnr = 0; + } else { + int tabnr = stl_items[l].minwid; + if (stl_items[l].minwid > 0) { + cur_tab_rec->def.type = kStlClickTabSwitch; + } else { + cur_tab_rec->def.type = kStlClickTabClose; + tabnr = -tabnr; + } + cur_tab_rec->def.tabnr = tabnr; + } + cur_tab_rec->def.func = NULL; + cur_tab_rec++; + } else if (stl_items[l].type == ClickFunc) { + cur_tab_rec->start = stl_items[l].start; + cur_tab_rec->def.type = kStlClickFuncRun; + cur_tab_rec->def.tabnr = stl_items[l].minwid; + cur_tab_rec->def.func = stl_items[l].cmd; + cur_tab_rec++; + } + } + cur_tab_rec->start = NULL; + cur_tab_rec->def.type = kStlClickDisabled; + cur_tab_rec->def.tabnr = 0; + cur_tab_rec->def.func = NULL; + } + + // When inside update_screen we do not want redrawing a statusline, ruler, + // title, etc. to trigger another redraw, it may cause an endless loop. + if (updating_screen) { + must_redraw = save_must_redraw; + curwin->w_redr_type = save_redr_type; + } + + return width; +} diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h new file mode 100644 index 0000000000..357a9a821f --- /dev/null +++ b/src/nvim/statusline.h @@ -0,0 +1,10 @@ +#ifndef NVIM_STATUSLINE_H +#define NVIM_STATUSLINE_H + +#include "nvim/buffer_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "statusline.h.generated.h" +#endif + +#endif // NVIM_STATUSLINE_H diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 22effaade0..16691d0ded 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -104,10 +104,10 @@ char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, c p += l - 1; continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash(p))) { + if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash((char *)p))) { length++; // count a backslash } - ++length; // count an ordinary char + length++; // count an ordinary char } char_u *escaped_string = xmalloc(length); @@ -120,7 +120,7 @@ char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, c p += l - 1; // skip multibyte char continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash(p))) { + if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash((char *)p))) { *p2++ = cc; } *p2++ = *p; @@ -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; @@ -222,13 +222,13 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n } if ((*p == '\n' && (csh_like || do_newline)) || (*p == '!' && (csh_like || do_special))) { - ++length; // insert backslash + length++; // insert backslash if (csh_like && do_special) { - ++length; // insert backslash + length++; // insert backslash } } if (do_special && find_cmdline_var(p, &l) >= 0) { - ++length; // insert backslash + length++; // insert backslash p += l - 1; } if (*p == '\\' && fish_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 } @@ -503,7 +503,7 @@ static int sort_compare(const void *s1, const void *s2) void sort_strings(char **files, int count) { - qsort((void *)files, (size_t)count, sizeof(char_u *), sort_compare); + qsort((void *)files, (size_t)count, sizeof(char *), sort_compare); } /* @@ -516,7 +516,7 @@ bool has_non_ascii(const char_u *s) const char_u *p; if (s != NULL) { - for (p = s; *p != NUL; ++p) { + for (p = s; *p != NUL; p++) { if (*p >= 128) { return true; } @@ -540,14 +540,12 @@ bool has_non_ascii_len(const char *const s, const size_t len) return false; } -/* - * Concatenate two strings and return the result in allocated memory. - */ -char_u *concat_str(const char_u *restrict str1, const char_u *restrict str2) +/// Concatenate two strings and return the result in allocated memory. +char *concat_str(const char *restrict str1, const char *restrict str2) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { size_t l = STRLEN(str1); - char_u *dest = xmalloc(l + STRLEN(str2) + 1); + char *dest = xmalloc(l + STRLEN(str2) + 1); STRCPY(dest, str1); STRCPY(dest + l, str2); return dest; @@ -1511,15 +1509,15 @@ int kv_do_printf(StringBuilder *str, const char *fmt, ...) /// Reverse text into allocated memory. /// /// @return the allocated string. -char_u *reverse_text(char_u *s) +char *reverse_text(char *s) FUNC_ATTR_NONNULL_RET { // Reverse the pattern. size_t len = STRLEN(s); - char_u *rev = xmalloc(len + 1); + char *rev = xmalloc(len + 1); size_t rev_i = len; for (size_t s_i = 0; s_i < len; s_i++) { - const int mb_len = utfc_ptr2len((char *)s + s_i); + const int mb_len = utfc_ptr2len(s + s_i); rev_i -= (size_t)mb_len; memmove(rev + rev_i, s + s_i, (size_t)mb_len); s_i += (size_t)mb_len - 1; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 4ec4a57d68..f055cc9b0e 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" @@ -36,13 +37,14 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #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 +380,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(); } @@ -607,7 +609,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) /* * Skip lines that end in a backslash. */ - for (; start_lnum > 1; --start_lnum) { + for (; start_lnum > 1; start_lnum--) { line = ml_get(start_lnum - 1); if (*line == NUL || *(line + STRLEN(line) - 1) != '\\') { break; @@ -685,7 +687,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) */ validate_current_state(); - for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum) { + for (current_lnum = lnum; current_lnum < end_lnum; current_lnum++) { syn_start_line(); for (;;) { had_sync_point = syn_finish_line(true); @@ -723,7 +725,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 +733,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; @@ -810,9 +812,7 @@ static void restore_chartab(char_u *chartab) } } -/* - * Return TRUE if the line-continuation pattern matches in line "lnum". - */ +/// Return true if the line-continuation pattern matches in line "lnum". static int syn_match_linecont(linenr_T lnum) { if (syn_block->b_syn_linecont_prog != NULL) { @@ -830,7 +830,7 @@ static int syn_match_linecont(linenr_T lnum) restore_chartab(buf_chartab); return r; } - return FALSE; + return false; } /* @@ -876,7 +876,7 @@ static void syn_update_ends(bool startofline) cur_si->si_m_endpos.lnum = 0; cur_si->si_m_endpos.col = 0; cur_si->si_h_endpos = cur_si->si_m_endpos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; } } } @@ -892,7 +892,7 @@ static void syn_update_ends(bool startofline) */ int i = current_state.ga_len - 1; if (keepend_level >= 0) { - for (; i > keepend_level; --i) { + for (; i > keepend_level; i--) { if (CUR_STATE(i).si_flags & HL_EXTEND) { break; } @@ -1029,7 +1029,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; } @@ -1184,7 +1184,7 @@ static void syn_stack_free_entry(synblock_T *block, synstate_T *p) clear_syn_state(p); p->sst_next = block->b_sst_firstfree; block->b_sst_firstfree = p; - ++block->b_sst_freecount; + block->b_sst_freecount++; } /* @@ -1223,7 +1223,7 @@ static synstate_T *store_current_state(void) * If the current state contains a start or end pattern that continues * from the previous line, we can't use it. Don't store it then. */ - for (i = current_state.ga_len - 1; i >= 0; --i) { + for (i = current_state.ga_len - 1; i >= 0; i--) { cur_si = &CUR_STATE(i); if (cur_si->si_h_startpos.lnum >= current_lnum || cur_si->si_m_endpos.lnum >= current_lnum @@ -1271,7 +1271,7 @@ static synstate_T *store_current_state(void) // list, after *sp p = syn_block->b_sst_firstfree; syn_block->b_sst_firstfree = p->sst_next; - --syn_block->b_sst_freecount; + syn_block->b_sst_freecount--; if (sp == NULL) { // Insert in front of the list p->sst_next = syn_block->b_sst_first; @@ -1300,7 +1300,7 @@ static synstate_T *store_current_state(void) } else { bp = sp->sst_union.sst_stack; } - for (i = 0; i < sp->sst_stacksize; ++i) { + for (i = 0; i < sp->sst_stacksize; i++) { bp[i].bs_idx = CUR_STATE(i).si_idx; bp[i].bs_flags = (int)CUR_STATE(i).si_flags; bp[i].bs_seqnr = CUR_STATE(i).si_seqnr; @@ -1334,7 +1334,7 @@ static void load_current_state(synstate_T *from) } else { bp = from->sst_union.sst_stack; } - for (i = 0; i < from->sst_stacksize; ++i) { + for (i = 0; i < from->sst_stacksize; i++) { CUR_STATE(i).si_idx = bp[i].bs_idx; CUR_STATE(i).si_flags = bp[i].bs_flags; CUR_STATE(i).si_seqnr = bp[i].bs_seqnr; @@ -1343,7 +1343,7 @@ static void load_current_state(synstate_T *from) if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND)) { keepend_level = i; } - CUR_STATE(i).si_ends = FALSE; + CUR_STATE(i).si_ends = false; CUR_STATE(i).si_m_lnum = 0; if (CUR_STATE(i).si_idx >= 0) { CUR_STATE(i).si_next_list = @@ -1500,7 +1500,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(); } } @@ -1549,7 +1549,7 @@ static bool syn_finish_line(const bool syncing) /// "col" is normally 0 for the first use in a line, and increments by one each /// time. It's allowed to skip characters and to stop before the end of the /// line. But only a "col" after a previously used column is allowed. -/// When "can_spell" is not NULL set it to TRUE when spell-checking should be +/// When "can_spell" is not NULL set it to true when spell-checking should be /// done. /// /// @param keep_state keep state of char at "col" @@ -1634,11 +1634,9 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con bool zero_width_next_list = false; garray_T zero_width_next_ga; - /* - * No character, no attributes! Past end of line? - * Do try matching with an empty line (could be the start of a region). - */ - line = syn_getcurline(); + // No character, no attributes! Past end of line? + // Do try matching with an empty line (could be the start of a region). + line = (char_u *)syn_getcurline(); if (line[current_col] == NUL && current_col != 0) { /* * If we found a match after the last column, use it. @@ -1706,12 +1704,10 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (syn_block->b_syn_containedin || cur_si == NULL || cur_si->si_cont_list != NULL) { - /* - * 2. Check for keywords, if on a keyword char after a non-keyword - * char. Don't do this when syncing. - */ + // 2. Check for keywords, if on a keyword char after a non-keyword + // char. Don't do this when syncing. if (do_keywords) { - line = syn_getcurline(); + line = (char_u *)syn_getcurline(); const char_u *cur_pos = line + current_col; if (vim_iswordp_buf(cur_pos, syn_buf) && (current_col == 0 @@ -1730,7 +1726,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con cur_si->si_m_endpos.col = endcol; cur_si->si_h_endpos.lnum = current_lnum; cur_si->si_h_endpos.col = endcol; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_end_idx = 0; cur_si->si_flags = flags; cur_si->si_seqnr = next_seqnr++; @@ -1979,13 +1975,11 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con * Handle searching for nextgroup match. */ if (current_next_list != NULL && !keep_next_list) { - /* - * If a nextgroup was not found, continue looking for one if: - * - this is an empty line and the "skipempty" option was given - * - we are on white space and the "skipwhite" option was given - */ + // If a nextgroup was not found, continue looking for one if: + // - this is an empty line and the "skipempty" option was given + // - we are on white space and the "skipwhite" option was given if (!found_match) { - line = syn_getcurline(); + line = (char_u *)syn_getcurline(); if (((current_next_flags & HL_SKIPWHITE) && ascii_iswhite(line[current_col])) || ((current_next_flags & HL_SKIPEMPTY) @@ -2022,7 +2016,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con current_flags = 0; current_seqnr = 0; if (cur_si != NULL) { - for (int idx = current_state.ga_len - 1; idx >= 0; --idx) { + for (int idx = current_state.ga_len - 1; idx >= 0; idx--) { sip = &CUR_STATE(idx); if ((current_lnum > sip->si_h_startpos.lnum || (current_lnum == sip->si_h_startpos.lnum @@ -2044,10 +2038,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (can_spell != NULL) { struct sp_syn sps; - /* - * set "can_spell" to TRUE if spell checking is supposed to be - * done in the current item. - */ + // set "can_spell" to true if spell checking is supposed to be + // done in the current item. if (syn_block->b_spell_cluster_id == 0) { // There is no @Spell cluster: Do spelling for items without // @NoSpell cluster. @@ -2095,9 +2087,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) { @@ -2110,7 +2102,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // nextgroup ends at end of line, unless "skipnl" or "skipempty" present if (current_next_list != NULL - && (line = syn_getcurline())[current_col] != NUL + && (line = (char_u *)syn_getcurline())[current_col] != NUL && line[current_col + 1] == NUL && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) { current_next_list = NULL; @@ -2190,7 +2182,7 @@ static stateitem_T *push_next_match(void) } else { cur_si->si_m_endpos = next_match_m_endpos; cur_si->si_h_endpos = next_match_h_endpos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_flags |= next_match_flags; cur_si->si_eoe_pos = next_match_eoe_pos; cur_si->si_end_idx = next_match_end_idx; @@ -2214,7 +2206,7 @@ static stateitem_T *push_next_match(void) cur_si->si_m_lnum = current_lnum; cur_si->si_m_endpos = next_match_eos_pos; cur_si->si_h_endpos = next_match_eos_pos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_end_idx = 0; cur_si->si_flags = HL_MATCH; cur_si->si_seqnr = next_seqnr++; @@ -2404,7 +2396,7 @@ static void check_keepend(void) * won't do anything. If there is no "extend" item "i" will be * "keepend_level" and all "keepend" items will work normally. */ - for (i = current_state.ga_len - 1; i > keepend_level; --i) { + for (i = current_state.ga_len - 1; i > keepend_level; i--) { if (CUR_STATE(i).si_flags & HL_EXTEND) { break; } @@ -2414,13 +2406,13 @@ static void check_keepend(void) maxpos.col = 0; maxpos_h.lnum = 0; maxpos_h.col = 0; - for (; i < current_state.ga_len; ++i) { + for (; i < current_state.ga_len; i++) { sip = &CUR_STATE(i); if (maxpos.lnum != 0) { limit_pos_zero(&sip->si_m_endpos, &maxpos); limit_pos_zero(&sip->si_h_endpos, &maxpos_h); limit_pos_zero(&sip->si_eoe_pos, &maxpos); - sip->si_ends = TRUE; + sip->si_ends = true; } if (sip->si_ends && (sip->si_flags & HL_KEEPEND)) { if (maxpos.lnum == 0 @@ -2480,12 +2472,12 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) // No end pattern matched. if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE) { // a "oneline" never continues in the next line - sip->si_ends = TRUE; + sip->si_ends = true; sip->si_m_endpos.lnum = current_lnum; sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline()); } else { // continues in the next line - sip->si_ends = FALSE; + sip->si_ends = false; sip->si_m_endpos.lnum = 0; } sip->si_h_endpos = sip->si_m_endpos; @@ -2494,7 +2486,7 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) sip->si_m_endpos = endpos; sip->si_h_endpos = hl_endpos; sip->si_eoe_pos = end_endpos; - sip->si_ends = TRUE; + sip->si_ends = true; sip->si_end_idx = end_idx; } } @@ -2506,7 +2498,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; } @@ -2517,7 +2509,7 @@ static void pop_current_state(void) { if (!GA_EMPTY(¤t_state)) { unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch); - --current_state.ga_len; + current_state.ga_len--; } // after the end of a pattern, try matching a keyword or pattern next_match_idx = -1; @@ -2582,7 +2574,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 +2582,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; } @@ -2611,7 +2603,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ * Find end pattern that matches first after "matchcol". */ best_idx = -1; - for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx) { + for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; idx++) { int lc_col = matchcol; spp = &(SYN_ITEMS(syn_block)[idx]); @@ -2876,17 +2868,15 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s result->col = col; } -/* - * Get current line in syntax buffer. - */ -static char_u *syn_getcurline(void) +/// Get current line in syntax buffer. +static char *syn_getcurline(void) { - return ml_get_buf(syn_buf, current_lnum, false); + return (char *)ml_get_buf(syn_buf, current_lnum, false); } /* * Call vim_regexec() to find a match with "rmp" in "syn_buf". - * Returns TRUE when there is a match. + * Returns true when there is a match. */ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st) { @@ -2914,9 +2904,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (profile_cmp(pt, st->slowest) < 0) { st->slowest = pt; } - ++st->count; + st->count++; if (r > 0) { - ++st->match; + st->match++; } } if (timed_out && !syn_win->w_s->b_syn_slow) { @@ -2927,9 +2917,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (r > 0) { rmp->startpos[0].lnum += lnum; rmp->endpos[0].lnum += lnum; - return TRUE; + return true; } - return FALSE; + return false; } /// Check one position in a line for a matching keyword. @@ -3018,12 +3008,12 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } - next = skiptowhite(arg); + next = (char_u *)skiptowhite((char *)arg); if (*arg == NUL) { if (curwin->w_s->b_syn_conceal) { msg("syntax conceal on"); @@ -3047,12 +3037,12 @@ static void syn_cmd_case(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } - next = skiptowhite(arg); + next = (char_u *)skiptowhite((char *)arg); if (*arg == NUL) { if (curwin->w_s->b_syn_ic) { msg("syntax case ignore"); @@ -3074,7 +3064,7 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *arg_end; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } @@ -3091,7 +3081,7 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) return; } - arg_end = skiptowhite(arg); + arg_end = (char_u *)skiptowhite((char *)arg); if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5) { curwin->w_s->b_syn_foldlevel = SYNFLD_START; } else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7) { @@ -3115,12 +3105,12 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) char_u *arg = (char_u *)eap->arg; char_u *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } - next = skiptowhite(arg); + next = (char_u *)skiptowhite((char *)arg); if (*arg == NUL) { if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { msg("syntax spell toplevel"); @@ -3141,7 +3131,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) } // assume spell checking changed, force a redraw - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Handle ":syntax iskeyword" command. @@ -3160,7 +3150,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) msg_puts("\n"); if (curwin->w_s->b_syn_isk != empty_option) { msg_puts("syntax iskeyword "); - msg_outtrans((char *)curwin->w_s->b_syn_isk); + msg_outtrans(curwin->w_s->b_syn_isk); } else { msg_outtrans(_("syntax iskeyword not set")); } @@ -3170,18 +3160,18 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) clear_string_option(&curwin->w_s->b_syn_isk); } else { memmove(save_chartab, curbuf->b_chartab, (size_t)32); - save_isk = curbuf->b_p_isk; - curbuf->b_p_isk = vim_strsave(arg); + save_isk = (char_u *)curbuf->b_p_isk; + curbuf->b_p_isk = (char *)vim_strsave(arg); buf_init_chartab(curbuf, false); memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32); memmove(curbuf->b_chartab, save_chartab, (size_t)32); clear_string_option(&curwin->w_s->b_syn_isk); curwin->w_s->b_syn_isk = curbuf->b_p_isk; - curbuf->b_p_isk = save_isk; + curbuf->b_p_isk = (char *)save_isk; } } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /* @@ -3280,7 +3270,7 @@ static void syn_remove_pattern(synblock_T *block, int idx) spp = &(SYN_ITEMS(block)[idx]); if (spp->sp_flags & HL_FOLD) { - --block->b_syn_folditems; + block->b_syn_folditems--; } syn_clear_pattern(block, idx); memmove(spp, spp + 1, sizeof(synpat_T) * (size_t)(block->b_syn_patterns.ga_len - idx - 1)); @@ -3322,7 +3312,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) char_u *arg_end; int id; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } @@ -3355,7 +3345,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) * Clear the group IDs that are in the argument. */ while (!ends_excmd(*arg)) { - arg_end = skiptowhite(arg); + arg_end = (char_u *)skiptowhite((char *)arg); if (*arg == '@') { id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1)); if (id == 0) { @@ -3381,7 +3371,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) arg = (char_u *)skipwhite((char *)arg_end); } } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } @@ -3469,13 +3459,13 @@ void syn_maybe_enable(void) /// Handle ":syntax [list]" command: list current syntax words. /// -/// @param syncing when TRUE: list syncing items +/// @param syncing when true: list syncing items static void syn_cmd_list(exarg_T *eap, int syncing) { char_u *arg = (char_u *)eap->arg; char_u *arg_end; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd((char *)arg); if (eap->skip) { return; } @@ -3524,7 +3514,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing) for (int id = 1; id <= highlight_num_groups() && !got_int; id++) { syn_list_one(id, syncing, false); } - for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id) { + for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; id++) { syn_list_cluster(id); } } else { @@ -3532,7 +3522,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing) * List the group IDs and syntax clusters that are in the argument. */ while (!ends_excmd(*arg) && !got_int) { - arg_end = skiptowhite(arg); + arg_end = (char_u *)skiptowhite((char *)arg); if (*arg == '@') { int id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1)); if (id == 0) { @@ -3653,7 +3643,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); @@ -3700,7 +3690,7 @@ static void syn_list_flags(struct name_list *nlist, int flags, int attr) { int i; - for (i = 0; nlist[i].flag != 0; ++i) { + for (i = 0; nlist[i].flag != 0; i++) { if (flags & nlist[i].flag) { msg_puts_attr(nlist[i].name, attr); msg_putchar(' '); @@ -3923,11 +3913,11 @@ static void syn_clear_keyword(int id, hashtab_T *ht) hash_lock(ht); todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (HASHITEM_EMPTY(hi)) { continue; } - --todo; + todo--; kp_prev = NULL; for (kp = HI2KE(hi); kp != NULL;) { if (kp->k_syn.id == id) { @@ -3965,9 +3955,9 @@ static void clear_keywtab(hashtab_T *ht) keyentry_T *kp_next; todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + 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); @@ -4004,7 +3994,7 @@ static void add_keyword(char_u *const name, const int id, const int flags, kp->k_char = conceal_char; kp->k_syn.cont_in_list = copy_id_list(cont_in_list); if (cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } kp->next_list = copy_id_list(next_list); @@ -4037,17 +4027,13 @@ static void add_keyword(char_u *const name, const int id, const int flags, /// /// @return a pointer to the first argument. /// Return NULL if the end of the command was found instead of further args. -static char_u *get_group_name(char_u *arg, char_u **name_end) +static char *get_group_name(char *arg, char **name_end) { - char_u *rest; - *name_end = skiptowhite(arg); - rest = (char_u *)skipwhite((char *)(*name_end)); + char *rest = skipwhite(*name_end); - /* - * Check if there are enough arguments. The first argument may be a - * pattern, where '|' is allowed, so only check for NUL. - */ + // Check if there are enough arguments. The first argument may be a + // pattern, where '|' is allowed, so only check for NUL. if (ends_excmd(*arg) || *rest == NUL) { return NULL; } @@ -4061,11 +4047,11 @@ static char_u *get_group_name(char_u *arg, char_u **name_end) /// /// @param arg next argument to be checked /// @param opt various things -/// @param skip TRUE if skipping over command +/// @param skip true if skipping over command /// /// @return a pointer to the next argument (which isn't an option). /// Return NULL for any error; -static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_char, int skip) +static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, int skip) { char_u *gname_start, *gname; int syn_id; @@ -4118,8 +4104,8 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0;) { p = flagtab[fidx].name; int i; - for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) { - if (arg[len] != (char_u)p[i] && arg[len] != (char_u)p[i + 1]) { + for (i = 0, len = 0; p[i] != NUL; i += 2, len++) { + if (arg[len] != p[i] && arg[len] != p[i + 1]) { break; } } @@ -4159,16 +4145,16 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { // cchar=? - *conceal_char = utf_ptr2char((char *)arg + 6); - arg += utfc_ptr2len((char *)arg + 6) - 1; + *conceal_char = utf_ptr2char(arg + 6); + arg += utfc_ptr2len(arg + 6) - 1; if (!vim_isprintc_strict(*conceal_char)) { emsg(_("E844: invalid cchar value")); return NULL; } - arg = (char_u *)skipwhite((char *)arg + 7); + arg = skipwhite(arg + 7); } else { opt->flags |= flagtab[fidx].flags; - arg = (char_u *)skipwhite((char *)arg + len); + arg = skipwhite(arg + len); if (flagtab[fidx].flags == HL_SYNC_HERE || flagtab[fidx].flags == HL_SYNC_THERE) { @@ -4176,12 +4162,12 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha emsg(_("E393: group[t]here not accepted here")); return NULL; } - gname_start = arg; + gname_start = (char_u *)arg; arg = skiptowhite(arg); - if (gname_start == arg) { + if (gname_start == (char_u *)arg) { return NULL; } - gname = vim_strnsave(gname_start, (size_t)(arg - gname_start)); + gname = vim_strnsave(gname_start, (size_t)((char_u *)arg - gname_start)); if (STRCMP(gname, "NONE") == 0) { *opt->sync_idx = NONE_IDX; } else { @@ -4202,7 +4188,7 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } xfree(gname); - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); } else if (flagtab[fidx].flags == HL_FOLD && foldmethodIsSyntax(curwin)) { // Need to update folds later. @@ -4242,33 +4228,33 @@ static void syn_incl_toplevel(int id, int *flagsp) */ static void syn_cmd_include(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int sgl_id = 1; - char_u *group_name_end; - char_u *rest; + char *group_name_end; + char *rest; char *errormsg = NULL; int prev_toplvl_grp; int prev_syn_inc_tag; bool source = false; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } if (arg[0] == '@') { - ++arg; + arg++; rest = get_group_name(arg, &group_name_end); if (rest == NULL) { emsg(_("E397: Filename required")); return; } - sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg)); + sgl_id = syn_check_cluster((char_u *)arg, (int)(group_name_end - arg)); if (sgl_id == 0) { return; } // separate_nextcmd() and expand_filename() depend on this - eap->arg = (char *)rest; + eap->arg = rest; } /* @@ -4316,13 +4302,13 @@ static void syn_cmd_include(exarg_T *eap, int syncing) */ static void syn_cmd_keyword(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; + char *arg = eap->arg; + char *group_name_end; int syn_id; - char_u *rest; - char_u *keyword_copy = NULL; - char_u *p; - char_u *kw; + char *rest; + char *keyword_copy = NULL; + char *p; + char *kw; syn_opt_arg_T syn_opt_arg; int cnt; int conceal_char = NUL; @@ -4333,7 +4319,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) if (eap->skip) { syn_id = -1; } else { - syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg)); + syn_id = syn_check_group(arg, (size_t)(group_name_end - arg)); } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. @@ -4352,7 +4338,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) // 1: collect the options and copy the keywords to keyword_copy. cnt = 0; p = keyword_copy; - for (; rest != NULL && !ends_excmd(*rest); rest = (char_u *)skipwhite((char *)rest)) { + for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; @@ -4374,11 +4360,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) // 2: Add an entry for each keyword. for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1) { - for (p = (char_u *)vim_strchr((char *)kw, '[');;) { + for (p = vim_strchr(kw, '[');;) { if (p != NULL) { *p = NUL; } - add_keyword(kw, syn_id, syn_opt_arg.flags, + add_keyword((char_u *)kw, syn_id, syn_opt_arg.flags, syn_opt_arg.cont_in_list, syn_opt_arg.next_list, conceal_char); if (p == NULL) { @@ -4397,7 +4383,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) kw = p + 1; break; // skip over the "]" } - const int l = utfc_ptr2len((char *)p + 1); + const int l = utfc_ptr2len(p + 1); memmove(p, p + 1, (size_t)l); p += l; @@ -4413,12 +4399,12 @@ error: } if (rest != NULL) { - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = (char *)check_nextcmd((char_u *)rest); } else { semsg(_(e_invarg2), arg); } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } @@ -4426,12 +4412,11 @@ error: /// /// Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .." /// -/// @param syncing TRUE for ":syntax sync match .. " +/// @param syncing true for ":syntax sync match .. " static void syn_cmd_match(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; + char *arg = eap->arg; + char *group_name_end; synpat_T item; // the item found in the line int syn_id; syn_opt_arg_T syn_opt_arg; @@ -4439,7 +4424,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) int conceal_char = NUL; // Isolate the group name, check for validity - rest = get_group_name(arg, &group_name_end); + char *rest = get_group_name(arg, &group_name_end); // Get options before the pattern syn_opt_arg.flags = 0; @@ -4453,8 +4438,8 @@ static void syn_cmd_match(exarg_T *eap, int syncing) // get the pattern. init_syn_patterns(); - memset(&item, 0, sizeof(item)); - rest = get_syn_pattern(rest, &item); + CLEAR_FIELD(item); + rest = get_syn_pattern((char_u *)rest, &item); if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; } @@ -4466,11 +4451,11 @@ static void syn_cmd_match(exarg_T *eap, int syncing) /* * Check for trailing command and illegal trailing arguments. */ - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = (char *)check_nextcmd((char_u *)rest); if (!ends_excmd(*rest) || eap->skip) { rest = NULL; } else { - if ((syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg))) != 0) { + if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) { syn_incl_toplevel(syn_id, &syn_opt_arg.flags); /* * Store the pattern in the syn_items list @@ -4488,7 +4473,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) spp->sp_syn.cont_in_list = syn_opt_arg.cont_in_list; spp->sp_cchar = conceal_char; if (syn_opt_arg.cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } spp->sp_next_list = syn_opt_arg.next_list; @@ -4497,10 +4482,10 @@ static void syn_cmd_match(exarg_T *eap, int syncing) curwin->w_s->b_syn_sync_flags |= SF_MATCH; } if (syn_opt_arg.flags & HL_FOLD) { - ++curwin->w_s->b_syn_folditems; + curwin->w_s->b_syn_folditems++; } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. return; // don't free the progs and patterns now } @@ -4524,15 +4509,15 @@ static void syn_cmd_match(exarg_T *eap, int syncing) /// Handle ":syntax region {group-name} [matchgroup={group-name}] /// start {start} .. [skip {skip}] end {end} .. [{options}]". /// -/// @param syncing TRUE for ":syntax sync region .." +/// @param syncing true for ":syntax sync region .." static void syn_cmd_region(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; // next arg, NULL on error - char_u *key_end; - char_u *key = NULL; - char_u *p; + char *arg = eap->arg; + char *group_name_end; + char *rest; // next arg, NULL on error + char *key_end; + char *key = NULL; + char *p; int item; #define ITEM_START 0 #define ITEM_SKIP 1 @@ -4583,10 +4568,10 @@ 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)); + key = (char *)vim_strnsave_up((char_u *)rest, (size_t)(key_end - rest)); if (STRCMP(key, "MATCHGROUP") == 0) { item = ITEM_MATCHGROUP; } else if (STRCMP(key, "START") == 0) { @@ -4602,13 +4587,13 @@ static void syn_cmd_region(exarg_T *eap, int syncing) } else { break; } - rest = (char_u *)skipwhite((char *)key_end); + rest = skipwhite(key_end); if (*rest != '=') { rest = NULL; semsg(_("E398: Missing '=': %s"), arg); break; } - rest = (char_u *)skipwhite((char *)rest + 1); + rest = skipwhite(rest + 1); if (*rest == NUL) { not_enough = true; break; @@ -4619,13 +4604,13 @@ static void syn_cmd_region(exarg_T *eap, int syncing) if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip) { matchgroup_id = 0; } else { - matchgroup_id = syn_check_group((char *)rest, (size_t)(p - rest)); + matchgroup_id = syn_check_group(rest, (size_t)(p - rest)); if (matchgroup_id == 0) { illegal = true; break; } } - rest = (char_u *)skipwhite((char *)p); + rest = skipwhite(p); } else { /* * Allocate room for a syn_pattern, and link it in the list of @@ -4646,7 +4631,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) assert(item == ITEM_SKIP || item == ITEM_END); reg_do_extmatch = REX_USE; } - rest = get_syn_pattern(rest, ppp->pp_synp); + rest = get_syn_pattern((char_u *)rest, ppp->pp_synp); reg_do_extmatch = 0; if (item == ITEM_END && vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { @@ -4673,18 +4658,18 @@ static void syn_cmd_region(exarg_T *eap, int syncing) * Check for trailing garbage or command. * If OK, add the item. */ - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = (char *)check_nextcmd((char_u *)rest); if (!ends_excmd(*rest) || eap->skip) { rest = NULL; } else { ga_grow(&(curwin->w_s->b_syn_patterns), pat_count); - if ((syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg))) != 0) { + if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) { syn_incl_toplevel(syn_id, &syn_opt_arg.flags); /* * Store the start/skip/end in the syn_items list */ int idx = curwin->w_s->b_syn_patterns.ga_len; - for (item = ITEM_START; item <= ITEM_END; ++item) { + for (item = ITEM_START; item <= ITEM_END; item++) { for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next) { SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp); SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing; @@ -4703,20 +4688,20 @@ static void syn_cmd_region(exarg_T *eap, int syncing) SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list = syn_opt_arg.cont_in_list; if (syn_opt_arg.cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } 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; + curwin->w_s->b_syn_folditems++; } } } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. success = true; // don't free the progs and patterns now } @@ -4726,7 +4711,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) /* * Free the allocated memory. */ - for (item = ITEM_START; item <= ITEM_END; ++item) { + for (item = ITEM_START; item <= ITEM_END; item++) { for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next) { if (!success && ppp->pp_synp != NULL) { vim_regfree(ppp->pp_synp->sp_prog); @@ -4945,7 +4930,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; @@ -4966,14 +4951,14 @@ static int syn_add_cluster(char_u *name) */ static void syn_cmd_cluster(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; + char *arg = eap->arg; + char *group_name_end; + char *rest; bool got_clstr = false; int opt_len; int list_op; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -4981,7 +4966,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) rest = get_group_name(arg, &group_name_end); if (rest != NULL) { - int scl_id = syn_check_cluster(arg, (int)(group_name_end - arg)); + int scl_id = syn_check_cluster((char_u *)arg, (int)(group_name_end - arg)); if (scl_id == 0) { return; } @@ -5019,7 +5004,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) } if (got_clstr) { - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all. } } @@ -5041,14 +5026,13 @@ static void init_syn_patterns(void) ga_set_growsize(&curwin->w_s->b_syn_patterns, 10); } -/* - * Get one pattern for a ":syntax match" or ":syntax region" command. - * Stores the pattern and program in a synpat_T. - * Returns a pointer to the next argument, or NULL in case of an error. - */ -static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) +/// Get one pattern for a ":syntax match" or ":syntax region" command. +/// Stores the pattern and program in a synpat_T. +/// +/// @return a pointer to the next argument, or NULL in case of an error. +static char *get_syn_pattern(char_u *arg, synpat_T *ci) { - char_u *end; + char *end; int *p; int idx; char *cpo_save; @@ -5058,17 +5042,17 @@ 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 = skip_regexp((char *)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; - p_cpo = ""; + p_cpo = empty_option; ci->sp_prog = vim_regcomp((char *)ci->sp_pattern, RE_MAGIC); p_cpo = cpo_save; @@ -5081,7 +5065,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 +5090,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 +5101,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 +5119,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 skipwhite(end); } /* @@ -5144,7 +5128,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; @@ -5152,26 +5136,26 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) char *cpo_save; if (ends_excmd(*arg_start)) { - syn_cmd_list(eap, TRUE); + syn_cmd_list(eap, true); return; } while (!ends_excmd(*arg_start)) { - arg_end = skiptowhite(arg_start); - next_arg = (char_u *)skipwhite((char *)arg_end); + arg_end = skiptowhite((char *)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 = skiptowhite((char *)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 +5164,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; + 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; @@ -5212,26 +5196,26 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) } if (curwin->w_s->b_syn_linecont_pat != NULL) { emsg(_("E403: syntax sync: line continuations pattern specified twice")); - finished = TRUE; + 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 = skip_regexp((char *)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); + (char *)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 cpo_save = p_cpo; - p_cpo = ""; + p_cpo = empty_option; curwin->w_s->b_syn_linecont_prog = - vim_regcomp((char *)curwin->w_s->b_syn_linecont_pat, RE_MAGIC); + vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC); p_cpo = cpo_save; syn_clear_time(&curwin->w_s->b_syn_linecont_time); @@ -5241,19 +5225,19 @@ 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) { - syn_cmd_match(eap, TRUE); + syn_cmd_match(eap, true); } else if (STRCMP(key, "REGION") == 0) { - syn_cmd_region(eap, TRUE); + syn_cmd_region(eap, true); } else if (STRCMP(key, "CLEAR") == 0) { - syn_cmd_clear(eap, TRUE); + syn_cmd_clear(eap, true); } else { - illegal = TRUE; + illegal = true; } - finished = TRUE; + finished = true; break; } arg_start = next_arg; @@ -5263,7 +5247,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) semsg(_("E404: Illegal arguments: %s"), arg_start); } else if (!finished) { eap->nextcmd = (char *)check_nextcmd(arg_start); - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } } @@ -5277,7 +5261,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) /// @param list where to store the resulting list, if not NULL, the list is silently skipped! /// /// @return FAIL for some error, OK for success. -static int get_id_list(char_u **const arg, const int keylen, int16_t **const list, const bool skip) +static int get_id_list(char **const arg, const int keylen, int16_t **const list, const bool skip) { char *p = NULL; char *end; @@ -5294,7 +5278,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis // grow when a regexp is used. In that case round 1 is done once again. for (int round = 1; round <= 2; round++) { // skip "contains" - p = skipwhite((char *)(*arg) + keylen); + p = skipwhite(*arg + keylen); if (*p != '=') { semsg(_("E405: Missing equal sign: %s"), *arg); break; @@ -5362,7 +5346,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis break; } - regmatch.rm_ic = TRUE; + regmatch.rm_ic = true; id = 0; for (int i = highlight_num_groups(); --i >= 0;) { if (vim_regexec(®match, (char *)highlight_group_name(i), (colnr_T)0)) { @@ -5401,7 +5385,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 != ',') { @@ -5419,7 +5403,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis } } - *arg = (char_u *)p; + *arg = p; if (failed || retval == NULL) { xfree(retval); return FAIL; @@ -5470,26 +5454,26 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in static int depth = 0; int r; - // If ssp has a "containedin" list and "cur_si" is in it, return TRUE. + // If ssp has a "containedin" list and "cur_si" is in it, return true. if (cur_si != NULL && ssp->cont_in_list != NULL && !(cur_si->si_flags & HL_MATCH)) { // Ignore transparent items without a contains argument. Double check // 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, &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn), SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED)) { - return TRUE; + return true; } } if (list == NULL) { - return FALSE; + return false; } /* @@ -5500,33 +5484,31 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in return !contained; } - /* - * If the first item is "ALLBUT", return TRUE if "id" is NOT in the - * contains list. We also require that "id" is at the same ":syn include" - * level as the list. - */ + // If the first item is "ALLBUT", return true if "id" is NOT in the + // contains list. We also require that "id" is at the same ":syn include" + // level as the list. item = *list; if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) { if (item < SYNID_TOP) { // ALL or ALLBUT: accept all groups in the same file if (item - SYNID_ALLBUT != ssp->inc_tag) { - return FALSE; + return false; } } else if (item < SYNID_CONTAINED) { // TOP: accept all not-contained groups in the same file if (item - SYNID_TOP != ssp->inc_tag || contained) { - return FALSE; + return false; } } else { // CONTAINED: accept all contained groups in the same file if (item - SYNID_CONTAINED != ssp->inc_tag || !contained) { - return FALSE; + return false; } } item = *++list; - retval = FALSE; + retval = false; } else { - retval = TRUE; + retval = true; } /* @@ -5541,9 +5523,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 +5596,7 @@ void ex_syntax(exarg_T *eap) } xfree(subcmd_name); if (eap->skip) { - --emsg_skip; + emsg_skip--; } } @@ -5624,8 +5606,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) @@ -5674,7 +5655,8 @@ static enum { EXP_SUBCMD, // expand ":syn" sub-commands EXP_CASE, // expand ":syn case" arguments EXP_SPELL, // expand ":syn spell" arguments - EXP_SYNC, // expand ":syn sync" arguments + EXP_SYNC, // expand ":syn sync" arguments + EXP_CLUSTER, // expand ":syn list @cluster" arguments } expand_what; /* @@ -5711,10 +5693,10 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) // (part of) subcommand already typed if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); + const char *p = (const char *)skiptowhite(arg); if (*p != NUL) { // Past first word. xp->xp_pattern = skipwhite(p); - if (*skiptowhite((char_u *)xp->xp_pattern) != NUL) { + if (*skiptowhite(xp->xp_pattern) != NUL) { xp->xp_context = EXPAND_NOTHING; } else if (STRNICMP(arg, "case", p - arg) == 0) { expand_what = EXP_CASE; @@ -5722,10 +5704,16 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) expand_what = EXP_SPELL; } else if (STRNICMP(arg, "sync", p - arg) == 0) { expand_what = EXP_SYNC; + } else if (STRNICMP(arg, "list", p - arg) == 0) { + p = skipwhite(p); + if (*p == '@') { + expand_what = EXP_CLUSTER; + } else { + xp->xp_context = EXPAND_HIGHLIGHT; + } } else if (STRNICMP(arg, "keyword", p - arg) == 0 || STRNICMP(arg, "region", p - arg) == 0 - || STRNICMP(arg, "match", p - arg) == 0 - || STRNICMP(arg, "list", p - arg) == 0) { + || STRNICMP(arg, "match", p - arg) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; } else { xp->xp_context = EXPAND_NOTHING; @@ -5759,6 +5747,14 @@ char *get_syntax_name(expand_T *xp, int idx) "maxlines=", "minlines=", "region", NULL }; return sync_args[idx]; } + case EXP_CLUSTER: + if (idx < curwin->w_s->b_syn_clusters.ga_len) { + vim_snprintf(xp->xp_buf, EXPAND_BUF_LEN, "@%s", + SYN_CLSTR(curwin->w_s)[idx].scl_name); + return xp->xp_buf; + } else { + return NULL; + } } return NULL; } @@ -5831,7 +5827,7 @@ int syn_get_stack_item(int i) { if (i >= current_state.ga_len) { // Need to invalidate the state, because we didn't properly finish it - // for the last character, "keep_state" was TRUE. + // for the last character, "keep_state" was true. invalidate_current_state(); current_col = MAXCOL; return -1; @@ -5926,7 +5922,7 @@ static void syntime_clear(void) msg(_(msg_no_items)); return; } - for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) { + for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) { spp = &(SYN_ITEMS(curwin->w_s)[idx]); syn_clear_time(&spp->sp_time); } @@ -5975,7 +5971,7 @@ static void syntime_report(void) proftime_T total_total = profile_zero(); int total_count = 0; time_entry_T *p; - for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) { + for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) { synpat_T *spp = &(SYN_ITEMS(curwin->w_s)[idx]); if (spp->sp_time.count > 0) { p = GA_APPEND_VIA_PTR(time_entry_T, &ga); @@ -6001,7 +5997,7 @@ static void syntime_report(void) msg_puts_title(_(" TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN")); msg_puts("\n"); - for (int idx = 0; idx < ga.ga_len && !got_int; ++idx) { + for (int idx = 0; idx < ga.ga_len && !got_int; idx++) { p = ((time_entry_T *)ga.ga_data) + idx; msg_puts(profile_msg(p->total)); @@ -6032,7 +6028,7 @@ static void syntime_report(void) if (len > (int)STRLEN(p->pattern)) { len = (int)STRLEN(p->pattern); } - msg_outtrans_len(p->pattern, len); + msg_outtrans_len((char *)p->pattern, len); msg_puts("\n"); } ga_clear(&ga); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5b799be381..5270412382 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -13,17 +13,19 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.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" @@ -33,6 +35,7 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -40,7 +43,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" @@ -54,6 +57,7 @@ typedef struct tag_pointers { // filled in by parse_tag_line(): char_u *tagname; // start of tag name (skip "file:") + // char_u *tagname_end; // char after tag name char_u *fname; // first char of file name char_u *fname_end; // char after file name @@ -199,7 +203,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } prev_num_matches = num_matches; - free_string_option(nofile_fname); + free_string_option((char *)nofile_fname); nofile_fname = NULL; clearpos(&saved_fmark.mark); // shutup gcc 4.0 @@ -212,7 +216,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) new_tag = true; if (g_do_tagpreview != 0) { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = vim_strsave(tag); + ptag_entry.tagname = (char *)vim_strsave(tag); } } else { if (g_do_tagpreview != 0) { @@ -236,7 +240,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) cur_fnum = ptag_entry.cur_fnum; } else { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = vim_strsave(tag); + ptag_entry.tagname = (char *)vim_strsave(tag); } } else { /* @@ -258,7 +262,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } // put the tag name in the tag stack - tagstack[tagstackidx].tagname = vim_strsave(tag); + tagstack[tagstackidx].tagname = (char *)vim_strsave(tag); curwin->w_tagstacklen = tagstacklen; @@ -437,9 +441,9 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // When desired match not found yet, try to find it (and others). if (use_tagstack) { - name = tagstack[tagstackidx].tagname; + name = (char_u *)tagstack[tagstackidx].tagname; } else if (g_do_tagpreview != 0) { - name = ptag_entry.tagname; + name = (char_u *)ptag_entry.tagname; } else { name = tag; } @@ -465,7 +469,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; } @@ -480,8 +484,8 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) flags |= TAG_NO_TAGFUNC; } - if (find_tags(name, &new_num_matches, &new_matches, flags, - max_num_matches, buf_ffname) == OK + if (find_tags((char *)name, &new_num_matches, &new_matches, flags, + max_num_matches, (char *)buf_ffname) == OK && new_num_matches < max_num_matches) { max_num_matches = MAXCOL; // If less than max_num_matches // found: all matches found. @@ -584,9 +588,9 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (use_tfu && parse_match((char_u *)matches[cur_match], &tagp2) == OK && tagp2.user_data) { XFREE_CLEAR(tagstack[tagstackidx].user_data); - tagstack[tagstackidx].user_data = vim_strnsave(tagp2.user_data, - (size_t)(tagp2.user_data_end - - tagp2.user_data)); + tagstack[tagstackidx].user_data = (char *)vim_strnsave(tagp2.user_data, + (size_t)(tagp2.user_data_end - + tagp2.user_data)); } tagstackidx++; @@ -653,13 +657,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; } @@ -734,7 +738,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char mt_names[matches[i][0] & MT_MASK]); msg_puts((char *)IObuff); if (tagp.tagkind != NULL) { - msg_outtrans_len(tagp.tagkind, + msg_outtrans_len((char *)tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind)); } msg_advance(13); @@ -748,7 +752,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char // it and put "..." in the middle p = tag_full_fname(&tagp); if (p != NULL) { - msg_outtrans_attr(p, HL_ATTR(HLF_D)); + msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); XFREE_CLEAR(p); } if (msg_col > 0) { @@ -790,7 +794,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } msg_advance(15); } - p = msg_outtrans_one(p, attr); + p = (char_u *)msg_outtrans_one((char *)p, attr); if (*p == TAB) { msg_puts_attr(" ", attr); break; @@ -848,7 +852,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char msg_putchar(' '); p++; } else { - p = msg_outtrans_one(p, 0); + p = (char_u *)msg_outtrans_one((char *)p, 0); } // don't display the "$/;\"" and "$?;\"" @@ -1033,7 +1037,7 @@ void do_tags(exarg_T *eap) // Highlight title msg_puts_title(_("\n # TO tag FROM line in file/text")); - for (i = 0; i < tagstacklen; ++i) { + for (i = 0; i < tagstacklen; i++) { if (tagstack[i].tagname != NULL) { name = fm_getname(&(tagstack[i].fmark), 30); if (name == NULL) { // file name not available @@ -1048,7 +1052,7 @@ void do_tags(exarg_T *eap) tagstack[i].tagname, tagstack[i].fmark.mark.lnum); msg_outtrans((char *)IObuff); - msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum + msg_outtrans_attr((char *)name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); } @@ -1076,9 +1080,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 } @@ -1181,7 +1185,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; - result = call_vim_function((char *)curbuf->b_p_tfu, 3, args, &rettv); + result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); curwin->w_cursor = save_pos; // restore the cursor position d->dv_refcount--; @@ -1320,7 +1324,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; }); @@ -1362,8 +1366,8 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl /// @param matchesp return: array of matches found /// @param mincount MAXCOL: find all matches other: minimal number of matches */ /// @param buf_ffname name of buffer for priority -int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mincount, - char_u *buf_ffname) +int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int mincount, + char *buf_ffname) { FILE *fp; char_u *lbuf; // line buffer @@ -1406,12 +1410,12 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi int cmplen; int match; // matches - int match_no_ic = 0; // matches with rm_ic == FALSE + int match_no_ic = 0; // matches with rm_ic == false int match_re; // match with regexp 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; @@ -1438,7 +1442,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi int help_only = (flags & TAG_HELP); int name_only = (flags & TAG_NAMES); int noic = (flags & TAG_NOIC); - int get_it_again = FALSE; + int get_it_again = false; int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); @@ -1456,17 +1460,17 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi p_ic = false; break; case TC_FOLLOWSCS: - p_ic = ignorecase(pat); + p_ic = ignorecase((char_u *)pat); break; case TC_SMART: - p_ic = ignorecase_opt(pat, true, true); + p_ic = ignorecase_opt((char_u *)pat, true, true); break; default: abort(); } help_save = curbuf->b_help; - orgpat.pat = pat; + orgpat.pat = (char_u *)pat; orgpat.regmatch.regprog = NULL; vimconv.vc_type = CONV_NONE; @@ -1476,7 +1480,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]); } @@ -1500,8 +1504,8 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' && ASCII_ISALPHA(pat[orgpat.len - 2]) && ASCII_ISALPHA(pat[orgpat.len - 1])) { - saved_pat = vim_strnsave(pat, (size_t)orgpat.len - 3); - help_lang_find = &pat[orgpat.len - 2]; + saved_pat = vim_strnsave((char_u *)pat, (size_t)orgpat.len - 3); + help_lang_find = (char_u *)&pat[orgpat.len - 2]; orgpat.pat = saved_pat; orgpat.len -= 3; } @@ -1511,7 +1515,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi } save_emsg_off = emsg_off; - emsg_off = TRUE; // don't want error for invalid RE here + emsg_off = true; // don't want error for invalid RE here prepare_pats(&orgpat, has_re); emsg_off = save_emsg_off; if (has_re && orgpat.regmatch.regprog == NULL) { @@ -1520,12 +1524,12 @@ 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; - retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, - flags, buf_ffname); + retval = find_tagfunc_tags((char_u *)pat, &ga_match[0], &match_count, flags, + (char_u *)buf_ffname); tfu_in_use = false; if (retval != NOTDONE) { goto findtag_end; @@ -1552,12 +1556,12 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi } orgpat.regmatch.rm_ic = ((p_ic || !noic) && (findall || orgpat.headlen == 0 || !p_tbs)); - for (round = 1; round <= 2; ++round) { + for (round = 1; round <= 2; round++) { linear = (orgpat.headlen == 0 || !p_tbs || round == 2); // Try tag file names from tags option one by one. for (first_file = true; - use_cscope || get_tagfname(&tn, first_file, tag_fname) == OK; + use_cscope || get_tagfname(&tn, first_file, (char *)tag_fname) == OK; first_file = false) { // A file that doesn't exist is silently ignored. Only when not a // single file is found, an error message is given (further on). @@ -1598,7 +1602,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi help_pri = 0; } else { help_pri = 1; - for (s = p_hlg; *s != NUL; ++s) { + for (s = p_hlg; *s != NUL; s++) { if (STRNICMP(s, help_lang, 2) == 0) { break; } @@ -1612,7 +1616,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++; } } } @@ -1701,7 +1705,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi eof = vim_fgets(lbuf, lbuf_size, fp); } // skip empty and blank lines - while (!eof && vim_isblankline(lbuf)) { + while (!eof && vim_isblankline((char *)lbuf)) { search_info.curr_offset = vim_ftell(fp); eof = vim_fgets(lbuf, lbuf_size, fp); } @@ -1722,7 +1726,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi eof = use_cscope ? cs_fgets(lbuf, lbuf_size) : vim_fgets(lbuf, lbuf_size, fp); - } while (!eof && vim_isblankline(lbuf)); + } while (!eof && vim_isblankline((char *)lbuf)); if (eof) { break; // end of file @@ -1737,7 +1741,7 @@ line_read_in: // Convert every line. Converting the pattern from 'enc' to // the tags file encoding doesn't work, because characters are // not recognized. - conv_line = string_convert(&vimconv, lbuf, NULL); + conv_line = (char_u *)string_convert(&vimconv, (char *)lbuf, NULL); if (conv_line != NULL) { // Copy or swap lbuf and conv_line. len = (int)STRLEN(conv_line) + 1; @@ -1777,7 +1781,7 @@ line_read_in: // encoding to 'encoding'. for (p = lbuf + 20; *p > ' ' && *p < 127; p++) {} *p = NUL; - convert_setup(&vimconv, lbuf + 20, p_enc); + convert_setup(&vimconv, (char *)lbuf + 20, p_enc); } // Read the next line. Unrecognized flags are ignored. @@ -1860,7 +1864,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) { @@ -2006,7 +2010,7 @@ parse_line: } // if tag length does not match, don't try comparing if (orgpat.len != cmplen) { - match = FALSE; + match = false; } else { if (orgpat.regmatch.rm_ic) { assert(cmplen >= 0); @@ -2023,7 +2027,7 @@ parse_line: /* * Has a regexp: Also find tags matching regexp. */ - match_re = FALSE; + match_re = false; if (!match && orgpat.regmatch.regprog != NULL) { int cc; @@ -2051,7 +2055,8 @@ parse_line: mtt = MT_GL_OTH; } else { // Decide in which array to store this match. - is_current = test_for_current(tagp.fname, tagp.fname_end, tag_fname, + is_current = test_for_current((char *)tagp.fname, (char *)tagp.fname_end, + (char *)tag_fname, buf_ffname); is_static = test_for_static(&tagp); @@ -2088,9 +2093,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 +2127,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 +2145,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 +2171,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 @@ -2234,7 +2238,7 @@ parse_line: if (use_cscope) { break; } - orgpat.regmatch.rm_ic = TRUE; // try another time while ignoring case + orgpat.regmatch.rm_ic = true; // try another time while ignoring case } if (!stop_searching) { @@ -2258,29 +2262,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; } } @@ -2332,17 +2336,17 @@ void free_tag_stuff(void) /// For help files, use "tags" file only. /// /// @param tnp holds status info -/// @param first TRUE when first file name is wanted +/// @param first true when first file name is wanted /// @param buf pointer to buffer of MAXPATHL chars /// /// @return FAIL if no more tag file names, OK otherwise. -int get_tagfname(tagname_T *tnp, int first, char_u *buf) +int get_tagfname(tagname_T *tnp, int first, char *buf) { - char_u *fname = NULL; - char_u *r_ptr; + char *fname = NULL; + char *r_ptr; if (first) { - memset(tnp, 0, sizeof(tagname_T)); + CLEAR_POINTER(tnp); } if (curbuf->b_help) { @@ -2353,7 +2357,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); } @@ -2364,22 +2368,21 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) if (tnp->tn_hf_idx > tag_fnames.ga_len || *p_hf == NUL) { return FAIL; } - ++tnp->tn_hf_idx; + tnp->tn_hf_idx++; STRCPY(buf, p_hf); STRCPY(path_tail((char *)buf), "tags"); #ifdef BACKSLASH_IN_FILENAME slash_adjust(buf); #endif - simplify_filename(buf); + simplify_filename((char_u *)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,25 +2390,24 @@ 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) ? (char_u *)curbuf->b_p_tags : p_tags); + tnp->tn_np = (char *)tnp->tn_tags; } /* * Loop until we have found a file name that can be used. * There are two states: - * tnp->tn_did_filefind_init == FALSE: setup for next part in 'tags'. - * tnp->tn_did_filefind_init == TRUE: find next file in this part. + * tnp->tn_did_filefind_init == false: setup for next part in 'tags'. + * tnp->tn_did_filefind_init == true: find next file in this part. */ for (;;) { if (tnp->tn_did_filefind_init) { - fname = vim_findfile(tnp->tn_search_ctx); + fname = (char *)vim_findfile(tnp->tn_search_ctx); if (fname != NULL) { break; } - tnp->tn_did_filefind_init = FALSE; + tnp->tn_did_filefind_init = false; } else { char_u *filename = NULL; @@ -2420,22 +2422,22 @@ 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, buf, MAXPATHL - 1, " ,"); - r_ptr = vim_findfile_stopdir(buf); + r_ptr = (char *)vim_findfile_stopdir((char_u *)buf); // move the filename one char forward and truncate the // filepath with a NUL - filename = (char_u *)path_tail((char *)buf); + filename = (char_u *)path_tail(buf); STRMOVE(filename + 1, filename); *filename++ = NUL; - tnp->tn_search_ctx = vim_findfile_init(buf, filename, - r_ptr, 100, - FALSE, // don't free visited list + tnp->tn_search_ctx = vim_findfile_init((char_u *)buf, filename, + (char_u *)r_ptr, 100, + false, // don't free visited list FINDFILE_FILE, // we search for a file tnp->tn_search_ctx, true, (char_u *)curbuf->b_ffname); if (tnp->tn_search_ctx != NULL) { - tnp->tn_did_filefind_init = TRUE; + tnp->tn_did_filefind_init = true; } } } @@ -2459,7 +2461,7 @@ void tagname_free(tagname_T *tnp) /// Parse one line from the tags file. Find start/end of tag name, start/end of /// file name and start of search pattern. /// -/// If is_etag is TRUE, tagp->fname and tagp->fname_end are not set. +/// If is_etag is true, tagp->fname and tagp->fname_end are not set. /// /// @param lbuf line to be parsed /// @@ -2478,7 +2480,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 +2491,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; @@ -2510,8 +2512,8 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) * Static tags produced by the new ctags program have the format: * 'tag file /pattern/;"<Tab>file:' " * - * Return TRUE if it is a static tag and adjust *tagname to the real tag. - * Return FALSE if it is not a static tag. + * Return true if it is a static tag and adjust *tagname to the real tag. + * Return false if it is not a static tag. */ static bool test_for_static(tagptrs_T *tagp) { @@ -2522,11 +2524,11 @@ static bool test_for_static(tagptrs_T *tagp) while ((p = (char_u *)vim_strchr((char *)p, '\t')) != NULL) { p++; if (STRNCMP(p, "file:", 5) == 0) { - return TRUE; + return true; } } - return FALSE; + return false; } // Returns the length of a matching tag line. @@ -2640,7 +2642,7 @@ static char_u *tag_full_fname(tagptrs_T *tagp) /// /// @param lbuf_arg line from the tags file for this tag /// @param forceit :ta with ! -/// @param keep_help keep help flag (FALSE for cscope) +/// @param keep_help keep help flag (false for cscope) /// /// @return OK for success, NOTAGFILE when file not found, FAIL otherwise. static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) @@ -2720,7 +2722,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 @@ -2732,7 +2734,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) * into a fullpath */ if (!curwin->w_p_pvw) { - full_fname = (char_u *)FullName_save((char *)fname, FALSE); + full_fname = (char_u *)FullName_save((char *)fname, false); fname = full_fname; /* @@ -2825,15 +2827,15 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) */ str = pbuf; if (pbuf[0] == '/' || pbuf[0] == '?') { - str = skip_regexp(pbuf + 1, pbuf[0], FALSE, NULL) + 1; + str = (char_u *)skip_regexp((char *)pbuf + 1, pbuf[0], false, NULL) + 1; } if (str > pbuf_end - 1) { // search command with nothing following save_p_ws = p_ws; save_p_ic = p_ic; save_p_scs = p_scs; p_ws = true; // need 'wrapscan' for backward searches - p_ic = FALSE; // don't ignore case now - p_scs = FALSE; + p_ic = false; // don't ignore case now + p_scs = false; save_lnum = curwin->w_cursor.lnum; if (tagp.tagline > 0) { // start search before line from "line:" field @@ -2944,7 +2946,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } @@ -3011,27 +3013,25 @@ static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bo return retval; } -/* - * Check if we have a tag for the buffer with name "buf_ffname". - * This is a bit slow, because of the full path compare in path_full_compare(). - * Return TRUE if tag for file "fname" if tag file "tag_fname" is for current - * file. - */ -static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, char_u *buf_ffname) +/// Check if we have a tag for the buffer with name "buf_ffname". +/// This is a bit slow, because of the full path compare in path_full_compare(). +/// +/// @return true if tag for file "fname" if tag file "tag_fname" is for current +/// file. +static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname) { int c; - int retval = FALSE; - char_u *fullname; + int retval = false; if (buf_ffname != NULL) { // if the buffer has a name { - c = *fname_end; + c = (unsigned char)(*fname_end); *fname_end = NUL; } - fullname = expand_tag_fname(fname, tag_fname, true); - retval = (path_full_compare((char *)fullname, (char *)buf_ffname, true, true) & kEqualFiles); + char *fullname = (char *)expand_tag_fname((char_u *)fname, (char_u *)tag_fname, true); + retval = (path_full_compare(fullname, buf_ffname, true, true) & kEqualFiles); xfree(fullname); - *fname_end = (char_u)c; + *fname_end = (char)c; } return retval; @@ -3051,7 +3051,7 @@ static int find_extra(char_u **pp) if (ascii_isdigit(*str)) { str = (char_u *)skipdigits((char *)str + 1); } else if (*str == '/' || *str == '?') { - str = skip_regexp(str + 1, *str, false, NULL); + str = (char_u *)skip_regexp((char *)str + 1, *str, false, NULL); if (*str != first_char) { str = NULL; } else { @@ -3107,13 +3107,13 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) extra_flag = 0; } if (pat[0] == '/') { - ret = find_tags(pat + 1, num_file, file, + ret = find_tags((char *)pat + 1, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC, - TAG_MANY, (char_u *)curbuf->b_ffname); + TAG_MANY, curbuf->b_ffname); } else { - ret = find_tags(pat, num_file, file, + ret = find_tags((char *)pat, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC, - TAG_MANY, (char_u *)curbuf->b_ffname); + TAG_MANY, curbuf->b_ffname); } if (ret == OK && !tagnames) { // Reorganize the tags for display and matching as strings of: @@ -3150,8 +3150,7 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) /// /// @param start start of the value /// @param end after the value; can be NULL -static int add_tag_field(dict_T *dict, const char *field_name, const char_u *start, - const char_u *end) +static int add_tag_field(dict_T *dict, const char *field_name, const char *start, const char *end) FUNC_ATTR_NONNULL_ARG(1, 2) { int len = 0; @@ -3171,7 +3170,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); @@ -3198,8 +3197,8 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) tagptrs_T tp; bool is_static; - ret = find_tags(pat, &num_matches, &matches, - TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); + ret = find_tags((char *)pat, &num_matches, &matches, + TAG_REGEXP | TAG_NOIC, MAXCOL, (char *)buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; i++) { int parse_result = parse_match((char_u *)matches[i], &tp); @@ -3220,11 +3219,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) tv_list_append_dict(list, dict); full_fname = tag_full_fname(&tp); - if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL - || add_tag_field(dict, "filename", full_fname, NULL) == FAIL - || add_tag_field(dict, "cmd", tp.command, tp.command_end) == FAIL - || add_tag_field(dict, "kind", tp.tagkind, - tp.tagkind ? tp.tagkind_end : NULL) == FAIL + if (add_tag_field(dict, "name", (char *)tp.tagname, (char *)tp.tagname_end) == FAIL + || add_tag_field(dict, "filename", (char *)full_fname, NULL) == FAIL + || add_tag_field(dict, "cmd", (char *)tp.command, (char *)tp.command_end) == FAIL + || add_tag_field(dict, "kind", (char *)tp.tagkind, + tp.tagkind ? (char *)tp.tagkind_end : NULL) == FAIL || tv_dict_add_nr(dict, S_LEN("static"), is_static) == FAIL) { ret = FAIL; } @@ -3250,23 +3249,23 @@ 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) { + if (add_tag_field(dict, (char *)n, (char *)s, (char *)p) == FAIL) { ret = FAIL; } n[len] = ':'; } else { // Skip field without colon. while (*p != NUL && *p >= ' ') { - ++p; + p++; } } if (*p == NUL) { @@ -3365,7 +3364,7 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur } wp->w_tagstacklen++; - tagstack[idx].tagname = tagname; + tagstack[idx].tagname = (char *)tagname; tagstack[idx].cur_fnum = cur_fnum; tagstack[idx].cur_match = cur_match; if (tagstack[idx].cur_match < 0) { @@ -3373,7 +3372,7 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur } tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; - tagstack[idx].user_data = user_data; + tagstack[idx].user_data = (char *)user_data; } // Add a list of items to the tag stack in the specified window 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..90966bcfad 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" @@ -68,8 +69,8 @@ #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/terminal.h" #include "nvim/ui.h" @@ -417,7 +418,7 @@ bool terminal_enter(void) // placed at end of buffer to "follow" output. #11072 handle_T save_curwin = curwin->handle; bool save_w_p_cul = curwin->w_p_cul; - char_u *save_w_p_culopt = NULL; + char *save_w_p_culopt = NULL; char_u save_w_p_culopt_flags = curwin->w_p_culopt_flags; int save_w_p_cuc = curwin->w_p_cuc; long save_w_p_so = curwin->w_p_so; @@ -425,7 +426,7 @@ bool terminal_enter(void) if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) { if (STRCMP(curwin->w_p_culopt, "number")) { save_w_p_culopt = curwin->w_p_culopt; - curwin->w_p_culopt = (char_u *)xstrdup("number"); + curwin->w_p_culopt = xstrdup("number"); } curwin->w_p_culopt_flags = CULOPT_NBR; } else { @@ -681,7 +682,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 +703,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); @@ -1374,7 +1375,7 @@ static bool send_mouse_event(Terminal *term, int c) curwin->w_redr_status = true; curwin = save_curwin; curbuf = curwin->w_buffer; - redraw_later(mouse_win, NOT_VALID); + redraw_later(mouse_win, UPD_NOT_VALID); invalidate_terminal(term, -1, -1); // Only need to exit focus if the scrolled window is the terminal window return mouse_win == curwin; diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6b16e888a9..ce23141c7a 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -114,6 +114,13 @@ if has('win32') let $PROMPT = '$P$G' endif +if has('mac') + " In MacOS, when starting a shell in a terminal, a bash deprecation warning + " message is displayed. This breaks the terminal test. Disable the warning + " message. + let $BASH_SILENCE_DEPRECATION_WARNING = 1 +endif + " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -257,6 +264,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 @@ -417,7 +425,7 @@ for s:test in sort(s:tests) set belloff=all let prev_error = '' let total_errors = [] - let run_nr = 1 + let g:run_nr = 1 " A test can set g:test_is_flaky to retry running the test. let g:test_is_flaky = 0 @@ -436,10 +444,10 @@ for s:test in sort(s:tests) call add(s:messages, 'Found errors in ' . s:test . ':') call extend(s:messages, v:errors) - call add(total_errors, 'Run ' . run_nr . ':') + call add(total_errors, 'Run ' . g:run_nr . ':') call extend(total_errors, v:errors) - if run_nr == 5 || prev_error == v:errors[0] + if g:run_nr >= 5 || prev_error == v:errors[0] call add(total_errors, 'Flaky test failed too often, giving up') let v:errors = total_errors break @@ -454,7 +462,7 @@ for s:test in sort(s:tests) let prev_error = v:errors[0] let v:errors = [] - let run_nr += 1 + let g:run_nr += 1 call RunTheTest(s:test) diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 6bc3607b69..472ed4ca14 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -5,7 +5,7 @@ if exists('s:did_load') set directory& set directory^=. set display= - set fillchars=vert:\|,fold:- + set fillchars=vert:\|,foldsep:\|,fold:- set formatoptions=tcq set fsync set laststatus=1 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..3064b199d9 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -341,6 +341,39 @@ func Test_WinScrolled_close_curwin() call delete('Xtestout') endfunc +func Test_WinScrolled_long_wrapped() + CheckRunVimInTerminal + + let lines =<< trim END + set scrolloff=0 + let height = winheight(0) + let width = winwidth(0) + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + call setline(1, repeat('foo', height * width)) + call cursor(1, height * width) + END + call writefile(lines, 'Xtest_winscrolled_long_wrapped') + let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6}) + + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, 'gj') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^1 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, '0') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, '$') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000) + + call delete('Xtest_winscrolled_long_wrapped') +endfunc + func Test_WinClosed() " Test that the pattern is matched against the closed window's ID, and both " <amatch> and <afile> are set to it. @@ -493,6 +526,26 @@ func Test_BufReadCmdHelpJump() au! BufReadCmd endfunc +" BufReadCmd is triggered for a "nofile" buffer. Check all values. +func Test_BufReadCmdNofile() + for val in ['nofile', + \ 'nowrite', + \ 'acwrite', + \ 'quickfix', + \ 'help', + \ 'prompt', + \ ] + new somefile + exe 'set buftype=' .. val + au BufReadCmd somefile call setline(1, 'triggered') + edit + call assert_equal('triggered', getline(1)) + + au! BufReadCmd + bwipe! + endfor +endfunc + func Test_augroup_deleted() " This caused a crash before E936 was introduced augroup x @@ -587,9 +640,26 @@ func Test_BufEnter() " On MS-Windows we can't edit the directory, make sure we wipe the right " buffer. bwipe! Xdir - call delete('Xdir', 'd') au! BufEnter + + " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter + " for historic reasons. Also test other 'buftype' values. + for val in ['nofile', + \ 'nowrite', + \ 'acwrite', + \ 'quickfix', + \ 'help', + \ 'prompt', + \ ] + new somefile + exe 'set buftype=' .. val + au BufEnter somefile call setline(1, 'some text') + edit + call assert_equal('some text', getline(1)) + bwipe! + au! BufEnter + endfor endfunc " Closing a window might cause an endless loop @@ -1766,6 +1836,21 @@ func Test_BufReadCmd() au! BufWriteCmd endfunc +func Test_BufWriteCmd() + autocmd BufWriteCmd Xbufwritecmd let g:written = 1 + new + file Xbufwritecmd + set buftype=acwrite + call mkdir('Xbufwritecmd') + write + " BufWriteCmd should be triggered even if a directory has the same name + call assert_equal(1, g:written) + call delete('Xbufwritecmd', 'd') + unlet g:written + au! BufWriteCmd + bwipe! +endfunc + func SetChangeMarks(start, end) exe a:start .. 'mark [' exe a:end .. 'mark ]' @@ -2614,6 +2699,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() @@ -2702,6 +2809,30 @@ func Test_autocmd_FileReadCmd() delfunc ReadFileCmd endfunc +" Test for passing invalid arguments to autocmd +func Test_autocmd_invalid_args() + " Additional character after * for event + call assert_fails('autocmd *a Xfile set ff=unix', 'E215:') + augroup Test + augroup END + " Invalid autocmd event + call assert_fails('autocmd Bufabc Xfile set ft=vim', 'E216:') + " Invalid autocmd event in a autocmd group + call assert_fails('autocmd Test Bufabc Xfile set ft=vim', 'E216:') + augroup! Test + " Execute all autocmds + call assert_fails('doautocmd * BufEnter', 'E217:') + call assert_fails('augroup! x1a2b3', 'E367:') + call assert_fails('autocmd BufNew <buffer=999> pwd', 'E680:') +endfunc + +" Test for deep nesting of autocmds +func Test_autocmd_deep_nesting() + autocmd BufEnter Xfile doautocmd BufEnter Xfile + call assert_fails('doautocmd BufEnter Xfile', 'E218:') + autocmd! BufEnter Xfile +endfunc + " Tests for SigUSR1 autocmd event, which is only available on posix systems. func Test_autocmd_sigusr1() CheckUnix @@ -2715,6 +2846,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_blob.vim b/src/nvim/testdir/test_blob.vim index af42b3857d..70529c14d5 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -294,7 +294,7 @@ func Test_blob_index() call assert_equal(2, index(0zDEADBEEF, 0xBE)) call assert_equal(-1, index(0zDEADBEEF, 0)) call assert_equal(2, index(0z11111111, 0x11, 2)) - call assert_equal(3, index(0z11110111, 0x11, 2)) + call assert_equal(3, 0z11110111->index(0x11, 2)) call assert_equal(2, index(0z11111111, 0x11, -2)) call assert_equal(3, index(0z11110111, 0x11, -2)) diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 67be3e6747..4def3b5df9 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -154,6 +154,24 @@ func Test_bdelete_cmd() set nobuflisted enew call assert_fails('bdelete ' .. bnr, 'E516:') + + " Deleting more than one buffer + new Xbuf1 + new Xbuf2 + exe 'bdel ' .. bufnr('Xbuf2') .. ' ' .. bufnr('Xbuf1') + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xbuf1')[0].loaded) + call assert_equal(0, getbufinfo('Xbuf2')[0].loaded) + + " Deleting more than one buffer and an invalid buffer + new Xbuf1 + new Xbuf2 + let cmd = "exe 'bdel ' .. bufnr('Xbuf2') .. ' xxx ' .. bufnr('Xbuf1')" + call assert_fails(cmd, 'E94:') + call assert_equal(2, winnr('$')) + call assert_equal(1, getbufinfo('Xbuf1')[0].loaded) + call assert_equal(0, getbufinfo('Xbuf2')[0].loaded) + %bwipe! endfunc @@ -168,6 +186,194 @@ func Test_buffer_error() %bwipe endfunc +" Test for the status messages displayed when unloading, deleting or wiping +" out buffers +func Test_buffer_statusmsg() + CheckEnglish + set report=1 + new Xbuf1 + new Xbuf2 + let bnr = bufnr() + exe "normal 2\<C-G>" + call assert_match('buf ' .. bnr .. ':', v:statusmsg) + bunload Xbuf1 Xbuf2 + call assert_equal('2 buffers unloaded', v:statusmsg) + bdel Xbuf1 Xbuf2 + call assert_equal('2 buffers deleted', v:statusmsg) + bwipe Xbuf1 Xbuf2 + call assert_equal('2 buffers wiped out', v:statusmsg) + set report& +endfunc + +" Test for quitting the 'swapfile exists' dialog with the split buffer +" command. +func Test_buffer_sbuf_cleanup() + call writefile([], 'Xfile') + " first open the file in a buffer + new Xfile + let bnr = bufnr() + close + " create the swap file + call writefile([], '.Xfile.swp') + " Remove the catch-all that runtest.vim adds + au! SwapExists + augroup BufTest + au! + autocmd SwapExists Xfile let v:swapchoice='q' + augroup END + exe 'sbuf ' . bnr + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + + " test for :sball + sball + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + + %bw! + set shortmess+=F + let v:statusmsg = '' + edit Xfile + call assert_equal('', v:statusmsg) + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + set shortmess& + + call delete('Xfile') + call delete('.Xfile.swp') + augroup BufTest + au! + augroup END + augroup! BufTest +endfunc + +" Test for deleting a modified buffer with :confirm +func Test_bdel_with_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/buffer_spec.lua' + CheckUnix + CheckNotGui + CheckFeature dialog_con + new + call setline(1, 'test') + call assert_fails('bdel', 'E89:') + call feedkeys('c', 'L') + confirm bdel + call assert_equal(2, winnr('$')) + call assert_equal(1, &modified) + call feedkeys('n', 'L') + confirm bdel + call assert_equal(1, winnr('$')) +endfunc + +" Test for editing another buffer from a modified buffer with :confirm +func Test_goto_buf_with_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/buffer_spec.lua' + CheckUnix + CheckNotGui + CheckFeature dialog_con + new Xfile + enew + call setline(1, 'test') + call assert_fails('b Xfile', 'E37:') + call feedkeys('c', 'L') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call feedkeys('y', 'L') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call feedkeys('n', 'L') + confirm b Xfile + call assert_equal('Xfile', @%) + close! +endfunc + +" Test for splitting buffer with 'switchbuf' +func Test_buffer_switchbuf() + new Xfile + wincmd w + set switchbuf=useopen + sbuf Xfile + call assert_equal(1, winnr()) + call assert_equal(2, winnr('$')) + set switchbuf=usetab + tabnew + sbuf Xfile + call assert_equal(1, tabpagenr()) + call assert_equal(2, tabpagenr('$')) + set switchbuf& + %bw +endfunc + +" Test for BufAdd autocommand wiping out the buffer +func Test_bufadd_autocmd_bwipe() + %bw! + augroup BufAdd_Wipe + au! + autocmd BufAdd Xfile %bw! + augroup END + edit Xfile + call assert_equal('', @%) + call assert_equal(0, bufexists('Xfile')) + augroup BufAdd_Wipe + au! + augroup END + augroup! BufAdd_Wipe +endfunc + +" Test for trying to load a buffer with text locked +" <C-\>e in the command line is used to lock the text +func Test_load_buf_with_text_locked() + new Xfile1 + edit Xfile2 + let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + %bw! +endfunc + +" Test for using CTRL-^ to edit the alternative file keeping the cursor +" position with 'nostartofline'. Also test using the 'buf' command. +func Test_buffer_edit_altfile() + call writefile(repeat(['one two'], 50), 'Xfile1') + call writefile(repeat(['five six'], 50), 'Xfile2') + set nosol + edit Xfile1 + call cursor(25, 5) + edit Xfile2 + call cursor(30, 4) + exe "normal \<C-^>" + call assert_equal([0, 25, 5, 0], getpos('.')) + exe "normal \<C-^>" + call assert_equal([0, 30, 4, 0], getpos('.')) + buf Xfile1 + call assert_equal([0, 25, 5, 0], getpos('.')) + buf Xfile2 + call assert_equal([0, 30, 4, 0], getpos('.')) + set sol& + call delete('Xfile1') + call delete('Xfile2') +endfunc + +" Test for running the :sball command with a maximum window count and a +" modified buffer +func Test_sball_with_count() + %bw! + edit Xfile1 + call setline(1, ['abc']) + new Xfile2 + new Xfile3 + new Xfile4 + 2sball + call assert_equal(bufnr('Xfile4'), winbufnr(1)) + call assert_equal(bufnr('Xfile1'), winbufnr(2)) + call assert_equal(0, getbufinfo('Xfile2')[0].loaded) + call assert_equal(0, getbufinfo('Xfile3')[0].loaded) + %bw! +endfunc + func Test_badd_options() new SomeNewBuffer setlocal numberwidth=3 diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index ffb8e3facd..3b5bcbce89 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, ['x']->setbufline(bufnr('$') + 1, 1)) call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) call assert_equal(['d'], getbufline(b, 4)) @@ -187,4 +187,24 @@ func Test_deletebufline_select_mode() bwipe! endfunc +func Test_setbufline_startup_nofile() + let before =<< trim [CODE] + set shortmess+=F + file Xresult + set buftype=nofile + call setbufline('', 1, 'success') + [CODE] + let after =<< trim [CODE] + set buftype= + write + quit + [CODE] + + if !RunVim(before, after, '--clean') + return + endif + call assert_equal(['success'], readfile('Xresult')) + call delete('Xresult') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index a1e53df774..d6d44d1901 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -113,7 +113,7 @@ func Test_chdir_func() call assert_equal('z', fnamemodify(3->getcwd(2), ':t')) tabnext | wincmd t call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) - call chdir('..') + eval '..'->chdir() call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index edf36b413b..66ee776a90 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -2,6 +2,11 @@ source check.vim CheckFeature job + +if !has('clientserver') + call assert_fails('call remote_startserver("local")', 'E942:') +endif + CheckFeature clientserver source shared.vim @@ -66,8 +71,9 @@ func Test_client_server() call remote_send(name, ":gui -f\<CR>") endif " Wait for the server to be up and answering requests. - sleep 100m - call WaitForAssert({-> assert_true(name->remote_expr("v:version", "", 1) != "")}) + " When using valgrind this can be very, very slow. + sleep 1 + call WaitForAssert({-> assert_match('\d', name->remote_expr("v:version", "", 1))}, 10000) call remote_send(name, ":let testvar = 'maybe'\<CR>") call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))}) @@ -178,6 +184,7 @@ func Test_client_server() call assert_fails("let x = remote_peek([])", 'E730:') call assert_fails("let x = remote_read('vim10')", 'E277:') + call assert_fails("call server2client('abc', 'xyz')", 'E258:') endfunc " Uncomment this line to get a debugging log diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 7aac731709..4bfd22cb6c 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -3,6 +3,7 @@ source check.vim source screendump.vim source view_util.vim +source shared.vim func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') @@ -126,6 +127,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 @@ -209,7 +244,7 @@ func Test_match_completion() return endif hi Aardig ctermfg=green - call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt') + call feedkeys(":match A\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"match Aardig', getreg(':')) call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"match none', getreg(':')) @@ -220,9 +255,7 @@ func Test_highlight_completion() return endif hi Aardig ctermfg=green - call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"hi Aardig', getreg(':')) - call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt') + call feedkeys(":hi default A\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"hi default Aardig', getreg(':')) call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"hi clear Aardig', getreg(':')) @@ -480,6 +513,7 @@ func Test_getcompletion() call delete('Xtags') set tags& + call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:') call assert_fails('call getcompletion("", "burp")', 'E475:') call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc @@ -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') @@ -1212,6 +1260,16 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc +func Test_cmdwin_bug() + let winid = win_getid() + sp + try + call feedkeys("q::call win_gotoid(" .. winid .. ")\<CR>:q\<CR>", 'x!') + catch /^Vim\%((\a\+)\)\=:E11/ + endtry + bw! +endfunc + func Test_cmdwin_restore() CheckScreendump @@ -1392,14 +1450,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 +1462,48 @@ func Test_cmdwin_tabpage() tabclose! endfunc +func Test_cmdwin_interrupted() + CheckScreendump + + " aborting the :smile output caused the cmdline window to use the current + " buffer. + let lines =<< trim [SCRIPT] + au WinNew * smile + [SCRIPT] + call writefile(lines, 'XTest_cmdwin') + + let buf = RunVimInTerminal('-S XTest_cmdwin', {'rows': 18}) + " open cmdwin + call term_sendkeys(buf, "q:") + call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 18))}) + " quit more prompt for :smile command + call term_sendkeys(buf, "q") + call WaitForAssert({-> assert_match('^$', term_getline(buf, 18))}) + " execute a simple command + call term_sendkeys(buf, "aecho 'done'\<CR>") + call VerifyScreenDump(buf, 'Test_cmdwin_interrupted', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_cmdwin') +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 @@ -1758,6 +1845,36 @@ func Test_read_shellcmd() endif endfunc +" Test for going up and down the directory tree using 'wildmenu' +func Test_wildmenu_dirstack() + CheckUnix + %bw! + call mkdir('Xdir1/dir2/dir3', 'p') + call writefile([], 'Xdir1/file1_1.txt') + call writefile([], 'Xdir1/file1_2.txt') + call writefile([], 'Xdir1/dir2/file2_1.txt') + call writefile([], 'Xdir1/dir2/file2_2.txt') + call writefile([], 'Xdir1/dir2/dir3/file3_1.txt') + call writefile([], 'Xdir1/dir2/dir3/file3_2.txt') + cd Xdir1/dir2/dir3 + set wildmenu + + call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e file3_1.txt', @:) + call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../dir3/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir2/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir2/dir3/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir2/dir3/file3_1.txt', @:) + + cd - + call delete('Xdir1', 'rf') + set wildmenu& +endfunc + " Test for recalling newer or older cmdline from history with <Up>, <Down>, " <S-Up>, <S-Down>, <PageUp>, <PageDown>, <C-p>, or <C-n>. func Test_recalling_cmdline() @@ -1806,6 +1923,199 @@ func Test_recalling_cmdline() cunmap <Plug>(save-cmdline) endfunc +" Test for using a popup menu for the command line completion matches +" (wildoptions=pum) +func Test_wildmenu_pum() + CheckRunVimInTerminal + + let commands =<< trim [CODE] + set wildmenu + set wildoptions=pum + set shm+=I + set noruler + set noshowcmd + [CODE] + call writefile(commands, 'Xtest') + + let buf = RunVimInTerminal('-S Xtest', #{rows: 10}) + + call term_sendkeys(buf, ":sign \<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_01', {}) + + call term_sendkeys(buf, "\<Down>\<Down>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {}) + + call term_sendkeys(buf, "\<C-N>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {}) + + call term_sendkeys(buf, "\<C-P>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {}) + + call term_sendkeys(buf, "\<Up>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_05', {}) + + " pressing <C-E> should end completion and go back to the original match + call term_sendkeys(buf, "\<C-E>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_06', {}) + + " pressing <C-Y> should select the current match and end completion + call term_sendkeys(buf, "\<Tab>\<C-P>\<C-P>\<C-Y>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_07', {}) + + " With 'wildmode' set to 'longest,full', completing a match should display + " the longest match, the wildmenu should not be displayed. + call term_sendkeys(buf, ":\<C-U>set wildmode=longest,full\<CR>") + call TermWait(buf) + call term_sendkeys(buf, ":sign u\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_08', {}) + + " pressing <Tab> should display the wildmenu + call term_sendkeys(buf, "\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {}) + + " pressing <Tab> second time should select the next entry in the menu + call term_sendkeys(buf, "\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {}) + + call term_sendkeys(buf, ":\<C-U>set wildmode=full\<CR>") + " " showing popup menu in different columns in the cmdline + call term_sendkeys(buf, ":sign define \<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_11', {}) + + call term_sendkeys(buf, " \<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_12', {}) + + call term_sendkeys(buf, " \<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {}) + + " Directory name completion + call mkdir('Xdir/XdirA/XdirB', 'p') + call writefile([], 'Xdir/XfileA') + call writefile([], 'Xdir/XdirA/XfileB') + call writefile([], 'Xdir/XdirA/XdirB/XfileC') + + call term_sendkeys(buf, "\<C-U>e Xdi\<Tab>\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {}) + + " Pressing <Right> on a directory name should go into that directory + call term_sendkeys(buf, "\<Right>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_15', {}) + + " Pressing <Left> on a directory name should go to the parent directory + call term_sendkeys(buf, "\<Left>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_16', {}) + + " Pressing <C-A> when the popup menu is displayed should list all the + " matches and remove the popup menu + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-A>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_17', {}) + + " Pressing <C-D> when the popup menu is displayed should remove the popup + " menu + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-D>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_18', {}) + + " Pressing <S-Tab> should open the popup menu with the last entry selected + call term_sendkeys(buf, "\<C-U>\<CR>:sign \<S-Tab>\<C-P>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_19', {}) + + " Pressing <Esc> should close the popup menu and cancel the cmd line + call term_sendkeys(buf, "\<C-U>\<CR>:sign \<Tab>\<Esc>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_20', {}) + + " Typing a character when the popup is open, should close the popup + call term_sendkeys(buf, ":sign \<Tab>x") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_21', {}) + + " When the popup is open, entering the cmdline window should close the popup + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-F>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_22', {}) + call term_sendkeys(buf, ":q\<CR>") + + " After the last popup menu item, <C-N> should show the original string + call term_sendkeys(buf, ":sign u\<Tab>\<C-N>\<C-N>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_23', {}) + + " Use the popup menu for the command name + call term_sendkeys(buf, "\<C-U>bu\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_24', {}) + + " Pressing the left arrow should remove the popup menu + call term_sendkeys(buf, "\<Left>\<Left>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_25', {}) + + " Pressing <BS> should remove the popup menu and erase the last character + call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<BS>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_26', {}) + + " Pressing <C-W> should remove the popup menu and erase the previous word + call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-W>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_27', {}) + + " Pressing <C-U> should remove the popup menu and erase the entire line + call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-U>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_28', {}) + + " Using <C-E> to cancel the popup menu and then pressing <Up> should recall + " the cmdline from history + call term_sendkeys(buf, "sign xyz\<Esc>:sign \<Tab>\<C-E>\<Up>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {}) + + " Check "list" still works + call term_sendkeys(buf, "\<C-U>set wildmode=longest,list\<CR>") + call term_sendkeys(buf, ":cn\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {}) + call term_sendkeys(buf, "s") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {}) + + " Tests a directory name contained full-width characters. + call mkdir('Xdir/ã‚ã„ã†', 'p') + call writefile([], 'Xdir/ã‚ã„ã†/abc') + call writefile([], 'Xdir/ã‚ã„ã†/xyz') + call writefile([], 'Xdir/ã‚ã„ã†/123') + + call term_sendkeys(buf, "\<C-U>set wildmode&\<CR>") + call term_sendkeys(buf, ":\<C-U>e Xdir/ã‚ã„ã†/\<Tab>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_32', {}) + + call term_sendkeys(buf, "\<C-U>\<CR>") + call StopVimInTerminal(buf) + call delete('Xtest') + call delete('Xdir', 'rf') +endfunc + " this was going over the end of IObuff func Test_report_error_with_composing() let caught = 'no' @@ -1875,4 +2185,67 @@ func Test_cmdline_redraw_tabline() call delete('Xcmdline_redraw_tabline') endfunc +func Test_wildmenu_pum_disable_while_shown() + set wildoptions=pum + set wildmenu + cnoremap <F2> <Cmd>set nowildmenu<CR> + call feedkeys(":sign \<Tab>\<F2>\<Esc>", 'tx') + call assert_equal(0, pumvisible()) + cunmap <F2> + set wildoptions& wildmenu& +endfunc + +func Test_setcmdline() + func SetText(text, pos) + autocmd CmdlineChanged * let g:cmdtype = expand('<afile>') + call assert_equal(0, setcmdline(a:text)) + call assert_equal(a:text, getcmdline()) + call assert_equal(len(a:text) + 1, getcmdpos()) + call assert_equal(getcmdtype(), g:cmdtype) + unlet g:cmdtype + autocmd! CmdlineChanged + + call assert_equal(0, setcmdline(a:text, a:pos)) + call assert_equal(a:text, getcmdline()) + call assert_equal(a:pos, getcmdpos()) + + call assert_fails('call setcmdline("' .. a:text .. '", -1)', 'E487:') + call assert_fails('call setcmdline({}, 0)', 'E928:') + call assert_fails('call setcmdline("' .. a:text .. '", {})', 'E728:') + + return '' + endfunc + + call feedkeys(":\<C-R>=SetText('set rtp?', 2)\<CR>\<CR>", 'xt') + call assert_equal('set rtp?', @:) + + call feedkeys(":let g:str = input('? ')\<CR>", 't') + call feedkeys("\<C-R>=SetText('foo', 4)\<CR>\<CR>", 'xt') + call assert_equal('foo', g:str) + unlet g:str + + delfunc SetText + + " setcmdline() returns 1 when not editing the command line. + call assert_equal(1, 'foo'->setcmdline()) + + " Called in custom function + func CustomComplete(A, L, P) + call assert_equal(0, setcmdline("DoCmd ")) + return "January\nFebruary\nMars\n" + endfunc + + com! -nargs=* -complete=custom,CustomComplete DoCmd : + call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd January February Mars', @:) + delcom DoCmd + delfunc CustomComplete + + " Called in <expr> + cnoremap <expr>a setcmdline('let foo=') + call feedkeys(":a\<CR>", 'tx') + call assert_equal('let foo=0', @:) + cunmap a +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_comments.vim b/src/nvim/testdir/test_comments.vim new file mode 100644 index 0000000000..c34b85c42d --- /dev/null +++ b/src/nvim/testdir/test_comments.vim @@ -0,0 +1,277 @@ +" Tests for the various flags in the 'comments' option + +" Test for the 'n' flag in 'comments' +func Test_comment_nested() + new + setlocal comments=n:> fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" + exe "normal 5GOE\<C-C>6GoG" + let expected =<< trim END + > A + > B + > C + > D + >>>> E + >>>> F + >>>> G + >>>> H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'b' flag in 'comments' +func Test_comment_blank() + new + setlocal comments=b:* fo+=ro + exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" + let expected =<< trim END + A + *B + * C + * D + * E + * F + *G + H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'f' flag in 'comments' (only the first line has a comment +" string) +func Test_comment_firstline() + new + setlocal comments=f:- fo+=ro + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) + %d + setlocal comments=:- + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) + close! +endfunc + +" Test for the 's', 'm' and 'e' flags in 'comments' +" Test for automatically adding comment leaders in insert mode +func Test_comment_threepiece() + new + setlocal expandtab + call setline(1, ["\t/*"]) + setlocal formatoptions=croql + call cursor(1, 3) + call feedkeys("A\<cr>\<cr>/", 'tnix') + call assert_equal(["\t/*", " *", " */"], getline(1, '$')) + + " If a comment ends in a single line, then don't add it in the next line + %d + call setline(1, '/* line1 */') + call feedkeys("A\<CR>next line", 'xt') + call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) + + %d + " Copy the trailing indentation from the leader comment to a new line + setlocal autoindent noexpandtab + call feedkeys("a\t/*\tone\ntwo\n/", 'xt') + call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) + close! +endfunc + +" Test for the 'r' flag in 'comments' (right align comment) +func Test_comment_rightalign() + new + setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro + exe "normal i=\<C-C>o\t /***\nD\n/" + exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" + let expected =<< trim END + = + A + /*** + ** B + ** C + ** D + ** E + ** F + ******/ + G + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'O' flag in 'comments' +func Test_comment_O() + new + setlocal comments=Ob:* fo+=ro + exe "normal i* B\nD\<C-C>kOA\<C-C>joC" + let expected =<< trim END + A + * B + * C + * D + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for using a multibyte character as a comment leader +func Test_comment_multibyte_leader() + new + let t =<< trim END + { + X + Xa + Xaï¼¹ + XY + XYZ + X ï¼¹ + X YZ + XX + XXa + XXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=cqm comments=n:X + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + let t =<< trim END + X + Xa + Xaï¼¹ + XY + XYZ + X ï¼¹ + X YZ + XX + XXa + XXY + END + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + Xa + Xa + XY + XY + XY + XZ + X ï¼¹ + X ï¼¹ + X Z + XX + XXa + XXY + + X + Xa + Xa + XY + XY + XY + XZ + X ï¼¹ + X ï¼¹ + X Z + XX + XXa + XXY + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& comments& + close! +endfunc + +" Test for a space character in 'comments' setting +func Test_comment_space() + new + setlocal comments=b:\ > fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC" + exe "normal Go > F\nH\<C-C>kOE\<C-C>joG" + let expected =<< trim END + A + > B + C + D + > E + > F + > G + > H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for formatting lines with and without comments +func Test_comment_format_lines() + new + call setline(1, ['one', '/* two */', 'three']) + normal gggqG + call assert_equal(['one', '/* two */', 'three'], getline(1, '$')) + close! +endfunc + +" Test for using 'a' in 'formatoptions' with comments +func Test_comment_autoformat() + new + setlocal formatoptions+=a + call feedkeys("a- one\n- two\n", 'xt') + call assert_equal(['- one', '- two', ''], getline(1, '$')) + + %d + call feedkeys("a\none\n", 'xt') + call assert_equal(['', 'one', ''], getline(1, '$')) + + setlocal formatoptions+=aw + %d + call feedkeys("aone \ntwo\n", 'xt') + call assert_equal(['one two', ''], getline(1, '$')) + + %d + call feedkeys("aone\ntwo\n", 'xt') + call assert_equal(['one', 'two', ''], getline(1, '$')) + + close! +endfunc + +" Test for joining lines with comments ('j' flag in 'formatoptions') +func Test_comment_join_lines_fo_j() + new + setlocal fo+=j comments=:// + call setline(1, ['i++; // comment1', ' // comment2']) + normal J + call assert_equal('i++; // comment1 comment2', getline(1)) + setlocal fo-=j + call setline(1, ['i++; // comment1', ' // comment2']) + normal J + call assert_equal('i++; // comment1 // comment2', getline(1)) + " Test with nested comments + setlocal fo+=j comments=n:>,n:) + call setline(1, ['i++; > ) > ) comment1', ' > ) comment2']) + normal J + call assert_equal('i++; > ) > ) comment1 comment2', getline(1)) + close! +endfunc + +" Test for formatting lines where only the first line has a comment. +func Test_comment_format_firstline_comment() + new + setlocal formatoptions=tcq + call setline(1, ['- one two', 'three']) + normal gggqG + call assert_equal(['- one two three'], getline(1, '$')) + + %d + call setline(1, ['- one', '- two']) + normal gggqG + call assert_equal(['- one', '- two'], getline(1, '$')) + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 3b8a5f27ad..f13842edc8 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -22,7 +22,7 @@ func Test_move_cursor() call cursor(3, 0) call assert_equal([3, 1, 0, 1], getcurpos()[1:]) " below last line goes to last line - call cursor(9, 1) + eval [9, 1]->cursor() call assert_equal([4, 1, 0, 1], getcurpos()[1:]) " pass string arguments call cursor('3', '3') diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index e038c0096a..2be94409ca 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -15,14 +15,18 @@ func CheckCWD() endfunc command! -nargs=0 -bar CheckCWD call CheckCWD() +" "options" argument can contain: +" 'msec' - time to wait for a match +" 'match' - "pattern" to use "lines" as pattern instead of text func CheckDbgOutput(buf, lines, options = {}) " Verify the expected output let lnum = 20 - len(a:lines) + let msec = get(a:options, 'msec', 1000) for l in a:lines if get(a:options, 'match', 'equal') ==# 'pattern' - call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200) + call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, msec) else - call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200) + call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, msec) endif let lnum += 1 endfor @@ -198,7 +202,7 @@ func Test_Debugger() " Start a debug session, so that reading the last line from the terminal " works properly. - call RunDbgCmd(buf, ':debug echo Foo()') + call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()']) " No breakpoints call RunDbgCmd(buf, 'breakl', ['No breakpoints defined']) @@ -814,9 +818,10 @@ func Test_Backtrace_CmdLine() \ '-S Xtest1.vim -c "debug call GlobalFunction()"', \ {'wait_for_ruler': 0}) - " Need to wait for the vim-in-terminal to be ready + " Need to wait for the vim-in-terminal to be ready. + " With valgrind this can take quite long. call CheckDbgOutput(buf, ['command line', - \ 'cmd: call GlobalFunction()']) + \ 'cmd: call GlobalFunction()'], #{msec: 5000}) " At this point the ontly thing in the stack is the cmdline call RunDbgCmd(buf, 'backtrace', [ @@ -967,14 +972,14 @@ func Test_debug_backtrace_level() " set a breakpoint and source file1.vim let buf = RunVimInTerminal( \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim', - \ #{ wait_for_ruler: 0 } ) + \ #{wait_for_ruler: 0}) call CheckDbgOutput(buf, [ \ 'Breakpoint in "' .. file1 .. '" line 1', \ 'Entering Debug mode. Type "cont" to continue.', \ 'command line..script ' .. file1, \ 'line 1: let s:file1_var = ''file1''' - \ ]) + \ ], #{msec: 5000}) " step through the initial declarations call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] ) diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 1dbbe578c5..1cb71664bd 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 @@ -744,17 +744,13 @@ func Test_diff_hlID() call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("") - call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange") - call assert_equal(diff_hlID(1, 2), hlID("DiffText")) call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText") call diff_hlID(2, 1)->synIDattr("name")->assert_equal("") - call assert_equal(diff_hlID(3, 1), hlID("DiffAdd")) call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd") - call diff_hlID(4, 1)->synIDattr("name")->assert_equal("") + eval 4->diff_hlID(1)->synIDattr("name")->assert_equal("") wincmd w - call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "") diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index acc34e5e7c..f08dff8605 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -211,6 +211,8 @@ func Test_digraphs() call Put_Dig("00") call Put_Dig("el") call assert_equal(['â€', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) + call assert_fails('exe "digraph a\<Esc> 100"', 'E104:') + call assert_fails('exe "digraph \<Esc>a 100"', 'E104:') call assert_fails('digraph xy z', 'E39:') call assert_fails('digraph x', 'E1214:') bw! @@ -491,6 +493,17 @@ func Test_show_digraph_cp1251() bwipe! endfunc +" Test for error in a keymap file +func Test_loadkeymap_error() + if !has('keymap') + return + endif + call assert_fails('loadkeymap', 'E105:') + call writefile(['loadkeymap', 'a'], 'Xkeymap') + call assert_fails('source Xkeymap', 'E791:') + call delete('Xkeymap') +endfunc + " Test for the characters displayed on the screen when entering a digraph func Test_entering_digraph() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 6938abbc28..217bb5d781 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -309,6 +309,88 @@ func Test_eob_fillchars() close endfunc +" Test for 'foldopen', 'foldclose' and 'foldsep' in 'fillchars' +func Test_fold_fillchars() + new + set fdc=2 foldenable foldmethod=manual + call setline(1, ['one', 'two', 'three', 'four', 'five']) + 2,4fold + " First check for the default setting for a closed fold + let lines = ScreenLines([1, 3], 8) + let expected = [ + \ ' one ', + \ '+ +-- 3', + \ ' five ' + \ ] + call assert_equal(expected, lines) + normal 2Gzo + " check the characters for an open fold + let lines = ScreenLines([1, 5], 8) + let expected = [ + \ ' one ', + \ '- two ', + \ '| three ', + \ '| four ', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + " change the setting + set fillchars=vert:\|,fold:-,eob:~,foldopen:[,foldclose:],foldsep:- + + " check the characters for an open fold + let lines = ScreenLines([1, 5], 8) + let expected = [ + \ ' one ', + \ '[ two ', + \ '- three ', + \ '- four ', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + " check the characters for a closed fold + normal 2Gzc + let lines = ScreenLines([1, 3], 8) + let expected = [ + \ ' one ', + \ '] +-- 3', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + %bw! + set fillchars& fdc& foldmethod& foldenable& +endfunc + +func Test_local_fillchars() + CheckScreendump + + let lines =<< trim END + call setline(1, ['window 1']->repeat(3)) + setlocal fillchars=stl:1,stlnc:a,vert:=,eob:x + vnew + call setline(1, ['window 2']->repeat(3)) + setlocal fillchars=stl:2,stlnc:b,vert:+,eob:y + new + wincmd J + call setline(1, ['window 3']->repeat(3)) + setlocal fillchars=stl:3,stlnc:c,vert:<,eob:z + vnew + call setline(1, ['window 4']->repeat(3)) + setlocal fillchars=stl:4,stlnc:d,vert:>,eob:o + END + call writefile(lines, 'Xdisplayfillchars') + let buf = RunVimInTerminal('-S Xdisplayfillchars', #{rows: 12}) + call VerifyScreenDump(buf, 'Test_display_fillchars_1', {}) + + call term_sendkeys(buf, ":wincmd k\r") + call VerifyScreenDump(buf, 'Test_display_fillchars_2', {}) + + call StopVimInTerminal(buf) + call delete('Xdisplayfillchars') +endfunc + func Test_display_linebreak_breakat() new vert resize 25 diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index a09346a595..679b877ef6 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 @@ -729,23 +713,32 @@ endfunc func Test_edit_CTRL_N() " Check keyword completion - new - set complete=. - call setline(1, ['INFER', 'loWER', '', '', ]) - call cursor(3, 1) - call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") - call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') - call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$')) - %d - call setline(1, ['INFER', 'loWER', '', '', ]) - call cursor(3, 1) - set ignorecase infercase - call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") - call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') - call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$')) - - set noignorecase noinfercase complete& - bw! + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + set complete=. + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$'), e) + %d + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + set ignorecase infercase + call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$'), e) + set noignorecase noinfercase + %d + call setline(1, ['one word', 'two word']) + exe "normal! Goo\<C-P>\<C-X>\<C-P>" + call assert_equal('one word', getline(3)) + %d + set complete& + bw! + endfor endfunc func Test_edit_CTRL_O() @@ -909,6 +902,24 @@ func Test_edit_CTRL_T() bw! endfunc +" Test thesaurus completion with different encodings +func Test_thesaurus_complete_with_encoding() + call writefile(['angry furious mad enraged'], 'Xthesaurus') + set thesaurus=Xthesaurus + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + bw! + endfor + set thesaurus= + call delete('Xthesaurus') +endfunc + " Test 'thesaurusfunc' func MyThesaurus(findstart, base) let mythesaurus = [ @@ -1620,6 +1631,7 @@ func Test_edit_InsertLeave_undo() bwipe! au! InsertLeave call delete('XtestUndo') + call delete(undofile('XtestUndo')) set undofile& endfunc @@ -1687,11 +1699,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 +1713,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 +1787,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_environ.vim b/src/nvim/testdir/test_environ.vim index dd34983ee5..d8344817f5 100644 --- a/src/nvim/testdir/test_environ.vim +++ b/src/nvim/testdir/test_environ.vim @@ -28,6 +28,26 @@ func Test_setenv() call assert_equal(v:null, getenv('TEST ENV')) endfunc +func Test_special_env() + " The value for $HOME is cached internally by Vim, ensure the value is up to + " date. + let orig_ENV = $HOME + + let $HOME = 'foo' + call assert_equal('foo', expand('~')) + " old $HOME value is kept until a new one is set + unlet $HOME + call assert_equal('foo', expand('~')) + + call setenv('HOME', 'bar') + call assert_equal('bar', expand('~')) + " old $HOME value is kept until a new one is set + call setenv('HOME', v:null) + call assert_equal('bar', expand('~')) + + let $HOME = orig_ENV +endfunc + func Test_external_env() call setenv('FOO', 'HelloWorld') if has('win32') 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_excmd.vim b/src/nvim/testdir/test_excmd.vim index dac7a6989d..9a9e5c546b 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -568,10 +568,12 @@ endfunc " Test for the :verbose command func Test_verbose_cmd() - call assert_equal([' verbose=1'], split(execute('verbose set vbs'), "\n")) + set verbose=3 + call assert_match(' verbose=1\n\s*Last set from ', execute('verbose set vbs'), "\n") call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n")) - let l = execute("4verbose set verbose | set verbose") - call assert_equal([' verbose=4', ' verbose=0'], split(l, "\n")) + set verbose=0 + call assert_match(' verbose=4\n\s*Last set from .*\n verbose=0', + \ execute("4verbose set verbose | set verbose")) endfunc " Test for the :delete command and the related abbreviated commands 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.vim b/src/nvim/testdir/test_expand.vim index 383921bb82..aa131a49ff 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -1,6 +1,7 @@ " Test for expanding file names source shared.vim +source check.vim func Test_with_directories() call mkdir('Xdir1') @@ -77,10 +78,11 @@ func Test_expandcmd() edit a1a2a3.rb call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) - call assert_fails('call expandcmd("make <afile>")', 'E495:') - call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_equal('make <afile>', expandcmd("make <afile>")) + call assert_equal('make <amatch>', expandcmd("make <amatch>")) + call assert_equal('make <abuf>', expandcmd("make <abuf>")) enew - call assert_fails('call expandcmd("make %")', 'E499:') + call assert_equal('make %', expandcmd("make %")) let $FOO="blue\tsky" call setline(1, "$FOO") call assert_equal("grep pat blue\tsky", expandcmd('grep pat <cfile>')) @@ -93,6 +95,11 @@ func Test_expandcmd() let $FOO= "foo bar baz" call assert_equal("e foo bar baz", expandcmd("e $FOO")) + if has('unix') + " test for using the shell to expand a command argument + call assert_equal('{1..4}', expandcmd('{1..4}')) + endif + unlet $FOO close! endfunc @@ -100,22 +107,30 @@ endfunc " Test for expanding <sfile>, <slnum> and <sflnum> outside of sourcing a script func Test_source_sfile() let lines =<< trim [SCRIPT] - :call assert_fails('echo expandcmd("<sfile>")', 'E498:') - :call assert_fails('echo expandcmd("<slnum>")', 'E842:') - :call assert_fails('echo expandcmd("<sflnum>")', 'E961:') - :call assert_fails('call expandcmd("edit <cfile>")', 'E446:') - :call assert_fails('call expandcmd("edit #")', 'E194:') - :call assert_fails('call expandcmd("edit #<2")', 'E684:') - :call assert_fails('call expandcmd("edit <cword>")', 'E348:') - :call assert_fails('call expandcmd("edit <cexpr>")', 'E348:') + :call assert_equal('<sfile>', expandcmd("<sfile>")) + :call assert_equal('<slnum>', expandcmd("<slnum>")) + :call assert_equal('<sflnum>', expandcmd("<sflnum>")) + :call assert_equal('edit <cfile>', expandcmd("edit <cfile>")) + :call assert_equal('edit #', expandcmd("edit #")) + :call assert_equal('edit #<2', expandcmd("edit #<2")) + :call assert_equal('edit <cword>', expandcmd("edit <cword>")) + :call assert_equal('edit <cexpr>', expandcmd("edit <cexpr>")) :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:') + : + :call assert_equal('', expand('<script>')) + :verbose echo expand('<script>') + :call add(v:errors, v:errmsg) + :verbose echo expand('<sfile>') + :call add(v:errors, v:errmsg) :call writefile(v:errors, 'Xresult') :qall! - [SCRIPT] call writefile(lines, 'Xscript') if RunVim([], [], '--clean -s Xscript') - call assert_equal([], readfile('Xresult')) + call assert_equal([ + \ 'E1274: No script file name to substitute for "<script>"', + \ 'E498: no :source file name to substitute for "<sfile>"'], + \ readfile('Xresult')) endif call delete('Xscript') call delete('Xresult') @@ -131,7 +146,72 @@ func Test_expand_filename_multicmd() call assert_equal(4, winnr('$')) call assert_equal('foo!', bufname(winbufnr(1))) call assert_equal('foo', bufname(winbufnr(2))) + call assert_fails('e %:s/.*//', 'E500:') %bwipe! endfunc +func Test_expandcmd_shell_nonomatch() + CheckNotMSWindows + call assert_equal('$*', expandcmd('$*')) +endfunc + +func Test_expand_script_source() + let lines0 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + so Xscript1 + func F0() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + let lines1 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + so Xscript2 + func F1() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + let lines2 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + func F2() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + call writefile(lines0, 'Xscript0') + call writefile(lines1, 'Xscript1') + call writefile(lines2, 'Xscript2') + + " Check the expansion of <script> at different levels. + let g:script_level = [] + let g:func_level = [] + let g:au_level = [] + + so Xscript0 + call F0() + call F1() + call F2() + doautocmd User + + call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:script_level) + call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:func_level) + call assert_equal(['Xscript2', 'Xscript1', 'Xscript0'], g:au_level) + + unlet g:script_level g:func_level + delfunc F0 + delfunc F1 + delfunc F2 + + call delete('Xscript0') + call delete('Xscript1') + call delete('Xscript2') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index b48c2e8a19..80bfdb8553 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() @@ -70,10 +107,15 @@ endfunc func Test_expand() new - call assert_equal("", expand('%:S')) + call assert_equal("", expand('%:S')) call assert_equal('3', '<slnum>'->expand()) call assert_equal(['4'], expand('<slnum>', v:false, v:true)) " Don't add any line above this, otherwise <slnum> will change. + call assert_equal("", expand('%')) + set verbose=1 + call assert_equal("", expand('%')) + set verbose=0 + call assert_equal("", expand('%:p')) quit endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 5b10e691e5..15622cd6fe 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -547,6 +547,7 @@ func Test_funcref() call assert_fails('echo funcref("{")', 'E475:') let OneByRef = funcref("One", repeat(["foo"], 20)) call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:') + call assert_fails('echo function("min") =~ function("min")', 'E694:') endfunc func Test_setmatches() diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index c6e781a1ef..fef0eb732f 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -140,7 +140,8 @@ func Test_FileChangedShell_edit() endfunc func Test_FileChangedShell_edit_dialog() - throw 'Skipped: requires a UI to be active' + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/filechanged_spec.lua' CheckNotGui CheckUnix " Using low level feedkeys() does not work on MS-Windows. @@ -190,7 +191,8 @@ func Test_FileChangedShell_edit_dialog() endfunc func Test_file_changed_dialog() - throw 'Skipped: requires a UI to be active' + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/filechanged_spec.lua' CheckUnix CheckNotGui au! FileChangedShell @@ -242,6 +244,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..68ce9148a4 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'], @@ -208,6 +209,7 @@ let s:filename_checks = { \ 'gdmo': ['file.mo', 'file.gdmo'], \ 'gdresource': ['file.tscn', 'file.tres'], \ 'gdscript': ['file.gd'], + \ 'gdshader': ['file.gdshader', 'file.shader'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], @@ -360,7 +362,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 +443,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'], @@ -586,6 +589,9 @@ let s:filename_checks = { \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], \ 'vala': ['file.vala'], \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vdmpp': ['file.vpp', 'file.vdmpp'], + \ 'vdmrt': ['file.vdmrt'], + \ 'vdmsl': ['file.vdm', 'file.vdmsl'], \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index c11e7b4fea..147eda5b0a 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1420,12 +1420,15 @@ func Test_trim() call assert_equal("vim", trim(" vim ", " ", 0)) call assert_equal("vim ", trim(" vim ", " ", 1)) call assert_equal(" vim", trim(" vim ", " ", 2)) - call assert_fails('call trim(" vim ", " ", [])', 'E745:') - call assert_fails('call trim(" vim ", " ", -1)', 'E475:') - call assert_fails('call trim(" vim ", " ", 3)', 'E475:') + call assert_fails('eval trim(" vim ", " ", [])', 'E745:') + call assert_fails('eval trim(" vim ", " ", -1)', 'E475:') + call assert_fails('eval trim(" vim ", " ", 3)', 'E475:') + call assert_fails('eval trim(" vim ", 0)', 'E475:') let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '') call assert_equal("x", trim(chars . "x" . chars)) + + call assert_fails('let c=trim([])', 'E730:') endfunc " Test for reg_recording() and reg_executing() @@ -1697,6 +1700,63 @@ func Test_platform_name() endif endfunc +" Test confirm({msg} [, {choices} [, {default} [, {type}]]]) +func Test_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/vimscript/input_spec.lua' + if !has('unix') || has('gui_running') + return + endif + + call feedkeys('o', 'L') + let a = confirm('Press O to proceed') + call assert_equal(1, a) + + call feedkeys('y', 'L') + let a = 'Are you sure?'->confirm("&Yes\n&No") + call assert_equal(1, a) + + call feedkeys('n', 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(2, a) + + " confirm() should return 0 when pressing CTRL-C. + call feedkeys("\<C-c>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(0, a) + + " <Esc> requires another character to avoid it being seen as the start of an + " escape sequence. Zero should be harmless. + eval "\<Esc>0"->feedkeys('L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(0, a) + + " Default choice is returned when pressing <CR>. + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(1, a) + + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No", 2) + call assert_equal(2, a) + + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No", 0) + call assert_equal(0, a) + + " Test with the {type} 4th argument + for type in ['Error', 'Question', 'Info', 'Warning', 'Generic'] + call feedkeys('y', 'L') + let a = confirm('Are you sure?', "&Yes\n&No\n", 1, type) + call assert_equal(1, a) + endfor + + call assert_fails('call confirm([])', 'E730:') + call assert_fails('call confirm("Are you sure?", [])', 'E730:') + call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", [])', 'E745:') + call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", 0, [])', 'E730:') +endfunc + func Test_readdir() call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') @@ -1724,7 +1784,7 @@ func Test_readdir() let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1}) call assert_equal(1, len(files)) - call delete('Xdir', 'rf') + eval 'Xdir'->delete('rf') endfunc func Test_delete_rf() @@ -1767,6 +1827,16 @@ endfunc func Test_char2nr() call assert_equal(12354, char2nr('ã‚', 1)) + call assert_equal(120, 'x'->char2nr()) +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() @@ -1810,6 +1880,22 @@ func Test_bufadd_bufload() exe 'bwipe ' .. buf2 call assert_equal(0, bufexists(buf2)) + " When 'buftype' is "nofile" then bufload() does not read the file. + " Other values too. + for val in [['nofile', 0], + \ ['nowrite', 1], + \ ['acwrite', 1], + \ ['quickfix', 0], + \ ['help', 1], + \ ['prompt', 0], + \ ] + bwipe! XotherName + let buf = bufadd('XotherName') + call setbufvar(buf, '&bt', val[0]) + call bufload(buf) + call assert_equal(val[1] ? ['some', 'text'] : [''], getbufline(buf, 1, '$'), val[0]) + endfor + bwipe someName bwipe XotherName call assert_equal(0, bufexists('someName')) 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_highlight.vim b/src/nvim/testdir/test_highlight.vim index efdf44a0d6..8e808a00d0 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -731,7 +731,8 @@ func Test_1_highlight_Normalgroup_exists() endif endfunc -function Test_no_space_before_xxx() +" Do this test last, sometimes restoring the columns doesn't work +func Test_z_no_space_before_xxx() " Note: we need to create this highlight group in the test because it does not exist in Neovim execute('hi StatusLineTermNC ctermfg=green') let l:org_columns = &columns @@ -739,7 +740,7 @@ function Test_no_space_before_xxx() let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC'))) call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC) let &columns = l:org_columns -endfunction +endfunc " Test for :highlight command errors func Test_highlight_cmd_errors() diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim index feb521e232..f1c31dee04 100644 --- a/src/nvim/testdir/test_history.vim +++ b/src/nvim/testdir/test_history.vim @@ -95,6 +95,23 @@ function Test_History() call assert_fails('call histnr([])', 'E730:') call assert_fails('history xyz', 'E488:') call assert_fails('history ,abc', 'E488:') + call assert_fails('call histdel(":", "\\%(")', 'E53:') +endfunction + +function Test_history_truncates_long_entry() + " History entry short enough to fit on the screen should not be truncated. + call histadd(':', 'echo x' .. repeat('y', &columns - 17) .. 'z') + let a = execute('history : -1') + + call assert_match("^\n # cmd history\n" + \ .. "> *\\d\\+ echo x" .. repeat('y', &columns - 17) .. 'z$', a) + + " Long history entry should be truncated to fit on the screen, with, '...' + " inserted in the string to indicate the that there is truncation. + call histadd(':', 'echo x' .. repeat('y', &columns - 16) .. 'z') + let a = execute('history : -1') + call assert_match("^\n # cmd history\n" + \ .. "> *\\d\\+ echo xy\\+\.\.\.y\\+z$", a) endfunction function Test_Search_history_window() diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 179218e48a..3e563f29f9 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -44,11 +44,11 @@ func Test_ins_complete() exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>" call assert_equal('run1 run2', getline('.')) - set cpt=.,w,i + set cpt=.,\ ,w,i " i-add-expands and switches to local exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>" call assert_equal("Makefile\tto\trun3", getline('.')) - " add-expands lines (it would end in an empty line if it didn't ignored + " add-expands lines (it would end in an empty line if it didn't ignore " itself) exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>" call assert_equal("Makefile\tto\trun3", getline('.')) @@ -68,6 +68,11 @@ func Test_ins_complete() call assert_equal('Xtest11.one', getline('.')) normal ddk + " Test for expanding a non-existing filename + exe "normal oa1b2X3Y4\<C-X>\<C-F>" + call assert_equal('a1b2X3Y4', getline('.')) + normal ddk + set cpt=w " checks make_cyclic in other window exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>" @@ -682,6 +687,21 @@ func Test_complete_func_error() call assert_equal([], complete_info(['items']).items) endfunc +" Test for recursively starting completion mode using complete() +func Test_recursive_complete_func() + func ListColors() + call complete(5, ["red", "blue"]) + return '' + endfunc + new + call setline(1, ['a1', 'a2']) + set complete=. + exe "normal Goa\<C-X>\<C-L>\<C-R>=ListColors()\<CR>\<C-N>" + call assert_equal('a2blue', getline(3)) + delfunc ListColors + bw! +endfunc + " Test for completing words following a completed word in a line func Test_complete_wrapscan() " complete words from another buffer @@ -721,6 +741,17 @@ func Test_complete_across_line() close! endfunc +" Test for completing words with a '.' at the end of a word. +func Test_complete_joinspaces() + new + call setline(1, ['one two.', 'three. four']) + set joinspaces + exe "normal Goon\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal("one two. three. four", getline(3)) + set joinspaces& + bw! +endfunc + " Test for using CTRL-L to add one character when completing matching func Test_complete_add_onechar() new @@ -741,6 +772,39 @@ func Test_complete_add_onechar() close! endfunc +" Test for using CTRL-X CTRL-L to complete whole lines lines +func Test_complete_wholeline() + new + " complete one-line + call setline(1, ['a1', 'a2']) + exe "normal ggoa\<C-X>\<C-L>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\<C-X>\<C-L>\<C-N>" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>\<C-N>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " repeat the test using CTRL-L + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\<C-X>\<C-L>\<C-L>" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>\<C-L>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + %d + " use CTRL-X CTRL-L to add one more line + call setline(1, ['a1', 'b1']) + setlocal complete=. + exe "normal ggOa\<C-X>\<C-L>\<C-X>\<C-L>\<C-X>\<C-L>" + call assert_equal(['a1', 'b1', '', 'a1', 'b1'], getline(1, '$')) + bw! +endfunc + " Test insert completion with 'cindent' (adjust the indent) func Test_complete_with_cindent() new @@ -829,6 +893,25 @@ func Test_complete_stop() close! endfunc +" Test for typing CTRL-R in insert completion mode to insert a register +" content. +func Test_complete_reginsert() + new + call setline(1, ['a1', 'a12', 'a123', 'a1234']) + + " if a valid CTRL-X mode key is returned from <C-R>=, then it should be + " processed. Otherwise, CTRL-X mode should be stopped and the key should be + " inserted. + exe "normal Goa\<C-P>\<C-R>=\"\\<C-P>\"\<CR>" + call assert_equal('a123', getline(5)) + let @r = "\<C-P>\<C-P>" + exe "normal GCa\<C-P>\<C-R>r" + call assert_equal('a12', getline(5)) + exe "normal GCa\<C-P>\<C-R>=\"x\"\<CR>" + call assert_equal('a1234x', getline(5)) + bw! +endfunc + func Test_issue_7021() CheckMSWindows @@ -842,6 +925,322 @@ func Test_issue_7021() set completeslash= endfunc +" Test for 'longest' setting in 'completeopt' with latin1 and utf-8 encodings +func Test_complete_longest_match() + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + set complete=. + set completeopt=menu,longest + call setline(1, ['pfx_a1', 'pfx_a12', 'pfx_a123', 'pfx_b1']) + exe "normal Gopfx\<C-P>" + call assert_equal('pfx_', getline(5)) + bw! + endfor + + " Test for completing additional words with longest match set + new + call setline(1, ['abc1', 'abd2']) + exe "normal Goab\<C-P>\<C-X>\<C-P>" + call assert_equal('ab', getline(3)) + bw! + set complete& completeopt& +endfunc + +" Test for removing the first displayed completion match and selecting the +" match just before that. +func Test_complete_erase_firstmatch() + new + call setline(1, ['a12', 'a34', 'a56']) + set complete=. + exe "normal Goa\<C-P>\<BS>\<BS>3\<CR>" + call assert_equal('a34', getline('$')) + set complete& + bw! +endfunc + +" Test for completing words from unloaded buffers +func Test_complete_from_unloadedbuf() + call writefile(['abc'], "Xfile1") + call writefile(['def'], "Xfile2") + edit Xfile1 + edit Xfile2 + new | close + enew + bunload Xfile1 Xfile2 + set complete=u + " complete from an unloaded buffer + exe "normal! ia\<C-P>" + call assert_equal('abc', getline(1)) + exe "normal! od\<C-P>" + call assert_equal('def', getline(2)) + set complete& + %bw! + call delete("Xfile1") + call delete("Xfile2") +endfunc + +" Test for completing whole lines from unloaded buffers +func Test_complete_wholeline_unloadedbuf() + call writefile(['a line1', 'a line2', 'a line3'], "Xfile1") + edit Xfile1 + enew + set complete=u + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a line2', getline(1)) + %d + " completing from an unlisted buffer should fail + bdel Xfile1 + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a', getline(1)) + set complete& + %bw! + call delete("Xfile1") +endfunc + +" Test for completing words from unlisted buffers +func Test_complete_from_unlistedbuf() + call writefile(['abc'], "Xfile1") + call writefile(['def'], "Xfile2") + edit Xfile1 + edit Xfile2 + new | close + bdel Xfile1 Xfile2 + set complete=U + " complete from an unlisted buffer + exe "normal! ia\<C-P>" + call assert_equal('abc', getline(1)) + exe "normal! od\<C-P>" + call assert_equal('def', getline(2)) + set complete& + %bw! + call delete("Xfile1") + call delete("Xfile2") +endfunc + +" Test for completing whole lines from unlisted buffers +func Test_complete_wholeline_unlistedbuf() + call writefile(['a line1', 'a line2', 'a line3'], "Xfile1") + edit Xfile1 + enew + set complete=U + " completing from a unloaded buffer should fail + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a', getline(1)) + %d + bdel Xfile1 + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a line2', getline(1)) + set complete& + %bw! + call delete("Xfile1") +endfunc + +" Test for adding a multibyte character using CTRL-L in completion mode +func Test_complete_mbyte_char_add() + new + set complete=. + call setline(1, 'abÄ—') + exe "normal! oa\<C-P>\<BS>\<BS>\<C-L>\<C-L>" + call assert_equal('abÄ—', getline(2)) + " Test for a leader with multibyte character + %d + call setline(1, 'abÄ—Ä•') + exe "normal! oabÄ—\<C-P>" + call assert_equal('abÄ—Ä•', getline(2)) + bw! +endfunc + +" Test for using <C-X><C-P> for local expansion even if 'complete' is set to +" not to complete matches from the local buffer. Also test using multiple +" <C-X> to cancel the current completion mode. +func Test_complete_local_expansion() + new + set complete=t + call setline(1, ['abc', 'def']) + exe "normal! Go\<C-X>\<C-P>" + call assert_equal("def", getline(3)) + exe "normal! Go\<C-P>" + call assert_equal("", getline(4)) + exe "normal! Go\<C-X>\<C-N>" + call assert_equal("abc", getline(5)) + exe "normal! Go\<C-N>" + call assert_equal("", getline(6)) + + " use multiple <C-X> to cancel the previous completion mode + exe "normal! Go\<C-P>\<C-X>\<C-P>" + call assert_equal("", getline(7)) + exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-P>" + call assert_equal("", getline(8)) + exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-X>\<C-P>" + call assert_equal("abc", getline(9)) + + " interrupt the current completion mode + set completeopt=menu,noinsert + exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-X>\<C-P>\<C-Y>" + call assert_equal("abc", getline(10)) + + " when only one <C-X> is used to interrupt, do normal expansion + exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-P>" + call assert_equal("", getline(11)) + set completeopt& + + " using two <C-X> in non-completion mode and restarting the same mode + exe "normal! God\<C-X>\<C-X>\<C-P>\<C-X>\<C-X>\<C-P>\<C-Y>" + call assert_equal("def", getline(12)) + + " test for adding a match from the original empty text + %d + call setline(1, 'abc def g') + exe "normal! o\<C-X>\<C-P>\<C-N>\<C-X>\<C-P>" + call assert_equal('def', getline(2)) + exe "normal! 0C\<C-X>\<C-N>\<C-P>\<C-X>\<C-N>" + call assert_equal('abc', getline(2)) + + bw! +endfunc + +" Test for undoing changes after a insert-mode completion +func Test_complete_undo() + new + set complete=. + " undo with 'ignorecase' + call setline(1, ['ABOVE', 'BELOW']) + set ignorecase + exe "normal! Goab\<C-G>u\<C-P>" + call assert_equal("ABOVE", getline(3)) + undo + call assert_equal("ab", getline(3)) + set ignorecase& + %d + " undo with longest match + set completeopt=menu,longest + call setline(1, ['above', 'about']) + exe "normal! Goa\<C-G>u\<C-P>" + call assert_equal("abo", getline(3)) + undo + call assert_equal("a", getline(3)) + set completeopt& + %d + " undo for line completion + call setline(1, ['above that change', 'below that change']) + exe "normal! Goabove\<C-G>u\<C-X>\<C-L>" + call assert_equal("above that change", getline(3)) + undo + call assert_equal("above", getline(3)) + + bw! +endfunc + +" Test for completing a very long word +func Test_complete_long_word() + set complete& + new + call setline(1, repeat('x', 950) .. ' one two three') + exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal(repeat('x', 950) .. ' one two three', getline(2)) + %d + " should fail when more than 950 characters are in a word + call setline(1, repeat('x', 951) .. ' one two three') + exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal(repeat('x', 951), getline(2)) + + " Test for adding a very long word to an existing completion + %d + call setline(1, ['abc', repeat('x', 1016) .. '012345']) + exe "normal! Goab\<C-P>\<C-X>\<C-P>" + call assert_equal('abc ' .. repeat('x', 1016) .. '0123', getline(3)) + bw! +endfunc + +" Test for some fields in the complete items used by complete() +func Test_complete_items() + func CompleteItems(idx) + let items = [[#{word: "one", dup: 1, user_data: 'u1'}, #{word: "one", dup: 1, user_data: 'u2'}], + \ [#{word: "one", dup: 0, user_data: 'u3'}, #{word: "one", dup: 0, user_data: 'u4'}], + \ [#{word: "one", icase: 1, user_data: 'u7'}, #{word: "oNE", icase: 1, user_data: 'u8'}], + \ [#{user_data: 'u9'}], + \ [#{word: "", user_data: 'u10'}], + \ [#{word: "", empty: 1, user_data: 'u11'}]] + call complete(col('.'), items[a:idx]) + return '' + endfunc + new + exe "normal! i\<C-R>=CompleteItems(0)\<CR>\<C-N>\<C-Y>" + call assert_equal('u2', v:completed_item.user_data) + call assert_equal('one', getline(1)) + exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-Y>" + call assert_equal('u3', v:completed_item.user_data) + call assert_equal('one', getline(2)) + exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-N>" + call assert_equal('', getline(3)) + set completeopt=menu,noinsert + exe "normal! o\<C-R>=CompleteItems(2)\<CR>one\<C-N>\<C-Y>" + call assert_equal('oNE', getline(4)) + call assert_equal('u8', v:completed_item.user_data) + set completeopt& + exe "normal! o\<C-R>=CompleteItems(3)\<CR>" + call assert_equal('', getline(5)) + exe "normal! o\<C-R>=CompleteItems(4)\<CR>" + call assert_equal('', getline(6)) + exe "normal! o\<C-R>=CompleteItems(5)\<CR>" + call assert_equal('', getline(7)) + call assert_equal('u11', v:completed_item.user_data) + " pass invalid argument to complete() + let cmd = "normal! o\<C-R>=complete(1, [[]])\<CR>" + call assert_fails('exe cmd', 'E730:') + bw! + delfunc CompleteItems +endfunc + +" Test for the "refresh" item in the dict returned by an insert completion +" function +func Test_complete_item_refresh_always() + let g:CallCount = 0 + func! Tcomplete(findstart, base) + if a:findstart + " locate the start of the word + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start - 1] =~ '\a' + let start -= 1 + endwhile + return start + else + let g:CallCount += 1 + let res = ["update1", "update12", "update123"] + return #{words: res, refresh: 'always'} + endif + endfunc + new + set completeopt=menu,longest + set completefunc=Tcomplete + exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>" + call assert_equal('up', getline(1)) + call assert_equal(2, g:CallCount) + set completeopt& + set completefunc& + bw! + delfunc Tcomplete +endfunc + +" Test for completing from a thesaurus file without read permission +func Test_complete_unreadable_thesaurus_file() + CheckUnix + CheckNotRoot + + call writefile(['about', 'above'], 'Xfile') + call setfperm('Xfile', '---r--r--') + new + set complete=sXfile + exe "normal! ia\<C-P>" + call assert_equal('a', getline(1)) + bw! + call delete('Xfile') + set complete& +endfunc + " Test to ensure 'Scanning...' messages are not recorded in messages history func Test_z1_complete_no_history() new @@ -884,4 +1283,52 @@ func Test_complete_smartindent() delfunction! FooBarComplete endfunc +func Test_complete_overrun() + " this was going past the end of the copied text + new + sil norm si”0s0 + bwipe! +endfunc + +func Test_infercase_very_long_line() + " this was truncating the line when inferring case + new + let longLine = "blah "->repeat(300) + let verylongLine = "blah "->repeat(400) + call setline(1, verylongLine) + call setline(2, longLine) + set ic infercase + exe "normal 2Go\<C-X>\<C-L>\<Esc>" + call assert_equal(longLine, getline(3)) + + " check that the too long text is NUL terminated + %del + norm o + norm 1987ax + exec "norm ox\<C-X>\<C-L>" + call assert_equal(repeat('x', 1987), getline(3)) + + bwipe! + set noic noinfercase +endfunc + +func Test_ins_complete_add() + " this was reading past the end of allocated memory + new + norm o + norm 7o€€ + sil! norm o + + bwipe! +endfunc + +func Test_ins_complete_end_of_line() + " this was reading past the end of the line + new + norm 8o€ý + sil! norm o + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index c1fe47d1c9..c178c87d3e 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -308,3 +308,21 @@ func Test_lambda_error() " This was causing a crash call assert_fails('ec{@{->{d->()()', 'E15') endfunc + +func Test_closure_error() + let l =<< trim END + func F1() closure + return 1 + endfunc + END + call writefile(l, 'Xscript') + let caught_932 = 0 + try + source Xscript + catch /E932:/ + let caught_932 = 1 + endtry + call assert_equal(1, caught_932) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index aa66d86af1..08c415a069 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -604,20 +604,23 @@ func Test_reverse_sort_uniq() call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l))) call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l)) call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l))) - call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) - call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) - call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) - call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) - - let l=[7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] - call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) - - let l=[7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] - call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) - call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) - call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) + if has('float') + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) + call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) + call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) + + let l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] + call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) + + let l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) + call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) + endif call assert_fails('call reverse("")', 'E899:') + call assert_fails('call uniq([1, 2], {x, y -> []})', 'E882:') endfunc " reduce a list or a blob @@ -649,6 +652,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_maparg.vim b/src/nvim/testdir/test_maparg.vim index dad4c81a7b..17c5f433ac 100644 --- a/src/nvim/testdir/test_maparg.vim +++ b/src/nvim/testdir/test_maparg.vim @@ -248,6 +248,8 @@ func Test_mapset() bwipe! call assert_fails('call mapset([], v:false, {})', 'E730:') + call assert_fails('call mapset("i", 0, "")', 'E715:') + call assert_fails('call mapset("i", 0, {})', 'E460:') endfunc func Check_ctrlb_map(d, check_alt) 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..4f842189b6 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() @@ -376,6 +352,70 @@ func Test_normal09a_operatorfunc() norm V10j,, call assert_equal(22, g:a) + " Use a lambda function for 'opfunc' + unmap <buffer> ,, + call cursor(1, 1) + let g:a=0 + nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@ + vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR> + 50 + norm V2j,, + call assert_equal(6, g:a) + norm V,, + call assert_equal(2, g:a) + norm ,,l + call assert_equal(0, g:a) + 50 + exe "norm 0\<c-v>10j2l,," + call assert_equal(11, g:a) + 50 + norm V10j,, + call assert_equal(22, g:a) + + " use a partial function for 'opfunc' + let g:OpVal = 0 + func! Test_opfunc1(x, y, type) + let g:OpVal = a:x + a:y + endfunc + set opfunc=function('Test_opfunc1',\ [5,\ 7]) + normal! g@l + call assert_equal(12, g:OpVal) + " delete the function and try to use g@ + delfunc Test_opfunc1 + call test_garbagecollect_now() + call assert_fails('normal! g@l', 'E117:') + set opfunc= + + " use a funcref for 'opfunc' + let g:OpVal = 0 + func! Test_opfunc2(x, y, type) + let g:OpVal = a:x + a:y + endfunc + set opfunc=funcref('Test_opfunc2',\ [4,\ 3]) + normal! g@l + call assert_equal(7, g:OpVal) + " delete the function and try to use g@ + delfunc Test_opfunc2 + call test_garbagecollect_now() + call assert_fails('normal! g@l', 'E933:') + set opfunc= + + " Try to use a function with two arguments for 'operatorfunc' + let g:OpVal = 0 + func! Test_opfunc3(x, y) + let g:OpVal = 4 + endfunc + set opfunc=Test_opfunc3 + call assert_fails('normal! g@l', 'E119:') + call assert_equal(0, g:OpVal) + set opfunc= + delfunc Test_opfunc3 + unlet g:OpVal + + " Try to use a lambda function with two arguments for 'operatorfunc' + set opfunc={x,\ y\ ->\ 'done'} + call assert_fails('normal! g@l', 'E119:') + " clean up unmap <buffer> ,, set opfunc= @@ -491,6 +531,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 +706,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 +726,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 +811,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 +917,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 +948,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 +1008,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 +1334,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 @@ -1327,6 +1455,7 @@ func Test_normal21_nv_hat() edit Xfoo | %bw call assert_fails(':buffer #', 'E86') call assert_fails(':execute "normal! \<C-^>"', 'E23') + call assert_fails("normal i\<C-R>#", 'E23:') " Test for the expected behavior when switching between two named buffers. edit Xfoo | edit Xbar @@ -1420,6 +1549,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 +2005,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 +2049,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 +2203,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 +3054,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 +3076,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 +3148,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 +3370,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 +3464,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..655d537336 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -234,6 +234,7 @@ func Test_complete() new call feedkeys("i\<C-N>\<Esc>", 'xt') bwipe! + call assert_fails('set complete=ix', 'E535:') set complete& endfun @@ -368,9 +369,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 @@ -423,32 +432,37 @@ func Test_copy_context() endfunc func Test_set_ttytype() - " Nvim does not support 'ttytype'. - if !has('nvim') && !has('gui_running') && has('unix') - " Setting 'ttytype' used to cause a double-free when exiting vim and - " when vim is compiled with -DEXITFREE. - set ttytype=ansi - call assert_equal('ansi', &ttytype) - call assert_equal(&ttytype, &term) - set ttytype=xterm - call assert_equal('xterm', &ttytype) - call assert_equal(&ttytype, &term) - try - set ttytype= - call assert_report('set ttytype= did not fail') - catch /E529/ - endtry - - " Some systems accept any terminal name and return dumb settings, - " check for failure of finding the entry and for missing 'cm' entry. - try - set ttytype=xxx - call assert_report('set ttytype=xxx did not fail') - catch /E522\|E437/ - endtry - - set ttytype& - call assert_equal(&ttytype, &term) + throw "Skipped: Nvim does not support 'ttytype'" + CheckUnix + CheckNotGui + + " Setting 'ttytype' used to cause a double-free when exiting vim and + " when vim is compiled with -DEXITFREE. + set ttytype=ansi + call assert_equal('ansi', &ttytype) + call assert_equal(&ttytype, &term) + set ttytype=xterm + call assert_equal('xterm', &ttytype) + call assert_equal(&ttytype, &term) + try + set ttytype= + call assert_report('set ttytype= did not fail') + catch /E529/ + endtry + + " Some systems accept any terminal name and return dumb settings, + " check for failure of finding the entry and for missing 'cm' entry. + try + set ttytype=xxx + call assert_report('set ttytype=xxx did not fail') + catch /E522\|E437/ + endtry + + set ttytype& + call assert_equal(&ttytype, &term) + + if has('gui') && !has('gui_running') + call assert_fails('set term=gui', 'E531:') endif endfunc @@ -766,7 +780,13 @@ func Test_shell() CheckUnix let save_shell = &shell set shell= - call assert_fails('shell', 'E91:') + let caught_e91 = 0 + try + shell + catch /E91:/ + let caught_e91 = 1 + endtry + call assert_equal(1, caught_e91) let &shell = save_shell endfunc @@ -812,11 +832,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_preview.vim b/src/nvim/testdir/test_preview.vim index 6c4ae414d3..b7b908e761 100644 --- a/src/nvim/testdir/test_preview.vim +++ b/src/nvim/testdir/test_preview.vim @@ -1,5 +1,8 @@ " Tests for the preview window +source check.vim +CheckFeature quickfix + func Test_Psearch() " this used to cause ml_get errors help @@ -13,6 +16,8 @@ func Test_Psearch() endfunc func Test_window_preview() + CheckFeature quickfix + " Open a preview window pedit Xa call assert_equal(2, winnr('$')) @@ -32,6 +37,8 @@ func Test_window_preview() endfunc func Test_window_preview_from_help() + CheckFeature quickfix + filetype on call writefile(['/* some C code */'], 'Xpreview.c') help diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index f6d573d76b..8c9e39570f 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -85,6 +85,12 @@ func s:setup_commands(cchar) endif endfunc +" This must be run before any error lists are created. +func Test_AA_cc_no_errors() + call assert_fails('cc', 'E42:') + call assert_fails('ll', 'E42:') +endfunc + " Tests for the :clist and :llist commands func XlistTests(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index 93865869fa..e3ca141328 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -98,8 +98,6 @@ func Do_test_quotestar_for_x11() " Running in a terminal and the GUI is available: Tell the server to open " the GUI and check that the remote command still works. - " Need to wait for the GUI to start up, otherwise the send hangs in trying - " to send to the terminal window. if has('gui_athena') || has('gui_motif') " For those GUIs, ignore the 'failed to create input context' error. call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>") @@ -107,7 +105,10 @@ func Do_test_quotestar_for_x11() call remote_send(name, ":gui -f\<CR>") endif " Wait for the server in the GUI to be up and answering requests. + " First need to wait for the GUI to start up, otherwise the send hangs in + " trying to send to the terminal window. " On some systems and with valgrind this can be very slow. + sleep 1 call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000) call remote_send(name, ":let @* = 'maybe'\<CR>") 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_startup.vim b/src/nvim/testdir/test_startup.vim index 39fafbf7b4..880ca62685 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -603,7 +603,7 @@ func Test_invalid_args() call assert_equal(0, v:shell_error) if has('quickfix') - " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'. + " Detect invalid repeated arguments '-t foo -t foo', '-q foo -q foo'. for opt in ['-t', '-q'] let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n") call assert_equal(1, v:shell_error) @@ -855,7 +855,7 @@ func Test_t_arg() call writefile([' first', ' second', ' third'], 'Xfile1') for t_arg in ['-t second', '-tsecond'] - if RunVim(before, after, '-t second') + if RunVim(before, after, t_arg) call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg) call delete('Xtestout') endif 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_swap.vim b/src/nvim/testdir/test_swap.vim index 923e1cbf50..34d1d585ce 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -2,6 +2,7 @@ source check.vim source shared.vim +source term_util.vim func s:swapname() return trim(execute('swapname')) @@ -374,24 +375,26 @@ func Test_swap_prompt_splitwin() call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))}) call StopVimInTerminal(buf) - " This caused Vim to crash when typing "q". - " TODO: it does not actually reproduce the crash. - call writefile(['au BufAdd * set virtualedit=all'], 'Xvimrc') - - let buf = RunVimInTerminal('-u Xvimrc Xfile1', {'rows': 20, 'wait_for_ruler': 0}) - call TermWait(buf) - call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 20))}) + " This caused Vim to crash when typing "q" at the swap file prompt. + let buf = RunVimInTerminal('-c "au bufadd * let foo_w = wincol()"', {'rows': 18}) + call term_sendkeys(buf, ":e Xfile1\<CR>") + call WaitForAssert({-> assert_match('More', term_getline(buf, 18))}) + call term_sendkeys(buf, " ") + call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 18))}) call term_sendkeys(buf, "q") + call TermWait(buf) + " check that Vim is still running + call term_sendkeys(buf, ":echo 'hello'\<CR>") + call WaitForAssert({-> assert_match('^hello', term_getline(buf, 18))}) + call term_sendkeys(buf, ":%bwipe!\<CR>") + call StopVimInTerminal(buf) %bwipe! call delete('Xfile1') - call delete('Xvimrc') endfunc func Test_swap_symlink() - if !has("unix") - return - endif + CheckUnix call writefile(['text'], 'Xtestfile') silent !ln -s -f Xtestfile Xtestlink diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 7ba0149971..ccff01486e 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -188,22 +188,26 @@ func Test_syntax_completion() call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) " Check that clearing "Aap" avoids it showing up before Boolean. - hi Aap ctermfg=blue + hi @Aap ctermfg=blue call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn list Aap Boolean Character ', @:) - hi clear Aap + call assert_match('^"syn list @Aap @boolean @character ', @:) + hi clear @Aap call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn list Boolean Character ', @:) + call assert_match('^"syn list @boolean @character ', @:) call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn match Boolean Character ', @:) + call assert_match('^"syn match @boolean @character ', @:) + + syn cluster Aax contains=Aap + call feedkeys(":syn list @A\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn list @Aax', @:) endfunc func Test_echohl_completion() call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx') " call assert_equal('"echohl NonText Normal none', @:) - call assert_equal('"echohl NonText Normal NormalFloat NormalNC none', @:) + call assert_equal('"echohl NonText Normal NormalFloat none', @:) endfunc func Test_syntax_arg_skipped() @@ -389,7 +393,7 @@ func Test_invalid_name() syn keyword Nop yes call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') syntax keyword @Wrong bar - call assert_match('W18:', execute('1messages')) + call assert_fails("syntax keyword @#Wrong bar", 'E5248:') syn clear hi clear Nop hi clear @Wrong 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_textformat.vim b/src/nvim/testdir/test_textformat.vim index 0fc56083aa..4eb6e69adf 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -962,78 +962,6 @@ func Test_tw_2_fo_tm_noai() bwipe! endfunc -func Test_tw_2_fo_cqm_com() - new - let t =<< trim END - { - X - Xa - Xaï¼¹ - XY - XYZ - X ï¼¹ - X YZ - XX - XXa - XXY - } - END - call setline(1, t) - call cursor(2, 1) - - set tw=2 fo=cqm comments=n:X - exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" - let t =<< trim END - X - Xa - Xaï¼¹ - XY - XYZ - X ï¼¹ - X YZ - XX - XXa - XXY - END - exe "normal o\n" . join(t, "\n") - - let expected =<< trim END - { - X - Xa - Xa - XY - XY - XY - XZ - X ï¼¹ - X ï¼¹ - X Z - XX - XXa - XXY - - X - Xa - Xa - XY - XY - XY - XZ - X ï¼¹ - X ï¼¹ - X Z - XX - XXa - XXY - } - END - call assert_equal(expected, getline(1, '$')) - - set tw& fo& comments& - bwipe! -endfunc - func Test_tw_2_fo_tm_replace() new let t =<< trim END @@ -1161,140 +1089,6 @@ func Test_whichwrap_multi_byte() bwipe! endfunc -" Test for automatically adding comment leaders in insert mode -func Test_threepiece_comment() - new - setlocal expandtab - call setline(1, ["\t/*"]) - setlocal formatoptions=croql - call cursor(1, 3) - call feedkeys("A\<cr>\<cr>/", 'tnix') - call assert_equal(["\t/*", " *", " */"], getline(1, '$')) - - " If a comment ends in a single line, then don't add it in the next line - %d - call setline(1, '/* line1 */') - call feedkeys("A\<CR>next line", 'xt') - call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) - - %d - " Copy the trailing indentation from the leader comment to a new line - setlocal autoindent noexpandtab - call feedkeys("a\t/*\tone\ntwo\n/", 'xt') - call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) - close! -endfunc - -" Test for the 'f' flag in 'comments' (only the first line has the comment -" string) -func Test_firstline_comment() - new - setlocal comments=f:- fo+=ro - exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" - call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) - %d - setlocal comments=:- - exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" - call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) - %bw! -endfunc - -" Test for the 'r' flag in 'comments' (right align comment) -func Test_comment_rightalign() - new - setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro - exe "normal i=\<C-C>o\t /***\nD\n/" - exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" - let expected =<< trim END - = - A - /*** - ** B - ** C - ** D - ** E - ** F - ******/ - G - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'b' flag in 'comments' -func Test_comment_blank() - new - setlocal comments=b:* fo+=ro - exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" - let expected =<< trim END - A - *B - * C - * D - * E - * F - *G - H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'n' flag in comments -func Test_comment_nested() - new - setlocal comments=n:> fo+=ro - exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" - exe "normal 5GOE\<C-C>6GoG" - let expected =<< trim END - > A - > B - > C - > D - >>>> E - >>>> F - >>>> G - >>>> H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for a space character in 'comments' setting -func Test_comment_space() - new - setlocal comments=b:\ > fo+=ro - exe "normal i> B\nD\<C-C>ggOA\<C-C>joC" - exe "normal Go > F\nH\<C-C>kOE\<C-C>joG" - let expected =<< trim END - A - > B - C - D - > E - > F - > G - > H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'O' flag in 'comments' -func Test_comment_O() - new - setlocal comments=Ob:* fo+=ro - exe "normal i* B\nD\<C-C>kOA\<C-C>joC" - let expected =<< trim END - A - * B - * C - * D - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - " Test for 'a' and 'w' flags in 'formatoptions' func Test_fo_a_w() new @@ -1334,25 +1128,6 @@ func Test_fo_a_w() %bw! endfunc -" Test for 'j' flag in 'formatoptions' -func Test_fo_j() - new - setlocal fo+=j comments=:// - call setline(1, ['i++; // comment1', ' // comment2']) - normal J - call assert_equal('i++; // comment1 comment2', getline(1)) - setlocal fo-=j - call setline(1, ['i++; // comment1', ' // comment2']) - normal J - call assert_equal('i++; // comment1 // comment2', getline(1)) - " Test with nested comments - setlocal fo+=j comments=n:>,n:) - call setline(1, ['i++; > ) > ) comment1', ' > ) comment2']) - normal J - call assert_equal('i++; > ) > ) comment1 comment2', getline(1)) - %bw! -endfunc - " Test for formatting lines using gq in visual mode func Test_visual_gq_format() new @@ -1487,53 +1262,6 @@ func Test_fo_2() close! endfunc -" Test for formatting lines where only the first line has a comment. -func Test_fo_gq_with_firstline_comment() - new - setlocal formatoptions=tcq - call setline(1, ['- one two', 'three']) - normal gggqG - call assert_equal(['- one two three'], getline(1, '$')) - - %d - call setline(1, ['- one', '- two']) - normal gggqG - call assert_equal(['- one', '- two'], getline(1, '$')) - close! -endfunc - -" Test for trying to join a comment line with a non-comment line -func Test_join_comments() - new - call setline(1, ['one', '/* two */', 'three']) - normal gggqG - call assert_equal(['one', '/* two */', 'three'], getline(1, '$')) - close! -endfunc - -" Test for using 'a' in 'formatoptions' with comments -func Test_autoformat_comments() - new - setlocal formatoptions+=a - call feedkeys("a- one\n- two\n", 'xt') - call assert_equal(['- one', '- two', ''], getline(1, '$')) - - %d - call feedkeys("a\none\n", 'xt') - call assert_equal(['', 'one', ''], getline(1, '$')) - - setlocal formatoptions+=aw - %d - call feedkeys("aone \ntwo\n", 'xt') - call assert_equal(['one two', ''], getline(1, '$')) - - %d - call feedkeys("aone\ntwo\n", 'xt') - call assert_equal(['one', 'two', ''], getline(1, '$')) - - close! -endfunc - " This was leaving the cursor after the end of a line. Complicated way to " have the problem show up with valgrind. func Test_correct_cursor_position() 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_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 646594e482..d71bb5bbb8 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -2027,16 +2027,179 @@ func Test_try_catch_verbose() endtry redir END let expected = [ - \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', - \ '', - \ 'Exception caught: Vim(echo):E121: Undefined variable: i', - \ '', - \ 'Exception finished: Vim(echo):E121: Undefined variable: i' - \ ] + \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', '', + \ 'Exception caught: Vim(echo):E121: Undefined variable: i', '', + \ 'Exception finished: Vim(echo):E121: Undefined variable: i'] call assert_equal(expected, split(msg, "\n")) + + " Test for verbose messages displayed when an exception is discarded + redir => msg + try + try + throw 'abc' + finally + throw 'xyz' + endtry + catch + endtry + redir END + let expected = [ + \ 'Exception thrown: abc', '', + \ 'Exception made pending: abc', '', + \ 'Exception thrown: xyz', '', + \ 'Exception discarded: abc', '', + \ 'Exception caught: xyz', '', + \ 'Exception finished: xyz'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :throw is resumed after :finally + redir => msg + try + try + throw 'abc' + finally + endtry + catch + endtry + redir END + let expected = [ + \ 'Exception thrown: abc', '', + \ 'Exception made pending: abc', '', + \ 'Exception resumed: abc', '', + \ 'Exception caught: abc', '', + \ 'Exception finished: abc'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :break is resumed after :finally + redir => msg + for i in range(1) + try + break + finally + endtry + endfor + redir END + let expected = [':break made pending', '', ':break resumed'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :continue is resumed after :finally + redir => msg + for i in range(1) + try + continue + finally + endtry + endfor + redir END + let expected = [':continue made pending', '', ':continue resumed'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :return is resumed after :finally + func Xtest() + try + return 'vim' + finally + endtry + endfunc + redir => msg + call Xtest() + redir END + let expected = [ + \ 'calling Xtest()', '', + \ ':return vim made pending', '', + \ ':return vim resumed', '', + \ 'Xtest returning ''vim''', '', + \ 'continuing in Test_try_catch_verbose'] + call assert_equal(expected, split(msg, "\n")) + delfunc Xtest + + " Test for messages displayed when :finish is resumed after :finally + call writefile(['try', 'finish', 'finally', 'endtry'], 'Xscript') + redir => msg + source Xscript + redir END + let expected = [ + \ ':finish made pending', '', + \ ':finish resumed', '', + \ 'finished sourcing Xscript', + \ 'continuing in Test_try_catch_verbose'] + call assert_equal(expected, split(msg, "\n")[1:]) + call delete('Xscript') + + " Test for messages displayed when a pending :continue is discarded by an + " exception in a finally handler + redir => msg + try + for i in range(1) + try + continue + finally + throw 'abc' + endtry + endfor + catch + endtry + redir END + let expected = [ + \ ':continue made pending', '', + \ 'Exception thrown: abc', '', + \ ':continue discarded', '', + \ 'Exception caught: abc', '', + \ 'Exception finished: abc'] + call assert_equal(expected, split(msg, "\n")) + set verbose& endfunc +" Test for throwing an exception from a BufEnter autocmd {{{1 +func Test_BufEnter_exception() + augroup bufenter_exception + au! + autocmd BufEnter Xfile1 throw 'abc' + augroup END + + let caught_abc = 0 + try + sp Xfile1 + catch /^abc/ + let caught_abc = 1 + endtry + call assert_equal(1, caught_abc) + call assert_equal(1, winnr('$')) + + augroup bufenter_exception + au! + augroup END + augroup! bufenter_exception + %bwipe! + + " Test for recursively throwing exceptions in autocmds + augroup bufenter_exception + au! + autocmd BufEnter Xfile1 throw 'bufenter' + autocmd BufLeave Xfile1 throw 'bufleave' + augroup END + + let ex_count = 0 + try + try + sp Xfile1 + catch /^bufenter/ + let ex_count += 1 + endtry + catch /^bufleave/ + let ex_count += 10 + endtry + call assert_equal(10, ex_count) + call assert_equal(2, winnr('$')) + + augroup bufenter_exception + au! + augroup END + augroup! bufenter_exception + %bwipe! +endfunc + " Test for using throw in a called function with following error {{{1 func Test_user_command_throw_in_function_call() let lines =<< trim END 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_user_func.vim b/src/nvim/testdir/test_user_func.vim index 5231ef7b4f..c14624f5b4 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -169,3 +169,10 @@ endfunc func Test_failed_call_in_try() try | call UnknownFunc() | catch | endtry endfunc + +" Test for listing user-defined functions +func Test_function_list() + call assert_fails("function Xabc", 'E123:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab 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_viminfo.vim b/src/nvim/testdir/test_viminfo.vim new file mode 100644 index 0000000000..2d6d598011 --- /dev/null +++ b/src/nvim/testdir/test_viminfo.vim @@ -0,0 +1,21 @@ + +" Test for errors in setting 'viminfo' +func Test_viminfo_option_error() + " Missing number + call assert_fails('set viminfo=\"', 'E526:') + for c in split("'/:<@s", '\zs') + call assert_fails('set viminfo=' .. c, 'E526:') + endfor + + " Missing comma + call assert_fails('set viminfo=%10!', 'E527:') + call assert_fails('set viminfo=!%10', 'E527:') + call assert_fails('set viminfo=h%10', 'E527:') + call assert_fails('set viminfo=c%10', 'E527:') + call assert_fails('set viminfo=:10%10', 'E527:') + + " Missing ' setting + call assert_fails('set viminfo=%10', 'E528:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index de4629451b..97e879c9ef 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 @@ -1812,6 +1829,9 @@ func Test_missing_end() endtry call assert_equal(1, caught_e733) + " Using endfunc with :if + call assert_fails('exe "if 1 | endfunc | endif"', 'E193:') + " Missing 'in' in a :for statement call assert_fails('for i range(1) | endfor', 'E690:') endfunc @@ -1858,6 +1878,15 @@ func Test_deep_nest() @a let @a = '' endfunc + + " Deep nesting of function ... endfunction + func Test5() + let @a = join(repeat(['function X()'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endfunction'], 51), "\n") + @a + let @a = '' + endfunc [SCRIPT] call writefile(lines, 'Xscript') @@ -1865,20 +1894,31 @@ func Test_deep_nest() " Deep nesting of if ... endif call term_sendkeys(buf, ":call Test1()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))}) " Deep nesting of for ... endfor call term_sendkeys(buf, ":call Test2()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) " Deep nesting of while ... endwhile call term_sendkeys(buf, ":call Test3()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) " Deep nesting of try ... endtry call term_sendkeys(buf, ":call Test4()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))}) + " Deep nesting of function ... endfunction + call term_sendkeys(buf, ":call Test5()\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^E1058:', term_getline(buf, 4))}) + call term_sendkeys(buf, "\<C-C>\n") + call term_wait(buf) + "let l = '' "for i in range(1, 6) " let l ..= term_getline(buf, i) . "\n" 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..4a252dca3e 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. @@ -323,19 +326,19 @@ static int assert_beeps(typval_T *argvars, bool no_beep) } /// "assert_beeps(cmd [, error])" function -void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_beeps(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_beeps(argvars, false); } /// "assert_nobeep(cmd [, error])" function -void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_nobeep(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_beeps(argvars, true); } /// "assert_equal(expected, actual[, msg])" function -void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_equal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } @@ -430,19 +433,19 @@ static int assert_equalfile(typval_T *argvars) } /// "assert_equalfile(fname-one, fname-two[, msg])" function -void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_equalfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equalfile(argvars); } /// "assert_notequal(expected, actual[, msg])" function -void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_notequal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); } /// "assert_exception(string[, msg])" function -void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -454,7 +457,7 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_clear(&ga); rettv->vval.v_number = 1; } else if (error != NULL - && strstr((char *)get_vim_var_str(VV_EXCEPTION), error) == NULL) { + && strstr(get_vim_var_str(VV_EXCEPTION), error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER); @@ -465,7 +468,7 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "assert_fails(cmd [, error [, msg]])" function -void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; @@ -490,7 +493,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const error = tv_get_string_buf_chk(&argvars[1], buf); if (error == NULL - || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL) { + || strstr(get_vim_var_str(VV_ERRMSG), error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); @@ -510,7 +513,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // "assert_false(actual[, msg])" function -void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_false(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_bool(argvars, false); } @@ -571,25 +574,25 @@ static int assert_inrange(typval_T *argvars) } /// "assert_inrange(lower, upper[, msg])" function -void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_inrange(argvars); } /// "assert_match(pattern, actual[, msg])" function -void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); } /// "assert_notmatch(pattern, actual[, msg])" function -void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_notmatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); } /// "assert_report(msg)" function -void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_report(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -601,13 +604,13 @@ void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "assert_true(actual[, msg])" function -void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_true(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_bool(argvars, true); } /// "test_garbagecollect_now()" function -void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is dangerous, any Lists and Dicts used internally may be freed // while still in use. @@ -615,7 +618,7 @@ void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "test_write_list_log()" function -void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) +void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, EvalFuncData fptr) { const char *const fname = tv_get_string_chk(&argvars[0]); if (fname == NULL) { diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c new file mode 100644 index 0000000000..125146f712 --- /dev/null +++ b/src/nvim/textformat.c @@ -0,0 +1,1127 @@ +// 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 + +// textformat.c: text formatting functions + +#include <stdbool.h> + +#include "nvim/ascii.h" +#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/getchar.h" +#include "nvim/globals.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/move.h" +#include "nvim/normal.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/pos.h" +#include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textformat.c.generated.h" +#endif + +static bool did_add_space = false; ///< auto_format() added an extra space + ///< under the cursor + +#define WHITECHAR(cc) (ascii_iswhite(cc) \ + && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1))) + +/// Return true if format option 'x' is in effect. +/// Take care of no formatting when 'paste' is set. +bool has_format_option(int x) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (p_paste) { + return false; + } + return vim_strchr(curbuf->b_p_fo, x) != NULL; +} + +/// Format text at the current insert position. +/// +/// If the INSCHAR_COM_LIST flag is present, then the value of second_indent +/// will be the comment leader length sent to open_line(). +/// +/// @param c character to be inserted (can be NUL) +void internal_format(int textwidth, int second_indent, int flags, bool format_only, int c) +{ + int cc; + int save_char = NUL; + bool haveto_redraw = false; + const bool fo_ins_blank = has_format_option(FO_INS_BLANK); + const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); + const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); + const bool fo_white_par = has_format_option(FO_WHITE_PAR); + bool first_line = true; + colnr_T leader_len; + bool no_leader = false; + int do_comments = (flags & INSCHAR_DO_COM); + int has_lbr = curwin->w_p_lbr; + + // make sure win_lbr_chartabsize() counts correctly + curwin->w_p_lbr = false; + + // When 'ai' is off we don't want a space under the cursor to be + // deleted. Replace it with an 'x' temporarily. + if (!curbuf->b_p_ai + && !(State & VREPLACE_FLAG)) { + cc = gchar_cursor(); + if (ascii_iswhite(cc)) { + save_char = cc; + pchar_cursor('x'); + } + } + + // Repeat breaking lines, until the current line is not too long. + while (!got_int) { + int startcol; // Cursor column at entry + int wantcol; // column at textwidth border + int foundcol; // column for start of spaces + int end_foundcol = 0; // column for start of word + colnr_T len; + colnr_T virtcol; + int orig_col = 0; + char_u *saved_text = NULL; + colnr_T col; + colnr_T end_col; + bool did_do_comment = false; + + virtcol = get_nolist_virtcol() + + char2cells(c != NUL ? c : gchar_cursor()); + if (virtcol <= (colnr_T)textwidth) { + break; + } + + if (no_leader) { + do_comments = false; + } else if (!(flags & INSCHAR_FORMAT) + && has_format_option(FO_WRAP_COMS)) { + do_comments = true; + } + + // Don't break until after the comment leader + if (do_comments) { + char_u *line = get_cursor_line_ptr(); + leader_len = get_leader_len((char *)line, NULL, false, true); + if (leader_len == 0 && curbuf->b_p_cin) { + // Check for a line comment after code. + int comment_start = check_linecomment((char *)line); + if (comment_start != MAXCOL) { + leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); + if (leader_len != 0) { + leader_len += comment_start; + } + } + } + } else { + leader_len = 0; + } + + // If the line doesn't start with a comment leader, then don't + // start one in a following broken line. Avoids that a %word + // moved to the start of the next line causes all following lines + // to start with %. + if (leader_len == 0) { + no_leader = true; + } + if (!(flags & INSCHAR_FORMAT) + && leader_len == 0 + && !has_format_option(FO_WRAP)) { + break; + } + if ((startcol = curwin->w_cursor.col) == 0) { + break; + } + + // find column of textwidth border + coladvance((colnr_T)textwidth); + wantcol = curwin->w_cursor.col; + + curwin->w_cursor.col = startcol; + foundcol = 0; + int skip_pos = 0; + + // Find position to break at. + // Stop at first entered white when 'formatoptions' has 'v' + while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) + || (flags & INSCHAR_FORMAT) + || curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col >= Insstart.col) { + if (curwin->w_cursor.col == startcol && c != NUL) { + cc = c; + } else { + cc = gchar_cursor(); + } + if (WHITECHAR(cc)) { + // remember position of blank just before text + end_col = curwin->w_cursor.col; + + // find start of sequence of blanks + int wcc = 0; // counter for whitespace chars + while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { + dec_cursor(); + cc = gchar_cursor(); + + // Increment count of how many whitespace chars in this + // group; we only need to know if it's more than one. + if (wcc < 2) { + wcc++; + } + } + if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { + break; // only spaces in front of text + } + + // Don't break after a period when 'formatoptions' has 'p' and + // there are less than two spaces. + if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { + continue; + } + + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { + break; + } + + if (has_format_option(FO_ONE_LETTER)) { + // do not break after one-letter words + if (curwin->w_cursor.col == 0) { + break; // one-letter word at begin + } + // do not break "#a b" when 'tw' is 2 + if (curwin->w_cursor.col <= leader_len) { + break; + } + col = curwin->w_cursor.col; + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) { + continue; // one-letter, continue + } + curwin->w_cursor.col = col; + } + + inc_cursor(); + + end_foundcol = end_col + 1; + foundcol = curwin->w_cursor.col; + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + break; + } + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { + int ncc; + bool allow_break; + + // Break after or before a multi-byte character. + if (curwin->w_cursor.col != startcol) { + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { + break; + } + col = curwin->w_cursor.col; + inc_cursor(); + ncc = gchar_cursor(); + allow_break = utf_allow_break(cc, ncc); + + // If we have already checked this position, skip! + if (curwin->w_cursor.col != skip_pos && allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + break; + } + } + curwin->w_cursor.col = col; + } + + if (curwin->w_cursor.col == 0) { + break; + } + + ncc = cc; + col = curwin->w_cursor.col; + + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) { + continue; // break with space + } + // Don't break until after the comment leader. + if (curwin->w_cursor.col < leader_len) { + break; + } + + curwin->w_cursor.col = col; + skip_pos = curwin->w_cursor.col; + + allow_break = utf_allow_break(cc, ncc); + + // Must handle this to respect line break prohibition. + if (allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + } + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + const bool ncc_allow_break = utf_allow_break_before(ncc); + + if (allow_break) { + break; + } + if (!ncc_allow_break && !fo_rigor_tw) { + // Enable at most 1 punct hang outside of textwidth. + if (curwin->w_cursor.col == startcol) { + // We are inserting a non-breakable char, postpone + // line break check to next insert. + end_foundcol = foundcol = 0; + break; + } + + // Neither cc nor ncc is NUL if we are here, so + // it's safe to inc_cursor. + col = curwin->w_cursor.col; + + inc_cursor(); + cc = ncc; + ncc = gchar_cursor(); + // handle insert + ncc = (ncc != NUL) ? ncc : c; + + allow_break = utf_allow_break(cc, ncc); + + if (allow_break) { + // Break only when we are not at end of line. + end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; + break; + } + curwin->w_cursor.col = col; + } + } + } + if (curwin->w_cursor.col == 0) { + break; + } + dec_cursor(); + } + + if (foundcol == 0) { // no spaces, cannot break line + curwin->w_cursor.col = startcol; + break; + } + + // Going to break the line, remove any "$" now. + undisplay_dollar(); + + // Offset between cursor position and line break is used by replace + // stack functions. MODE_VREPLACE does not use this, and backspaces + // over the text instead. + if (State & VREPLACE_FLAG) { + orig_col = startcol; // Will start backspacing from here + } else { + replace_offset = startcol - end_foundcol; + } + + // adjust startcol for spaces that will be deleted and + // characters that will remain on top line + curwin->w_cursor.col = foundcol; + while ((cc = gchar_cursor(), WHITECHAR(cc)) + && (!fo_white_par || curwin->w_cursor.col < startcol)) { + inc_cursor(); + } + startcol -= curwin->w_cursor.col; + if (startcol < 0) { + startcol = 0; + } + + if (State & VREPLACE_FLAG) { + // In MODE_VREPLACE state, we will backspace over the text to be + // wrapped, so save a copy now to put on the next line. + saved_text = vim_strsave(get_cursor_pos_ptr()); + curwin->w_cursor.col = orig_col; + saved_text[startcol] = NUL; + + // Backspace over characters that will move to the next line + if (!fo_white_par) { + backspace_until_column(foundcol); + } + } else { + // put cursor after pos. to break line + if (!fo_white_par) { + curwin->w_cursor.col = foundcol; + } + } + + // Split the line just before the margin. + // Only insert/delete lines, but don't really redraw the window. + open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX + + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + + (do_comments ? OPENLINE_DO_COM : 0) + + OPENLINE_FORMAT + + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), + ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), + &did_do_comment); + if (!(flags & INSCHAR_COM_LIST)) { + old_indent = 0; + } + + // If a comment leader was inserted, may also do this on a following + // line. + if (did_do_comment) { + no_leader = false; + } + + replace_offset = 0; + if (first_line) { + if (!(flags & INSCHAR_COM_LIST)) { + // This section is for auto-wrap of numeric lists. When not + // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST + // flag will be set and open_line() will handle it (as seen + // above). The code here (and in get_number_indent()) will + // recognize comments if needed... + if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { + second_indent = get_number_indent(curwin->w_cursor.lnum - 1); + } + if (second_indent >= 0) { + if (State & VREPLACE_FLAG) { + change_indent(INDENT_SET, second_indent, false, NUL, true); + } else if (leader_len > 0 && second_indent - leader_len > 0) { + int padding = second_indent - leader_len; + + // We started at the first_line of a numbered list + // that has a comment. the open_line() function has + // inserted the proper comment leader and positioned + // the cursor at the end of the split line. Now we + // add the additional whitespace needed after the + // comment leader for the numbered list. + for (int i = 0; i < padding; i++) { + ins_str(" "); + } + changed_bytes(curwin->w_cursor.lnum, leader_len); + } else { + (void)set_indent(second_indent, SIN_CHANGED); + } + } + } + first_line = false; + } + + if (State & VREPLACE_FLAG) { + // In MODE_VREPLACE state we have backspaced over the text to be + // moved, now we re-insert it into the new line. + ins_bytes((char *)saved_text); + xfree(saved_text); + } else { + // Check if cursor is not past the NUL off the line, cindent + // may have added or removed indent. + curwin->w_cursor.col += startcol; + len = (colnr_T)STRLEN(get_cursor_line_ptr()); + if (curwin->w_cursor.col > len) { + curwin->w_cursor.col = len; + } + } + + haveto_redraw = true; + set_can_cindent(true); + // moved the cursor, don't autoindent or cindent now + did_ai = false; + did_si = false; + can_si = false; + can_si_back = false; + line_breakcheck(); + } + + if (save_char != NUL) { // put back space after cursor + pchar_cursor((char_u)save_char); + } + + curwin->w_p_lbr = has_lbr; + + if (!format_only && haveto_redraw) { + update_topline(curwin); + redraw_curbuf_later(UPD_VALID); + } +} + +/// Blank lines, and lines containing only the comment leader, are left +/// untouched by the formatting. The function returns true in this +/// case. It also returns true when a line starts with the end of a comment +/// ('e' in comment flags), so that this line is skipped, and not joined to the +/// previous line. A new paragraph starts after a blank line, or when the +/// comment leader changes. +static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, bool do_comments) +{ + char_u *flags = NULL; // init for GCC + char_u *ptr; + + ptr = ml_get(lnum); + if (do_comments) { + *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); + } else { + *leader_len = 0; + } + + if (*leader_len > 0) { + // Search for 'e' flag in comment leader flags. + flags = *leader_flags; + while (*flags && *flags != ':' && *flags != COM_END) { + flags++; + } + } + + return *skipwhite((char *)ptr + *leader_len) == NUL + || (*leader_len > 0 && *flags == COM_END) + || startPS(lnum, NUL, false); +} + +/// @return true if line "lnum" ends in a white character. +static bool ends_in_white(linenr_T lnum) +{ + char_u *s = ml_get(lnum); + size_t l; + + if (*s == NUL) { + return false; + } + l = STRLEN(s) - 1; + return ascii_iswhite(s[l]); +} + +/// @return true if the two comment leaders given are the same. +/// +/// @param lnum The first line. White-space is ignored. +/// +/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'. +static bool same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, int leader2_len, + char_u *leader2_flags) +{ + int idx1 = 0, idx2 = 0; + char_u *p; + char_u *line1; + char_u *line2; + + if (leader1_len == 0) { + return leader2_len == 0; + } + + // If first leader has 'f' flag, the lines can be joined only if the + // second line does not have a leader. + // If first leader has 'e' flag, the lines can never be joined. + // If first leader has 's' flag, the lines can only be joined if there is + // some text after it and the second line has the 'm' flag. + if (leader1_flags != NULL) { + for (p = leader1_flags; *p && *p != ':'; p++) { + if (*p == COM_FIRST) { + return leader2_len == 0; + } + if (*p == COM_END) { + return false; + } + if (*p == COM_START) { + if (*(ml_get(lnum) + leader1_len) == NUL) { + return false; + } + if (leader2_flags == NULL || leader2_len == 0) { + return false; + } + for (p = leader2_flags; *p && *p != ':'; p++) { + if (*p == COM_MIDDLE) { + return true; + } + } + return false; + } + } + } + + // Get current line and next line, compare the leaders. + // The first line has to be saved, only one line can be locked at a time. + line1 = vim_strsave(ml_get(lnum)); + for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {} + line2 = ml_get(lnum + 1); + for (idx2 = 0; idx2 < leader2_len; idx2++) { + if (!ascii_iswhite(line2[idx2])) { + if (line1[idx1++] != line2[idx2]) { + break; + } + } else { + while (ascii_iswhite(line1[idx1])) { + idx1++; + } + } + } + xfree(line1); + + return idx2 == leader2_len && idx1 == leader1_len; +} + +/// Used for auto-formatting. +/// +/// @return true when a paragraph starts in line "lnum". +/// false when the previous line is in the same paragraph. +static bool paragraph_start(linenr_T lnum) +{ + char_u *p; + int leader_len = 0; // leader len of current line + char_u *leader_flags = NULL; // flags for leader of current line + int next_leader_len = 0; // leader len of next line + char_u *next_leader_flags = NULL; // flags for leader of next line + + if (lnum <= 1) { + return true; // start of the file + } + p = ml_get(lnum - 1); + if (*p == NUL) { + return true; // after empty line + } + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { + return true; // after non-paragraph line + } + + if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { + return true; // "lnum" is not a paragraph line + } + + if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) { + return true; // missing trailing space in previous line. + } + if (has_format_option(FO_Q_NUMBER) && (get_number_indent(lnum) > 0)) { + return true; // numbered item starts in "lnum". + } + if (!same_leader(lnum - 1, leader_len, leader_flags, + next_leader_len, next_leader_flags)) { + return true; // change of comment leader. + } + return false; +} + +/// Called after inserting or deleting text: When 'formatoptions' includes the +/// 'a' flag format from the current line until the end of the paragraph. +/// Keep the cursor at the same position relative to the text. +/// The caller must have saved the cursor line for undo, following ones will be +/// saved here. +/// +/// @param trailblank when true also format with trailing blank +/// @param prev_line may start in previous line +void auto_format(bool trailblank, bool prev_line) +{ + pos_T pos; + colnr_T len; + char_u *old; + char_u *new, *pnew; + int wasatend; + int cc; + + if (!has_format_option(FO_AUTO)) { + return; + } + + pos = curwin->w_cursor; + old = get_cursor_line_ptr(); + + // may remove added space + check_auto_format(false); + + // Don't format in Insert mode when the cursor is on a trailing blank, the + // user might insert normal text next. Also skip formatting when "1" is + // in 'formatoptions' and there is a single character before the cursor. + // Otherwise the line would be broken and when typing another non-white + // next they are not joined back together. + wasatend = (pos.col == (colnr_T)STRLEN(old)); + if (*old != NUL && !trailblank && wasatend) { + dec_cursor(); + cc = gchar_cursor(); + if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 + && has_format_option(FO_ONE_LETTER)) { + dec_cursor(); + } + cc = gchar_cursor(); + if (WHITECHAR(cc)) { + curwin->w_cursor = pos; + return; + } + curwin->w_cursor = pos; + } + + // With the 'c' flag in 'formatoptions' and 't' missing: only format + // comments. + if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) + && get_leader_len((char *)old, NULL, false, true) == 0) { + return; + } + + // May start formatting in a previous line, so that after "x" a word is + // moved to the previous line if it fits there now. Only when this is not + // the start of a paragraph. + if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { + curwin->w_cursor.lnum--; + if (u_save_cursor() == FAIL) { + return; + } + } + + // Do the formatting and restore the cursor position. "saved_cursor" will + // be adjusted for the text formatting. + saved_cursor = pos; + format_lines((linenr_T) - 1, false); + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + // "cannot happen" + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); + } else { + check_cursor_col(); + } + + // Insert mode: If the cursor is now after the end of the line while it + // previously wasn't, the line was broken. Because of the rule above we + // need to add a space when 'w' is in 'formatoptions' to keep a paragraph + // formatted. + if (!wasatend && has_format_option(FO_WHITE_PAR)) { + new = get_cursor_line_ptr(); + len = (colnr_T)STRLEN(new); + if (curwin->w_cursor.col == len) { + pnew = vim_strnsave(new, (size_t)len + 2); + pnew[len] = ' '; + pnew[len + 1] = NUL; + ml_replace(curwin->w_cursor.lnum, (char *)pnew, false); + // remove the space later + did_add_space = true; + } else { + // may remove added space + check_auto_format(false); + } + } + + check_cursor(); +} + +/// When an extra space was added to continue a paragraph for auto-formatting, +/// delete it now. The space must be under the cursor, just after the insert +/// position. +/// +/// @param end_insert true when ending Insert mode +void check_auto_format(bool end_insert) +{ + int c = ' '; + int cc; + + if (did_add_space) { + cc = gchar_cursor(); + if (!WHITECHAR(cc)) { + // Somehow the space was removed already. + did_add_space = false; + } else { + if (!end_insert) { + inc_cursor(); + c = gchar_cursor(); + dec_cursor(); + } + if (c != NUL) { + // The space is no longer at the end of the line, delete it. + del_char(false); + did_add_space = false; + } + } + } +} + +/// Find out textwidth to be used for formatting: +/// if 'textwidth' option is set, use it +/// else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' +/// if invalid value, use 0. +/// Set default to window width (maximum 79) for "gq" operator. +/// +/// @param ff force formatting (for "gq" command) +int comp_textwidth(bool ff) +{ + int textwidth = (int)curbuf->b_p_tw; + if (textwidth == 0 && curbuf->b_p_wm) { + // The width is the window width minus 'wrapmargin' minus all the + // things that add to the margin. + textwidth = curwin->w_width_inner - (int)curbuf->b_p_wm; + if (cmdwin_type != 0) { + textwidth -= 1; + } + textwidth -= win_fdccol_count(curwin); + textwidth -= win_signcol_count(curwin); + + if (curwin->w_p_nu || curwin->w_p_rnu) { + textwidth -= 8; + } + } + if (textwidth < 0) { + textwidth = 0; + } + if (ff && textwidth == 0) { + textwidth = curwin->w_width_inner - 1; + if (textwidth > 79) { + textwidth = 79; + } + } + return textwidth; +} + +/// Implementation of the format operator 'gq'. +/// +/// @param keep_cursor keep cursor on same text char +void op_format(oparg_T *oap, bool keep_cursor) +{ + linenr_T old_line_count = curbuf->b_ml.ml_line_count; + + // Place the cursor where the "gq" or "gw" command was given, so that "u" + // can put it back there. + curwin->w_cursor = oap->cursor_start; + + if (u_save((linenr_T)(oap->start.lnum - 1), + (linenr_T)(oap->end.lnum + 1)) == FAIL) { + return; + } + curwin->w_cursor = oap->start; + + if (oap->is_VIsual) { + // When there is no change: need to remove the Visual selection + redraw_curbuf_later(UPD_INVERTED); + } + + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // Set '[ mark at the start of the formatted area + curbuf->b_op_start = oap->start; + } + + // For "gw" remember the cursor position and put it back below (adjusted + // for joined and split lines). + if (keep_cursor) { + saved_cursor = oap->cursor_start; + } + + format_lines((linenr_T)oap->line_count, keep_cursor); + + // Leave the cursor at the first non-blank of the last formatted line. + // If the cursor was moved one line back (e.g. with "Q}") go to the next + // line, so "." will do the next lines. + if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum++; + } + beginline(BL_WHITE | BL_FIX); + old_line_count = curbuf->b_ml.ml_line_count - old_line_count; + msgmore(old_line_count); + + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // put '] mark on the end of the formatted area + curbuf->b_op_end = curwin->w_cursor; + } + + if (keep_cursor) { + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + // formatting may have made the cursor position invalid + check_cursor(); + } + + if (oap->is_VIsual) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_old_cursor_lnum != 0) { + // When lines have been inserted or deleted, adjust the end of + // the Visual area to be redrawn. + if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { + wp->w_old_cursor_lnum += old_line_count; + } else { + wp->w_old_visual_lnum += old_line_count; + } + } + } + } +} + +/// Implementation of the format operator 'gq' for when using 'formatexpr'. +void op_formatexpr(oparg_T *oap) +{ + if (oap->is_VIsual) { + // When there is no change: need to remove the Visual selection + redraw_curbuf_later(UPD_INVERTED); + } + + if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) { + // As documented: when 'formatexpr' returns non-zero fall back to + // internal formatting. + op_format(oap, false); + } +} + +/// @param c character to be inserted +int fex_format(linenr_T lnum, long count, int c) +{ + int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL); + int r; + + // Set v:lnum to the first line number and v:count to the number of lines. + // Set v:char to the character to be inserted (can be NUL). + set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); + set_vim_var_nr(VV_COUNT, (varnumber_T)count); + set_vim_var_char(c); + + // Make a copy, the option could be changed while calling it. + char *fex = xstrdup(curbuf->b_p_fex); + // Evaluate the function. + if (use_sandbox) { + sandbox++; + } + r = (int)eval_to_number(fex); + if (use_sandbox) { + sandbox--; + } + + set_vim_var_string(VV_CHAR, NULL, -1); + xfree(fex); + + return r; +} + +/// @param line_count number of lines to format, starting at the cursor position. +/// when negative, format until the end of the paragraph. +/// +/// Lines after the cursor line are saved for undo, caller must have saved the +/// first line. +/// +/// @param avoid_fex don't use 'formatexpr' +void format_lines(linenr_T line_count, bool avoid_fex) +{ + bool is_not_par; // current line not part of parag. + bool next_is_not_par; // next line not part of paragraph + bool is_end_par; // at end of paragraph + bool prev_is_end_par = false; // prev. line not part of parag. + bool next_is_start_par = false; + int leader_len = 0; // leader len of current line + int next_leader_len; // leader len of next line + char_u *leader_flags = NULL; // flags for leader of current line + char_u *next_leader_flags = NULL; // flags for leader of next line + bool advance = true; + int second_indent = -1; // indent for second line (comment aware) + bool first_par_line = true; + int smd_save; + long count; + bool need_set_indent = true; // set indent of next paragraph + linenr_T first_line = curwin->w_cursor.lnum; + bool force_format = false; + const int old_State = State; + + // length of a line to force formatting: 3 * 'tw' + const int max_len = comp_textwidth(true) * 3; + + // check for 'q', '2' and '1' in 'formatoptions' + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + int do_comments_list = 0; // format comments with 'n' or '2' + const bool do_second_indent = has_format_option(FO_Q_SECOND); + const bool do_number_indent = has_format_option(FO_Q_NUMBER); + const bool do_trail_white = has_format_option(FO_WHITE_PAR); + + // Get info about the previous and current line. + if (curwin->w_cursor.lnum > 1) { + is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, + &leader_len, &leader_flags, do_comments); + } else { + is_not_par = true; + } + next_is_not_par = fmt_check_par(curwin->w_cursor.lnum, + &next_leader_len, &next_leader_flags, do_comments); + is_end_par = (is_not_par || next_is_not_par); + if (!is_end_par && do_trail_white) { + is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); + } + + curwin->w_cursor.lnum--; + for (count = line_count; count != 0 && !got_int; count--) { + // Advance to next paragraph. + if (advance) { + curwin->w_cursor.lnum++; + prev_is_end_par = is_end_par; + is_not_par = next_is_not_par; + leader_len = next_leader_len; + leader_flags = next_leader_flags; + } + + // The last line to be formatted. + if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { + next_is_not_par = true; + next_leader_len = 0; + next_leader_flags = NULL; + } else { + next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1, + &next_leader_len, &next_leader_flags, do_comments); + if (do_number_indent) { + next_is_start_par = + (get_number_indent(curwin->w_cursor.lnum + 1) > 0); + } + } + advance = true; + is_end_par = (is_not_par || next_is_not_par || next_is_start_par); + if (!is_end_par && do_trail_white) { + is_end_par = !ends_in_white(curwin->w_cursor.lnum); + } + + // Skip lines that are not in a paragraph. + if (is_not_par) { + if (line_count < 0) { + break; + } + } else { + // For the first line of a paragraph, check indent of second line. + // Don't do this for comments and empty lines. + if (first_par_line + && (do_second_indent || do_number_indent) + && prev_is_end_par + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { + if (leader_len == 0 && next_leader_len == 0) { + // no comment found + second_indent = + get_indent_lnum(curwin->w_cursor.lnum + 1); + } else { + second_indent = next_leader_len; + do_comments_list = 1; + } + } else if (do_number_indent) { + if (leader_len == 0 && next_leader_len == 0) { + // no comment found + second_indent = + get_number_indent(curwin->w_cursor.lnum); + } else { + // get_number_indent() is now "comment aware"... + second_indent = + get_number_indent(curwin->w_cursor.lnum); + do_comments_list = 1; + } + } + } + + // When the comment leader changes, it's the end of the paragraph. + if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count + || !same_leader(curwin->w_cursor.lnum, + leader_len, leader_flags, + next_leader_len, + next_leader_flags)) { + // Special case: If the next line starts with a line comment + // and this line has a line comment after some text, the + // paragraph doesn't really end. + if (next_leader_flags == NULL + || STRNCMP(next_leader_flags, "://", 3) != 0 + || check_linecomment((char *)get_cursor_line_ptr()) == MAXCOL) { + is_end_par = true; + } + } + + // If we have got to the end of a paragraph, or the line is + // getting long, format it. + if (is_end_par || force_format) { + if (need_set_indent) { + int indent = 0; // amount of indent needed + + // Replace indent in first line of a paragraph with minimal + // number of tabs and spaces, according to current options. + // For the very first formatted line keep the current + // indent. + if (curwin->w_cursor.lnum == first_line) { + indent = get_indent(); + } else if (curbuf->b_p_lisp) { + indent = get_lisp_indent(); + } else { + if (cindent_on()) { + indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); + } else { + indent = get_indent(); + } + } + (void)set_indent(indent, SIN_CHANGED); + } + + // put cursor on last non-space + State = MODE_NORMAL; // don't go past end-of-line + coladvance(MAXCOL); + while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { + dec_cursor(); + } + + // do the formatting, without 'showmode' + State = MODE_INSERT; // for open_line() + smd_save = p_smd; + p_smd = false; + insertchar(NUL, INSCHAR_FORMAT + + (do_comments ? INSCHAR_DO_COM : 0) + + (do_comments && do_comments_list ? INSCHAR_COM_LIST : 0) + + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); + State = old_State; + p_smd = smd_save; + second_indent = -1; + // at end of par.: need to set indent of next par. + need_set_indent = is_end_par; + if (is_end_par) { + // When called with a negative line count, break at the + // end of the paragraph. + if (line_count < 0) { + break; + } + first_par_line = true; + } + force_format = false; + } + + // When still in same paragraph, join the lines together. But + // first delete the leader from the second line. + if (!is_end_par) { + advance = false; + curwin->w_cursor.lnum++; + curwin->w_cursor.col = 0; + if (line_count < 0 && u_save_cursor() == FAIL) { + break; + } + if (next_leader_len > 0) { + (void)del_bytes(next_leader_len, false, false); + mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, + (long)-next_leader_len, 0); + } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND + int indent = (int)getwhitecols_curline(); + + if (indent > 0) { + (void)del_bytes(indent, false, false); + mark_col_adjust(curwin->w_cursor.lnum, + (colnr_T)0, 0L, (long)-indent, 0); + } + } + curwin->w_cursor.lnum--; + if (do_join(2, true, false, false, false) == FAIL) { + beep_flush(); + break; + } + first_par_line = false; + // If the line is getting long, format it next time + if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) { + force_format = true; + } else { + force_format = false; + } + } + } + line_breakcheck(); + } +} diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h new file mode 100644 index 0000000000..3c918a028b --- /dev/null +++ b/src/nvim/textformat.h @@ -0,0 +1,10 @@ +#ifndef NVIM_TEXTFORMAT_H +#define NVIM_TEXTFORMAT_H + +#include "nvim/normal.h" // for oparg_T +#include "nvim/pos.h" // for linenr_T + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textformat.h.generated.h" +#endif +#endif // NVIM_TEXTFORMAT_H diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c new file mode 100644 index 0000000000..02174edeb1 --- /dev/null +++ b/src/nvim/textobject.c @@ -0,0 +1,1742 @@ +// 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 + +// textobject.c: functions for text objects + +#include <stdbool.h> + +#include "nvim/ascii.h" +#include "nvim/cursor.h" +#include "nvim/drawscreen.h" +#include "nvim/edit.h" +#include "nvim/eval/funcs.h" +#include "nvim/fold.h" +#include "nvim/globals.h" +#include "nvim/indent.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/normal.h" +#include "nvim/pos.h" +#include "nvim/search.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textobject.c.generated.h" +#endif + +/// Find the start of the next sentence, searching in the direction specified +/// by the "dir" argument. The cursor is positioned on the start of the next +/// sentence when found. If the next sentence is found, return OK. Return FAIL +/// otherwise. See ":h sentence" for the precise definition of a "sentence" +/// text object. +int findsent(Direction dir, long count) +{ + pos_T pos, tpos; + int c; + int (*func)(pos_T *); + bool noskip = false; // do not skip blanks + + pos = curwin->w_cursor; + if (dir == FORWARD) { + func = incl; + } else { + func = decl; + } + + while (count--) { + const pos_T prev_pos = pos; + + // if on an empty line, skip up to a non-empty line + if (gchar_pos(&pos) == NUL) { + do { + if ((*func)(&pos) == -1) { + break; + } + } while (gchar_pos(&pos) == NUL); + if (dir == FORWARD) { + goto found; + } + // if on the start of a paragraph or a section and searching forward, + // go to the next line + } else if (dir == FORWARD && pos.col == 0 + && startPS(pos.lnum, NUL, false)) { + if (pos.lnum == curbuf->b_ml.ml_line_count) { + return FAIL; + } + pos.lnum++; + goto found; + } else if (dir == BACKWARD) { + decl(&pos); + } + + // go back to the previous non-white non-punctuation character + bool found_dot = false; + while (c = gchar_pos(&pos), ascii_iswhite(c) + || vim_strchr(".!?)]\"'", c) != NULL) { + tpos = pos; + if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { + break; + } + if (found_dot) { + break; + } + if (vim_strchr(".!?", c) != NULL) { + found_dot = true; + } + if (vim_strchr(")]\"'", c) != NULL + && vim_strchr(".!?)]\"'", gchar_pos(&tpos)) == NULL) { + break; + } + decl(&pos); + } + + // remember the line where the search started + const int startlnum = pos.lnum; + const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; + + for (;;) { // find end of sentence + c = gchar_pos(&pos); + if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, false))) { + if (dir == BACKWARD && pos.lnum != startlnum) { + pos.lnum++; + } + break; + } + if (c == '.' || c == '!' || c == '?') { + tpos = pos; + do { + if ((c = inc(&tpos)) == -1) { + break; + } + } while (vim_strchr(")]\"'", c = gchar_pos(&tpos)) + != NULL); + if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL + || (cpo_J && (c == ' ' && inc(&tpos) >= 0 + && gchar_pos(&tpos) == ' '))) { + pos = tpos; + if (gchar_pos(&pos) == NUL) { // skip NUL at EOL + inc(&pos); + } + break; + } + } + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + noskip = true; + break; + } + } +found: + // skip white space + while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) { + if (incl(&pos) == -1) { + break; + } + } + + if (equalpos(prev_pos, pos)) { + // didn't actually move, advance one character and try again + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + break; + } + count++; + } + } + + setpcmark(); + curwin->w_cursor = pos; + return OK; +} + +/// Find the next paragraph or section in direction 'dir'. +/// Paragraphs are currently supposed to be separated by empty lines. +/// If 'what' is NUL we go to the next paragraph. +/// If 'what' is '{' or '}' we go to the next section. +/// If 'both' is true also stop at '}'. +/// +/// @param pincl Return: true if last char is to be included +/// +/// @return true if the next paragraph or section was found. +bool findpar(bool *pincl, int dir, long count, int what, bool both) +{ + linenr_T curr; + bool did_skip; // true after separating lines have been skipped + bool first; // true on first line + linenr_T fold_first; // first line of a closed fold + linenr_T fold_last; // last line of a closed fold + bool fold_skipped; // true if a closed fold was skipped this + // iteration + + curr = curwin->w_cursor.lnum; + + while (count--) { + did_skip = false; + for (first = true;; first = false) { + if (*ml_get(curr) != NUL) { + did_skip = true; + } + + // skip folded lines + fold_skipped = false; + if (first && hasFolding(curr, &fold_first, &fold_last)) { + curr = ((dir > 0) ? fold_last : fold_first) + dir; + fold_skipped = true; + } + + if (!first && did_skip && startPS(curr, what, both)) { + break; + } + + if (fold_skipped) { + curr -= dir; + } + if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { + if (count) { + return false; + } + curr -= dir; + break; + } + } + } + setpcmark(); + if (both && *ml_get(curr) == '}') { // include line with '}' + curr++; + } + curwin->w_cursor.lnum = curr; + if (curr == curbuf->b_ml.ml_line_count && what != '}') { + char_u *line = ml_get(curr); + + // Put the cursor on the last character in the last line and make the + // motion inclusive. + if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) { + curwin->w_cursor.col--; + curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); + *pincl = true; + } + } else { + curwin->w_cursor.col = 0; + } + return true; +} + +/// check if the string 's' is a nroff macro that is in option 'opt' +static bool inmacro(char_u *opt, char_u *s) +{ + char_u *macro; + + for (macro = opt; macro[0]; macro++) { + // Accept two characters in the option being equal to two characters + // in the line. A space in the option matches with a space in the + // line or the line having ended. + if ((macro[0] == s[0] + || (macro[0] == ' ' + && (s[0] == NUL || s[0] == ' '))) + && (macro[1] == s[1] + || ((macro[1] == NUL || macro[1] == ' ') + && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { + break; + } + macro++; + if (macro[0] == NUL) { + break; + } + } + return macro[0] != NUL; +} + +/// startPS: return true if line 'lnum' is the start of a section or paragraph. +/// If 'para' is '{' or '}' only check for sections. +/// If 'both' is true also stop at '}' +bool startPS(linenr_T lnum, int para, bool both) +{ + char_u *s; + + s = ml_get(lnum); + if (*s == para || *s == '\f' || (both && *s == '}')) { + return true; + } + if (*s == '.' && (inmacro((char_u *)p_sections, s + 1) + || (!para && inmacro(p_para, s + 1)))) { + return true; + } + return false; +} + +// The following routines do the word searches performed by the 'w', 'W', +// 'b', 'B', 'e', and 'E' commands. + +// To perform these searches, characters are placed into one of three +// classes, and transitions between classes determine word boundaries. +// +// The classes are: +// +// 0 - white space +// 1 - punctuation +// 2 or higher - keyword characters (letters, digits and underscore) + +static bool cls_bigword; ///< true for "W", "B" or "E" + +/// cls() - returns the class of character at curwin->w_cursor +/// +/// If a 'W', 'B', or 'E' motion is being done (cls_bigword == true), chars +/// from class 2 and higher are reported as class 1 since only white space +/// boundaries are of interest. +static int cls(void) +{ + int c; + + c = gchar_cursor(); + if (c == ' ' || c == '\t' || c == NUL) { + return 0; + } + + c = utf_class(c); + + // If cls_bigword is true, report all non-blanks as class 1. + if (c != 0 && cls_bigword) { + return 1; + } + return c; +} + +/// fwd_word(count, type, eol) - move forward one word +/// +/// @return FAIL if the cursor was already at the end of the file. +/// If eol is true, last word stops at end of line (for operators). +/// +/// @param bigword "W", "E" or "B" +int fwd_word(long count, bool bigword, bool eol) +{ + int sclass; // starting class + int i; + int last_line; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } + sclass = cls(); + + // We always move at least one character, unless on the last + // character in the buffer. + last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); + i = inc_cursor(); + if (i == -1 || (i >= 1 && last_line)) { // started at last char in file + return FAIL; + } + if (i >= 1 && eol && count == 0) { // started at last char in line + return OK; + } + + // Go one char past end of current word (if any) + if (sclass != 0) { + while (cls() == sclass) { + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) { + return OK; + } + } + } + + // go to next non-white + while (cls() == 0) { + // We'll stop if we land on a blank line + if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) { + break; + } + + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) { + return OK; + } + } + } + return OK; +} + +/// bck_word() - move backward 'count' words +/// +/// If stop is true and we are already on the start of a word, move one less. +/// +/// Returns FAIL if top of the file was reached. +int bck_word(long count, bool bigword, bool stop) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the first char of the + // first line. + if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { + curwin->w_cursor.col = 0; + } + sclass = cls(); + if (dec_cursor() == -1) { // started at start of file + return FAIL; + } + + if (!stop || sclass == cls() || sclass == 0) { + // Skip white space before the word. + // Stop on an empty line. + while (cls() == 0) { + if (curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) { + goto finished; + } + if (dec_cursor() == -1) { // hit start of file, stop here + return OK; + } + } + + // Move backward to start of this word. + if (skip_chars(cls(), BACKWARD)) { + return OK; + } + } + + inc_cursor(); // overshot - forward one +finished: + stop = false; + } + return OK; +} + +/// end_word() - move to the end of the word +/// +/// There is an apparent bug in the 'e' motion of the real vi. At least on the +/// System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' +/// motion crosses blank lines. When the real vi crosses a blank line in an +/// 'e' motion, the cursor is placed on the FIRST character of the next +/// non-blank line. The 'E' command, however, works correctly. Since this +/// appears to be a bug, I have not duplicated it here. +/// +/// Returns FAIL if end of the file was reached. +/// +/// If stop is true and we are already on the end of a word, move one less. +/// If empty is true stop on an empty line. +int end_word(long count, bool bigword, bool stop, bool empty) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } + sclass = cls(); + if (inc_cursor() == -1) { + return FAIL; + } + + // If we're in the middle of a word, we just have to move to the end + // of it. + if (cls() == sclass && sclass != 0) { + // Move forward to end of the current word + if (skip_chars(sclass, FORWARD)) { + return FAIL; + } + } else if (!stop || sclass == 0) { + // We were at the end of a word. Go to the end of the next word. + // First skip white space, if 'empty' is true, stop at empty line. + while (cls() == 0) { + if (empty && curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) { + goto finished; + } + if (inc_cursor() == -1) { // hit end of file, stop here + return FAIL; + } + } + + // Move forward to the end of this word. + if (skip_chars(cls(), FORWARD)) { + return FAIL; + } + } + dec_cursor(); // overshot - one char backward +finished: + stop = false; // we move only one word less + } + return OK; +} + +/// Move back to the end of the word. +/// +/// @param bigword true for "B" +/// @param eol if true, then stop at end of line. +/// +/// @return FAIL if start of the file was reached. +int bckend_word(long count, bool bigword, bool eol) +{ + int sclass; // starting class + int i; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + sclass = cls(); + if ((i = dec_cursor()) == -1) { + return FAIL; + } + if (eol && i == 1) { + return OK; + } + + // Move backward to before the start of this word. + if (sclass != 0) { + while (cls() == sclass) { + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { + return OK; + } + } + } + + // Move backward to end of the previous word + while (cls() == 0) { + if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { + break; + } + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { + return OK; + } + } + } + return OK; +} + +/// Skip a row of characters of the same class. +/// +/// @return true when end-of-file reached, false otherwise. +static bool skip_chars(int cclass, int dir) +{ + while (cls() == cclass) { + if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) { + return true; + } + } + return false; +} + +/// Go back to the start of the word or the start of white space +static void back_in_line(void) +{ + int sclass; // starting class + + sclass = cls(); + for (;;) { + if (curwin->w_cursor.col == 0) { // stop at start of line + break; + } + dec_cursor(); + if (cls() != sclass) { // stop at start of word + inc_cursor(); + break; + } + } +} + +static void find_first_blank(pos_T *posp) +{ + int c; + + while (decl(posp) != -1) { + c = gchar_pos(posp); + if (!ascii_iswhite(c)) { + incl(posp); + break; + } + } +} + +/// Skip count/2 sentences and count/2 separating white spaces. +/// +/// @param at_start_sent cursor is at start of sentence +static void findsent_forward(long count, bool at_start_sent) +{ + while (count--) { + findsent(FORWARD, 1L); + if (at_start_sent) { + find_first_blank(&curwin->w_cursor); + } + if (count == 0 || at_start_sent) { + decl(&curwin->w_cursor); + } + at_start_sent = !at_start_sent; + } +} + +/// Find word under cursor, cursor at end. +/// Used while an operator is pending, and in Visual mode. +/// +/// @param include true: include word and white space +/// @param bigword false == word, true == WORD +int current_word(oparg_T *oap, long count, bool include, bool bigword) +{ + pos_T start_pos; + pos_T pos; + bool inclusive = true; + int include_white = false; + + cls_bigword = bigword; + clearpos(&start_pos); + + // Correct cursor when 'selection' is exclusive + if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) { + dec_cursor(); + } + + // When Visual mode is not active, or when the VIsual area is only one + // character, select the word and/or white space under the cursor. + if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { + // Go to start of current word or white space. + back_in_line(); + start_pos = curwin->w_cursor; + + // If the start is on white space, and white space should be included + // (" word"), or start is not on white space, and white space should + // not be included ("word"), find end of word. + if ((cls() == 0) == include) { + if (end_word(1L, bigword, true, true) == FAIL) { + return FAIL; + } + } else { + // If the start is not on white space, and white space should be + // included ("word "), or start is on white space and white + // space should not be included (" "), find start of word. + // If we end up in the first column of the next line (single char + // word) back up to end of the line. + fwd_word(1L, bigword, true); + if (curwin->w_cursor.col == 0) { + decl(&curwin->w_cursor); + } else { + oneleft(); + } + + if (include) { + include_white = true; + } + } + + if (VIsual_active) { + // should do something when inclusive == false ! + VIsual = start_pos; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + } + count--; + } + + // When count is still > 0, extend with more objects. + while (count > 0) { + inclusive = true; + if (VIsual_active && lt(curwin->w_cursor, VIsual)) { + // In Visual mode, with cursor at start: move cursor back. + if (decl(&curwin->w_cursor) == -1) { + return FAIL; + } + if (include != (cls() != 0)) { + if (bck_word(1L, bigword, true) == FAIL) { + return FAIL; + } + } else { + if (bckend_word(1L, bigword, true) == FAIL) { + return FAIL; + } + (void)incl(&curwin->w_cursor); + } + } else { + // Move cursor forward one word and/or white area. + if (incl(&curwin->w_cursor) == -1) { + return FAIL; + } + if (include != (cls() == 0)) { + if (fwd_word(1L, bigword, true) == FAIL && count > 1) { + return FAIL; + } + // If end is just past a new-line, we don't want to include + // the first character on the line. + // Put cursor on last char of white. + if (oneleft() == FAIL) { + inclusive = false; + } + } else { + if (end_word(1L, bigword, true, true) == FAIL) { + return FAIL; + } + } + } + count--; + } + + if (include_white && (cls() != 0 + || (curwin->w_cursor.col == 0 && !inclusive))) { + // If we don't include white space at the end, move the start + // to include some white space there. This makes "daw" work + // better on the last word in a sentence (and "2daw" on last-but-one + // word). Also when "2daw" deletes "word." at the end of the line + // (cursor is at start of next line). + // But don't delete white space at start of line (indent). + pos = curwin->w_cursor; // save cursor position + curwin->w_cursor = start_pos; + if (oneleft() == OK) { + back_in_line(); + if (cls() == 0 && curwin->w_cursor.col > 0) { + if (VIsual_active) { + VIsual = curwin->w_cursor; + } else { + oap->start = curwin->w_cursor; + } + } + } + curwin->w_cursor = pos; // put cursor back at end + } + + if (VIsual_active) { + if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) { + inc_cursor(); + } + if (VIsual_mode == 'V') { + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + } + } else { + oap->inclusive = inclusive; + } + + return OK; +} + +/// Find sentence(s) under the cursor, cursor at end. +/// When Visual active, extend it by one or more sentences. +int current_sent(oparg_T *oap, long count, bool include) +{ + pos_T start_pos; + pos_T pos; + bool start_blank; + int c; + bool at_start_sent; + long ncount; + + start_pos = curwin->w_cursor; + pos = start_pos; + findsent(FORWARD, 1L); // Find start of next sentence. + + // When the Visual area is bigger than one character: Extend it. + if (VIsual_active && !equalpos(start_pos, VIsual)) { +extend: + if (lt(start_pos, VIsual)) { + // Cursor at start of Visual area. + // Find out where we are: + // - in the white space before a sentence + // - in a sentence or just after it + // - at the start of a sentence + at_start_sent = true; + decl(&pos); + while (lt(pos, curwin->w_cursor)) { + c = gchar_pos(&pos); + if (!ascii_iswhite(c)) { + at_start_sent = false; + break; + } + incl(&pos); + } + if (!at_start_sent) { + findsent(BACKWARD, 1L); + if (equalpos(curwin->w_cursor, start_pos)) { + at_start_sent = true; // exactly at start of sentence + } else { + // inside a sentence, go to its end (start of next) + findsent(FORWARD, 1L); + } + } + if (include) { // "as" gets twice as much as "is" + count *= 2; + } + while (count--) { + if (at_start_sent) { + find_first_blank(&curwin->w_cursor); + } + c = gchar_cursor(); + if (!at_start_sent || (!include && !ascii_iswhite(c))) { + findsent(BACKWARD, 1L); + } + at_start_sent = !at_start_sent; + } + } else { + // Cursor at end of Visual area. + // Find out where we are: + // - just before a sentence + // - just before or in the white space before a sentence + // - in a sentence + incl(&pos); + at_start_sent = true; + if (!equalpos(pos, curwin->w_cursor)) { // not just before a sentence + at_start_sent = false; + while (lt(pos, curwin->w_cursor)) { + c = gchar_pos(&pos); + if (!ascii_iswhite(c)) { + at_start_sent = true; + break; + } + incl(&pos); + } + if (at_start_sent) { // in the sentence + findsent(BACKWARD, 1L); + } else { // in/before white before a sentence + curwin->w_cursor = start_pos; + } + } + + if (include) { // "as" gets twice as much as "is" + count *= 2; + } + findsent_forward(count, at_start_sent); + if (*p_sel == 'e') { + curwin->w_cursor.col++; + } + } + return OK; + } + + // If the cursor started on a blank, check if it is just before the start + // of the next sentence. + while (c = gchar_pos(&pos), ascii_iswhite(c)) { + incl(&pos); + } + if (equalpos(pos, curwin->w_cursor)) { + start_blank = true; + find_first_blank(&start_pos); // go back to first blank + } else { + start_blank = false; + findsent(BACKWARD, 1L); + start_pos = curwin->w_cursor; + } + if (include) { + ncount = count * 2; + } else { + ncount = count; + if (start_blank) { + ncount--; + } + } + if (ncount > 0) { + findsent_forward(ncount, true); + } else { + decl(&curwin->w_cursor); + } + + if (include) { + // If the blank in front of the sentence is included, exclude the + // blanks at the end of the sentence, go back to the first blank. + // If there are no trailing blanks, try to include leading blanks. + if (start_blank) { + find_first_blank(&curwin->w_cursor); + c = gchar_pos(&curwin->w_cursor); + if (ascii_iswhite(c)) { + decl(&curwin->w_cursor); + } + } else if (c = gchar_cursor(), !ascii_iswhite(c)) { + find_first_blank(&start_pos); + } + } + + if (VIsual_active) { + // Avoid getting stuck with "is" on a single space before a sentence. + if (equalpos(start_pos, curwin->w_cursor)) { + goto extend; + } + if (*p_sel == 'e') { + curwin->w_cursor.col++; + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } else { + // include a newline after the sentence, if there is one + if (incl(&curwin->w_cursor) == -1) { + oap->inclusive = true; + } else { + oap->inclusive = false; + } + oap->start = start_pos; + oap->motion_type = kMTCharWise; + } + return OK; +} + +/// Find block under the cursor, cursor at end. +/// "what" and "other" are two matching parenthesis/brace/etc. +/// +/// @param include true == include white space +/// @param what '(', '{', etc. +/// @param other ')', '}', etc. +int current_block(oparg_T *oap, long count, bool include, int what, int other) +{ + pos_T old_pos; + pos_T *pos = NULL; + pos_T start_pos; + pos_T *end_pos; + pos_T old_start, old_end; + char *save_cpo; + bool sol = false; // '{' at start of line + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + + // If we start on '(', '{', ')', '}', etc., use the whole block inclusive. + if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { + setpcmark(); + if (what == '{') { // ignore indent + while (inindent(1)) { + if (inc_cursor() != 0) { + break; + } + } + } + if (gchar_cursor() == what) { + // cursor on '(' or '{', move cursor just after it + curwin->w_cursor.col++; + } + } else if (lt(VIsual, curwin->w_cursor)) { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } else { + old_end = VIsual; + } + + // Search backwards for unclosed '(', '{', etc.. + // Put this position in start_pos. + // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the + // user wants. + save_cpo = p_cpo; + p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"; + if ((pos = findmatch(NULL, what)) != NULL) { + while (count-- > 0) { + if ((pos = findmatch(NULL, what)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } else { + while (count-- > 0) { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } + p_cpo = save_cpo; + + // Search for matching ')', '}', etc. + // Put this position in curwin->w_cursor. + if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + + // Try to exclude the '(', '{', ')', '}', etc. when "include" is false. + // If the ending '}', ')' or ']' is only preceded by indent, skip that + // indent. But only if the resulting area is not smaller than what we + // started with. + while (!include) { + incl(&start_pos); + sol = (curwin->w_cursor.col == 0); + decl(&curwin->w_cursor); + while (inindent(1)) { + sol = true; + if (decl(&curwin->w_cursor) != 0) { + break; + } + } + + // In Visual mode, when the resulting area is not bigger than what we + // started with, extend it to the next block, and then exclude again. + // Don't try to expand the area if the area is empty. + if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) + && !equalpos(start_pos, curwin->w_cursor) + && VIsual_active) { + curwin->w_cursor = old_start; + decl(&curwin->w_cursor); + if ((pos = findmatch(NULL, what)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + start_pos = *pos; + curwin->w_cursor = *pos; + if ((end_pos = findmatch(NULL, other)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + } else { + break; + } + } + + if (VIsual_active) { + if (*p_sel == 'e') { + inc(&curwin->w_cursor); + } + if (sol && gchar_cursor() != NUL) { + inc(&curwin->w_cursor); // include the line break + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + oap->inclusive = false; + if (sol) { + incl(&curwin->w_cursor); + } else if (ltoreq(start_pos, curwin->w_cursor)) { + // Include the character under the cursor. + oap->inclusive = true; + } else { + // End is before the start (no text in between <>, [], etc.): don't + // operate on any text. + curwin->w_cursor = start_pos; + } + } + + return OK; +} + +/// @param end_tag when true, return true if the cursor is on "</aaa>". +/// +/// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". +static bool in_html_tag(bool end_tag) +{ + char_u *line = get_cursor_line_ptr(); + char_u *p; + int c; + int lc = NUL; + pos_T pos; + + for (p = line + curwin->w_cursor.col; p > line;) { + if (*p == '<') { // find '<' under/before cursor + break; + } + MB_PTR_BACK(line, p); + if (*p == '>') { // find '>' before cursor + break; + } + } + if (*p != '<') { + return false; + } + + pos.lnum = curwin->w_cursor.lnum; + pos.col = (colnr_T)(p - line); + + MB_PTR_ADV(p); + if (end_tag) { + // check that there is a '/' after the '<' + return *p == '/'; + } + + // check that there is no '/' after the '<' + if (*p == '/') { + return false; + } + + // check that the matching '>' is not preceded by '/' + for (;;) { + if (inc(&pos) < 0) { + return false; + } + c = *ml_get_pos(&pos); + if (c == '>') { + break; + } + lc = c; + } + return lc != '/'; +} + +/// Find tag block under the cursor, cursor at end. +/// +/// @param include true == include white space +int current_tagblock(oparg_T *oap, long count_arg, bool include) +{ + long count = count_arg; + pos_T old_pos; + pos_T start_pos; + pos_T end_pos; + pos_T old_start, old_end; + char_u *p; + char_u *cp; + int len; + bool do_include = include; + bool save_p_ws = p_ws; + int retval = FAIL; + int is_inclusive = true; + + p_ws = false; + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + if (!VIsual_active || *p_sel == 'e') { + decl(&old_end); // old_end is inclusive + } + + // If we start on "<aaa>" select that block. + if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { + setpcmark(); + + // ignore indent + while (inindent(1)) { + if (inc_cursor() != 0) { + break; + } + } + + if (in_html_tag(false)) { + // cursor on start tag, move to its '>' + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { + break; + } + } + } else if (in_html_tag(true)) { + // cursor on end tag, move to just before it + while (*get_cursor_pos_ptr() != '<') { + if (dec_cursor() < 0) { + break; + } + } + dec_cursor(); + old_end = curwin->w_cursor; + } + } else if (lt(VIsual, curwin->w_cursor)) { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } else { + old_end = VIsual; + } + +again: + // Search backwards for unclosed "<aaa>". + // Put this position in start_pos. + for (long n = 0; n < count; n++) { + if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + "", + "</[^>]*>", BACKWARD, NULL, 0, + NULL, (linenr_T)0, 0L) <= 0) { + curwin->w_cursor = old_pos; + goto theend; + } + } + start_pos = curwin->w_cursor; + + // Search for matching "</aaa>". First isolate the "aaa". + inc_cursor(); + p = get_cursor_pos_ptr(); + for (cp = p; + *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); + MB_PTR_ADV(cp)) {} + len = (int)(cp - p); + if (len == 0) { + curwin->w_cursor = old_pos; + goto theend; + } + const size_t spat_len = (size_t)len + 39; + char *const spat = xmalloc(spat_len); + const size_t epat_len = (size_t)len + 9; + char *const epat = xmalloc(epat_len); + snprintf(spat, spat_len, + "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); + snprintf(epat, epat_len, "</%.*s>\\c", len, p); + + const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); + + xfree(spat); + xfree(epat); + + if (r < 1 || lt(curwin->w_cursor, old_end)) { + // Can't find other end or it's before the previous end. Could be a + // HTML tag that doesn't have a matching end. Search backwards for + // another starting tag. + count = 1; + curwin->w_cursor = start_pos; + goto again; + } + + if (do_include) { + // Include up to the '>'. + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { + break; + } + } + } else { + char_u *c = get_cursor_pos_ptr(); + // Exclude the '<' of the end tag. + // If the closing tag is on new line, do not decrement cursor, but make + // operation exclusive, so that the linefeed will be selected + if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { + // do not decrement cursor + is_inclusive = false; + } else if (*c == '<') { + dec_cursor(); + } + } + end_pos = curwin->w_cursor; + + if (!do_include) { + // Exclude the start tag. + curwin->w_cursor = start_pos; + while (inc_cursor() >= 0) { + if (*get_cursor_pos_ptr() == '>') { + inc_cursor(); + start_pos = curwin->w_cursor; + break; + } + } + curwin->w_cursor = end_pos; + + // If we are in Visual mode and now have the same text as before set + // "do_include" and try again. + if (VIsual_active + && equalpos(start_pos, old_start) + && equalpos(end_pos, old_end)) { + do_include = true; + curwin->w_cursor = old_start; + count = count_arg; + goto again; + } + } + + if (VIsual_active) { + // If the end is before the start there is no text between tags, select + // the char under the cursor. + if (lt(end_pos, start_pos)) { + curwin->w_cursor = start_pos; + } else if (*p_sel == 'e') { + inc_cursor(); + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + if (lt(end_pos, start_pos)) { + // End is before the start: there is no text between tags; operate + // on an empty area. + curwin->w_cursor = start_pos; + oap->inclusive = false; + } else { + oap->inclusive = is_inclusive; + } + } + retval = OK; + +theend: + p_ws = save_p_ws; + return retval; +} + +/// @param include true == include white space +/// @param type 'p' for paragraph, 'S' for section +int current_par(oparg_T *oap, long count, bool include, int type) +{ + linenr_T start_lnum; + linenr_T end_lnum; + int white_in_front; + int dir; + int start_is_white; + int prev_start_is_white; + int retval = OK; + int do_white = false; + int t; + int i; + + if (type == 'S') { // not implemented yet + return FAIL; + } + + start_lnum = curwin->w_cursor.lnum; + + // When visual area is more than one line: extend it. + if (VIsual_active && start_lnum != VIsual.lnum) { +extend: + if (start_lnum < VIsual.lnum) { + dir = BACKWARD; + } else { + dir = FORWARD; + } + for (i = (int)count; --i >= 0;) { + if (start_lnum == + (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { + retval = FAIL; + break; + } + + prev_start_is_white = -1; + for (t = 0; t < 2; t++) { + start_lnum += dir; + start_is_white = linewhite(start_lnum); + if (prev_start_is_white == start_is_white) { + start_lnum -= dir; + break; + } + for (;;) { + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) { + break; + } + if (start_is_white != linewhite(start_lnum + dir) + || (!start_is_white + && startPS(start_lnum + (dir > 0 + ? 1 : 0), 0, 0))) { + break; + } + start_lnum += dir; + } + if (!include) { + break; + } + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) { + break; + } + prev_start_is_white = start_is_white; + } + } + curwin->w_cursor.lnum = start_lnum; + curwin->w_cursor.col = 0; + return retval; + } + + // First move back to the start_lnum of the paragraph or white lines + white_in_front = linewhite(start_lnum); + while (start_lnum > 1) { + if (white_in_front) { // stop at first white line + if (!linewhite(start_lnum - 1)) { + break; + } + } else { // stop at first non-white line of start of paragraph + if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) { + break; + } + } + start_lnum--; + } + + // Move past the end of any white lines. + end_lnum = start_lnum; + while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { + end_lnum++; + } + + end_lnum--; + i = (int)count; + if (!include && white_in_front) { + i--; + } + while (i--) { + if (end_lnum == curbuf->b_ml.ml_line_count) { + return FAIL; + } + + if (!include) { + do_white = linewhite(end_lnum + 1); + } + + if (include || !do_white) { + 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++; + } + } + + if (i == 0 && white_in_front && include) { + break; + } + + // skip to end of white lines after paragraph + if (include || do_white) { + while (end_lnum < curbuf->b_ml.ml_line_count + && linewhite(end_lnum + 1)) { + end_lnum++; + } + } + } + + // If there are no empty lines at the end, try to find some empty lines at + // the start (unless that has been done already). + if (!white_in_front && !linewhite(end_lnum) && include) { + while (start_lnum > 1 && linewhite(start_lnum - 1)) { + start_lnum--; + } + } + + if (VIsual_active) { + // Problem: when doing "Vipipip" nothing happens in a single white + // line, we get stuck there. Trap this here. + if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { + goto extend; + } + if (VIsual.lnum != start_lnum) { + VIsual.lnum = start_lnum; + VIsual.col = 0; + } + VIsual_mode = 'V'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start.lnum = start_lnum; + oap->start.col = 0; + oap->motion_type = kMTLineWise; + } + curwin->w_cursor.lnum = end_lnum; + curwin->w_cursor.col = 0; + + return OK; +} + +/// Search quote char from string line[col]. +/// Quote character escaped by one of the characters in "escape" is not counted +/// as a quote. +/// +/// @param escape escape characters, can be NULL +/// +/// @return column number of "quotechar" or -1 when not found. +static int find_next_quote(char_u *line, int col, int quotechar, char_u *escape) +{ + int c; + + for (;;) { + c = line[col]; + if (c == NUL) { + return -1; + } else if (escape != NULL && vim_strchr((char *)escape, c)) { + col++; + if (line[col] == NUL) { + return -1; + } + } else if (c == quotechar) { + break; + } + col += utfc_ptr2len((char *)line + col); + } + return col; +} + +/// Search backwards in "line" from column "col_start" to find "quotechar". +/// Quote character escaped by one of the characters in "escape" is not counted +/// as a quote. +/// +/// @param escape escape characters, can be NULL +/// +/// @return the found column or zero. +static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *escape) +{ + int n; + + while (col_start > 0) { + col_start--; + col_start -= utf_head_off(line, line + col_start); + n = 0; + if (escape != NULL) { + while (col_start - n > 0 && vim_strchr((char *)escape, + line[col_start - n - 1]) != NULL) { + n++; + } + } + if (n & 1) { + col_start -= n; // uneven number of escape chars, skip it + } else if (line[col_start] == quotechar) { + break; + } + } + return col_start; +} + +/// Find quote under the cursor, cursor at end. +/// +/// @param include true == include quote char +/// @param quotechar Quote character +/// +/// @return true if found, else false. +bool current_quote(oparg_T *oap, long count, bool include, int quotechar) + FUNC_ATTR_NONNULL_ALL +{ + char_u *line = get_cursor_line_ptr(); + int col_end; + int col_start = curwin->w_cursor.col; + bool inclusive = false; + bool vis_empty = true; // Visual selection <= 1 char + bool vis_bef_curs = false; // Visual starts before cursor + bool did_exclusive_adj = false; // adjusted pos for 'selection' + bool inside_quotes = false; // Looks like "i'" done before + bool selected_quote = false; // Has quote inside selection + int i; + bool restore_vis_bef = false; // resotre VIsual on abort + + // When 'selection' is "exclusive" move the cursor to where it would be + // with 'selection' "inclusive", so that the logic is the same for both. + // The cursor then is moved forward after adjusting the area. + if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + + vis_bef_curs = lt(VIsual, curwin->w_cursor); + vis_empty = equalpos(VIsual, curwin->w_cursor); + if (*p_sel == 'e') { + if (vis_bef_curs) { + dec_cursor(); + did_exclusive_adj = true; + } else if (!vis_empty) { + dec(&VIsual); + did_exclusive_adj = true; + } + vis_empty = equalpos(VIsual, curwin->w_cursor); + if (!vis_bef_curs && !vis_empty) { + // VIsual needs to be start of Visual selection. + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + vis_bef_curs = true; + restore_vis_bef = true; + } + } + } + + if (!vis_empty) { + // Check if the existing selection exactly spans the text inside + // quotes. + if (vis_bef_curs) { + inside_quotes = VIsual.col > 0 + && line[VIsual.col - 1] == quotechar + && line[curwin->w_cursor.col] != NUL + && line[curwin->w_cursor.col + 1] == quotechar; + i = VIsual.col; + col_end = curwin->w_cursor.col; + } else { + inside_quotes = curwin->w_cursor.col > 0 + && line[curwin->w_cursor.col - 1] == quotechar + && line[VIsual.col] != NUL + && line[VIsual.col + 1] == quotechar; + i = curwin->w_cursor.col; + col_end = VIsual.col; + } + + // Find out if we have a quote in the selection. + while (i <= col_end) { + // check for going over the end of the line, which can happen if + // the line was changed after the Visual area was selected. + if (line[i] == NUL) { + break; + } + if (line[i++] == quotechar) { + selected_quote = true; + break; + } + } + } + + if (!vis_empty && line[col_start] == quotechar) { + // Already selecting something and on a quote character. Find the + // next quoted string. + if (vis_bef_curs) { + // Assume we are on a closing quote: move to after the next + // opening quote. + col_start = find_next_quote(line, col_start + 1, quotechar, NULL); + if (col_start < 0) { + goto abort_search; + } + col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + if (col_end < 0) { + // We were on a starting quote perhaps? + col_end = col_start; + col_start = curwin->w_cursor.col; + } + } else { + col_end = find_prev_quote(line, col_start, quotechar, NULL); + if (line[col_end] != quotechar) { + goto abort_search; + } + col_start = find_prev_quote(line, col_end, quotechar, (char_u *)curbuf->b_p_qe); + if (line[col_start] != quotechar) { + // We were on an ending quote perhaps? + col_start = col_end; + col_end = curwin->w_cursor.col; + } + } + } else if (line[col_start] == quotechar || !vis_empty) { + int first_col = col_start; + + if (!vis_empty) { + if (vis_bef_curs) { + first_col = find_next_quote(line, col_start, quotechar, NULL); + } else { + first_col = find_prev_quote(line, col_start, quotechar, NULL); + } + } + // The cursor is on a quote, we don't know if it's the opening or + // closing quote. Search from the start of the line to find out. + // Also do this when there is a Visual area, a' may leave the cursor + // in between two strings. + col_start = 0; + for (;;) { + // Find open quote character. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0 || col_start > first_col) { + goto abort_search; + } + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } + // If is cursor between start and end quote character, it is + // target text object. + if (col_start <= first_col && first_col <= col_end) { + break; + } + col_start = col_end + 1; + } + } else { + // Search backward for a starting quote. + col_start = find_prev_quote(line, col_start, quotechar, (char_u *)curbuf->b_p_qe); + if (line[col_start] != quotechar) { + // No quote before the cursor, look after the cursor. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0) { + goto abort_search; + } + } + + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } + } + + // When "include" is true, include spaces after closing quote or before + // the starting quote. + if (include) { + if (ascii_iswhite(line[col_end + 1])) { + while (ascii_iswhite(line[col_end + 1])) { + col_end++; + } + } else { + while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { + col_start--; + } + } + } + + // Set start position. After vi" another i" must include the ". + // For v2i" include the quotes. + if (!include && count < 2 && (vis_empty || !inside_quotes)) { + col_start++; + } + curwin->w_cursor.col = col_start; + if (VIsual_active) { + // Set the start of the Visual area when the Visual area was empty, we + // were just inside quotes or the Visual area didn't start at a quote + // and didn't include a quote. + if (vis_empty + || (vis_bef_curs + && !selected_quote + && (inside_quotes + || (line[VIsual.col] != quotechar + && (VIsual.col == 0 + || line[VIsual.col - 1] != quotechar))))) { + VIsual = curwin->w_cursor; + redraw_curbuf_later(UPD_INVERTED); + } + } else { + oap->start = curwin->w_cursor; + oap->motion_type = kMTCharWise; + } + + // Set end position. + curwin->w_cursor.col = col_end; + if ((include || count > 1 + // After vi" another i" must include the ". + || (!vis_empty && inside_quotes)) && inc_cursor() == 2) { + inclusive = true; + } + if (VIsual_active) { + if (vis_empty || vis_bef_curs) { + // decrement cursor when 'selection' is not exclusive + if (*p_sel != 'e') { + dec_cursor(); + } + } else { + // Cursor is at start of Visual area. Set the end of the Visual + // area when it was just inside quotes or it didn't end at a + // quote. + if (inside_quotes + || (!selected_quote + && line[VIsual.col] != quotechar + && (line[VIsual.col] == NUL + || line[VIsual.col + 1] != quotechar))) { + dec_cursor(); + VIsual = curwin->w_cursor; + } + curwin->w_cursor.col = col_start; + } + if (VIsual_mode == 'V') { + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + } + } else { + // Set inclusive and other oap's flags. + oap->inclusive = inclusive; + } + + return true; + +abort_search: + if (VIsual_active && *p_sel == 'e') { + if (did_exclusive_adj) { + inc_cursor(); + } + if (restore_vis_bef) { + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + } + } + return false; +} diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h new file mode 100644 index 0000000000..26f88613fd --- /dev/null +++ b/src/nvim/textobject.h @@ -0,0 +1,11 @@ +#ifndef NVIM_TEXTOBJECT_H +#define NVIM_TEXTOBJECT_H + +#include "nvim/normal.h" // for oparg_T +#include "nvim/pos.h" // for linenr_T +#include "nvim/vim.h" // for Direction + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textobject.h.generated.h" +#endif +#endif // NVIM_TEXTOBJECT_H diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 61a59bcf06..d269878f46 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -233,12 +233,12 @@ static void tinput_wait_enqueue(void **argv) if (ui_client_channel_id) { Array args = ARRAY_DICT_INIT; Error err = ERROR_INIT; - ADD(args, STRING_OBJ(copy_string(keys))); + ADD(args, STRING_OBJ(copy_string(keys, NULL))); // TODO(bfredl): could be non-blocking now with paste? ArenaMem res_mem = NULL; Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &res_mem, &err); consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); } else { consumed = input_enqueue(keys); } @@ -398,6 +398,14 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) button = last_pressed_button; } + if (ev == TERMKEY_MOUSE_UNKNOWN && !(key->code.mouse[0] & 0x20)) { + int code = key->code.mouse[0] & ~0x3c; + if (code == 66 || code == 67) { + ev = TERMKEY_MOUSE_PRESS; + button = code - 60; + } + } + if (button == 0 || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG && ev != TERMKEY_MOUSE_RELEASE)) { return; @@ -431,8 +439,11 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) if (button == 4) { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp"); } else if (button == 5) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, - "ScrollWheelDown"); + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown"); + } else if (button == 6) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelLeft"); + } else if (button == 7) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelRight"); } else { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); last_pressed_button = button; 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..fb10bf21d9 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -22,12 +22,23 @@ typedef int handle_T; // absent callback etc. typedef int LuaRef; -typedef void (*FunPtr)(void); +/// Type used for VimL VAR_FLOAT values +typedef double float_T; + +typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler; + +typedef union { + float_T (*float_func)(float_T); + const MsgpackRpcRequestHandler *api_handler; + void *nullptr; +} EvalFuncData; 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..46f4d137c4 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" @@ -336,7 +336,7 @@ void vim_beep(unsigned val) // When 'debug' contains "beep" produce a message. If we are sourcing // a script or executing a function give the user a hint where the beep // comes from. - if (vim_strchr((char *)p_debug, 'e') != NULL) { + if (vim_strchr(p_debug, 'e') != NULL) { msg_source(HL_ATTR(HLF_W)); msg_attr(_("Beep!"), HL_ATTR(HLF_W)); } @@ -517,11 +517,10 @@ void ui_flush(void) } if (pending_mode_info_update) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array style = mode_style_array(&arena); bool enabled = (*p_guicursor != NUL); ui_call_mode_info_set(enabled, style); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); pending_mode_info_update = false; } if (pending_mode_update && !starting) { @@ -566,7 +565,7 @@ void ui_check_mouse(void) // - 'a' is in 'mouse' and "c" is in MOUSE_A, or // - the current buffer is a help file and 'h' is in 'mouse' and we are in a // normal editing mode (not at hit-return message). - for (char_u *p = p_mouse; *p; p++) { + for (char_u *p = (char_u *)p_mouse; *p; p++) { switch (*p) { case 'a': if (vim_strchr(MOUSE_A, checkfor) != NULL) { @@ -663,6 +662,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.h b/src/nvim/ui.h index 7dd2f5bce3..996b3467a6 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -47,8 +47,6 @@ enum { typedef int LineFlags; -EXTERN ArenaMem ui_ext_fixblk INIT(= NULL); - struct ui_t { bool rgb; bool override; ///< Force highest-requested UI capabilities. diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 84098e9476..809d278029 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -194,9 +194,9 @@ static void ui_bridge_suspend_event(void **argv) static void ui_bridge_option_set(UI *ui, String name, Object value) { - String copy_name = copy_string(name); + String copy_name = copy_string(name, NULL); Object *copy_value = xmalloc(sizeof(Object)); - *copy_value = copy_object(value); + *copy_value = copy_object(value, NULL); UI_BRIDGE_CALL(ui, option_set, 4, ui, copy_name.data, INT2PTR(copy_name.size), copy_value); // TODO(bfredl): when/if TUI/bridge teardown is refactored to use events, the diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index a586fec3bf..265c54f72d 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -55,7 +55,7 @@ UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, /// async 'redraw' events, which are expected when nvim acts as an ui client. /// get handled in msgpack_rpc/unpacker.c and directly dispatched to handlers /// of specific ui events, like ui_client_event_grid_resize and so on. -Object handle_ui_client_redraw(uint64_t channel_id, Array args, Error *error) +Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Error *error) { api_set_error(error, kErrorTypeValidation, "'redraw' cannot be sent as a request"); return NIL; 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..97a925f3ad 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,12 +680,12 @@ 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; } - bool has_directory = os_isdir((char_u *)dir_name); + bool has_directory = os_isdir(dir_name); if (!has_directory && *dirp == NUL && !reading) { // Last directory in the list does not exist, create it. int ret; @@ -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; } @@ -806,7 +773,7 @@ static bool serialize_header(bufinfo_T *bi, char_u *hash) undo_write_bytes(bi, (uintmax_t)buf->b_ml.ml_line_count, 4); size_t len = buf->b_u_line_ptr ? STRLEN(buf->b_u_line_ptr) : 0; undo_write_bytes(bi, len, 4); - if (len > 0 && !undo_write(bi, buf->b_u_line_ptr, len)) { + if (len > 0 && !undo_write(bi, (char_u *)buf->b_u_line_ptr, len)) { return false; } undo_write_bytes(bi, (uintmax_t)buf->b_u_line_lnum, 4); @@ -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); } } @@ -1636,7 +1593,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT curbuf->b_u_oldhead = old_idx < 0 ? NULL : uhp_table[old_idx]; curbuf->b_u_newhead = new_idx < 0 ? NULL : uhp_table[new_idx]; curbuf->b_u_curhead = cur_idx < 0 ? NULL : uhp_table[cur_idx]; - curbuf->b_u_line_ptr = line_ptr; + curbuf->b_u_line_ptr = (char *)line_ptr; curbuf->b_u_line_lnum = line_lnum; curbuf->b_u_line_colnr = line_colnr; curbuf->b_u_numhead = num_head; @@ -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 - for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum) { + 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 { @@ -2636,7 +2560,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == curbuf && wp->w_p_cole > 0) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } } @@ -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 @@ -3077,13 +2960,11 @@ void u_saveline(linenr_T lnum) } else { curbuf->b_u_line_colnr = 0; } - curbuf->b_u_line_ptr = u_save_line(lnum); + curbuf->b_u_line_ptr = (char *)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,15 +2991,15 @@ void u_undoline(void) return; } - oldp = u_save_line(curbuf->b_u_line_lnum); - ml_replace(curbuf->b_u_line_lnum, (char *)curbuf->b_u_line_ptr, true); + char_u *oldp = u_save_line(curbuf->b_u_line_lnum); + ml_replace(curbuf->b_u_line_lnum, 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), (colnr_T)STRLEN(curbuf->b_u_line_ptr), kExtmarkUndo); xfree(curbuf->b_u_line_ptr); - curbuf->b_u_line_ptr = oldp; + curbuf->b_u_line_ptr = (char *)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..5b2101587f 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" @@ -104,7 +106,7 @@ static struct { /// Return NULL if there is no matching command. /// /// @param *p end of the command (possibly including count) -/// @param full set to TRUE for a full match +/// @param full set to true for a full match /// @param xp used for completion, NULL otherwise /// @param complp completion flags or NULL char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) @@ -125,7 +127,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) for (j = 0; j < gap->ga_len; j++) { uc = USER_CMD_GA(gap, j); cp = eap->cmd; - np = (char *)uc->uc_name; + np = uc->uc_name; k = 0; while (k < len && *np != NUL && *cp++ == *np++) { k++; @@ -165,9 +167,9 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) } if (xp != NULL) { xp->xp_luaref = uc->uc_compl_luaref; - xp->xp_arg = (char *)uc->uc_compl_arg; + xp->xp_arg = 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. @@ -214,7 +216,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) // Check for attributes while (*arg == '-') { arg++; // Skip "-". - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); if (*p == NUL) { // Cursor is still in the attribute. p = strchr(arg, '='); @@ -246,7 +248,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) } // After the attributes comes the new command name. - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); if (*p == NUL) { xp->xp_context = EXPAND_USER_COMMANDS; xp->xp_pattern = (char *)arg; @@ -270,12 +272,12 @@ char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; } idx -= buf->b_ucmds.ga_len; if (idx < ucmds.ga_len) { - char *name = (char *)USER_CMD(idx)->uc_name; + char *name = USER_CMD(idx)->uc_name; for (int i = 0; i < buf->b_ucmds.ga_len; i++) { if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) { @@ -295,14 +297,14 @@ char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) char *get_user_command_name(int idx, int cmdidx) { if (cmdidx == CMD_USER && idx < ucmds.ga_len) { - return (char *)USER_CMD(idx)->uc_name; + return USER_CMD(idx)->uc_name; } if (cmdidx == CMD_USER_BUF) { // In cmdwin, the alternative buffer should be used. const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; } } return NULL; @@ -529,7 +531,7 @@ static void uc_list(char *name, size_t name_len) } } - msg_outtrans_special(cmd->uc_rep, false, + msg_outtrans_special((char_u *)cmd->uc_rep, false, name_len == 0 ? Columns - 47 : 0); if (p_verbose > 0) { last_set_msg(cmd->uc_script_ctx); @@ -881,17 +883,17 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, gap->ga_len++; - cmd->uc_name = (char_u *)p; + cmd->uc_name = p; } - cmd->uc_rep = (char_u *)rep_buf; + cmd->uc_rep = rep_buf; cmd->uc_argt = 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_arg = compl_arg; cmd->uc_compl_luaref = compl_luaref; cmd->uc_preview_luaref = preview_luaref; cmd->uc_addr_type = addr_type; @@ -928,7 +930,7 @@ void ex_command(exarg_T *eap) // Check for attributes while (*p == '-') { p++; - end = (char *)skiptowhite((char_u *)p); + end = skiptowhite(p); if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, (char_u **)&compl_arg, &addr_type_arg) == FAIL) { return; @@ -1059,11 +1061,11 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, buf[l++] = arg[++pos]; } else { buf[l++] = arg[pos]; - if (ascii_iswhite(arg[pos + 1])) { - *end = pos + 1; - *len = l; - return false; - } + } + if (ascii_iswhite(arg[pos + 1])) { + *end = pos + 1; + *len = l; + return false; } } @@ -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) { @@ -1567,7 +1569,7 @@ int do_ucmd(exarg_T *eap, bool preview) // Second round: copy result into "buf". buf = NULL; for (;;) { - p = (char *)cmd->uc_rep; // source + p = cmd->uc_rep; // source q = buf; // destination totlen = 0; diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h index 637862b216..4d2cf0d9de 100644 --- a/src/nvim/usercmd.h +++ b/src/nvim/usercmd.h @@ -4,14 +4,14 @@ #include "nvim/ex_cmds_defs.h" typedef struct ucmd { - char_u *uc_name; // The command name + char *uc_name; // The command name uint32_t uc_argt; // The argument type - char_u *uc_rep; // The command's replacement string + char *uc_rep; // The command's replacement string long uc_def; // The default value for a range/count int uc_compl; // completion type cmd_addr_T uc_addr_type; // The command's address type sctx_T uc_script_ctx; // SCTX where the command was defined - char_u *uc_compl_arg; // completion argument if any + char *uc_compl_arg; // completion argument if any LuaRef uc_compl_luaref; // Reference to Lua completion function LuaRef uc_preview_luaref; // Reference to Lua preview function LuaRef uc_luaref; // Reference to Lua function diff --git a/src/nvim/version.c b/src/nvim/version.c index 3ffae6592c..5888d4614e 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" @@ -2015,7 +2016,7 @@ void ex_version(exarg_T *eap) /// Output a string for the version message. If it's going to wrap, output a /// newline, unless the message is too long to fit on the screen anyway. -/// When "wrap" is TRUE wrap the string in []. +/// When "wrap" is true wrap the string in []. /// @param s /// @param wrap static void version_msg_wrap(char *s, int wrap) @@ -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; @@ -2071,7 +2072,7 @@ void list_in_columns(char_u **items, int size, int current) // Find the length of the longest item, use that + 1 as the column width. int i; for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) { - int l = vim_strsize((char *)items[i]) + (i == current ? 2 : 0); + int l = vim_strsize(items[i]) + (i == current ? 2 : 0); if (l > width) { width = l; @@ -2083,7 +2084,7 @@ void list_in_columns(char_u **items, int size, int current) if (Columns < width) { // Not enough screen columns - show one per line for (i = 0; i < item_count; i++) { - version_msg_wrap((char *)items[i], i == current); + version_msg_wrap(items[i], i == current); if (msg_col > 0 && i < item_count - 1) { msg_putchar('\n'); } @@ -2105,7 +2106,7 @@ void list_in_columns(char_u **items, int size, int current) if (idx == current) { msg_putchar('['); } - msg_puts((char *)items[idx]); + msg_puts(items[idx]); if (idx == current) { msg_putchar(']'); } @@ -2199,7 +2200,7 @@ void maybe_intro_message(void) if (buf_is_empty(curbuf) && (curbuf->b_fname == NULL) && (firstwin->w_next == NULL) - && (vim_strchr((char *)p_shm, SHM_INTRO) == NULL)) { + && (vim_strchr(p_shm, SHM_INTRO) == NULL)) { intro_message(false); } } @@ -2208,7 +2209,7 @@ void maybe_intro_message(void) /// Only used when starting Vim on an empty file, without a file name. /// Or with the ":intro" command (for Sven :-). /// -/// @param colon TRUE for ":intro" +/// @param colon true for ":intro" void intro_message(int colon) { int i; @@ -2255,7 +2256,7 @@ void intro_message(int colon) row = blanklines / 2; if (((row >= 2) && (Columns >= 50)) || colon) { - for (i = 0; i < (int)ARRAY_SIZE(lines); ++i) { + for (i = 0; i < (int)ARRAY_SIZE(lines); i++) { p = lines[i]; if (sponsor != 0) { @@ -2273,7 +2274,7 @@ void intro_message(int colon) } if (*p != NUL) { - do_intro_line(row, (char_u *)_(p), 0); + do_intro_line(row, _(p), 0); } row++; } @@ -2286,15 +2287,14 @@ void intro_message(int colon) } } -static void do_intro_line(long row, char_u *mesg, int attr) +static void do_intro_line(long row, char *mesg, int attr) { - long col; - char_u *p; + char *p; int l; int clen; // Center the message horizontally. - col = vim_strsize((char *)mesg); + long col = vim_strsize(mesg); col = (Columns - col) / 2; @@ -2309,8 +2309,8 @@ static void do_intro_line(long row, char_u *mesg, int attr) for (l = 0; p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); l++) { - clen += ptr2cells((char *)p + l); - l += utfc_ptr2len((char *)p + l) - 1; + clen += ptr2cells(p + l); + l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); grid_puts_len(&default_grid, p, l, (int)row, (int)col, @@ -2325,6 +2325,6 @@ static void do_intro_line(long row, char_u *mesg, int attr) void ex_intro(exarg_T *eap) { screenclear(); - intro_message(TRUE); - wait_return(TRUE); + intro_message(true); + wait_return(true); } diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 31ac5a67ff..ec8d6ab153 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -167,15 +167,6 @@ enum { #define MIN_SWAP_PAGE_SIZE 1048 #define MAX_SWAP_PAGE_SIZE 50000 -// Boolean constants - -#ifndef TRUE -# define FALSE 0 // note: this is an int, not a long! -# define TRUE 1 -#endif - -#define MAYBE 2 // sometimes used for a variant on TRUE - #define STATUS_HEIGHT 1 // height of a status line under a window #define QF_WINHEIGHT 10 // default height for quickfix window @@ -199,6 +190,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 @@ -238,10 +230,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() # endif #endif -#define STRRCHR(s, c) (char_u *)strrchr((const char *)(s), (c)) - -#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) -#define STRNCAT(d, s, n) strncat((char *)(d), (char *)(s), (size_t)(n)) +#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf) #define STRLCAT(d, s, n) xstrlcat((char *)(d), (char *)(s), (size_t)(n)) // Character used as separated in autoload function/variable names. @@ -274,8 +263,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/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index b8835127e7..55f54cedbe 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -142,9 +142,7 @@ static inline void viml_preader_get_line(ParserInputReader *const preader, .allocated = true, .size = pline.size, }; - cpline.data = (char *)string_convert(&preader->conv, - (char_u *)pline.data, - &cpline.size); + cpline.data = string_convert(&preader->conv, (char *)pline.data, &cpline.size); if (pline.allocated) { xfree((void *)pline.data); } diff --git a/src/nvim/window.c b/src/nvim/window.c index b737215616..74ea1172ee 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" @@ -37,13 +42,13 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/optionstr.h" #include "nvim/os/os.h" #include "nvim/os_unix.h" #include "nvim/path.h" #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 +127,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; @@ -210,7 +215,7 @@ newwindow: case Ctrl_Q: case 'q': reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("quit", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("quit", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -218,7 +223,7 @@ newwindow: case Ctrl_C: case 'c': reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("close", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("close", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -251,7 +256,7 @@ newwindow: case 'o': CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("only", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("only", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -483,14 +488,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 +523,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; @@ -617,12 +622,12 @@ wingotofile: } } -static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Prenum) +static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum) { size_t len = STRLCPY(bufp, cmd, bufsize); if (Prenum > 0 && len < bufsize) { - vim_snprintf((char *)bufp + len, bufsize - len, "%" PRId64, Prenum); + vim_snprintf(bufp + len, bufsize - len, "%" PRId64, Prenum); } } @@ -698,16 +703,16 @@ 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); + redraw_later(wp, UPD_VALID); return wp; } @@ -722,36 +727,38 @@ void win_set_minimal_style(win_T *wp) // Hide EOB region: use " " fillchar and cleared highlighting if (wp->w_p_fcs_chars.eob != ' ') { - char_u *old = wp->w_p_fcs; + char_u *old = (char_u *)wp->w_p_fcs; wp->w_p_fcs = ((*old == NUL) - ? (char_u *)xstrdup("eob: ") - : 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); + ? xstrdup("eob: ") + : concat_str((char *)old, ",eob: ")); + free_string_option((char *)old); } + // TODO(bfredl): this could use a highlight namespace directly, + // and avoid pecularities around window options + char_u *old = (char_u *)wp->w_p_winhl; + wp->w_p_winhl = ((*old == NUL) + ? xstrdup("EndOfBuffer:") + : concat_str((char *)old, ",EndOfBuffer:")); + free_string_option((char *)old); + parse_winhl_opt(wp); + // signcolumn: use 'auto' if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) { free_string_option(wp->w_p_scl); - wp->w_p_scl = (char_u *)xstrdup("auto"); + wp->w_p_scl = xstrdup("auto"); } // foldcolumn: use '0' if (wp->w_p_fdc[0] != '0') { free_string_option(wp->w_p_fdc); - wp->w_p_fdc = (char_u *)xstrdup("0"); + wp->w_p_fdc = xstrdup("0"); } // colorcolumn: cleared if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { free_string_option(wp->w_p_cc); - wp->w_p_cc = (char_u *)xstrdup(""); + wp->w_p_cc = xstrdup(""); } } @@ -789,13 +796,13 @@ 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); - must_redraw = MAX(must_redraw, VALID); + win_set_inner_size(wp, true); + must_redraw = MAX(must_redraw, UPD_VALID); wp->w_pos_changed = true; if (change_external || change_border) { wp->w_hl_needs_update = true; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } // compute initial position @@ -832,7 +839,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) // changing border style while keeping border only requires redrawing border if (fconfig.border) { wp->w_redr_border = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } @@ -921,7 +928,7 @@ void ui_ext_win_position(win_T *wp) wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { wp->w_grid_alloc.valid = false; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } } else { @@ -1270,7 +1277,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); } /* @@ -1292,9 +1299,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } else { curfrp = oldwin->w_frame; if (flags & WSP_BELOW) { - before = FALSE; + before = false; } else if (flags & WSP_ABOVE) { - before = TRUE; + before = true; } else if (flags & WSP_VERT) { before = !p_spr; } else { @@ -1467,8 +1474,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // Both windows need redrawing. Update all status lines, in case they // show something related to the window count or position. - redraw_later(wp, NOT_VALID); - redraw_later(oldwin, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); + redraw_later(oldwin, UPD_NOT_VALID); status_redraw_all(); if (need_status) { @@ -1563,10 +1570,10 @@ static void win_init(win_T *newp, win_T *oldp, int flags) taggy_T *tag = &newp->w_tagstack[i]; *tag = oldp->w_tagstack[i]; if (tag->tagname != NULL) { - tag->tagname = vim_strsave(tag->tagname); + tag->tagname = xstrdup(tag->tagname); } if (tag->user_data != NULL) { - tag->user_data = vim_strsave(tag->user_data); + tag->user_data = xstrdup(tag->user_data); } } newp->w_tagstackidx = oldp->w_tagstackidx; @@ -1590,7 +1597,7 @@ static void win_init_some(win_T *newp, win_T *oldp) { // Use the same argument list. newp->w_alist = oldp->w_alist; - ++newp->w_alist->al_refcount; + newp->w_alist->al_refcount++; newp->w_arg_idx = oldp->w_arg_idx; // copy options from existing window @@ -1670,7 +1677,7 @@ int win_count(void) int count = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - ++count; + count++; } return count; } @@ -1716,7 +1723,7 @@ int make_windows(int count, bool vertical) block_autocmds(); // todo is number of windows left to create - for (todo = count - 1; todo > 0; --todo) { + for (todo = count - 1; todo > 0; todo--) { if (vertical) { if (win_split(curwin->w_width - (curwin->w_width - todo) / (todo + 1) - 1, WSP_VERT | WSP_ABOVE) == FAIL) { @@ -1830,8 +1837,8 @@ static void win_exchange(long Prenum) } win_enter(wp, true); - redraw_later(curwin, NOT_VALID); - redraw_later(wp, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } // rotate windows: if upwards true the second window becomes the first one @@ -1915,7 +1922,7 @@ static void win_rotate(bool upwards, int count) wp1->w_pos_changed = true; wp2->w_pos_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /* @@ -2027,7 +2034,7 @@ void win_move_after(win_T *win1, win_T *win2) frame_append(win2->w_frame, win1->w_frame); (void)win_comp_pos(); // recompute w_winrow for all windows - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } win_enter(win1, false); @@ -2075,7 +2082,7 @@ static int get_maximum_wincount(frame_T *fr, int height) void win_equal(win_T *next_curwin, bool current, int dir) { if (dir == 0) { - dir = *p_ead; + dir = (unsigned char)(*p_ead); } win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, topframe, dir, 0, tabline_height(), @@ -2118,7 +2125,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int frame_new_height(topfr, height, false, false); topfr->fr_win->w_wincol = col; frame_new_width(topfr, width, false, false); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } else if (topfr->fr_layout == FR_ROW) { topfr->fr_width = width; @@ -2193,7 +2200,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } if (has_next_curwin) { - --totwincount; // don't count curwin + totwincount--; // don't count curwin } } @@ -2323,7 +2330,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } if (has_next_curwin) { - --totwincount; // don't count curwin + totwincount--; // don't count curwin } } @@ -2429,7 +2436,7 @@ void entering_window(win_T *const win) void win_init_empty(win_T *wp) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_lines_valid = 0; wp->w_cursor.lnum = 1; wp->w_curswant = wp->w_cursor.col = 0; @@ -2459,7 +2466,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 +2503,7 @@ void close_windows(buf_T *buf, bool keep_curwin) } } - --RedrawingDisabled; + RedrawingDisabled--; redraw_tabline = true; if (h != tabline_height()) { @@ -2928,7 +2935,7 @@ int win_close(win_T *win, bool free_buf, bool force) } curwin->w_pos_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); return OK; } @@ -3089,7 +3096,7 @@ void win_free_all(void) cmdwin_type = 0; while (first_tabpage->tp_next != NULL) { - tabpage_close(TRUE); + tabpage_close(true); } while (lastwin != NULL && lastwin->w_floating) { @@ -3688,7 +3695,7 @@ static void frame_add_vsep(const frame_T *frp) wp = frp->fr_win; if (wp->w_vsep_width == 0) { if (wp->w_width > 0) { // don't make it negative - --wp->w_width; + wp->w_width--; } wp->w_vsep_width = 1; } @@ -3820,7 +3827,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) { @@ -4018,7 +4025,7 @@ static tabpage_T *alloc_tabpage(void) // Init t: variables. tp->tp_vars = tv_dict_alloc(); init_var_dict(tp->tp_vars, &tp->tp_winvar, VAR_SCOPE); - tp->tp_diff_invalid = TRUE; + tp->tp_diff_invalid = true; tp->tp_ch_used = p_ch; return tp; @@ -4030,7 +4037,7 @@ void free_tabpage(tabpage_T *tp) pmap_del(handle_T)(&tabpage_handles, tp->handle); diff_clear(tp); - for (idx = 0; idx < SNAP_COUNT; ++idx) { + for (idx = 0; idx < SNAP_COUNT; idx++) { clear_snapshot(tp, idx); } vars_clear(&tp->tp_vars->dv_hashtab); // free all t: variables @@ -4093,7 +4100,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; @@ -4108,7 +4115,7 @@ int win_new_tabpage(int after, char_u *filename) newtp->tp_topframe = topframe; last_status(false); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); tabpage_check_windows(old_curtab); @@ -4166,7 +4173,7 @@ int make_tabpages(int maxcount) */ block_autocmds(); - for (todo = count - 1; todo > 0; --todo) { + for (todo = count - 1; todo > 0; todo--) { if (win_new_tabpage(0, NULL) == FAIL) { break; } @@ -4239,7 +4246,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; } @@ -4254,7 +4261,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; } @@ -4336,6 +4343,11 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a // Use the stored value of p_ch, so that it can be different for each tab page. if (p_ch != curtab->tp_ch_used) { clear_cmdline = true; + if (msg_grid.chars && p_ch < curtab->tp_ch_used) { + // TODO(bfredl): a bit expensive, should be enough to invalidate the + // region between the old and the new p_ch. + grid_invalidate(&msg_grid); + } } p_ch = curtab->tp_ch_used; @@ -4366,7 +4378,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a } } - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /// tells external UI that windows and inline floats in old_curtab are invisible @@ -4433,7 +4445,7 @@ void goto_tabpage(int n) // "gT": go to previous tab page, wrap around end. "N gT" repeats // this N times. ttp = curtab; - for (i = n; i < 0; ++i) { + for (i = n; i < 0; i++) { for (tp = first_tabpage; tp->tp_next != ttp && tp->tp_next != NULL; tp = tp->tp_next) {} ttp = tp; @@ -4516,7 +4528,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 @@ -4829,7 +4841,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } if (!curwin_invalid) { prevwin = curwin; // remember for CTRL-W p - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } curwin = wp; curbuf = wp->w_buffer; @@ -4860,13 +4872,20 @@ static void win_enter_ext(win_T *const wp, const int flags) curwin->w_redr_status = true; redraw_tabline = true; if (restart_edit) { - redraw_later(curwin, VALID); // causes status line redraw + redraw_later(curwin, UPD_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, UPD_NOT_VALID); + } + if (prevwin) { + if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) { + redraw_later(prevwin, UPD_NOT_VALID); + } } // set window height to desired minimal value @@ -5035,6 +5054,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; @@ -5175,7 +5196,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); } } @@ -5346,6 +5367,7 @@ void may_trigger_winscrolled(void) win_T *wp = curwin; if (wp->w_last_topline != wp->w_topline || wp->w_last_leftcol != wp->w_leftcol + || wp->w_last_skipcol != wp->w_skipcol || wp->w_last_width != wp->w_width || wp->w_last_height != wp->w_height) { char winid[NUMBUFLEN]; @@ -5359,6 +5381,7 @@ void may_trigger_winscrolled(void) if (win_valid_any_tab(wp)) { wp->w_last_topline = wp->w_topline; wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_skipcol = wp->w_skipcol; wp->w_last_width = wp->w_width; wp->w_last_height = wp->w_height; } @@ -5454,7 +5477,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) // position changed, redraw wp->w_winrow = *row; wp->w_wincol = *col; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_redr_status = true; wp->w_pos_changed = true; } @@ -5497,7 +5520,7 @@ void win_setheight_win(int height, win_T *win) if (win->w_floating) { win->w_float_config.height = height; win_config_float(win, win->w_float_config); - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } else { frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height); @@ -5517,7 +5540,7 @@ void win_setheight_win(int height, win_T *win) curtab->tp_ch_used = p_ch; msg_row = row; msg_col = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); redraw_cmdline = true; } } @@ -5551,7 +5574,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 @@ -5580,7 +5603,7 @@ static void frame_setheight(frame_T *curfrp, int height) * 2: compute the room available and adjust the height to it. * Try not to reduce the height of a window with 'winfixheight' set. */ - for (run = 1; run <= 2; ++run) { + for (run = 1; run <= 2; run++) { room = 0; room_reserved = 0; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { @@ -5653,7 +5676,7 @@ static void frame_setheight(frame_T *curfrp, int height) * that is not enough, takes lines from frames above the current * frame. */ - for (run = 0; run < 2; ++run) { + for (run = 0; run < 2; run++) { if (run == 0) { frp = curfrp->fr_next; // 1st run: start with next window } else { @@ -5719,13 +5742,13 @@ void win_setwidth_win(int width, win_T *wp) if (wp->w_floating) { wp->w_float_config.width = width; win_config_float(wp, wp->w_float_config); - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { frame_setwidth(wp->w_frame, width + wp->w_vsep_width); // recompute the window positions (void)win_comp_pos(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } @@ -5772,7 +5795,7 @@ static void frame_setwidth(frame_T *curfrp, int width) * containing frame. * 2: compute the room available and adjust the width to it. */ - for (run = 1; run <= 2; ++run) { + for (run = 1; run <= 2; run++) { room = 0; room_reserved = 0; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { @@ -5825,7 +5848,7 @@ static void frame_setwidth(frame_T *curfrp, int width) * that is not enough, takes lines from frames left of the current * frame. */ - for (run = 0; run < 2; ++run) { + for (run = 0; run < 2; run++) { if (run == 0) { frp = curfrp->fr_next; // 1st run: start with next window } else { @@ -5914,6 +5937,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; @@ -5964,6 +5994,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; @@ -6019,9 +6051,9 @@ 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); + redraw_all_later(UPD_SOME_VALID); showmode(); } @@ -6128,7 +6160,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } } (void)win_comp_pos(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } #define FRACTION_MULT 16384L @@ -6162,7 +6194,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) @@ -6225,7 +6257,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--; @@ -6266,12 +6298,12 @@ void scroll_to_fraction(win_T *wp, int prev_height) } win_comp_scroll(wp); - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); wp->w_redr_status = true; 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) { @@ -6285,7 +6317,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. @@ -6304,22 +6336,24 @@ 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?? + redraw_later(wp, UPD_NOT_VALID); // UPD_SOME_VALID?? } 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); + redraw_later(wp, UPD_NOT_VALID); } if (wp->w_buffer->terminal) { @@ -6346,7 +6380,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; @@ -6381,6 +6415,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) { @@ -6465,17 +6512,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); } @@ -6542,7 +6589,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u /* * Search forward for the last char of the file name. - * Also allow "://" when ':' is not in 'isfname'. + * Also allow ":/" when ':' is not in 'isfname'. */ len = 0; while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') @@ -6561,7 +6608,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)); } @@ -6572,7 +6619,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) { @@ -6726,7 +6773,7 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) wp->w_hsep_height = 0; comp_col(); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else { // For a column or row frame, recursively call this function for all child frames FOR_ALL_FRAMES(fp, fr->fr_child) { @@ -6738,9 +6785,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) @@ -6756,7 +6805,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) { @@ -6777,7 +6826,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; } } @@ -7024,7 +7073,7 @@ void restore_snapshot(int idx, int close_curwin) if (wp != NULL && close_curwin) { win_goto(wp); } - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } clear_snapshot(curtab, idx); } @@ -7095,7 +7144,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; @@ -7228,6 +7277,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 = 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) { @@ -7265,19 +7396,6 @@ int win_getid(typval_T *argvars) return 0; } -int win_gotoid(typval_T *argvars) -{ - int id = (int)tv_get_number(&argvars[0]); - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->handle == id) { - goto_tabpage_win(tp, wp); - return 1; - } - } - return 0; -} - void win_get_tabwin(handle_T id, int *tabnr, int *winnr) { *tabnr = 0; |