aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2022-08-19 12:26:08 -0600
committerJosh Rahm <rahm@google.com>2022-08-19 13:06:41 -0600
commita7237662f96933efe29eed8212464571e3778cd0 (patch)
tree27930202726b4251437c8cfa53069f65b4db90dc /src
parent02292344929069ea63c0bb872cc22d552d86b67f (diff)
parentb2f979b30beac67906b2dd717fcb6a34f46f5e54 (diff)
downloadrneovim-tmp.tar.gz
rneovim-tmp.tar.bz2
rneovim-tmp.zip
Merge branch 'master' of https://github.com/neovim/neovim into rahmtmp
Diffstat (limited to 'src')
-rw-r--r--src/mpack/mpack_core.h1
-rwxr-xr-xsrc/nvim/CMakeLists.txt17
-rw-r--r--src/nvim/api/autocmd.c2
-rw-r--r--src/nvim/api/buffer.c1
-rw-r--r--src/nvim/api/command.c16
-rw-r--r--src/nvim/api/extmark.c9
-rw-r--r--src/nvim/api/keysets.lua3
-rw-r--r--src/nvim/api/private/helpers.c6
-rw-r--r--src/nvim/api/private/helpers.h10
-rw-r--r--src/nvim/api/ui.c6
-rw-r--r--src/nvim/api/vim.c69
-rw-r--r--src/nvim/api/vimscript.c7
-rw-r--r--src/nvim/api/win_config.c6
-rw-r--r--src/nvim/api/window.c27
-rw-r--r--src/nvim/arglist.c1157
-rw-r--r--src/nvim/arglist.h11
-rw-r--r--src/nvim/autocmd.c28
-rw-r--r--src/nvim/autocmd.h21
-rw-r--r--src/nvim/buffer.c310
-rw-r--r--src/nvim/buffer.h5
-rw-r--r--src/nvim/buffer_defs.h57
-rw-r--r--src/nvim/change.c10
-rw-r--r--src/nvim/channel.c5
-rw-r--r--src/nvim/charset.c2
-rw-r--r--src/nvim/cmdhist.c743
-rw-r--r--src/nvim/cmdhist.h33
-rw-r--r--src/nvim/context.c3
-rw-r--r--src/nvim/cursor.c2
-rw-r--r--src/nvim/debugger.c38
-rw-r--r--src/nvim/decoration.c54
-rw-r--r--src/nvim/decoration_provider.c6
-rw-r--r--src/nvim/decoration_provider.h1
-rw-r--r--src/nvim/diff.c19
-rw-r--r--src/nvim/digraph.c4
-rw-r--r--src/nvim/drawline.c2706
-rw-r--r--src/nvim/drawline.h24
-rw-r--r--src/nvim/drawscreen.c2373
-rw-r--r--src/nvim/drawscreen.h25
-rw-r--r--src/nvim/edit.c50
-rw-r--r--src/nvim/eval.c966
-rw-r--r--src/nvim/eval.h9
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/eval/encode.c6
-rw-r--r--src/nvim/eval/funcs.c1042
-rw-r--r--src/nvim/eval/typval.c4
-rw-r--r--src/nvim/eval/typval.h5
-rw-r--r--src/nvim/eval/userfunc.c352
-rw-r--r--src/nvim/eval/userfunc.h9
-rw-r--r--src/nvim/eval/vars.c3
-rw-r--r--src/nvim/ex_cmds.c1163
-rw-r--r--src/nvim/ex_cmds.h6
-rw-r--r--src/nvim/ex_cmds.lua8
-rw-r--r--src/nvim/ex_cmds2.c1867
-rw-r--r--src/nvim/ex_cmds2.h24
-rw-r--r--src/nvim/ex_docmd.c1196
-rw-r--r--src/nvim/ex_eval.c54
-rw-r--r--src/nvim/ex_eval.h76
-rw-r--r--src/nvim/ex_eval_defs.h79
-rw-r--r--src/nvim/ex_getln.c1241
-rw-r--r--src/nvim/ex_getln.h24
-rw-r--r--src/nvim/ex_session.c3
-rw-r--r--src/nvim/file_search.c35
-rw-r--r--src/nvim/fileio.c45
-rw-r--r--src/nvim/fileio.h1
-rw-r--r--src/nvim/fold.c129
-rw-r--r--src/nvim/generators/gen_unicode_tables.lua6
-rw-r--r--src/nvim/getchar.c153
-rw-r--r--src/nvim/globals.h19
-rw-r--r--src/nvim/grid.c131
-rw-r--r--src/nvim/grid.h4
-rw-r--r--src/nvim/hardcopy.c37
-rw-r--r--src/nvim/hashtab.c10
-rw-r--r--src/nvim/help.c1178
-rw-r--r--src/nvim/help.h11
-rw-r--r--src/nvim/highlight.c280
-rw-r--r--src/nvim/highlight.h8
-rw-r--r--src/nvim/highlight_defs.h20
-rw-r--r--src/nvim/highlight_group.c67
-rw-r--r--src/nvim/highlight_group.h3
-rw-r--r--src/nvim/if_cscope.c12
-rw-r--r--src/nvim/indent.c312
-rw-r--r--src/nvim/indent_c.c44
-rw-r--r--src/nvim/insexpand.c16
-rw-r--r--src/nvim/lua/converter.c2
-rw-r--r--src/nvim/lua/converter.h2
-rw-r--r--src/nvim/lua/executor.c14
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/lua/stdlib.c3
-rw-r--r--src/nvim/lua/xdiff.c6
-rw-r--r--src/nvim/main.c48
-rw-r--r--src/nvim/main.h1
-rw-r--r--src/nvim/mapping.c29
-rw-r--r--src/nvim/mark.c5
-rw-r--r--src/nvim/match.c9
-rw-r--r--src/nvim/mbyte.c224
-rw-r--r--src/nvim/mbyte.h49
-rw-r--r--src/nvim/mbyte_defs.h56
-rw-r--r--src/nvim/memfile.c4
-rw-r--r--src/nvim/memline.c55
-rw-r--r--src/nvim/memory.c6
-rw-r--r--src/nvim/menu.c29
-rw-r--r--src/nvim/menu.h21
-rw-r--r--src/nvim/menu_defs.h64
-rw-r--r--src/nvim/message.c143
-rw-r--r--src/nvim/mouse.c7
-rw-r--r--src/nvim/move.c26
-rw-r--r--src/nvim/normal.c85
-rw-r--r--src/nvim/ops.c133
-rw-r--r--src/nvim/option.c983
-rw-r--r--src/nvim/os/env.c28
-rw-r--r--src/nvim/os/fs.c27
-rw-r--r--src/nvim/os/input.c4
-rw-r--r--src/nvim/os/pty_conpty_win.h3
-rw-r--r--src/nvim/os/shell.c1
-rw-r--r--src/nvim/os/signal.c2
-rw-r--r--src/nvim/path.c26
-rw-r--r--src/nvim/popupmenu.c (renamed from src/nvim/popupmnu.c)31
-rw-r--r--src/nvim/popupmenu.h (renamed from src/nvim/popupmnu.h)8
-rw-r--r--src/nvim/profile.c664
-rw-r--r--src/nvim/profile.h3
-rw-r--r--src/nvim/quickfix.c148
-rw-r--r--src/nvim/regexp.c32
-rw-r--r--src/nvim/regexp_bt.c16
-rw-r--r--src/nvim/regexp_defs.h1
-rw-r--r--src/nvim/regexp_nfa.c8
-rw-r--r--src/nvim/runtime.c1200
-rw-r--r--src/nvim/runtime.h71
-rw-r--r--src/nvim/screen.c5795
-rw-r--r--src/nvim/screen.h34
-rw-r--r--src/nvim/search.c90
-rw-r--r--src/nvim/shada.c30
-rw-r--r--src/nvim/sign.c83
-rw-r--r--src/nvim/sign_defs.h14
-rw-r--r--src/nvim/spell.c3902
-rw-r--r--src/nvim/spell_defs.h65
-rw-r--r--src/nvim/spellfile.c126
-rw-r--r--src/nvim/spellsuggest.c3800
-rw-r--r--src/nvim/spellsuggest.h9
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/strings.c20
-rw-r--r--src/nvim/syntax.c108
-rw-r--r--src/nvim/tag.c88
-rw-r--r--src/nvim/tag.h2
-rw-r--r--src/nvim/terminal.c10
-rw-r--r--src/nvim/testdir/runtest.vim1
-rw-r--r--src/nvim/testdir/test_arglist.vim44
-rw-r--r--src/nvim/testdir/test_autocmd.vim75
-rw-r--r--src/nvim/testdir/test_bufline.vim2
-rw-r--r--src/nvim/testdir/test_cmdline.vim79
-rw-r--r--src/nvim/testdir/test_diffmode.vim2
-rw-r--r--src/nvim/testdir/test_edit.vim129
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim12
-rw-r--r--src/nvim/testdir/test_exit.vim2
-rw-r--r--src/nvim/testdir/test_expand_func.vim45
-rw-r--r--src/nvim/testdir/test_filechanged.vim9
-rw-r--r--src/nvim/testdir/test_filetype.vim4
-rw-r--r--src/nvim/testdir/test_functions.vim9
-rw-r--r--src/nvim/testdir/test_gf.vim16
-rw-r--r--src/nvim/testdir/test_global.vim5
-rw-r--r--src/nvim/testdir/test_goto.vim18
-rw-r--r--src/nvim/testdir/test_listdict.vim2
-rw-r--r--src/nvim/testdir/test_messages.vim65
-rw-r--r--src/nvim/testdir/test_normal.vim258
-rw-r--r--src/nvim/testdir/test_options.vim19
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim4
-rw-r--r--src/nvim/testdir/test_registers.vim11
-rw-r--r--src/nvim/testdir/test_selectmode.vim150
-rw-r--r--src/nvim/testdir/test_spell.vim10
-rw-r--r--src/nvim/testdir/test_spellfile.vim39
-rw-r--r--src/nvim/testdir/test_substitute.vim38
-rw-r--r--src/nvim/testdir/test_tabpage.vim16
-rw-r--r--src/nvim/testdir/test_tagjump.vim13
-rw-r--r--src/nvim/testdir/test_textobjects.vim1
-rw-r--r--src/nvim/testdir/test_undo.vim6
-rw-r--r--src/nvim/testdir/test_utf8.vim45
-rw-r--r--src/nvim/testdir/test_vimscript.vim25
-rw-r--r--src/nvim/testdir/test_visual.vim206
-rw-r--r--src/nvim/testdir/test_writefile.vim44
-rw-r--r--src/nvim/testing.c15
-rw-r--r--src/nvim/tui/tui.c70
-rw-r--r--src/nvim/types.h2
-rw-r--r--src/nvim/ui.c8
-rw-r--r--src/nvim/ui_compositor.c4
-rw-r--r--src/nvim/undo.c760
-rw-r--r--src/nvim/undo_defs.h6
-rw-r--r--src/nvim/usercmd.c10
-rw-r--r--src/nvim/version.c5
-rw-r--r--src/nvim/vim.h5
-rw-r--r--src/nvim/viml/parser/expressions.c4
-rw-r--r--src/nvim/window.c227
190 files changed, 20385 insertions, 19119 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..79ae7994f7 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -9,9 +9,9 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/eval/typval.h"
-#include "nvim/fileio.h"
#include "nvim/lua/executor.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index d3895d31cf..5e90e40dd3 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -18,6 +18,7 @@
#include "nvim/change.h"
#include "nvim/cursor.h"
#include "nvim/decoration.h"
+#include "nvim/drawscreen.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/extmark.h"
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index bc766ff39c..1323fc347b 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -11,6 +11,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/autocmd.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/lua/executor.h"
#include "nvim/ops.h"
#include "nvim/regexp.h"
@@ -300,10 +301,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
FUNC_API_SINCE(10)
{
exarg_T ea;
- memset(&ea, 0, sizeof(ea));
+ CLEAR_FIELD(ea);
CmdParseInfo cmdinfo;
- memset(&cmdinfo, 0, sizeof(cmdinfo));
+ CLEAR_FIELD(cmdinfo);
char *cmdline = NULL;
char *cmdname = NULL;
@@ -625,6 +626,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
garray_T capture_local;
const int save_msg_silent = msg_silent;
garray_T * const save_capture_ga = capture_ga;
+ const int save_msg_col = msg_col;
if (output) {
ga_init(&capture_local, 1, 80);
@@ -635,6 +637,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
try_start();
if (output) {
msg_silent++;
+ msg_col = 0; // prevent leading spaces
}
WITH_SCRIPT_CONTEXT(channel_id, {
@@ -644,6 +647,8 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
if (output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
+ // Put msg_col back where it was, since nothing should have been written.
+ msg_col = save_msg_col;
}
try_end(err);
@@ -819,9 +824,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
char *p = replace_makeprg(eap, eap->arg, cmdlinep);
if (p != eap->arg) {
// If replace_makeprg modified the cmdline string, correct the argument pointers.
- assert(argc == 1);
eap->arg = p;
- eap->args[0] = p;
+ // We can only know the position of the first argument because the argument list can be used
+ // multiple times in makeprg / grepprg.
+ if (argc >= 1) {
+ eap->args[0] = p;
+ }
}
}
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index da1b6beeda..933aa85530 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -10,11 +10,11 @@
#include "nvim/api/private/helpers.h"
#include "nvim/charset.h"
#include "nvim/decoration_provider.h"
+#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
#include "nvim/memline.h"
-#include "nvim/screen.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.c.generated.h"
@@ -441,8 +441,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// the extmark end position (if it exists) will be shifted
/// in when new text is inserted (true for right, false
/// for left). Defaults to false.
-/// - priority: a priority value for the highlight group. For
-/// example treesitter highlighting uses a value of 100.
+/// - priority: a priority value for the highlight group or sign
+/// attribute. For example treesitter highlighting uses a
+/// value of 100.
/// - strict: boolean that indicates extmark should not be placed
/// if the line or column value is past the end of the
/// buffer or end of the line respectively. Defaults to true.
@@ -1030,6 +1031,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
}
p->active = true;
+ p->hl_valid++;
+ p->hl_cached = false;
return;
error:
decor_provider_clear(p);
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index a764fb069b..32104ef6dc 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -106,7 +106,6 @@ return {
"reverse";
"nocombine";
"default";
- "global";
"cterm";
"foreground"; "fg";
"background"; "bg";
@@ -114,9 +113,9 @@ return {
"ctermbg";
"special"; "sp";
"link";
+ "global_link";
"fallback";
"blend";
- "temp";
};
highlight_cterm = {
"bold";
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index fad75d55be..c466fc53e1 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -19,8 +19,8 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_eval.h"
#include "nvim/extmark.h"
-#include "nvim/fileio.h"
#include "nvim/highlight_group.h"
#include "nvim/lib/kvec.h"
#include "nvim/lua/executor.h"
@@ -54,7 +54,7 @@ void try_enter(TryState *const tstate)
// save_dbg_stuff()/restore_dbg_stuff().
*tstate = (TryState) {
.current_exception = current_exception,
- .msg_list = (const struct msglist *const *)msg_list,
+ .msg_list = (const msglist_T *const *)msg_list,
.private_msg_list = NULL,
.trylevel = trylevel,
.got_int = got_int,
@@ -89,7 +89,7 @@ bool try_leave(const TryState *const tstate, Error *const err)
assert(msg_list == &tstate->private_msg_list);
assert(*msg_list == NULL);
assert(current_exception == NULL);
- msg_list = (struct msglist **)tstate->msg_list;
+ msg_list = (msglist_T **)tstate->msg_list;
current_exception = tstate->current_exception;
trylevel = tstate->trylevel;
got_int = tstate->got_int;
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 1441da853c..4608554448 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -3,7 +3,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/decoration.h"
-#include "nvim/ex_eval.h"
+#include "nvim/ex_eval_defs.h"
#include "nvim/getchar.h"
#include "nvim/lib/kvec.h"
#include "nvim/memory.h"
@@ -130,8 +130,8 @@ EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT);
/// processed and that “other VimL code” must not be affected.
typedef struct {
except_T *current_exception;
- struct msglist *private_msg_list;
- const struct msglist *const *msg_list;
+ msglist_T *private_msg_list;
+ const msglist_T *const *msg_list;
int trylevel;
int got_int;
int need_rethrow;
@@ -144,8 +144,8 @@ typedef struct {
// TODO(bfredl): prepare error-handling at "top level" (nv_event).
#define TRY_WRAP(code) \
do { \
- struct msglist **saved_msg_list = msg_list; \
- struct msglist *private_msg_list; \
+ msglist_T **saved_msg_list = msg_list; \
+ msglist_T *private_msg_list; \
msg_list = &private_msg_list; \
private_msg_list = NULL; \
code \
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 6239e414a7..6f7bfa244a 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -11,14 +11,14 @@
#include "nvim/api/ui.h"
#include "nvim/channel.h"
#include "nvim/cursor_shape.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/map.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/option.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
#include "nvim/window.h"
@@ -224,7 +224,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
ui->event = remote_ui_event;
ui->inspect = remote_ui_inspect;
- memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
+ CLEAR_FIELD(ui->ui_ext);
for (size_t i = 0; i < options.size; i++) {
ui_set_option(ui, true, options.items[i].key, options.items[i].value, err);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index e2f58dba62..e4dc219e9a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -23,17 +23,19 @@
#include "nvim/context.h"
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
@@ -52,8 +54,8 @@
#include "nvim/option.h"
#include "nvim/os/input.h"
#include "nvim/os/process.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
+#include "nvim/runtime.h"
#include "nvim/state.h"
#include "nvim/types.h"
#include "nvim/ui.h"
@@ -91,7 +93,6 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err)
}
/// Gets a highlight definition by id. |hlID()|
-///
/// @param hl_id Highlight id as returned by |hlID()|
/// @param rgb Export RGB colors
/// @param[out] err Error details, if any
@@ -180,35 +181,38 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
}
}
-/// Set active namespace for highlights.
-///
-/// NB: this function can be called from async contexts, but the
-/// semantics are not yet well-defined. To start with
-/// |nvim_set_decoration_provider| on_win and on_line callbacks
-/// are explicitly allowed to change the namespace during a redraw cycle.
+/// Set active namespace for highlights. This can be set for a single window,
+/// see |nvim_win_set_hl_ns|.
///
-/// @param ns_id the namespace to activate
+/// @param ns_id the namespace to use
/// @param[out] err Error details, if any
-void nvim__set_hl_ns(Integer ns_id, Error *err)
- FUNC_API_FAST
+void nvim_set_hl_ns(Integer ns_id, Error *err)
+ FUNC_API_SINCE(10)
{
- if (ns_id >= 0) {
- ns_hl_active = (NS)ns_id;
+ if (ns_id < 0) {
+ api_set_error(err, kErrorTypeValidation, "no such namespace");
+ return;
}
- // TODO(bfredl): this is a little bit hackish. Eventually we want a standard
- // event path for redraws caused by "fast" events. This could tie in with
- // better throttling of async events causing redraws, such as non-batched
- // nvim_buf_set_extmark calls from async contexts.
- if (!provider_active && !ns_hl_changed && must_redraw < NOT_VALID) {
- multiqueue_put(main_loop.events, on_redraw_event, 0);
- }
- ns_hl_changed = true;
+ ns_hl_global = (NS)ns_id;
+ hl_check_ns();
+ redraw_all_later(NOT_VALID);
}
-static void on_redraw_event(void **argv)
+/// Set active namespace for highlights while redrawing.
+///
+/// This function meant to be called while redrawing, primarily from
+/// |nvim_set_decoration_provider| on_win and on_line callbacks, which
+/// are allowed to change the namespace during a redraw cycle.
+///
+/// @param ns_id the namespace to activate
+/// @param[out] err Error details, if any
+void nvim_set_hl_ns_fast(Integer ns_id, Error *err)
+ FUNC_API_SINCE(10)
+ FUNC_API_FAST
{
- redraw_all_later(NOT_VALID);
+ ns_hl_fast = (NS)ns_id;
+ hl_check_ns();
}
/// Sends input-keys to Nvim, subject to various quirks controlled by `mode`
@@ -478,7 +482,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
ADD_C(args, INTEGER_OBJ(log_level));
ADD_C(args, DICTIONARY_OBJ(opts));
- return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err);
+ return NLUA_EXEC_STATIC("return vim.notify(...)", args, err);
}
/// Calculates the number of display cells occupied by `text`.
@@ -1833,11 +1837,9 @@ Array nvim_get_proc_children(Integer pid, Error *err)
if (rv == 2) {
// syscall failed (possibly because of kernel options), try shelling out.
DLOG("fallback to vim._os_proc_children()");
- Array a = ARRAY_DICT_INIT;
+ MAXSIZE_TEMP_ARRAY(a, 1);
ADD(a, INTEGER_OBJ(pid));
- String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)");
- Object o = nlua_exec(s, a, err);
- api_free_array(a);
+ Object o = NLUA_EXEC_STATIC("return vim._os_proc_children(...)", a, err);
if (o.type == kObjectTypeArray) {
rvobj = o.data.array;
} else if (!ERROR_SET(err)) {
@@ -1878,12 +1880,9 @@ Object nvim_get_proc(Integer pid, Error *err)
}
#else
// Cross-platform process info APIs are miserable, so use `ps` instead.
- Array a = ARRAY_DICT_INIT;
+ MAXSIZE_TEMP_ARRAY(a, 1);
ADD(a, INTEGER_OBJ(pid));
- String s = cstr_to_string("return vim._os_proc_info(select(1, ...))");
- Object o = nlua_exec(s, a, err);
- api_free_string(s);
- api_free_array(a);
+ Object o = NLUA_EXEC_STATIC("return vim._os_proc_info(...)", a, err);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
return NIL; // Process not found.
} else if (o.type == kObjectTypeDictionary) {
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 478e146781..a28bfd2ab9 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -14,8 +14,9 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ops.h"
+#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/vim.h"
#include "nvim/viml/parser/expressions.h"
@@ -48,6 +49,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
{
const int save_msg_silent = msg_silent;
garray_T *const save_capture_ga = capture_ga;
+ const int save_msg_col = msg_col;
garray_T capture_local;
if (output) {
ga_init(&capture_local, 1, 80);
@@ -57,6 +59,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
try_start();
if (output) {
msg_silent++;
+ msg_col = 0; // prevent leading spaces
}
const sctx_T save_current_sctx = api_set_sctx(channel_id);
@@ -65,6 +68,8 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
if (output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
+ // Put msg_col back where it was, since nothing should have been written.
+ msg_col = save_msg_col;
}
current_sctx = save_current_sctx;
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index d36c5bfb95..0c89726d71 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -10,9 +10,9 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/win_config.h"
#include "nvim/ascii.h"
+#include "nvim/drawscreen.h"
#include "nvim/highlight_group.h"
#include "nvim/option.h"
-#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -167,7 +167,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
if (fconfig.style == kWinStyleMinimal) {
win_set_minimal_style(wp);
- didset_window_options(wp);
+ didset_window_options(wp, true);
}
return wp->handle;
}
@@ -209,7 +209,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
}
if (fconfig.style == kWinStyleMinimal) {
win_set_minimal_style(win);
- didset_window_options(win);
+ didset_window_options(win, true);
}
}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 5a4ff70257..580dfd8639 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -12,12 +12,12 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/ex_docmd.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/move.h"
#include "nvim/option.h"
-#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/vim.h"
#include "nvim/window.h"
@@ -426,3 +426,28 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
try_end(err);
return res;
}
+
+/// Set highlight namespace for a window. This will use highlights defined in
+/// this namespace, but fall back to global highlights (ns=0) when missing.
+///
+/// This takes predecence over the 'winhighlight' option.
+///
+/// @param ns_id the namespace to use
+/// @param[out] err Error details, if any
+void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err)
+ FUNC_API_SINCE(10)
+{
+ win_T *win = find_window_by_handle(window, err);
+ if (!win) {
+ return;
+ }
+
+ // -1 is allowed as inherit global namespace
+ if (ns_id < -1) {
+ api_set_error(err, kErrorTypeValidation, "no such namespace");
+ }
+
+ win->w_ns_hl = (NS)ns_id;
+ win->w_hl_needs_update = true;
+ redraw_later(win, NOT_VALID);
+}
diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c
new file mode 100644
index 0000000000..7d8917cc73
--- /dev/null
+++ b/src/nvim/arglist.c
@@ -0,0 +1,1157 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// arglist.c: functions for dealing with the argument list
+
+#include <assert.h>
+#include <stdbool.h>
+
+#include "nvim/arglist.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/eval.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_getln.h"
+#include "nvim/fileio.h"
+#include "nvim/garray.h"
+#include "nvim/globals.h"
+#include "nvim/mark.h"
+#include "nvim/memory.h"
+#include "nvim/os/input.h"
+#include "nvim/path.h"
+#include "nvim/regexp.h"
+#include "nvim/strings.h"
+#include "nvim/undo.h"
+#include "nvim/version.h"
+#include "nvim/vim.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "arglist.c.generated.h"
+#endif
+
+enum {
+ AL_SET = 1,
+ AL_ADD = 2,
+ AL_DEL = 3,
+};
+
+/// Clear an argument list: free all file names and reset it to zero entries.
+void alist_clear(alist_T *al)
+{
+#define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname)
+ GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME);
+}
+
+/// Init an argument list.
+void alist_init(alist_T *al)
+{
+ ga_init(&al->al_ga, (int)sizeof(aentry_T), 5);
+}
+
+/// Remove a reference from an argument list.
+/// Ignored when the argument list is the global one.
+/// If the argument list is no longer used by any window, free it.
+void alist_unlink(alist_T *al)
+{
+ if (al != &global_alist && --al->al_refcount <= 0) {
+ alist_clear(al);
+ xfree(al);
+ }
+}
+
+/// Create a new argument list and use it for the current window.
+void alist_new(void)
+{
+ curwin->w_alist = xmalloc(sizeof(*curwin->w_alist));
+ curwin->w_alist->al_refcount = 1;
+ curwin->w_alist->id = ++max_alist_id;
+ alist_init(curwin->w_alist);
+}
+
+#if !defined(UNIX)
+
+/// Expand the file names in the global argument list.
+/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer
+/// numbers to be re-used.
+void alist_expand(int *fnum_list, int fnum_len)
+{
+ char *save_p_su = p_su;
+
+ // Don't use 'suffixes' here. This should work like the shell did the
+ // expansion. Also, the vimrc file isn't read yet, thus the user
+ // can't set the options.
+ p_su = empty_option;
+ char **old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT);
+ for (int i = 0; i < GARGCOUNT; i++) {
+ old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname);
+ }
+ int old_arg_count = GARGCOUNT;
+ char **new_arg_files;
+ int new_arg_file_count;
+ if (expand_wildcards(old_arg_count, old_arg_files,
+ &new_arg_file_count, &new_arg_files,
+ EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK
+ && new_arg_file_count > 0) {
+ alist_set(&global_alist, new_arg_file_count, new_arg_files,
+ true, fnum_list, fnum_len);
+ FreeWild(old_arg_count, old_arg_files);
+ }
+ p_su = save_p_su;
+}
+#endif
+
+/// Set the argument list for the current window.
+/// Takes over the allocated files[] and the allocated fnames in it.
+void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len)
+{
+ static int recursive = 0;
+
+ if (recursive) {
+ emsg(_(e_au_recursive));
+ return;
+ }
+ recursive++;
+
+ alist_clear(al);
+ ga_grow(&al->al_ga, count);
+ {
+ for (int i = 0; i < count; i++) {
+ if (got_int) {
+ // When adding many buffers this can take a long time. Allow
+ // interrupting here.
+ while (i < count) {
+ xfree(files[i++]);
+ }
+ break;
+ }
+
+ // May set buffer name of a buffer previously used for the
+ // argument list, so that it's re-used by alist_add.
+ if (fnum_list != NULL && i < fnum_len) {
+ buf_set_name(fnum_list[i], files[i]);
+ }
+
+ alist_add(al, files[i], use_curbuf ? 2 : 1);
+ os_breakcheck();
+ }
+ xfree(files);
+ }
+
+ if (al == &global_alist) {
+ arg_had_last = false;
+ }
+ recursive--;
+}
+
+/// Add file "fname" to argument list "al".
+/// "fname" must have been allocated and "al" must have been checked for room.
+///
+/// @param set_fnum 1: set buffer number; 2: re-use curbuf
+void alist_add(alist_T *al, char *fname, int set_fnum)
+{
+ if (fname == NULL) { // don't add NULL file names
+ return;
+ }
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(fname);
+#endif
+ AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname;
+ if (set_fnum > 0) {
+ AARGLIST(al)[al->al_ga.ga_len].ae_fnum =
+ buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0));
+ }
+ al->al_ga.ga_len++;
+}
+
+#if defined(BACKSLASH_IN_FILENAME)
+
+/// Adjust slashes in file names. Called after 'shellslash' was set.
+void alist_slash_adjust(void)
+{
+ for (int i = 0; i < GARGCOUNT; i++) {
+ if (GARGLIST[i].ae_fname != NULL) {
+ slash_adjust(GARGLIST[i].ae_fname);
+ }
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_alist != &global_alist) {
+ for (int i = 0; i < WARGCOUNT(wp); i++) {
+ if (WARGLIST(wp)[i].ae_fname != NULL) {
+ slash_adjust(WARGLIST(wp)[i].ae_fname);
+ }
+ }
+ }
+ }
+}
+
+#endif
+
+/// Isolate one argument, taking backticks.
+/// Changes the argument in-place, puts a NUL after it. Backticks remain.
+///
+/// @return a pointer to the start of the next argument.
+static char *do_one_arg(char *str)
+{
+ char *p;
+ bool inbacktick;
+
+ inbacktick = false;
+ for (p = str; *str; str++) {
+ // When the backslash is used for escaping the special meaning of a
+ // character we need to keep it until wildcard expansion.
+ if (rem_backslash((char_u *)str)) {
+ *p++ = *str++;
+ *p++ = *str;
+ } else {
+ // An item ends at a space not in backticks
+ if (!inbacktick && ascii_isspace(*str)) {
+ break;
+ }
+ if (*str == '`') {
+ inbacktick ^= true;
+ }
+ *p++ = *str;
+ }
+ }
+ str = skipwhite(str);
+ *p = NUL;
+
+ return str;
+}
+
+/// Separate the arguments in "str" and return a list of pointers in the
+/// growarray "gap".
+static void get_arglist(garray_T *gap, char *str, int escaped)
+{
+ ga_init(gap, (int)sizeof(char_u *), 20);
+ while (*str != NUL) {
+ GA_APPEND(char *, gap, str);
+
+ // If str is escaped, don't handle backslashes or spaces
+ if (!escaped) {
+ return;
+ }
+
+ // Isolate one argument, change it in-place, put a NUL after it.
+ str = do_one_arg(str);
+ }
+}
+
+/// Parse a list of arguments (file names), expand them and return in
+/// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'.
+///
+/// @return FAIL or OK.
+int get_arglist_exp(char_u *str, int *fcountp, char ***fnamesp, bool wig)
+{
+ garray_T ga;
+ int i;
+
+ get_arglist(&ga, (char *)str, true);
+
+ if (wig) {
+ i = expand_wildcards(ga.ga_len, ga.ga_data,
+ fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD);
+ } else {
+ i = gen_expand_wildcards(ga.ga_len, ga.ga_data,
+ fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD);
+ }
+
+ ga_clear(&ga);
+ return i;
+}
+
+/// Check the validity of the arg_idx for each other window.
+static void alist_check_arg_idx(void)
+{
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_alist == curwin->w_alist) {
+ check_arg_idx(win);
+ }
+ }
+}
+
+/// Add files[count] to the arglist of the current window after arg "after".
+/// The file names in files[count] must have been allocated and are taken over.
+/// Files[] itself is not taken over.
+///
+/// @param after: where to add: 0 = before first one
+/// @param will_edit will edit adding argument
+static void alist_add_list(int count, char **files, int after, bool will_edit)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int old_argcount = ARGCOUNT;
+ ga_grow(&ALIST(curwin)->al_ga, count);
+ {
+ if (after < 0) {
+ after = 0;
+ }
+ if (after > ARGCOUNT) {
+ after = ARGCOUNT;
+ }
+ if (after < ARGCOUNT) {
+ memmove(&(ARGLIST[after + count]), &(ARGLIST[after]),
+ (size_t)(ARGCOUNT - after) * sizeof(aentry_T));
+ }
+ for (int i = 0; i < count; i++) {
+ const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0);
+ ARGLIST[after + i].ae_fname = (char_u *)files[i];
+ ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags);
+ }
+ ALIST(curwin)->al_ga.ga_len += count;
+ if (old_argcount > 0 && curwin->w_arg_idx >= after) {
+ curwin->w_arg_idx += count;
+ }
+ return;
+ }
+}
+
+/// @param str
+/// @param what
+/// AL_SET: Redefine the argument list to 'str'.
+/// AL_ADD: add files in 'str' to the argument list after "after".
+/// AL_DEL: remove files in 'str' from the argument list.
+/// @param after
+/// 0 means before first one
+/// @param will_edit will edit added argument
+///
+/// @return FAIL for failure, OK otherwise.
+static int do_arglist(char *str, int what, int after, bool will_edit)
+ FUNC_ATTR_NONNULL_ALL
+{
+ garray_T new_ga;
+ int exp_count;
+ char **exp_files;
+ char *p;
+ int match;
+ int arg_escaped = true;
+
+ // Set default argument for ":argadd" command.
+ if (what == AL_ADD && *str == NUL) {
+ if (curbuf->b_ffname == NULL) {
+ return FAIL;
+ }
+ str = curbuf->b_fname;
+ arg_escaped = false;
+ }
+
+ // Collect all file name arguments in "new_ga".
+ get_arglist(&new_ga, str, arg_escaped);
+
+ if (what == AL_DEL) {
+ regmatch_T regmatch;
+ bool didone;
+
+ // Delete the items: use each item as a regexp and find a match in the
+ // argument list.
+ regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set
+ for (int i = 0; i < new_ga.ga_len && !got_int; i++) {
+ p = ((char **)new_ga.ga_data)[i];
+ p = file_pat_to_reg_pat(p, NULL, NULL, false);
+ if (p == NULL) {
+ break;
+ }
+ regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0);
+ if (regmatch.regprog == NULL) {
+ xfree(p);
+ break;
+ }
+
+ didone = false;
+ for (match = 0; match < ARGCOUNT; match++) {
+ if (vim_regexec(&regmatch, alist_name(&ARGLIST[match]), (colnr_T)0)) {
+ didone = true;
+ xfree(ARGLIST[match].ae_fname);
+ memmove(ARGLIST + match, ARGLIST + match + 1,
+ (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T));
+ ALIST(curwin)->al_ga.ga_len--;
+ if (curwin->w_arg_idx > match) {
+ curwin->w_arg_idx--;
+ }
+ match--;
+ }
+ }
+
+ vim_regfree(regmatch.regprog);
+ xfree(p);
+ if (!didone) {
+ semsg(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]);
+ }
+ }
+ ga_clear(&new_ga);
+ } else {
+ int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data,
+ &exp_count, &exp_files,
+ EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND);
+ ga_clear(&new_ga);
+ if (i == FAIL || exp_count == 0) {
+ emsg(_(e_nomatch));
+ return FAIL;
+ }
+
+ if (what == AL_ADD) {
+ alist_add_list(exp_count, exp_files, after, will_edit);
+ xfree(exp_files);
+ } else {
+ assert(what == AL_SET);
+ alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0);
+ }
+ }
+
+ alist_check_arg_idx();
+
+ return OK;
+}
+
+/// Redefine the argument list.
+void set_arglist(char *str)
+{
+ do_arglist(str, AL_SET, 0, false);
+}
+
+/// @return true if window "win" is editing the file at the current argument
+/// index.
+bool editing_arg_idx(win_T *win)
+{
+ return !(win->w_arg_idx >= WARGCOUNT(win)
+ || (win->w_buffer->b_fnum
+ != WARGLIST(win)[win->w_arg_idx].ae_fnum
+ && (win->w_buffer->b_ffname == NULL
+ || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]),
+ win->w_buffer->b_ffname, true,
+ true) & kEqualFiles))));
+}
+
+/// Check if window "win" is editing the w_arg_idx file in its argument list.
+void check_arg_idx(win_T *win)
+{
+ if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) {
+ // We are not editing the current entry in the argument list.
+ // Set "arg_had_last" if we are editing the last one.
+ win->w_arg_idx_invalid = true;
+ if (win->w_arg_idx != WARGCOUNT(win) - 1
+ && arg_had_last == false
+ && ALIST(win) == &global_alist
+ && GARGCOUNT > 0
+ && win->w_arg_idx < GARGCOUNT
+ && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum
+ || (win->w_buffer->b_ffname != NULL
+ && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]),
+ win->w_buffer->b_ffname, true, true)
+ & kEqualFiles)))) {
+ arg_had_last = true;
+ }
+ } else {
+ // We are editing the current entry in the argument list.
+ // Set "arg_had_last" if it's also the last one
+ win->w_arg_idx_invalid = false;
+ if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) {
+ arg_had_last = true;
+ }
+ }
+}
+
+/// ":args", ":argslocal" and ":argsglobal".
+void ex_args(exarg_T *eap)
+{
+ if (eap->cmdidx != CMD_args) {
+ alist_unlink(ALIST(curwin));
+ if (eap->cmdidx == CMD_argglobal) {
+ ALIST(curwin) = &global_alist;
+ } else { // eap->cmdidx == CMD_arglocal
+ alist_new();
+ }
+ }
+
+ if (*eap->arg != NUL) {
+ // ":args file ..": define new argument list, handle like ":next"
+ // Also for ":argslocal file .." and ":argsglobal file ..".
+ ex_next(eap);
+ } else if (eap->cmdidx == CMD_args) {
+ // ":args": list arguments.
+ if (ARGCOUNT > 0) {
+ char **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT);
+ // Overwrite the command, for a short list there is no scrolling
+ // required and no wait_return().
+ gotocmdline(true);
+ for (int i = 0; i < ARGCOUNT; i++) {
+ items[i] = alist_name(&ARGLIST[i]);
+ }
+ list_in_columns(items, ARGCOUNT, curwin->w_arg_idx);
+ xfree(items);
+ }
+ } else if (eap->cmdidx == CMD_arglocal) {
+ garray_T *gap = &curwin->w_alist->al_ga;
+
+ // ":argslocal": make a local copy of the global argument list.
+ ga_grow(gap, GARGCOUNT);
+ for (int i = 0; i < GARGCOUNT; i++) {
+ if (GARGLIST[i].ae_fname != NULL) {
+ AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname =
+ vim_strsave(GARGLIST[i].ae_fname);
+ AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum =
+ GARGLIST[i].ae_fnum;
+ gap->ga_len++;
+ }
+ }
+ }
+}
+
+/// ":previous", ":sprevious", ":Next" and ":sNext".
+void ex_previous(exarg_T *eap)
+{
+ // If past the last one already, go to the last one.
+ if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) {
+ do_argfile(eap, ARGCOUNT - 1);
+ } else {
+ do_argfile(eap, curwin->w_arg_idx - (int)eap->line2);
+ }
+}
+
+/// ":rewind", ":first", ":sfirst" and ":srewind".
+void ex_rewind(exarg_T *eap)
+{
+ do_argfile(eap, 0);
+}
+
+/// ":last" and ":slast".
+void ex_last(exarg_T *eap)
+{
+ do_argfile(eap, ARGCOUNT - 1);
+}
+
+/// ":argument" and ":sargument".
+void ex_argument(exarg_T *eap)
+{
+ int i;
+
+ if (eap->addr_count > 0) {
+ i = (int)eap->line2 - 1;
+ } else {
+ i = curwin->w_arg_idx;
+ }
+ do_argfile(eap, i);
+}
+
+/// Edit file "argn" of the argument lists.
+void do_argfile(exarg_T *eap, int argn)
+{
+ int other;
+ char *p;
+ int old_arg_idx = curwin->w_arg_idx;
+
+ if (argn < 0 || argn >= ARGCOUNT) {
+ if (ARGCOUNT <= 1) {
+ emsg(_("E163: There is only one file to edit"));
+ } else if (argn < 0) {
+ emsg(_("E164: Cannot go before first file"));
+ } else {
+ emsg(_("E165: Cannot go beyond last file"));
+ }
+ } else {
+ setpcmark();
+
+ // split window or create new tab page first
+ if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) {
+ if (win_split(0, 0) == FAIL) {
+ return;
+ }
+ RESET_BINDING(curwin);
+ } else {
+ // if 'hidden' set, only check for changed file when re-editing
+ // the same buffer
+ other = true;
+ if (buf_hide(curbuf)) {
+ p = fix_fname(alist_name(&ARGLIST[argn]));
+ other = otherfile(p);
+ xfree(p);
+ }
+ if ((!buf_hide(curbuf) || !other)
+ && check_changed(curbuf, CCGD_AW
+ | (other ? 0 : CCGD_MULTWIN)
+ | (eap->forceit ? CCGD_FORCEIT : 0)
+ | CCGD_EXCMD)) {
+ return;
+ }
+ }
+
+ curwin->w_arg_idx = argn;
+ if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) {
+ arg_had_last = true;
+ }
+
+ // Edit the file; always use the last known line number.
+ // When it fails (e.g. Abort for already edited file) restore the
+ // argument index.
+ if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL,
+ eap, ECMD_LAST,
+ (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0)
+ + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) {
+ curwin->w_arg_idx = old_arg_idx;
+ } else if (eap->cmdidx != CMD_argdo) {
+ // like Vi: set the mark where the cursor is in the file.
+ setmark('\'');
+ }
+ }
+}
+
+/// ":next", and commands that behave like it.
+void ex_next(exarg_T *eap)
+{
+ int i;
+
+ // check for changed buffer now, if this fails the argument list is not
+ // redefined.
+ if (buf_hide(curbuf)
+ || eap->cmdidx == CMD_snext
+ || !check_changed(curbuf, CCGD_AW
+ | (eap->forceit ? CCGD_FORCEIT : 0)
+ | CCGD_EXCMD)) {
+ if (*eap->arg != NUL) { // redefine file list
+ if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) {
+ return;
+ }
+ i = 0;
+ } else {
+ i = curwin->w_arg_idx + (int)eap->line2;
+ }
+ do_argfile(eap, i);
+ }
+}
+
+/// ":argdedupe"
+void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED)
+{
+ for (int i = 0; i < ARGCOUNT; i++) {
+ for (int j = i + 1; j < ARGCOUNT; j++) {
+ if (FNAMECMP(ARGLIST[i].ae_fname, ARGLIST[j].ae_fname) == 0) {
+ xfree(ARGLIST[j].ae_fname);
+ memmove(ARGLIST + j, ARGLIST + j + 1,
+ (size_t)(ARGCOUNT - j - 1) * sizeof(aentry_T));
+ ARGCOUNT--;
+
+ if (curwin->w_arg_idx == j) {
+ curwin->w_arg_idx = i;
+ } else if (curwin->w_arg_idx > j) {
+ curwin->w_arg_idx--;
+ }
+
+ j--;
+ }
+ }
+ }
+}
+
+/// ":argedit"
+void ex_argedit(exarg_T *eap)
+{
+ int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1;
+ // Whether curbuf will be reused, curbuf->b_ffname will be set.
+ bool curbuf_is_reusable = curbuf_reusable();
+
+ if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) {
+ return;
+ }
+ maketitle();
+
+ if (curwin->w_arg_idx == 0
+ && (curbuf->b_ml.ml_flags & ML_EMPTY)
+ && (curbuf->b_ffname == NULL || curbuf_is_reusable)) {
+ i = 0;
+ }
+ // Edit the argument.
+ if (i < ARGCOUNT) {
+ do_argfile(eap, i);
+ }
+}
+
+/// ":argadd"
+void ex_argadd(exarg_T *eap)
+{
+ do_arglist(eap->arg, AL_ADD,
+ eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1,
+ false);
+ maketitle();
+}
+
+/// ":argdelete"
+void ex_argdelete(exarg_T *eap)
+{
+ if (eap->addr_count > 0 || *eap->arg == NUL) {
+ // ":argdel" works like ":.argdel"
+ if (eap->addr_count == 0) {
+ if (curwin->w_arg_idx >= ARGCOUNT) {
+ emsg(_("E610: No argument to delete"));
+ return;
+ }
+ eap->line1 = eap->line2 = curwin->w_arg_idx + 1;
+ } else if (eap->line2 > ARGCOUNT) {
+ // ":1,4argdel": Delete all arguments in the range.
+ eap->line2 = ARGCOUNT;
+ }
+ linenr_T n = eap->line2 - eap->line1 + 1;
+ if (*eap->arg != NUL) {
+ // Can't have both a range and an argument.
+ emsg(_(e_invarg));
+ } else if (n <= 0) {
+ // Don't give an error for ":%argdel" if the list is empty.
+ if (eap->line1 != 1 || eap->line2 != 0) {
+ emsg(_(e_invrange));
+ }
+ } else {
+ for (linenr_T i = eap->line1; i <= eap->line2; i++) {
+ xfree(ARGLIST[i - 1].ae_fname);
+ }
+ memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2,
+ (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T));
+ ALIST(curwin)->al_ga.ga_len -= (int)n;
+ if (curwin->w_arg_idx >= eap->line2) {
+ curwin->w_arg_idx -= (int)n;
+ } else if (curwin->w_arg_idx > eap->line1) {
+ curwin->w_arg_idx = (int)eap->line1;
+ }
+ if (ARGCOUNT == 0) {
+ curwin->w_arg_idx = 0;
+ } else if (curwin->w_arg_idx >= ARGCOUNT) {
+ curwin->w_arg_idx = ARGCOUNT - 1;
+ }
+ }
+ } else {
+ do_arglist(eap->arg, AL_DEL, 0, false);
+ }
+ maketitle();
+}
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// argedit and argdelete commands.
+char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= ARGCOUNT) {
+ return NULL;
+ }
+ return alist_name(&ARGLIST[idx]);
+}
+
+/// Get the file name for an argument list entry.
+char *alist_name(aentry_T *aep)
+{
+ buf_T *bp;
+
+ // Use the name from the associated buffer if it exists.
+ bp = buflist_findnr(aep->ae_fnum);
+ if (bp == NULL || bp->b_fname == NULL) {
+ return (char *)aep->ae_fname;
+ }
+ return bp->b_fname;
+}
+
+/// do_arg_all(): Open up to 'count' windows, one for each argument.
+///
+/// @param forceit hide buffers in current windows
+/// @param keep_tabs keep current tabs, for ":tab drop file"
+static void do_arg_all(int count, int forceit, int keep_tabs)
+{
+ uint8_t *opened; // Array of weight for which args are open:
+ // 0: not opened
+ // 1: opened in other tab
+ // 2: opened in curtab
+ // 3: opened in curtab and curwin
+
+ int opened_len; // length of opened[]
+ int use_firstwin = false; // use first window for arglist
+ bool tab_drop_empty_window = false;
+ int split_ret = OK;
+ bool p_ea_save;
+ alist_T *alist; // argument list to be used
+ buf_T *buf;
+ tabpage_T *tpnext;
+ int had_tab = cmdmod.cmod_tab;
+ win_T *old_curwin, *last_curwin;
+ tabpage_T *old_curtab, *last_curtab;
+ win_T *new_curwin = NULL;
+ tabpage_T *new_curtab = NULL;
+
+ assert(firstwin != NULL); // satisfy coverity
+
+ if (ARGCOUNT <= 0) {
+ // Don't give an error message. We don't want it when the ":all" command is in the .vimrc.
+ return;
+ }
+ setpcmark();
+
+ opened_len = ARGCOUNT;
+ opened = xcalloc((size_t)opened_len, 1);
+
+ // Autocommands may do anything to the argument list. Make sure it's not
+ // freed while we are working here by "locking" it. We still have to
+ // watch out for its size to be changed.
+ alist = curwin->w_alist;
+ alist->al_refcount++;
+
+ old_curwin = curwin;
+ old_curtab = curtab;
+
+ // Try closing all windows that are not in the argument list.
+ // Also close windows that are not full width;
+ // When 'hidden' or "forceit" set the buffer becomes hidden.
+ // Windows that have a changed buffer and can't be hidden won't be closed.
+ // When the ":tab" modifier was used do this for all tab pages.
+ if (had_tab > 0) {
+ goto_tabpage_tp(first_tabpage, true, true);
+ }
+ for (;;) {
+ win_T *wpnext = NULL;
+ tpnext = curtab->tp_next;
+ for (win_T *wp = firstwin; wp != NULL; wp = wpnext) {
+ int i;
+ wpnext = wp->w_next;
+ buf = wp->w_buffer;
+ if (buf->b_ffname == NULL
+ || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) {
+ i = opened_len;
+ } else {
+ // check if the buffer in this window is in the arglist
+ for (i = 0; i < opened_len; i++) {
+ if (i < alist->al_ga.ga_len
+ && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum
+ || path_full_compare(alist_name(&AARGLIST(alist)[i]),
+ buf->b_ffname,
+ true, true) & kEqualFiles)) {
+ int weight = 1;
+
+ if (old_curtab == curtab) {
+ weight++;
+ if (old_curwin == wp) {
+ weight++;
+ }
+ }
+
+ if (weight > (int)opened[i]) {
+ opened[i] = (uint8_t)weight;
+ if (i == 0) {
+ if (new_curwin != NULL) {
+ new_curwin->w_arg_idx = opened_len;
+ }
+ new_curwin = wp;
+ new_curtab = curtab;
+ }
+ } else if (keep_tabs) {
+ i = opened_len;
+ }
+
+ if (wp->w_alist != alist) {
+ // Use the current argument list for all windows containing a file from it.
+ alist_unlink(wp->w_alist);
+ wp->w_alist = alist;
+ wp->w_alist->al_refcount++;
+ }
+ break;
+ }
+ }
+ }
+ wp->w_arg_idx = i;
+
+ if (i == opened_len && !keep_tabs) { // close this window
+ if (buf_hide(buf) || forceit || buf->b_nwindows > 1
+ || !bufIsChanged(buf)) {
+ // If the buffer was changed, and we would like to hide it, try autowriting.
+ if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) {
+ bufref_T bufref;
+ set_bufref(&bufref, buf);
+ (void)autowrite(buf, false);
+ // Check if autocommands removed the window.
+ if (!win_valid(wp) || !bufref_valid(&bufref)) {
+ wpnext = firstwin; // Start all over...
+ continue;
+ }
+ }
+ // don't close last window
+ if (ONE_WINDOW
+ && (first_tabpage->tp_next == NULL || !had_tab)) {
+ use_firstwin = true;
+ } else {
+ win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false);
+ // check if autocommands removed the next window
+ if (!win_valid(wpnext)) {
+ // start all over...
+ wpnext = firstwin;
+ }
+ }
+ }
+ }
+ }
+
+ // Without the ":tab" modifier only do the current tab page.
+ if (had_tab == 0 || tpnext == NULL) {
+ break;
+ }
+
+ // check if autocommands removed the next tab page
+ if (!valid_tabpage(tpnext)) {
+ tpnext = first_tabpage; // start all over...
+ }
+ goto_tabpage_tp(tpnext, true, true);
+ }
+
+ // Open a window for files in the argument list that don't have one.
+ // ARGCOUNT may change while doing this, because of autocommands.
+ if (count > opened_len || count <= 0) {
+ count = opened_len;
+ }
+
+ // Don't execute Win/Buf Enter/Leave autocommands here.
+ autocmd_no_enter++;
+ autocmd_no_leave++;
+ last_curwin = curwin;
+ last_curtab = curtab;
+ win_enter(lastwin, false);
+ // ":tab drop file" should re-use an empty window to avoid "--remote-tab"
+ // leaving an empty tab page when executed locally.
+ if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1
+ && curbuf->b_ffname == NULL && !curbuf->b_changed) {
+ use_firstwin = true;
+ tab_drop_empty_window = true;
+ }
+
+ for (int i = 0; i < count && !got_int; i++) {
+ if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) {
+ arg_had_last = true;
+ }
+ if (opened[i] > 0) {
+ // Move the already present window to below the current window
+ if (curwin->w_arg_idx != i) {
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_arg_idx == i) {
+ if (keep_tabs) {
+ new_curwin = wp;
+ new_curtab = curtab;
+ } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) {
+ emsg(_("E249: window layout changed unexpectedly"));
+ i = count;
+ break;
+ } else {
+ win_move_after(wp, curwin);
+ }
+ break;
+ }
+ }
+ }
+ } else if (split_ret == OK) {
+ // trigger events for tab drop
+ if (tab_drop_empty_window && i == count - 1) {
+ autocmd_no_enter--;
+ }
+ if (!use_firstwin) { // split current window
+ p_ea_save = p_ea;
+ p_ea = true; // use space from all windows
+ split_ret = win_split(0, WSP_ROOM | WSP_BELOW);
+ p_ea = p_ea_save;
+ if (split_ret == FAIL) {
+ continue;
+ }
+ } else { // first window: do autocmd for leaving this buffer
+ autocmd_no_leave--;
+ }
+
+ // edit file "i"
+ curwin->w_arg_idx = i;
+ if (i == 0) {
+ new_curwin = curwin;
+ new_curtab = curtab;
+ }
+ (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE,
+ ((buf_hide(curwin->w_buffer)
+ || bufIsChanged(curwin->w_buffer))
+ ? ECMD_HIDE : 0) + ECMD_OLDBUF,
+ curwin);
+ if (tab_drop_empty_window && i == count - 1) {
+ autocmd_no_enter++;
+ }
+ if (use_firstwin) {
+ autocmd_no_leave++;
+ }
+ use_firstwin = false;
+ }
+ os_breakcheck();
+
+ // When ":tab" was used open a new tab for a new window repeatedly.
+ if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) {
+ cmdmod.cmod_tab = 9999;
+ }
+ }
+
+ // Remove the "lock" on the argument list.
+ alist_unlink(alist);
+
+ autocmd_no_enter--;
+ // restore last referenced tabpage's curwin
+ if (last_curtab != new_curtab) {
+ if (valid_tabpage(last_curtab)) {
+ goto_tabpage_tp(last_curtab, true, true);
+ }
+ if (win_valid(last_curwin)) {
+ win_enter(last_curwin, false);
+ }
+ }
+ // to window with first arg
+ if (valid_tabpage(new_curtab)) {
+ goto_tabpage_tp(new_curtab, true, true);
+ }
+ if (win_valid(new_curwin)) {
+ win_enter(new_curwin, false);
+ }
+
+ autocmd_no_leave--;
+ xfree(opened);
+}
+
+/// ":all" and ":sall".
+/// Also used for ":tab drop file ..." after setting the argument list.
+void ex_all(exarg_T *eap)
+{
+ if (eap->addr_count == 0) {
+ eap->line2 = 9999;
+ }
+ do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop);
+}
+
+/// Concatenate all files in the argument list, separated by spaces, and return
+/// it in one allocated string.
+/// Spaces and backslashes in the file names are escaped with a backslash.
+char *arg_all(void)
+{
+ char *retval = NULL;
+
+ // Do this loop two times:
+ // first time: compute the total length
+ // second time: concatenate the names
+ for (;;) {
+ int len = 0;
+ for (int idx = 0; idx < ARGCOUNT; idx++) {
+ char *p = alist_name(&ARGLIST[idx]);
+ if (p == NULL) {
+ continue;
+ }
+ if (len > 0) {
+ // insert a space in between names
+ if (retval != NULL) {
+ retval[len] = ' ';
+ }
+ len++;
+ }
+ for (; *p != NUL; p++) {
+ if (*p == ' '
+#ifndef BACKSLASH_IN_FILENAME
+ || *p == '\\'
+#endif
+ || *p == '`') {
+ // insert a backslash
+ if (retval != NULL) {
+ retval[len] = '\\';
+ }
+ len++;
+ }
+ if (retval != NULL) {
+ retval[len] = *p;
+ }
+ len++;
+ }
+ }
+
+ // second time: break here
+ if (retval != NULL) {
+ retval[len] = NUL;
+ break;
+ }
+
+ // allocate memory
+ retval = xmalloc((size_t)len + 1);
+ }
+
+ return retval;
+}
+
+/// "argc([window id])" function
+void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // use the current window
+ rettv->vval.v_number = ARGCOUNT;
+ } else if (argvars[0].v_type == VAR_NUMBER
+ && tv_get_number(&argvars[0]) == -1) {
+ // use the global argument list
+ rettv->vval.v_number = GARGCOUNT;
+ } else {
+ // use the argument list of the specified window
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp != NULL) {
+ rettv->vval.v_number = WARGCOUNT(wp);
+ } else {
+ rettv->vval.v_number = -1;
+ }
+ }
+}
+
+/// "argidx()" function
+void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = curwin->w_arg_idx;
+}
+
+/// "arglistid()" function
+void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+ win_T *wp = find_tabwin(&argvars[0], &argvars[1]);
+ if (wp != NULL) {
+ rettv->vval.v_number = wp->w_alist->id;
+ }
+}
+
+/// Get the argument list for a given window
+static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv)
+{
+ tv_list_alloc_ret(rettv, argcount);
+ if (arglist != NULL) {
+ for (int idx = 0; idx < argcount; idx++) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)alist_name(&arglist[idx]), -1);
+ }
+ }
+}
+
+/// "argv(nr)" function
+void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ aentry_T *arglist = NULL;
+ int argcount = -1;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ arglist = ARGLIST;
+ argcount = ARGCOUNT;
+ } else if (argvars[1].v_type == VAR_NUMBER
+ && tv_get_number(&argvars[1]) == -1) {
+ arglist = GARGLIST;
+ argcount = GARGCOUNT;
+ } else {
+ win_T *wp = find_win_by_nr_or_id(&argvars[1]);
+ if (wp != NULL) {
+ // Use the argument list of the specified window
+ arglist = WARGLIST(wp);
+ argcount = WARGCOUNT(wp);
+ }
+ }
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ int idx = (int)tv_get_number_chk(&argvars[0], NULL);
+ if (arglist != NULL && idx >= 0 && idx < argcount) {
+ rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx]));
+ } else if (idx == -1) {
+ get_arglist_as_rettv(arglist, argcount, rettv);
+ }
+ } else {
+ get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
+ }
+}
diff --git a/src/nvim/arglist.h b/src/nvim/arglist.h
new file mode 100644
index 0000000000..b2e0f411d4
--- /dev/null
+++ b/src/nvim/arglist.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_ARGLIST_H
+#define NVIM_ARGLIST_H
+
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "arglist.h.generated.h"
+#endif
+
+#endif // NVIM_ARGLIST_H
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index bbb044fba3..b5b2a73be1 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -11,20 +11,25 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/grid.h"
#include "nvim/insexpand.h"
#include "nvim/lua/executor.h"
#include "nvim/map.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/ui_compositor.h"
@@ -1141,7 +1146,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
ac->id = id;
ac->exec = aucmd_exec_copy(aucmd);
ac->script_ctx = current_sctx;
- ac->script_ctx.sc_lnum += sourcing_lnum;
+ ac->script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&ac->script_ctx);
ac->next = NULL;
ac->once = once;
@@ -1769,10 +1774,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Don't redraw while doing autocommands.
RedrawingDisabled++;
- char *save_sourcing_name = sourcing_name;
- sourcing_name = NULL; // don't free this one
- linenr_T save_sourcing_lnum = sourcing_lnum;
- sourcing_lnum = 0; // no line number here
+
+ // name and lnum are filled in later
+ estack_push(ETYPE_AUCMD, NULL, 0);
const sctx_T save_current_sctx = current_sctx;
@@ -1876,9 +1880,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
autocmd_busy = save_autocmd_busy;
filechangeshell_busy = false;
autocmd_nested = save_autocmd_nested;
- xfree(sourcing_name);
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
+ xfree(SOURCING_NAME);
+ estack_pop();
xfree(autocmd_fname);
autocmd_fname = save_autocmd_fname;
autocmd_bufnr = save_autocmd_bufnr;
@@ -1981,8 +1984,9 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
AutoPat *ap;
AutoCmd *cp;
char *s;
+ char **const sourcing_namep = &SOURCING_NAME;
- XFREE_CLEAR(sourcing_name);
+ XFREE_CLEAR(*sourcing_namep);
for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
apc->curpat = NULL;
@@ -2007,11 +2011,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
const size_t sourcing_name_len
= (STRLEN(s) + strlen(name) + (size_t)ap->patlen + 1);
- sourcing_name = xmalloc(sourcing_name_len);
- snprintf(sourcing_name, sourcing_name_len, s, name, ap->pat);
+ *sourcing_namep = xmalloc(sourcing_name_len);
+ snprintf(*sourcing_namep, sourcing_name_len, s, name, ap->pat);
if (p_verbose >= 8) {
verbose_enter();
- smsg(_("Executing %s"), sourcing_name);
+ smsg(_("Executing %s"), *sourcing_namep);
verbose_leave();
}
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index a085a03455..d559d8c3d2 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -22,7 +22,8 @@ typedef struct {
bool save_VIsual_active; ///< saved VIsual_active
} aco_save_T;
-typedef struct AutoCmd {
+typedef struct AutoCmd_S AutoCmd;
+struct AutoCmd_S {
AucmdExecutable exec;
bool once; // "One shot": removed after execution
bool nested; // If autocommands nest here
@@ -30,11 +31,12 @@ typedef struct AutoCmd {
int64_t id; // ID used for uniquely tracking an autocmd.
sctx_T script_ctx; // script context where defined
char *desc; // Description for the autocmd.
- struct AutoCmd *next; // Next AutoCmd in list
-} AutoCmd;
+ AutoCmd *next; // Next AutoCmd in list
+};
-typedef struct AutoPat {
- struct AutoPat *next; // next AutoPat in AutoPat list; MUST
+typedef struct AutoPat_S AutoPat;
+struct AutoPat_S {
+ AutoPat *next; // next AutoPat in AutoPat list; MUST
// be the first entry
char *pat; // pattern as typed (NULL when pattern
// has been removed)
@@ -45,10 +47,11 @@ typedef struct AutoPat {
int buflocal_nr; // !=0 for buffer-local AutoPat
char allow_dirs; // Pattern may match whole path
char last; // last pattern for apply_autocmds()
-} AutoPat;
+};
/// Struct used to keep status while executing autocommands for an event.
-typedef struct AutoPatCmd {
+typedef struct AutoPatCmd_S AutoPatCmd;
+struct AutoPatCmd_S {
AutoPat *curpat; // next AutoPat to examine
AutoCmd *nextcmd; // next AutoCmd to execute
int group; // group being used
@@ -58,8 +61,8 @@ typedef struct AutoPatCmd {
event_T event; // current event
int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted
Object *data; // arbitrary data
- struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation
-} AutoPatCmd;
+ AutoPatCmd *next; // chain of active apc-s for auto-invalidation
+};
// Set by the apply_autocmds_group function if the given event is equal to
// EVENT_FILETYPE. Used by the readfile function in order to determine if
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index f23a1caf8b..f9bce2476f 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -25,8 +25,10 @@
#include <string.h>
#include "nvim/api/private/helpers.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
@@ -36,6 +38,7 @@
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
@@ -50,6 +53,7 @@
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/hashtab.h"
+#include "nvim/help.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
@@ -69,7 +73,7 @@
#include "nvim/plines.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
#include "nvim/strings.h"
@@ -793,8 +797,8 @@ static void free_buffer(buf_T *buf)
if (autocmd_busy) {
// Do not free the buffer structure while autocommands are executing,
// it's still needed. Free it when autocmd_busy is reset.
- memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm));
- memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist));
+ CLEAR_FIELD(buf->b_namedm);
+ CLEAR_FIELD(buf->b_changelist);
buf->b_next = au_pending_free_buf;
au_pending_free_buf = buf;
} else {
@@ -1532,6 +1536,15 @@ void set_curbuf(buf_T *buf, int action)
/// be pointing to freed memory.
void enter_buffer(buf_T *buf)
{
+ // when closing the current buffer stop Visual mode
+ if (VIsual_active
+#if defined(EXITFREE)
+ && !entered_free_all_mem
+#endif
+ ) {
+ end_visual_mode();
+ }
+
// Get the buffer in the current window.
curwin->w_buffer = buf;
curbuf = buf;
@@ -2595,7 +2608,7 @@ void get_winopts(buf_T *buf)
}
if (curwin->w_float_config.style == kWinStyleMinimal) {
- didset_window_options(curwin);
+ didset_window_options(curwin, false);
win_set_minimal_style(curwin);
}
@@ -2603,7 +2616,7 @@ void get_winopts(buf_T *buf)
if (p_fdls >= 0) {
curwin->w_p_fdl = p_fdls;
}
- didset_window_options(curwin);
+ didset_window_options(curwin, false);
}
/// Find the mark for the buffer 'buf' for the current window.
@@ -4628,279 +4641,6 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname)
#endif
}
-/// Get the file name for an argument list entry.
-char *alist_name(aentry_T *aep)
-{
- buf_T *bp;
-
- // Use the name from the associated buffer if it exists.
- bp = buflist_findnr(aep->ae_fnum);
- if (bp == NULL || bp->b_fname == NULL) {
- return (char *)aep->ae_fname;
- }
- return bp->b_fname;
-}
-
-/// do_arg_all(): Open up to 'count' windows, one for each argument.
-///
-/// @param forceit hide buffers in current windows
-/// @param keep_tabs keep current tabs, for ":tab drop file"
-void do_arg_all(int count, int forceit, int keep_tabs)
-{
- uint8_t *opened; // Array of weight for which args are open:
- // 0: not opened
- // 1: opened in other tab
- // 2: opened in curtab
- // 3: opened in curtab and curwin
-
- int opened_len; // length of opened[]
- int use_firstwin = false; // use first window for arglist
- bool tab_drop_empty_window = false;
- int split_ret = OK;
- bool p_ea_save;
- alist_T *alist; // argument list to be used
- buf_T *buf;
- tabpage_T *tpnext;
- int had_tab = cmdmod.cmod_tab;
- win_T *old_curwin, *last_curwin;
- tabpage_T *old_curtab, *last_curtab;
- win_T *new_curwin = NULL;
- tabpage_T *new_curtab = NULL;
-
- assert(firstwin != NULL); // satisfy coverity
-
- if (ARGCOUNT <= 0) {
- // Don't give an error message. We don't want it when the ":all" command is in the .vimrc.
- return;
- }
- setpcmark();
-
- opened_len = ARGCOUNT;
- opened = xcalloc((size_t)opened_len, 1);
-
- // Autocommands may do anything to the argument list. Make sure it's not
- // freed while we are working here by "locking" it. We still have to
- // watch out for its size to be changed.
- alist = curwin->w_alist;
- alist->al_refcount++;
-
- old_curwin = curwin;
- old_curtab = curtab;
-
- // Try closing all windows that are not in the argument list.
- // Also close windows that are not full width;
- // When 'hidden' or "forceit" set the buffer becomes hidden.
- // Windows that have a changed buffer and can't be hidden won't be closed.
- // When the ":tab" modifier was used do this for all tab pages.
- if (had_tab > 0) {
- goto_tabpage_tp(first_tabpage, true, true);
- }
- for (;;) {
- win_T *wpnext = NULL;
- tpnext = curtab->tp_next;
- for (win_T *wp = firstwin; wp != NULL; wp = wpnext) {
- int i;
- wpnext = wp->w_next;
- buf = wp->w_buffer;
- if (buf->b_ffname == NULL
- || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) {
- i = opened_len;
- } else {
- // check if the buffer in this window is in the arglist
- for (i = 0; i < opened_len; i++) {
- if (i < alist->al_ga.ga_len
- && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum
- || path_full_compare(alist_name(&AARGLIST(alist)[i]),
- buf->b_ffname,
- true, true) & kEqualFiles)) {
- int weight = 1;
-
- if (old_curtab == curtab) {
- weight++;
- if (old_curwin == wp) {
- weight++;
- }
- }
-
- if (weight > (int)opened[i]) {
- opened[i] = (uint8_t)weight;
- if (i == 0) {
- if (new_curwin != NULL) {
- new_curwin->w_arg_idx = opened_len;
- }
- new_curwin = wp;
- new_curtab = curtab;
- }
- } else if (keep_tabs) {
- i = opened_len;
- }
-
- if (wp->w_alist != alist) {
- // Use the current argument list for all windows containing a file from it.
- alist_unlink(wp->w_alist);
- wp->w_alist = alist;
- wp->w_alist->al_refcount++;
- }
- break;
- }
- }
- }
- wp->w_arg_idx = i;
-
- if (i == opened_len && !keep_tabs) { // close this window
- if (buf_hide(buf) || forceit || buf->b_nwindows > 1
- || !bufIsChanged(buf)) {
- // If the buffer was changed, and we would like to hide it, try autowriting.
- if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) {
- bufref_T bufref;
- set_bufref(&bufref, buf);
- (void)autowrite(buf, false);
- // Check if autocommands removed the window.
- if (!win_valid(wp) || !bufref_valid(&bufref)) {
- wpnext = firstwin; // Start all over...
- continue;
- }
- }
- // don't close last window
- if (ONE_WINDOW
- && (first_tabpage->tp_next == NULL || !had_tab)) {
- use_firstwin = true;
- } else {
- win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false);
- // check if autocommands removed the next window
- if (!win_valid(wpnext)) {
- // start all over...
- wpnext = firstwin;
- }
- }
- }
- }
- }
-
- // Without the ":tab" modifier only do the current tab page.
- if (had_tab == 0 || tpnext == NULL) {
- break;
- }
-
- // check if autocommands removed the next tab page
- if (!valid_tabpage(tpnext)) {
- tpnext = first_tabpage; // start all over...
- }
- goto_tabpage_tp(tpnext, true, true);
- }
-
- // Open a window for files in the argument list that don't have one.
- // ARGCOUNT may change while doing this, because of autocommands.
- if (count > opened_len || count <= 0) {
- count = opened_len;
- }
-
- // Don't execute Win/Buf Enter/Leave autocommands here.
- autocmd_no_enter++;
- autocmd_no_leave++;
- last_curwin = curwin;
- last_curtab = curtab;
- win_enter(lastwin, false);
- // ":tab drop file" should re-use an empty window to avoid "--remote-tab"
- // leaving an empty tab page when executed locally.
- if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1
- && curbuf->b_ffname == NULL && !curbuf->b_changed) {
- use_firstwin = true;
- tab_drop_empty_window = true;
- }
-
- for (int i = 0; i < count && !got_int; i++) {
- if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) {
- arg_had_last = true;
- }
- if (opened[i] > 0) {
- // Move the already present window to below the current window
- if (curwin->w_arg_idx != i) {
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_arg_idx == i) {
- if (keep_tabs) {
- new_curwin = wp;
- new_curtab = curtab;
- } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) {
- emsg(_("E249: window layout changed unexpectedly"));
- i = count;
- break;
- } else {
- win_move_after(wp, curwin);
- }
- break;
- }
- }
- }
- } else if (split_ret == OK) {
- // trigger events for tab drop
- if (tab_drop_empty_window && i == count - 1) {
- autocmd_no_enter--;
- }
- if (!use_firstwin) { // split current window
- p_ea_save = p_ea;
- p_ea = true; // use space from all windows
- split_ret = win_split(0, WSP_ROOM | WSP_BELOW);
- p_ea = p_ea_save;
- if (split_ret == FAIL) {
- continue;
- }
- } else { // first window: do autocmd for leaving this buffer
- autocmd_no_leave--;
- }
-
- // edit file "i"
- curwin->w_arg_idx = i;
- if (i == 0) {
- new_curwin = curwin;
- new_curtab = curtab;
- }
- (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE,
- ((buf_hide(curwin->w_buffer)
- || bufIsChanged(curwin->w_buffer))
- ? ECMD_HIDE : 0) + ECMD_OLDBUF,
- curwin);
- if (tab_drop_empty_window && i == count - 1) {
- autocmd_no_enter++;
- }
- if (use_firstwin) {
- autocmd_no_leave++;
- }
- use_firstwin = false;
- }
- os_breakcheck();
-
- // When ":tab" was used open a new tab for a new window repeatedly.
- if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) {
- cmdmod.cmod_tab = 9999;
- }
- }
-
- // Remove the "lock" on the argument list.
- alist_unlink(alist);
-
- autocmd_no_enter--;
- // restore last referenced tabpage's curwin
- if (last_curtab != new_curtab) {
- if (valid_tabpage(last_curtab)) {
- goto_tabpage_tp(last_curtab, true, true);
- }
- if (win_valid(last_curwin)) {
- win_enter(last_curwin, false);
- }
- }
- // to window with first arg
- if (valid_tabpage(new_curtab)) {
- goto_tabpage_tp(new_curtab, true, true);
- }
- if (win_valid(new_curwin)) {
- win_enter(new_curwin, false);
- }
-
- autocmd_no_leave--;
- xfree(opened);
-}
-
/// @return true if "buf" is a prompt buffer.
bool bt_prompt(buf_T *buf)
FUNC_ATTR_PURE
@@ -5139,8 +4879,6 @@ static int chk_modeline(linenr_T lnum, int flags)
intmax_t vers;
int end;
int retval = OK;
- char *save_sourcing_name;
- linenr_T save_sourcing_lnum;
prev = -1;
for (s = (char *)ml_get(lnum); *s != NUL; s++) {
@@ -5185,10 +4923,8 @@ static int chk_modeline(linenr_T lnum, int flags)
s = linecopy = xstrdup(s); // copy the line, it will change
- save_sourcing_lnum = sourcing_lnum;
- save_sourcing_name = sourcing_name;
- sourcing_lnum = lnum; // prepare for emsg()
- sourcing_name = "modelines";
+ // prepare for emsg()
+ estack_push(ETYPE_MODELINE, "modelines", lnum);
end = false;
while (end == false) {
@@ -5228,7 +4964,7 @@ static int chk_modeline(linenr_T lnum, int flags)
const sctx_T save_current_sctx = current_sctx;
current_sctx.sc_sid = SID_MODELINE;
current_sctx.sc_seq = 0;
- current_sctx.sc_lnum = 0;
+ current_sctx.sc_lnum = lnum;
// Make sure no risky things are executed as a side effect.
secure = 1;
@@ -5243,9 +4979,7 @@ static int chk_modeline(linenr_T lnum, int flags)
s = e + 1; // advance to next part
}
- sourcing_lnum = save_sourcing_lnum;
- sourcing_name = save_sourcing_name;
-
+ estack_pop();
xfree(linecopy);
return retval;
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index b452eb227e..7627b6a596 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -1,12 +1,13 @@
#ifndef NVIM_BUFFER_H
#define NVIM_BUFFER_H
-#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/func_attr.h"
+#include "nvim/grid_defs.h" // for StlClickRecord
#include "nvim/macros.h"
#include "nvim/memline.h"
#include "nvim/pos.h" // for linenr_T
-#include "nvim/screen.h" // for StlClickRecord
// Values for buflist_getfile()
enum getf_values {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index b7f66e6dba..769788a63e 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -30,14 +30,12 @@ typedef struct {
#include "nvim/option_defs.h"
// for jump list and tag stack sizes in a buffer and mark types
#include "nvim/mark_defs.h"
-// for u_header_T; needs buf_T.
+// for u_header_T
#include "nvim/undo_defs.h"
// for hashtab_T
#include "nvim/hashtab.h"
// for dict_T
#include "nvim/eval/typval.h"
-// for proftime_T
-#include "nvim/profile.h"
// for String
#include "nvim/api/private/defs.h"
// for Map(K, V)
@@ -433,7 +431,7 @@ typedef struct {
typedef struct {
hashtab_T b_keywtab; // syntax keywords hash table
hashtab_T b_keywtab_ic; // idem, ignore case
- int b_syn_error; // TRUE when error occurred in HL
+ bool b_syn_error; // true when error occurred in HL
bool b_syn_slow; // true when 'redrawtime' reached
int b_syn_ic; // ignore case for :syn cmds
int b_syn_foldlevel; // how to compute foldlevel on a line
@@ -1150,43 +1148,6 @@ typedef struct {
pos_T w_cursor_corr; // corrected cursor position
} pos_save_T;
-/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode
-/// \addtogroup MENU_INDEX
-/// @{
-enum {
- MENU_INDEX_INVALID = -1,
- MENU_INDEX_NORMAL = 0,
- MENU_INDEX_VISUAL = 1,
- MENU_INDEX_SELECT = 2,
- MENU_INDEX_OP_PENDING = 3,
- MENU_INDEX_INSERT = 4,
- MENU_INDEX_CMDLINE = 5,
- MENU_INDEX_TERMINAL = 6,
- MENU_INDEX_TIP = 7,
- MENU_MODES = 8,
-};
-
-typedef struct VimMenu vimmenu_T;
-
-struct VimMenu {
- int modes; ///< Which modes is this menu visible for
- int enabled; ///< for which modes the menu is enabled
- char *name; ///< Name of menu, possibly translated
- char *dname; ///< Displayed Name ("name" without '&')
- char *en_name; ///< "name" untranslated, NULL when
- ///< was not translated
- char *en_dname; ///< NULL when "dname" untranslated
- int mnemonic; ///< mnemonic key (after '&')
- char *actext; ///< accelerator text (after TAB)
- long priority; ///< Menu order priority
- char *strings[MENU_MODES]; ///< Mapped string for each mode
- int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
- bool silent[MENU_MODES]; ///< A silent flag for each mode
- vimmenu_T *children; ///< Children of sub-menu
- vimmenu_T *parent; ///< Parent of menu
- vimmenu_T *next; ///< Next item in menu
-};
-
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1198,11 +1159,14 @@ struct window_S {
synblock_T *w_s; ///< for :ownsyntax
+ int w_ns_hl;
+ int w_ns_hl_winhl;
+ int w_ns_hl_active;
+ int *w_ns_hl_attr;
+
int w_hl_id_normal; ///< 'winhighlight' normal id
int w_hl_attr_normal; ///< 'winhighlight' normal final attrs
-
- int w_hl_ids[HLF_COUNT]; ///< 'winhighlight' id
- int w_hl_attrs[HLF_COUNT]; ///< 'winhighlight' final attrs
+ int w_hl_attr_normalnc; ///< 'winhighlight' NormalNC final attrs
int w_hl_needs_update; ///< attrs need to be recalculated
@@ -1525,11 +1489,6 @@ struct window_S {
size_t w_winbar_click_defs_size;
};
-static inline int win_hl_attr(win_T *wp, int hlf)
-{
- return wp->w_hl_attrs[hlf];
-}
-
/// Macros defined in Vim, but not in Neovim
#define CHANGEDTICK(buf) \
(=== Include buffer.h & use buf_(get|set|inc) _changedtick ===)
diff --git a/src/nvim/change.c b/src/nvim/change.c
index c063ece907..5184dc0689 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -10,6 +10,7 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/extmark.h"
@@ -23,7 +24,6 @@
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/ui.h"
@@ -421,12 +421,12 @@ void deleted_lines(linenr_T lnum, linenr_T count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
- // if we deleted the entire buffer, we need to implicitly add a new empty line
bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY;
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM,
- -(linenr_T)count + (made_empty?1:0),
- kExtmarkUndo);
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), MAXLNUM, -(linenr_T)count, kExtmarkNOOP);
+ // if we deleted the entire buffer, we need to implicitly add a new empty line
+ extmark_adjust(curbuf, lnum, (linenr_T)(lnum + count - 1), MAXLNUM,
+ -(linenr_T)count + (made_empty ? 1 : 0), kExtmarkUndo);
changed_lines(lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true);
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 20fae3a206..5910053025 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -4,14 +4,15 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
+#include "nvim/autocmd.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/event/socket.h"
-#include "nvim/fileio.h"
#include "nvim/lua/executor.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/server.h"
+#include "nvim/os/fs.h"
#include "nvim/os/shell.h"
#ifdef WIN32
# include "nvim/os/os_win_console.h"
@@ -314,8 +315,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
ChannelStdinMode stdin_mode, const char *cwd, uint16_t pty_width,
uint16_t pty_height, dict_T *env, varnumber_T *status_out)
{
- assert(cwd == NULL || os_isdir_executable(cwd));
-
Channel *chan = channel_alloc(kChannelStreamProc);
chan->on_data = on_stdout;
chan->on_stderr = on_stderr;
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index a26a7f6aaf..6238d85b3a 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -126,7 +126,7 @@ int buf_init_chartab(buf_T *buf, int global)
}
// Init word char flags all to false
- memset(buf->b_chartab, 0, (size_t)32);
+ CLEAR_FIELD(buf->b_chartab);
// In lisp mode the '-' character is included in keywords.
if (buf->b_p_lisp) {
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
new file mode 100644
index 0000000000..5725a6655d
--- /dev/null
+++ b/src/nvim/cmdhist.c
@@ -0,0 +1,743 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// cmdhist.c: Functions for the history of the command-line.
+
+#include "nvim/ascii.h"
+#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
+#include "nvim/ex_getln.h"
+#include "nvim/regexp.h"
+#include "nvim/strings.h"
+#include "nvim/ui.h"
+#include "nvim/vim.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "cmdhist.c.generated.h"
+#endif
+
+static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
+static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; ///< lastused entry
+/// identifying (unique) number of newest history entry
+static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
+static int hislen = 0; ///< actual length of history tables
+
+/// Return the length of the history tables
+int get_hislen(void)
+{
+ return hislen;
+}
+
+/// Return a pointer to a specified history table
+histentry_T *get_histentry(int hist_type)
+{
+ return history[hist_type];
+}
+
+void set_histentry(int hist_type, histentry_T *entry)
+{
+ history[hist_type] = entry;
+}
+
+int *get_hisidx(int hist_type)
+{
+ return &hisidx[hist_type];
+}
+
+int *get_hisnum(int hist_type)
+{
+ return &hisnum[hist_type];
+}
+
+/// Translate a history character to the associated type number
+HistoryType hist_char2type(const int c)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ switch (c) {
+ case ':':
+ return HIST_CMD;
+ case '=':
+ return HIST_EXPR;
+ case '@':
+ return HIST_INPUT;
+ case '>':
+ return HIST_DEBUG;
+ case NUL:
+ case '/':
+ case '?':
+ return HIST_SEARCH;
+ default:
+ return HIST_INVALID;
+ }
+ // Silence -Wreturn-type
+ return 0;
+}
+
+/// Table of history names.
+/// These names are used in :history and various hist...() functions.
+/// It is sufficient to give the significant prefix of a history name.
+static char *(history_names[]) = {
+ "cmd",
+ "search",
+ "expr",
+ "input",
+ "debug",
+ NULL
+};
+
+/// Function given to ExpandGeneric() to obtain the possible first
+/// arguments of the ":history command.
+char *get_history_arg(expand_T *xp, int idx)
+{
+ static char_u compl[2] = { NUL, NUL };
+ char *short_names = ":=@>?/";
+ int short_names_count = (int)STRLEN(short_names);
+ int history_name_count = ARRAY_SIZE(history_names) - 1;
+
+ if (idx < short_names_count) {
+ compl[0] = (char_u)short_names[idx];
+ return (char *)compl;
+ }
+ if (idx < short_names_count + history_name_count) {
+ return history_names[idx - short_names_count];
+ }
+ if (idx == short_names_count + history_name_count) {
+ return "all";
+ }
+ return NULL;
+}
+
+/// Initialize command line history.
+/// Also used to re-allocate history tables when size changes.
+void init_history(void)
+{
+ assert(p_hi >= 0 && p_hi <= INT_MAX);
+ int newlen = (int)p_hi;
+ int oldlen = hislen;
+
+ // If history tables size changed, reallocate them.
+ // Tables are circular arrays (current position marked by hisidx[type]).
+ // On copying them to the new arrays, we take the chance to reorder them.
+ if (newlen != oldlen) {
+ for (int type = 0; type < HIST_COUNT; type++) {
+ histentry_T *temp = (newlen
+ ? xmalloc((size_t)newlen * sizeof(*temp))
+ : NULL);
+
+ int j = hisidx[type];
+ if (j >= 0) {
+ // old array gets partitioned this way:
+ // [0 , i1 ) --> newest entries to be deleted
+ // [i1 , i1 + l1) --> newest entries to be copied
+ // [i1 + l1 , i2 ) --> oldest entries to be deleted
+ // [i2 , i2 + l2) --> oldest entries to be copied
+ int l1 = MIN(j + 1, newlen); // how many newest to copy
+ int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy
+ int i1 = j + 1 - l1; // copy newest from here
+ int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here
+
+ // copy as much entries as they fit to new table, reordering them
+ if (newlen) {
+ // copy oldest entries
+ memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
+ // copy newest entries
+ memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
+ }
+
+ // delete entries that don't fit in newlen, if any
+ for (int i = 0; i < i1; i++) {
+ hist_free_entry(history[type] + i);
+ }
+ for (int i = i1 + l1; i < i2; i++) {
+ hist_free_entry(history[type] + i);
+ }
+ }
+
+ // clear remaining space, if any
+ int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries
+ if (newlen) {
+ memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
+ }
+
+ hisidx[type] = l3 - 1;
+ xfree(history[type]);
+ history[type] = temp;
+ }
+ hislen = newlen;
+ }
+}
+
+static inline void hist_free_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ xfree(hisptr->hisstr);
+ tv_list_unref(hisptr->additional_elements);
+ clear_hist_entry(hisptr);
+}
+
+static inline void clear_hist_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ CLEAR_POINTER(hisptr);
+}
+
+/// Check if command line 'str' is already in history.
+/// If 'move_to_front' is true, matching entry is moved to end of history.
+///
+/// @param move_to_front Move the entry to the front if it exists
+static int in_history(int type, char_u *str, int move_to_front, int sep)
+{
+ int last_i = -1;
+
+ if (hisidx[type] < 0) {
+ return false;
+ }
+ int i = hisidx[type];
+ do {
+ if (history[type][i].hisstr == NULL) {
+ return false;
+ }
+
+ // For search history, check that the separator character matches as
+ // well.
+ char_u *p = history[type][i].hisstr;
+ if (STRCMP(str, p) == 0
+ && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
+ if (!move_to_front) {
+ return true;
+ }
+ last_i = i;
+ break;
+ }
+ if (--i < 0) {
+ i = hislen - 1;
+ }
+ } while (i != hisidx[type]);
+
+ if (last_i >= 0) {
+ list_T *const list = history[type][i].additional_elements;
+ str = history[type][i].hisstr;
+ while (i != hisidx[type]) {
+ if (++i >= hislen) {
+ i = 0;
+ }
+ history[type][last_i] = history[type][i];
+ last_i = i;
+ }
+ tv_list_unref(list);
+ history[type][i].hisnum = ++hisnum[type];
+ history[type][i].hisstr = str;
+ history[type][i].timestamp = os_time();
+ history[type][i].additional_elements = NULL;
+ return true;
+ }
+ return false;
+}
+
+/// Convert history name to its HIST_ equivalent
+///
+/// Names are taken from the table above. When `name` is empty returns currently
+/// active history or HIST_DEFAULT, depending on `return_default` argument.
+///
+/// @param[in] name Converted name.
+/// @param[in] len Name length.
+/// @param[in] return_default Determines whether HIST_DEFAULT should be
+/// returned or value based on `ccline.cmdfirstc`.
+///
+/// @return Any value from HistoryType enum, including HIST_INVALID. May not
+/// return HIST_DEFAULT unless return_default is true.
+static HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // No argument: use current history.
+ if (len == 0) {
+ return return_default ? HIST_DEFAULT : hist_char2type(get_cmdline_firstc());
+ }
+
+ for (HistoryType i = 0; history_names[i] != NULL; i++) {
+ if (STRNICMP(name, history_names[i], len) == 0) {
+ return i;
+ }
+ }
+
+ if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) {
+ return hist_char2type(name[0]);
+ }
+
+ return HIST_INVALID;
+}
+
+static int last_maptick = -1; // last seen maptick
+
+/// Add the given string to the given history. If the string is already in the
+/// history then it is moved to the front.
+///
+/// @param histype may be one of the HIST_ values.
+/// @param in_map consider maptick when inside a mapping
+/// @param sep separator character used (search hist)
+void add_to_history(int histype, char_u *new_entry, int in_map, int sep)
+{
+ histentry_T *hisptr;
+
+ if (hislen == 0 || histype == HIST_INVALID) { // no history
+ return;
+ }
+ assert(histype != HIST_DEFAULT);
+
+ if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) {
+ return;
+ }
+
+ // Searches inside the same mapping overwrite each other, so that only
+ // the last line is kept. Be careful not to remove a line that was moved
+ // down, only lines that were added.
+ if (histype == HIST_SEARCH && in_map) {
+ if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
+ // Current line is from the same mapping, remove it
+ hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
+ hist_free_entry(hisptr);
+ hisnum[histype]--;
+ if (--hisidx[HIST_SEARCH] < 0) {
+ hisidx[HIST_SEARCH] = hislen - 1;
+ }
+ }
+ last_maptick = -1;
+ }
+ if (!in_history(histype, new_entry, true, sep)) {
+ if (++hisidx[histype] == hislen) {
+ hisidx[histype] = 0;
+ }
+ hisptr = &history[histype][hisidx[histype]];
+ hist_free_entry(hisptr);
+
+ // Store the separator after the NUL of the string.
+ size_t len = STRLEN(new_entry);
+ hisptr->hisstr = vim_strnsave(new_entry, len + 2);
+ hisptr->timestamp = os_time();
+ hisptr->additional_elements = NULL;
+ hisptr->hisstr[len + 1] = (char_u)sep;
+
+ hisptr->hisnum = ++hisnum[histype];
+ if (histype == HIST_SEARCH && in_map) {
+ last_maptick = maptick;
+ }
+ }
+}
+
+/// Get identifier of newest history entry.
+///
+/// @param histype may be one of the HIST_ values.
+static int get_history_idx(int histype)
+{
+ if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
+ || hisidx[histype] < 0) {
+ return -1;
+ }
+
+ return history[histype][hisidx[histype]].hisnum;
+}
+
+/// Calculate history index from a number:
+///
+/// @param num > 0: seen as identifying number of a history entry
+/// < 0: relative position in history wrt newest entry
+/// @param histype may be one of the HIST_ values.
+static int calc_hist_idx(int histype, int num)
+{
+ int i;
+ int wrapped = false;
+
+ if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
+ || (i = hisidx[histype]) < 0 || num == 0) {
+ return -1;
+ }
+
+ histentry_T *hist = history[histype];
+ if (num > 0) {
+ while (hist[i].hisnum > num) {
+ if (--i < 0) {
+ if (wrapped) {
+ break;
+ }
+ i += hislen;
+ wrapped = true;
+ }
+ }
+ if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) {
+ return i;
+ }
+ } else if (-num <= hislen) {
+ i += num + 1;
+ if (i < 0) {
+ i += hislen;
+ }
+ if (hist[i].hisstr != NULL) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/// Get a history entry by its index.
+///
+/// @param histype may be one of the HIST_ values.
+static char_u *get_history_entry(int histype, int idx)
+{
+ idx = calc_hist_idx(histype, idx);
+ if (idx >= 0) {
+ return history[histype][idx].hisstr;
+ } else {
+ return (char_u *)"";
+ }
+}
+
+/// Clear all entries in a history
+///
+/// @param[in] histype One of the HIST_ values.
+///
+/// @return OK if there was something to clean and histype was one of HIST_
+/// values, FAIL otherwise.
+int clr_history(const int histype)
+{
+ if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
+ histentry_T *hisptr = history[histype];
+ for (int i = hislen; i--; hisptr++) {
+ hist_free_entry(hisptr);
+ }
+ hisidx[histype] = -1; // mark history as cleared
+ hisnum[histype] = 0; // reset identifier counter
+ return OK;
+ }
+ return FAIL;
+}
+
+/// Remove all entries matching {str} from a history.
+///
+/// @param histype may be one of the HIST_ values.
+static int del_history_entry(int histype, char_u *str)
+{
+ regmatch_T regmatch;
+ histentry_T *hisptr;
+ int idx;
+ int i;
+ int last;
+ bool found = false;
+
+ regmatch.regprog = NULL;
+ regmatch.rm_ic = false; // always match case
+ if (hislen != 0
+ && histype >= 0
+ && histype < HIST_COUNT
+ && *str != NUL
+ && (idx = hisidx[histype]) >= 0
+ && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING)) != NULL) {
+ i = last = idx;
+ do {
+ hisptr = &history[histype][i];
+ if (hisptr->hisstr == NULL) {
+ break;
+ }
+ if (vim_regexec(&regmatch, (char *)hisptr->hisstr, (colnr_T)0)) {
+ found = true;
+ hist_free_entry(hisptr);
+ } else {
+ if (i != last) {
+ history[histype][last] = *hisptr;
+ clear_hist_entry(hisptr);
+ }
+ if (--last < 0) {
+ last += hislen;
+ }
+ }
+ if (--i < 0) {
+ i += hislen;
+ }
+ } while (i != idx);
+ if (history[histype][idx].hisstr == NULL) {
+ hisidx[histype] = -1;
+ }
+ }
+ vim_regfree(regmatch.regprog);
+ return found;
+}
+
+/// Remove an indexed entry from a history.
+///
+/// @param histype may be one of the HIST_ values.
+static int del_history_idx(int histype, int idx)
+{
+ int i = calc_hist_idx(histype, idx);
+ if (i < 0) {
+ return false;
+ }
+ idx = hisidx[histype];
+ hist_free_entry(&history[histype][i]);
+
+ // When deleting the last added search string in a mapping, reset
+ // last_maptick, so that the last added search string isn't deleted again.
+ if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
+ last_maptick = -1;
+ }
+
+ while (i != idx) {
+ int j = (i + 1) % hislen;
+ history[histype][i] = history[histype][j];
+ i = j;
+ }
+ clear_hist_entry(&history[histype][idx]);
+ if (--i < 0) {
+ i += hislen;
+ }
+ hisidx[histype] = i;
+ return true;
+}
+
+/// "histadd()" function
+void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType histype;
+
+ rettv->vval.v_number = false;
+ if (check_secure()) {
+ return;
+ }
+ const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
+ if (histype != HIST_INVALID) {
+ char buf[NUMBUFLEN];
+ str = tv_get_string_buf(&argvars[1], buf);
+ if (*str != NUL) {
+ init_history();
+ add_to_history(histype, (char_u *)str, false, NUL);
+ rettv->vval.v_number = true;
+ return;
+ }
+ }
+}
+
+/// "histdel()" function
+void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n;
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ n = 0;
+ } else if (argvars[1].v_type == VAR_UNKNOWN) {
+ // only one argument: clear entire history
+ n = clr_history(get_histtype(str, strlen(str), false));
+ } else if (argvars[1].v_type == VAR_NUMBER) {
+ // index given: remove that entry
+ n = del_history_idx(get_histtype(str, strlen(str), false),
+ (int)tv_get_number(&argvars[1]));
+ } else {
+ // string given: remove all matching entries
+ char buf[NUMBUFLEN];
+ n = del_history_entry(get_histtype(str, strlen(str), false),
+ (char_u *)tv_get_string_buf(&argvars[1], buf));
+ }
+ rettv->vval.v_number = n;
+}
+
+/// "histget()" function
+void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType type;
+ int idx;
+
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ type = get_histtype(str, strlen(str), false);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ idx = get_history_idx(type);
+ } else {
+ idx = (int)tv_get_number_chk(&argvars[1], NULL);
+ }
+ // -1 on type error
+ rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx));
+ }
+ rettv->v_type = VAR_STRING;
+}
+
+/// "histnr()" function
+void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const histname = tv_get_string_chk(&argvars[0]);
+ HistoryType i = histname == NULL
+ ? HIST_INVALID
+ : get_histtype(histname, strlen(histname), false);
+ if (i != HIST_INVALID) {
+ i = get_history_idx(i);
+ }
+ rettv->vval.v_number = i;
+}
+
+/// :history command - print a history
+void ex_history(exarg_T *eap)
+{
+ histentry_T *hist;
+ int histype1 = HIST_CMD;
+ int histype2 = HIST_CMD;
+ int hisidx1 = 1;
+ int hisidx2 = -1;
+ int idx;
+ int i, j, k;
+ char *end;
+ char_u *arg = (char_u *)eap->arg;
+
+ if (hislen == 0) {
+ msg(_("'history' option is zero"));
+ return;
+ }
+
+ if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
+ end = (char *)arg;
+ while (ASCII_ISALPHA(*end)
+ || vim_strchr(":=@>/?", *end) != NULL) {
+ end++;
+ }
+ histype1 = get_histtype((const char *)arg, (size_t)(end - (char *)arg), false);
+ if (histype1 == HIST_INVALID) {
+ if (STRNICMP(arg, "all", end - (char *)arg) == 0) {
+ histype1 = 0;
+ histype2 = HIST_COUNT - 1;
+ } else {
+ semsg(_(e_trailing_arg), arg);
+ return;
+ }
+ } else {
+ histype2 = histype1;
+ }
+ } else {
+ end = (char *)arg;
+ }
+ if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
+ semsg(_(e_trailing_arg), end);
+ return;
+ }
+
+ for (; !got_int && histype1 <= histype2; histype1++) {
+ STRCPY(IObuff, "\n # ");
+ assert(history_names[histype1] != NULL);
+ STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
+ msg_puts_title((char *)IObuff);
+ idx = hisidx[histype1];
+ hist = history[histype1];
+ j = hisidx1;
+ k = hisidx2;
+ if (j < 0) {
+ j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
+ }
+ if (k < 0) {
+ k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
+ }
+ if (idx >= 0 && j <= k) {
+ for (i = idx + 1; !got_int; i++) {
+ if (i == hislen) {
+ i = 0;
+ }
+ if (hist[i].hisstr != NULL
+ && hist[i].hisnum >= j && hist[i].hisnum <= k) {
+ msg_putchar('\n');
+ snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
+ hist[i].hisnum);
+ if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) {
+ trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff),
+ Columns - 10, IOSIZE - (int)STRLEN(IObuff));
+ } else {
+ STRCAT(IObuff, hist[i].hisstr);
+ }
+ msg_outtrans((char *)IObuff);
+ ui_flush();
+ }
+ if (i == idx) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+/// Iterate over history items
+///
+/// @warning No history-editing functions must be run while iteration is in
+/// progress.
+///
+/// @param[in] iter Pointer to the last history entry.
+/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
+/// parameter is not NULL.
+/// @param[in] zero If true then zero (but not free) returned items.
+///
+/// @warning When using this parameter user is
+/// responsible for calling clr_history()
+/// itself after iteration is over. If
+/// clr_history() is not called behaviour is
+/// undefined. No functions that work with
+/// history must be called during iteration
+/// in this case.
+/// @param[out] hist Next history entry.
+///
+/// @return Pointer used in next iteration or NULL to indicate that iteration
+/// was finished.
+const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
+ histentry_T *const hist)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
+{
+ *hist = (histentry_T) {
+ .hisstr = NULL
+ };
+ if (hisidx[history_type] == -1) {
+ return NULL;
+ }
+ histentry_T *const hstart = &(history[history_type][0]);
+ histentry_T *const hlast = &(history[history_type][hisidx[history_type]]);
+ const histentry_T *const hend = &(history[history_type][hislen - 1]);
+ histentry_T *hiter;
+ if (iter == NULL) {
+ histentry_T *hfirst = hlast;
+ do {
+ hfirst++;
+ if (hfirst > hend) {
+ hfirst = hstart;
+ }
+ if (hfirst->hisstr != NULL) {
+ break;
+ }
+ } while (hfirst != hlast);
+ hiter = hfirst;
+ } else {
+ hiter = (histentry_T *)iter;
+ }
+ if (hiter == NULL) {
+ return NULL;
+ }
+ *hist = *hiter;
+ if (zero) {
+ CLEAR_POINTER(hiter);
+ }
+ if (hiter == hlast) {
+ return NULL;
+ }
+ hiter++;
+ return (const void *)((hiter > hend) ? hstart : hiter);
+}
+
+/// Get array of history items
+///
+/// @param[in] history_type Type of the history to get array for.
+/// @param[out] new_hisidx Location where last index in the new array should
+/// be saved.
+/// @param[out] new_hisnum Location where last history number in the new
+/// history should be saved.
+///
+/// @return Pointer to the array or NULL.
+histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
+ int **const new_hisnum)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ init_history();
+ *new_hisidx = &(hisidx[history_type]);
+ *new_hisnum = &(hisnum[history_type]);
+ return history[history_type];
+}
diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h
new file mode 100644
index 0000000000..797b79a5f0
--- /dev/null
+++ b/src/nvim/cmdhist.h
@@ -0,0 +1,33 @@
+#ifndef NVIM_CMDHIST_H
+#define NVIM_CMDHIST_H
+
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/os/time.h"
+
+/// Present history tables
+typedef enum {
+ HIST_DEFAULT = -2, ///< Default (current) history.
+ HIST_INVALID = -1, ///< Unknown history.
+ HIST_CMD = 0, ///< Colon commands.
+ HIST_SEARCH, ///< Search commands.
+ HIST_EXPR, ///< Expressions (e.g. from entering = register).
+ HIST_INPUT, ///< input() lines.
+ HIST_DEBUG, ///< Debug commands.
+} HistoryType;
+
+/// Number of history tables
+#define HIST_COUNT (HIST_DEBUG + 1)
+
+/// History entry definition
+typedef struct hist_entry {
+ int hisnum; ///< Entry identifier number.
+ char_u *hisstr; ///< Actual entry, separator char after the NUL.
+ Timestamp timestamp; ///< Time when entry was added.
+ list_T *additional_elements; ///< Additional entries from ShaDa file.
+} histentry_T;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "cmdhist.h.generated.h"
+#endif
+#endif // NVIM_CMDHIST_H
diff --git a/src/nvim/context.c b/src/nvim/context.c
index db26667009..e3ae9355bf 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -9,6 +9,7 @@
#include "nvim/api/vimscript.h"
#include "nvim/context.h"
#include "nvim/eval/encode.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
#include "nvim/option.h"
#include "nvim/shada.h"
@@ -249,7 +250,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
ctx->funcs = (Array)ARRAY_DICT_INIT;
Error err = ERROR_INIT;
- HASHTAB_ITER(&func_hashtab, hi, {
+ HASHTAB_ITER(func_tbl_get(), hi, {
const char_u *const name = hi->hi_key;
bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
bool isscript = (name[0] == K_SPECIAL);
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 1446257f7e..d4670dedc7 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -9,6 +9,7 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
#include "nvim/mark.h"
@@ -17,7 +18,6 @@
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/vim.h"
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index 0eaff06833..a061bd961b 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -8,6 +8,7 @@
#include "nvim/ascii.h"
#include "nvim/charset.h"
#include "nvim/debugger.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
@@ -17,7 +18,7 @@
#include "nvim/os/os.h"
#include "nvim/pos.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/types.h"
#include "nvim/vim.h"
@@ -98,14 +99,17 @@ void do_debug(char_u *cmd)
xfree(debug_newval);
debug_newval = NULL;
}
- if (sourcing_name != NULL) {
- msg(sourcing_name);
+ char *sname = estack_sfile(ESTACK_NONE);
+ if (sname != NULL) {
+ msg(sname);
}
- if (sourcing_lnum != 0) {
- smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd);
+ xfree(sname);
+ if (SOURCING_LNUM != 0) {
+ smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
} else {
smsg(_("cmd: %s"), cmd);
}
+
// Repeat getting a command and executing it.
for (;;) {
msg_scroll = true;
@@ -287,12 +291,12 @@ void do_debug(char_u *cmd)
debug_did_msg = true;
}
-static int get_maxbacktrace_level(void)
+static int get_maxbacktrace_level(char *sname)
{
int maxbacktrace = 0;
- if (sourcing_name != NULL) {
- char *p = sourcing_name;
+ if (sname != NULL) {
+ char *p = sname;
char *q;
while ((q = strstr(p, "..")) != NULL) {
p = q + 2;
@@ -320,20 +324,24 @@ static void do_checkbacktracelevel(void)
debug_backtrace_level = 0;
msg(_("frame is zero"));
} else {
- int max = get_maxbacktrace_level();
+ char *sname = estack_sfile(ESTACK_NONE);
+ int max = get_maxbacktrace_level(sname);
+
if (debug_backtrace_level > max) {
debug_backtrace_level = max;
smsg(_("frame at highest level: %d"), max);
}
+ xfree(sname);
}
}
static void do_showbacktrace(char_u *cmd)
{
- if (sourcing_name != NULL) {
+ char *sname = estack_sfile(ESTACK_NONE);
+ int max = get_maxbacktrace_level(sname);
+ if (sname != NULL) {
int i = 0;
- int max = get_maxbacktrace_level();
- char *cur = sourcing_name;
+ char *cur = sname;
while (!got_int) {
char *next = strstr(cur, "..");
if (next != NULL) {
@@ -351,9 +359,11 @@ static void do_showbacktrace(char_u *cmd)
*next = '.';
cur = next + 2;
}
+ xfree(sname);
}
- if (sourcing_lnum != 0) {
- smsg(_("line %" PRId64 ": %s"), (int64_t)sourcing_lnum, cmd);
+
+ if (SOURCING_LNUM != 0) {
+ smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
} else {
smsg(_("cmd: %s"), cmd);
}
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index e7c76fe38e..a93fb599c4 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -4,12 +4,12 @@
#include "nvim/api/ui.h"
#include "nvim/buffer.h"
#include "nvim/decoration.h"
+#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
#include "nvim/move.h"
-#include "nvim/screen.h"
#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -358,7 +358,8 @@ next_mark:
return attr;
}
-void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs[])
+void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[],
+ HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs)
{
if (!buf->b_signs) {
return;
@@ -383,30 +384,37 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs
goto next_mark;
}
- int j;
- for (j = (*num_signs); j > 0; j--) {
- if (sattrs[j].sat_prio <= decor->priority) {
- break;
- }
- sattrs[j] = sattrs[j - 1];
- }
- if (j < SIGN_SHOW_MAX) {
- memset(&sattrs[j], 0, sizeof(sign_attrs_T));
- sattrs[j].sat_text = decor->sign_text;
- if (decor->sign_hl_id != 0) {
- sattrs[j].sat_texthl = syn_id2attr(decor->sign_hl_id);
- }
- if (decor->number_hl_id != 0) {
- sattrs[j].sat_numhl = syn_id2attr(decor->number_hl_id);
+ if (decor->sign_text) {
+ int j;
+ for (j = (*num_signs); j > 0; j--) {
+ if (sattrs[j - 1].priority >= decor->priority) {
+ break;
+ }
+ sattrs[j] = sattrs[j - 1];
}
- if (decor->line_hl_id != 0) {
- sattrs[j].sat_linehl = syn_id2attr(decor->line_hl_id);
+ if (j < SIGN_SHOW_MAX) {
+ sattrs[j] = (SignTextAttrs) {
+ .text = decor->sign_text,
+ .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id),
+ .priority = decor->priority
+ };
+ (*num_signs)++;
}
- if (decor->cursorline_hl_id != 0) {
- sattrs[j].sat_culhl = syn_id2attr(decor->cursorline_hl_id);
+ }
+
+ struct { HlPriAttr *dest; int hl; } cattrs[] = {
+ { line_attrs, decor->line_hl_id },
+ { num_attrs, decor->number_hl_id },
+ { cul_attrs, decor->cursorline_hl_id },
+ { NULL, -1 },
+ };
+ for (int i = 0; cattrs[i].dest; i++) {
+ if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) {
+ *cattrs[i].dest = (HlPriAttr) {
+ .attr_id = syn_id2attr(cattrs[i].hl),
+ .priority = decor->priority
+ };
}
- sattrs[j].sat_prio = decor->priority;
- (*num_signs)++;
}
next_mark:
diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c
index 04d875c4e3..95e13b4240 100644
--- a/src/nvim/decoration_provider.c
+++ b/src/nvim/decoration_provider.c
@@ -14,7 +14,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, false, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, \
- LUA_NOREF, -1 }
+ LUA_NOREF, -1, false }
static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args,
bool default_true, char **perr)
@@ -107,8 +107,6 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
}
}
}
-
- win_check_ns_hl(wp);
}
/// For each provider invoke the 'line' callback for a given window row.
@@ -135,7 +133,7 @@ void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *
kv_A(*providers, k) = NULL;
}
- win_check_ns_hl(wp);
+ hl_check_ns();
}
}
}
diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h
index 3ec7c80357..dd1ed6c581 100644
--- a/src/nvim/decoration_provider.h
+++ b/src/nvim/decoration_provider.h
@@ -13,6 +13,7 @@ typedef struct {
LuaRef redraw_end;
LuaRef hl_def;
int hl_valid;
+ bool hl_cached;
} DecorProvider;
typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 849204f789..c1fdbc1b9a 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -14,11 +14,13 @@
#include <stdbool.h>
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
@@ -35,7 +37,6 @@
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
@@ -956,13 +957,13 @@ void ex_diffupdate(exarg_T *eap)
// Only use the internal method if it did not fail for one of the buffers.
diffio_T diffio;
- memset(&diffio, 0, sizeof(diffio));
+ CLEAR_FIELD(diffio);
diffio.dio_internal = diff_internal() && !diff_internal_failed();
diff_try_update(&diffio, idx_orig, eap);
if (diffio.dio_internal && diff_internal_failed()) {
// Internal diff failed, use external diff instead.
- memset(&diffio, 0, sizeof(diffio));
+ CLEAR_FIELD(diffio);
diff_try_update(&diffio, idx_orig, eap);
}
@@ -1075,9 +1076,9 @@ static int diff_file_internal(diffio_T *diffio)
xdemitconf_t emit_cfg;
xdemitcb_t emit_cb;
- memset(&param, 0, sizeof(param));
- memset(&emit_cfg, 0, sizeof(emit_cfg));
- memset(&emit_cb, 0, sizeof(emit_cb));
+ CLEAR_FIELD(param);
+ CLEAR_FIELD(emit_cfg);
+ CLEAR_FIELD(emit_cb);
param.flags = (unsigned long)diff_algorithm;
@@ -2143,14 +2144,14 @@ int diffopt_changed(void)
long diff_algorithm_new = 0;
long diff_indent_heuristic = 0;
- char_u *p = p_dip;
+ char *p = (char *)p_dip;
while (*p != NUL) {
if (STRNCMP(p, "filler", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_FILLER;
} else if ((STRNCMP(p, "context:", 8) == 0) && ascii_isdigit(p[8])) {
p += 8;
- diff_context_new = getdigits_int((char **)&p, false, diff_context_new);
+ diff_context_new = getdigits_int(&p, false, diff_context_new);
} else if (STRNCMP(p, "iblank", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_IBLANK;
@@ -2174,7 +2175,7 @@ int diffopt_changed(void)
diff_flags_new |= DIFF_VERTICAL;
} else if ((STRNCMP(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) {
p += 11;
- diff_foldcolumn_new = getdigits_int((char **)&p, false, diff_foldcolumn_new);
+ diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new);
} else if (STRNCMP(p, "hiddenoff", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 733b3d3d5d..0f511bd37c 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -12,8 +12,8 @@
#include "nvim/ascii.h"
#include "nvim/charset.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval/typval.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/garray.h"
@@ -24,7 +24,7 @@
#include "nvim/message.h"
#include "nvim/normal.h"
#include "nvim/os/input.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/vim.h"
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
new file mode 100644
index 0000000000..95026ff8ed
--- /dev/null
+++ b/src/nvim/drawline.c
@@ -0,0 +1,2706 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// drawline.c: Functions for drawing window lines on the screen.
+// This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level.
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nvim/arabic.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/cursor_shape.h"
+#include "nvim/diff.h"
+#include "nvim/drawline.h"
+#include "nvim/fold.h"
+#include "nvim/grid.h"
+#include "nvim/highlight.h"
+#include "nvim/highlight_group.h"
+#include "nvim/indent.h"
+#include "nvim/match.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/plines.h"
+#include "nvim/quickfix.h"
+#include "nvim/search.h"
+#include "nvim/sign.h"
+#include "nvim/spell.h"
+#include "nvim/state.h"
+#include "nvim/syntax.h"
+#include "nvim/undo.h"
+#include "nvim/window.h"
+
+#define MB_FILLER_CHAR '<' // character used when a double-width character
+ // doesn't fit.
+
+/// for line_putchar. Contains the state that needs to be remembered from
+/// putting one character to the next.
+typedef struct {
+ const char *p;
+ int prev_c; ///< previous Arabic character
+ int prev_c1; ///< first composing char for prev_c
+} LineState;
+#define LINE_STATE(p) { p, 0, 0 }
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "drawline.c.generated.h"
+#endif
+
+/// Advance **color_cols
+///
+/// @return true when there are columns to draw.
+static bool advance_color_col(int vcol, int **color_cols)
+{
+ while (**color_cols >= 0 && vcol > **color_cols) {
+ (*color_cols)++;
+ }
+ return **color_cols >= 0;
+}
+
+/// Used when 'cursorlineopt' contains "screenline": compute the margins between
+/// which the highlighting is used.
+static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
+{
+ // cache previous calculations depending on w_virtcol
+ static int saved_w_virtcol;
+ static win_T *prev_wp;
+ static int prev_left_col;
+ static int prev_right_col;
+ static int prev_col_off;
+
+ int cur_col_off = win_col_off(wp);
+ int width1;
+ int width2;
+
+ if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp
+ && prev_col_off == cur_col_off) {
+ *right_col = prev_right_col;
+ *left_col = prev_left_col;
+ return;
+ }
+
+ width1 = wp->w_width - cur_col_off;
+ width2 = width1 + win_col_off2(wp);
+
+ *left_col = 0;
+ *right_col = width1;
+
+ if (wp->w_virtcol >= (colnr_T)width1) {
+ *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2;
+ }
+ if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
+ *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1;
+ }
+
+ // cache values
+ prev_left_col = *left_col;
+ prev_right_col = *right_col;
+ prev_wp = wp;
+ saved_w_virtcol = wp->w_virtcol;
+ prev_col_off = cur_col_off;
+}
+
+/// Put a single char from an UTF-8 buffer into a line buffer.
+///
+/// Handles composing chars and arabic shaping state.
+static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol)
+{
+ const char_u *p = (char_u *)s->p;
+ int cells = utf_ptr2cells((char *)p);
+ int c_len = utfc_ptr2len((char *)p);
+ int u8c, u8cc[MAX_MCO];
+ if (cells > maxcells) {
+ return -1;
+ }
+ u8c = utfc_ptr2char(p, u8cc);
+ if (*p == TAB) {
+ cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
+ for (int c = 0; c < cells; c++) {
+ schar_from_ascii(dest[c], ' ');
+ }
+ goto done;
+ } else if (*p < 0x80 && u8cc[0] == 0) {
+ schar_from_ascii(dest[0], (char)(*p));
+ s->prev_c = u8c;
+ } else {
+ if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
+ // Do Arabic shaping.
+ int pc, pc1, nc;
+ int pcc[MAX_MCO];
+ int firstbyte = *p;
+
+ // The idea of what is the previous and next
+ // character depends on 'rightleft'.
+ if (rl) {
+ pc = s->prev_c;
+ pc1 = s->prev_c1;
+ nc = utf_ptr2char((char *)p + c_len);
+ s->prev_c1 = u8cc[0];
+ } else {
+ pc = utfc_ptr2char(p + c_len, pcc);
+ nc = s->prev_c;
+ pc1 = pcc[0];
+ }
+ s->prev_c = u8c;
+
+ u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
+ } else {
+ s->prev_c = u8c;
+ }
+ schar_from_cc(dest[0], u8c, u8cc);
+ }
+ if (cells > 1) {
+ dest[1][0] = 0;
+ }
+done:
+ s->p += c_len;
+ return cells;
+}
+
+static inline void provider_err_virt_text(linenr_T lnum, char *err)
+{
+ Decoration err_decor = DECORATION_INIT;
+ int hl_err = syn_check_group(S_LEN("ErrorMsg"));
+ kv_push(err_decor.virt_text,
+ ((VirtTextChunk){ .text = err,
+ .hl_id = hl_err }));
+ err_decor.virt_text_width = (int)mb_string2cells(err);
+ decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0);
+}
+
+static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col,
+ int win_row)
+{
+ DecorState *state = &decor_state;
+ int right_pos = max_col;
+ bool do_eol = state->eol_col > -1;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (!(item->start_row == state->row
+ && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) {
+ continue;
+ }
+ if (item->win_col == -1) {
+ if (item->decor.virt_text_pos == kVTRightAlign) {
+ right_pos -= item->decor.virt_text_width;
+ item->win_col = right_pos;
+ } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
+ item->win_col = state->eol_col;
+ } else if (item->decor.virt_text_pos == kVTWinCol) {
+ item->win_col = MAX(item->decor.col + col_off, 0);
+ }
+ }
+ if (item->win_col < 0) {
+ continue;
+ }
+ int col;
+ if (item->decor.ui_watched) {
+ // send mark position to UI
+ col = item->win_col;
+ WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col };
+ kv_push(win_extmark_arr, m);
+ }
+ if (kv_size(item->decor.virt_text)) {
+ col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text,
+ item->decor.hl_mode, max_col, item->win_col - col_off);
+ }
+ item->win_col = -2; // deactivate
+ if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
+ state->eol_col = col + 1;
+ }
+
+ *end_col = MAX(*end_col, col);
+ }
+}
+
+static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col,
+ int vcol)
+{
+ LineState s = LINE_STATE("");
+ int virt_attr = 0;
+ size_t virt_pos = 0;
+
+ while (col < max_col) {
+ if (!*s.p) {
+ if (virt_pos >= kv_size(vt)) {
+ break;
+ }
+ virt_attr = 0;
+ do {
+ s.p = kv_A(vt, virt_pos).text;
+ int hl_id = kv_A(vt, virt_pos).hl_id;
+ virt_attr = hl_combine_attr(virt_attr,
+ hl_id > 0 ? syn_id2attr(hl_id) : 0);
+ virt_pos++;
+ } while (!s.p && virt_pos < kv_size(vt));
+ if (!s.p) {
+ break;
+ }
+ }
+ if (!*s.p) {
+ continue;
+ }
+ int attr;
+ bool through = false;
+ if (hl_mode == kHlModeCombine) {
+ attr = hl_combine_attr(linebuf_attr[col], virt_attr);
+ } else if (hl_mode == kHlModeBlend) {
+ through = (*s.p == ' ');
+ attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
+ } else {
+ attr = virt_attr;
+ }
+ schar_T dummy[2];
+ int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col],
+ max_col - col, false, vcol);
+ // if we failed to emit a char, we still need to advance
+ cells = MAX(cells, 1);
+
+ for (int c = 0; c < cells; c++) {
+ linebuf_attr[col++] = attr;
+ }
+ vcol += cells;
+ }
+ return col;
+}
+
+/// Return true if CursorLineSign highlight is to be used.
+static bool use_cursor_line_sign(win_T *wp, linenr_T lnum)
+{
+ return wp->w_p_cul
+ && lnum == wp->w_cursor.lnum
+ && (wp->w_p_culopt_flags & CULOPT_NBR);
+}
+
+// Get information needed to display the sign in line 'lnum' in window 'wp'.
+// If 'nrcol' is TRUE, the sign is going to be displayed in the number column.
+// Otherwise the sign is going to be displayed in the sign column.
+//
+// @param count max number of signs
+// @param[out] n_extrap number of characters from pp_extra to display
+// @param sign_idxp Index of the displayed sign
+static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs sattrs[],
+ int row, int startrow, int filler_lines, int filler_todo,
+ int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size,
+ char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx,
+ int cul_attr)
+{
+ // Draw cells with the sign value or blank.
+ *c_extrap = ' ';
+ *c_finalp = NUL;
+ if (nrcol) {
+ *n_extrap = number_width(wp) + 1;
+ } else {
+ if (use_cursor_line_sign(wp, lnum)) {
+ *char_attrp = win_hl_attr(wp, HLF_CLS);
+ } else {
+ *char_attrp = win_hl_attr(wp, HLF_SC);
+ }
+ *n_extrap = win_signcol_width(wp);
+ }
+
+ if (row == startrow + filler_lines && filler_todo <= 0) {
+ SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth);
+ if (sattr != NULL) {
+ *pp_extra = sattr->text;
+ if (*pp_extra != NULL) {
+ *c_extrap = NUL;
+ *c_finalp = NUL;
+
+ if (nrcol) {
+ int n, width = number_width(wp) - 2;
+ for (n = 0; n < width; n++) {
+ extra[n] = ' ';
+ }
+ extra[n] = NUL;
+ STRCAT(extra, *pp_extra);
+ STRCAT(extra, " ");
+ *pp_extra = extra;
+ *n_extrap = (int)STRLEN(*pp_extra);
+ } else {
+ size_t symbol_blen = STRLEN(*pp_extra);
+
+ // TODO(oni-link): Is sign text already extended to
+ // full cell width?
+ assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra)));
+ // symbol(s) bytes + (filling spaces) (one byte each)
+ *n_extrap = (int)symbol_blen + win_signcol_width(wp) -
+ (int)mb_string2cells((char *)(*pp_extra));
+
+ assert(extra_size > symbol_blen);
+ memset(extra, ' ', extra_size);
+ memcpy(extra, *pp_extra, symbol_blen);
+
+ *pp_extra = extra;
+ (*pp_extra)[*n_extrap] = NUL;
+ }
+ }
+
+ if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) {
+ *char_attrp = cul_attr;
+ } else {
+ *char_attrp = sattr->hl_attr_id;
+ }
+ }
+ }
+}
+
+static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr,
+ int *num_attr, int *cul_attr)
+{
+ HlPriAttr line_attrs = { *line_attr, 0 };
+ HlPriAttr num_attrs = { *num_attr, 0 };
+ HlPriAttr cul_attrs = { *cul_attr, 0 };
+
+ // TODO(bfredl, vigoux): line_attr should not take priority over decoration!
+ int num_signs = buf_get_signattrs(buf, lnum, sattrs, &num_attrs, &line_attrs, &cul_attrs);
+ decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs, &num_attrs, &line_attrs, &cul_attrs);
+
+ *line_attr = line_attrs.attr_id;
+ *num_attr = num_attrs.attr_id;
+ *cul_attr = cul_attrs.attr_id;
+
+ return num_signs;
+}
+
+/// Return true if CursorLineNr highlight is to be used for the number column.
+///
+/// - 'cursorline' must be set
+/// - lnum must be the cursor line
+/// - 'cursorlineopt' has "number"
+/// - don't highlight filler lines (when in diff mode)
+/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
+/// itself on the first screenline of the wrapped line, otherwise highlight the number column of
+/// all screenlines of the wrapped line.
+static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
+{
+ return wp->w_p_cul
+ && lnum == wp->w_cursor.lnum
+ && (wp->w_p_culopt_flags & CULOPT_NBR)
+ && (row == startrow + filler_lines
+ || (row > startrow + filler_lines
+ && (wp->w_p_culopt_flags & CULOPT_LINE)));
+}
+
+static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len)
+{
+ long num;
+ char *fmt = "%*ld ";
+
+ if (wp->w_p_nu && !wp->w_p_rnu) {
+ // 'number' + 'norelativenumber'
+ num = (long)lnum;
+ } else {
+ // 'relativenumber', don't use negative numbers
+ num = labs((long)get_cursor_rel_lnum(wp, lnum));
+ if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
+ // 'number' + 'relativenumber'
+ num = lnum;
+ fmt = "%-*ld ";
+ }
+ }
+
+ snprintf((char *)buf, buf_len, fmt, number_width(wp), num);
+}
+
+static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
+{
+ if (wp->w_p_rnu) {
+ if (lnum < wp->w_cursor.lnum) {
+ // Use LineNrAbove
+ return win_hl_attr(wp, HLF_LNA);
+ }
+ if (lnum > wp->w_cursor.lnum) {
+ // Use LineNrBelow
+ return win_hl_attr(wp, HLF_LNB);
+ }
+ }
+
+ if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) {
+ // TODO(vim): Can we use CursorLine instead of CursorLineNr
+ // when CursorLineNr isn't set?
+ return win_hl_attr(wp, HLF_CLN);
+ }
+
+ return win_hl_attr(wp, HLF_N);
+}
+
+static void apply_cursorline_highlight(win_T *wp, linenr_T lnum, int *line_attr, int *cul_attr,
+ int *line_attr_lowprio)
+{
+ *cul_attr = win_hl_attr(wp, HLF_CUL);
+ HlAttrs ae = syn_attr2entry(*cul_attr);
+ // We make a compromise here (#7383):
+ // * low-priority CursorLine if fg is not set
+ // * high-priority ("same as Vim" priority) CursorLine if fg is set
+ if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
+ *line_attr_lowprio = *cul_attr;
+ } else {
+ if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer)
+ && qf_current_entry(wp) == lnum) {
+ *line_attr = hl_combine_attr(*cul_attr, *line_attr);
+ } else {
+ *line_attr = *cul_attr;
+ }
+ }
+}
+
+/// Display line "lnum" of window 'wp' on the screen.
+/// wp->w_virtcol needs to be valid.
+///
+/// @param lnum line to display
+/// @param startrow first row relative to window grid
+/// @param endrow last grid row to be redrawn
+/// @param nochange not updating for changed text
+/// @param number_only only update the number column
+/// @param foldinfo fold info for this line
+/// @param[in, out] providers decoration providers active this line
+/// items will be disables if they cause errors
+/// or explicitly return `false`.
+///
+/// @return the number of last row the line occupies.
+int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only,
+ foldinfo_T foldinfo, DecorProviders *providers, char **provider_err)
+{
+ int c = 0; // init for GCC
+ long vcol = 0; // virtual column (for tabs)
+ long vcol_sbr = -1; // virtual column after showbreak
+ long vcol_prev = -1; // "vcol" of previous character
+ char_u *line; // current line
+ char_u *ptr; // current position in "line"
+ int row; // row in the window, excl w_winrow
+ ScreenGrid *grid = &wp->w_grid; // grid specific to the window
+
+ char_u extra[57]; // sign, line number and 'fdc' must
+ // fit in here
+ int n_extra = 0; // number of extra chars
+ char_u *p_extra = NULL; // string of extra chars, plus NUL
+ char_u *p_extra_free = NULL; // p_extra needs to be freed
+ int c_extra = NUL; // extra chars, all the same
+ int c_final = NUL; // final char, mandatory if set
+ int extra_attr = 0; // attributes when n_extra != 0
+ static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying
+ // curwin->w_p_lcs_chars.eol at
+ // end-of-line
+ int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
+ int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
+ bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
+
+ // saved "extra" items for when draw_state becomes WL_LINE (again)
+ int saved_n_extra = 0;
+ char_u *saved_p_extra = NULL;
+ int saved_c_extra = 0;
+ int saved_c_final = 0;
+ int saved_char_attr = 0;
+
+ int n_attr = 0; // chars with special attr
+ int saved_attr2 = 0; // char_attr saved for n_attr
+ int n_attr3 = 0; // chars with overruling special attr
+ int saved_attr3 = 0; // char_attr saved for n_attr3
+
+ int n_skip = 0; // nr of chars to skip for 'nowrap'
+
+ int fromcol = -10; // start of inverting
+ int tocol = MAXCOL; // end of inverting
+ int fromcol_prev = -2; // start of inverting after cursor
+ bool noinvcur = false; // don't invert the cursor
+ bool lnum_in_visual_area = false;
+ pos_T pos;
+ long v;
+
+ int char_attr = 0; // attributes for next character
+ bool attr_pri = false; // char_attr has priority
+ bool area_highlighting = false; // Visual or incsearch highlighting in this line
+ int attr = 0; // attributes for area highlighting
+ int area_attr = 0; // attributes desired by highlighting
+ int search_attr = 0; // attributes desired by 'hlsearch'
+ int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
+ int syntax_attr = 0; // attributes desired by syntax
+ bool has_syntax = false; // this buffer has syntax highl.
+ int save_did_emsg;
+ int eol_hl_off = 0; // 1 if highlighted char after EOL
+ bool draw_color_col = false; // highlight colorcolumn
+ int *color_cols = NULL; // pointer to according columns array
+ bool has_spell = false; // this buffer has spell checking
+#define SPWORDLEN 150
+ char_u nextline[SPWORDLEN * 2]; // text with start of the next line
+ int nextlinecol = 0; // column where nextline[] starts
+ int nextline_idx = 0; // index in nextline[] where next line
+ // starts
+ int spell_attr = 0; // attributes desired by spelling
+ int word_end = 0; // last byte with same spell_attr
+ static linenr_T checked_lnum = 0; // line number for "checked_col"
+ static int checked_col = 0; // column in "checked_lnum" up to which
+ // there are no spell errors
+ static int cap_col = -1; // column to check for Cap word
+ static linenr_T capcol_lnum = 0; // line number where "cap_col"
+ int cur_checked_col = 0; // checked column for current line
+ int extra_check = 0; // has syntax or linebreak
+ int multi_attr = 0; // attributes desired by multibyte
+ int mb_l = 1; // multi-byte byte length
+ int mb_c = 0; // decoded multi-byte character
+ bool mb_utf8 = false; // screen char is UTF-8 char
+ int u8cc[MAX_MCO]; // composing UTF-8 chars
+ int filler_lines; // nr of filler lines to be drawn
+ int filler_todo; // nr of filler lines still to do + 1
+ hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting
+ int change_start = MAXCOL; // first col of changed area
+ int change_end = -1; // last col of changed area
+ colnr_T trailcol = MAXCOL; // start of trailing spaces
+ colnr_T leadcol = 0; // start of leading spaces
+ bool in_multispace = false; // in multiple consecutive spaces
+ int multispace_pos = 0; // position in lcs-multispace string
+ bool need_showbreak = false; // overlong line, skip first x chars
+ int line_attr = 0; // attribute for the whole line
+ int line_attr_save;
+ int line_attr_lowprio = 0; // low-priority attribute for the line
+ int line_attr_lowprio_save;
+ int prev_c = 0; // previous Arabic character
+ int prev_c1 = 0; // first composing char for prev_c
+
+ bool search_attr_from_match = false; // if search_attr is from :match
+ bool has_decor = false; // this buffer has decoration
+ int win_col_offset = 0; // offset for window columns
+
+ char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext
+
+ bool area_active = false;
+
+ int cul_attr = 0; // set when 'cursorline' active
+ // 'cursorlineopt' has "screenline" and cursor is in this line
+ bool cul_screenline = false;
+ // margin columns for the screen line, needed for when 'cursorlineopt'
+ // contains "screenline"
+ int left_curline_col = 0;
+ int right_curline_col = 0;
+
+ // draw_state: items that are drawn in sequence:
+#define WL_START 0 // nothing done yet
+#define WL_CMDLINE (WL_START + 1) // cmdline window column
+#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn'
+#define WL_SIGN (WL_FOLD + 1) // column for signs
+#define WL_NR (WL_SIGN + 1) // line number
+#define WL_BRI (WL_NR + 1) // 'breakindent'
+#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff'
+#define WL_LINE (WL_SBR + 1) // text in the line
+ int draw_state = WL_START; // what to draw next
+
+ int syntax_flags = 0;
+ int syntax_seqnr = 0;
+ int prev_syntax_id = 0;
+ int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
+ bool is_concealing = false;
+ int boguscols = 0; ///< nonexistent columns added to
+ ///< force wrapping
+ int vcol_off = 0; ///< offset for concealed characters
+ int did_wcol = false;
+ int match_conc = 0; ///< cchar for match functions
+ int old_boguscols = 0;
+#define VCOL_HLC (vcol - vcol_off)
+#define FIX_FOR_BOGUSCOLS \
+ { \
+ n_extra += vcol_off; \
+ vcol -= vcol_off; \
+ vcol_off = 0; \
+ col -= boguscols; \
+ old_boguscols = boguscols; \
+ boguscols = 0; \
+ }
+
+ if (startrow > endrow) { // past the end already!
+ return startrow;
+ }
+
+ row = startrow;
+
+ buf_T *buf = wp->w_buffer;
+ bool end_fill = (lnum == buf->b_ml.ml_line_count + 1);
+
+ if (!number_only) {
+ // To speed up the loop below, set extra_check when there is linebreak,
+ // trailing white space and/or syntax processing to be done.
+ extra_check = wp->w_p_lbr;
+ if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow
+ && !has_fold && !end_fill) {
+ // Prepare for syntax highlighting in this line. When there is an
+ // error, stop syntax highlighting.
+ save_did_emsg = did_emsg;
+ did_emsg = false;
+ syntax_start(wp, lnum);
+ if (did_emsg) {
+ wp->w_s->b_syn_error = true;
+ } else {
+ did_emsg = save_did_emsg;
+ if (!wp->w_s->b_syn_slow) {
+ has_syntax = true;
+ extra_check = true;
+ }
+ }
+ }
+
+ has_decor = decor_redraw_line(buf, lnum - 1, &decor_state);
+
+ providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err);
+
+ if (*provider_err) {
+ provider_err_virt_text(lnum, *provider_err);
+ has_decor = true;
+ *provider_err = NULL;
+ }
+
+ if (has_decor) {
+ extra_check = true;
+ }
+
+ // Check for columns to display for 'colorcolumn'.
+ color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
+ if (color_cols != NULL) {
+ draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ }
+
+ if (wp->w_p_spell
+ && !has_fold
+ && !end_fill
+ && *wp->w_s->b_p_spl != NUL
+ && !GA_EMPTY(&wp->w_s->b_langp)
+ && *(char **)(wp->w_s->b_langp.ga_data) != NULL) {
+ // Prepare for spell checking.
+ has_spell = true;
+ extra_check = true;
+
+ // Get the start of the next line, so that words that wrap to the next
+ // line are found too: "et<line-break>al.".
+ // Trick: skip a few chars for C/shell/Vim comments
+ nextline[SPWORDLEN] = NUL;
+ if (lnum < wp->w_buffer->b_ml.ml_line_count) {
+ line = ml_get_buf(wp->w_buffer, lnum + 1, false);
+ spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
+ }
+
+ // When a word wrapped from the previous line the start of the current
+ // line is valid.
+ if (lnum == checked_lnum) {
+ cur_checked_col = checked_col;
+ }
+ checked_lnum = 0;
+
+ // When there was a sentence end in the previous line may require a
+ // word starting with capital in this line. In line 1 always check
+ // the first word.
+ if (lnum != capcol_lnum) {
+ cap_col = -1;
+ }
+ if (lnum == 1) {
+ cap_col = 0;
+ }
+ capcol_lnum = 0;
+ }
+
+ // handle Visual active in this window
+ if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
+ pos_T *top, *bot;
+
+ if (ltoreq(curwin->w_cursor, VIsual)) {
+ // Visual is after curwin->w_cursor
+ top = &curwin->w_cursor;
+ bot = &VIsual;
+ } else {
+ // Visual is before curwin->w_cursor
+ top = &VIsual;
+ bot = &curwin->w_cursor;
+ }
+ lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum);
+ if (VIsual_mode == Ctrl_V) {
+ // block mode
+ if (lnum_in_visual_area) {
+ fromcol = wp->w_old_cursor_fcol;
+ tocol = wp->w_old_cursor_lcol;
+ }
+ } else {
+ // non-block mode
+ if (lnum > top->lnum && lnum <= bot->lnum) {
+ fromcol = 0;
+ } else if (lnum == top->lnum) {
+ if (VIsual_mode == 'V') { // linewise
+ fromcol = 0;
+ } else {
+ getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL);
+ if (gchar_pos(top) == NUL) {
+ tocol = fromcol + 1;
+ }
+ }
+ }
+ if (VIsual_mode != 'V' && lnum == bot->lnum) {
+ if (*p_sel == 'e' && bot->col == 0
+ && bot->coladd == 0) {
+ fromcol = -10;
+ tocol = MAXCOL;
+ } else if (bot->col == MAXCOL) {
+ tocol = MAXCOL;
+ } else {
+ pos = *bot;
+ if (*p_sel == 'e') {
+ getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL);
+ } else {
+ getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol);
+ tocol++;
+ }
+ }
+ }
+ }
+
+ // Check if the char under the cursor should be inverted (highlighted).
+ if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin
+ && cursor_is_block_during_visual(*p_sel == 'e')) {
+ noinvcur = true;
+ }
+
+ // if inverting in this line set area_highlighting
+ if (fromcol >= 0) {
+ area_highlighting = true;
+ attr = win_hl_attr(wp, HLF_V);
+ }
+ // handle 'incsearch' and ":s///c" highlighting
+ } else if (highlight_match
+ && wp == curwin
+ && !has_fold
+ && lnum >= curwin->w_cursor.lnum
+ && lnum <= curwin->w_cursor.lnum + search_match_lines) {
+ if (lnum == curwin->w_cursor.lnum) {
+ getvcol(curwin, &(curwin->w_cursor),
+ (colnr_T *)&fromcol, NULL, NULL);
+ } else {
+ fromcol = 0;
+ }
+ if (lnum == curwin->w_cursor.lnum + search_match_lines) {
+ pos.lnum = lnum;
+ pos.col = search_match_endcol;
+ getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
+ }
+ // do at least one character; happens when past end of line
+ if (fromcol == tocol && search_match_endcol) {
+ tocol = fromcol + 1;
+ }
+ area_highlighting = true;
+ attr = win_hl_attr(wp, HLF_I);
+ }
+ }
+
+ int bg_attr = win_bg_attr(wp);
+
+ filler_lines = diff_check(wp, lnum);
+ if (filler_lines < 0) {
+ if (filler_lines == -1) {
+ if (diff_find_change(wp, lnum, &change_start, &change_end)) {
+ diff_hlf = HLF_ADD; // added line
+ } else if (change_start == 0) {
+ diff_hlf = HLF_TXD; // changed text
+ } else {
+ diff_hlf = HLF_CHD; // changed line
+ }
+ } else {
+ diff_hlf = HLF_ADD; // added line
+ }
+ filler_lines = 0;
+ area_highlighting = true;
+ }
+ VirtLines virt_lines = KV_INITIAL_VALUE;
+ int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines);
+ filler_lines += n_virt_lines;
+ if (lnum == wp->w_topline) {
+ filler_lines = wp->w_topfill;
+ n_virt_lines = MIN(n_virt_lines, filler_lines);
+ }
+ filler_todo = filler_lines;
+
+ // Cursor line highlighting for 'cursorline' in the current window.
+ if (lnum == wp->w_cursor.lnum) {
+ // Do not show the cursor line in the text when Visual mode is active,
+ // because it's not clear what is selected then.
+ if (wp->w_p_cul && !(wp == curwin && VIsual_active)
+ && wp->w_p_culopt_flags != CULOPT_NBR) {
+ cul_screenline = (wp->w_p_wrap
+ && (wp->w_p_culopt_flags & CULOPT_SCRLINE));
+ if (!cul_screenline) {
+ apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio);
+ } else {
+ margin_columns_win(wp, &left_curline_col, &right_curline_col);
+ }
+ area_highlighting = true;
+ }
+ }
+
+ SignTextAttrs sattrs[SIGN_SHOW_MAX]; // sign attributes for the sign column
+ int sign_num_attr = 0; // sign attribute for the number column
+ int sign_cul_attr = 0; // sign attribute for cursorline
+ CLEAR_FIELD(sattrs);
+ int num_signs = get_sign_attrs(buf, lnum, sattrs, &line_attr, &sign_num_attr, &sign_cul_attr);
+
+ // Highlight the current line in the quickfix window.
+ if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) {
+ line_attr = win_hl_attr(wp, HLF_QFL);
+ }
+
+ if (line_attr_lowprio || line_attr) {
+ area_highlighting = true;
+ }
+
+ if (cul_screenline) {
+ line_attr_save = line_attr;
+ line_attr_lowprio_save = line_attr_lowprio;
+ }
+
+ line = end_fill ? (char_u *)"" : ml_get_buf(wp->w_buffer, lnum, false);
+ ptr = line;
+
+ if (has_spell && !number_only) {
+ // For checking first word with a capital skip white space.
+ if (cap_col == 0) {
+ cap_col = (int)getwhitecols(line);
+ }
+
+ // To be able to spell-check over line boundaries copy the end of the
+ // current line into nextline[]. Above the start of the next line was
+ // copied to nextline[SPWORDLEN].
+ if (nextline[SPWORDLEN] == NUL) {
+ // No next line or it is empty.
+ nextlinecol = MAXCOL;
+ nextline_idx = 0;
+ } else {
+ v = (long)STRLEN(line);
+ if (v < SPWORDLEN) {
+ // Short line, use it completely and append the start of the
+ // next line.
+ nextlinecol = 0;
+ memmove(nextline, line, (size_t)v);
+ STRMOVE(nextline + v, nextline + SPWORDLEN);
+ nextline_idx = (int)v + 1;
+ } else {
+ // Long line, use only the last SPWORDLEN bytes.
+ nextlinecol = (int)v - SPWORDLEN;
+ memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512
+ nextline_idx = SPWORDLEN + 1;
+ }
+ }
+ }
+
+ if (wp->w_p_list && !has_fold && !end_fill) {
+ if (wp->w_p_lcs_chars.space
+ || wp->w_p_lcs_chars.multispace != NULL
+ || wp->w_p_lcs_chars.leadmultispace != NULL
+ || wp->w_p_lcs_chars.trail
+ || wp->w_p_lcs_chars.lead
+ || wp->w_p_lcs_chars.nbsp) {
+ extra_check = true;
+ }
+ // find start of trailing whitespace
+ if (wp->w_p_lcs_chars.trail) {
+ trailcol = (colnr_T)STRLEN(ptr);
+ while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) {
+ trailcol--;
+ }
+ trailcol += (colnr_T)(ptr - line);
+ }
+ // find end of leading whitespace
+ if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
+ leadcol = 0;
+ while (ascii_iswhite(ptr[leadcol])) {
+ leadcol++;
+ }
+ if (ptr[leadcol] == NUL) {
+ // in a line full of spaces all of them are treated as trailing
+ leadcol = (colnr_T)0;
+ } else {
+ // keep track of the first column not filled with spaces
+ leadcol += (colnr_T)(ptr - line) + 1;
+ }
+ }
+ }
+
+ // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
+ // first character to be displayed.
+ if (wp->w_p_wrap) {
+ v = wp->w_skipcol;
+ } else {
+ v = wp->w_leftcol;
+ }
+ if (v > 0 && !number_only) {
+ char_u *prev_ptr = ptr;
+ while (vcol < v && *ptr != NUL) {
+ c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL);
+ vcol += c;
+ prev_ptr = ptr;
+ MB_PTR_ADV(ptr);
+ }
+
+ // When:
+ // - 'cuc' is set, or
+ // - 'colorcolumn' is set, or
+ // - 'virtualedit' is set, or
+ // - the visual mode is active,
+ // the end of the line may be before the start of the displayed part.
+ if (vcol < v && (wp->w_p_cuc
+ || draw_color_col
+ || virtual_active()
+ || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
+ vcol = v;
+ }
+
+ // Handle a character that's not completely on the screen: Put ptr at
+ // that character but skip the first few screen characters.
+ if (vcol > v) {
+ vcol -= c;
+ ptr = prev_ptr;
+ // If the character fits on the screen, don't need to skip it.
+ // Except for a TAB.
+ if (utf_ptr2cells((char *)ptr) >= c || *ptr == TAB) {
+ n_skip = (int)(v - vcol);
+ }
+ }
+
+ // Adjust for when the inverted text is before the screen,
+ // and when the start of the inverted text is before the screen.
+ if (tocol <= vcol) {
+ fromcol = 0;
+ } else if (fromcol >= 0 && fromcol < vcol) {
+ fromcol = (int)vcol;
+ }
+
+ // When w_skipcol is non-zero, first line needs 'showbreak'
+ if (wp->w_p_wrap) {
+ need_showbreak = true;
+ }
+ // When spell checking a word we need to figure out the start of the
+ // word and if it's badly spelled or not.
+ if (has_spell) {
+ size_t len;
+ colnr_T linecol = (colnr_T)(ptr - line);
+ hlf_T spell_hlf = HLF_COUNT;
+
+ pos = wp->w_cursor;
+ wp->w_cursor.lnum = lnum;
+ wp->w_cursor.col = linecol;
+ len = spell_move_to(wp, FORWARD, true, true, &spell_hlf);
+
+ // spell_move_to() may call ml_get() and make "line" invalid
+ line = ml_get_buf(wp->w_buffer, lnum, false);
+ ptr = line + linecol;
+
+ if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
+ // no bad word found at line start, don't check until end of a
+ // word
+ spell_hlf = HLF_COUNT;
+ word_end = (int)(spell_to_word_end(ptr, wp) - line + 1);
+ } else {
+ // bad word found, use attributes until end of word
+ assert(len <= INT_MAX);
+ word_end = wp->w_cursor.col + (int)len + 1;
+
+ // Turn index into actual attributes.
+ if (spell_hlf != HLF_COUNT) {
+ spell_attr = highlight_attr[spell_hlf];
+ }
+ }
+ wp->w_cursor = pos;
+
+ // Need to restart syntax highlighting for this line.
+ if (has_syntax) {
+ syntax_start(wp, lnum);
+ }
+ }
+ }
+
+ // Correct highlighting for cursor that can't be disabled.
+ // Avoids having to check this for each character.
+ if (fromcol >= 0) {
+ if (noinvcur) {
+ if ((colnr_T)fromcol == wp->w_virtcol) {
+ // highlighting starts at cursor, let it start just after the
+ // cursor
+ fromcol_prev = fromcol;
+ fromcol = -1;
+ } else if ((colnr_T)fromcol < wp->w_virtcol) {
+ // restart highlighting after the cursor
+ fromcol_prev = wp->w_virtcol;
+ }
+ }
+ if (fromcol >= tocol) {
+ fromcol = -1;
+ }
+ }
+
+ if (!number_only && !has_fold && !end_fill) {
+ v = ptr - line;
+ area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v,
+ &line, &screen_search_hl, &search_attr,
+ &search_attr_from_match);
+ ptr = line + v; // "line" may have been updated
+ }
+
+ int off = 0; // Offset relative start of line
+ int col = 0; // Visual column on screen.
+ if (wp->w_p_rl) {
+ // Rightleft window: process the text in the normal direction, but put
+ // it in linebuf_char[off] from right to left. Start at the
+ // rightmost column of the window.
+ col = grid->cols - 1;
+ off += col;
+ }
+
+ // won't highlight after TERM_ATTRS_MAX columns
+ int term_attrs[TERM_ATTRS_MAX] = { 0 };
+ if (wp->w_buffer->terminal) {
+ terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs);
+ extra_check = true;
+ }
+
+ int sign_idx = 0;
+ // Repeat for the whole displayed line.
+ for (;;) {
+ int has_match_conc = 0; ///< match wants to conceal
+ int decor_conceal = 0;
+
+ bool did_decrement_ptr = false;
+
+ // Skip this quickly when working on the text.
+ if (draw_state != WL_LINE) {
+ if (cul_screenline) {
+ cul_attr = 0;
+ line_attr = line_attr_save;
+ line_attr_lowprio = line_attr_lowprio_save;
+ }
+
+ if (draw_state == WL_CMDLINE - 1 && n_extra == 0) {
+ draw_state = WL_CMDLINE;
+ if (cmdwin_type != 0 && wp == curwin) {
+ // Draw the cmdline character.
+ n_extra = 1;
+ c_extra = cmdwin_type;
+ c_final = NUL;
+ char_attr = win_hl_attr(wp, HLF_AT);
+ }
+ }
+
+ if (draw_state == WL_FOLD - 1 && n_extra == 0) {
+ int fdc = compute_foldcolumn(wp, 0);
+
+ draw_state = WL_FOLD;
+ if (fdc > 0) {
+ // Draw the 'foldcolumn'. Allocate a buffer, "extra" may
+ // already be in use.
+ xfree(p_extra_free);
+ p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1);
+ n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
+ p_extra_free[n_extra] = NUL;
+ p_extra = p_extra_free;
+ c_extra = NUL;
+ c_final = NUL;
+ if (use_cursor_line_sign(wp, lnum)) {
+ char_attr = win_hl_attr(wp, HLF_CLF);
+ } else {
+ char_attr = win_hl_attr(wp, HLF_FC);
+ }
+ }
+ }
+
+ // sign column, this is hit until sign_idx reaches count
+ if (draw_state == WL_SIGN - 1 && n_extra == 0) {
+ draw_state = WL_SIGN;
+ // Show the sign column when there are any signs in this buffer
+ if (wp->w_scwidth > 0) {
+ get_sign_display_info(false, wp, lnum, sattrs, row,
+ startrow, filler_lines, filler_todo,
+ &c_extra, &c_final, extra, sizeof(extra),
+ &p_extra, &n_extra, &char_attr, sign_idx,
+ sign_cul_attr);
+ sign_idx++;
+ if (sign_idx < wp->w_scwidth) {
+ draw_state = WL_SIGN - 1;
+ } else {
+ sign_idx = 0;
+ }
+ }
+ }
+
+ if (draw_state == WL_NR - 1 && n_extra == 0) {
+ draw_state = WL_NR;
+ // Display the absolute or relative line number. After the
+ // first fill with blanks when the 'n' flag isn't in 'cpo'
+ if ((wp->w_p_nu || wp->w_p_rnu)
+ && (row == startrow + filler_lines
+ || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) {
+ // If 'signcolumn' is set to 'number' and a sign is present
+ // in 'lnum', then display the sign instead of the line
+ // number.
+ if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) {
+ get_sign_display_info(true, wp, lnum, sattrs, row,
+ startrow, filler_lines, filler_todo,
+ &c_extra, &c_final, extra, sizeof(extra),
+ &p_extra, &n_extra, &char_attr, sign_idx,
+ sign_cul_attr);
+ } else {
+ // Draw the line number (empty space after wrapping).
+ if (row == startrow + filler_lines) {
+ get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra));
+ if (wp->w_skipcol > 0) {
+ for (p_extra = extra; *p_extra == ' '; p_extra++) {
+ *p_extra = '-';
+ }
+ }
+ if (wp->w_p_rl) { // reverse line numbers
+ // like rl_mirror(), but keep the space at the end
+ char_u *p2 = (char_u *)skipwhite((char *)extra);
+ p2 = skiptowhite(p2) - 1;
+ for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) {
+ const char_u t = *p1;
+ *p1 = *p2;
+ *p2 = t;
+ }
+ }
+ p_extra = extra;
+ c_extra = NUL;
+ } else {
+ c_extra = ' ';
+ }
+ c_final = NUL;
+ n_extra = number_width(wp) + 1;
+ if (sign_num_attr > 0) {
+ char_attr = sign_num_attr;
+ } else {
+ char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines);
+ }
+ }
+ }
+ }
+
+ if (draw_state == WL_NR && n_extra == 0) {
+ win_col_offset = off;
+ }
+
+ if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
+ && n_extra == 0 && *get_showbreak_value(wp) != NUL) {
+ // draw indent after showbreak value
+ draw_state = WL_BRI;
+ } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) {
+ // after the showbreak, draw the breakindent
+ draw_state = WL_BRI - 1;
+ }
+
+ // draw 'breakindent': indent wrapped text accordingly
+ if (draw_state == WL_BRI - 1 && n_extra == 0) {
+ draw_state = WL_BRI;
+ // if need_showbreak is set, breakindent also applies
+ if (wp->w_p_bri && (row != startrow || need_showbreak)
+ && filler_lines == 0) {
+ char_attr = 0;
+
+ if (diff_hlf != (hlf_T)0) {
+ char_attr = win_hl_attr(wp, (int)diff_hlf);
+ }
+ p_extra = NULL;
+ c_extra = ' ';
+ c_final = NUL;
+ n_extra =
+ get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false));
+ if (row == startrow) {
+ n_extra -= win_col_off2(wp);
+ if (n_extra < 0) {
+ n_extra = 0;
+ }
+ }
+ if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
+ need_showbreak = false;
+ }
+ // Correct end of highlighted area for 'breakindent',
+ // required wen 'linebreak' is also set.
+ if (tocol == vcol) {
+ tocol += n_extra;
+ }
+ }
+ }
+
+ if (draw_state == WL_SBR - 1 && n_extra == 0) {
+ draw_state = WL_SBR;
+ if (filler_todo > filler_lines - n_virt_lines) {
+ // TODO(bfredl): check this doesn't inhibit TUI-style
+ // clear-to-end-of-line.
+ c_extra = ' ';
+ c_final = NUL;
+ if (wp->w_p_rl) {
+ n_extra = col + 1;
+ } else {
+ n_extra = grid->cols - col;
+ }
+ char_attr = 0;
+ } else if (filler_todo > 0) {
+ // draw "deleted" diff line(s)
+ if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
+ c_extra = '-';
+ c_final = NUL;
+ } else {
+ c_extra = wp->w_p_fcs_chars.diff;
+ c_final = NUL;
+ }
+ if (wp->w_p_rl) {
+ n_extra = col + 1;
+ } else {
+ n_extra = grid->cols - col;
+ }
+ char_attr = win_hl_attr(wp, HLF_DED);
+ }
+ char_u *const sbr = get_showbreak_value(wp);
+ if (*sbr != NUL && need_showbreak) {
+ // Draw 'showbreak' at the start of each broken line.
+ p_extra = sbr;
+ c_extra = NUL;
+ c_final = NUL;
+ n_extra = (int)STRLEN(sbr);
+ char_attr = win_hl_attr(wp, HLF_AT);
+ if (wp->w_skipcol == 0 || !wp->w_p_wrap) {
+ need_showbreak = false;
+ }
+ vcol_sbr = vcol + mb_charlen(sbr);
+ // Correct end of highlighted area for 'showbreak',
+ // required when 'linebreak' is also set.
+ if (tocol == vcol) {
+ tocol += n_extra;
+ }
+ // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
+ if (cul_attr) {
+ char_attr = hl_combine_attr(cul_attr, char_attr);
+ }
+ }
+ }
+
+ if (draw_state == WL_LINE - 1 && n_extra == 0) {
+ sign_idx = 0;
+ draw_state = WL_LINE;
+
+ if (has_decor && row == startrow + filler_lines) {
+ // hide virt_text on text hidden by 'nowrap'
+ decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state);
+ }
+
+ if (saved_n_extra) {
+ // Continue item from end of wrapped line.
+ n_extra = saved_n_extra;
+ c_extra = saved_c_extra;
+ c_final = saved_c_final;
+ p_extra = saved_p_extra;
+ char_attr = saved_char_attr;
+ } else {
+ char_attr = 0;
+ }
+ }
+ }
+
+ if (cul_screenline && draw_state == WL_LINE
+ && vcol >= left_curline_col
+ && vcol < right_curline_col) {
+ apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio);
+ }
+
+ // When still displaying '$' of change command, stop at cursor
+ if (((dollar_vcol >= 0
+ && wp == curwin
+ && lnum == wp->w_cursor.lnum
+ && vcol >= (long)wp->w_virtcol)
+ || (number_only && draw_state > WL_NR))
+ && filler_todo <= 0) {
+ draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
+ grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false);
+ // Pretend we have finished updating the window. Except when
+ // 'cursorcolumn' is set.
+ if (wp->w_p_cuc) {
+ row = wp->w_cline_row + wp->w_cline_height;
+ } else {
+ row = grid->rows;
+ }
+ break;
+ }
+
+ if (draw_state == WL_LINE
+ && has_fold
+ && col == win_col_offset
+ && n_extra == 0
+ && row == startrow) {
+ char_attr = win_hl_attr(wp, HLF_FL);
+
+ linenr_T lnume = lnum + foldinfo.fi_lines - 1;
+ memset(buf_fold, ' ', FOLD_TEXT_LEN);
+ p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold);
+ n_extra = (int)STRLEN(p_extra);
+
+ if (p_extra != buf_fold) {
+ xfree(p_extra_free);
+ p_extra_free = p_extra;
+ }
+ c_extra = NUL;
+ c_final = NUL;
+ p_extra[n_extra] = NUL;
+ }
+
+ if (draw_state == WL_LINE
+ && has_fold
+ && col < grid->cols
+ && n_extra == 0
+ && row == startrow) {
+ // fill rest of line with 'fold'
+ c_extra = wp->w_p_fcs_chars.fold;
+ c_final = NUL;
+
+ n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col);
+ }
+
+ if (draw_state == WL_LINE
+ && has_fold
+ && col >= grid->cols
+ && n_extra != 0
+ && row == startrow) {
+ // Truncate the folding.
+ n_extra = 0;
+ }
+
+ if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
+ // handle Visual or match highlighting in this line
+ if (vcol == fromcol
+ || (vcol + 1 == fromcol && n_extra == 0
+ && utf_ptr2cells((char *)ptr) > 1)
+ || ((int)vcol_prev == fromcol_prev
+ && vcol_prev < vcol // not at margin
+ && vcol < tocol)) {
+ area_attr = attr; // start highlighting
+ if (area_highlighting) {
+ area_active = true;
+ }
+ } else if (area_attr != 0 && (vcol == tocol
+ || (noinvcur
+ && (colnr_T)vcol == wp->w_virtcol))) {
+ area_attr = 0; // stop highlighting
+ area_active = false;
+ }
+
+ if (!n_extra) {
+ // Check for start/end of 'hlsearch' and other matches.
+ // After end, check for start/end of next match.
+ // When another match, have to check for start again.
+ v = (ptr - line);
+ search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &screen_search_hl,
+ &has_match_conc,
+ &match_conc, lcs_eol_one, &search_attr_from_match);
+ ptr = line + v; // "line" may have been changed
+
+ // Do not allow a conceal over EOL otherwise EOL will be missed
+ // and bad things happen.
+ if (*ptr == NUL) {
+ has_match_conc = 0;
+ }
+ }
+
+ if (diff_hlf != (hlf_T)0) {
+ if (diff_hlf == HLF_CHD && ptr - line >= change_start
+ && n_extra == 0) {
+ diff_hlf = HLF_TXD; // changed text
+ }
+ if (diff_hlf == HLF_TXD && ptr - line > change_end
+ && n_extra == 0) {
+ diff_hlf = HLF_CHD; // changed line
+ }
+ line_attr = win_hl_attr(wp, (int)diff_hlf);
+ // Overlay CursorLine onto diff-mode highlight.
+ if (cul_attr) {
+ line_attr = 0 != line_attr_lowprio // Low-priority CursorLine
+ ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr),
+ hl_get_underline())
+ : hl_combine_attr(line_attr, cul_attr);
+ }
+ }
+
+ // Decide which of the highlight attributes to use.
+ attr_pri = true;
+
+ if (area_attr != 0) {
+ char_attr = hl_combine_attr(line_attr, area_attr);
+ if (!highlight_match) {
+ // let search highlight show in Visual area if possible
+ char_attr = hl_combine_attr(search_attr, char_attr);
+ }
+ } else if (search_attr != 0) {
+ char_attr = hl_combine_attr(line_attr, search_attr);
+ } else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL)
+ || vcol < fromcol || vcol_prev < fromcol_prev
+ || vcol >= tocol)) {
+ // Use line_attr when not in the Visual or 'incsearch' area
+ // (area_attr may be 0 when "noinvcur" is set).
+ char_attr = line_attr;
+ } else {
+ attr_pri = false;
+ if (has_syntax) {
+ char_attr = syntax_attr;
+ } else {
+ char_attr = 0;
+ }
+ }
+ }
+
+ // Get the next character to put on the screen.
+ //
+ // The "p_extra" points to the extra stuff that is inserted to
+ // represent special characters (non-printable stuff) and other
+ // things. When all characters are the same, c_extra is used.
+ // If c_final is set, it will compulsorily be used at the end.
+ // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past
+ // "p_extra[n_extra]".
+ // For the '$' of the 'list' option, n_extra == 1, p_extra == "".
+ if (n_extra > 0) {
+ if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) {
+ c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra;
+ mb_c = c; // doesn't handle non-utf-8 multi-byte!
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false;
+ }
+ } else {
+ assert(p_extra != NULL);
+ c = *p_extra;
+ mb_c = c;
+ // If the UTF-8 character is more than one byte:
+ // Decode it into "mb_c".
+ mb_l = utfc_ptr2len((char *)p_extra);
+ mb_utf8 = false;
+ if (mb_l > n_extra) {
+ mb_l = 1;
+ } else if (mb_l > 1) {
+ mb_c = utfc_ptr2char(p_extra, u8cc);
+ mb_utf8 = true;
+ c = 0xc0;
+ }
+ if (mb_l == 0) { // at the NUL at end-of-line
+ mb_l = 1;
+ }
+
+ // If a double-width char doesn't fit display a '>' in the last column.
+ if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1))
+ && utf_char2cells(mb_c) == 2) {
+ c = '>';
+ mb_c = c;
+ mb_l = 1;
+ (void)mb_l;
+ multi_attr = win_hl_attr(wp, HLF_AT);
+
+ if (cul_attr) {
+ multi_attr = 0 != line_attr_lowprio
+ ? hl_combine_attr(cul_attr, multi_attr)
+ : hl_combine_attr(multi_attr, cul_attr);
+ }
+
+ // put the pointer back to output the double-width
+ // character at the start of the next line.
+ n_extra++;
+ p_extra--;
+ } else {
+ n_extra -= mb_l - 1;
+ p_extra += mb_l - 1;
+ }
+ p_extra++;
+ }
+ n_extra--;
+ } else if (foldinfo.fi_lines > 0) {
+ // skip writing the buffer line itself
+ c = NUL;
+ XFREE_CLEAR(p_extra_free);
+ } else {
+ int c0;
+
+ XFREE_CLEAR(p_extra_free);
+
+ // Get a character from the line itself.
+ c0 = c = *ptr;
+ mb_c = c;
+ // If the UTF-8 character is more than one byte: Decode it
+ // into "mb_c".
+ mb_l = utfc_ptr2len((char *)ptr);
+ mb_utf8 = false;
+ if (mb_l > 1) {
+ mb_c = utfc_ptr2char(ptr, u8cc);
+ // Overlong encoded ASCII or ASCII with composing char
+ // is displayed normally, except a NUL.
+ if (mb_c < 0x80) {
+ c0 = c = mb_c;
+ }
+ mb_utf8 = true;
+
+ // At start of the line we can have a composing char.
+ // Draw it as a space with a composing char.
+ if (utf_iscomposing(mb_c)) {
+ int i;
+
+ for (i = MAX_MCO - 1; i > 0; i--) {
+ u8cc[i] = u8cc[i - 1];
+ }
+ u8cc[0] = mb_c;
+ mb_c = ' ';
+ }
+ }
+
+ if ((mb_l == 1 && c >= 0x80)
+ || (mb_l >= 1 && mb_c == 0)
+ || (mb_l > 1 && (!vim_isprintc(mb_c)))) {
+ // Illegal UTF-8 byte: display as <xx>.
+ // Non-BMP character : display as ? or fullwidth ?.
+ transchar_hex((char *)extra, mb_c);
+ if (wp->w_p_rl) { // reverse
+ rl_mirror(extra);
+ }
+
+ p_extra = extra;
+ c = *p_extra;
+ mb_c = mb_ptr2char_adv((const char_u **)&p_extra);
+ mb_utf8 = (c >= 0x80);
+ n_extra = (int)STRLEN(p_extra);
+ c_extra = NUL;
+ c_final = NUL;
+ if (area_attr == 0 && search_attr == 0) {
+ n_attr = n_extra + 1;
+ extra_attr = win_hl_attr(wp, HLF_8);
+ saved_attr2 = char_attr; // save current attr
+ }
+ } else if (mb_l == 0) { // at the NUL at end-of-line
+ mb_l = 1;
+ } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) {
+ // Do Arabic shaping.
+ int pc, pc1, nc;
+ int pcc[MAX_MCO];
+
+ // The idea of what is the previous and next
+ // character depends on 'rightleft'.
+ if (wp->w_p_rl) {
+ pc = prev_c;
+ pc1 = prev_c1;
+ nc = utf_ptr2char((char *)ptr + mb_l);
+ prev_c1 = u8cc[0];
+ } else {
+ pc = utfc_ptr2char(ptr + mb_l, pcc);
+ nc = prev_c;
+ pc1 = pcc[0];
+ }
+ prev_c = mb_c;
+
+ mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc);
+ } else {
+ prev_c = mb_c;
+ }
+ // If a double-width char doesn't fit display a '>' in the
+ // last column; the character is displayed at the start of the
+ // next line.
+ if ((wp->w_p_rl ? (col <= 0) :
+ (col >= grid->cols - 1))
+ && utf_char2cells(mb_c) == 2) {
+ c = '>';
+ mb_c = c;
+ mb_utf8 = false;
+ mb_l = 1;
+ multi_attr = win_hl_attr(wp, HLF_AT);
+ // Put pointer back so that the character will be
+ // displayed at the start of the next line.
+ ptr--;
+ did_decrement_ptr = true;
+ } else if (*ptr != NUL) {
+ ptr += mb_l - 1;
+ }
+
+ // If a double-width char doesn't fit at the left side display a '<' in
+ // the first column. Don't do this for unprintable characters.
+ if (n_skip > 0 && mb_l > 1 && n_extra == 0) {
+ n_extra = 1;
+ c_extra = MB_FILLER_CHAR;
+ c_final = NUL;
+ c = ' ';
+ if (area_attr == 0 && search_attr == 0) {
+ n_attr = n_extra + 1;
+ extra_attr = win_hl_attr(wp, HLF_AT);
+ saved_attr2 = char_attr; // save current attr
+ }
+ mb_c = c;
+ mb_utf8 = false;
+ mb_l = 1;
+ }
+ ptr++;
+
+ if (extra_check) {
+ bool can_spell = true;
+
+ // Get syntax attribute, unless still at the start of the line
+ // (double-wide char that doesn't fit).
+ v = (ptr - line);
+ if (has_syntax && v > 0) {
+ // Get the syntax attribute for the character. If there
+ // is an error, disable syntax highlighting.
+ save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ syntax_attr = get_syntax_attr((colnr_T)v - 1,
+ has_spell ? &can_spell : NULL, false);
+
+ if (did_emsg) {
+ wp->w_s->b_syn_error = true;
+ has_syntax = false;
+ } else {
+ did_emsg = save_did_emsg;
+ }
+
+ if (wp->w_s->b_syn_slow) {
+ has_syntax = false;
+ }
+
+ // Need to get the line again, a multi-line regexp may
+ // have made it invalid.
+ line = ml_get_buf(wp->w_buffer, lnum, false);
+ ptr = line + v;
+
+ if (!attr_pri) {
+ if (cul_attr) {
+ char_attr = 0 != line_attr_lowprio
+ ? hl_combine_attr(cul_attr, syntax_attr)
+ : hl_combine_attr(syntax_attr, cul_attr);
+ } else {
+ char_attr = syntax_attr;
+ }
+ } else {
+ char_attr = hl_combine_attr(syntax_attr, char_attr);
+ }
+ // no concealing past the end of the line, it interferes
+ // with line highlighting.
+ if (c == NUL) {
+ syntax_flags = 0;
+ } else {
+ syntax_flags = get_syntax_info(&syntax_seqnr);
+ }
+ } else if (!attr_pri) {
+ char_attr = 0;
+ }
+
+ // Check spelling (unless at the end of the line).
+ // Only do this when there is no syntax highlighting, the
+ // @Spell cluster is not used or the current syntax item
+ // contains the @Spell cluster.
+ v = (ptr - line);
+ if (has_spell && v >= word_end && v > cur_checked_col) {
+ spell_attr = 0;
+ if (!attr_pri) {
+ char_attr = syntax_attr;
+ }
+ if (c != 0 && (!has_syntax || can_spell)) {
+ char_u *prev_ptr;
+ char_u *p;
+ int len;
+ hlf_T spell_hlf = HLF_COUNT;
+ prev_ptr = ptr - mb_l;
+ v -= mb_l - 1;
+
+ // Use nextline[] if possible, it has the start of the
+ // next line concatenated.
+ if ((prev_ptr - line) - nextlinecol >= 0) {
+ p = nextline + ((prev_ptr - line) - nextlinecol);
+ } else {
+ p = prev_ptr;
+ }
+ cap_col -= (int)(prev_ptr - line);
+ size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange);
+ assert(tmplen <= INT_MAX);
+ len = (int)tmplen;
+ word_end = (int)v + len;
+
+ // In Insert mode only highlight a word that
+ // doesn't touch the cursor.
+ if (spell_hlf != HLF_COUNT
+ && (State & MODE_INSERT)
+ && wp->w_cursor.lnum == lnum
+ && wp->w_cursor.col >=
+ (colnr_T)(prev_ptr - line)
+ && wp->w_cursor.col < (colnr_T)word_end) {
+ spell_hlf = HLF_COUNT;
+ spell_redraw_lnum = lnum;
+ }
+
+ if (spell_hlf == HLF_COUNT && p != prev_ptr
+ && (p - nextline) + len > nextline_idx) {
+ // Remember that the good word continues at the
+ // start of the next line.
+ checked_lnum = lnum + 1;
+ checked_col = (int)((p - nextline) + len - nextline_idx);
+ }
+
+ // Turn index into actual attributes.
+ if (spell_hlf != HLF_COUNT) {
+ spell_attr = highlight_attr[spell_hlf];
+ }
+
+ if (cap_col > 0) {
+ if (p != prev_ptr
+ && (p - nextline) + cap_col >= nextline_idx) {
+ // Remember that the word in the next line
+ // must start with a capital.
+ capcol_lnum = lnum + 1;
+ cap_col = (int)((p - nextline) + cap_col
+ - nextline_idx);
+ } else {
+ // Compute the actual column.
+ cap_col += (int)(prev_ptr - line);
+ }
+ }
+ }
+ }
+ if (spell_attr != 0) {
+ if (!attr_pri) {
+ char_attr = hl_combine_attr(char_attr, spell_attr);
+ } else {
+ char_attr = hl_combine_attr(spell_attr, char_attr);
+ }
+ }
+
+ if (wp->w_buffer->terminal) {
+ char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
+ }
+
+ if (has_decor && v > 0) {
+ bool selected = (area_active || (area_highlighting && noinvcur
+ && (colnr_T)vcol == wp->w_virtcol));
+ int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off,
+ selected, &decor_state);
+ if (extmark_attr != 0) {
+ if (!attr_pri) {
+ char_attr = hl_combine_attr(char_attr, extmark_attr);
+ } else {
+ char_attr = hl_combine_attr(extmark_attr, char_attr);
+ }
+ }
+
+ decor_conceal = decor_state.conceal;
+ if (decor_conceal && decor_state.conceal_char) {
+ decor_conceal = 2; // really??
+ }
+ }
+
+ // Found last space before word: check for line break.
+ if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
+ && !vim_isbreak((int)(*ptr))) {
+ int mb_off = utf_head_off(line, ptr - 1);
+ char_u *p = ptr - (mb_off + 1);
+ // TODO(neovim): is passing p for start of the line OK?
+ n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1;
+
+ // We have just drawn the showbreak value, no need to add
+ // space for it again.
+ if (vcol == vcol_sbr) {
+ n_extra -= mb_charlen(get_showbreak_value(wp));
+ if (n_extra < 0) {
+ n_extra = 0;
+ }
+ }
+
+ if (c == TAB && n_extra + col > grid->cols) {
+ n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array) - 1;
+ }
+ c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
+ c_final = NUL;
+ if (ascii_iswhite(c)) {
+ if (c == TAB) {
+ // See "Tab alignment" below.
+ FIX_FOR_BOGUSCOLS;
+ }
+ if (!wp->w_p_list) {
+ c = ' ';
+ }
+ }
+ }
+
+ in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
+
+ // 'list': Change char 160 to 'nbsp' and space to 'space'.
+ // But not when the character is followed by a composing
+ // character (use mb_l to check that).
+ if (wp->w_p_list
+ && ((((c == 160 && mb_l == 1)
+ || (mb_utf8
+ && ((mb_c == 160 && mb_l == 2)
+ || (mb_c == 0x202f && mb_l == 3))))
+ && wp->w_p_lcs_chars.nbsp)
+ || (c == ' '
+ && mb_l == 1
+ && (wp->w_p_lcs_chars.space
+ || (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
+ && ptr - line >= leadcol
+ && ptr - line <= trailcol))) {
+ if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
+ c = wp->w_p_lcs_chars.multispace[multispace_pos++];
+ if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ } else {
+ c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
+ }
+ n_attr = 1;
+ extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = char_attr; // save current attr
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false;
+ }
+ }
+
+ if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol)
+ || (leadcol != 0 && ptr < line + leadcol))) {
+ if (leadcol != 0 && in_multispace && ptr < line + leadcol
+ && wp->w_p_lcs_chars.leadmultispace != NULL) {
+ c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
+ if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
+ c = wp->w_p_lcs_chars.trail;
+ } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
+ c = wp->w_p_lcs_chars.lead;
+ } else if (leadcol != 0 && wp->w_p_lcs_chars.space) {
+ c = wp->w_p_lcs_chars.space;
+ }
+
+ n_attr = 1;
+ extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = char_attr; // save current attr
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false;
+ }
+ }
+ }
+
+ // Handling of non-printable characters.
+ if (!vim_isprintc(c)) {
+ // when getting a character from the file, we may have to
+ // turn it into something else on the way to putting it on the screen.
+ if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
+ int tab_len = 0;
+ long vcol_adjusted = vcol; // removed showbreak length
+ char_u *const sbr = get_showbreak_value(wp);
+
+ // Only adjust the tab_len, when at the first column after the
+ // showbreak value was drawn.
+ if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) {
+ vcol_adjusted = vcol - mb_charlen(sbr);
+ }
+ // tab amount depends on current column
+ tab_len = tabstop_padding((colnr_T)vcol_adjusted,
+ wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array) - 1;
+
+ if (!wp->w_p_lbr || !wp->w_p_list) {
+ n_extra = tab_len;
+ } else {
+ char_u *p;
+ int i;
+ int saved_nextra = n_extra;
+
+ if (vcol_off > 0) {
+ // there are characters to conceal
+ tab_len += vcol_off;
+ }
+ // boguscols before FIX_FOR_BOGUSCOLS macro from above.
+ if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0
+ && n_extra > tab_len) {
+ tab_len += n_extra - tab_len;
+ }
+
+ // If n_extra > 0, it gives the number of chars
+ // to use for a tab, else we need to calculate the width
+ // for a tab.
+ int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2));
+ if (wp->w_p_lcs_chars.tab3) {
+ len += utf_char2len(wp->w_p_lcs_chars.tab3);
+ }
+ if (n_extra > 0) {
+ len += n_extra - tab_len;
+ }
+ c = wp->w_p_lcs_chars.tab1;
+ p = xmalloc((size_t)len + 1);
+ memset(p, ' ', (size_t)len);
+ p[len] = NUL;
+ xfree(p_extra_free);
+ p_extra_free = p;
+ for (i = 0; i < tab_len; i++) {
+ if (*p == NUL) {
+ tab_len = i;
+ break;
+ }
+ int lcs = wp->w_p_lcs_chars.tab2;
+
+ // if tab3 is given, use it for the last char
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
+ }
+ p += utf_char2bytes(lcs, (char *)p);
+ n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
+ }
+ p_extra = p_extra_free;
+
+ // n_extra will be increased by FIX_FOX_BOGUSCOLS
+ // macro below, so need to adjust for that here
+ if (vcol_off > 0) {
+ n_extra -= vcol_off;
+ }
+ }
+
+ {
+ int vc_saved = vcol_off;
+
+ // Tab alignment should be identical regardless of
+ // 'conceallevel' value. So tab compensates of all
+ // previous concealed characters, and thus resets
+ // vcol_off and boguscols accumulated so far in the
+ // line. Note that the tab can be longer than
+ // 'tabstop' when there are concealed characters.
+ FIX_FOR_BOGUSCOLS;
+
+ // Make sure, the highlighting for the tab char will be
+ // correctly set further below (effectively reverts the
+ // FIX_FOR_BOGSUCOLS macro).
+ if (n_extra == tab_len + vc_saved && wp->w_p_list
+ && wp->w_p_lcs_chars.tab1) {
+ tab_len += vc_saved;
+ }
+ }
+
+ mb_utf8 = false; // don't draw as UTF-8
+ if (wp->w_p_list) {
+ c = (n_extra == 0 && wp->w_p_lcs_chars.tab3)
+ ? wp->w_p_lcs_chars.tab3
+ : wp->w_p_lcs_chars.tab1;
+ if (wp->w_p_lbr) {
+ c_extra = NUL; // using p_extra from above
+ } else {
+ c_extra = wp->w_p_lcs_chars.tab2;
+ }
+ c_final = wp->w_p_lcs_chars.tab3;
+ n_attr = tab_len + 1;
+ extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = char_attr; // save current attr
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ }
+ } else {
+ c_final = NUL;
+ c_extra = ' ';
+ c = ' ';
+ }
+ } else if (c == NUL
+ && (wp->w_p_list
+ || ((fromcol >= 0 || fromcol_prev >= 0)
+ && tocol > vcol
+ && VIsual_mode != Ctrl_V
+ && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))
+ && !(noinvcur
+ && lnum == wp->w_cursor.lnum
+ && (colnr_T)vcol == wp->w_virtcol)))
+ && lcs_eol_one > 0) {
+ // Display a '$' after the line or highlight an extra
+ // character if the line break is included.
+ // For a diff line the highlighting continues after the "$".
+ if (diff_hlf == (hlf_T)0
+ && line_attr == 0
+ && line_attr_lowprio == 0) {
+ // In virtualedit, visual selections may extend beyond end of line
+ if (area_highlighting && virtual_active()
+ && tocol != MAXCOL && vcol < tocol) {
+ n_extra = 0;
+ } else {
+ p_extra = at_end_str;
+ n_extra = 1;
+ c_extra = NUL;
+ c_final = NUL;
+ }
+ }
+ if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
+ c = wp->w_p_lcs_chars.eol;
+ } else {
+ c = ' ';
+ }
+ lcs_eol_one = -1;
+ ptr--; // put it back at the NUL
+ extra_attr = win_hl_attr(wp, HLF_AT);
+ n_attr = 1;
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false; // don't draw as UTF-8
+ }
+ } else if (c != NUL) {
+ p_extra = transchar_buf(wp->w_buffer, c);
+ if (n_extra == 0) {
+ n_extra = byte2cells(c) - 1;
+ }
+ if ((dy_flags & DY_UHEX) && wp->w_p_rl) {
+ rl_mirror(p_extra); // reverse "<12>"
+ }
+ c_extra = NUL;
+ c_final = NUL;
+ if (wp->w_p_lbr) {
+ char_u *p;
+
+ c = *p_extra;
+ p = xmalloc((size_t)n_extra + 1);
+ memset(p, ' ', (size_t)n_extra);
+ STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); // NOLINT(runtime/printf)
+ p[n_extra] = NUL;
+ xfree(p_extra_free);
+ p_extra_free = p_extra = p;
+ } else {
+ n_extra = byte2cells(c) - 1;
+ c = *p_extra++;
+ }
+ n_attr = n_extra + 1;
+ extra_attr = win_hl_attr(wp, HLF_8);
+ saved_attr2 = char_attr; // save current attr
+ mb_utf8 = false; // don't draw as UTF-8
+ } else if (VIsual_active
+ && (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
+ && virtual_active()
+ && tocol != MAXCOL
+ && vcol < tocol
+ && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) {
+ c = ' ';
+ ptr--; // put it back at the NUL
+ }
+ }
+
+ if (wp->w_p_cole > 0
+ && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp))
+ && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0)
+ && !(lnum_in_visual_area && vim_strchr((char *)wp->w_p_cocu, 'v') == NULL)) {
+ char_attr = conceal_attr;
+ if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0)
+ || has_match_conc > 1 || decor_conceal > 1)
+ && (syn_get_sub_char() != NUL
+ || (has_match_conc && match_conc)
+ || (decor_conceal && decor_state.conceal_char)
+ || wp->w_p_cole == 1)
+ && wp->w_p_cole != 3) {
+ // First time at this concealed item: display one
+ // character.
+ if (has_match_conc && match_conc) {
+ c = match_conc;
+ } else if (decor_conceal && decor_state.conceal_char) {
+ c = decor_state.conceal_char;
+ if (decor_state.conceal_attr) {
+ char_attr = decor_state.conceal_attr;
+ }
+ } else if (syn_get_sub_char() != NUL) {
+ c = syn_get_sub_char();
+ } else if (wp->w_p_lcs_chars.conceal != NUL) {
+ c = wp->w_p_lcs_chars.conceal;
+ } else {
+ c = ' ';
+ }
+
+ prev_syntax_id = syntax_seqnr;
+
+ if (n_extra > 0) {
+ vcol_off += n_extra;
+ }
+ vcol += n_extra;
+ if (wp->w_p_wrap && n_extra > 0) {
+ if (wp->w_p_rl) {
+ col -= n_extra;
+ boguscols -= n_extra;
+ } else {
+ boguscols += n_extra;
+ col += n_extra;
+ }
+ }
+ n_extra = 0;
+ n_attr = 0;
+ } else if (n_skip == 0) {
+ is_concealing = true;
+ n_skip = 1;
+ }
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false; // don't draw as UTF-8
+ }
+ } else {
+ prev_syntax_id = 0;
+ is_concealing = false;
+ }
+
+ if (n_skip > 0 && did_decrement_ptr) {
+ // not showing the '>', put pointer back to avoid getting stuck
+ ptr++;
+ }
+ } // end of printing from buffer content
+
+ // In the cursor line and we may be concealing characters: correct
+ // the cursor column when we reach its position.
+ if (!did_wcol && draw_state == WL_LINE
+ && wp == curwin && lnum == wp->w_cursor.lnum
+ && conceal_cursor_line(wp)
+ && (int)wp->w_virtcol <= vcol + n_skip) {
+ if (wp->w_p_rl) {
+ wp->w_wcol = grid->cols - col + boguscols - 1;
+ } else {
+ wp->w_wcol = col - boguscols;
+ }
+ wp->w_wrow = row;
+ did_wcol = true;
+ wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
+ }
+
+ // Don't override visual selection highlighting.
+ if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) {
+ char_attr = hl_combine_attr(char_attr, extra_attr);
+ }
+
+ // Handle the case where we are in column 0 but not on the first
+ // character of the line and the user wants us to show us a
+ // special character (via 'listchars' option "precedes:<char>".
+ if (lcs_prec_todo != NUL
+ && wp->w_p_list
+ && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
+ && filler_todo <= 0
+ && draw_state > WL_NR
+ && c != NUL) {
+ c = wp->w_p_lcs_chars.prec;
+ lcs_prec_todo = NUL;
+ if (utf_char2cells(mb_c) > 1) {
+ // Double-width character being overwritten by the "precedes"
+ // character, need to fill up half the character.
+ c_extra = MB_FILLER_CHAR;
+ c_final = NUL;
+ n_extra = 1;
+ n_attr = 2;
+ extra_attr = win_hl_attr(wp, HLF_AT);
+ }
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false; // don't draw as UTF-8
+ }
+ saved_attr3 = char_attr; // save current attr
+ char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
+ n_attr3 = 1;
+ }
+
+ // At end of the text line or just after the last character.
+ if (c == NUL && eol_hl_off == 0) {
+ // flag to indicate whether prevcol equals startcol of search_hl or
+ // one of the matches
+ bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl,
+ (long)(ptr - line) - 1);
+
+ // Invert at least one char, used for Visual and empty line or
+ // highlight match at end of line. If it's beyond the last
+ // char on the screen, just overwrite that one (tricky!) Not
+ // needed when a '$' was displayed for 'list'.
+ if (wp->w_p_lcs_chars.eol == lcs_eol_one
+ && ((area_attr != 0 && vcol == fromcol
+ && (VIsual_mode != Ctrl_V
+ || lnum == VIsual.lnum
+ || lnum == curwin->w_cursor.lnum))
+ // highlight 'hlsearch' match at end of line
+ || prevcol_hl_flag)) {
+ int n = 0;
+
+ if (wp->w_p_rl) {
+ if (col < 0) {
+ n = 1;
+ }
+ } else {
+ if (col >= grid->cols) {
+ n = -1;
+ }
+ }
+ if (n != 0) {
+ // At the window boundary, highlight the last character
+ // instead (better than nothing).
+ off += n;
+ col += n;
+ } else {
+ // Add a blank character to highlight.
+ schar_from_ascii(linebuf_char[off], ' ');
+ }
+ if (area_attr == 0 && !has_fold) {
+ // Use attributes from match with highest priority among
+ // 'search_hl' and the match list.
+ get_search_match_hl(wp, &screen_search_hl, (long)(ptr - line), &char_attr);
+ }
+
+ int eol_attr = char_attr;
+ if (cul_attr) {
+ eol_attr = hl_combine_attr(cul_attr, eol_attr);
+ }
+ linebuf_attr[off] = eol_attr;
+ if (wp->w_p_rl) {
+ col--;
+ off--;
+ } else {
+ col++;
+ off++;
+ }
+ vcol++;
+ eol_hl_off = 1;
+ }
+ // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
+ if (wp->w_p_wrap) {
+ v = wp->w_skipcol;
+ } else {
+ v = wp->w_leftcol;
+ }
+
+ // check if line ends before left margin
+ if (vcol < v + col - win_col_off(wp)) {
+ vcol = v + col - win_col_off(wp);
+ }
+ // Get rid of the boguscols now, we want to draw until the right
+ // edge for 'cursorcolumn'.
+ col -= boguscols;
+ // boguscols = 0; // Disabled because value never read after this
+
+ if (draw_color_col) {
+ draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ }
+
+ bool has_virttext = false;
+ // Make sure alignment is the same regardless
+ // if listchars=eol:X is used or not.
+ int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0
+ ? 1 : 0);
+
+ if (has_decor) {
+ has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
+ col + eol_skip);
+ }
+
+ if (((wp->w_p_cuc
+ && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
+ && (int)wp->w_virtcol <
+ (long)grid->cols * (row - startrow + 1) + v
+ && lnum != wp->w_cursor.lnum)
+ || draw_color_col || line_attr_lowprio || line_attr
+ || diff_hlf != (hlf_T)0 || has_virttext)) {
+ int rightmost_vcol = 0;
+ int i;
+
+ if (wp->w_p_cuc) {
+ rightmost_vcol = wp->w_virtcol;
+ }
+
+ if (draw_color_col) {
+ // determine rightmost colorcolumn to possibly draw
+ for (i = 0; color_cols[i] >= 0; i++) {
+ if (rightmost_vcol < color_cols[i]) {
+ rightmost_vcol = color_cols[i];
+ }
+ }
+ }
+
+ int cuc_attr = win_hl_attr(wp, HLF_CUC);
+ int mc_attr = win_hl_attr(wp, HLF_MC);
+
+ int diff_attr = 0;
+ if (diff_hlf == HLF_TXD) {
+ diff_hlf = HLF_CHD;
+ }
+ if (diff_hlf != 0) {
+ diff_attr = win_hl_attr(wp, (int)diff_hlf);
+ }
+
+ int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
+ if (base_attr || line_attr || has_virttext) {
+ rightmost_vcol = INT_MAX;
+ }
+
+ int col_stride = wp->w_p_rl ? -1 : 1;
+
+ while (wp->w_p_rl ? col >= 0 : col < grid->cols) {
+ schar_from_ascii(linebuf_char[off], ' ');
+ col += col_stride;
+ if (draw_color_col) {
+ draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ }
+
+ int col_attr = base_attr;
+
+ if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
+ col_attr = cuc_attr;
+ } else if (draw_color_col && VCOL_HLC == *color_cols) {
+ col_attr = mc_attr;
+ }
+
+ col_attr = hl_combine_attr(col_attr, line_attr);
+
+ linebuf_attr[off] = col_attr;
+ off += col_stride;
+
+ if (VCOL_HLC >= rightmost_vcol) {
+ break;
+ }
+
+ vcol += 1;
+ }
+ }
+
+ // TODO(bfredl): integrate with the common beyond-the-end-loop
+ if (wp->w_buffer->terminal) {
+ // terminal buffers may need to highlight beyond the end of the
+ // logical line
+ int n = wp->w_p_rl ? -1 : 1;
+ while (col >= 0 && col < grid->cols) {
+ schar_from_ascii(linebuf_char[off], ' ');
+ linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol];
+ off += n;
+ vcol += n;
+ col += n;
+ }
+ }
+
+ draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
+ grid_put_linebuf(grid, row, 0, col, grid->cols, wp->w_p_rl, wp, bg_attr, false);
+ row++;
+
+ // Update w_cline_height and w_cline_folded if the cursor line was
+ // updated (saves a call to plines_win() later).
+ if (wp == curwin && lnum == curwin->w_cursor.lnum) {
+ curwin->w_cline_row = startrow;
+ curwin->w_cline_height = row - startrow;
+ curwin->w_cline_folded = foldinfo.fi_lines > 0;
+ curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
+ conceal_cursor_used = conceal_cursor_line(curwin);
+ }
+ break;
+ }
+
+ // Show "extends" character from 'listchars' if beyond the line end and
+ // 'list' is set.
+ if (wp->w_p_lcs_chars.ext != NUL
+ && draw_state == WL_LINE
+ && wp->w_p_list
+ && !wp->w_p_wrap
+ && filler_todo <= 0
+ && (wp->w_p_rl ? col == 0 : col == grid->cols - 1)
+ && !has_fold
+ && (*ptr != NUL
+ || lcs_eol_one > 0
+ || (n_extra && (c_extra != NUL || *p_extra != NUL)))) {
+ c = wp->w_p_lcs_chars.ext;
+ char_attr = win_hl_attr(wp, HLF_AT);
+ mb_c = c;
+ if (utf_char2len(c) > 1) {
+ mb_utf8 = true;
+ u8cc[0] = 0;
+ c = 0xc0;
+ } else {
+ mb_utf8 = false;
+ }
+ }
+
+ // advance to the next 'colorcolumn'
+ if (draw_color_col) {
+ draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ }
+
+ // Highlight the cursor column if 'cursorcolumn' is set. But don't
+ // highlight the cursor position itself.
+ // Also highlight the 'colorcolumn' if it is different than
+ // 'cursorcolumn'
+ // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak'
+ // options are set
+ vcol_save_attr = -1;
+ if ((draw_state == WL_LINE
+ || draw_state == WL_BRI
+ || draw_state == WL_SBR)
+ && !lnum_in_visual_area
+ && search_attr == 0
+ && area_attr == 0
+ && filler_todo <= 0) {
+ if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
+ && lnum != wp->w_cursor.lnum) {
+ vcol_save_attr = char_attr;
+ char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);
+ } else if (draw_color_col && VCOL_HLC == *color_cols) {
+ vcol_save_attr = char_attr;
+ char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr);
+ }
+ }
+
+ // Apply lowest-priority line attr now, so everything can override it.
+ if (draw_state == WL_LINE) {
+ char_attr = hl_combine_attr(line_attr_lowprio, char_attr);
+ }
+
+ // Store character to be displayed.
+ // Skip characters that are left of the screen for 'nowrap'.
+ vcol_prev = vcol;
+ if (draw_state < WL_LINE || n_skip <= 0) {
+ //
+ // Store the character.
+ //
+ if (wp->w_p_rl && utf_char2cells(mb_c) > 1) {
+ // A double-wide character is: put first half in left cell.
+ off--;
+ col--;
+ }
+ if (mb_utf8) {
+ schar_from_cc(linebuf_char[off], mb_c, u8cc);
+ } else {
+ schar_from_ascii(linebuf_char[off], (char)c);
+ }
+ if (multi_attr) {
+ linebuf_attr[off] = multi_attr;
+ multi_attr = 0;
+ } else {
+ linebuf_attr[off] = char_attr;
+ }
+
+ if (utf_char2cells(mb_c) > 1) {
+ // Need to fill two screen columns.
+ off++;
+ col++;
+ // UTF-8: Put a 0 in the second screen char.
+ linebuf_char[off][0] = 0;
+ if (draw_state > WL_NR && filler_todo <= 0) {
+ vcol++;
+ }
+ // When "tocol" is halfway through a character, set it to the end of
+ // the character, otherwise highlighting won't stop.
+ if (tocol == vcol) {
+ tocol++;
+ }
+ if (wp->w_p_rl) {
+ // now it's time to backup one cell
+ off--;
+ col--;
+ }
+ }
+ if (wp->w_p_rl) {
+ off--;
+ col--;
+ } else {
+ off++;
+ col++;
+ }
+ } else if (wp->w_p_cole > 0 && is_concealing) {
+ n_skip--;
+ vcol_off++;
+ if (n_extra > 0) {
+ vcol_off += n_extra;
+ }
+ if (wp->w_p_wrap) {
+ // Special voodoo required if 'wrap' is on.
+ //
+ // Advance the column indicator to force the line
+ // drawing to wrap early. This will make the line
+ // take up the same screen space when parts are concealed,
+ // so that cursor line computations aren't messed up.
+ //
+ // To avoid the fictitious advance of 'col' causing
+ // trailing junk to be written out of the screen line
+ // we are building, 'boguscols' keeps track of the number
+ // of bad columns we have advanced.
+ if (n_extra > 0) {
+ vcol += n_extra;
+ if (wp->w_p_rl) {
+ col -= n_extra;
+ boguscols -= n_extra;
+ } else {
+ col += n_extra;
+ boguscols += n_extra;
+ }
+ n_extra = 0;
+ n_attr = 0;
+ }
+
+ if (utf_char2cells(mb_c) > 1) {
+ // Need to fill two screen columns.
+ if (wp->w_p_rl) {
+ boguscols--;
+ col--;
+ } else {
+ boguscols++;
+ col++;
+ }
+ }
+
+ if (wp->w_p_rl) {
+ boguscols--;
+ col--;
+ } else {
+ boguscols++;
+ col++;
+ }
+ } else {
+ if (n_extra > 0) {
+ vcol += n_extra;
+ n_extra = 0;
+ n_attr = 0;
+ }
+ }
+ } else {
+ n_skip--;
+ }
+
+ // Only advance the "vcol" when after the 'number' or 'relativenumber'
+ // column.
+ if (draw_state > WL_NR
+ && filler_todo <= 0) {
+ vcol++;
+ }
+
+ if (vcol_save_attr >= 0) {
+ char_attr = vcol_save_attr;
+ }
+
+ // restore attributes after "predeces" in 'listchars'
+ if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) {
+ char_attr = saved_attr3;
+ }
+
+ // restore attributes after last 'listchars' or 'number' char
+ if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) {
+ char_attr = saved_attr2;
+ }
+
+ // At end of screen line and there is more to come: Display the line
+ // so far. If there is no more to display it is caught above.
+ if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols))
+ && foldinfo.fi_lines == 0
+ && (draw_state != WL_LINE
+ || *ptr != NUL
+ || filler_todo > 0
+ || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
+ && p_extra != at_end_str)
+ || (n_extra != 0
+ && (c_extra != NUL || *p_extra != NUL)))) {
+ bool wrap = wp->w_p_wrap // Wrapping enabled.
+ && filler_todo <= 0 // Not drawing diff filler lines.
+ && lcs_eol_one != -1 // Haven't printed the lcs_eol character.
+ && row != endrow - 1 // Not the last line being displayed.
+ && (grid->cols == Columns // Window spans the width of the screen,
+ || ui_has(kUIMultigrid)) // or has dedicated grid.
+ && !wp->w_p_rl; // Not right-to-left.
+
+ int draw_col = col - boguscols;
+ if (filler_todo > 0) {
+ int index = filler_todo - (filler_lines - n_virt_lines);
+ if (index > 0) {
+ int i = (int)kv_size(virt_lines) - index;
+ assert(i >= 0);
+ int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset;
+ draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line,
+ kHlModeReplace, grid->cols, offset);
+ }
+ } else {
+ draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row);
+ }
+
+ grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap);
+ if (wrap) {
+ ScreenGrid *current_grid = grid;
+ int current_row = row, dummy_col = 0; // dummy_col unused
+ grid_adjust(&current_grid, &current_row, &dummy_col);
+
+ // Force a redraw of the first column of the next line.
+ current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1;
+
+ // Remember that the line wraps, used for modeless copy.
+ current_grid->line_wraps[current_row] = true;
+ }
+
+ boguscols = 0;
+ row++;
+
+ // When not wrapping and finished diff lines, or when displayed
+ // '$' and highlighting until last column, break here.
+ if ((!wp->w_p_wrap && filler_todo <= 0) || lcs_eol_one == -1) {
+ break;
+ }
+
+ // When the window is too narrow draw all "@" lines.
+ if (draw_state != WL_LINE && filler_todo <= 0) {
+ win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT);
+ row = endrow;
+ }
+
+ // When line got too long for screen break here.
+ if (row == endrow) {
+ row++;
+ break;
+ }
+
+ col = 0;
+ off = 0;
+ if (wp->w_p_rl) {
+ col = grid->cols - 1; // col is not used if breaking!
+ off += col;
+ }
+
+ // reset the drawing state for the start of a wrapped line
+ draw_state = WL_START;
+ saved_n_extra = n_extra;
+ saved_p_extra = p_extra;
+ saved_c_extra = c_extra;
+ saved_c_final = c_final;
+ saved_char_attr = char_attr;
+ n_extra = 0;
+ lcs_prec_todo = wp->w_p_lcs_chars.prec;
+ if (filler_todo <= 0) {
+ need_showbreak = true;
+ }
+ filler_todo--;
+ // When the filler lines are actually below the last line of the
+ // file, don't draw the line itself, break here.
+ if (filler_todo == 0 && (wp->w_botfill || end_fill)) {
+ break;
+ }
+ }
+ } // for every character in the line
+
+ // After an empty line check first word for capital.
+ if (*skipwhite((char *)line) == NUL) {
+ capcol_lnum = lnum + 1;
+ cap_col = 0;
+ }
+
+ kv_destroy(virt_lines);
+ xfree(p_extra_free);
+ return row;
+}
diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h
new file mode 100644
index 0000000000..e50969983e
--- /dev/null
+++ b/src/nvim/drawline.h
@@ -0,0 +1,24 @@
+#ifndef NVIM_DRAWLINE_H
+#define NVIM_DRAWLINE_H
+
+#include "nvim/decoration_provider.h"
+#include "nvim/fold.h"
+#include "nvim/screen.h"
+
+// Maximum columns for terminal highlight attributes
+#define TERM_ATTRS_MAX 1024
+
+typedef struct {
+ NS ns_id;
+ uint64_t mark_id;
+ int win_row;
+ int win_col;
+} WinExtmark;
+EXTERN kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE);
+
+EXTERN bool conceal_cursor_used INIT(= false);
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "drawline.h.generated.h"
+#endif
+#endif // NVIM_DRAWLINE_H
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
new file mode 100644
index 0000000000..75fe0565c2
--- /dev/null
+++ b/src/nvim/drawscreen.c
@@ -0,0 +1,2373 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// drawscreen.c: Code for updating all the windows on the screen.
+// This is the top level, drawline.c is the middle and grid.c/screen.c the lower level.
+
+// update_screen() is the function that updates all windows and status lines.
+// It is called from the main loop when must_redraw is non-zero. It may be
+// called from other places when an immediate screen update is needed.
+//
+// The part of the buffer that is displayed in a window is set with:
+// - w_topline (first buffer line in window)
+// - w_topfill (filler lines above the first line)
+// - w_leftcol (leftmost window cell in window),
+// - w_skipcol (skipped window cells of first line)
+//
+// Commands that only move the cursor around in a window, do not need to take
+// action to update the display. The main loop will check if w_topline is
+// valid and update it (scroll the window) when needed.
+//
+// Commands that scroll a window change w_topline and must call
+// check_cursor() to move the cursor into the visible part of the window, and
+// call redraw_later(wp, VALID) to have the window displayed by update_screen()
+// later.
+//
+// Commands that change text in the buffer must call changed_bytes() or
+// changed_lines() to mark the area that changed and will require updating
+// later. The main loop will call update_screen(), which will update each
+// window that shows the changed buffer. This assumes text above the change
+// can remain displayed as it is. Text after the change may need updating for
+// scrolling, folding and syntax highlighting.
+//
+// Commands that change how a window is displayed (e.g., setting 'list') or
+// invalidate the contents of a window in another way (e.g., change fold
+// settings), must call redraw_later(wp, NOT_VALID) to have the whole window
+// redisplayed by update_screen() later.
+//
+// Commands that change how a buffer is displayed (e.g., setting 'tabstop')
+// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the
+// buffer redisplayed by update_screen() later.
+//
+// Commands that change highlighting and possibly cause a scroll too must call
+// redraw_later(wp, SOME_VALID) to update the whole window but still use
+// scrolling to avoid redrawing everything. But the length of displayed lines
+// must not change, use NOT_VALID then.
+//
+// Commands that move the window position must call redraw_later(wp, NOT_VALID).
+// TODO(neovim): should minimize redrawing by scrolling when possible.
+//
+// Commands that change everything (e.g., resizing the screen) must call
+// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR).
+//
+// Things that are handled indirectly:
+// - When messages scroll the screen up, msg_scrolled will be set and
+// update_screen() called to redraw.
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
+#include "nvim/ex_getln.h"
+#include "nvim/grid.h"
+#include "nvim/highlight.h"
+#include "nvim/highlight_group.h"
+#include "nvim/insexpand.h"
+#include "nvim/match.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/plines.h"
+#include "nvim/popupmenu.h"
+#include "nvim/profile.h"
+#include "nvim/regexp.h"
+#include "nvim/syntax.h"
+#include "nvim/ui_compositor.h"
+#include "nvim/undo.h"
+#include "nvim/version.h"
+#include "nvim/window.h"
+
+/// corner value flags for hsep_connected and vsep_connected
+typedef enum {
+ WC_TOP_LEFT = 0,
+ WC_TOP_RIGHT,
+ WC_BOTTOM_LEFT,
+ WC_BOTTOM_RIGHT,
+} WindowCorner;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "drawscreen.c.generated.h"
+#endif
+
+static bool redraw_popupmenu = false;
+static bool msg_grid_invalid = false;
+static bool resizing = false;
+
+static char *provider_err = NULL;
+
+/// Check if the cursor line needs to be redrawn because of 'concealcursor'.
+///
+/// When cursor is moved at the same time, both lines will be redrawn regardless.
+void conceal_check_cursor_line(void)
+{
+ bool should_conceal = conceal_cursor_line(curwin);
+ if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) {
+ redrawWinline(curwin, curwin->w_cursor.lnum);
+ // Need to recompute cursor column, e.g., when starting Visual mode
+ // without concealing.
+ curs_columns(curwin, true);
+ }
+}
+
+/// Resize the screen to Rows and Columns.
+///
+/// Allocate default_grid.chars[] and other grid arrays.
+///
+/// There may be some time between setting Rows and Columns and (re)allocating
+/// default_grid arrays. This happens when starting up and when
+/// (manually) changing the screen size. Always use default_grid.rows and
+/// default_grid.Columns to access items in default_grid.chars[]. Use Rows
+/// and Columns for positioning text etc. where the final size of the screen is
+/// needed.
+void screenalloc(void)
+{
+ // It's possible that we produce an out-of-memory message below, which
+ // will cause this function to be called again. To break the loop, just
+ // return here.
+ if (resizing) {
+ return;
+ }
+ resizing = true;
+
+ int retry_count = 0;
+
+retry:
+ // Allocation of the screen buffers is done only when the size changes and
+ // when Rows and Columns have been set and we have started doing full
+ // screen stuff.
+ if ((default_grid.chars != NULL
+ && Rows == default_grid.rows
+ && Columns == default_grid.cols)
+ || Rows == 0
+ || Columns == 0
+ || (!full_screen && default_grid.chars == NULL)) {
+ resizing = false;
+ return;
+ }
+
+ // Note that the window sizes are updated before reallocating the arrays,
+ // thus we must not redraw here!
+ RedrawingDisabled++;
+
+ // win_new_screensize will recompute floats position, but tell the
+ // compositor to not redraw them yet
+ ui_comp_set_screen_valid(false);
+ if (msg_grid.chars) {
+ msg_grid_invalid = true;
+ }
+
+ win_new_screensize(); // fit the windows in the new sized screen
+
+ comp_col(); // recompute columns for shown command and ruler
+
+ // We're changing the size of the screen.
+ // - Allocate new arrays for default_grid
+ // - Move lines from the old arrays into the new arrays, clear extra
+ // lines (unless the screen is going to be cleared).
+ // - Free the old arrays.
+ //
+ // If anything fails, make grid arrays NULL, so we don't do anything!
+ // Continuing with the old arrays may result in a crash, because the
+ // size is wrong.
+
+ grid_alloc(&default_grid, Rows, Columns, true, true);
+ StlClickDefinition *new_tab_page_click_defs =
+ xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs));
+
+ stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size);
+ xfree(tab_page_click_defs);
+
+ tab_page_click_defs = new_tab_page_click_defs;
+ tab_page_click_defs_size = Columns;
+
+ default_grid.comp_height = Rows;
+ default_grid.comp_width = Columns;
+
+ default_grid.row_offset = 0;
+ default_grid.col_offset = 0;
+ default_grid.handle = DEFAULT_GRID_HANDLE;
+
+ must_redraw = CLEAR; // need to clear the screen later
+
+ RedrawingDisabled--;
+
+ // Do not apply autocommands more than 3 times to avoid an endless loop
+ // in case applying autocommands always changes Rows or Columns.
+ if (starting == 0 && ++retry_count <= 3) {
+ apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf);
+ // In rare cases, autocommands may have altered Rows or Columns,
+ // jump back to check if we need to allocate the screen again.
+ goto retry;
+ }
+
+ resizing = false;
+}
+
+void screenclear(void)
+{
+ check_for_delay(false);
+ screenalloc(); // allocate screen buffers if size changed
+
+ int i;
+
+ if (starting == NO_SCREEN || default_grid.chars == NULL) {
+ return;
+ }
+
+ // blank out the default grid
+ for (i = 0; i < default_grid.rows; i++) {
+ grid_clear_line(&default_grid, default_grid.line_offset[i],
+ default_grid.cols, true);
+ default_grid.line_wraps[i] = false;
+ }
+
+ ui_call_grid_clear(1); // clear the display
+ ui_comp_set_screen_valid(true);
+
+ ns_hl_fast = -1;
+
+ clear_cmdline = false;
+ mode_displayed = false;
+
+ redraw_all_later(NOT_VALID);
+ redraw_cmdline = true;
+ redraw_tabline = true;
+ redraw_popupmenu = true;
+ pum_invalidate();
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_floating) {
+ wp->w_redr_type = CLEAR;
+ }
+ }
+ if (must_redraw == CLEAR) {
+ must_redraw = NOT_VALID; // no need to clear again
+ }
+ compute_cmdrow();
+ msg_row = cmdline_row; // put cursor on last line for messages
+ msg_col = 0;
+ msg_scrolled = 0; // can't scroll back
+ msg_didany = false;
+ msg_didout = false;
+ if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) {
+ grid_invalidate(&msg_grid);
+ msg_grid_validate();
+ msg_grid_invalid = false;
+ clear_cmdline = true;
+ }
+}
+
+/// Set dimensions of the Nvim application "screen".
+void screen_resize(int width, int height)
+{
+ // Avoid recursiveness, can happen when setting the window size causes
+ // another window-changed signal.
+ if (updating_screen || resizing_screen) {
+ return;
+ }
+
+ if (width < 0 || height < 0) { // just checking...
+ return;
+ }
+
+ if (State == MODE_HITRETURN || State == MODE_SETWSIZE) {
+ // postpone the resizing
+ State = MODE_SETWSIZE;
+ return;
+ }
+
+ // curwin->w_buffer can be NULL when we are closing a window and the
+ // buffer has already been closed and removing a scrollbar causes a resize
+ // event. Don't resize then, it will happen after entering another buffer.
+ if (curwin->w_buffer == NULL) {
+ return;
+ }
+
+ resizing_screen = true;
+
+ Rows = height;
+ Columns = width;
+ check_screensize();
+ int max_p_ch = Rows - min_rows() + 1;
+ if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) {
+ p_ch = max_p_ch ? max_p_ch : 1;
+ }
+ height = Rows;
+ width = Columns;
+ p_lines = Rows;
+ p_columns = Columns;
+ ui_call_grid_resize(1, width, height);
+
+ /// The window layout used to be adjusted here, but it now happens in
+ /// screenalloc() (also invoked from screenclear()). That is because the
+ /// recursize "resizing_screen" check above may skip this, but not screenalloc().
+
+ if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) {
+ screenclear();
+ }
+
+ if (starting != NO_SCREEN) {
+ maketitle();
+
+ changed_line_abv_curs();
+ invalidate_botline();
+
+ // We only redraw when it's needed:
+ // - While at the more prompt or executing an external command, don't
+ // redraw, but position the cursor.
+ // - While editing the command line, only redraw that.
+ // - in Ex mode, don't redraw anything.
+ // - Otherwise, redraw right now, and position the cursor.
+ // Always need to call update_screen() or screenalloc(), to make
+ // sure Rows/Columns and the size of the screen is correct!
+ if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM
+ || exmode_active) {
+ screenalloc();
+ if (msg_grid.chars) {
+ msg_grid_validate();
+ }
+ // TODO(bfredl): sometimes messes up the output. Implement clear+redraw
+ // also for the pager? (or: what if the pager was just a modal window?)
+ ui_comp_set_screen_valid(true);
+ repeat_message();
+ } else {
+ if (curwin->w_p_scb) {
+ do_check_scrollbind(true);
+ }
+ if (State & MODE_CMDLINE) {
+ redraw_popupmenu = false;
+ update_screen(NOT_VALID);
+ redrawcmdline();
+ if (pum_drawn()) {
+ cmdline_pum_display(false);
+ }
+ } else {
+ update_topline(curwin);
+ if (pum_drawn()) {
+ // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first.
+ // For now make sure the nested update_screen(0) won't redraw the
+ // pum at the old position. Try to untangle this later.
+ redraw_popupmenu = false;
+ ins_compl_show_pum();
+ }
+ update_screen(NOT_VALID);
+ if (redrawing()) {
+ setcursor();
+ }
+ }
+ }
+ ui_flush();
+ }
+ resizing_screen = false;
+}
+
+/// Redraw the parts of the screen that is marked for redraw.
+///
+/// Most code shouldn't call this directly, rather use redraw_later() and
+/// and redraw_all_later() to mark parts of the screen as needing a redraw.
+///
+/// @param type set to a NOT_VALID to force redraw of entire screen
+int update_screen(int type)
+{
+ static bool did_intro = false;
+ bool is_stl_global = global_stl_height() > 0;
+
+ // Don't do anything if the screen structures are (not yet) valid.
+ // A VimResized autocmd can invoke redrawing in the middle of a resize,
+ // which would bypass the checks in screen_resize for popupmenu etc.
+ if (!default_grid.chars || resizing) {
+ return FAIL;
+ }
+
+ // May have postponed updating diffs.
+ if (need_diff_redraw) {
+ diff_redraw(true);
+ }
+
+ if (must_redraw) {
+ if (type < must_redraw) { // use maximal type
+ type = must_redraw;
+ }
+
+ // must_redraw is reset here, so that when we run into some weird
+ // reason to redraw while busy redrawing (e.g., asynchronous
+ // scrolling), or update_topline() in win_update() will cause a
+ // scroll, or a decoration provider requires a redraw, the screen
+ // will be redrawn later or in win_update().
+ must_redraw = 0;
+ }
+
+ // Need to update w_lines[].
+ if (curwin->w_lines_valid == 0 && type < NOT_VALID) {
+ type = NOT_VALID;
+ }
+
+ // Postpone the redrawing when it's not needed and when being called
+ // recursively.
+ if (!redrawing() || updating_screen) {
+ must_redraw = type;
+ if (type > INVERTED_ALL) {
+ curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now
+ }
+ return FAIL;
+ }
+ updating_screen = 1;
+
+ display_tick++; // let syntax code know we're in a next round of
+ // display updating
+
+ // Tricky: vim code can reset msg_scrolled behind our back, so need
+ // separate bookkeeping for now.
+ if (msg_did_scroll) {
+ msg_did_scroll = false;
+ msg_scrolled_at_flush = 0;
+ }
+
+ if (type >= CLEAR || !default_grid.valid) {
+ ui_comp_set_screen_valid(false);
+ }
+
+ // if the screen was scrolled up when displaying a message, scroll it down
+ if (msg_scrolled || msg_grid_invalid) {
+ clear_cmdline = true;
+ int valid = MAX(Rows - msg_scrollsize(), 0);
+ if (msg_grid.chars) {
+ // non-displayed part of msg_grid is considered invalid.
+ for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) {
+ grid_clear_line(&msg_grid, msg_grid.line_offset[i],
+ msg_grid.cols, false);
+ }
+ }
+ if (msg_use_msgsep()) {
+ msg_grid.throttled = false;
+ // CLEAR is already handled
+ if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) {
+ ui_comp_set_screen_valid(false);
+ for (int i = valid; i < Rows - p_ch; i++) {
+ grid_clear_line(&default_grid, default_grid.line_offset[i],
+ Columns, false);
+ }
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_floating) {
+ continue;
+ }
+ if (W_ENDROW(wp) > valid) {
+ wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID);
+ }
+ if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) {
+ wp->w_redr_status = true;
+ }
+ }
+ if (is_stl_global && Rows - p_ch - 1 > valid) {
+ curwin->w_redr_status = true;
+ }
+ }
+ msg_grid_set_pos(Rows - (int)p_ch, false);
+ msg_grid_invalid = false;
+ } else if (msg_scrolled > Rows - 5) { // clearing is faster
+ type = CLEAR;
+ } else if (type != CLEAR) {
+ check_for_delay(false);
+ grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns);
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_floating) {
+ continue;
+ }
+ if (wp->w_winrow < msg_scrolled) {
+ if (W_ENDROW(wp) > msg_scrolled
+ && wp->w_redr_type < REDRAW_TOP
+ && wp->w_lines_valid > 0
+ && wp->w_topline == wp->w_lines[0].wl_lnum) {
+ wp->w_upd_rows = msg_scrolled - wp->w_winrow;
+ wp->w_redr_type = REDRAW_TOP;
+ } else {
+ wp->w_redr_type = NOT_VALID;
+ if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) {
+ wp->w_redr_status = true;
+ }
+ }
+ }
+ }
+ if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) {
+ curwin->w_redr_status = true;
+ }
+ redraw_cmdline = true;
+ redraw_tabline = true;
+ }
+ msg_scrolled = 0;
+ msg_scrolled_at_flush = 0;
+ need_wait_return = false;
+ }
+
+ win_ui_flush();
+ msg_ext_check_clear();
+
+ // reset cmdline_row now (may have been changed temporarily)
+ compute_cmdrow();
+
+ bool hl_changed = false;
+ // Check for changed highlighting
+ if (need_highlight_changed) {
+ highlight_changed();
+ hl_changed = true;
+ }
+
+ if (type == CLEAR) { // first clear screen
+ screenclear(); // will reset clear_cmdline
+ cmdline_screen_cleared(); // clear external cmdline state
+ type = NOT_VALID;
+ // must_redraw may be set indirectly, avoid another redraw later
+ must_redraw = 0;
+ } else if (!default_grid.valid) {
+ grid_invalidate(&default_grid);
+ default_grid.valid = true;
+ }
+
+ // After disabling msgsep the grid might not have been deallocated yet,
+ // hence we also need to check msg_grid.chars
+ if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) {
+ grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0);
+ }
+
+ ui_comp_set_screen_valid(true);
+
+ DecorProviders providers;
+ decor_providers_start(&providers, type, &provider_err);
+
+ // "start" callback could have changed highlights for global elements
+ if (win_check_ns_hl(NULL)) {
+ redraw_cmdline = true;
+ redraw_tabline = true;
+ }
+
+ if (clear_cmdline) { // going to clear cmdline (done below)
+ check_for_delay(false);
+ }
+
+ // Force redraw when width of 'number' or 'relativenumber' column
+ // changes.
+ if (curwin->w_redr_type < NOT_VALID
+ && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu)
+ ? number_width(curwin) : 0)) {
+ curwin->w_redr_type = NOT_VALID;
+ }
+
+ // Only start redrawing if there is really something to do.
+ if (type == INVERTED) {
+ update_curswant();
+ }
+ if (curwin->w_redr_type < type
+ && !((type == VALID
+ && curwin->w_lines[0].wl_valid
+ && curwin->w_topfill == curwin->w_old_topfill
+ && curwin->w_botfill == curwin->w_old_botfill
+ && curwin->w_topline == curwin->w_lines[0].wl_lnum)
+ || (type == INVERTED
+ && VIsual_active
+ && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum
+ && curwin->w_old_visual_mode == VIsual_mode
+ && (curwin->w_valid & VALID_VIRTCOL)
+ && curwin->w_old_curswant == curwin->w_curswant))) {
+ curwin->w_redr_type = type;
+ }
+
+ // Redraw the tab pages line if needed.
+ if (redraw_tabline || type >= NOT_VALID) {
+ update_window_hl(curwin, type >= NOT_VALID);
+ FOR_ALL_TABS(tp) {
+ if (tp != curtab) {
+ update_window_hl(tp->tp_curwin, type >= NOT_VALID);
+ }
+ }
+ draw_tabline();
+ }
+
+ // Correct stored syntax highlighting info for changes in each displayed
+ // buffer. Each buffer must only be done once.
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ update_window_hl(wp, type >= NOT_VALID || hl_changed);
+
+ buf_T *buf = wp->w_buffer;
+ if (buf->b_mod_set) {
+ if (buf->b_mod_tick_syn < display_tick
+ && syntax_present(wp)) {
+ syn_stack_apply_changes(buf);
+ buf->b_mod_tick_syn = display_tick;
+ }
+
+ if (buf->b_mod_tick_decor < display_tick) {
+ decor_providers_invoke_buf(buf, &providers, &provider_err);
+ buf->b_mod_tick_decor = display_tick;
+ }
+ }
+ }
+
+ // Go from top to bottom through the windows, redrawing the ones that need it.
+ bool did_one = false;
+ screen_search_hl.rm.regprog = NULL;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) {
+ grid_invalidate(&wp->w_grid_alloc);
+ wp->w_redr_type = NOT_VALID;
+ }
+
+ win_check_ns_hl(wp);
+
+ // reallocate grid if needed.
+ win_grid_alloc(wp);
+
+ if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) {
+ win_redr_border(wp);
+ }
+
+ if (wp->w_redr_type != 0) {
+ if (!did_one) {
+ did_one = true;
+ start_search_hl();
+ }
+ win_update(wp, &providers);
+ }
+
+ // redraw status line and window bar after the window to minimize cursor movement
+ if (wp->w_redr_status) {
+ win_redr_winbar(wp);
+ win_redr_status(wp);
+ }
+ }
+
+ end_search_hl();
+
+ // May need to redraw the popup menu.
+ if (pum_drawn() && must_redraw_pum) {
+ win_check_ns_hl(curwin);
+ pum_redraw();
+ }
+
+ win_check_ns_hl(NULL);
+
+ // Reset b_mod_set flags. Going through all windows is probably faster
+ // than going through all buffers (there could be many buffers).
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ wp->w_buffer->b_mod_set = false;
+ }
+
+ updating_screen = 0;
+
+ // Clear or redraw the command line. Done last, because scrolling may
+ // mess up the command line.
+ if (clear_cmdline || redraw_cmdline || redraw_mode) {
+ showmode();
+ }
+
+ // May put up an introductory message when not editing a file
+ if (!did_intro) {
+ maybe_intro_message();
+ }
+ did_intro = true;
+
+ decor_providers_invoke_end(&providers, &provider_err);
+ kvi_destroy(providers);
+
+ // either cmdline is cleared, not drawn or mode is last drawn
+ cmdline_was_last_drawn = false;
+ return OK;
+}
+
+static void win_redr_border(win_T *wp)
+{
+ wp->w_redr_border = false;
+ if (!(wp->w_floating && wp->w_float_config.border)) {
+ return;
+ }
+
+ ScreenGrid *grid = &wp->w_grid_alloc;
+
+ schar_T *chars = wp->w_float_config.border_chars;
+ int *attrs = wp->w_float_config.border_attr;
+
+ int *adj = wp->w_border_adj;
+ int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner;
+
+ char* title = wp->w_float_config.title;
+ size_t n_title = wp->w_float_config.n_title;
+ stl_hlrec_t* title_hl = wp->w_float_config.title_hl;
+
+ int m8[MAX_MCO + 1];
+ int cc;
+ int len;
+ int t_attr = title_hl != NULL && title_hl->userhl
+ ? syn_id2attr(title_hl->userhl)
+ : 0;
+ t_attr = hl_combine_attr(attrs[1], t_attr);
+
+ int title_pos = 2;
+ switch (wp->w_float_config.title_pos) {
+ case kTitleLeft:
+ title_pos = 2;
+ break;
+ case kTitleRight:
+ title_pos = icol - 2 - vim_strsize(title);
+ break;
+ case kTitleCenter:
+ title_pos = (icol - vim_strsize(title)) / 2 - 1;
+ break;
+ }
+ title_pos = title_pos < 2 ? 2 : title_pos;
+
+ if (adj[0]) {
+ grid_puts_line_start(grid, 0);
+ if (adj[3]) {
+ grid_put_schar(grid, 0, 0, chars[0], attrs[0]);
+ }
+ for (int i = 0; i < icol; i++) {
+ schar_T ch;
+ int attr;
+ // Draw the title if in the correct position.
+ if (i > title_pos && n_title > 0 && i < icol - 2) {
+ cc = utfc_ptr2char((char_u*) title, m8);
+ len = utfc_ptr2len(title);
+ n_title -= len;
+ title += len;
+
+ while (title_hl != NULL &&
+ (title_hl + 1)->start != NULL &&
+ (title_hl + 1)->start < title) {
+ ++ title_hl;
+ t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl));
+ }
+
+ schar_from_cc(ch, cc, m8);
+ attr = t_attr;
+ } else {
+ memcpy(ch, chars[1], sizeof(schar_T));
+ attr = attrs[1];
+ }
+ grid_put_schar(grid, 0, i + adj[3], ch, attr);
+ }
+ if (adj[1]) {
+ grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]);
+ }
+ grid_puts_line_flush(false);
+ }
+
+ for (int i = 0; i < irow; i++) {
+ if (adj[3]) {
+ grid_puts_line_start(grid, i + adj[0]);
+ grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]);
+ grid_puts_line_flush(false);
+ }
+ if (adj[1]) {
+ int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
+ grid_puts_line_start(grid, i + adj[0]);
+ grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]);
+ grid_puts_line_flush(false);
+ }
+ }
+
+ if (adj[2]) {
+ grid_puts_line_start(grid, irow + adj[0]);
+ if (adj[3]) {
+ grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]);
+ }
+ for (int i = 0; i < icol; i++) {
+ int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
+ grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]);
+ }
+ if (adj[1]) {
+ grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]);
+ }
+ grid_puts_line_flush(false);
+ }
+}
+
+/// Redraw the status line of window `wp`.
+///
+/// If inversion is possible we use it. Else '=' characters are used.
+static void win_redr_status(win_T *wp)
+{
+ int row;
+ int col;
+ char_u *p;
+ int len;
+ int fillchar;
+ int attr;
+ int width;
+ int this_ru_col;
+ bool is_stl_global = global_stl_height() > 0;
+ static bool busy = false;
+
+ // May get here recursively when 'statusline' (indirectly)
+ // invokes ":redrawstatus". Simply ignore the call then.
+ if (busy
+ // Also ignore if wildmenu is showing.
+ || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) {
+ return;
+ }
+ busy = true;
+
+ wp->w_redr_status = false;
+ if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) {
+ // no status line, either global statusline is enabled or the window is a last window
+ redraw_cmdline = true;
+ } else if (!redrawing()) {
+ // Don't redraw right now, do it later. Don't update status line when
+ // popup menu is visible and may be drawn over it
+ wp->w_redr_status = true;
+ } else if (*p_stl != NUL || *wp->w_p_stl != NUL) {
+ // redraw custom status line
+ redraw_custom_statusline(wp);
+ } else {
+ fillchar = fillchar_status(&attr, wp);
+ width = is_stl_global ? Columns : wp->w_width;
+
+ get_trans_bufname(wp->w_buffer);
+ p = NameBuff;
+ len = (int)STRLEN(p);
+
+ if (bt_help(wp->w_buffer)
+ || wp->w_p_pvw
+ || bufIsChanged(wp->w_buffer)
+ || wp->w_buffer->b_p_ro) {
+ *(p + len++) = ' ';
+ }
+ if (bt_help(wp->w_buffer)) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]"));
+ len += (int)STRLEN(p + len);
+ }
+ if (wp->w_p_pvw) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]"));
+ len += (int)STRLEN(p + len);
+ }
+ if (bufIsChanged(wp->w_buffer)) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]");
+ len += (int)STRLEN(p + len);
+ }
+ if (wp->w_buffer->b_p_ro) {
+ snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]"));
+ // len += (int)STRLEN(p + len); // dead assignment
+ }
+
+ this_ru_col = ru_col - (Columns - width);
+ if (this_ru_col < (width + 1) / 2) {
+ this_ru_col = (width + 1) / 2;
+ }
+ if (this_ru_col <= 1) {
+ p = (char_u *)"<"; // No room for file name!
+ len = 1;
+ } else {
+ int clen = 0, i;
+
+ // Count total number of display cells.
+ clen = (int)mb_string2cells((char *)p);
+
+ // Find first character that will fit.
+ // Going from start to end is much faster for DBCS.
+ for (i = 0; p[i] != NUL && clen >= this_ru_col - 1;
+ i += utfc_ptr2len((char *)p + i)) {
+ clen -= utf_ptr2cells((char *)p + i);
+ }
+ len = clen;
+ if (i > 0) {
+ p = p + i - 1;
+ *p = '<';
+ len++;
+ }
+ }
+
+ row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
+ col = is_stl_global ? 0 : wp->w_wincol;
+ grid_puts(&default_grid, p, row, col, attr);
+ grid_fill(&default_grid, row, row + 1, len + col,
+ this_ru_col + col, fillchar, fillchar, attr);
+
+ if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL)
+ && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) {
+ grid_puts(&default_grid, NameBuff, row,
+ (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr);
+ }
+
+ win_redr_ruler(wp, true);
+ }
+
+ // May need to draw the character below the vertical separator.
+ if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
+ if (stl_connected(wp)) {
+ fillchar = fillchar_status(&attr, wp);
+ } else {
+ fillchar = fillchar_vsep(wp, &attr);
+ }
+ grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
+ }
+ busy = false;
+}
+
+/// Redraw the status line according to 'statusline' and take care of any
+/// errors encountered.
+static void redraw_custom_statusline(win_T *wp)
+{
+ static bool entered = false;
+ int saved_did_emsg = did_emsg;
+
+ // When called recursively return. This can happen when the statusline
+ // contains an expression that triggers a redraw.
+ if (entered) {
+ return;
+ }
+ entered = true;
+
+ did_emsg = false;
+ win_redr_custom(wp, false, false);
+ if (did_emsg) {
+ // When there is an error disable the statusline, otherwise the
+ // display is messed up with errors and a redraw triggers the problem
+ // again and again.
+ set_string_option_direct("statusline", -1, "",
+ OPT_FREE | (*wp->w_p_stl != NUL
+ ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
+ }
+ did_emsg |= saved_did_emsg;
+ entered = false;
+}
+
+static void win_redr_winbar(win_T *wp)
+{
+ static bool entered = false;
+
+ // Return when called recursively. This can happen when the winbar contains an expression
+ // that triggers a redraw.
+ if (entered) {
+ return;
+ }
+ entered = true;
+
+ if (wp->w_winbar_height == 0 || !redrawing()) {
+ // Do nothing.
+ } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) {
+ int saved_did_emsg = did_emsg;
+
+ did_emsg = false;
+ win_redr_custom(wp, true, false);
+ if (did_emsg) {
+ // When there is an error disable the winbar, otherwise the
+ // display is messed up with errors and a redraw triggers the problem
+ // again and again.
+ set_string_option_direct("winbar", -1, "",
+ OPT_FREE | (*wp->w_p_stl != NUL
+ ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
+ }
+ did_emsg |= saved_did_emsg;
+ }
+ entered = false;
+}
+
+/// Show current status info in ruler and various other places
+///
+/// @param always if false, only show ruler if position has changed.
+void showruler(bool always)
+{
+ if (!always && !redrawing()) {
+ return;
+ }
+ if ((*p_stl != NUL || *curwin->w_p_stl != NUL)
+ && (curwin->w_status_height || global_stl_height())) {
+ redraw_custom_statusline(curwin);
+ } else {
+ win_redr_ruler(curwin, always);
+ }
+ if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) {
+ win_redr_winbar(curwin);
+ }
+
+ if (need_maketitle
+ || (p_icon && (stl_syntax & STL_IN_ICON))
+ || (p_title && (stl_syntax & STL_IN_TITLE))) {
+ maketitle();
+ }
+
+ // Redraw the tab pages line if needed.
+ if (redraw_tabline) {
+ draw_tabline();
+ }
+}
+
+static void redraw_win_signcol(win_T *wp)
+{
+ // If we can compute a change in the automatic sizing of the sign column
+ // under 'signcolumn=auto:X' and signs currently placed in the buffer, better
+ // figuring it out here so we can redraw the entire screen for it.
+ int scwidth = wp->w_scwidth;
+ wp->w_scwidth = win_signcol_count(wp);
+ if (wp->w_scwidth != scwidth) {
+ changed_line_abv_curs_win(wp);
+ }
+}
+
+/// Check if horizontal separator of window "wp" at specified window corner is connected to the
+/// horizontal separator of another window
+/// Assumes global statusline is enabled
+static bool hsep_connected(win_T *wp, WindowCorner corner)
+{
+ bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT);
+ int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT)
+ ? wp->w_winrow - 1 : W_ENDROW(wp);
+ frame_T *fr = wp->w_frame;
+
+ while (fr->fr_parent != NULL) {
+ if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) {
+ fr = before ? fr->fr_prev : fr->fr_next;
+ break;
+ }
+ fr = fr->fr_parent;
+ }
+ if (fr->fr_parent == NULL) {
+ return false;
+ }
+ while (fr->fr_layout != FR_LEAF) {
+ fr = fr->fr_child;
+ if (fr->fr_parent->fr_layout == FR_ROW && before) {
+ while (fr->fr_next != NULL) {
+ fr = fr->fr_next;
+ }
+ } else {
+ while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) {
+ fr = fr->fr_next;
+ }
+ }
+ }
+
+ return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win));
+}
+
+/// Check if vertical separator of window "wp" at specified window corner is connected to the
+/// vertical separator of another window
+static bool vsep_connected(win_T *wp, WindowCorner corner)
+{
+ bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT);
+ int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT)
+ ? wp->w_wincol - 1 : W_ENDCOL(wp);
+ frame_T *fr = wp->w_frame;
+
+ while (fr->fr_parent != NULL) {
+ if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) {
+ fr = before ? fr->fr_prev : fr->fr_next;
+ break;
+ }
+ fr = fr->fr_parent;
+ }
+ if (fr->fr_parent == NULL) {
+ return false;
+ }
+ while (fr->fr_layout != FR_LEAF) {
+ fr = fr->fr_child;
+ if (fr->fr_parent->fr_layout == FR_COL && before) {
+ while (fr->fr_next != NULL) {
+ fr = fr->fr_next;
+ }
+ } else {
+ while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) {
+ fr = fr->fr_next;
+ }
+ }
+ }
+
+ return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win));
+}
+
+/// Draw the vertical separator right of window "wp"
+static void draw_vsep_win(win_T *wp)
+{
+ int hl;
+ int c;
+
+ if (wp->w_vsep_width) {
+ // draw the vertical separator right of this window
+ c = fillchar_vsep(wp, &hl);
+ grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp),
+ W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl);
+ }
+}
+
+/// Draw the horizontal separator below window "wp"
+static void draw_hsep_win(win_T *wp)
+{
+ int hl;
+ int c;
+
+ if (wp->w_hsep_height) {
+ // draw the horizontal separator below this window
+ c = fillchar_hsep(wp, &hl);
+ grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1,
+ wp->w_wincol, W_ENDCOL(wp), c, c, hl);
+ }
+}
+
+/// Get the separator connector for specified window corner of window "wp"
+static int get_corner_sep_connector(win_T *wp, WindowCorner corner)
+{
+ // It's impossible for windows to be connected neither vertically nor horizontally
+ // So if they're not vertically connected, assume they're horizontally connected
+ if (vsep_connected(wp, corner)) {
+ if (hsep_connected(wp, corner)) {
+ return wp->w_p_fcs_chars.verthoriz;
+ } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) {
+ return wp->w_p_fcs_chars.vertright;
+ } else {
+ return wp->w_p_fcs_chars.vertleft;
+ }
+ } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) {
+ return wp->w_p_fcs_chars.horizdown;
+ } else {
+ return wp->w_p_fcs_chars.horizup;
+ }
+}
+
+/// Draw separator connecting characters on the corners of window "wp"
+static void draw_sep_connectors_win(win_T *wp)
+{
+ // Don't draw separator connectors unless global statusline is enabled and the window has
+ // either a horizontal or vertical separator
+ if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) {
+ return;
+ }
+
+ int hl = win_hl_attr(wp, HLF_C);
+
+ // Determine which edges of the screen the window is located on so we can avoid drawing separators
+ // on corners contained in those edges
+ bool win_at_top;
+ bool win_at_bottom = wp->w_hsep_height == 0;
+ bool win_at_left;
+ bool win_at_right = wp->w_vsep_width == 0;
+ frame_T *frp;
+
+ for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) {
+ if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) {
+ break;
+ }
+ }
+ win_at_top = frp->fr_parent == NULL;
+ for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) {
+ if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) {
+ break;
+ }
+ }
+ win_at_left = frp->fr_parent == NULL;
+
+ // Draw the appropriate separator connector in every corner where drawing them is necessary
+ if (!(win_at_top || win_at_left)) {
+ grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT),
+ wp->w_winrow - 1, wp->w_wincol - 1, hl);
+ }
+ if (!(win_at_top || win_at_right)) {
+ grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT),
+ wp->w_winrow - 1, W_ENDCOL(wp), hl);
+ }
+ if (!(win_at_bottom || win_at_left)) {
+ grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT),
+ W_ENDROW(wp), wp->w_wincol - 1, hl);
+ }
+ if (!(win_at_bottom || win_at_right)) {
+ grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT),
+ W_ENDROW(wp), W_ENDCOL(wp), hl);
+ }
+}
+
+/// Update a single window.
+///
+/// This may cause the windows below it also to be redrawn (when clearing the
+/// screen or scrolling lines).
+///
+/// How the window is redrawn depends on wp->w_redr_type. Each type also
+/// implies the one below it.
+/// NOT_VALID redraw the whole window
+/// SOME_VALID redraw the whole window but do scroll when possible
+/// REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID
+/// INVERTED redraw the changed part of the Visual area
+/// INVERTED_ALL redraw the whole Visual area
+/// VALID 1. scroll up/down to adjust for a changed w_topline
+/// 2. update lines at the top when scrolled down
+/// 3. redraw changed text:
+/// - if wp->w_buffer->b_mod_set set, update lines between
+/// b_mod_top and b_mod_bot.
+/// - if wp->w_redraw_top non-zero, redraw lines between
+/// wp->w_redraw_top and wp->w_redr_bot.
+/// - continue redrawing when syntax status is invalid.
+/// 4. if scrolled up, update lines at the bottom.
+/// This results in three areas that may need updating:
+/// top: from first row to top_end (when scrolled down)
+/// mid: from mid_start to mid_end (update inversion or changed text)
+/// bot: from bot_start to last row (when scrolled up)
+static void win_update(win_T *wp, DecorProviders *providers)
+{
+ bool called_decor_providers = false;
+win_update_start:
+ ;
+ buf_T *buf = wp->w_buffer;
+ int type;
+ int top_end = 0; // Below last row of the top area that needs
+ // updating. 0 when no top area updating.
+ int mid_start = 999; // first row of the mid area that needs
+ // updating. 999 when no mid area updating.
+ int mid_end = 0; // Below last row of the mid area that needs
+ // updating. 0 when no mid area updating.
+ int bot_start = 999; // first row of the bot area that needs
+ // updating. 999 when no bot area updating
+ bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit
+ bool top_to_mod = false; // redraw above mod_top
+
+ int row; // current window row to display
+ linenr_T lnum; // current buffer lnum to display
+ int idx; // current index in w_lines[]
+ int srow; // starting row of the current line
+
+ bool eof = false; // if true, we hit the end of the file
+ bool didline = false; // if true, we finished the last line
+ int i;
+ long j;
+ static bool recursive = false; // being called recursively
+ const linenr_T old_botline = wp->w_botline;
+ // Remember what happened to the previous line.
+#define DID_NONE 1 // didn't update a line
+#define DID_LINE 2 // updated a normal line
+#define DID_FOLD 3 // updated a folded line
+ int did_update = DID_NONE;
+ linenr_T syntax_last_parsed = 0; // last parsed text line
+ linenr_T mod_top = 0;
+ linenr_T mod_bot = 0;
+ int save_got_int;
+
+ type = wp->w_redr_type;
+
+ if (type >= NOT_VALID) {
+ wp->w_redr_status = true;
+ wp->w_lines_valid = 0;
+ }
+
+ // Window is zero-height: Only need to draw the separator
+ if (wp->w_grid.rows == 0) {
+ // draw the horizontal separator below this window
+ draw_hsep_win(wp);
+ draw_sep_connectors_win(wp);
+ wp->w_redr_type = 0;
+ return;
+ }
+
+ // Window is zero-width: Only need to draw the separator.
+ if (wp->w_grid.cols == 0) {
+ // draw the vertical separator right of this window
+ draw_vsep_win(wp);
+ draw_sep_connectors_win(wp);
+ wp->w_redr_type = 0;
+ return;
+ }
+
+ redraw_win_signcol(wp);
+
+ init_search_hl(wp, &screen_search_hl);
+
+ // Force redraw when width of 'number' or 'relativenumber' column
+ // changes.
+ i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0;
+ if (wp->w_nrwidth != i) {
+ type = NOT_VALID;
+ wp->w_nrwidth = i;
+
+ if (buf->terminal) {
+ terminal_check_size(buf->terminal);
+ }
+ } else if (buf->b_mod_set
+ && buf->b_mod_xlines != 0
+ && wp->w_redraw_top != 0) {
+ // When there are both inserted/deleted lines and specific lines to be
+ // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw
+ // everything (only happens when redrawing is off for while).
+ type = NOT_VALID;
+ } else {
+ // Set mod_top to the first line that needs displaying because of
+ // changes. Set mod_bot to the first line after the changes.
+ mod_top = wp->w_redraw_top;
+ if (wp->w_redraw_bot != 0) {
+ mod_bot = wp->w_redraw_bot + 1;
+ } else {
+ mod_bot = 0;
+ }
+ if (buf->b_mod_set) {
+ if (mod_top == 0 || mod_top > buf->b_mod_top) {
+ mod_top = buf->b_mod_top;
+ // Need to redraw lines above the change that may be included
+ // in a pattern match.
+ if (syntax_present(wp)) {
+ mod_top -= buf->b_s.b_syn_sync_linebreaks;
+ if (mod_top < 1) {
+ mod_top = 1;
+ }
+ }
+ }
+ if (mod_bot == 0 || mod_bot < buf->b_mod_bot) {
+ mod_bot = buf->b_mod_bot;
+ }
+
+ // When 'hlsearch' is on and using a multi-line search pattern, a
+ // change in one line may make the Search highlighting in a
+ // previous line invalid. Simple solution: redraw all visible
+ // lines above the change.
+ // Same for a match pattern.
+ if (screen_search_hl.rm.regprog != NULL
+ && re_multiline(screen_search_hl.rm.regprog)) {
+ top_to_mod = true;
+ } else {
+ const matchitem_T *cur = wp->w_match_head;
+ while (cur != NULL) {
+ if (cur->match.regprog != NULL
+ && re_multiline(cur->match.regprog)) {
+ top_to_mod = true;
+ break;
+ }
+ cur = cur->next;
+ }
+ }
+ }
+ if (mod_top != 0 && hasAnyFolding(wp)) {
+ linenr_T lnumt, lnumb;
+
+ // A change in a line can cause lines above it to become folded or
+ // unfolded. Find the top most buffer line that may be affected.
+ // If the line was previously folded and displayed, get the first
+ // line of that fold. If the line is folded now, get the first
+ // folded line. Use the minimum of these two.
+
+ // Find last valid w_lines[] entry above mod_top. Set lnumt to
+ // the line below it. If there is no valid entry, use w_topline.
+ // Find the first valid w_lines[] entry below mod_bot. Set lnumb
+ // to this line. If there is no valid entry, use MAXLNUM.
+ lnumt = wp->w_topline;
+ lnumb = MAXLNUM;
+ for (i = 0; i < wp->w_lines_valid; i++) {
+ if (wp->w_lines[i].wl_valid) {
+ if (wp->w_lines[i].wl_lastlnum < mod_top) {
+ lnumt = wp->w_lines[i].wl_lastlnum + 1;
+ }
+ if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) {
+ lnumb = wp->w_lines[i].wl_lnum;
+ // When there is a fold column it might need updating
+ // in the next line ("J" just above an open fold).
+ if (compute_foldcolumn(wp, 0) > 0) {
+ lnumb++;
+ }
+ }
+ }
+ }
+
+ (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL);
+ if (mod_top > lnumt) {
+ mod_top = lnumt;
+ }
+
+ // Now do the same for the bottom line (one above mod_bot).
+ mod_bot--;
+ (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL);
+ mod_bot++;
+ if (mod_bot < lnumb) {
+ mod_bot = lnumb;
+ }
+ }
+
+ // When a change starts above w_topline and the end is below
+ // w_topline, start redrawing at w_topline.
+ // If the end of the change is above w_topline: do like no change was
+ // made, but redraw the first line to find changes in syntax.
+ if (mod_top != 0 && mod_top < wp->w_topline) {
+ if (mod_bot > wp->w_topline) {
+ mod_top = wp->w_topline;
+ } else if (syntax_present(wp)) {
+ top_end = 1;
+ }
+ }
+
+ // When line numbers are displayed need to redraw all lines below
+ // inserted/deleted lines.
+ if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) {
+ mod_bot = MAXLNUM;
+ }
+ }
+ wp->w_redraw_top = 0; // reset for next time
+ wp->w_redraw_bot = 0;
+
+ // When only displaying the lines at the top, set top_end. Used when
+ // window has scrolled down for msg_scrolled.
+ if (type == REDRAW_TOP) {
+ j = 0;
+ for (i = 0; i < wp->w_lines_valid; i++) {
+ j += wp->w_lines[i].wl_size;
+ if (j >= wp->w_upd_rows) {
+ top_end = (int)j;
+ break;
+ }
+ }
+ if (top_end == 0) {
+ // not found (cannot happen?): redraw everything
+ type = NOT_VALID;
+ } else {
+ // top area defined, the rest is VALID
+ type = VALID;
+ }
+ }
+
+ // If there are no changes on the screen that require a complete redraw,
+ // handle three cases:
+ // 1: we are off the top of the screen by a few lines: scroll down
+ // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up
+ // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in
+ // w_lines[] that needs updating.
+ if ((type == VALID || type == SOME_VALID
+ || type == INVERTED || type == INVERTED_ALL)
+ && !wp->w_botfill && !wp->w_old_botfill) {
+ if (mod_top != 0
+ && wp->w_topline == mod_top
+ && (!wp->w_lines[0].wl_valid
+ || wp->w_topline == wp->w_lines[0].wl_lnum)) {
+ // w_topline is the first changed line and window is not scrolled,
+ // the scrolling from changed lines will be done further down.
+ } else if (wp->w_lines[0].wl_valid
+ && (wp->w_topline < wp->w_lines[0].wl_lnum
+ || (wp->w_topline == wp->w_lines[0].wl_lnum
+ && wp->w_topfill > wp->w_old_topfill))) {
+ // New topline is above old topline: May scroll down.
+ if (hasAnyFolding(wp)) {
+ linenr_T ln;
+
+ // count the number of lines we are off, counting a sequence
+ // of folded lines as one
+ j = 0;
+ for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) {
+ j++;
+ if (j >= wp->w_grid.rows - 2) {
+ break;
+ }
+ (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL);
+ }
+ } else {
+ j = wp->w_lines[0].wl_lnum - wp->w_topline;
+ }
+ if (j < wp->w_grid.rows - 2) { // not too far off
+ i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);
+ // insert extra lines for previously invisible filler lines
+ if (wp->w_lines[0].wl_lnum != wp->w_topline) {
+ i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill;
+ }
+ if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off
+ // Try to insert the correct number of lines.
+ // If not the last window, delete the lines at the bottom.
+ // win_ins_lines may fail when the terminal can't do it.
+ win_scroll_lines(wp, 0, i);
+ if (wp->w_lines_valid != 0) {
+ // Need to update rows that are new, stop at the
+ // first one that scrolled down.
+ top_end = i;
+ scrolled_down = true;
+
+ // Move the entries that were scrolled, disable
+ // the entries for the lines to be redrawn.
+ if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) {
+ wp->w_lines_valid = wp->w_grid.rows;
+ }
+ for (idx = wp->w_lines_valid; idx - j >= 0; idx--) {
+ wp->w_lines[idx] = wp->w_lines[idx - j];
+ }
+ while (idx >= 0) {
+ wp->w_lines[idx--].wl_valid = false;
+ }
+ }
+ } else {
+ mid_start = 0; // redraw all lines
+ }
+ } else {
+ mid_start = 0; // redraw all lines
+ }
+ } else {
+ // New topline is at or below old topline: May scroll up.
+ // When topline didn't change, find first entry in w_lines[] that
+ // needs updating.
+
+ // try to find wp->w_topline in wp->w_lines[].wl_lnum
+ j = -1;
+ row = 0;
+ for (i = 0; i < wp->w_lines_valid; i++) {
+ if (wp->w_lines[i].wl_valid
+ && wp->w_lines[i].wl_lnum == wp->w_topline) {
+ j = i;
+ break;
+ }
+ row += wp->w_lines[i].wl_size;
+ }
+ if (j == -1) {
+ // if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all
+ // lines
+ mid_start = 0;
+ } else {
+ // Try to delete the correct number of lines.
+ // wp->w_topline is at wp->w_lines[i].wl_lnum.
+
+ // If the topline didn't change, delete old filler lines,
+ // otherwise delete filler lines of the new topline...
+ if (wp->w_lines[0].wl_lnum == wp->w_topline) {
+ row += wp->w_old_topfill;
+ } else {
+ row += win_get_fill(wp, wp->w_topline);
+ }
+ // ... but don't delete new filler lines.
+ row -= wp->w_topfill;
+ if (row > 0) {
+ win_scroll_lines(wp, 0, -row);
+ bot_start = wp->w_grid.rows - row;
+ }
+ if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) {
+ // Skip the lines (below the deleted lines) that are still
+ // valid and don't need redrawing. Copy their info
+ // upwards, to compensate for the deleted lines. Set
+ // bot_start to the first row that needs redrawing.
+ bot_start = 0;
+ idx = 0;
+ for (;;) {
+ wp->w_lines[idx] = wp->w_lines[j];
+ // stop at line that didn't fit, unless it is still
+ // valid (no lines deleted)
+ if (row > 0 && bot_start + row
+ + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) {
+ wp->w_lines_valid = idx + 1;
+ break;
+ }
+ bot_start += wp->w_lines[idx++].wl_size;
+
+ // stop at the last valid entry in w_lines[].wl_size
+ if (++j >= wp->w_lines_valid) {
+ wp->w_lines_valid = idx;
+ break;
+ }
+ }
+
+ // Correct the first entry for filler lines at the top
+ // when it won't get updated below.
+ if (win_may_fill(wp) && bot_start > 0) {
+ wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true)
+ + wp->w_topfill);
+ }
+ }
+ }
+ }
+
+ // When starting redraw in the first line, redraw all lines.
+ if (mid_start == 0) {
+ mid_end = wp->w_grid.rows;
+ }
+ } else {
+ // Not VALID or INVERTED: redraw all lines.
+ mid_start = 0;
+ mid_end = wp->w_grid.rows;
+ }
+
+ if (type == SOME_VALID) {
+ // SOME_VALID: redraw all lines.
+ mid_start = 0;
+ mid_end = wp->w_grid.rows;
+ type = NOT_VALID;
+ }
+
+ // check if we are updating or removing the inverted part
+ if ((VIsual_active && buf == curwin->w_buffer)
+ || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) {
+ linenr_T from, to;
+
+ if (VIsual_active) {
+ if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) {
+ // If the type of Visual selection changed, redraw the whole
+ // selection. Also when the ownership of the X selection is
+ // gained or lost.
+ if (curwin->w_cursor.lnum < VIsual.lnum) {
+ from = curwin->w_cursor.lnum;
+ to = VIsual.lnum;
+ } else {
+ from = VIsual.lnum;
+ to = curwin->w_cursor.lnum;
+ }
+ // redraw more when the cursor moved as well
+ if (wp->w_old_cursor_lnum < from) {
+ from = wp->w_old_cursor_lnum;
+ }
+ if (wp->w_old_cursor_lnum > to) {
+ to = wp->w_old_cursor_lnum;
+ }
+ if (wp->w_old_visual_lnum < from) {
+ from = wp->w_old_visual_lnum;
+ }
+ if (wp->w_old_visual_lnum > to) {
+ to = wp->w_old_visual_lnum;
+ }
+ } else {
+ // Find the line numbers that need to be updated: The lines
+ // between the old cursor position and the current cursor
+ // position. Also check if the Visual position changed.
+ if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) {
+ from = curwin->w_cursor.lnum;
+ to = wp->w_old_cursor_lnum;
+ } else {
+ from = wp->w_old_cursor_lnum;
+ to = curwin->w_cursor.lnum;
+ if (from == 0) { // Visual mode just started
+ from = to;
+ }
+ }
+
+ if (VIsual.lnum != wp->w_old_visual_lnum
+ || VIsual.col != wp->w_old_visual_col) {
+ if (wp->w_old_visual_lnum < from
+ && wp->w_old_visual_lnum != 0) {
+ from = wp->w_old_visual_lnum;
+ }
+ if (wp->w_old_visual_lnum > to) {
+ to = wp->w_old_visual_lnum;
+ }
+ if (VIsual.lnum < from) {
+ from = VIsual.lnum;
+ }
+ if (VIsual.lnum > to) {
+ to = VIsual.lnum;
+ }
+ }
+ }
+
+ // If in block mode and changed column or curwin->w_curswant:
+ // update all lines.
+ // First compute the actual start and end column.
+ if (VIsual_mode == Ctrl_V) {
+ colnr_T fromc, toc;
+ unsigned int save_ve_flags = curwin->w_ve_flags;
+
+ if (curwin->w_p_lbr) {
+ curwin->w_ve_flags = VE_ALL;
+ }
+
+ getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc);
+ toc++;
+ curwin->w_ve_flags = save_ve_flags;
+ // Highlight to the end of the line, unless 'virtualedit' has
+ // "block".
+ if (curwin->w_curswant == MAXCOL) {
+ if (get_ve_flags() & VE_BLOCK) {
+ pos_T pos;
+ int cursor_above = curwin->w_cursor.lnum < VIsual.lnum;
+
+ // Need to find the longest line.
+ toc = 0;
+ pos.coladd = 0;
+ for (pos.lnum = curwin->w_cursor.lnum;
+ cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum;
+ pos.lnum += cursor_above ? 1 : -1) {
+ colnr_T t;
+
+ pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false));
+ getvvcol(wp, &pos, NULL, NULL, &t);
+ if (toc < t) {
+ toc = t;
+ }
+ }
+ toc++;
+ } else {
+ toc = MAXCOL;
+ }
+ }
+
+ if (fromc != wp->w_old_cursor_fcol
+ || toc != wp->w_old_cursor_lcol) {
+ if (from > VIsual.lnum) {
+ from = VIsual.lnum;
+ }
+ if (to < VIsual.lnum) {
+ to = VIsual.lnum;
+ }
+ }
+ wp->w_old_cursor_fcol = fromc;
+ wp->w_old_cursor_lcol = toc;
+ }
+ } else {
+ // Use the line numbers of the old Visual area.
+ if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) {
+ from = wp->w_old_cursor_lnum;
+ to = wp->w_old_visual_lnum;
+ } else {
+ from = wp->w_old_visual_lnum;
+ to = wp->w_old_cursor_lnum;
+ }
+ }
+
+ // There is no need to update lines above the top of the window.
+ if (from < wp->w_topline) {
+ from = wp->w_topline;
+ }
+
+ // If we know the value of w_botline, use it to restrict the update to
+ // the lines that are visible in the window.
+ if (wp->w_valid & VALID_BOTLINE) {
+ if (from >= wp->w_botline) {
+ from = wp->w_botline - 1;
+ }
+ if (to >= wp->w_botline) {
+ to = wp->w_botline - 1;
+ }
+ }
+
+ // Find the minimal part to be updated.
+ // Watch out for scrolling that made entries in w_lines[] invalid.
+ // E.g., CTRL-U makes the first half of w_lines[] invalid and sets
+ // top_end; need to redraw from top_end to the "to" line.
+ // A middle mouse click with a Visual selection may change the text
+ // above the Visual area and reset wl_valid, do count these for
+ // mid_end (in srow).
+ if (mid_start > 0) {
+ lnum = wp->w_topline;
+ idx = 0;
+ srow = 0;
+ if (scrolled_down) {
+ mid_start = top_end;
+ } else {
+ mid_start = 0;
+ }
+ while (lnum < from && idx < wp->w_lines_valid) { // find start
+ if (wp->w_lines[idx].wl_valid) {
+ mid_start += wp->w_lines[idx].wl_size;
+ } else if (!scrolled_down) {
+ srow += wp->w_lines[idx].wl_size;
+ }
+ idx++;
+ if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) {
+ lnum = wp->w_lines[idx].wl_lnum;
+ } else {
+ lnum++;
+ }
+ }
+ srow += mid_start;
+ mid_end = wp->w_grid.rows;
+ for (; idx < wp->w_lines_valid; idx++) { // find end
+ if (wp->w_lines[idx].wl_valid
+ && wp->w_lines[idx].wl_lnum >= to + 1) {
+ // Only update until first row of this line
+ mid_end = srow;
+ break;
+ }
+ srow += wp->w_lines[idx].wl_size;
+ }
+ }
+ }
+
+ if (VIsual_active && buf == curwin->w_buffer) {
+ wp->w_old_visual_mode = (char)VIsual_mode;
+ wp->w_old_cursor_lnum = curwin->w_cursor.lnum;
+ wp->w_old_visual_lnum = VIsual.lnum;
+ wp->w_old_visual_col = VIsual.col;
+ wp->w_old_curswant = curwin->w_curswant;
+ } else {
+ wp->w_old_visual_mode = 0;
+ wp->w_old_cursor_lnum = 0;
+ wp->w_old_visual_lnum = 0;
+ wp->w_old_visual_col = 0;
+ }
+
+ // reset got_int, otherwise regexp won't work
+ save_got_int = got_int;
+ got_int = 0;
+ // Set the time limit to 'redrawtime'.
+ proftime_T syntax_tm = profile_setlimit(p_rdt);
+ syn_set_timeout(&syntax_tm);
+
+ // Update all the window rows.
+ idx = 0; // first entry in w_lines[].wl_size
+ row = 0;
+ srow = 0;
+ lnum = wp->w_topline; // first line shown in window
+
+ win_extmark_arr.size = 0;
+
+ decor_redraw_reset(buf, &decor_state);
+
+ DecorProviders line_providers;
+ decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ (void)win_signcol_count(wp); // check if provider changed signcol width
+ if (must_redraw != 0) {
+ must_redraw = 0;
+ if (!called_decor_providers) {
+ called_decor_providers = true;
+ goto win_update_start;
+ }
+ }
+
+ bool cursorline_standout = win_cursorline_standout(wp);
+
+ win_check_ns_hl(wp);
+
+ for (;;) {
+ // stop updating when reached the end of the window (check for _past_
+ // the end of the window is at the end of the loop)
+ if (row == wp->w_grid.rows) {
+ didline = true;
+ break;
+ }
+
+ // stop updating when hit the end of the file
+ if (lnum > buf->b_ml.ml_line_count) {
+ eof = true;
+ break;
+ }
+
+ // Remember the starting row of the line that is going to be dealt
+ // with. It is used further down when the line doesn't fit.
+ srow = row;
+
+ // Update a line when it is in an area that needs updating, when it
+ // has changes or w_lines[idx] is invalid.
+ // "bot_start" may be halfway a wrapped line after using
+ // win_scroll_lines(), check if the current line includes it.
+ // When syntax folding is being used, the saved syntax states will
+ // already have been updated, we can't see where the syntax state is
+ // the same again, just update until the end of the window.
+ if (row < top_end
+ || (row >= mid_start && row < mid_end)
+ || top_to_mod
+ || idx >= wp->w_lines_valid
+ || (row + wp->w_lines[idx].wl_size > bot_start)
+ || (mod_top != 0
+ && (lnum == mod_top
+ || (lnum >= mod_top
+ && (lnum < mod_bot
+ || did_update == DID_FOLD
+ || (did_update == DID_LINE
+ && syntax_present(wp)
+ && ((foldmethodIsSyntax(wp)
+ && hasAnyFolding(wp))
+ || syntax_check_changed(lnum)))
+ // match in fixed position might need redraw
+ // if lines were inserted or deleted
+ || (wp->w_match_head != NULL
+ && buf->b_mod_xlines != 0)))))
+ || (cursorline_standout && lnum == wp->w_cursor.lnum)
+ || lnum == wp->w_last_cursorline) {
+ if (lnum == mod_top) {
+ top_to_mod = false;
+ }
+
+ // When at start of changed lines: May scroll following lines
+ // up or down to minimize redrawing.
+ // Don't do this when the change continues until the end.
+ // Don't scroll when dollar_vcol >= 0, keep the "$".
+ // Don't scroll when redrawing the top, scrolled already above.
+ if (lnum == mod_top
+ && mod_bot != MAXLNUM
+ && !(dollar_vcol >= 0 && mod_bot == mod_top + 1)
+ && row >= top_end) {
+ int old_rows = 0;
+ int new_rows = 0;
+ int xtra_rows;
+ linenr_T l;
+
+ // Count the old number of window rows, using w_lines[], which
+ // should still contain the sizes for the lines as they are
+ // currently displayed.
+ for (i = idx; i < wp->w_lines_valid; i++) {
+ // Only valid lines have a meaningful wl_lnum. Invalid
+ // lines are part of the changed area.
+ if (wp->w_lines[i].wl_valid
+ && wp->w_lines[i].wl_lnum == mod_bot) {
+ break;
+ }
+ old_rows += wp->w_lines[i].wl_size;
+ if (wp->w_lines[i].wl_valid
+ && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) {
+ // Must have found the last valid entry above mod_bot.
+ // Add following invalid entries.
+ i++;
+ while (i < wp->w_lines_valid
+ && !wp->w_lines[i].wl_valid) {
+ old_rows += wp->w_lines[i++].wl_size;
+ }
+ break;
+ }
+ }
+
+ if (i >= wp->w_lines_valid) {
+ // We can't find a valid line below the changed lines,
+ // need to redraw until the end of the window.
+ // Inserting/deleting lines has no use.
+ bot_start = 0;
+ } else {
+ // Able to count old number of rows: Count new window
+ // rows, and may insert/delete lines
+ j = idx;
+ for (l = lnum; l < mod_bot; l++) {
+ if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) {
+ new_rows++;
+ } else if (l == wp->w_topline) {
+ new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill;
+ } else {
+ new_rows += plines_win(wp, l, true);
+ }
+ j++;
+ if (new_rows > wp->w_grid.rows - row - 2) {
+ // it's getting too much, must redraw the rest
+ new_rows = 9999;
+ break;
+ }
+ }
+ xtra_rows = new_rows - old_rows;
+ if (xtra_rows < 0) {
+ // May scroll text up. If there is not enough
+ // remaining text or scrolling fails, must redraw the
+ // rest. If scrolling works, must redraw the text
+ // below the scrolled text.
+ if (row - xtra_rows >= wp->w_grid.rows - 2) {
+ mod_bot = MAXLNUM;
+ } else {
+ win_scroll_lines(wp, row, xtra_rows);
+ bot_start = wp->w_grid.rows + xtra_rows;
+ }
+ } else if (xtra_rows > 0) {
+ // May scroll text down. If there is not enough
+ // remaining text of scrolling fails, must redraw the
+ // rest.
+ if (row + xtra_rows >= wp->w_grid.rows - 2) {
+ mod_bot = MAXLNUM;
+ } else {
+ win_scroll_lines(wp, row + old_rows, xtra_rows);
+ if (top_end > row + old_rows) {
+ // Scrolled the part at the top that requires
+ // updating down.
+ top_end += xtra_rows;
+ }
+ }
+ }
+
+ // When not updating the rest, may need to move w_lines[]
+ // entries.
+ if (mod_bot != MAXLNUM && i != j) {
+ if (j < i) {
+ int x = row + new_rows;
+
+ // move entries in w_lines[] upwards
+ for (;;) {
+ // stop at last valid entry in w_lines[]
+ if (i >= wp->w_lines_valid) {
+ wp->w_lines_valid = (int)j;
+ break;
+ }
+ wp->w_lines[j] = wp->w_lines[i];
+ // stop at a line that won't fit
+ if (x + (int)wp->w_lines[j].wl_size
+ > wp->w_grid.rows) {
+ wp->w_lines_valid = (int)j + 1;
+ break;
+ }
+ x += wp->w_lines[j++].wl_size;
+ i++;
+ }
+ if (bot_start > x) {
+ bot_start = x;
+ }
+ } else { // j > i
+ // move entries in w_lines[] downwards
+ j -= i;
+ wp->w_lines_valid += (linenr_T)j;
+ if (wp->w_lines_valid > wp->w_grid.rows) {
+ wp->w_lines_valid = wp->w_grid.rows;
+ }
+ for (i = wp->w_lines_valid; i - j >= idx; i--) {
+ wp->w_lines[i] = wp->w_lines[i - j];
+ }
+
+ // The w_lines[] entries for inserted lines are
+ // now invalid, but wl_size may be used above.
+ // Reset to zero.
+ while (i >= idx) {
+ wp->w_lines[i].wl_size = 0;
+ wp->w_lines[i--].wl_valid = false;
+ }
+ }
+ }
+ }
+ }
+
+ // When lines are folded, display one line for all of them.
+ // Otherwise, display normally (can be several display lines when
+ // 'wrap' is on).
+ foldinfo_T foldinfo = fold_info(wp, lnum);
+
+ if (foldinfo.fi_lines == 0
+ && idx < wp->w_lines_valid
+ && wp->w_lines[idx].wl_valid
+ && wp->w_lines[idx].wl_lnum == lnum
+ && lnum > wp->w_topline
+ && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
+ && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows
+ && win_get_fill(wp, lnum) == 0) {
+ // This line is not going to fit. Don't draw anything here,
+ // will draw "@ " lines below.
+ row = wp->w_grid.rows + 1;
+ } else {
+ prepare_search_hl(wp, &screen_search_hl, lnum);
+ // Let the syntax stuff know we skipped a few lines.
+ if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum
+ && syntax_present(wp)) {
+ syntax_end_parsing(syntax_last_parsed + 1);
+ }
+
+ // Display one line
+ row = win_line(wp, lnum, srow,
+ foldinfo.fi_lines ? srow : wp->w_grid.rows,
+ mod_top == 0, false, foldinfo, &line_providers, &provider_err);
+
+ if (foldinfo.fi_lines == 0) {
+ wp->w_lines[idx].wl_folded = false;
+ wp->w_lines[idx].wl_lastlnum = lnum;
+ did_update = DID_LINE;
+ syntax_last_parsed = lnum;
+ } else {
+ foldinfo.fi_lines--;
+ wp->w_lines[idx].wl_folded = true;
+ wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
+ did_update = DID_FOLD;
+ }
+ }
+
+ wp->w_lines[idx].wl_lnum = lnum;
+ wp->w_lines[idx].wl_valid = true;
+
+ if (row > wp->w_grid.rows) { // past end of grid
+ // we may need the size of that too long line later on
+ if (dollar_vcol == -1) {
+ wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true);
+ }
+ idx++;
+ break;
+ }
+ if (dollar_vcol == -1) {
+ wp->w_lines[idx].wl_size = (uint16_t)(row - srow);
+ }
+ idx++;
+ lnum += foldinfo.fi_lines + 1;
+ } else {
+ if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) {
+ // 'relativenumber' set and cursor moved vertically: The
+ // text doesn't need to be drawn, but the number column does.
+ foldinfo_T info = fold_info(wp, lnum);
+ (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true,
+ info, &line_providers, &provider_err);
+ }
+
+ // This line does not need to be drawn, advance to the next one.
+ row += wp->w_lines[idx++].wl_size;
+ if (row > wp->w_grid.rows) { // past end of screen
+ break;
+ }
+ lnum = wp->w_lines[idx - 1].wl_lastlnum + 1;
+ did_update = DID_NONE;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ eof = true;
+ break;
+ }
+ }
+ // End of loop over all window lines.
+
+ // Now that the window has been redrawn with the old and new cursor line,
+ // update w_last_cursorline.
+ wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0;
+
+ wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0;
+
+ if (idx > wp->w_lines_valid) {
+ wp->w_lines_valid = idx;
+ }
+
+ // Let the syntax stuff know we stop parsing here.
+ if (syntax_last_parsed != 0 && syntax_present(wp)) {
+ syntax_end_parsing(syntax_last_parsed + 1);
+ }
+
+ // If we didn't hit the end of the file, and we didn't finish the last
+ // line we were working on, then the line didn't fit.
+ wp->w_empty_rows = 0;
+ wp->w_filler_rows = 0;
+ if (!eof && !didline) {
+ int at_attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, HLF_AT));
+ if (lnum == wp->w_topline) {
+ // Single line that does not fit!
+ // Don't overwrite it, it can be edited.
+ wp->w_botline = lnum + 1;
+ } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) {
+ // Window ends in filler lines.
+ wp->w_botline = lnum;
+ wp->w_filler_rows = wp->w_grid.rows - srow;
+ } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate"
+ int scr_row = wp->w_grid.rows - 1;
+
+ // Last line isn't finished: Display "@@@" in the last screen line.
+ grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr);
+
+ grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols,
+ '@', ' ', at_attr);
+ set_empty_rows(wp, srow);
+ wp->w_botline = lnum;
+ } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline"
+ int start_col = wp->w_grid.cols - 3;
+
+ // Last line isn't finished: Display "@@@" at the end.
+ grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows,
+ MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr);
+ set_empty_rows(wp, srow);
+ wp->w_botline = lnum;
+ } else {
+ win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT);
+ wp->w_botline = lnum;
+ }
+ } else {
+ if (eof) { // we hit the end of the file
+ wp->w_botline = buf->b_ml.ml_line_count + 1;
+ j = win_get_fill(wp, wp->w_botline);
+ if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) {
+ // Display filler text below last line. win_line() will check
+ // for ml_line_count+1 and only draw filler lines
+ foldinfo_T info = FOLDINFO_INIT;
+ row = win_line(wp, wp->w_botline, row, wp->w_grid.rows,
+ false, false, info, &line_providers, &provider_err);
+ }
+ } else if (dollar_vcol == -1) {
+ wp->w_botline = lnum;
+ }
+
+ // make sure the rest of the screen is blank
+ // write the 'eob' character to rows that aren't part of the file.
+ win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows,
+ HLF_EOB);
+ }
+
+ kvi_destroy(line_providers);
+
+ if (wp->w_redr_type >= REDRAW_TOP) {
+ draw_vsep_win(wp);
+ draw_hsep_win(wp);
+ draw_sep_connectors_win(wp);
+ }
+ syn_set_timeout(NULL);
+
+ // Reset the type of redrawing required, the window has been updated.
+ wp->w_redr_type = 0;
+ wp->w_old_topfill = wp->w_topfill;
+ wp->w_old_botfill = wp->w_botfill;
+
+ // Send win_extmarks if needed
+ for (size_t n = 0; n < kv_size(win_extmark_arr); n++) {
+ ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle,
+ kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id,
+ kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col);
+ }
+
+ if (dollar_vcol == -1) {
+ // There is a trick with w_botline. If we invalidate it on each
+ // change that might modify it, this will cause a lot of expensive
+ // calls to plines_win() in update_topline() each time. Therefore the
+ // value of w_botline is often approximated, and this value is used to
+ // compute the value of w_topline. If the value of w_botline was
+ // wrong, check that the value of w_topline is correct (cursor is on
+ // the visible part of the text). If it's not, we need to redraw
+ // again. Mostly this just means scrolling up a few lines, so it
+ // doesn't look too bad. Only do this for the current window (where
+ // changes are relevant).
+ wp->w_valid |= VALID_BOTLINE;
+ wp->w_viewport_invalid = true;
+ if (wp == curwin && wp->w_botline != old_botline && !recursive) {
+ recursive = true;
+ curwin->w_valid &= ~VALID_TOPLINE;
+ update_topline(curwin); // may invalidate w_botline again
+ if (must_redraw != 0) {
+ // Don't update for changes in buffer again.
+ i = curbuf->b_mod_set;
+ curbuf->b_mod_set = false;
+ win_update(curwin, providers);
+ must_redraw = 0;
+ curbuf->b_mod_set = i;
+ }
+ recursive = false;
+ }
+ }
+
+ // restore got_int, unless CTRL-C was hit while redrawing
+ if (!got_int) {
+ got_int = save_got_int;
+ }
+}
+
+/// Redraw a window later, with update_screen(type).
+///
+/// Set must_redraw only if not already set to a higher value.
+/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing.
+void redraw_later(win_T *wp, int type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (!exiting && wp->w_redr_type < type) {
+ wp->w_redr_type = type;
+ if (type >= NOT_VALID) {
+ wp->w_lines_valid = 0;
+ }
+ if (must_redraw < type) { // must_redraw is the maximum of all windows
+ must_redraw = type;
+ }
+ }
+}
+
+/// Mark all windows to be redrawn later.
+void redraw_all_later(int type)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ redraw_later(wp, type);
+ }
+ // This may be needed when switching tabs.
+ if (must_redraw < type) {
+ must_redraw = type;
+ }
+}
+
+void screen_invalidate_highlights(void)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ redraw_later(wp, NOT_VALID);
+ wp->w_grid_alloc.valid = false;
+ }
+}
+
+/// Mark all windows that are editing the current buffer to be updated later.
+void redraw_curbuf_later(int type)
+{
+ redraw_buf_later(curbuf, type);
+}
+
+void redraw_buf_later(buf_T *buf, int type)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf) {
+ redraw_later(wp, type);
+ }
+ }
+}
+
+void redraw_buf_line_later(buf_T *buf, linenr_T line)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf
+ && line >= wp->w_topline && line < wp->w_botline) {
+ redrawWinline(wp, line);
+ }
+ }
+}
+
+void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf
+ && lastline >= wp->w_topline && firstline < wp->w_botline) {
+ if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) {
+ wp->w_redraw_top = firstline;
+ }
+ if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) {
+ wp->w_redraw_bot = lastline;
+ }
+ redraw_later(wp, VALID);
+ }
+ }
+}
+
+/// called when the status bars for the buffer 'buf' need to be updated
+void redraw_buf_status_later(buf_T *buf)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf
+ && (wp->w_status_height
+ || (wp == curwin && global_stl_height())
+ || wp->w_winbar_height)) {
+ wp->w_redr_status = true;
+ if (must_redraw < VALID) {
+ must_redraw = VALID;
+ }
+ }
+ }
+}
+
+/// Mark all status lines and window bars for redraw; used after first :cd
+void status_redraw_all(void)
+{
+ bool is_stl_global = global_stl_height() != 0;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin)
+ || wp->w_winbar_height) {
+ wp->w_redr_status = true;
+ redraw_later(wp, VALID);
+ }
+ }
+}
+
+/// Marks all status lines and window bars of the current buffer for redraw.
+void status_redraw_curbuf(void)
+{
+ status_redraw_buf(curbuf);
+}
+
+/// Marks all status lines and window bars of the given buffer for redraw.
+void status_redraw_buf(buf_T *buf)
+{
+ bool is_stl_global = global_stl_height() != 0;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height)
+ || (is_stl_global && wp == curwin) || wp->w_winbar_height)) {
+ wp->w_redr_status = true;
+ redraw_later(wp, VALID);
+ }
+ }
+}
+
+/// Redraw all status lines that need to be redrawn.
+void redraw_statuslines(void)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_redr_status) {
+ win_redr_winbar(wp);
+ win_redr_status(wp);
+ }
+ }
+ if (redraw_tabline) {
+ draw_tabline();
+ }
+}
+
+/// Changed something in the current window, at buffer line "lnum", that
+/// requires that line and possibly other lines to be redrawn.
+/// Used when entering/leaving Insert mode with the cursor on a folded line.
+/// Used to remove the "$" from a change command.
+/// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot
+/// may become invalid and the whole window will have to be redrawn.
+void redrawWinline(win_T *wp, linenr_T lnum)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (lnum >= wp->w_topline
+ && lnum < wp->w_botline) {
+ if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) {
+ wp->w_redraw_top = lnum;
+ }
+ if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) {
+ wp->w_redraw_bot = lnum;
+ }
+ redraw_later(wp, VALID);
+ }
+}
diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h
new file mode 100644
index 0000000000..3eac1caaa1
--- /dev/null
+++ b/src/nvim/drawscreen.h
@@ -0,0 +1,25 @@
+#ifndef NVIM_DRAWSCREEN_H
+#define NVIM_DRAWSCREEN_H
+
+#include "nvim/drawline.h"
+
+/// flags for update_screen()
+/// The higher the value, the higher the priority
+enum {
+ VALID = 10, ///< buffer not changed, or changes marked with b_mod_*
+ INVERTED = 20, ///< redisplay inverted part that changed
+ INVERTED_ALL = 25, ///< redisplay whole inverted part
+ REDRAW_TOP = 30, ///< display first w_upd_rows screen lines
+ SOME_VALID = 35, ///< like NOT_VALID but may scroll
+ NOT_VALID = 40, ///< buffer needs complete redraw
+ CLEAR = 50, ///< screen messed up, clear it
+};
+
+/// While redrawing the screen this flag is set. It means the screen size
+/// ('lines' and 'rows') must not be changed.
+EXTERN bool updating_screen INIT(= 0);
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "drawscreen.h.generated.h"
+#endif
+#endif // NVIM_DRAWSCREEN_H
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 969e0af9f5..3a47731715 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -16,6 +16,7 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/event/loop.h"
@@ -25,6 +26,7 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
@@ -46,9 +48,8 @@
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/state.h"
@@ -1747,7 +1748,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
replace_push(replaced);
replaced = NUL;
}
- ++start_col;
+ start_col++;
}
}
@@ -1913,7 +1914,7 @@ int get_literal(bool no_simplify)
cc = cc * 10 + nc - '0';
}
- ++i;
+ i++;
}
if (cc > 255
@@ -1948,7 +1949,7 @@ int get_literal(bool no_simplify)
cc = '\n';
}
- --no_mapping;
+ no_mapping--;
if (nc) {
vungetc(nc);
// A character typed with i_CTRL-V_digit cannot have modifiers.
@@ -2022,7 +2023,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv)
/// @param second_indent indent for second line if >= 0
void insertchar(int c, int flags, int second_indent)
{
- char_u *p;
+ char *p;
int force_format = flags & INSCHAR_FORMAT;
const int textwidth = comp_textwidth(force_format);
@@ -2081,13 +2082,13 @@ void insertchar(int c, int flags, int second_indent)
// Need to remove existing (middle) comment leader and insert end
// comment leader. First, check what comment leader we can find.
char_u *line = get_cursor_line_ptr();
- int i = get_leader_len((char *)line, (char **)&p, false, true);
- if (i > 0 && vim_strchr((char *)p, COM_MIDDLE) != NULL) { // Just checking
+ int i = get_leader_len((char *)line, &p, false, true);
+ if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking
// Skip middle-comment string
while (*p && p[-1] != ':') { // find end of middle flags
p++;
}
- int middle_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ",");
+ int middle_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
// Don't count trailing white space for middle_len
while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) {
middle_len--;
@@ -2097,7 +2098,7 @@ void insertchar(int c, int flags, int second_indent)
while (*p && p[-1] != ':') { // find end of end flags
p++;
}
- int end_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ",");
+ int end_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
// Skip white space before the cursor
i = curwin->w_cursor.col;
@@ -3253,7 +3254,7 @@ int cursor_down(long n, int upd_topline)
if (hasFolding(lnum, NULL, &last)) {
lnum = last + 1;
} else {
- ++lnum;
+ lnum++;
}
if (lnum >= curbuf->b_ml.ml_line_count) {
break;
@@ -3433,7 +3434,7 @@ void replace_push(int c)
memmove(p + 1, p, (size_t)replace_offset);
}
*p = (char_u)c;
- ++replace_stack_nr;
+ replace_stack_nr++;
}
/*
@@ -3468,7 +3469,7 @@ static void replace_join(int off)
{
for (ssize_t i = replace_stack_nr; --i >= 0;) {
if (replace_stack[i] == NUL && off-- <= 0) {
- --replace_stack_nr;
+ replace_stack_nr--;
memmove(replace_stack + i, replace_stack + i + 1,
(size_t)(replace_stack_nr - i));
return;
@@ -3794,12 +3795,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
while (*look == '>') {
look++;
}
- }
- /*
- * Is it a word: "=word"?
- */
- else if (*look == '=' && look[1] != ',' && look[1] != NUL) {
- ++look;
+ // Is it a word: "=word"?
+ } else if (*look == '=' && look[1] != ',' && look[1] != NUL) {
+ look++;
if (*look == '~') {
icase = true;
look++;
@@ -4253,7 +4251,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
// Otherwise remove the mode message.
if (reg_recording != 0 || restart_edit != NUL) {
showmode();
- } else if (p_smd) {
+ } else if (p_smd && (got_int || !skip_showmode())) {
msg("");
}
// Exit Insert mode
@@ -5221,8 +5219,8 @@ static bool ins_tab(void)
// Find first white before the cursor
fpos = curwin->w_cursor;
while (fpos.col > 0 && ascii_iswhite(ptr[-1])) {
- --fpos.col;
- --ptr;
+ fpos.col--;
+ ptr--;
}
// In Replace mode, don't change characters before the insert point.
@@ -5254,8 +5252,8 @@ static bool ins_tab(void)
}
}
}
- ++fpos.col;
- ++ptr;
+ fpos.col++;
+ ptr++;
vcol += i;
}
@@ -5266,8 +5264,8 @@ static bool ins_tab(void)
// Skip over the spaces we need.
while (vcol < want_vcol && *ptr == ' ') {
vcol += lbr_chartabsize(line, ptr, vcol);
- ++ptr;
- ++repl_off;
+ ptr++;
+ repl_off++;
}
if (vcol > want_vcol) {
// Must have a char with 'showbreak' just before it.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 7cb8bdee19..53f1074033 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1,9 +1,7 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * eval.c: Expression evaluation.
- */
+// eval.c: Expression evaluation.
#include <math.h>
#include <stdlib.h>
@@ -15,10 +13,12 @@
#endif
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
@@ -29,9 +29,10 @@
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
-#include "nvim/fileio.h"
#include "nvim/getchar.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
@@ -43,8 +44,10 @@
#include "nvim/os/input.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
+#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sign.h"
@@ -71,19 +74,15 @@ static char * const namespace_char = "abglstvw";
/// Variable used for g:
static ScopeDictDictItem globvars_var;
-/*
- * Old Vim variables such as "v:version" are also available without the "v:".
- * Also in functions. We need a special hashtable for them.
- */
+/// Old Vim variables such as "v:version" are also available without the "v:".
+/// Also in functions. We need a special hashtable for them.
static hashtab_T compat_hashtab;
/// Used for checking if local variables or arguments used in a lambda.
bool *eval_lavars_used = NULL;
-/*
- * Array to hold the hashtab with variables local to each sourced script.
- * Each item holds a variable (nameless) that points to the dict_T.
- */
+/// Array to hold the hashtab with variables local to each sourced script.
+/// Each item holds a variable (nameless) that points to the dict_T.
typedef struct {
ScopeDictDictItem sv_var;
dict_T sv_dict;
@@ -98,9 +97,7 @@ static int echo_attr = 0; // attributes used for ":echo"
// The names of packages that once were loaded are remembered.
static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL };
-/*
- * Info used by a ":for" loop.
- */
+/// Info used by a ":for" loop.
typedef struct {
int fi_semicolon; // TRUE if ending in '; var]'
int fi_varcount; // nr of variables in the list
@@ -356,8 +353,6 @@ void eval_init(void)
{
vimvars[VV_VERSION].vv_nr = VIM_VERSION_100;
- struct vimvar *p;
-
init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE);
init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE);
vimvardict.dv_lock = VAR_FIXED;
@@ -365,7 +360,7 @@ void eval_init(void)
func_init();
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
- p = &vimvars[i];
+ struct vimvar *p = &vimvars[i];
assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN);
STRCPY(p->vv_di.di_key, p->vv_name);
if (p->vv_flags & VV_RO) {
@@ -447,10 +442,8 @@ void eval_init(void)
#if defined(EXITFREE)
void eval_clear(void)
{
- struct vimvar *p;
-
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
- p = &vimvars[i];
+ struct vimvar *p = &vimvars[i];
if (p->vv_di.di_tv.v_type == VAR_STRING) {
XFREE_CLEAR(p->vv_str);
} else if (p->vv_di.di_tv.v_type == VAR_LIST) {
@@ -473,13 +466,13 @@ void eval_clear(void)
// autoloaded script names
ga_clear_strings(&ga_loaded);
- /* Script-local variables. First clear all the variables and in a second
- * loop free the scriptvar_T, because a variable in one script might hold
- * a reference to the whole scope of another script. */
- for (int i = 1; i <= ga_scripts.ga_len; ++i) {
+ // Script-local variables. First clear all the variables and in a second
+ // loop free the scriptvar_T, because a variable in one script might hold
+ // a reference to the whole scope of another script.
+ for (int i = 1; i <= ga_scripts.ga_len; i++) {
vars_clear(&SCRIPT_VARS(i));
}
- for (int i = 1; i <= ga_scripts.ga_len; ++i) {
+ for (int i = 1; i <= ga_scripts.ga_len; i++) {
xfree(SCRIPT_SV(i));
}
ga_clear(&ga_scripts);
@@ -518,10 +511,6 @@ static char *redir_varname = NULL;
/// @return OK if successfully completed the setup. FAIL otherwise.
int var_redir_start(char *name, int append)
{
- int save_emsg;
- int err;
- typval_T tv;
-
// Catch a bad name early.
if (!eval_isnamec1(*name)) {
emsg(_(e_invarg));
@@ -553,10 +542,11 @@ int var_redir_start(char *name, int append)
return FAIL;
}
- /* check if we can write to the variable: set it to or append an empty
- * string */
- save_emsg = did_emsg;
- did_emsg = FALSE;
+ // check if we can write to the variable: set it to or append an empty
+ // string
+ int save_emsg = did_emsg;
+ did_emsg = false;
+ typval_T tv;
tv.v_type = VAR_STRING;
tv.vval.v_string = "";
if (append) {
@@ -565,7 +555,7 @@ int var_redir_start(char *name, int append)
set_var_lval(redir_lval, redir_endp, &tv, true, false, "=");
}
clear_lval(redir_lval);
- err = did_emsg;
+ int err = did_emsg;
did_emsg |= save_emsg;
if (err) {
redir_endp = NULL; // don't store a value, only cleanup
@@ -585,12 +575,11 @@ int var_redir_start(char *name, int append)
/// :redir END
void var_redir_str(char *value, int value_len)
{
- int len;
-
if (redir_lval == NULL) {
return;
}
+ int len;
if (value_len == -1) {
len = (int)STRLEN(value); // Append the entire string
} else {
@@ -606,12 +595,11 @@ void var_redir_str(char *value, int value_len)
/// Frees the allocated memory.
void var_redir_stop(void)
{
- typval_T tv;
-
if (redir_lval != NULL) {
// If there was no error: assign the text to the variable.
if (redir_endp != NULL) {
ga_append(&redir_ga, NUL); // Append the trailing NUL.
+ typval_T tv;
tv.v_type = VAR_STRING;
tv.vval.v_string = redir_ga.ga_data;
// Call get_lval() again, if it's inside a Dict or List it may
@@ -935,7 +923,7 @@ varnumber_T eval_to_number(char *expr)
varnumber_T retval;
char *p = skipwhite(expr);
- ++emsg_off;
+ emsg_off++;
if (eval1(&p, &rettv, true) == FAIL) {
retval = -1;
@@ -943,7 +931,7 @@ varnumber_T eval_to_number(char *expr)
retval = tv_get_number_chk(&rettv, NULL);
tv_clear(&rettv);
}
- --emsg_off;
+ emsg_off--;
return retval;
}
@@ -1000,11 +988,9 @@ void prepare_vimvar(int idx, typval_T *save_tv)
/// When no longer defined, remove the variable from the v: hashtable.
void restore_vimvar(int idx, typval_T *save_tv)
{
- hashitem_T *hi;
-
vimvars[idx].vv_tv = *save_tv;
if (vimvars[idx].vv_type == VAR_UNKNOWN) {
- hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key);
+ hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key);
if (HASHITEM_EMPTY(hi)) {
internal_error("restore_vimvar()");
} else {
@@ -1040,7 +1026,7 @@ list_T *eval_spell_expr(char *badword, char *expr)
vimvars[VV_VAL].vv_type = VAR_STRING;
vimvars[VV_VAL].vv_str = badword;
if (p_verbose == 0) {
- ++emsg_off;
+ emsg_off++;
}
if (eval1(&p, &rettv, true) == OK) {
@@ -1052,7 +1038,7 @@ list_T *eval_spell_expr(char *badword, char *expr)
}
if (p_verbose == 0) {
- --emsg_off;
+ emsg_off--;
}
restore_vimvar(VV_VAL, &save_val);
@@ -1134,12 +1120,11 @@ varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv)
FUNC_ATTR_NONNULL_ALL
{
typval_T rettv;
- varnumber_T retval;
if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) {
return -1;
}
- retval = tv_get_number_chk(&rettv, NULL);
+ varnumber_T retval = tv_get_number_chk(&rettv, NULL);
tv_clear(&rettv);
return retval;
}
@@ -1191,42 +1176,6 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
return rettv.vval.v_list;
}
-/// Prepare profiling for entering a child or something else that is not
-/// counted for the script/function itself.
-/// Should always be called in pair with prof_child_exit().
-///
-/// @param tm place to store waittime
-void prof_child_enter(proftime_T *tm)
-{
- funccall_T *fc = get_current_funccal();
-
- if (fc != NULL && fc->func->uf_profiling) {
- fc->prof_child = profile_start();
- }
-
- script_prof_save(tm);
-}
-
-/// Take care of time spent in a child.
-/// Should always be called after prof_child_enter().
-///
-/// @param tm where waittime was stored
-void prof_child_exit(proftime_T *tm)
-{
- funccall_T *fc = get_current_funccal();
-
- if (fc != NULL && fc->func->uf_profiling) {
- fc->prof_child = profile_end(fc->prof_child);
- // don't count waiting time
- fc->prof_child = profile_sub_wait(*tm, fc->prof_child);
- fc->func->uf_tm_children =
- profile_add(fc->func->uf_tm_children, fc->prof_child);
- fc->func->uf_tml_children =
- profile_add(fc->func->uf_tml_children, fc->prof_child);
- }
- script_prof_restore(tm);
-}
-
/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding
/// it in "*cp". Doesn't give error messages.
int eval_foldexpr(char *arg, int *cp)
@@ -1235,11 +1184,11 @@ int eval_foldexpr(char *arg, int *cp)
varnumber_T retval;
int use_sandbox = was_set_insecurely(curwin, "foldexpr", OPT_LOCAL);
- ++emsg_off;
+ emsg_off++;
if (use_sandbox) {
- ++sandbox;
+ sandbox++;
}
- ++textlock;
+ textlock++;
*cp = NUL;
if (eval0(arg, &tv, NULL, true) == FAIL) {
retval = 0;
@@ -1260,11 +1209,11 @@ int eval_foldexpr(char *arg, int *cp)
}
tv_clear(&tv);
}
- --emsg_off;
+ emsg_off--;
if (use_sandbox) {
- --sandbox;
+ sandbox--;
}
- --textlock;
+ textlock--;
return (int)retval;
}
@@ -1298,16 +1247,11 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
const bool skip, const int flags, const int fne_flags)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
- dictitem_T *v;
- typval_T var1;
- typval_T var2;
- int empty1 = FALSE;
- listitem_T *ni;
- hashtab_T *ht = NULL;
+ bool empty1 = false;
int quiet = flags & GLV_QUIET;
// Clear everything in "lp".
- memset(lp, 0, sizeof(lval_T));
+ CLEAR_POINTER(lp);
if (skip) {
// When skipping just find the end of the name.
@@ -1355,11 +1299,13 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
return p;
}
+ hashtab_T *ht = NULL;
+
// Only pass &ht when we would write to the variable, it prevents autoload
// as well.
- v = find_var(lp->ll_name, lp->ll_name_len,
- (flags & GLV_READ_ONLY) ? NULL : &ht,
- flags & GLV_NO_AUTOLOAD);
+ dictitem_T *v = find_var(lp->ll_name, lp->ll_name_len,
+ (flags & GLV_READ_ONLY) ? NULL : &ht,
+ flags & GLV_NO_AUTOLOAD);
if (v == NULL && !quiet) {
semsg(_("E121: Undefined variable: %.*s"),
(int)lp->ll_name_len, lp->ll_name);
@@ -1370,7 +1316,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
// Loop until no more [idx] or .key is following.
lp->ll_tv = &v->di_tv;
+ typval_T var1;
var1.v_type = VAR_UNKNOWN;
+ typval_T var2;
var2.v_type = VAR_UNKNOWN;
while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) {
if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
@@ -1612,7 +1560,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string.
tv_clear(&var2);
if (lp->ll_n2 < 0) {
- ni = tv_list_find(lp->ll_list, (int)lp->ll_n2);
+ listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2);
if (ni == NULL) {
if (!quiet) {
semsg(_(e_listidx), (int64_t)lp->ll_n2);
@@ -1765,9 +1713,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
ll_n1++;
}
- /*
- * Assign the List values to the list items.
- */
+ // Assign the List values to the list items.
for (ri = tv_list_first(rettv->vval.v_list); ri != NULL;) {
if (op != NULL && *op != '=') {
eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op);
@@ -1888,7 +1834,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
}
if (skip) {
- ++emsg_skip;
+ emsg_skip++;
}
if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
*errp = false;
@@ -1930,7 +1876,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
}
}
if (skip) {
- --emsg_skip;
+ emsg_skip--;
}
return fi;
@@ -2009,7 +1955,7 @@ void free_for_info(void *fi_void)
void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
FUNC_ATTR_NONNULL_ALL
{
- int got_eq = FALSE;
+ bool got_eq = false;
int c;
char *p;
@@ -2035,7 +1981,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
if (c == '&') {
c = (uint8_t)xp->xp_pattern[1];
if (c == '&') {
- ++xp->xp_pattern;
+ xp->xp_pattern++;
xp->xp_context = cmdidx != CMD_let || got_eq
? EXPAND_EXPRESSION : EXPAND_NOTHING;
} else if (c != ' ') {
@@ -2048,7 +1994,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
// environment variable
xp->xp_context = EXPAND_ENV_VARS;
} else if (c == '=') {
- got_eq = TRUE;
+ got_eq = true;
xp->xp_context = EXPAND_EXPRESSION;
} else if (c == '#'
&& xp->xp_context == EXPAND_EXPRESSION) {
@@ -2073,7 +2019,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
xp->xp_context = EXPAND_NOTHING;
} else if (c == '|') {
if (xp->xp_pattern[1] == '|') {
- ++xp->xp_pattern;
+ xp->xp_pattern++;
xp->xp_context = EXPAND_EXPRESSION;
} else {
xp->xp_context = EXPAND_COMMANDS;
@@ -2123,11 +2069,9 @@ void del_menutrans_vars(void)
hash_unlock(&globvarht);
}
-/*
- * Local string buffer for the next two functions to store a variable name
- * with its prefix. Allocated in cat_prefix_varname(), freed later in
- * get_user_var_name().
- */
+/// Local string buffer for the next two functions to store a variable name
+/// with its prefix. Allocated in cat_prefix_varname(), freed later in
+/// get_user_var_name().
static char *varnamebuf = NULL;
static size_t varnamebuflen = 0;
@@ -2171,10 +2115,10 @@ char *get_user_var_name(expand_T *xp, int idx)
if (gdone++ == 0) {
hi = globvarht.ht_array;
} else {
- ++hi;
+ hi++;
}
while (HASHITEM_EMPTY(hi)) {
- ++hi;
+ hi++;
}
if (STRNCMP("g:", xp->xp_pattern, 2) == 0) {
return cat_prefix_varname('g', (char *)hi->hi_key);
@@ -2188,10 +2132,10 @@ char *get_user_var_name(expand_T *xp, int idx)
if (bdone++ == 0) {
hi = ht->ht_array;
} else {
- ++hi;
+ hi++;
}
while (HASHITEM_EMPTY(hi)) {
- ++hi;
+ hi++;
}
return cat_prefix_varname('b', (char *)hi->hi_key);
}
@@ -2202,10 +2146,10 @@ char *get_user_var_name(expand_T *xp, int idx)
if (wdone++ == 0) {
hi = ht->ht_array;
} else {
- ++hi;
+ hi++;
}
while (HASHITEM_EMPTY(hi)) {
- ++hi;
+ hi++;
}
return cat_prefix_varname('w', (char *)hi->hi_key);
}
@@ -2216,10 +2160,10 @@ char *get_user_var_name(expand_T *xp, int idx)
if (tdone++ == 0) {
hi = ht->ht_array;
} else {
- ++hi;
+ hi++;
}
while (HASHITEM_EMPTY(hi)) {
- ++hi;
+ hi++;
}
return cat_prefix_varname('t', (char *)hi->hi_key);
}
@@ -2291,7 +2235,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
funcexe.evaluate = evaluate;
funcexe.partial = partial;
funcexe.basetv = basetv;
- int ret = get_func_tv((char_u *)s, len, rettv, (char_u **)arg, &funcexe);
+ int ret = get_func_tv((char_u *)s, len, rettv, arg, &funcexe);
xfree(s);
@@ -2317,11 +2261,9 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
// TODO(ZyX-I): move to eval/expressions
-/*
- * The "evaluate" argument: When FALSE, the argument is only parsed but not
- * executed. The function may return OK, but the rettv will be of type
- * VAR_UNKNOWN. The function still returns FAIL for a syntax error.
- */
+/// The "evaluate" argument: When FALSE, the argument is only parsed but not
+/// executed. The function may return OK, but the rettv will be of type
+/// VAR_UNKNOWN. The function still returns FAIL for a syntax error.
/// Handle zero level expression.
/// This calls eval1() and handles error message and nextcmd.
@@ -2372,18 +2314,16 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
/// @return OK or FAIL.
int eval1(char **arg, typval_T *rettv, int evaluate)
{
- int result;
+ bool result;
typval_T var2;
- /*
- * Get the first variable.
- */
+ // Get the first variable.
if (eval2(arg, rettv, evaluate) == FAIL) {
return FAIL;
}
if ((*arg)[0] == '?') {
- result = FALSE;
+ result = false;
if (evaluate) {
bool error = false;
@@ -2396,17 +2336,13 @@ int eval1(char **arg, typval_T *rettv, int evaluate)
}
}
- /*
- * Get the second variable.
- */
+ // Get the second variable.
*arg = skipwhite(*arg + 1);
if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive!
return FAIL;
}
- /*
- * Check for the ":".
- */
+ // Check for the ":".
if ((*arg)[0] != ':') {
emsg(_("E109: Missing ':' after '?'"));
if (evaluate && result) {
@@ -2415,9 +2351,7 @@ int eval1(char **arg, typval_T *rettv, int evaluate)
return FAIL;
}
- /*
- * Get the third variable.
- */
+ // Get the third variable.
*arg = skipwhite(*arg + 1);
if (eval1(arg, &var2, evaluate && !result) == FAIL) { // Recursive!
if (evaluate && result) {
@@ -2445,22 +2379,16 @@ int eval1(char **arg, typval_T *rettv, int evaluate)
static int eval2(char **arg, typval_T *rettv, int evaluate)
{
typval_T var2;
- long result;
- int first;
bool error = false;
- /*
- * Get the first variable.
- */
+ // Get the first variable.
if (eval3(arg, rettv, evaluate) == FAIL) {
return FAIL;
}
- /*
- * Repeat until there is no following "||".
- */
- first = TRUE;
- result = FALSE;
+ // Repeat until there is no following "||".
+ bool first = true;
+ bool result = false;
while ((*arg)[0] == '|' && (*arg)[1] == '|') {
if (evaluate && first) {
if (tv_get_number_chk(rettv, &error) != 0) {
@@ -2473,17 +2401,13 @@ static int eval2(char **arg, typval_T *rettv, int evaluate)
first = false;
}
- /*
- * Get the second variable.
- */
+ // Get the second variable.
*arg = skipwhite(*arg + 2);
if (eval3(arg, &var2, evaluate && !result) == FAIL) {
return FAIL;
}
- /*
- * Compute the result.
- */
+ // Compute the result.
if (evaluate && !result) {
if (tv_get_number_chk(&var2, &error) != 0) {
result = true;
@@ -2514,22 +2438,16 @@ static int eval2(char **arg, typval_T *rettv, int evaluate)
static int eval3(char **arg, typval_T *rettv, int evaluate)
{
typval_T var2;
- long result;
- int first;
bool error = false;
- /*
- * Get the first variable.
- */
+ // Get the first variable.
if (eval4(arg, rettv, evaluate) == FAIL) {
return FAIL;
}
- /*
- * Repeat until there is no following "&&".
- */
- first = TRUE;
- result = TRUE;
+ // Repeat until there is no following "&&".
+ bool first = true;
+ bool result = true;
while ((*arg)[0] == '&' && (*arg)[1] == '&') {
if (evaluate && first) {
if (tv_get_number_chk(rettv, &error) == 0) {
@@ -2542,17 +2460,13 @@ static int eval3(char **arg, typval_T *rettv, int evaluate)
first = false;
}
- /*
- * Get the second variable.
- */
+ // Get the second variable.
*arg = skipwhite(*arg + 2);
if (eval4(arg, &var2, evaluate && result) == FAIL) {
return FAIL;
}
- /*
- * Compute the result.
- */
+ // Compute the result.
if (evaluate && result) {
if (tv_get_number_chk(&var2, &error) == 0) {
result = false;
@@ -2597,9 +2511,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
int len = 2;
bool ic;
- /*
- * Get the first variable.
- */
+ // Get the first variable.
if (eval5(arg, rettv, evaluate) == FAIL) {
return FAIL;
}
@@ -2648,9 +2560,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
break;
}
- /*
- * If there is a comparative operator, use it.
- */
+ // If there is a comparative operator, use it.
if (type != EXPR_UNKNOWN) {
// extra question mark appended: ignore case
if (p[len] == '?') {
@@ -2701,16 +2611,12 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
float_T f1 = 0, f2 = 0;
char *p;
- /*
- * Get the first variable.
- */
- if (eval6(arg, rettv, evaluate, FALSE) == FAIL) {
+ // Get the first variable.
+ if (eval6(arg, rettv, evaluate, false) == FAIL) {
return FAIL;
}
- /*
- * Repeat computing, until no '+', '-' or '.' is following.
- */
+ // Repeat computing, until no '+', '-' or '.' is following.
for (;;) {
op = (char_u)(**arg);
if (op != '+' && op != '-' && op != '.') {
@@ -2732,9 +2638,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
}
}
- /*
- * Get the second variable.
- */
+ // Get the second variable.
if (op == '.' && *(*arg + 1) == '.') { // ..string concatenation
(*arg)++;
}
@@ -2745,9 +2649,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
}
if (evaluate) {
- /*
- * Compute the result.
- */
+ // Compute the result.
if (op == '.') {
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
@@ -2876,16 +2778,12 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
float_T f1 = 0, f2 = 0;
bool error = false;
- /*
- * Get the first variable.
- */
+ // Get the first variable.
if (eval7(arg, rettv, evaluate, want_string) == FAIL) {
return FAIL;
}
- /*
- * Repeat computing, until no '*', '/' or '%' is following.
- */
+ // Repeat computing, until no '*', '/' or '%' is following.
for (;;) {
op = (char_u)(**arg);
if (op != '*' && op != '/' && op != '%') {
@@ -2908,9 +2806,7 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
n1 = 0;
}
- /*
- * Get the second variable.
- */
+ // Get the second variable.
*arg = skipwhite(*arg + 1);
if (eval7(arg, &var2, evaluate, false) == FAIL) {
return FAIL;
@@ -2935,10 +2831,8 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
}
}
- /*
- * Compute the result.
- * When either side is a float the result is a float.
- */
+ // Compute the result.
+ // When either side is a float the result is a float.
if (use_float) {
if (op == '*') {
f1 = f1 * f2;
@@ -3051,9 +2945,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
get_float = true;
p = skipdigits(p + 2);
if (*p == 'e' || *p == 'E') {
- ++p;
+ p++;
if (*p == '-' || *p == '+') {
- ++p;
+ p++;
}
if (!ascii_isdigit(*p)) {
get_float = false;
@@ -3147,7 +3041,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Lambda: {arg, arg -> expr}
// Dictionary: {'key': val, 'key': val}
case '{':
- ret = get_lambda_tv((char_u **)arg, rettv, evaluate);
+ ret = get_lambda_tv(arg, rettv, evaluate);
if (ret == NOTDONE) {
ret = dict_get_tv(arg, rettv, evaluate, false);
}
@@ -3164,15 +3058,12 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Register contents: @r.
case '@':
- ++*arg;
+ (*arg)++;
int regname = mb_cptr2char_adv((const char_u**) arg);
if (evaluate) {
rettv->v_type = VAR_STRING;
rettv->vval.v_string = get_reg_contents(regname, kGRegExprSrc);
}
- // if (**arg != NUL) {
- // ++*arg;
- // }
break;
// nested expression: (expression).
@@ -3180,7 +3071,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
*arg = skipwhite(*arg + 1);
ret = eval1(arg, rettv, evaluate); // recursive!
if (**arg == ')') {
- ++*arg;
+ (*arg)++;
} else if (ret == OK) {
emsg(_("E110: Missing ')'"));
tv_clear(rettv);
@@ -3326,7 +3217,7 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
funcexe.selfdict = selfdict;
funcexe.basetv = basetv;
const int ret = get_func_tv((char_u *)funcname, is_lua ? (int)(*arg - funcname) : -1, rettv,
- (char_u **)arg, &funcexe);
+ arg, &funcexe);
// Clear the funcref afterwards, so that deleting it while
// evaluating the arguments is possible (see test55).
@@ -3354,7 +3245,7 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu
typval_T base = *rettv;
rettv->v_type = VAR_UNKNOWN;
- int ret = get_lambda_tv((char_u **)arg, rettv, evaluate);
+ int ret = get_lambda_tv(arg, rettv, evaluate);
if (ret != OK) {
return FAIL;
} else if (**arg != '(') {
@@ -3504,9 +3395,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
typval_T var1 = TV_INITIAL_VALUE;
typval_T var2 = TV_INITIAL_VALUE;
if (**arg == '.') {
- /*
- * dict.name
- */
+ // dict.name
key = *arg + 1;
for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) {}
if (len == 0) {
@@ -3514,11 +3403,9 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
}
*arg = skipwhite(key + len);
} else {
- /*
- * something[idx]
- *
- * Get the (first) variable from inside the [].
- */
+ // something[idx]
+ //
+ // Get the (first) variable from inside the [].
*arg = skipwhite(*arg + 1);
if (**arg == ':') {
empty1 = true;
@@ -3530,9 +3417,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
return FAIL;
}
- /*
- * Get the second variable from inside the [:].
- */
+ // Get the second variable from inside the [:].
if (**arg == ':') {
range = true;
*arg = skipwhite(*arg + 1);
@@ -3773,11 +3658,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate)
FUNC_ATTR_NONNULL_ARG(1)
{
- long numval;
- char *stringval;
- getoption_T opt_type;
bool working = (**arg == '+'); // has("+option")
- int ret = OK;
int opt_flags;
// Isolate the option name and find its value.
@@ -3794,10 +3675,14 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
return OK;
}
+ long numval;
+ char *stringval;
+ int ret = OK;
+
char c = *option_end;
*option_end = NUL;
- opt_type = get_option_value(*arg, &numval,
- rettv == NULL ? NULL : &stringval, opt_flags);
+ getoption_T opt_type = get_option_value(*arg, &numval,
+ rettv == NULL ? NULL : &stringval, opt_flags);
if (opt_type == gov_unknown) {
if (rettv != NULL) {
@@ -3838,9 +3723,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
char *p;
unsigned int extra = 0;
- /*
- * Find the end of the string, skipping backslashed characters.
- */
+ // Find the end of the string, skipping backslashed characters.
for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
if (*p == '\\' && p[1] != NUL) {
p++;
@@ -3864,10 +3747,8 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
return OK;
}
- /*
- * Copy the string into allocated memory, handling backslashed
- * characters.
- */
+ // Copy the string into allocated memory, handling backslashed
+ // characters.
const int len = (int)(p - *arg + extra);
char *name = xmalloc((size_t)len);
rettv->v_type = VAR_STRING;
@@ -3906,7 +3787,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
}
nr = 0;
while (--n >= 0 && ascii_isxdigit(p[1])) {
- ++p;
+ p++;
nr = (nr << 4) + hex2nr(*p);
}
p++;
@@ -3936,7 +3817,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
*name = (char)((*name << 3) + *p++ - '0');
}
}
- ++name;
+ name++;
break;
// Special key, e.g.: "\<C-W>"
@@ -3958,11 +3839,11 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
FALLTHROUGH;
default:
- mb_copy_char((const char_u **)&p, (char_u **)&name);
+ mb_copy_char((const char **)&p, &name);
break;
}
} else {
- mb_copy_char((const char_u **)&p, (char_u **)&name);
+ mb_copy_char((const char **)&p, &name);
}
}
*name = NUL;
@@ -3980,19 +3861,16 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate)
{
char *p;
- char *str;
int reduce = 0;
- /*
- * Find the end of the string, skipping ''.
- */
+ // Find the end of the string, skipping ''.
for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p)) {
if (*p == '\'') {
if (p[1] != '\'') {
break;
}
- ++reduce;
- ++p;
+ reduce++;
+ p++;
}
}
@@ -4007,10 +3885,8 @@ static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate)
return OK;
}
- /*
- * Copy the string into allocated memory, handling '' to ' reduction.
- */
- str = xmalloc((size_t)((p - *arg) - reduce));
+ // Copy the string into allocated memory, handling '' to ' reduction.
+ char *str = xmalloc((size_t)((p - *arg) - reduce));
rettv->v_type = VAR_STRING;
rettv->vval.v_string = str;
@@ -4019,9 +3895,9 @@ static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate)
if (p[1] != '\'') {
break;
}
- ++p;
+ p++;
}
- mb_copy_char((const char_u **)&p, (char_u **)&str);
+ mb_copy_char((const char **)&p, &str);
}
*str = NUL;
*arg = p + 1;
@@ -4120,16 +3996,14 @@ failret:
/// @param ic ignore case
bool func_equal(typval_T *tv1, typval_T *tv2, bool ic)
{
- char_u *s1, *s2;
- dict_T *d1, *d2;
- int a1, a2;
-
// empty and NULL function name considered the same
- s1 = (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial));
+ char_u *s1 =
+ (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial));
if (s1 != NULL && *s1 == NUL) {
s1 = NULL;
}
- s2 = (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial));
+ char_u *s2 =
+ (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial));
if (s2 != NULL && *s2 == NUL) {
s2 = NULL;
}
@@ -4142,8 +4016,8 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic)
}
// empty dict and NULL dict is different
- d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict;
- d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict;
+ dict_T *d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict;
+ dict_T *d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict;
if (d1 == NULL || d2 == NULL) {
if (d1 != d2) {
return false;
@@ -4153,8 +4027,8 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic)
}
// empty list and no list considered the same
- a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc;
- a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc;
+ int a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc;
+ int a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc;
if (a1 != a2) {
return false;
}
@@ -4183,25 +4057,23 @@ int get_copyID(void)
return current_copyID;
}
-/*
- * Garbage collection for lists and dictionaries.
- *
- * We use reference counts to be able to free most items right away when they
- * are no longer used. But for composite items it's possible that it becomes
- * unused while the reference count is > 0: When there is a recursive
- * reference. Example:
- * :let l = [1, 2, 3]
- * :let d = {9: l}
- * :let l[1] = d
- *
- * Since this is quite unusual we handle this with garbage collection: every
- * once in a while find out which lists and dicts are not referenced from any
- * variable.
- *
- * Here is a good reference text about garbage collection (refers to Python
- * but it applies to all reference-counting mechanisms):
- * http://python.ca/nas/python/gc/
- */
+/// Garbage collection for lists and dictionaries.
+///
+/// We use reference counts to be able to free most items right away when they
+/// are no longer used. But for composite items it's possible that it becomes
+/// unused while the reference count is > 0: When there is a recursive
+/// reference. Example:
+/// :let l = [1, 2, 3]
+/// :let d = {9: l}
+/// :let l[1] = d
+///
+/// Since this is quite unusual we handle this with garbage collection: every
+/// once in a while find out which lists and dicts are not referenced from any
+/// variable.
+///
+/// Here is a good reference text about garbage collection (refers to Python
+/// but it applies to all reference-counting mechanisms):
+/// http://python.ca/nas/python/gc/
/// Do garbage collection for lists and dicts.
///
@@ -4220,6 +4092,23 @@ bool garbage_collect(bool testing)
garbage_collect_at_exit = false;
}
+ // The execution stack can grow big, limit the size.
+ if (exestack.ga_maxlen - exestack.ga_len > 500) {
+ // Keep 150% of the current size, with a minimum of the growth size.
+ int n = exestack.ga_len / 2;
+ if (n < exestack.ga_growsize) {
+ n = exestack.ga_growsize;
+ }
+
+ // Don't make it bigger though.
+ if (exestack.ga_len + n < exestack.ga_maxlen) {
+ size_t new_len = (size_t)exestack.ga_itemsize * (size_t)(exestack.ga_len + n);
+ char *pp = xrealloc(exestack.ga_data, new_len);
+ exestack.ga_maxlen = exestack.ga_len + n;
+ exestack.ga_data = pp;
+ }
+ }
+
// We advance by two (COPYID_INC) because we add one for items referenced
// through previous_funccal.
const int copyID = get_copyID();
@@ -4233,7 +4122,7 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_in_previous_funccal)(copyID);
// script-local variables
- for (int i = 1; i <= ga_scripts.ga_len; ++i) {
+ for (int i = 1; i <= ga_scripts.ga_len; i++) {
ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL);
}
@@ -4692,21 +4581,16 @@ static int get_literal_key(char **arg, typval_T *tv)
/// @return OK or FAIL. Returns NOTDONE for {expr}.
static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal)
{
- dict_T *d = NULL;
- typval_T tvkey;
typval_T tv;
char *key = NULL;
- dictitem_T *item;
char *start = skipwhite(*arg + 1);
char buf[NUMBUFLEN];
- /*
- * First check if it's not a curly-braces thing: {expr}.
- * Must do this without evaluating, otherwise a function may be called
- * twice. Unfortunately this means we need to call eval1() twice for the
- * first item.
- * But {} is an empty Dictionary.
- */
+ // First check if it's not a curly-braces thing: {expr}.
+ // Must do this without evaluating, otherwise a function may be called
+ // twice. Unfortunately this means we need to call eval1() twice for the
+ // first item.
+ // But {} is an empty Dictionary.
if (*start != '}') {
if (eval1(&start, &tv, false) == FAIL) { // recursive!
return FAIL;
@@ -4716,9 +4600,11 @@ static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal)
}
}
+ dict_T *d = NULL;
if (evaluate) {
d = tv_dict_alloc();
}
+ typval_T tvkey;
tvkey.v_type = VAR_UNKNOWN;
tv.v_type = VAR_UNKNOWN;
@@ -4751,7 +4637,7 @@ static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal)
goto failret;
}
if (evaluate) {
- item = tv_dict_find(d, (const char *)key, -1);
+ dictitem_T *item = tv_dict_find(d, (const char *)key, -1);
if (item != NULL) {
semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key);
tv_clear(&tvkey);
@@ -4806,8 +4692,6 @@ failret:
size_t string2float(const char *const text, float_T *const ret_value)
FUNC_ATTR_NONNULL_ALL
{
- char *s = NULL;
-
// MS-Windows does not deal with "inf" and "nan" properly
if (STRNICMP(text, "inf", 3) == 0) {
*ret_value = (float_T)INFINITY;
@@ -4821,6 +4705,7 @@ size_t string2float(const char *const text, float_T *const ret_value)
*ret_value = (float_T)NAN;
return 3;
}
+ char *s = NULL;
*ret_value = strtod(text, &s);
return (size_t)(s - text);
}
@@ -4834,23 +4719,18 @@ size_t string2float(const char *const text, float_T *const ret_value)
/// @return FAIL if the name is invalid.
static int get_env_tv(char **arg, typval_T *rettv, int evaluate)
{
- char *name;
- char *string = NULL;
- int len;
- int cc;
-
- ++*arg;
- name = *arg;
- len = get_env_len((const char **)arg);
+ (*arg)++;
+ char *name = *arg;
+ int len = get_env_len((const char **)arg);
if (evaluate) {
if (len == 0) {
return FAIL; // Invalid empty name.
}
- cc = (char_u)name[len];
+ int cc = (int)name[len];
name[len] = NUL;
// First try vim_getenv(), fast for normal environment vars.
- string = vim_getenv(name);
+ char *string = vim_getenv(name);
if (string == NULL || *string == NUL) {
xfree(string);
@@ -4868,18 +4748,6 @@ static int get_env_tv(char **arg, typval_T *rettv, int evaluate)
return OK;
}
-/// Get the argument list for a given window
-void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv)
-{
- tv_list_alloc_ret(rettv, argcount);
- if (arglist != NULL) {
- for (int idx = 0; idx < argcount; idx++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)alist_name(&arglist[idx]), -1);
- }
- }
-}
-
/// Add an assert error to v:errors.
void assert_error(garray_T *gap)
{
@@ -4909,17 +4777,10 @@ win_T *find_win_by_nr_or_id(typval_T *vp)
/// Implementation of map() and filter().
void filter_map(typval_T *argvars, typval_T *rettv, int map)
{
- typval_T *expr;
list_T *l = NULL;
- dictitem_T *di;
- hashtab_T *ht;
- hashitem_T *hi;
dict_T *d = NULL;
- typval_T save_val;
- typval_T save_key;
blob_T *b = NULL;
int rem = false;
- int todo;
char *ermsg = map ? "map()" : "filter()";
const char *const arg_errmsg = (map
? N_("map() argument")
@@ -4950,18 +4811,20 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
return;
}
- expr = &argvars[1];
+ typval_T *expr = &argvars[1];
// On type errors, the preceding call has already displayed an error
// message. Avoid a misleading error message for an empty string that
// was not passed as argument.
if (expr->v_type != VAR_UNKNOWN) {
+ typval_T save_val;
prepare_vimvar(VV_VAL, &save_val);
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
save_did_emsg = did_emsg;
- did_emsg = FALSE;
+ did_emsg = false;
+ typval_T save_key;
prepare_vimvar(VV_KEY, &save_key);
if (argvars[0].v_type == VAR_DICT) {
vimvars[VV_KEY].vv_type = VAR_STRING;
@@ -4970,14 +4833,14 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
if (map && d->dv_lock == VAR_UNLOCKED) {
d->dv_lock = VAR_LOCKED;
}
- ht = &d->dv_hashtab;
+ hashtab_T *ht = &d->dv_hashtab;
hash_lock(ht);
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0; ++hi) {
+ int todo = (int)ht->ht_used;
+ for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- --todo;
+ todo--;
- di = TV_DICT_HI2DI(hi);
+ dictitem_T *di = TV_DICT_HI2DI(hi);
if (map
&& (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
@@ -5126,7 +4989,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr
if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
name = s;
- trans_name = (char *)trans_function_name((char_u **)&name, false,
+ trans_name = (char *)trans_function_name(&name, false,
TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD
| TFN_NO_DEREF, NULL, NULL);
if (*name != NUL) {
@@ -5330,29 +5193,6 @@ linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf)
return (linenr_T)tv_get_number_chk(tv, NULL);
}
-void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv)
-{
- if (what_arg->v_type == VAR_UNKNOWN) {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- if (is_qf || wp != NULL) {
- (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list);
- }
- } else {
- tv_dict_alloc_ret(rettv);
- if (is_qf || wp != NULL) {
- if (what_arg->v_type == VAR_DICT) {
- dict_T *d = what_arg->vval.v_dict;
-
- if (d != NULL) {
- qf_get_properties(wp, d, rettv->vval.v_dict);
- }
- } else {
- emsg(_(e_dictreq));
- }
- }
- }
-}
-
/// @return information (variables, options, etc.) about a tab page
/// as a dictionary.
dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
@@ -5689,11 +5529,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T
FUNC_ATTR_NONNULL_ARG(4, 5)
{
linenr_T lnum = lnum_arg + (append ? 1 : 0);
- const char *line = NULL;
- list_T *l = NULL;
- listitem_T *li = NULL;
long added = 0;
- linenr_T append_lnum;
buf_T *curbuf_save = NULL;
win_T *curwin_save = NULL;
const bool is_curbuf = buf == curbuf;
@@ -5715,6 +5551,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T
find_win_for_curbuf();
}
+ linenr_T append_lnum;
if (append) {
// appendbufline() uses the line number below which we insert
append_lnum = lnum - 1;
@@ -5724,6 +5561,9 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T
append_lnum = curbuf->b_ml.ml_line_count;
}
+ list_T *l = NULL;
+ listitem_T *li = NULL;
+ const char *line = NULL;
if (lines->v_type == VAR_LIST) {
l = lines->vval.v_list;
li = tv_list_first(l);
@@ -5807,7 +5647,6 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T
void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
FUNC_ATTR_NONNULL_ALL
{
- const void *iter = NULL;
list_T *const list = tv_list_alloc(kListLenShouldKnow);
rettv->v_type = VAR_LIST;
rettv->vval.v_list = list;
@@ -5816,6 +5655,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
if (dirs == NULL) {
return;
}
+ const void *iter = NULL;
do {
size_t dir_len;
const char *dir;
@@ -5925,9 +5765,9 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
#ifdef USE_CRNL
// translate <CR><NL> into <NL>
char *d = res;
- for (char *s = res; *s; ++s) {
+ for (char *s = res; *s; s++) {
if (s[0] == CAR && s[1] == NL) {
- ++s;
+ s++;
}
*d++ = *s;
@@ -6499,12 +6339,9 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
// Argument can be [lnum, col, coladd].
if (tv->v_type == VAR_LIST) {
- list_T *l;
- int len;
bool error = false;
- listitem_T *li;
- l = tv->vval.v_list;
+ list_T *l = tv->vval.v_list;
if (l == NULL) {
return NULL;
}
@@ -6521,6 +6358,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
if (error) {
return NULL;
}
+ int len;
if (charcol) {
len = mb_charlen(ml_get(pos.lnum));
} else {
@@ -6528,7 +6366,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
// We accept "$" for the column number: last column.
- li = tv_list_find(l, 1L);
+ listitem_T *li = tv_list_find(l, 1L);
if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING
&& TV_LIST_ITEM_TV(li)->vval.v_string != NULL
&& STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) {
@@ -6628,8 +6466,6 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol)
{
list_T *l;
- int i = 0;
- long n;
// List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only
// there when "fnump" isn't NULL; "coladd" and "curswant" are optional.
@@ -6640,6 +6476,8 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
return FAIL;
}
+ int i = 0;
+ long n;
if (fnump != NULL) {
n = tv_list_find_nr(l, i++, NULL); // fnum
if (n < 0) {
@@ -6692,15 +6530,13 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
/// @return 0 for error.
int get_env_len(const char **arg)
{
- int len;
-
const char *p;
for (p = *arg; vim_isIDc(*p); p++) {}
if (p == *arg) { // No name found.
return 0;
}
- len = (int)(p - *arg);
+ int len = (int)(p - *arg);
*arg = p;
return len;
}
@@ -6748,8 +6584,6 @@ int get_id_len(const char **const arg)
/// 0 if something else is wrong.
int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbose)
{
- int len;
-
*alias = NULL; // default to no alias
if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA
@@ -6758,7 +6592,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
*arg += 3;
return get_id_len(arg) + 3;
}
- len = eval_fname_script(*arg);
+ int len = eval_fname_script(*arg);
if (len > 0) {
// literal "<SID>", "s:" or "<SNR>"
*arg += len;
@@ -6776,10 +6610,8 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
return len;
}
- /*
- * Include any <SID> etc in the expanded string:
- * Thus the -len here.
- */
+ // Include any <SID> etc in the expanded string:
+ // Thus the -len here.
char *temp_string = make_expanded_name(*arg - len, expr_start, expr_end, (char *)p);
if (temp_string == NULL) {
return -1;
@@ -6811,10 +6643,6 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
const char *find_name_end(const char *arg, const char **expr_start, const char **expr_end,
int flags)
{
- int mb_nest = 0;
- int br_nest = 0;
- int len;
-
if (expr_start != NULL) {
*expr_start = NULL;
*expr_end = NULL;
@@ -6825,6 +6653,10 @@ const char *find_name_end(const char *arg, const char **expr_start, const char *
return arg;
}
+ int mb_nest = 0;
+ int br_nest = 0;
+ int len;
+
const char *p;
for (p = arg; *p != NUL
&& (eval_isnamec(*p)
@@ -6842,7 +6674,7 @@ const char *find_name_end(const char *arg, const char **expr_start, const char *
// skip over "str\"ing" to avoid counting [ and ] inside it.
for (p = p + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
if (*p == '\\' && p[1] != NUL) {
- ++p;
+ p++;
}
}
if (*p == NUL) {
@@ -6860,9 +6692,9 @@ const char *find_name_end(const char *arg, const char **expr_start, const char *
if (mb_nest == 0) {
if (*p == '[') {
- ++br_nest;
+ br_nest++;
} else if (*p == ']') {
- --br_nest;
+ br_nest--;
}
}
@@ -6898,20 +6730,19 @@ const char *find_name_end(const char *arg, const char **expr_start, const char *
static char *make_expanded_name(const char *in_start, char *expr_start, char *expr_end,
char *in_end)
{
- char c1;
- char *retval = NULL;
- char *temp_result;
- char *nextcmd = NULL;
-
if (expr_end == NULL || in_end == NULL) {
return NULL;
}
+
+ char *retval = NULL;
+ char *nextcmd = NULL;
+
*expr_start = NUL;
*expr_end = NUL;
- c1 = *in_end;
+ char c1 = *in_end;
*in_end = NUL;
- temp_result = eval_to_string(expr_start + 1, &nextcmd, false);
+ char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false);
if (temp_result != NULL && nextcmd == NULL) {
retval = xmalloc(STRLEN(temp_result) + (size_t)(expr_start - in_start)
+ (size_t)(in_end - expr_end) + 1);
@@ -7354,7 +7185,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
if (rettv->v_type == VAR_DICT) {
selfdict = rettv->vval.v_dict;
if (selfdict != NULL) {
- ++selfdict->dv_refcount;
+ selfdict->dv_refcount++;
}
} else {
selfdict = NULL;
@@ -7431,8 +7262,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va
const size_t varname_len, int no_autoload)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- hashitem_T *hi;
-
if (varname_len == 0) {
// Must be something like "s:", otherwise "ht" would be NULL.
switch (htname) {
@@ -7456,7 +7285,7 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va
return NULL;
}
- hi = hash_find_len(ht, varname, varname_len);
+ hashitem_T *hi = hash_find_len(ht, varname, varname_len);
if (HASHITEM_EMPTY(hi)) {
// For global variables we may try auto-loading the script. If it
// worked find the variable again. Don't auto-load a script if it was
@@ -7491,7 +7320,6 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va
hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char **varname,
dict_T **d)
{
- hashitem_T *hi;
funccall_T *funccal = get_funccal();
*d = NULL;
@@ -7507,7 +7335,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
*varname = name;
// "version" is "v:version" in all scopes
- hi = hash_find_len(&compat_hashtab, name, name_len);
+ hashitem_T *hi = hash_find_len(&compat_hashtab, name, name_len);
if (!HASHITEM_EMPTY(hi)) {
return &compat_hashtab;
}
@@ -7596,28 +7424,26 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var
/// sourcing this script and when executing functions defined in the script.
void new_script_vars(scid_T id)
{
- hashtab_T *ht;
scriptvar_T *sv;
ga_grow(&ga_scripts, id - ga_scripts.ga_len);
- {
- /* Re-allocating ga_data means that an ht_array pointing to
- * ht_smallarray becomes invalid. We can recognize this: ht_mask is
- * at its init value. Also reset "v_dict", it's always the same. */
- for (int i = 1; i <= ga_scripts.ga_len; ++i) {
- ht = &SCRIPT_VARS(i);
- if (ht->ht_mask == HT_INIT_SIZE - 1) {
- ht->ht_array = ht->ht_smallarray;
- }
- sv = SCRIPT_SV(i);
- sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict;
- }
- while (ga_scripts.ga_len < id) {
- sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T));
- init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
- ++ga_scripts.ga_len;
+ // Re-allocating ga_data means that an ht_array pointing to
+ // ht_smallarray becomes invalid. We can recognize this: ht_mask is
+ // at its init value. Also reset "v_dict", it's always the same.
+ for (int i = 1; i <= ga_scripts.ga_len; i++) {
+ hashtab_T *ht = &SCRIPT_VARS(i);
+ if (ht->ht_mask == HT_INIT_SIZE - 1) {
+ ht->ht_array = ht->ht_smallarray;
}
+ sv = SCRIPT_SV(i);
+ sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict;
+ }
+
+ while (ga_scripts.ga_len < id) {
+ sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T));
+ init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
+ ga_scripts.ga_len++;
}
}
@@ -7641,8 +7467,8 @@ void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, ScopeType scope)
/// Unreference a dictionary initialized by init_var_dict().
void unref_var_dict(dict_T *dict)
{
- /* Now the dict needs to be freed if no one else is using it, go back to
- * normal reference counting. */
+ // Now the dict needs to be freed if no one else is using it, go back to
+ // normal reference counting.
dict->dv_refcount -= DO_NOT_FREE_CNT - 1;
tv_dict_unref(dict);
}
@@ -7674,7 +7500,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
emsg(_("E698: variable nested too deep for making a copy"));
return FAIL;
}
- ++recurse;
+ recurse++;
switch (from->v_type) {
case VAR_NUMBER:
@@ -7727,7 +7553,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
} else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) {
// use the copy made earlier
to->vval.v_dict = from->vval.v_dict->dv_copydict;
- ++to->vval.v_dict->dv_refcount;
+ to->vval.v_dict->dv_refcount++;
} else {
to->vval.v_dict = tv_dict_copy(conv, from->vval.v_dict, deep, copyID);
}
@@ -7739,7 +7565,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
internal_error("var_item_copy(UNKNOWN)");
ret = FAIL;
}
- --recurse;
+ recurse--;
return ret;
}
@@ -7756,7 +7582,7 @@ void ex_echo(exarg_T *eap)
const int called_emsg_before = called_emsg;
if (eap->skip) {
- ++emsg_skip;
+ emsg_skip++;
}
while (*arg != NUL && *arg != '|' && *arg != '\n' && !got_int) {
// If eval1() causes an error message the text from the command may
@@ -7836,12 +7662,11 @@ void ex_execute(exarg_T *eap)
typval_T rettv;
int ret = OK;
garray_T ga;
- int save_did_emsg;
ga_init(&ga, 1, 80);
if (eap->skip) {
- ++emsg_skip;
+ emsg_skip++;
}
while (*arg != NUL && *arg != '|' && *arg != '\n') {
ret = eval1_emsg(&arg, &rettv, !eap->skip);
@@ -7885,7 +7710,7 @@ void ex_execute(exarg_T *eap)
ui_flush();
} else if (eap->cmdidx == CMD_echoerr) {
// We don't want to abort following commands, restore did_emsg.
- save_did_emsg = did_emsg;
+ int save_did_emsg = did_emsg;
msg_ext_set_kind("echoerr");
emsg(ga.ga_data);
if (!force_abort) {
@@ -7899,7 +7724,7 @@ void ex_execute(exarg_T *eap)
ga_clear(&ga);
if (eap->skip) {
- --emsg_skip;
+ emsg_skip--;
}
eap->nextcmd = (char *)check_nextcmd((char_u *)arg);
@@ -7915,7 +7740,7 @@ const char *find_option_end(const char **const arg, int *const opt_flags)
{
const char *p = *arg;
- ++p;
+ p++;
if (*p == 'g' && p[1] == ':') {
*opt_flags = OPT_GLOBAL;
p += 2;
@@ -7941,174 +7766,6 @@ const char *find_option_end(const char **const arg, int *const opt_flags)
return p;
}
-/// Start profiling function "fp".
-void func_do_profile(ufunc_T *fp)
-{
- int len = fp->uf_lines.ga_len;
-
- if (!fp->uf_prof_initialized) {
- if (len == 0) {
- len = 1; // avoid getting error for allocating zero bytes
- }
- fp->uf_tm_count = 0;
- fp->uf_tm_self = profile_zero();
- fp->uf_tm_total = profile_zero();
-
- if (fp->uf_tml_count == NULL) {
- fp->uf_tml_count = xcalloc((size_t)len, sizeof(int));
- }
-
- if (fp->uf_tml_total == NULL) {
- fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T));
- }
-
- if (fp->uf_tml_self == NULL) {
- fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T));
- }
-
- fp->uf_tml_idx = -1;
- fp->uf_prof_initialized = true;
- }
-
- fp->uf_profiling = TRUE;
-}
-
-/// Dump the profiling results for all functions in file "fd".
-void func_dump_profile(FILE *fd)
-{
- hashitem_T *hi;
- int todo;
- ufunc_T *fp;
- ufunc_T **sorttab;
- int st_len = 0;
-
- todo = (int)func_hashtab.ht_used;
- if (todo == 0) {
- return; // nothing to dump
- }
-
- sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
-
- for (hi = func_hashtab.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- fp = HI2UF(hi);
- if (fp->uf_prof_initialized) {
- sorttab[st_len++] = fp;
-
- if (fp->uf_name[0] == K_SPECIAL) {
- fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3);
- } else {
- fprintf(fd, "FUNCTION %s()\n", fp->uf_name);
- }
- if (fp->uf_script_ctx.sc_sid != 0) {
- bool should_free;
- const LastSet last_set = (LastSet){
- .script_ctx = fp->uf_script_ctx,
- .channel_id = 0,
- };
- char *p = (char *)get_scriptname(last_set, &should_free);
- fprintf(fd, " Defined: %s:%" PRIdLINENR "\n",
- p, fp->uf_script_ctx.sc_lnum);
- if (should_free) {
- xfree(p);
- }
- }
- if (fp->uf_tm_count == 1) {
- fprintf(fd, "Called 1 time\n");
- } else {
- fprintf(fd, "Called %d times\n", fp->uf_tm_count);
- }
- fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total));
- fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self));
- fprintf(fd, "\n");
- fprintf(fd, "count total (s) self (s)\n");
-
- for (int i = 0; i < fp->uf_lines.ga_len; ++i) {
- if (FUNCLINE(fp, i) == NULL) {
- continue;
- }
- prof_func_line(fd, fp->uf_tml_count[i],
- &fp->uf_tml_total[i], &fp->uf_tml_self[i], TRUE);
- fprintf(fd, "%s\n", FUNCLINE(fp, i));
- }
- fprintf(fd, "\n");
- }
- }
- }
-
- if (st_len > 0) {
- qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
- prof_total_cmp);
- prof_sort_list(fd, sorttab, st_len, "TOTAL", FALSE);
- qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
- prof_self_cmp);
- prof_sort_list(fd, sorttab, st_len, "SELF", TRUE);
- }
-
- xfree(sorttab);
-}
-
-/// @param prefer_self when equal print only self time
-static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, int prefer_self)
-{
- int i;
- ufunc_T *fp;
-
- fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title);
- fprintf(fd, "count total (s) self (s) function\n");
- for (i = 0; i < 20 && i < st_len; ++i) {
- fp = sorttab[i];
- prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self,
- prefer_self);
- if (fp->uf_name[0] == K_SPECIAL) {
- fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3);
- } else {
- fprintf(fd, " %s()\n", fp->uf_name);
- }
- }
- fprintf(fd, "\n");
-}
-
-/// Print the count and times for one function or function line.
-///
-/// @param prefer_self when equal print only self time
-static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self,
- int prefer_self)
-{
- if (count > 0) {
- fprintf(fd, "%5d ", count);
- if (prefer_self && profile_equal(*total, *self)) {
- fprintf(fd, " ");
- } else {
- fprintf(fd, "%s ", profile_msg(*total));
- }
- if (!prefer_self && profile_equal(*total, *self)) {
- fprintf(fd, " ");
- } else {
- fprintf(fd, "%s ", profile_msg(*self));
- }
- } else {
- fprintf(fd, " ");
- }
-}
-
-/// Compare function for total time sorting.
-static int prof_total_cmp(const void *s1, const void *s2)
-{
- ufunc_T *p1 = *(ufunc_T **)s1;
- ufunc_T *p2 = *(ufunc_T **)s2;
- return profile_cmp(p1->uf_tm_total, p2->uf_tm_total);
-}
-
-/// Compare function for self time sorting.
-static int prof_self_cmp(const void *s1, const void *s2)
-{
- ufunc_T *p1 = *(ufunc_T **)s1;
- ufunc_T *p2 = *(ufunc_T **)s2;
- return profile_cmp(p1->uf_tm_self, p2->uf_tm_self);
-}
-
/// Return the autoload script name for a function or variable name
/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
///
@@ -8183,61 +7840,6 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r
return ret;
}
-/// Called when starting to read a function line.
-/// "sourcing_lnum" must be correct!
-/// When skipping lines it may not actually be executed, but we won't find out
-/// until later and we need to store the time now.
-void func_line_start(void *cookie)
-{
- funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
-
- if (fp->uf_profiling && sourcing_lnum >= 1
- && sourcing_lnum <= fp->uf_lines.ga_len) {
- fp->uf_tml_idx = sourcing_lnum - 1;
- // Skip continuation lines.
- while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) {
- fp->uf_tml_idx--;
- }
- fp->uf_tml_execed = false;
- fp->uf_tml_start = profile_start();
- fp->uf_tml_children = profile_zero();
- fp->uf_tml_wait = profile_get_wait();
- }
-}
-
-/// Called when actually executing a function line.
-void func_line_exec(void *cookie)
-{
- funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
-
- if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
- fp->uf_tml_execed = TRUE;
- }
-}
-
-/// Called when done with a function line.
-void func_line_end(void *cookie)
-{
- funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
-
- if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
- if (fp->uf_tml_execed) {
- ++fp->uf_tml_count[fp->uf_tml_idx];
- fp->uf_tml_start = profile_end(fp->uf_tml_start);
- fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start);
- fp->uf_tml_total[fp->uf_tml_idx] =
- profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start);
- fp->uf_tml_self[fp->uf_tml_idx] =
- profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start,
- fp->uf_tml_children);
- }
- fp->uf_tml_idx = -1;
- }
-}
-
static var_flavour_T var_flavour(char *varname)
FUNC_ATTR_PURE
{
@@ -8325,11 +7927,9 @@ int store_session_globals(FILE *fd)
}
if ((fprintf(fd, "let %s = %c%s%c",
this_var->di_key,
- ((this_var->di_tv.v_type == VAR_STRING) ? '"'
- : ' '),
+ ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' '),
p,
- ((this_var->di_tv.v_type == VAR_STRING) ? '"'
- : ' ')) < 0)
+ ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' ')) < 0)
|| put_eol(fd) == FAIL) {
xfree(p);
return FAIL;
@@ -8414,10 +8014,8 @@ int modify_fname(char *src, bool tilde_file, size_t *usedlen, char **fnamep, cha
size_t *fnamelen)
{
int valid = 0;
- char *tail;
char *s, *p, *pbuf;
char dirname[MAXPATHL];
- int c;
bool has_fullname = false;
bool has_homerelative = false;
@@ -8479,6 +8077,8 @@ repeat:
}
}
+ int c;
+
// ":." - path relative to the current directory
// ":~" - path relative to the home directory
// ":8" - shortname path - postponed till after
@@ -8544,7 +8144,7 @@ repeat:
}
}
- tail = path_tail(*fnamep);
+ char *tail = path_tail(*fnamep);
*fnamelen = STRLEN(*fnamep);
// ":h" - head, remove "/file_name", can be repeated
@@ -8585,10 +8185,9 @@ repeat:
// ":r" - root, without extension, can be repeated
while (src[*usedlen] == ':'
&& (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) {
- /* find a '.' in the tail:
- * - for second :e: before the current fname
- * - otherwise: The last '.'
- */
+ // find a '.' in the tail:
+ // - for second :e: before the current fname
+ // - otherwise: The last '.'
const bool is_second_e = *fnamep > tail;
if (src[*usedlen + 1] == 'e' && is_second_e) {
s = (*fnamep) - 2;
@@ -8639,18 +8238,16 @@ repeat:
if (src[*usedlen] == ':'
&& (src[*usedlen + 1] == 's'
|| (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) {
- int sep;
- char *flags;
- int didit = false;
+ bool didit = false;
- flags = "";
+ char *flags = "";
s = src + *usedlen + 2;
if (src[*usedlen + 1] == 'g') {
flags = "g";
s++;
}
- sep = (char_u)(*s++);
+ int sep = (char_u)(*s++);
if (sep) {
// find end of pattern
p = vim_strchr(s, sep);
@@ -8668,7 +8265,7 @@ repeat:
*fnamelen = STRLEN(s);
xfree(*bufp);
*bufp = s;
- didit = TRUE;
+ didit = true;
xfree(sub);
xfree(str);
}
@@ -8709,26 +8306,22 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags
{
int sublen;
regmatch_T regmatch;
- int do_all;
- char *tail;
- char *end;
garray_T ga;
- char *save_cpo;
char *zero_width = NULL;
// Make 'cpoptions' empty, so that the 'l' flag doesn't work here
- save_cpo = p_cpo;
+ char *save_cpo = p_cpo;
p_cpo = (char *)empty_option;
ga_init(&ga, 1, 200);
- do_all = (flags[0] == 'g');
+ int do_all = (flags[0] == 'g');
regmatch.rm_ic = p_ic;
regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
- tail = str;
- end = str + STRLEN(str);
+ char *tail = str;
+ char *end = str + STRLEN(str);
while (vim_regexec_nl(&regmatch, (char_u *)str, (colnr_T)(tail - str))) {
// Skip empty match except for first match.
if (regmatch.startp[0] == regmatch.endp[0]) {
@@ -8869,8 +8462,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
struct caller_scope saved_provider_caller_scope = provider_caller_scope;
provider_caller_scope = (struct caller_scope) {
.script_ctx = current_sctx,
- .sourcing_name = sourcing_name,
- .sourcing_lnum = sourcing_lnum,
+ .es_entry = ((estack_T *)exestack.ga_data)[exestack.ga_len - 1],
.autocmd_fname = autocmd_fname,
.autocmd_match = autocmd_match,
.autocmd_bufnr = autocmd_bufnr,
@@ -8969,8 +8561,8 @@ bool eval_has_provider(const char *feat)
/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.
void eval_fmt_source_name_line(char *buf, size_t bufsize)
{
- if (sourcing_name) {
- snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum);
+ if (SOURCING_NAME) {
+ snprintf(buf, bufsize, "%s:%" PRIdLINENR, SOURCING_NAME, SOURCING_LNUM);
} else {
snprintf(buf, bufsize, "?");
}
@@ -9012,8 +8604,6 @@ void invoke_prompt_callback(void)
{
typval_T rettv;
typval_T argv[2];
- char *text;
- char *prompt;
linenr_T lnum = curbuf->b_ml.ml_line_count;
// Add a new line for the prompt before invoking the callback, so that
@@ -9025,8 +8615,8 @@ void invoke_prompt_callback(void)
if (curbuf->b_prompt_callback.type == kCallbackNone) {
return;
}
- text = (char *)ml_get(lnum);
- prompt = (char *)prompt_text();
+ char *text = (char *)ml_get(lnum);
+ char *prompt = (char *)prompt_text();
if (STRLEN(text) >= STRLEN(prompt)) {
text += STRLEN(prompt);
}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 7dbd18737a..b0cb5fd8c1 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -11,15 +11,6 @@
#define COPYID_INC 2
#define COPYID_MASK (~0x1)
-// All user-defined functions are found in this hashtable.
-extern hashtab_T func_hashtab;
-
-// From user function to hashitem and back.
-EXTERN ufunc_T dumuf;
-#define UF2HIKEY(fp) ((fp)->uf_name)
-#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
-#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
-
/*
* Structure returned by get_lval() and used by set_var_lval().
* For a plain name:
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 6d8776d08b..e4e9b34ec6 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -72,6 +72,7 @@ return {
chanclose={args={1, 2}},
chansend={args=2},
char2nr={args={1, 2}, base=1},
+ charclass={args=1, base=1},
charcol={args=1, base=1},
charidx={args={2, 3}, base=1},
chdir={args=1, base=1},
@@ -327,6 +328,7 @@ return {
serverstop={args=1},
setbufline={args=3, base=3},
setbufvar={args=3, base=3},
+ setcellwidths={args=1, base=1},
setcharpos={args=2, base=2},
setcharsearch={args=1, base=1},
setcmdpos={args=1, base=1},
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 090939666d..bb514fba47 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -30,12 +30,12 @@
#include "nvim/vim.h" // For _()
const char *const encode_bool_var_names[] = {
- [kBoolVarTrue] = "true",
- [kBoolVarFalse] = "false",
+ [kBoolVarTrue] = "v:true",
+ [kBoolVarFalse] = "v:false",
};
const char *const encode_special_var_names[] = {
- [kSpecialVarNull] = "null",
+ [kSpecialVarNull] = "v:null",
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index f24285063e..3b9dc92137 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -7,12 +7,14 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/context.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -27,10 +29,12 @@
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/if_cscope.h"
@@ -45,6 +49,7 @@
#include "nvim/match.h"
#include "nvim/math.h"
#include "nvim/memline.h"
+#include "nvim/menu.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -52,18 +57,20 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os/dl.h"
-#include "nvim/os/input.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
#include "nvim/plines.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
+#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sha256.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
+#include "nvim/spellsuggest.h"
#include "nvim/state.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
@@ -117,13 +124,12 @@ static va_list dummy_ap;
char *get_function_name(expand_T *xp, int idx)
{
static int intidx = -1;
- char_u *name;
if (idx == 0) {
intidx = -1;
}
if (intidx < 0) {
- name = (char_u *)get_user_func_name(xp, idx);
+ char_u *name = (char_u *)get_user_func_name(xp, idx);
if (name != NULL) {
if (*name != NUL && *name != '<'
&& STRNCMP("g:", xp->xp_pattern, 2) == 0) {
@@ -154,13 +160,12 @@ char *get_function_name(expand_T *xp, int idx)
char *get_expr_name(expand_T *xp, int idx)
{
static int intidx = -1;
- char_u *name;
if (idx == 0) {
intidx = -1;
}
if (intidx < 0) {
- name = (char_u *)get_function_name(xp, idx);
+ char_u *name = (char_u *)get_function_name(xp, idx);
if (name != NULL) {
return (char *)name;
}
@@ -294,10 +299,9 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[0].v_type == VAR_FLOAT) {
float_op_wrapper(argvars, rettv, (FunPtr)&fabs);
} else {
- varnumber_T n;
bool error = false;
- n = tv_get_number_chk(&argvars[0], &error);
+ varnumber_T n = tv_get_number_chk(&argvars[0], &error);
if (error) {
rettv->vval.v_number = -1;
} else if (n > 0) {
@@ -371,77 +375,6 @@ static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // use the current window
- rettv->vval.v_number = ARGCOUNT;
- } else if (argvars[0].v_type == VAR_NUMBER
- && tv_get_number(&argvars[0]) == -1) {
- // use the global argument list
- rettv->vval.v_number = GARGCOUNT;
- } else {
- // use the argument list of the specified window
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp != NULL) {
- rettv->vval.v_number = WARGCOUNT(wp);
- } else {
- rettv->vval.v_number = -1;
- }
- }
-}
-
-/// "argidx()" function
-static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = curwin->w_arg_idx;
-}
-
-/// "arglistid" function
-static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
- win_T *wp = find_tabwin(&argvars[0], &argvars[1]);
- if (wp != NULL) {
- rettv->vval.v_number = wp->w_alist->id;
- }
-}
-
-/// "argv(nr)" function
-static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- aentry_T *arglist = NULL;
- int argcount = -1;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type == VAR_UNKNOWN) {
- arglist = ARGLIST;
- argcount = ARGCOUNT;
- } else if (argvars[1].v_type == VAR_NUMBER
- && tv_get_number(&argvars[1]) == -1) {
- arglist = GARGLIST;
- argcount = GARGCOUNT;
- } else {
- win_T *wp = find_win_by_nr_or_id(&argvars[1]);
- if (wp != NULL) {
- // Use the argument list of the specified window
- arglist = WARGLIST(wp);
- argcount = WARGCOUNT(wp);
- }
- }
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- int idx = (int)tv_get_number_chk(&argvars[0], NULL);
- if (arglist != NULL && idx >= 0 && idx < argcount) {
- rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx]));
- } else if (idx == -1) {
- get_arglist_as_rettv(arglist, argcount, rettv);
- }
- } else {
- get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
- }
-}
-
/// "atan2()" function
static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -479,8 +412,8 @@ static buf_T *find_buffer(typval_T *avar)
} else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) {
buf = buflist_findname_exp(avar->vval.v_string);
if (buf == NULL) {
- /* No full path name match, try a match with a URL or a "nofile"
- * buffer, these don't use the full path. */
+ // No full path name match, try a match with a URL or a "nofile"
+ // buffer, these don't use the full path.
FOR_ALL_BUFFERS(bp) {
if (bp->b_fname != NULL
&& (path_with_url(bp->b_fname) || bt_nofilename(bp))
@@ -632,17 +565,15 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Get buffer by number or pattern.
buf_T *tv_get_buf(typval_T *tv, int curtab_only)
{
- char_u *name = (char_u *)tv->vval.v_string;
- int save_magic;
- char *save_cpo;
- buf_T *buf;
-
if (tv->v_type == VAR_NUMBER) {
return buflist_findnr((int)tv->vval.v_number);
}
if (tv->v_type != VAR_STRING) {
return NULL;
}
+
+ char_u *name = (char_u *)tv->vval.v_string;
+
if (name == NULL || *name == NUL) {
return curbuf;
}
@@ -651,13 +582,13 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only)
}
// Ignore 'magic' and 'cpoptions' here to make scripts portable
- save_magic = p_magic;
- p_magic = TRUE;
- save_cpo = p_cpo;
+ int save_magic = p_magic;
+ p_magic = true;
+ char *save_cpo = p_cpo;
p_cpo = "";
- buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name),
- true, false, curtab_only));
+ buf_T *buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name),
+ true, false, curtab_only));
p_magic = save_magic;
p_cpo = save_cpo;
@@ -686,10 +617,8 @@ buf_T *tv_get_buf_from_arg(typval_T *const tv) FUNC_ATTR_NONNULL_ALL
/// valid.
buf_T *get_buf_arg(typval_T *arg)
{
- buf_T *buf;
-
emsg_off++;
- buf = tv_get_buf(arg, false);
+ buf_T *buf = tv_get_buf(arg, false);
emsg_off--;
if (buf == NULL) {
semsg(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
@@ -735,13 +664,13 @@ static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
/// "byteidx()" function
static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- byteidx(argvars, rettv, FALSE);
+ byteidx(argvars, rettv, false);
}
/// "byteidxcomp()" function
static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- byteidx(argvars, rettv, TRUE);
+ byteidx(argvars, rettv, true);
}
/// "call(func, arglist [, dict])" function
@@ -758,7 +687,6 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool owned = false;
char_u *func;
partial_T *partial = NULL;
- dict_T *selfdict = NULL;
if (argvars[0].v_type == VAR_FUNC) {
func = (char_u *)argvars[0].vval.v_string;
} else if (argvars[0].v_type == VAR_PARTIAL) {
@@ -776,6 +704,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return; // type error or empty name
}
+ dict_T *selfdict = NULL;
if (argvars[2].v_type != VAR_UNKNOWN) {
if (argvars[2].v_type != VAR_DICT) {
emsg(_(e_dictreq));
@@ -898,10 +827,9 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
{
colnr_T col = 0;
- pos_T *fp;
int fnum = curbuf->b_fnum;
- fp = var2fpos(&argvars[0], false, &fnum, charcol);
+ pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol);
if (fp != NULL && fnum == curbuf->b_fnum) {
if (fp->col == MAXCOL) {
// '> can be MAXCOL, get the length of the line then
@@ -984,9 +912,6 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "chdir(dir)" function
static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- char_u *cwd;
- CdScope scope = kCdScopeGlobal;
-
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
@@ -997,7 +922,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
// Return the current directory
- cwd = xmalloc(MAXPATHL);
+ char_u *cwd = xmalloc(MAXPATHL);
if (os_dirname(cwd, MAXPATHL) != FAIL) {
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(cwd);
@@ -1006,6 +931,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
xfree(cwd);
+ CdScope scope = kCdScopeGlobal;
if (curwin->w_localdir != NULL) {
scope = kCdScopeWindow;
} else if (curtab->tp_localdir != NULL) {
@@ -1021,11 +947,8 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "cindent(lnum)" function
static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T pos;
- linenr_T lnum;
-
- pos = curwin->w_cursor;
- lnum = tv_get_lnum(argvars);
+ pos_T pos = curwin->w_cursor;
+ linenr_T lnum = tv_get_lnum(argvars);
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = lnum;
rettv->vval.v_number = get_c_indent();
@@ -1060,14 +983,12 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
char buf[NUMBUFLEN];
char buf2[NUMBUFLEN];
- const char *message;
const char *buttons = NULL;
int def = 1;
int type = VIM_GENERIC;
- const char *typestr;
bool error = false;
- message = tv_get_string_chk(&argvars[0]);
+ const char *message = tv_get_string_chk(&argvars[0]);
if (message == NULL) {
error = true;
}
@@ -1079,7 +1000,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[2].v_type != VAR_UNKNOWN) {
def = (int)tv_get_number_chk(&argvars[2], &error);
if (argvars[3].v_type != VAR_UNKNOWN) {
- typestr = tv_get_string_buf_chk(&argvars[3], buf2);
+ const char *typestr = tv_get_string_buf_chk(&argvars[3], buf2);
if (typestr == NULL) {
error = true;
} else {
@@ -1152,15 +1073,13 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
} else if (argvars[0].v_type == VAR_LIST) {
- listitem_T *li;
- list_T *l;
- long idx;
+ list_T *l = argvars[0].vval.v_list;
- if ((l = argvars[0].vval.v_list) != NULL) {
- li = tv_list_first(l);
+ if (l != NULL) {
+ listitem_T *li = tv_list_first(l);
if (argvars[2].v_type != VAR_UNKNOWN) {
if (argvars[3].v_type != VAR_UNKNOWN) {
- idx = tv_get_number_chk(&argvars[3], &error);
+ long idx = tv_get_number_chk(&argvars[3], &error);
if (!error) {
li = tv_list_find(l, (int)idx);
if (li == NULL) {
@@ -1180,19 +1099,17 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
} else if (argvars[0].v_type == VAR_DICT) {
- int todo;
- dict_T *d;
- hashitem_T *hi;
+ dict_T *d = argvars[0].vval.v_dict;
- if ((d = argvars[0].vval.v_dict) != NULL) {
+ if (d != NULL) {
if (argvars[2].v_type != VAR_UNKNOWN) {
if (argvars[3].v_type != VAR_UNKNOWN) {
emsg(_(e_invarg));
}
}
- todo = error ? 0 : (int)d->dv_hashtab.ht_used;
- for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) {
+ int todo = error ? 0 : (int)d->dv_hashtab.ht_used;
+ for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
@@ -1414,10 +1331,8 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "debugbreak()" function
static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int pid;
-
rettv->vval.v_number = FAIL;
- pid = (int)tv_get_number(&argvars[0]);
+ int pid = (int)tv_get_number(&argvars[0]);
if (pid == 0) {
emsg(_(e_invarg));
} else {
@@ -1564,10 +1479,6 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "deletebufline()" function
static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- linenr_T last;
- buf_T *curbuf_save = NULL;
- win_T *curwin_save = NULL;
-
buf_T *const buf = tv_get_buf(&argvars[0], false);
if (buf == NULL) {
rettv->vval.v_number = 1; // FAIL
@@ -1576,6 +1487,7 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const bool is_curbuf = buf == curbuf;
const bool save_VIsual_active = VIsual_active;
+ linenr_T last;
const linenr_T first = tv_get_lnum_buf(&argvars[1], buf);
if (argvars[2].v_type != VAR_UNKNOWN) {
last = tv_get_lnum_buf(&argvars[2], buf);
@@ -1589,6 +1501,8 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ buf_T *curbuf_save = NULL;
+ win_T *curwin_save = NULL;
if (!is_curbuf) {
VIsual_active = false;
curbuf_save = curbuf;
@@ -1660,8 +1574,6 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static int change_start = 0;
static int change_end = 0;
static hlf_T hlID = (hlf_T)0;
- int filler_lines;
- int col;
if (lnum < 0) { // ignore type error in {lnum} arg
lnum = 0;
@@ -1670,7 +1582,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|| changedtick != buf_get_changedtick(curbuf)
|| fnum != curbuf->b_fnum) {
// New line, buffer, change: need to get the values.
- filler_lines = diff_check(curwin, lnum);
+ int filler_lines = diff_check(curwin, lnum);
if (filler_lines < 0) {
if (filler_lines == -1) {
change_start = MAXCOL;
@@ -1692,7 +1604,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
if (hlID == HLF_CHD || hlID == HLF_TXD) {
- col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
+ int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
if (col >= change_start && col <= change_end) {
hlID = HLF_TXD; // Changed text.
} else {
@@ -2042,12 +1954,9 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "expand()" function
static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- size_t len;
char *errormsg;
int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
- expand_T xpc;
bool error = false;
- char_u *result;
#ifdef BACKSLASH_IN_FILENAME
char_u *p_csl_save = p_csl;
@@ -2066,7 +1975,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *s = tv_get_string(&argvars[0]);
if (*s == '%' || *s == '#' || *s == '<') {
emsg_off++;
- result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);
+ size_t len;
+ char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);
emsg_off--;
if (rettv->v_type == VAR_LIST) {
tv_list_alloc_ret(rettv, (result != NULL));
@@ -2085,6 +1995,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
options |= WILD_KEEP_ALL;
}
if (!error) {
+ expand_T xpc;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
if (p_wic) {
@@ -2151,8 +2062,6 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "flatten(list[, {maxdepth}])" function
static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- list_T *list;
- long maxdepth;
bool error = false;
if (argvars[0].v_type != VAR_LIST) {
@@ -2160,6 +2069,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ long maxdepth;
if (argvars[1].v_type == VAR_UNKNOWN) {
maxdepth = 999999;
} else {
@@ -2173,7 +2083,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
- list = argvars[0].vval.v_list;
+ list_T *list = argvars[0].vval.v_list;
if (list != NULL
&& !var_check_lock(tv_list_locked(list),
N_("flatten() argument"),
@@ -2190,7 +2100,6 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *const arg_errmsg = N_("extend() argument");
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
- long before;
bool error = false;
list_T *const l1 = argvars[0].vval.v_list;
@@ -2198,7 +2107,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
listitem_T *item;
if (argvars[2].v_type != VAR_UNKNOWN) {
- before = (long)tv_get_number_chk(&argvars[2], &error);
+ long before = (long)tv_get_number_chk(&argvars[2], &error);
if (error) {
return; // Type error; errmsg already given.
}
@@ -2360,7 +2269,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
/// "filter()" function
static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- filter_map(argvars, rettv, FALSE);
+ filter_map(argvars, rettv, false);
}
/// "finddir({fname}[, {path}[, {count}]])" function
@@ -2440,130 +2349,6 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
xfree(fbuf);
}
-/// "foldclosed()" function
-static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- linenr_T first;
- linenr_T last;
- if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
- if (end) {
- rettv->vval.v_number = (varnumber_T)last;
- } else {
- rettv->vval.v_number = (varnumber_T)first;
- }
- return;
- }
- }
- rettv->vval.v_number = -1;
-}
-
-/// "foldclosed()" function
-static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- foldclosed_both(argvars, rettv, FALSE);
-}
-
-/// "foldclosedend()" function
-static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- foldclosed_both(argvars, rettv, TRUE);
-}
-
-/// "foldlevel()" function
-static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = foldLevel(lnum);
- }
-}
-
-/// "foldtext()" function
-static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T foldstart;
- linenr_T foldend;
- char_u *dashes;
- linenr_T lnum;
- char_u *s;
- char_u *r;
- int len;
- char *txt;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
- foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
- dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES);
- if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) {
- // Find first non-empty line in the fold.
- for (lnum = foldstart; lnum < foldend; lnum++) {
- if (!linewhite(lnum)) {
- break;
- }
- }
-
- // Find interesting text in this line.
- s = (char_u *)skipwhite((char *)ml_get(lnum));
- // skip C comment-start
- if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
- s = (char_u *)skipwhite((char *)s + 2);
- if (*skipwhite((char *)s) == NUL && lnum + 1 < foldend) {
- s = (char_u *)skipwhite((char *)ml_get(lnum + 1));
- if (*s == '*') {
- s = (char_u *)skipwhite((char *)s + 1);
- }
- }
- }
- unsigned long count = (unsigned long)foldend - (unsigned long)foldstart + 1;
- txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
- r = xmalloc(STRLEN(txt)
- + STRLEN(dashes) // for %s
- + 20 // for %3ld
- + STRLEN(s)); // concatenated
- sprintf((char *)r, txt, dashes, count);
- len = (int)STRLEN(r);
- STRCAT(r, s);
- // remove 'foldmarker' and 'commentstring'
- foldtext_cleanup(r + len);
- rettv->vval.v_string = (char *)r;
- }
-}
-
-/// "foldtextresult(lnum)" function
-static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *text;
- char_u buf[FOLD_TEXT_LEN];
- static bool entered = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (entered) {
- return; // reject recursive use
- }
- entered = true;
- linenr_T lnum = tv_get_lnum(argvars);
- // Treat illegal types and illegal string values for {lnum} the same.
- if (lnum < 0) {
- lnum = 0;
- }
-
- foldinfo_T info = fold_info(curwin, lnum);
- if (info.fi_lines > 0) {
- text = get_foldtext(curwin, lnum, lnum + (linenr_T)info.fi_lines - 1, info, buf);
- if (text == buf) {
- text = vim_strsave(text);
- }
- rettv->vval.v_string = (char *)text;
- }
-
- entered = false;
-}
-
/// "foreground()" function
static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{}
@@ -2593,10 +2378,6 @@ static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "get()" function
static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- listitem_T *li;
- list_T *l;
- dictitem_T *di;
- dict_T *d;
typval_T *tv = NULL;
bool what_is_dict = false;
@@ -2617,17 +2398,19 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
} else if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) != NULL) {
+ list_T *l = argvars[0].vval.v_list;
+ if (l != NULL) {
bool error = false;
- li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error));
+ listitem_T *li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error));
if (!error && li != NULL) {
tv = TV_LIST_ITEM_TV(li);
}
}
} else if (argvars[0].v_type == VAR_DICT) {
- if ((d = argvars[0].vval.v_dict) != NULL) {
- di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
+ dict_T *d = argvars[0].vval.v_dict;
+ if (d != NULL) {
+ dictitem_T *di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
if (di != NULL) {
tv = &di->di_tv;
}
@@ -2639,7 +2422,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[0].v_type == VAR_PARTIAL) {
pt = argvars[0].vval.v_partial;
} else {
- memset(&fref_pt, 0, sizeof(fref_pt));
+ CLEAR_FIELD(fref_pt);
fref_pt.pt_name = (char_u *)argvars[0].vval.v_string;
pt = &fref_pt;
}
@@ -2851,147 +2634,6 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/// "getchar()" and "getcharstr()" functions
-static void getchar_common(typval_T *argvars, typval_T *rettv)
- FUNC_ATTR_NONNULL_ALL
-{
- varnumber_T n;
- bool error = false;
-
- no_mapping++;
- allow_keys++;
- for (;;) {
- // Position the cursor. Needed after a message that ends in a space,
- // or if event processing caused a redraw.
- ui_cursor_goto(msg_row, msg_col);
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // getchar(): blocking wait.
- // TODO(bfredl): deduplicate shared logic with state_enter ?
- if (!char_avail()) {
- (void)os_inchar(NULL, 0, -1, 0, main_loop.events);
- if (!multiqueue_empty(main_loop.events)) {
- state_handle_k_event();
- continue;
- }
- }
- n = safe_vgetc();
- } else if (tv_get_number_chk(&argvars[0], &error) == 1) {
- // getchar(1): only check if char avail
- n = vpeekc_any();
- } else if (error || vpeekc_any() == NUL) {
- // illegal argument or getchar(0) and no char avail: return zero
- n = 0;
- } else {
- // getchar(0) and char avail() != NUL: get a character.
- // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE.
- n = safe_vgetc();
- }
-
- if (n == K_IGNORE
- || n == K_MOUSEMOVE
- || n == K_VER_SCROLLBAR
- || n == K_HOR_SCROLLBAR) {
- continue;
- }
- break;
- }
- no_mapping--;
- allow_keys--;
-
- if (!ui_has_messages()) {
- // redraw the screen after getchar()
- update_screen(CLEAR);
- }
-
- set_vim_var_nr(VV_MOUSE_WIN, 0);
- set_vim_var_nr(VV_MOUSE_WINID, 0);
- set_vim_var_nr(VV_MOUSE_LNUM, 0);
- set_vim_var_nr(VV_MOUSE_COL, 0);
-
- rettv->vval.v_number = n;
- if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) {
- char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1
- int i = 0;
-
- // Turn a special key into three bytes, plus modifier.
- if (mod_mask != 0) {
- temp[i++] = K_SPECIAL;
- temp[i++] = KS_MODIFIER;
- temp[i++] = (char_u)mod_mask;
- }
- if (IS_SPECIAL(n)) {
- temp[i++] = K_SPECIAL;
- temp[i++] = (char_u)K_SECOND(n);
- temp[i++] = K_THIRD(n);
- } else {
- i += utf_char2bytes((int)n, (char *)temp + i);
- }
- assert(i < 10);
- temp[i++] = NUL;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char *)vim_strsave(temp);
-
- if (is_mouse_key((int)n)) {
- int row = mouse_row;
- int col = mouse_col;
- int grid = mouse_grid;
- linenr_T lnum;
- win_T *wp;
- int winnr = 1;
-
- if (row >= 0 && col >= 0) {
- // Find the window at the mouse coordinates and compute the
- // text position.
- win_T *const win = mouse_find_win(&grid, &row, &col);
- if (win == NULL) {
- return;
- }
- (void)mouse_comp_pos(win, &row, &col, &lnum);
- for (wp = firstwin; wp != win; wp = wp->w_next) {
- ++winnr;
- }
- set_vim_var_nr(VV_MOUSE_WIN, winnr);
- set_vim_var_nr(VV_MOUSE_WINID, wp->handle);
- set_vim_var_nr(VV_MOUSE_LNUM, lnum);
- set_vim_var_nr(VV_MOUSE_COL, col + 1);
- }
- }
- }
-}
-
-/// "getchar()" function
-static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getchar_common(argvars, rettv);
-}
-
-/// "getcharstr()" function
-static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getchar_common(argvars, rettv);
-
- if (rettv->v_type == VAR_NUMBER) {
- char temp[7]; // mbyte-char: 6, NUL: 1
- const varnumber_T n = rettv->vval.v_number;
- int i = 0;
-
- if (n != 0) {
- i += utf_char2bytes((int)n, (char *)temp);
- }
- assert(i < 7);
- temp[i++] = NUL;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrdup(temp);
- }
-}
-
-/// "getcharmod()" function
-static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = mod_mask;
-}
-
static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol)
{
pos_T *fp = NULL;
@@ -3203,15 +2845,15 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// Numbers of the scope objects (window, tab) we want the working directory
// of. A `-1` means to skip this scope, a `0` means the current object.
int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeWindow] = 0, // Number of window to look at.
[kCdScopeTabpage] = 0, // Number of tab to look at.
};
char *cwd = NULL; // Current working directory to print
char *from = NULL; // The original string to copy
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
@@ -3455,13 +3097,6 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
get_buffer_lines(curbuf, lnum, end, retlist, rettv);
}
-/// "getloclist()" function
-static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- get_qf_loc_list(false, wp, &argvars[1], rettv);
-}
-
/// "getmarklist()" function
static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -3483,8 +3118,6 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "getmousepos()" function
static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- dict_T *d;
- win_T *wp;
int row = mouse_row;
int col = mouse_col;
int grid = mouse_grid;
@@ -3495,12 +3128,12 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
varnumber_T column = 0;
tv_dict_alloc_ret(rettv);
- d = rettv->vval.v_dict;
+ dict_T *d = rettv->vval.v_dict;
tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1);
tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1);
- wp = mouse_find_win(&grid, &row, &col);
+ win_T *wp = mouse_find_win(&grid, &row, &col);
if (wp != NULL) {
int height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
// The height is adjusted by 1 when there is a bottom border. This is not
@@ -3546,12 +3179,6 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
getpos_both(argvars, rettv, false, false);
}
-/// "getqflist()" functions
-static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_qf_loc_list(true, NULL, &argvars[0], rettv);
-}
-
/// Common between getreg(), getreginfo() and getregtype(): get the register
/// name from the first argument.
/// Returns zero on error.
@@ -3788,7 +3415,6 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Move the window wp into a new split of targetwin in a given direction
static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags)
{
- int dir;
int height = wp->w_height;
win_T *oldwin = curwin;
@@ -3802,6 +3428,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags
}
// Remove the old window and frame from the tree of frames
+ int dir;
(void)winframe_remove(wp, &dir, NULL);
win_remove(wp, NULL);
last_status(false); // may need to remove last status line
@@ -3826,12 +3453,8 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags
/// "win_splitmove()" function
static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- win_T *wp;
- win_T *targetwin;
- int flags = 0, size = 0;
-
- wp = find_win_by_nr_or_id(&argvars[0]);
- targetwin = find_win_by_nr_or_id(&argvars[1]);
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ win_T *targetwin = find_win_by_nr_or_id(&argvars[1]);
if (wp == NULL || targetwin == NULL || wp == targetwin
|| !win_valid(wp) || !win_valid(targetwin)
@@ -3841,6 +3464,8 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ int flags = 0, size = 0;
+
if (argvars[2].v_type != VAR_UNKNOWN) {
dict_T *d;
dictitem_T *di;
@@ -3890,8 +3515,8 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
expand_T xpc;
bool error = false;
- /* When the optional second argument is non-zero, don't remove matches
- * for 'wildignore' and don't put matches for 'suffixes' at the end. */
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
rettv->v_type = VAR_STRING;
if (argvars[1].v_type != VAR_UNKNOWN) {
if (tv_get_number_chk(&argvars[1], &error)) {
@@ -3964,7 +3589,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (file != NULL && !error) {
garray_T ga;
ga_init(&ga, (int)sizeof(char_u *), 10);
- globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
+ globpath((char *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
if (rettv->v_type == VAR_STRING) {
rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
@@ -4306,87 +3931,6 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/// "histadd()" function
-static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType histype;
-
- rettv->vval.v_number = false;
- if (check_secure()) {
- return;
- }
- const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
- histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
- if (histype != HIST_INVALID) {
- char buf[NUMBUFLEN];
- str = tv_get_string_buf(&argvars[1], buf);
- if (*str != NUL) {
- init_history();
- add_to_history(histype, (char_u *)str, false, NUL);
- rettv->vval.v_number = true;
- return;
- }
- }
-}
-
-/// "histdel()" function
-static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n;
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- n = 0;
- } else if (argvars[1].v_type == VAR_UNKNOWN) {
- // only one argument: clear entire history
- n = clr_history(get_histtype(str, strlen(str), false));
- } else if (argvars[1].v_type == VAR_NUMBER) {
- // index given: remove that entry
- n = del_history_idx(get_histtype(str, strlen(str), false),
- (int)tv_get_number(&argvars[1]));
- } else {
- // string given: remove all matching entries
- char buf[NUMBUFLEN];
- n = del_history_entry(get_histtype(str, strlen(str), false),
- (char_u *)tv_get_string_buf(&argvars[1], buf));
- }
- rettv->vval.v_number = n;
-}
-
-/// "histget()" function
-static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType type;
- int idx;
-
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- type = get_histtype(str, strlen(str), false);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- idx = get_history_idx(type);
- } else {
- idx = (int)tv_get_number_chk(&argvars[1], NULL);
- }
- // -1 on type error
- rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx));
- }
- rettv->v_type = VAR_STRING;
-}
-
-/// "histnr()" function
-static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const history = tv_get_string_chk(&argvars[0]);
- HistoryType i = history == NULL
- ? HIST_INVALID
- : get_histtype(history, strlen(history), false);
- if (i != HIST_INVALID) {
- i = get_history_idx(i);
- }
- rettv->vval.v_number = i;
-}
-
/// "highlightID(name)" function
static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -4526,21 +4070,18 @@ static bool inputsecret_flag = false;
/// Also handles inputsecret() when inputsecret is set.
static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- get_user_input(argvars, rettv, FALSE, inputsecret_flag);
+ get_user_input(argvars, rettv, false, inputsecret_flag);
}
/// "inputdialog()" function
static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- get_user_input(argvars, rettv, TRUE, inputsecret_flag);
+ get_user_input(argvars, rettv, true, inputsecret_flag);
}
/// "inputlist()" function
static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int selected;
- int mouse_used;
-
if (argvars[0].v_type != VAR_LIST) {
semsg(_(e_listarg), "inputlist()");
return;
@@ -4558,7 +4099,8 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
});
// Ask for choice.
- selected = prompt_for_number(&mouse_used);
+ int mouse_used;
+ int selected = prompt_for_number(&mouse_used);
if (mouse_used) {
selected -= lines_left;
}
@@ -4695,7 +4237,6 @@ static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
lval_T lv;
- dictitem_T *di;
rettv->vval.v_number = -1;
const char_u *const end = (char_u *)get_lval((char *)tv_get_string(&argvars[0]),
@@ -4708,7 +4249,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
semsg(_(e_trailing_arg), end);
} else {
if (lv.ll_tv == NULL) {
- di = find_var(lv.ll_name, lv.ll_name_len, NULL, true);
+ dictitem_T *di = find_var(lv.ll_name, lv.ll_name_len, NULL, true);
if (di != NULL) {
// Consider a variable locked when:
// 1. the variable itself is locked
@@ -5016,7 +4557,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (new_cwd && *new_cwd != NUL) {
cwd = new_cwd;
// The new cwd must be a directory.
- if (!os_isdir_executable((const char *)cwd)) {
+ if (!os_isdir((const char_u *)cwd)) {
semsg(_(e_invarg2), "expected valid directory");
shell_free_argv(argv);
return;
@@ -5410,7 +4951,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "map()" function
static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- filter_map(argvars, rettv, TRUE);
+ filter_map(argvars, rettv, true);
}
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
@@ -5420,18 +4961,16 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
long len = 0;
char_u *expr = NULL;
regmatch_T regmatch;
- char *save_cpo;
long start = 0;
long nth = 1;
colnr_T startcol = 0;
bool match = false;
list_T *l = NULL;
- listitem_T *li = NULL;
long idx = 0;
char_u *tofree = NULL;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
- save_cpo = p_cpo;
+ char *save_cpo = p_cpo;
p_cpo = "";
rettv->vval.v_number = -1;
@@ -5458,6 +4997,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
break;
}
+ listitem_T *li = NULL;
if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) == NULL) {
goto theend;
@@ -5566,10 +5106,8 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz((const char *)regmatch.startp[0], rd);
- TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
- regmatch.startp[0] - expr);
- TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
- regmatch.endp[0] - expr);
+ TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(regmatch.startp[0] - expr);
+ TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(regmatch.endp[0] - expr);
if (l != NULL) {
TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
}
@@ -5710,13 +5248,13 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool
/// "max()" function
static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- max_min(argvars, rettv, TRUE);
+ max_min(argvars, rettv, true);
}
/// "min()" function
static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- max_min(argvars, rettv, FALSE);
+ max_min(argvars, rettv, false);
}
/// "mkdir()" function
@@ -6047,14 +5585,13 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
{
- int len;
int saved_did_emsg = did_emsg;
// Get the required length, allocate the buffer and do it for real.
did_emsg = false;
char buf[NUMBUFLEN];
const char *fmt = tv_get_string_buf(&argvars[0], buf);
- len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
+ int len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
if (!did_emsg) {
char *s = xmalloc((size_t)len + 1);
rettv->vval.v_string = s;
@@ -6067,13 +5604,12 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "prompt_setcallback({buffer}, {callback})" function
static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- buf_T *buf;
Callback prompt_callback = { .type = kCallbackNone };
if (check_secure()) {
return;
}
- buf = tv_get_buf(&argvars[0], false);
+ buf_T *buf = tv_get_buf(&argvars[0], false);
if (buf == NULL) {
return;
}
@@ -6091,13 +5627,12 @@ static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr
/// "prompt_setinterrupt({buffer}, {callback})" function
static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- buf_T *buf;
Callback interrupt_callback = { .type = kCallbackNone };
if (check_secure()) {
return;
}
- buf = tv_get_buf(&argvars[0], false);
+ buf_T *buf = tv_get_buf(&argvars[0], false);
if (buf == NULL) {
return;
}
@@ -6342,13 +5877,11 @@ static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "range()" function
static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- varnumber_T start;
varnumber_T end;
varnumber_T stride = 1;
- varnumber_T i;
bool error = false;
- start = tv_get_number_chk(&argvars[0], &error);
+ varnumber_T start = tv_get_number_chk(&argvars[0], &error);
if (argvars[1].v_type == VAR_UNKNOWN) {
end = start - 1;
start = 0;
@@ -6368,7 +5901,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
emsg(_("E727: Start past end"));
} else {
tv_list_alloc_ret(rettv, (end - start) / stride);
- for (i = start; stride > 0 ? i <= end : i >= end; i += stride) {
+ for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) {
tv_list_append_number(rettv->vval.v_list, i);
}
}
@@ -6379,8 +5912,6 @@ static varnumber_T readdir_checkitem(void *context, const char *name)
FUNC_ATTR_NONNULL_ALL
{
typval_T *expr = (typval_T *)context;
- typval_T save_val;
- typval_T rettv;
typval_T argv[2];
varnumber_T retval = 0;
bool error = false;
@@ -6389,11 +5920,13 @@ static varnumber_T readdir_checkitem(void *context, const char *name)
return 1;
}
+ typval_T save_val;
prepare_vimvar(VV_VAL, &save_val);
set_vim_var_string(VV_VAL, name, -1);
argv[0].v_type = VAR_STRING;
argv[0].vval.v_string = (char *)name;
+ typval_T rettv;
if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
goto theend;
}
@@ -6435,12 +5968,11 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool binary = false;
bool blob = false;
FILE *fd;
- char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
+ char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf);
- int readlen; // size of last fread()
- char_u *prev = NULL; // previously read bytes, if any
- long prevlen = 0; // length of data in prev
- long prevsize = 0; // size of prev buffer
+ char_u *prev = NULL; // previously read bytes, if any
+ long prevlen = 0; // length of data in prev
+ long prevsize = 0; // size of prev buffer
long maxline = MAXLNUM;
if (argvars[1].v_type != VAR_UNKNOWN) {
@@ -6482,7 +6014,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
while (maxline < 0 || tv_list_len(l) < maxline) {
- readlen = (int)fread(buf, 1, (size_t)io_size, fd);
+ int readlen = (int)fread(buf, 1, (size_t)io_size, fd);
// This for loop processes what was read, but is also entered at end
// of file so that either:
@@ -6514,9 +6046,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
assert(len < INT_MAX);
s = vim_strnsave(start, len);
} else {
- /* Change "prev" buffer to be the right size. This way
- * the bytes are only copied once, and very long lines are
- * allocated only once. */
+ // Change "prev" buffer to be the right size. This way
+ // the bytes are only copied once, and very long lines are
+ // allocated only once.
s = xrealloc(prev, (size_t)prevlen + len + 1);
memcpy(s + prevlen, start, len);
s[(size_t)prevlen + len] = NUL;
@@ -6590,10 +6122,10 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (start < p) {
// There's part of a line in buf, store it in "prev".
if (p - start + prevlen >= prevsize) {
- /* A common use case is ordinary text files and "prev" gets a
- * fragment of a line, so the first allocation is made
- * small, to avoid repeatedly 'allocing' large and
- * 'reallocing' small. */
+ // A common use case is ordinary text files and "prev" gets a
+ // fragment of a line, so the first allocation is made
+ // small, to avoid repeatedly 'allocing' large and
+ // 'reallocing' small.
if (prevsize == 0) {
prevsize = (long)(p - start);
} else {
@@ -7142,7 +6674,6 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static int get_search_arg(typval_T *varp, int *flagsp)
{
int dir = FORWARD;
- int mask;
if (varp->v_type != VAR_UNKNOWN) {
char nbuf[NUMBUFLEN];
@@ -7150,6 +6681,7 @@ static int get_search_arg(typval_T *varp, int *flagsp)
if (flags == NULL) {
return 0; // Type error; errmsg already given.
}
+ int mask;
while (*flags != NUL) {
switch (*flags) {
case 'b':
@@ -7199,26 +6731,19 @@ static int get_search_arg(typval_T *varp, int *flagsp)
/// Shared by search() and searchpos() functions.
static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
{
- int flags;
- pos_T pos;
- pos_T save_cursor;
bool save_p_ws = p_ws;
- int dir;
int retval = 0; // default: FAIL
long lnum_stop = 0;
- proftime_T tm;
long time_limit = 0;
int options = SEARCH_KEEP;
- int subpatnum;
- searchit_arg_T sia;
bool use_skip = false;
const char *const pat = tv_get_string(&argvars[0]);
- dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
+ int dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
if (dir == 0) {
goto theend;
}
- flags = *flagsp;
+ int flags = *flagsp;
if (flags & SP_START) {
options |= SEARCH_START;
}
@@ -7245,25 +6770,27 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
// Set the time limit, if there is one.
- tm = profile_setlimit(time_limit);
-
- /*
- * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
- * Check to make sure only those flags are set.
- * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
- * flags cannot be set. Check for that condition also.
- */
+ proftime_T tm = profile_setlimit(time_limit);
+
+ // This function does not accept SP_REPEAT and SP_RETCOUNT flags.
+ // Check to make sure only those flags are set.
+ // Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
+ // flags cannot be set. Check for that condition also.
if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
|| ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
semsg(_(e_invarg2), tv_get_string(&argvars[1]));
goto theend;
}
- pos = save_cursor = curwin->w_cursor;
+ pos_T save_cursor;
+ pos_T pos = save_cursor = curwin->w_cursor;
pos_T firstpos = { 0 };
- memset(&sia, 0, sizeof(sia));
- sia.sa_stop_lnum = (linenr_T)lnum_stop;
- sia.sa_tm = &tm;
+ searchit_arg_T sia = {
+ .sa_stop_lnum = (linenr_T)lnum_stop,
+ .sa_tm = &tm,
+ };
+
+ int subpatnum;
// Repeat until {skip} returns false.
for (;;) {
@@ -7398,8 +6925,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
sctx_T save_current_sctx;
- char *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
- linenr_T save_sourcing_lnum;
+ char *save_autocmd_fname, *save_autocmd_match;
int save_autocmd_bufnr;
funccal_entry_T funccal_entry;
@@ -7407,16 +6933,14 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// If this is called from a provider function, restore the scope
// information of the caller.
save_current_sctx = current_sctx;
- save_sourcing_name = sourcing_name;
- save_sourcing_lnum = sourcing_lnum;
save_autocmd_fname = autocmd_fname;
save_autocmd_match = autocmd_match;
save_autocmd_bufnr = autocmd_bufnr;
save_funccal(&funccal_entry);
current_sctx = provider_caller_scope.script_ctx;
- sourcing_name = provider_caller_scope.sourcing_name;
- sourcing_lnum = provider_caller_scope.sourcing_lnum;
+ ga_grow(&exestack, 1);
+ ((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry;
autocmd_fname = provider_caller_scope.autocmd_fname;
autocmd_match = provider_caller_scope.autocmd_match;
autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
@@ -7433,8 +6957,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (l_provider_call_nesting) {
current_sctx = save_current_sctx;
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
+ exestack.ga_len--;
autocmd_fname = save_autocmd_fname;
autocmd_match = save_autocmd_match;
autocmd_bufnr = save_autocmd_bufnr;
@@ -7568,14 +7091,13 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "screenattr()" function
static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int c;
-
- ScreenGrid *grid;
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
+ ScreenGrid *grid;
screenchar_adjust(&grid, &row, &col);
+ int c;
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
@@ -7587,14 +7109,13 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "screenchar()" function
static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int c;
-
- ScreenGrid *grid;
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
+ ScreenGrid *grid;
screenchar_adjust(&grid, &row, &col);
+ int c;
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
@@ -7606,10 +7127,10 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "screenchars()" function
static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- ScreenGrid *grid;
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
+ ScreenGrid *grid;
screenchar_adjust(&grid, &row, &col);
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
@@ -7640,10 +7161,6 @@ static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "screenpos({winid}, {lnum}, {col})" function
static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T pos;
- int row = 0;
- int scol = 0, ccol = 0, ecol = 0;
-
tv_dict_alloc_ret(rettv);
dict_T *dict = rettv->vval.v_dict;
@@ -7652,9 +7169,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- pos.lnum = (linenr_T)tv_get_number(&argvars[1]);
- pos.col = (colnr_T)tv_get_number(&argvars[2]) - 1;
- pos.coladd = 0;
+ pos_T pos = {
+ .lnum = (linenr_T)tv_get_number(&argvars[1]),
+ .col = (colnr_T)tv_get_number(&argvars[2]) - 1,
+ .coladd = 0
+ };
+ int row = 0;
+ int scol = 0, ccol = 0, ecol = 0;
textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
tv_dict_add_nr(dict, S_LEN("row"), row);
@@ -7722,7 +7243,6 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
{
bool save_p_ws = p_ws;
- int dir;
int flags = 0;
int retval = 0; // default: FAIL
long lnum_stop = 0;
@@ -7740,7 +7260,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
// Handle the optional fourth argument: flags.
- dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
+ int dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
if (dir == 0) {
goto theend;
}
@@ -7834,33 +7354,24 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
long time_limit)
FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- char *save_cpo;
- char_u *pat, *pat2 = NULL, *pat3 = NULL;
long retval = 0;
- pos_T pos;
- pos_T firstpos;
- pos_T foundpos;
- pos_T save_cursor;
- pos_T save_pos;
- int n;
int nest = 1;
bool use_skip = false;
int options = SEARCH_KEEP;
- proftime_T tm;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
- save_cpo = p_cpo;
+ char *save_cpo = p_cpo;
p_cpo = (char *)empty_option;
// Set the time limit, if there is one.
- tm = profile_setlimit(time_limit);
+ proftime_T tm = profile_setlimit(time_limit);
// Make two search patterns: start/end (pat2, for in nested pairs) and
// start/middle/end (pat3, for the top pair).
const size_t pat2_len = strlen(spat) + strlen(epat) + 17;
- pat2 = xmalloc(pat2_len);
+ char_u *pat2 = xmalloc(pat2_len);
const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25;
- pat3 = xmalloc(pat3_len);
+ char_u *pat3 = xmalloc(pat3_len);
snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
if (*mpat == NUL) {
STRCPY(pat3, pat2);
@@ -7876,19 +7387,21 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
use_skip = eval_expr_valid_arg(skip);
}
- save_cursor = curwin->w_cursor;
- pos = curwin->w_cursor;
+ pos_T save_cursor = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
+ pos_T firstpos;
clearpos(&firstpos);
+ pos_T foundpos;
clearpos(&foundpos);
- pat = pat3;
+ char_u *pat = pat3;
for (;;) {
- searchit_arg_T sia;
- memset(&sia, 0, sizeof(sia));
- sia.sa_stop_lnum = lnum_stop;
- sia.sa_tm = &tm;
+ searchit_arg_T sia = {
+ .sa_stop_lnum = lnum_stop,
+ .sa_tm = &tm,
+ };
- n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
- options, RE_SEARCH, &sia);
+ int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
+ options, RE_SEARCH, &sia);
if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
// didn't find it or found the first match again: FAIL
break;
@@ -7914,7 +7427,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
// If the skip pattern matches, ignore this match.
if (use_skip) {
- save_pos = curwin->w_cursor;
+ pos_T save_pos = curwin->w_cursor;
curwin->w_cursor = pos;
bool err = false;
const bool r = eval_expr_to_bool(skip, &err);
@@ -8103,13 +7616,13 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Otherwise use the column number as a byte offset.
static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
{
- pos_T pos;
- int fnum;
colnr_T curswant = -1;
rettv->vval.v_number = -1;
const char *const name = tv_get_string_chk(argvars);
if (name != NULL) {
+ pos_T pos;
+ int fnum;
if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) {
if (pos.col != MAXCOL && --pos.col < 0) {
pos.col = 0;
@@ -8143,15 +7656,13 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- dict_T *d;
- dictitem_T *di;
-
if (argvars[0].v_type != VAR_DICT) {
emsg(_(e_dictreq));
return;
}
- if ((d = argvars[0].vval.v_dict) != NULL) {
+ dict_T *d = argvars[0].vval.v_dict;
+ if (d != NULL) {
char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false);
if (csearch != NULL) {
int pcc[MAX_MCO];
@@ -8159,7 +7670,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
set_last_csearch(c, csearch, utfc_ptr2len((char *)csearch));
}
- di = tv_dict_find(d, S_LEN("forward"));
+ dictitem_T *di = tv_dict_find(d, S_LEN("forward"));
if (di != NULL) {
set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD);
}
@@ -8240,117 +7751,17 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv);
}
-/// Create quickfix/location list from VimL values
-///
-/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
-/// args argument in which case errors out, including VAR_UNKNOWN parameters.
-///
-/// @param[in,out] wp Window to create location list for. May be NULL in
-/// which case quickfix list will be created.
-/// @param[in] args [list, action, what]
-/// @param[in] args[0] Quickfix list contents.
-/// @param[in] args[1] Optional. Action to perform:
-/// append to an existing list, replace its content,
-/// or create a new one.
-/// @param[in] args[2] Optional. Quickfix list properties or title.
-/// Defaults to caller function name.
-/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
-static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
- FUNC_ATTR_NONNULL_ARG(2, 3)
-{
- static char *e_invact = N_("E927: Invalid action: '%s'");
- const char *title = NULL;
- char action = ' ';
- static int recursive = 0;
- rettv->vval.v_number = -1;
- dict_T *what = NULL;
-
- typval_T *list_arg = &args[0];
- if (list_arg->v_type != VAR_LIST) {
- emsg(_(e_listreq));
- return;
- } else if (recursive != 0) {
- emsg(_(e_au_recursive));
- return;
- }
-
- typval_T *action_arg = &args[1];
- if (action_arg->v_type == VAR_UNKNOWN) {
- // Option argument was not given.
- goto skip_args;
- } else if (action_arg->v_type != VAR_STRING) {
- emsg(_(e_stringreq));
- return;
- }
- const char *const act = tv_get_string_chk(action_arg);
- if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
- && act[1] == NUL) {
- action = *act;
- } else {
- semsg(_(e_invact), act);
- return;
- }
-
- typval_T *const what_arg = &args[2];
- if (what_arg->v_type == VAR_UNKNOWN) {
- // Option argument was not given.
- goto skip_args;
- } else if (what_arg->v_type == VAR_STRING) {
- title = tv_get_string_chk(what_arg);
- if (!title) {
- // Type error. Error already printed by tv_get_string_chk().
- return;
- }
- } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) {
- what = what_arg->vval.v_dict;
- } else {
- emsg(_(e_dictreq));
- return;
- }
-
-skip_args:
- if (!title) {
- title = (wp ? ":setloclist()" : ":setqflist()");
- }
-
- recursive++;
- list_T *const l = list_arg->vval.v_list;
- if (set_errorlist(wp, l, action, (char *)title, what) == OK) {
- rettv->vval.v_number = 0;
- }
- recursive--;
-}
-
-/// "setloclist()" function
-static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win;
-
- rettv->vval.v_number = -1;
-
- win = find_win_by_nr_or_id(&argvars[0]);
- if (win != NULL) {
- set_qf_ll_list(win, &argvars[1], rettv);
- }
-}
-
/// "setpos()" function
static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
set_position(argvars, rettv, false);
}
-/// "setqflist()" function
-static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- set_qf_ll_list(NULL, argvars, rettv);
-}
-
/// Translate a register type string to the yank type and block length
-static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *const block_len)
+static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len)
FUNC_ATTR_NONNULL_ALL
{
- char *stropt = (char *)(*pp);
+ char *stropt = *pp;
switch (*stropt) {
case 'v':
case 'c': // character-wise selection
@@ -8372,7 +7783,7 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c
default:
return FAIL;
}
- *pp = (char_u *)stropt;
+ *pp = stropt;
return OK;
}
@@ -8380,11 +7791,9 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c
static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
bool append = false;
- MotionType yank_type;
- long block_len;
- block_len = -1;
- yank_type = kMTUnknown;
+ long block_len = -1;
+ MotionType yank_type = kMTUnknown;
rettv->vval.v_number = 1; // FAIL is default.
@@ -8404,7 +7813,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (tv_dict_len(d) == 0) {
// Empty dict, clear the register (like setreg(0, []))
- char_u *lstval[2] = { NULL, NULL };
+ char *lstval[2] = { NULL, NULL };
write_reg_contents_lst(regname, lstval, false, kMTUnknown, -1);
return;
}
@@ -8416,7 +7825,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *stropt = tv_dict_get_string(d, "regtype", false);
if (stropt != NULL) {
- const int ret = get_yank_type((char_u **)&stropt, &yank_type, &block_len);
+ const int ret = get_yank_type((char **)&stropt, &yank_type, &block_len);
if (ret == FAIL || *(++stropt) != NUL) {
semsg(_(e_invargval), "value");
@@ -8459,7 +7868,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
set_unnamed = true;
break;
default:
- get_yank_type((char_u **)&stropt, &yank_type, &block_len);
+ get_yank_type((char **)&stropt, &yank_type, &block_len);
}
}
}
@@ -8493,7 +7902,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
});
*curval++ = NULL;
- write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, (colnr_T)block_len);
+ write_reg_contents_lst(regname, lstval, append, yank_type, (colnr_T)block_len);
free_lstval:
while (curallocval > allocval) {
@@ -8523,14 +7932,12 @@ free_lstval:
static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
static char *e_invact2 = N_("E962: Invalid action: '%s'");
- win_T *wp;
- dict_T *d;
char action = 'r';
rettv->vval.v_number = -1;
// first argument: window number or id
- wp = find_win_by_nr_or_id(&argvars[0]);
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
if (wp == NULL) {
return;
}
@@ -8540,7 +7947,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
emsg(_(e_dictreq));
return;
}
- d = argvars[1].vval.v_dict;
+ dict_T *d = argvars[1].vval.v_dict;
if (d == NULL) {
return;
}
@@ -8600,9 +8007,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 0;
if (argvars[0].v_type != VAR_UNKNOWN) {
- long col;
-
- col = (long)tv_get_number_chk(argvars, NULL);
+ long col = (long)tv_get_number_chk(argvars, NULL);
if (col < 0) {
return; // type error; errmsg already given
}
@@ -8681,10 +8086,9 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- bool rpc = false;
CallbackReader on_stdin = CALLBACK_READER_INIT;
dict_T *opts = argvars[0].vval.v_dict;
- rpc = tv_dict_get_number(opts, "rpc") != 0;
+ bool rpc = tv_dict_get_number(opts, "rpc") != 0;
if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
return;
@@ -8732,9 +8136,6 @@ static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "spellbadword()" function
static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- const char *word = "";
- hlf_T attr = HLF_COUNT;
- size_t len = 0;
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
@@ -8748,6 +8149,9 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ const char *word = "";
+ hlf_T attr = HLF_COUNT;
+ size_t len = 0;
if (argvars[0].v_type == VAR_UNKNOWN) {
// Find the start and length of the badly spelled word.
len = spell_move_to(curwin, FORWARD, true, true, &attr);
@@ -8779,22 +8183,16 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_alloc_ret(rettv, 2);
tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len);
tv_list_append_string(rettv->vval.v_list,
- (attr == HLF_SPB ? "bad"
- : attr == HLF_SPR ? "rare"
- : attr == HLF_SPL ? "local"
- : attr ==
- HLF_SPC ? "caps"
- :
- NULL), -1);
+ (attr == HLF_SPB ? "bad" :
+ attr == HLF_SPR ? "rare" :
+ attr == HLF_SPL ? "local" :
+ attr == HLF_SPC ? "caps" : NULL), -1);
}
/// "spellsuggest()" function
static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- bool typeerr = false;
- int maxcount;
garray_T ga = GA_EMPTY_INIT_VALUE;
- bool need_capital = false;
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
@@ -8808,8 +8206,11 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ int maxcount;
+ bool need_capital = false;
const char *const str = tv_get_string(&argvars[0]);
if (argvars[1].v_type != VAR_UNKNOWN) {
+ bool typeerr = false;
maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr);
if (maxcount <= 0) {
goto f_spellsuggest_return;
@@ -8838,14 +8239,12 @@ f_spellsuggest_return:
static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- char *save_cpo;
- int match;
colnr_T col = 0;
bool keepempty = false;
bool typeerr = false;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
- save_cpo = p_cpo;
+ char *save_cpo = p_cpo;
p_cpo = "";
const char *str = tv_get_string(&argvars[0]);
@@ -8878,6 +8277,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
};
if (regmatch.regprog != NULL) {
while (*str != NUL || keepempty) {
+ bool match;
if (*str == NUL) {
match = false; // Empty item at the end.
} else {
@@ -8978,7 +8378,6 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int base = 10;
- varnumber_T n;
int what = 0;
if (argvars[1].v_type != VAR_UNKNOWN) {
@@ -9008,6 +8407,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
what |= STR2NR_HEX | STR2NR_FORCE;
break;
}
+ varnumber_T n;
vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false);
// Text after the number is silently ignored.
if (isneg) {
@@ -9583,7 +8983,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const linenr_T lnum = tv_get_lnum(argvars);
const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
- memset(str, NUL, sizeof(str));
+ CLEAR_FIELD(str);
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0
&& (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {
@@ -9694,11 +9094,9 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Common code for tabpagewinnr() and winnr().
static int get_winnr(tabpage_T *tp, typval_T *argvar)
{
- win_T *twin;
int nr = 1;
- win_T *wp;
- twin = (tp == curtab) ? curwin : tp->tp_curwin;
+ win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin;
if (argvar->v_type != VAR_UNKNOWN) {
bool invalid_arg = false;
const char *const arg = tv_get_string_chk(argvar);
@@ -9713,20 +9111,20 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
}
} else {
// Extract the window count (if specified). e.g. winnr('3j')
- char_u *endp;
- long count = strtol((char *)arg, (char **)&endp, 10);
+ char *endp;
+ long count = strtol((char *)arg, &endp, 10);
if (count <= 0) {
// if count is not specified, default to 1
count = 1;
}
if (endp != NULL && *endp != '\0') {
- if (strequal((char *)endp, "j")) {
+ if (strequal(endp, "j")) {
twin = win_vert_neighbor(tp, twin, false, count);
- } else if (strequal((char *)endp, "k")) {
+ } else if (strequal(endp, "k")) {
twin = win_vert_neighbor(tp, twin, true, count);
- } else if (strequal((char *)endp, "h")) {
+ } else if (strequal(endp, "h")) {
twin = win_horz_neighbor(tp, twin, true, count);
- } else if (strequal((char *)endp, "l")) {
+ } else if (strequal(endp, "l")) {
twin = win_horz_neighbor(tp, twin, false, count);
} else {
invalid_arg = true;
@@ -9743,14 +9141,14 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
}
if (nr > 0) {
- for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
+ for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
wp != twin; wp = wp->w_next) {
if (wp == NULL) {
// didn't find it in this tabpage
nr = 0;
break;
}
- ++nr;
+ nr++;
}
}
return nr;
@@ -9772,13 +9170,11 @@ static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "tagfiles()" function
static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- char *fname;
- tagname_T tn;
-
tv_list_alloc_ret(rettv, kListLenUnknown);
- fname = xmalloc(MAXPATHL);
+ char *fname = xmalloc(MAXPATHL);
bool first = true;
+ tagname_T tn;
while (get_tagfname(&tn, first, (char_u *)fname) == OK) {
tv_list_append_string(rettv->vval.v_list, fname, -1);
first = false;
@@ -9857,7 +9253,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (new_cwd && *new_cwd != NUL) {
cwd = new_cwd;
// The new cwd must be a directory.
- if (!os_isdir_executable(cwd)) {
+ if (!os_isdir((const char_u *)cwd)) {
semsg(_(e_invarg2), "expected valid directory");
shell_free_argv(argv);
return;
@@ -9978,7 +9374,6 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr)
static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int repeat = 1;
- dict_T *dict;
rettv->vval.v_number = -1;
if (check_secure()) {
@@ -9986,8 +9381,8 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT
- || (dict = argvars[2].vval.v_dict) == NULL) {
+ dict_T *dict = argvars[2].vval.v_dict;
+ if (argvars[2].v_type != VAR_DICT || dict == NULL) {
semsg(_(e_invarg2), tv_get_string(&argvars[2]));
return;
}
@@ -10128,10 +9523,8 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char buf2[NUMBUFLEN];
const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
const char_u *mask = NULL;
- const char_u *tail;
const char_u *prev;
const char_u *p;
- int c1;
int dir = 0;
rettv->v_type = VAR_STRING;
@@ -10156,6 +9549,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+ int c1;
if (dir == 0 || dir == 1) {
// Trim leading characters
while (*head != NUL) {
@@ -10178,7 +9572,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
- tail = head + STRLEN(head);
+ const char_u *tail = head + STRLEN(head);
if (dir == 0 || dir == 2) {
// Trim trailing characters
for (; tail > head; tail = prev) {
@@ -10277,10 +9671,9 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
colnr_T vcol = 0;
- pos_T *fp;
int fnum = curbuf->b_fnum;
- fp = var2fpos(&argvars[0], false, &fnum, false);
+ pos_T *fp = var2fpos(&argvars[0], false, &fnum, false);
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
&& fnum == curbuf->b_fnum) {
// Limit the column to a valid value, getvvcol() doesn't check.
@@ -10293,7 +9686,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
getvvcol(curwin, fp, NULL, NULL, &vcol);
- ++vcol;
+ vcol++;
}
rettv->vval.v_number = vcol;
@@ -10384,17 +9777,14 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "win_move_separator()" function
static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- win_T *wp;
- int offset;
-
rettv->vval.v_number = false;
- wp = find_win_by_nr_or_id(&argvars[0]);
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
if (wp == NULL || wp->w_floating) {
return;
}
- offset = (int)tv_get_number(&argvars[1]);
+ int offset = (int)tv_get_number(&argvars[1]);
win_drag_vsep_line(wp, offset);
rettv->vval.v_number = true;
}
@@ -10475,18 +9865,15 @@ static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "winnr()" function
static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int nr = 1;
-
- nr = get_winnr(curtab, &argvars[0]);
- rettv->vval.v_number = nr;
+ rettv->vval.v_number = get_winnr(curtab, &argvars[0]);
}
/// "winrestcmd()" function
static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- garray_T ga;
char_u buf[50];
+ garray_T ga;
ga_init(&ga, (int)sizeof(char), 70);
// Do this twice to handle some window layouts properly.
@@ -10511,10 +9898,9 @@ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "winrestview()" function
static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- dict_T *dict;
+ dict_T *dict = argvars[0].vval.v_dict;
- if (argvars[0].v_type != VAR_DICT
- || (dict = argvars[0].vval.v_dict) == NULL) {
+ if (argvars[0].v_type != VAR_DICT || dict == NULL) {
emsg(_(e_invarg));
} else {
dictitem_T *di;
@@ -10562,10 +9948,8 @@ static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "winsaveview()" function
static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- dict_T *dict;
-
tv_dict_alloc_ret(rettv);
- dict = rettv->vval.v_dict;
+ dict_T *dict = rettv->vval.v_dict;
tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum);
tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col);
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index ff1808ed91..8822bb0491 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -233,7 +233,7 @@ void tv_list_init_static10(staticList10_T *const sl)
#define SL_SIZE ARRAY_SIZE(sl->sl_items)
list_T *const l = &sl->sl_list;
- memset(sl, 0, sizeof(staticList10_T));
+ CLEAR_POINTER(sl);
l->lv_first = &sl->sl_items[0];
l->lv_last = &sl->sl_items[SL_SIZE - 1];
l->lv_refcount = DO_NOT_FREE_CNT;
@@ -261,7 +261,7 @@ void tv_list_init_static10(staticList10_T *const sl)
void tv_list_init_static(list_T *const l)
FUNC_ATTR_NONNULL_ALL
{
- memset(l, 0, sizeof(*l));
+ CLEAR_POINTER(l);
l->lv_refcount = DO_NOT_FREE_CNT;
list_log(l, NULL, NULL, "sinit");
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index c02351947b..c4bc9f603b 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -13,10 +13,9 @@
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
#include "nvim/macros.h"
-#include "nvim/mbyte.h"
+#include "nvim/mbyte_defs.h"
#include "nvim/message.h"
#include "nvim/pos.h" // for linenr_T
-#include "nvim/profile.h" // for proftime_T
#include "nvim/types.h"
#ifdef LOG_LIST_ACTIONS
# include "nvim/memory.h"
@@ -356,6 +355,8 @@ struct ufunc {
///< used for s: variables
int uf_refcount; ///< reference count, see func_name_refcount()
funccall_T *uf_scoped; ///< l: local variables for closure
+ char_u *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with
+ ///< "<SNR>" as a string, otherwise NULL
char_u uf_name[]; ///< Name of function (actual size equals name);
///< can start with <SNR>123_
///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR)
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 2f4799db57..c46cb6ba5d 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -12,8 +12,8 @@
#include "nvim/eval/funcs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
@@ -21,7 +21,9 @@
#include "nvim/insexpand.h"
#include "nvim/lua/executor.h"
#include "nvim/os/input.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
@@ -44,7 +46,7 @@
# include "eval/userfunc.c.generated.h"
#endif
-hashtab_T func_hashtab;
+static hashtab_T func_hashtab;
// Used by get_func_tv()
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
@@ -66,13 +68,19 @@ void func_init(void)
hash_init(&func_hashtab);
}
+/// Return the function hash table
+hashtab_T *func_tbl_get(void)
+{
+ return &func_hashtab;
+}
+
/// Get function arguments.
-static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs,
+static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int *varargs,
garray_T *default_args, bool skip)
{
bool mustend = false;
- char_u *arg = *argp;
- char_u *p = arg;
+ char *arg = *argp;
+ char *p = arg;
char_u c;
int i;
@@ -89,7 +97,7 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i
// Isolate the arguments: "arg1, arg2, ...)"
bool any_default = false;
- while (*p != endchar) {
+ while (*p != (char)endchar) {
if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
if (varargs != NULL) {
*varargs = true;
@@ -111,44 +119,43 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i
}
if (newargs != NULL) {
ga_grow(newargs, 1);
- c = *p;
+ c = (char_u)(*p);
*p = NUL;
- arg = vim_strsave(arg);
+ arg = xstrdup(arg);
// Check for duplicate argument name.
for (i = 0; i < newargs->ga_len; i++) {
- if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
+ if (STRCMP(((char **)(newargs->ga_data))[i], arg) == 0) {
semsg(_("E853: Duplicate argument name: %s"), arg);
xfree(arg);
goto err_ret;
}
}
- ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ ((char **)(newargs->ga_data))[newargs->ga_len] = arg;
newargs->ga_len++;
- *p = c;
+ *p = (char)c;
}
- if (*skipwhite((char *)p) == '=' && default_args != NULL) {
+ if (*skipwhite(p) == '=' && default_args != NULL) {
typval_T rettv;
any_default = true;
- p = (char_u *)skipwhite((char *)p) + 1;
- p = (char_u *)skipwhite((char *)p);
- char_u *expr = p;
- if (eval1((char **)&p, &rettv, false) != FAIL) {
+ p = skipwhite(p) + 1;
+ p = skipwhite(p);
+ char_u *expr = (char_u *)p;
+ if (eval1(&p, &rettv, false) != FAIL) {
ga_grow(default_args, 1);
// trim trailing whitespace
- while (p > expr && ascii_iswhite(p[-1])) {
+ while (p > (char *)expr && ascii_iswhite(p[-1])) {
p--;
}
- c = *p;
+ c = (char_u)(*p);
*p = NUL;
expr = vim_strsave(expr);
- ((char_u **)(default_args->ga_data))
- [default_args->ga_len] = expr;
+ ((char **)(default_args->ga_data))[default_args->ga_len] = (char *)expr;
default_args->ga_len++;
- *p = c;
+ *p = (char)c;
} else {
mustend = true;
}
@@ -162,15 +169,15 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, i
mustend = true;
}
}
- p = (char_u *)skipwhite((char *)p);
- if (mustend && *p != endchar) {
+ p = skipwhite(p);
+ if (mustend && *p != (char)endchar) {
if (!skip) {
semsg(_(e_invarg2), *argp);
}
break;
}
}
- if (*p != endchar) {
+ if (*p != (char)endchar) {
goto err_ret;
}
p++; // skip "endchar"
@@ -213,10 +220,21 @@ char_u *get_lambda_name(void)
return name;
}
+static void set_ufunc_name(ufunc_T *fp, char_u *name)
+{
+ STRCPY(fp->uf_name, name);
+
+ if (name[0] == K_SPECIAL) {
+ fp->uf_name_exp = xmalloc(STRLEN(name) + 3);
+ STRCPY(fp->uf_name_exp, "<SNR>");
+ STRCAT(fp->uf_name_exp, fp->uf_name + 3);
+ }
+}
+
/// Parse a lambda expression and get a Funcref from "*arg".
///
/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
-int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
+int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
{
garray_T newargs = GA_EMPTY_INIT_VALUE;
garray_T *pnewargs;
@@ -224,13 +242,13 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
partial_T *pt = NULL;
int varargs;
int ret;
- char_u *start = (char_u *)skipwhite((char *)(*arg) + 1);
+ char_u *start = (char_u *)skipwhite(*arg + 1);
char_u *s, *e;
bool *old_eval_lavars = eval_lavars_used;
bool eval_lavars = false;
// First, check if this is a lambda expression. "->" must exists.
- ret = get_function_args(&start, '-', NULL, NULL, NULL, true);
+ ret = get_function_args((char **)&start, '-', NULL, NULL, NULL, true);
if (ret == FAIL || *start != '>') {
return NOTDONE;
}
@@ -241,7 +259,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
} else {
pnewargs = NULL;
}
- *arg = (char_u *)skipwhite((char *)(*arg) + 1);
+ *arg = skipwhite(*arg + 1);
ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, false);
if (ret == FAIL || **arg != '>') {
goto errret;
@@ -253,14 +271,14 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
}
// Get the start and the end of the expression.
- *arg = (char_u *)skipwhite((char *)(*arg) + 1);
- s = *arg;
- ret = skip_expr((char **)arg);
+ *arg = skipwhite((*arg) + 1);
+ s = (char_u *)(*arg);
+ ret = skip_expr(arg);
if (ret == FAIL) {
goto errret;
}
- e = *arg;
- *arg = (char_u *)skipwhite((char *)(*arg));
+ e = (char_u *)(*arg);
+ *arg = skipwhite(*arg);
if (**arg != '}') {
goto errret;
}
@@ -282,7 +300,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
// Add "return " before the expression.
size_t len = (size_t)(7 + e - s + 1);
p = (char_u *)xmalloc(len);
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p;
STRCPY(p, "return ");
STRLCPY(p + 7, s, e - s + 1);
if (strstr((char *)p + 7, "a:") == NULL) {
@@ -291,7 +309,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
}
fp->uf_refcount = 1;
- STRCPY(fp->uf_name, name);
+ set_ufunc_name(fp, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1);
@@ -313,7 +331,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
fp->uf_flags = flags;
fp->uf_calls = 0;
fp->uf_script_ctx = current_sctx;
- fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len;
+ fp->uf_script_ctx.sc_lnum += SOURCING_LNUM - newlines.ga_len;
pt->pt_func = fp;
pt->pt_refcount = 1;
@@ -411,34 +429,32 @@ void emsg_funcname(char *ermsg, const char_u *name)
/// @param funcexe various values
///
/// @return OK or FAIL.
-int get_func_tv(const char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe)
+int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe)
{
- char_u *argp;
+ char *argp;
int ret = OK;
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
int argcount = 0; // number of arguments found
- /*
- * Get the arguments.
- */
+ // Get the arguments.
argp = *arg;
while (argcount < MAX_FUNC_ARGS
- (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) {
- argp = (char_u *)skipwhite((char *)argp + 1); // skip the '(' or ','
+ argp = skipwhite(argp + 1); // skip the '(' or ','
if (*argp == ')' || *argp == ',' || *argp == NUL) {
break;
}
- if (eval1((char **)&argp, &argvars[argcount], funcexe->evaluate) == FAIL) {
+ if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) {
ret = FAIL;
break;
}
- ++argcount;
+ argcount++;
if (*argp != ',') {
break;
}
}
if (*argp == ')') {
- ++argp;
+ argp++;
} else {
ret = FAIL;
}
@@ -472,7 +488,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char_u **arg, func
tv_clear(&argvars[argcount]);
}
- *arg = (char_u *)skipwhite((char *)argp);
+ *arg = skipwhite(argp);
return ret;
}
@@ -740,6 +756,7 @@ static void func_clear_items(ufunc_T *fp)
ga_clear_strings(&(fp->uf_args));
ga_clear_strings(&(fp->uf_def_args));
ga_clear_strings(&(fp->uf_lines));
+ XFREE_CLEAR(fp->uf_name_exp);
if (fp->uf_cb_free != NULL) {
fp->uf_cb_free(fp->uf_cb_state);
@@ -803,8 +820,6 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
linenr_T firstline, linenr_T lastline, dict_T *selfdict)
FUNC_ATTR_NONNULL_ARG(1, 3, 4)
{
- char_u *save_sourcing_name;
- linenr_T save_sourcing_lnum;
bool using_sandbox = false;
funccall_T *fc;
int save_did_emsg;
@@ -814,7 +829,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
int ai;
bool islambda = false;
char_u numbuf[NUMBUFLEN];
- char_u *name;
+ char *name;
typval_T *tv_to_free[MAX_FUNC_ARGS];
int tv_to_free_len = 0;
proftime_T wait_start;
@@ -830,7 +845,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
rettv->vval.v_number = -1;
return;
}
- ++depth;
+ depth++;
// Save search patterns and redo buffer.
save_search_patterns();
if (!ins_compl_active()) {
@@ -870,7 +885,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// some compiler that checks the destination size.
v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
#ifndef __clang_analyzer__
- name = v->di_key;
+ name = (char *)v->di_key;
STRCPY(name, "self");
#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
@@ -896,7 +911,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// destination size.
v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
#ifndef __clang_analyzer__
- name = v->di_key;
+ name = (char *)v->di_key;
STRCPY(name, "000");
#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
@@ -935,13 +950,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// evaluate named argument default expression
isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount;
if (isdefault) {
- char_u *default_expr = NULL;
+ char *default_expr = NULL;
def_rettv.v_type = VAR_NUMBER;
def_rettv.vval.v_number = -1;
- default_expr = ((char_u **)(fp->uf_def_args.ga_data))
+ default_expr = ((char **)(fp->uf_def_args.ga_data))
[ai + fp->uf_def_args.ga_len];
- if (eval1((char **)&default_expr, &def_rettv, true) == FAIL) {
+ if (eval1(&default_expr, &def_rettv, true) == FAIL) {
default_arg_err = true;
break;
}
@@ -953,7 +968,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
}
// "..." argument a:1, a:2, etc.
snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
- name = numbuf;
+ name = (char *)numbuf;
}
if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
@@ -994,73 +1009,49 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Don't redraw while executing the function.
RedrawingDisabled++;
- save_sourcing_name = (char_u *)sourcing_name;
- save_sourcing_lnum = sourcing_lnum;
- sourcing_lnum = 1;
if (fp->uf_flags & FC_SANDBOX) {
using_sandbox = true;
sandbox++;
}
- // need space for new sourcing_name:
- // * save_sourcing_name
- // * "["number"].." or "function "
- // * "<SNR>" + fp->uf_name - 3
- // * terminating NUL
- size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name))
- + STRLEN(fp->uf_name) + 27;
- sourcing_name = xmalloc(len);
- {
- if (save_sourcing_name != NULL
- && STRNCMP(save_sourcing_name, "function ", 9) == 0) {
- vim_snprintf(sourcing_name,
- len,
- "%s[%" PRId64 "]..",
- save_sourcing_name,
- (int64_t)save_sourcing_lnum);
- } else {
- STRCPY(sourcing_name, "function ");
- }
- cat_func_name((char_u *)sourcing_name + STRLEN(sourcing_name), fp);
-
- if (p_verbose >= 12) {
- ++no_wait_return;
- verbose_enter_scroll();
+ estack_push_ufunc(fp, 1);
+ if (p_verbose >= 12) {
+ no_wait_return++;
+ verbose_enter_scroll();
- smsg(_("calling %s"), sourcing_name);
- if (p_verbose >= 14) {
- msg_puts("(");
- for (int i = 0; i < argcount; i++) {
- if (i > 0) {
- msg_puts(", ");
- }
- if (argvars[i].v_type == VAR_NUMBER) {
- msg_outnum((long)argvars[i].vval.v_number);
- } else {
- // Do not want errors such as E724 here.
- emsg_off++;
- char *tofree = encode_tv2string(&argvars[i], NULL);
- emsg_off--;
- if (tofree != NULL) {
- char *s = tofree;
- char buf[MSG_BUF_LEN];
- if (vim_strsize(s) > MSG_BUF_CLEN) {
- trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf));
- s = buf;
- }
- msg_puts(s);
- xfree(tofree);
+ smsg(_("calling %s"), SOURCING_NAME);
+ if (p_verbose >= 14) {
+ msg_puts("(");
+ for (int i = 0; i < argcount; i++) {
+ if (i > 0) {
+ msg_puts(", ");
+ }
+ if (argvars[i].v_type == VAR_NUMBER) {
+ msg_outnum((long)argvars[i].vval.v_number);
+ } else {
+ // Do not want errors such as E724 here.
+ emsg_off++;
+ char *tofree = encode_tv2string(&argvars[i], NULL);
+ emsg_off--;
+ if (tofree != NULL) {
+ char *s = tofree;
+ char buf[MSG_BUF_LEN];
+ if (vim_strsize(s) > MSG_BUF_CLEN) {
+ trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf));
+ s = buf;
}
+ msg_puts(s);
+ xfree(tofree);
}
}
- msg_puts(")");
}
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
+ msg_puts(")");
}
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ no_wait_return--;
}
const bool do_profiling_yes = do_profiling == PROF_YES;
@@ -1097,12 +1088,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
if (default_arg_err && (fp->uf_flags & FC_ABORT)) {
did_emsg = true;
} else if (islambda) {
- char_u *p = *(char_u **)fp->uf_lines.ga_data + 7;
+ char *p = *(char **)fp->uf_lines.ga_data + 7;
// A Lambda always has the command "return {expr}". It is much faster
// to evaluate {expr} directly.
ex_nesting_level++;
- (void)eval1((char **)&p, rettv, true);
+ (void)eval1(&p, rettv, true);
ex_nesting_level--;
} else {
// call do_cmdline() to execute the lines
@@ -1110,7 +1101,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
}
- --RedrawingDisabled;
+ RedrawingDisabled--;
// when the function was aborted because of an error, return -1
if ((did_emsg
@@ -1140,14 +1131,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// when being verbose, mention the return value
if (p_verbose >= 12) {
- ++no_wait_return;
+ no_wait_return++;
verbose_enter_scroll();
if (aborting()) {
- smsg(_("%s aborted"), sourcing_name);
+ smsg(_("%s aborted"), SOURCING_NAME);
} else if (fc->rettv->v_type == VAR_NUMBER) {
smsg(_("%s returning #%" PRId64 ""),
- sourcing_name, (int64_t)fc->rettv->vval.v_number);
+ SOURCING_NAME, (int64_t)fc->rettv->vval.v_number);
} else {
char buf[MSG_BUF_LEN];
@@ -1163,19 +1154,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
s = buf;
}
- smsg(_("%s returning %s"), sourcing_name, s);
+ smsg(_("%s returning %s"), SOURCING_NAME, s);
xfree(tofree);
}
}
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
- --no_wait_return;
+ no_wait_return--;
}
- xfree(sourcing_name);
- sourcing_name = (char *)save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
+ estack_pop();
current_sctx = save_current_sctx;
if (do_profiling_yes) {
script_prof_restore(&wait_start);
@@ -1184,15 +1173,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
sandbox--;
}
- if (p_verbose >= 12 && sourcing_name != NULL) {
- ++no_wait_return;
+ if (p_verbose >= 12 && SOURCING_NAME != NULL) {
+ no_wait_return++;
verbose_enter_scroll();
- smsg(_("continuing in %s"), sourcing_name);
+ smsg(_("continuing in %s"), SOURCING_NAME);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
- --no_wait_return;
+ no_wait_return--;
}
did_emsg |= save_did_emsg;
@@ -1633,9 +1622,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
msg_puts(" ");
}
msg_puts(force ? "function! " : "function ");
- if (fp->uf_name[0] == K_SPECIAL) {
- msg_puts_attr("<SNR>", HL_ATTR(HLF_8));
- msg_puts((const char *)fp->uf_name + 3);
+ if (fp->uf_name_exp != NULL) {
+ msg_puts((const char *)fp->uf_name_exp);
} else {
msg_puts((const char *)fp->uf_name);
}
@@ -1691,7 +1679,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
/// @param partial return: partial of a FuncRef
///
/// @return the function name in allocated memory, or NULL for failure.
-char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial)
+char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial)
FUNC_ATTR_NONNULL_ARG(1)
{
char_u *name = NULL;
@@ -1702,13 +1690,13 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
lval_T lv;
if (fdp != NULL) {
- memset(fdp, 0, sizeof(funcdict_T));
+ CLEAR_POINTER(fdp);
}
- start = *pp;
+ start = (char_u *)(*pp);
// Check for hard coded <SNR>: already translated function ID (from a user
// command).
- if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA
+ if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA
&& (*pp)[2] == KE_SNR) {
*pp += 3;
len = get_id_len((const char **)pp) + 3;
@@ -1742,7 +1730,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
semsg(_(e_invarg2), start);
}
} else {
- *pp = (char_u *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR);
+ *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR);
}
goto theend;
}
@@ -1756,7 +1744,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
}
if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) {
name = vim_strsave((char_u *)lv.ll_tv->vval.v_string);
- *pp = (char_u *)end;
+ *pp = (char *)end;
} else if (lv.ll_tv->v_type == VAR_PARTIAL
&& lv.ll_tv->vval.v_partial != NULL) {
if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') {
@@ -1767,10 +1755,10 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
}
name = xmallocz((size_t)len);
memcpy(name, end + 1, (size_t)len);
- *pp = (char_u *)end + 1 + len;
+ *pp = (char *)end + 1 + len;
} else {
name = vim_strsave((char_u *)partial_name(lv.ll_tv->vval.v_partial));
- *pp = (char_u *)end;
+ *pp = (char *)end;
}
if (partial != NULL) {
*partial = lv.ll_tv->vval.v_partial;
@@ -1781,7 +1769,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
|| fdp->fd_newkey == NULL)) {
emsg(_(e_funcref));
} else {
- *pp = (char_u *)end;
+ *pp = (char *)end;
}
name = NULL;
}
@@ -1790,7 +1778,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
if (lv.ll_name == NULL) {
// Error found, but continue after the function name.
- *pp = (char_u *)end;
+ *pp = (char *)end;
goto theend;
}
@@ -1803,16 +1791,16 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
name = NULL;
}
} else if (!(flags & TFN_NO_DEREF)) {
- len = (int)(end - *pp);
+ len = (int)(end - (char_u *)(*pp));
name = deref_func_name((const char *)(*pp), &len, partial,
flags & TFN_NO_AUTOLOAD);
- if (name == *pp) {
+ if (name == (char_u *)(*pp)) {
name = NULL;
}
}
if (name != NULL) {
name = vim_strsave(name);
- *pp = (char_u *)end;
+ *pp = (char *)end;
if (STRNCMP(name, "<SNR>", 5) == 0) {
// Change "<SNR>" to the byte sequence.
name[0] = K_SPECIAL;
@@ -1890,7 +1878,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp,
}
memmove(name + lead, lv.ll_name, (size_t)len);
name[lead + len] = NUL;
- *pp = (char_u *)end;
+ *pp = (char *)end;
theend:
clear_lval(&lv);
@@ -1941,7 +1929,7 @@ void ex_function(exarg_T *eap)
todo = (int)func_hashtab.ht_used;
for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
- --todo;
+ todo--;
fp = HI2UF(hi);
if (message_filtered(fp->uf_name)) {
continue;
@@ -1974,7 +1962,7 @@ void ex_function(exarg_T *eap)
todo = (int)func_hashtab.ht_used;
for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
- --todo;
+ todo--;
fp = HI2UF(hi);
if (!isdigit(*fp->uf_name)
&& vim_regexec(&regmatch, (char *)fp->uf_name, 0)) {
@@ -1986,7 +1974,7 @@ void ex_function(exarg_T *eap)
}
}
if (*p == '/') {
- ++p;
+ p++;
}
eap->nextcmd = (char *)check_nextcmd(p);
return;
@@ -2007,7 +1995,7 @@ void ex_function(exarg_T *eap)
// s:func script-local function name
// g:func global function name, same as "func"
p = (char_u *)eap->arg;
- name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
+ name = trans_function_name((char **)&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
paren = (vim_strchr((char *)p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
/*
@@ -2064,7 +2052,7 @@ void ex_function(exarg_T *eap)
msg_putchar(' ');
}
}
- msg_prt_line(FUNCLINE(fp, j), false);
+ msg_prt_line((char_u *)FUNCLINE(fp, j), false);
ui_flush(); // show a line at a time
os_breakcheck();
}
@@ -2108,9 +2096,8 @@ void ex_function(exarg_T *eap)
}
if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) {
int j = (*arg == K_SPECIAL) ? 3 : 0;
- while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j])
- : eval_isnamec(arg[j]))) {
- ++j;
+ while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) : eval_isnamec(arg[j]))) {
+ j++;
}
if (arg[j] != NUL) {
emsg_funcname((char *)e_invarg2, arg);
@@ -2122,7 +2109,7 @@ void ex_function(exarg_T *eap)
}
}
- if (get_function_args(&p, ')', &newargs, &varargs,
+ if (get_function_args((char **)&p, ')', &newargs, &varargs,
&default_args, eap->skip) == FAIL) {
goto errret_2;
}
@@ -2192,7 +2179,7 @@ void ex_function(exarg_T *eap)
}
// Save the starting line number.
- sourcing_lnum_top = sourcing_lnum;
+ sourcing_lnum_top = SOURCING_LNUM;
indent = 2;
nesting = 0;
@@ -2234,10 +2221,10 @@ void ex_function(exarg_T *eap)
ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
}
- // Detect line continuation: sourcing_lnum increased more than one.
+ // Detect line continuation: SOURCING_LNUM increased more than one.
sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
- if (sourcing_lnum < sourcing_lnum_off) {
- sourcing_lnum_off -= sourcing_lnum;
+ if (SOURCING_LNUM < sourcing_lnum_off) {
+ sourcing_lnum_off -= SOURCING_LNUM;
} else {
sourcing_lnum_off = 0;
}
@@ -2315,7 +2302,7 @@ void ex_function(exarg_T *eap)
p = (char_u *)skipwhite((char *)p + 1);
}
p += eval_fname_script((const char *)p);
- xfree(trans_function_name(&p, true, 0, NULL, NULL));
+ xfree(trans_function_name((char **)&p, true, 0, NULL, NULL));
if (*skipwhite((char *)p) == '(') {
nesting++;
indent += 2;
@@ -2400,12 +2387,12 @@ void ex_function(exarg_T *eap)
// allocates 250 bytes per line, this saves 80% on average. The cost
// is an extra alloc/free.
p = vim_strsave(theline);
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p;
// Add NULL lines for continuation lines, so that the line count is
// equal to the index in the growarray.
while (sourcing_lnum_off-- > 0) {
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
+ ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL;
}
// Check for end of eap->arg.
@@ -2454,9 +2441,12 @@ void ex_function(exarg_T *eap)
fp = NULL;
overwrite = true;
} else {
- // redefine existing function
+ char_u *exp_name = fp->uf_name_exp;
+ // redefine existing function, keep the expanded name
XFREE_CLEAR(name);
+ fp->uf_name_exp = NULL;
func_clear_items(fp);
+ fp->uf_name_exp = exp_name;
fp->uf_profiling = false;
fp->uf_prof_initialized = false;
}
@@ -2495,13 +2485,12 @@ void ex_function(exarg_T *eap)
// Check that the autoload name matches the script name.
int j = FAIL;
- if (sourcing_name != NULL) {
+ if (SOURCING_NAME != NULL) {
scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name));
p = (char_u *)vim_strchr((char *)scriptname, '/');
plen = (int)STRLEN(p);
- slen = (int)STRLEN(sourcing_name);
- if (slen > plen && FNAMECMP(p,
- sourcing_name + slen - plen) == 0) {
+ slen = (int)STRLEN(SOURCING_NAME);
+ if (slen > plen && FNAMECMP(p, SOURCING_NAME + slen - plen) == 0) {
j = OK;
}
xfree(scriptname);
@@ -2536,7 +2525,7 @@ void ex_function(exarg_T *eap)
}
// insert the new function in the function list
- STRCPY(fp->uf_name, name);
+ set_ufunc_name(fp, name);
if (overwrite) {
hi = hash_find(&func_hashtab, (char *)name);
hi->hi_key = UF2HIKEY(fp);
@@ -2628,8 +2617,7 @@ bool function_exists(const char *const name, bool no_deref)
if (no_deref) {
flag |= TFN_NO_DEREF;
}
- char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL,
- NULL);
+ char *const p = (char *)trans_function_name((char **)&nm, false, flag, NULL, NULL);
nm = (char_u *)skipwhite((char *)nm);
// Only accept "funcname", "funcname ", "funcname (..." and
@@ -2656,10 +2644,10 @@ char *get_user_func_name(expand_T *xp, int idx)
assert(hi);
if (done < func_hashtab.ht_used) {
if (done++ > 0) {
- ++hi;
+ hi++;
}
while (HASHITEM_EMPTY(hi)) {
- ++hi;
+ hi++;
}
fp = HI2UF(hi);
@@ -2693,7 +2681,7 @@ void ex_delfunction(exarg_T *eap)
funcdict_T fudi;
p = (char_u *)eap->arg;
- name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
+ name = trans_function_name((char **)&p, eap->skip, 0, &fudi, NULL);
xfree(fudi.fd_newkey);
if (name == NULL) {
if (fudi.fd_dict != NULL && !eap->skip) {
@@ -2867,7 +2855,7 @@ void ex_return(exarg_T *eap)
}
if (eap->skip) {
- ++emsg_skip;
+ emsg_skip++;
}
eap->nextcmd = NULL;
@@ -2899,7 +2887,7 @@ void ex_return(exarg_T *eap)
}
if (eap->skip) {
- --emsg_skip;
+ emsg_skip--;
}
}
@@ -2932,7 +2920,7 @@ void ex_call(exarg_T *eap)
return;
}
- tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
+ tofree = trans_function_name((char **)&arg, false, TFN_INT, &fudi, &partial);
if (fudi.fd_newkey != NULL) {
// Still need to give an error message for missing key.
semsg(_(e_dictkey), fudi.fd_newkey);
@@ -2987,7 +2975,7 @@ void ex_call(exarg_T *eap)
funcexe.evaluate = true;
funcexe.partial = partial;
funcexe.selfdict = fudi.fd_dict;
- if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) {
+ if (get_func_tv(name, -1, &rettv, (char **)&arg, &funcexe) == FAIL) {
failed = true;
break;
}
@@ -3145,8 +3133,7 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat)
// If breakpoints have been added/deleted need to check for it.
if (fcp->dbg_tick != debug_tick) {
- fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
- sourcing_lnum);
+ fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM);
fcp->dbg_tick = debug_tick;
}
if (do_profiling == PROF_YES) {
@@ -3160,14 +3147,14 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat)
} else {
// Skip NULL lines (continuation lines).
while (fcp->linenr < gap->ga_len
- && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) {
+ && ((char **)(gap->ga_data))[fcp->linenr] == NULL) {
fcp->linenr++;
}
if (fcp->linenr >= gap->ga_len) {
retval = NULL;
} else {
- retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]);
- sourcing_lnum = fcp->linenr;
+ retval = (char_u *)xstrdup(((char **)(gap->ga_data))[fcp->linenr++]);
+ SOURCING_LNUM = fcp->linenr;
if (do_profiling == PROF_YES) {
func_line_start(cookie);
}
@@ -3175,11 +3162,10 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat)
}
// Did we encounter a breakpoint?
- if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) {
- dbg_breakpoint(fp->uf_name, sourcing_lnum);
+ if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) {
+ dbg_breakpoint(fp->uf_name, SOURCING_LNUM);
// Find next breakpoint.
- fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name,
- sourcing_lnum);
+ fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM);
fcp->dbg_tick = debug_tick;
}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index ed86aaad4a..4b7007aae9 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -4,6 +4,11 @@
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
+// From user function to hashitem and back.
+#define UF2HIKEY(fp) ((fp)->uf_name)
+#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
+#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+
///< Structure used by trans_function_name()
typedef struct {
dict_T *fd_dict; ///< Dictionary used.
@@ -59,8 +64,8 @@ typedef struct {
.basetv = NULL, \
}
-#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
-#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
+#define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j]
+#define FUNCLINE(fp, j) ((char **)(fp->uf_lines.ga_data))[j]
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/userfunc.h.generated.h"
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index ea1c3a8c4e..51123e1a85 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -7,6 +7,7 @@
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
@@ -15,8 +16,10 @@
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/window.h"
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 9d1ac185d4..bc8e823797 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -15,15 +15,18 @@
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
@@ -36,6 +39,7 @@
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/help.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
@@ -59,9 +63,9 @@
#include "nvim/os_unix.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/strings.h"
@@ -300,7 +304,7 @@ void ex_align(exarg_T *eap)
new_indent--;
break;
}
- --new_indent;
+ new_indent--;
}
}
}
@@ -1091,12 +1095,12 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
if (line1 == n) {
line1 = curwin->w_cursor.lnum;
}
- ++line1;
+ line1++;
if (curwin->w_cursor.lnum < line1) {
- ++line1;
+ line1++;
}
if (curwin->w_cursor.lnum < line2) {
- ++line2;
+ line2++;
}
++curwin->w_cursor.lnum;
}
@@ -1198,7 +1202,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
break;
}
}
- ++p;
+ p++;
}
} while (trailarg != NULL);
@@ -1442,7 +1446,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
beginline(BL_WHITE | BL_FIX); // cursor on first non-blank
- --no_wait_return;
+ no_wait_return--;
if (linecount > p_report) {
if (do_in) {
@@ -1460,8 +1464,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
error:
// put cursor back in same position for ":w !cmd"
curwin->w_cursor = cursor_save;
- --no_wait_return;
- wait_return(FALSE);
+ no_wait_return--;
+ wait_return(false);
}
filterend:
@@ -2111,7 +2115,7 @@ void do_wqall(exarg_T *eap)
* 4. if overwriting is allowed (even after a dialog)
*/
if (not_writing()) {
- ++error;
+ error++;
break;
}
if (buf->b_ffname == NULL) {
@@ -2246,7 +2250,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln
}
}
if (other) {
- --no_wait_return;
+ no_wait_return--;
}
if (setpm) {
setpcmark();
@@ -2959,7 +2963,7 @@ void ex_append(exarg_T *eap)
}
if (eap->cmdidx != CMD_append) {
- --lnum;
+ lnum--;
}
// when the buffer is empty need to delete the dummy line
@@ -3015,7 +3019,7 @@ void ex_append(exarg_T *eap)
vcol = 0;
for (p = theline; indent > vcol; ++p) {
if (*p == ' ') {
- ++vcol;
+ vcol++;
} else if (*p == TAB) {
vcol += 8 - vcol % 8;
} else {
@@ -3044,7 +3048,7 @@ void ex_append(exarg_T *eap)
}
xfree(theline);
- ++lnum;
+ lnum++;
if (empty) {
ml_delete(2L, false);
@@ -3136,10 +3140,10 @@ void ex_z(exarg_T *eap)
kind = x;
if (*kind == '-' || *kind == '+' || *kind == '='
|| *kind == '^' || *kind == '.') {
- ++x;
+ x++;
}
while (*x == '-' || *x == '+') {
- ++x;
+ x++;
}
if (*x != 0) {
@@ -3196,7 +3200,7 @@ void ex_z(exarg_T *eap)
if (*kind == '+') {
start += (linenr_T)bigness * (linenr_T)(x - kind - 1) + 1;
} else if (eap->addr_count == 0) {
- ++start;
+ start++;
}
end = start + (linenr_T)bigness - 1;
curs = end;
@@ -3343,6 +3347,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo
if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) {
save_re_pat(RE_SUBST, (char_u *)pat, p_magic);
}
+ // put pattern in history
add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL);
}
@@ -3541,7 +3546,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
which_pat = RE_LAST; // use last used regexp
delimiter = (char_u)(*cmd++); // remember delimiter character
pat = cmd; // remember start of search pat
- cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, (char_u **)&eap->arg);
+ cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, &eap->arg);
if (cmd[0] == delimiter) { // end delimiter found
*cmd++ = NUL; // replace it with a NUL
has_second_delim = true;
@@ -4296,7 +4301,7 @@ skip:
* has been appended to new_start, we don't need
* it in the buffer.
*/
- ++lnum;
+ lnum++;
if (u_savedel(lnum, nmatch_tl) != OK) {
break;
}
@@ -4626,7 +4631,7 @@ void ex_global(exarg_T *eap)
delim = *cmd; // get the delimiter
cmd++; // skip delimiter if there is one
pat = cmd; // remember start of pattern
- cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, (char_u **)&eap->arg);
+ cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, &eap->arg);
if (cmd[0] == delim) { // end delimiter found
*cmd++ = NUL; // replace it with a NUL
}
@@ -4775,1124 +4780,6 @@ bool prepare_tagpreview(bool undo_sync)
return false;
}
-/// ":help": open a read-only window on a help file
-void ex_help(exarg_T *eap)
-{
- char *arg;
- char *tag;
- FILE *helpfd; // file descriptor of help file
- int n;
- int i;
- win_T *wp;
- int num_matches;
- char **matches;
- char *p;
- int empty_fnum = 0;
- int alt_fnum = 0;
- buf_T *buf;
- int len;
- char *lang;
- const bool old_KeyTyped = KeyTyped;
-
- if (eap != NULL) {
- /*
- * A ":help" command ends at the first LF, or at a '|' that is
- * followed by some text. Set nextcmd to the following command.
- */
- for (arg = eap->arg; *arg; arg++) {
- if (*arg == '\n' || *arg == '\r'
- || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) {
- *arg++ = NUL;
- eap->nextcmd = arg;
- break;
- }
- }
- arg = eap->arg;
-
- if (eap->forceit && *arg == NUL && !curbuf->b_help) {
- emsg(_("E478: Don't panic!"));
- return;
- }
-
- if (eap->skip) { // not executing commands
- return;
- }
- } else {
- arg = "";
- }
-
- // remove trailing blanks
- p = arg + STRLEN(arg) - 1;
- while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') {
- *p-- = NUL;
- }
-
- // Check for a specified language
- lang = check_help_lang(arg);
-
- // When no argument given go to the index.
- if (*arg == NUL) {
- arg = "help.txt";
- }
-
- /*
- * Check if there is a match for the argument.
- */
- n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit);
-
- i = 0;
- if (n != FAIL && lang != NULL) {
- // Find first item with the requested language.
- for (i = 0; i < num_matches; ++i) {
- len = (int)STRLEN(matches[i]);
- if (len > 3 && matches[i][len - 3] == '@'
- && STRICMP(matches[i] + len - 2, lang) == 0) {
- break;
- }
- }
- }
- if (i >= num_matches || n == FAIL) {
- if (lang != NULL) {
- semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg);
- } else {
- semsg(_("E149: Sorry, no help for %s"), arg);
- }
- if (n != FAIL) {
- FreeWild(num_matches, matches);
- }
- return;
- }
-
- // The first match (in the requested language) is the best match.
- tag = xstrdup(matches[i]);
- FreeWild(num_matches, matches);
-
- /*
- * Re-use an existing help window or open a new one.
- * Always open a new one for ":tab help".
- */
- if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) {
- if (cmdmod.cmod_tab != 0) {
- wp = NULL;
- } else {
- wp = NULL;
- FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (bt_help(wp2->w_buffer)) {
- wp = wp2;
- break;
- }
- }
- }
- if (wp != NULL && wp->w_buffer->b_nwindows > 0) {
- win_enter(wp, true);
- } else {
- // There is no help window yet.
- // Try to open the file specified by the "helpfile" option.
- if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) {
- smsg(_("Sorry, help file \"%s\" not found"), p_hf);
- goto erret;
- }
- fclose(helpfd);
-
- // Split off help window; put it at far top if no position
- // specified, the current window is vertically split and
- // narrow.
- n = WSP_HELP;
- if (cmdmod.cmod_split == 0 && curwin->w_width != Columns
- && curwin->w_width < 80) {
- n |= WSP_TOP;
- }
- if (win_split(0, n) == FAIL) {
- goto erret;
- }
-
- if (curwin->w_height < p_hh) {
- win_setheight((int)p_hh);
- }
-
- /*
- * Open help file (do_ecmd() will set b_help flag, readfile() will
- * set b_p_ro flag).
- * Set the alternate file to the previously edited file.
- */
- alt_fnum = curbuf->b_fnum;
- (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL,
- ECMD_HIDE + ECMD_SET_HELP,
- NULL); // buffer is still open, don't store info
-
- if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
- curwin->w_alt_fnum = alt_fnum;
- }
- empty_fnum = curbuf->b_fnum;
- }
- }
-
- restart_edit = 0; // don't want insert mode in help file
-
- // Restore KeyTyped, setting 'filetype=help' may reset it.
- // It is needed for do_tag top open folds under the cursor.
- KeyTyped = old_KeyTyped;
-
- do_tag((char_u *)tag, DT_HELP, 1, false, true);
-
- // Delete the empty buffer if we're not using it. Careful: autocommands
- // may have jumped to another window, check that the buffer is not in a
- // window.
- if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) {
- buf = buflist_findnr(empty_fnum);
- if (buf != NULL && buf->b_nwindows == 0) {
- wipe_buffer(buf, true);
- }
- }
-
- // keep the previous alternate file
- if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum
- && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
- curwin->w_alt_fnum = alt_fnum;
- }
-
-erret:
- xfree(tag);
-}
-
-/// In an argument search for a language specifiers in the form "@xx".
-/// Changes the "@" to NUL if found, and returns a pointer to "xx".
-///
-/// @return NULL if not found.
-char *check_help_lang(char *arg)
-{
- int len = (int)STRLEN(arg);
-
- if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2])
- && ASCII_ISALPHA(arg[len - 1])) {
- arg[len - 3] = NUL; // remove the '@'
- return arg + len - 2;
- }
- return NULL;
-}
-
-/// Return a heuristic indicating how well the given string matches. The
-/// smaller the number, the better the match. This is the order of priorities,
-/// from best match to worst match:
-/// - Match with least alphanumeric characters is better.
-/// - Match with least total characters is better.
-/// - Match towards the start is better.
-/// - Match starting with "+" is worse (feature instead of command)
-/// Assumption is made that the matched_string passed has already been found to
-/// match some string for which help is requested. webb.
-///
-/// @param offset offset for match
-/// @param wrong_case no matching case
-///
-/// @return a heuristic indicating how well the given string matches.
-int help_heuristic(char *matched_string, int offset, int wrong_case)
- FUNC_ATTR_PURE
-{
- int num_letters;
- char *p;
-
- num_letters = 0;
- for (p = matched_string; *p; p++) {
- if (ASCII_ISALNUM(*p)) {
- num_letters++;
- }
- }
-
- /*
- * Multiply the number of letters by 100 to give it a much bigger
- * weighting than the number of characters.
- * If there only is a match while ignoring case, add 5000.
- * If the match starts in the middle of a word, add 10000 to put it
- * somewhere in the last half.
- * If the match is more than 2 chars from the start, multiply by 200 to
- * put it after matches at the start.
- */
- if (offset > 0
- && ASCII_ISALNUM(matched_string[offset])
- && ASCII_ISALNUM(matched_string[offset - 1])) {
- offset += 10000;
- } else if (offset > 2) {
- offset *= 200;
- }
- if (wrong_case) {
- offset += 5000;
- }
- // Features are less interesting than the subjects themselves, but "+"
- // alone is not a feature.
- if (matched_string[0] == '+' && matched_string[1] != NUL) {
- offset += 100;
- }
- return 100 * num_letters + (int)STRLEN(matched_string) + offset;
-}
-
-/// Compare functions for qsort() below, that checks the help heuristics number
-/// that has been put after the tagname by find_tags().
-static int help_compare(const void *s1, const void *s2)
-{
- char *p1;
- char *p2;
-
- p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
- p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
-
- // Compare by help heuristic number first.
- int cmp = strcmp(p1, p2);
- if (cmp != 0) {
- return cmp;
- }
-
- // Compare by strings as tie-breaker when same heuristic number.
- return strcmp(*(char **)s1, *(char **)s2);
-}
-
-/// Find all help tags matching "arg", sort them and return in matches[], with
-/// the number of matches in num_matches.
-/// The matches will be sorted with a "best" match algorithm.
-/// When "keep_lang" is true try keeping the language of the current buffer.
-int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang)
-{
- int i;
-
- // Specific tags that either have a specific replacement or won't go
- // through the generic rules.
- static char *(except_tbl[][2]) = {
- { "*", "star" },
- { "g*", "gstar" },
- { "[*", "[star" },
- { "]*", "]star" },
- { ":*", ":star" },
- { "/*", "/star" }, // NOLINT
- { "/\\*", "/\\\\star" },
- { "\"*", "quotestar" },
- { "**", "starstar" },
- { "cpo-*", "cpo-star" },
- { "/\\(\\)", "/\\\\(\\\\)" },
- { "/\\%(\\)", "/\\\\%(\\\\)" },
- { "?", "?" },
- { "??", "??" },
- { ":?", ":?" },
- { "?<CR>", "?<CR>" },
- { "g?", "g?" },
- { "g?g?", "g?g?" },
- { "g??", "g??" },
- { "-?", "-?" },
- { "q?", "q?" },
- { "v_g?", "v_g?" },
- { "/\\?", "/\\\\?" },
- { "/\\z(\\)", "/\\\\z(\\\\)" },
- { "\\=", "\\\\=" },
- { ":s\\=", ":s\\\\=" },
- { "[count]", "\\[count]" },
- { "[quotex]", "\\[quotex]" },
- { "[range]", "\\[range]" },
- { ":[range]", ":\\[range]" },
- { "[pattern]", "\\[pattern]" },
- { "\\|", "\\\\bar" },
- { "\\%$", "/\\\\%\\$" },
- { "s/\\~", "s/\\\\\\~" },
- { "s/\\U", "s/\\\\U" },
- { "s/\\L", "s/\\\\L" },
- { "s/\\1", "s/\\\\1" },
- { "s/\\2", "s/\\\\2" },
- { "s/\\3", "s/\\\\3" },
- { "s/\\9", "s/\\\\9" },
- { NULL, NULL }
- };
-
- static const char *(expr_table[]) = {
- "!=?", "!~?", "<=?", "<?", "==?", "=~?",
- ">=?", ">?", "is?", "isnot?"
- };
- char *d = (char *)IObuff; // assume IObuff is long enough!
- d[0] = NUL;
-
- if (STRNICMP(arg, "expr-", 5) == 0) {
- // When the string starting with "expr-" and containing '?' and matches
- // the table, it is taken literally (but ~ is escaped). Otherwise '?'
- // is recognized as a wildcard.
- for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) {
- if (STRCMP(arg + 5, expr_table[i]) == 0) {
- for (int si = 0, di = 0;; si++) {
- if (arg[si] == '~') {
- d[di++] = '\\';
- }
- d[di++] = arg[si];
- if (arg[si] == NUL) {
- break;
- }
- }
- break;
- }
- }
- } else {
- // Recognize a few exceptions to the rule. Some strings that contain
- // '*'are changed to "star", otherwise '*' is recognized as a wildcard.
- for (i = 0; except_tbl[i][0] != NULL; i++) {
- if (STRCMP(arg, except_tbl[i][0]) == 0) {
- STRCPY(d, except_tbl[i][1]);
- break;
- }
- }
- }
-
- if (d[0] == NUL) { // no match in table
- // Replace "\S" with "/\\S", etc. Otherwise every tag is matched.
- // Also replace "\%^" and "\%(", they match every tag too.
- // Also "\zs", "\z1", etc.
- // Also "\@<", "\@=", "\@<=", etc.
- // And also "\_$" and "\_^".
- if (arg[0] == '\\'
- && ((arg[1] != NUL && arg[2] == NUL)
- || (vim_strchr("%_z@", arg[1]) != NULL
- && arg[2] != NUL))) {
- vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1);
- // Check for "/\\_$", should be "/\\_\$"
- if (d[3] == '_' && d[4] == '$') {
- STRCPY(d + 4, "\\$");
- }
- } else {
- // Replace:
- // "[:...:]" with "\[:...:]"
- // "[++...]" with "\[++...]"
- // "\{" with "\\{" -- matching "} \}"
- if ((arg[0] == '[' && (arg[1] == ':'
- || (arg[1] == '+' && arg[2] == '+')))
- || (arg[0] == '\\' && arg[1] == '{')) {
- *d++ = '\\';
- }
-
- // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'.
- if (*arg == '(' && arg[1] == '\'') {
- arg++;
- }
- for (const char *s = arg; *s; s++) {
- // Replace "|" with "bar" and '"' with "quote" to match the name of
- // the tags for these commands.
- // Replace "*" with ".*" and "?" with "." to match command line
- // completion.
- // Insert a backslash before '~', '$' and '.' to avoid their
- // special meaning.
- if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!?
- break;
- }
- switch (*s) {
- case '|':
- STRCPY(d, "bar");
- d += 3;
- continue;
- case '"':
- STRCPY(d, "quote");
- d += 5;
- continue;
- case '*':
- *d++ = '.';
- break;
- case '?':
- *d++ = '.';
- continue;
- case '$':
- case '.':
- case '~':
- *d++ = '\\';
- break;
- }
-
- /*
- * Replace "^x" by "CTRL-X". Don't do this for "^_" to make
- * ":help i_^_CTRL-D" work.
- * Insert '-' before and after "CTRL-X" when applicable.
- */
- if (*s < ' '
- || (*s == '^' && s[1]
- && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) {
- if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') {
- *d++ = '_'; // prepend a '_' to make x_CTRL-x
- }
- STRCPY(d, "CTRL-");
- d += 5;
- if (*s < ' ') {
- *d++ = (char)(*s + '@');
- if (d[-1] == '\\') {
- *d++ = '\\'; // double a backslash
- }
- } else {
- *d++ = *++s;
- }
- if (s[1] != NUL && s[1] != '_') {
- *d++ = '_'; // append a '_'
- }
- continue;
- } else if (*s == '^') { // "^" or "CTRL-^" or "^_"
- *d++ = '\\';
- }
- /*
- * Insert a backslash before a backslash after a slash, for search
- * pattern tags: "/\|" --> "/\\|".
- */
- else if (s[0] == '\\' && s[1] != '\\'
- && *arg == '/' && s == arg + 1) {
- *d++ = '\\';
- }
-
- // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in
- // "CTRL-\_CTRL-N"
- if (STRNICMP(s, "CTRL-\\_", 7) == 0) {
- STRCPY(d, "CTRL-\\\\");
- d += 7;
- s += 6;
- }
-
- *d++ = *s;
-
- // If tag contains "({" or "([", tag terminates at the "(".
- // This is for help on functions, e.g.: abs({expr}).
- if (*s == '(' && (s[1] == '{' || s[1] == '[')) {
- break;
- }
-
- // If tag starts with ', toss everything after a second '. Fixes
- // CTRL-] on 'option'. (would include the trailing '.').
- if (*s == '\'' && s > arg && *arg == '\'') {
- break;
- }
- // Also '{' and '}'. Fixes CTRL-] on '{address}'.
- if (*s == '}' && s > arg && *arg == '{') {
- break;
- }
- }
- *d = NUL;
-
- if (*IObuff == '`') {
- if ((char_u *)d > IObuff + 2 && d[-1] == '`') {
- // remove the backticks from `command`
- memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-2] = NUL;
- } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') {
- // remove the backticks and comma from `command`,
- memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-3] = NUL;
- } else if ((char_u *)d > IObuff + 4 && d[-3] == '`'
- && d[-2] == '\\' && d[-1] == '.') {
- // remove the backticks and dot from `command`\.
- memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-4] = NUL;
- }
- }
- }
- }
-
- *matches = NULL;
- *num_matches = 0;
- int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
- if (keep_lang) {
- flags |= TAG_KEEP_LANG;
- }
- if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK
- && *num_matches > 0) {
- // Sort the matches found on the heuristic number that is after the
- // tag name.
- qsort((void *)(*matches), (size_t)(*num_matches),
- sizeof(char_u *), help_compare);
- // Delete more than TAG_MANY to reduce the size of the listing.
- while (*num_matches > TAG_MANY) {
- xfree((*matches)[--*num_matches]);
- }
- }
- return OK;
-}
-
-/// Called when starting to edit a buffer for a help file.
-static void prepare_help_buffer(void)
-{
- curbuf->b_help = true;
- set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0);
-
- // Always set these options after jumping to a help tag, because the
- // user may have an autocommand that gets in the way.
- // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and
- // latin1 word characters (for translated help files).
- // Only set it when needed, buf_init_chartab() is some work.
- char *p = "!-~,^*,^|,^\",192-255";
- if (STRCMP(curbuf->b_p_isk, p) != 0) {
- set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
- check_buf_options(curbuf);
- (void)buf_init_chartab(curbuf, FALSE);
- }
-
- // Don't use the global foldmethod.
- set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0);
-
- curbuf->b_p_ts = 8; // 'tabstop' is 8.
- curwin->w_p_list = FALSE; // No list mode.
-
- curbuf->b_p_ma = FALSE; // Not modifiable.
- curbuf->b_p_bin = FALSE; // Reset 'bin' before reading file.
- curwin->w_p_nu = 0; // No line numbers.
- curwin->w_p_rnu = 0; // No relative line numbers.
- RESET_BINDING(curwin); // No scroll or cursor binding.
- curwin->w_p_arab = FALSE; // No arabic mode.
- curwin->w_p_rl = FALSE; // Help window is left-to-right.
- curwin->w_p_fen = FALSE; // No folding in the help window.
- curwin->w_p_diff = FALSE; // No 'diff'.
- curwin->w_p_spell = FALSE; // No spell checking.
-
- set_buflisted(FALSE);
-}
-
-/// After reading a help file: May cleanup a help buffer when syntax
-/// highlighting is not used.
-void fix_help_buffer(void)
-{
- linenr_T lnum;
- char *line;
- bool in_example = false;
-
- // Set filetype to "help".
- if (STRCMP(curbuf->b_p_ft, "help") != 0) {
- curbuf->b_ro_locked++;
- set_option_value("ft", 0L, "help", OPT_LOCAL);
- curbuf->b_ro_locked--;
- }
-
- if (!syntax_present(curwin)) {
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
- line = (char *)ml_get_buf(curbuf, lnum, false);
- const size_t len = STRLEN(line);
- if (in_example && len > 0 && !ascii_iswhite(line[0])) {
- // End of example: non-white or '<' in first column.
- if (line[0] == '<') {
- // blank-out a '<' in the first column
- line = (char *)ml_get_buf(curbuf, lnum, true);
- line[0] = ' ';
- }
- in_example = false;
- }
- if (!in_example && len > 0) {
- if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) {
- // blank-out a '>' in the last column (start of example)
- line = (char *)ml_get_buf(curbuf, lnum, true);
- line[len - 1] = ' ';
- in_example = true;
- } else if (line[len - 1] == '~') {
- // blank-out a '~' at the end of line (header marker)
- line = (char *)ml_get_buf(curbuf, lnum, true);
- line[len - 1] = ' ';
- }
- }
- }
- }
-
- /*
- * In the "help.txt" and "help.abx" file, add the locally added help
- * files. This uses the very first line in the help file.
- */
- char *const fname = path_tail(curbuf->b_fname);
- if (FNAMECMP(fname, "help.txt") == 0
- || (FNAMENCMP(fname, "help.", 5) == 0
- && ASCII_ISALPHA(fname[5])
- && ASCII_ISALPHA(fname[6])
- && TOLOWER_ASC(fname[7]) == 'x'
- && fname[8] == NUL)) {
- for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
- line = (char *)ml_get_buf(curbuf, lnum, false);
- if (strstr(line, "*local-additions*") == NULL) {
- continue;
- }
-
- // Go through all directories in 'runtimepath', skipping
- // $VIMRUNTIME.
- char *p = (char *)p_rtp;
- while (*p != NUL) {
- copy_option_part(&p, (char *)NameBuff, MAXPATHL, ",");
- char *const rt = vim_getenv("VIMRUNTIME");
- if (rt != NULL
- && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) {
- int fcount;
- char **fnames;
- char *s;
- vimconv_T vc;
- char *cp;
-
- // Find all "doc/ *.txt" files in this directory.
- if (!add_pathsep((char *)NameBuff)
- || STRLCAT(NameBuff, "doc/*.??[tx]",
- sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- continue;
- }
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char *buff_list[1] = { (char *)NameBuff };
- if (gen_expand_wildcards(1, buff_list, &fcount,
- &fnames, EW_FILE|EW_SILENT) == OK
- && fcount > 0) {
- // If foo.abx is found use it instead of foo.txt in
- // the same directory.
- for (int i1 = 0; i1 < fcount; i1++) {
- for (int i2 = 0; i2 < fcount; i2++) {
- if (i1 == i2) {
- continue;
- }
- if (fnames[i1] == NULL || fnames[i2] == NULL) {
- continue;
- }
- const char *const f1 = fnames[i1];
- const char *const f2 = fnames[i2];
- const char *const t1 = path_tail(f1);
- const char *const t2 = path_tail(f2);
- const char *const e1 = (char *)STRRCHR(t1, '.');
- const char *const e2 = (char *)STRRCHR(t2, '.');
- if (e1 == NULL || e2 == NULL) {
- continue;
- }
- if (FNAMECMP(e1, ".txt") != 0
- && FNAMECMP(e1, fname + 4) != 0) {
- // Not .txt and not .abx, remove it.
- XFREE_CLEAR(fnames[i1]);
- continue;
- }
- if (e1 - f1 != e2 - f2
- || FNAMENCMP(f1, f2, e1 - f1) != 0) {
- continue;
- }
- if (FNAMECMP(e1, ".txt") == 0
- && FNAMECMP(e2, fname + 4) == 0) {
- // use .abx instead of .txt
- XFREE_CLEAR(fnames[i1]);
- }
- }
- }
- for (int fi = 0; fi < fcount; fi++) {
- if (fnames[fi] == NULL) {
- continue;
- }
-
- FILE *const fd = os_fopen(fnames[fi], "r");
- if (fd == NULL) {
- continue;
- }
- vim_fgets(IObuff, IOSIZE, fd);
- if (IObuff[0] == '*'
- && (s = vim_strchr((char *)IObuff + 1, '*'))
- != NULL) {
- TriState this_utf = kNone;
- // Change tag definition to a
- // reference and remove <CR>/<NL>.
- IObuff[0] = '|';
- *s = '|';
- while (*s != NUL) {
- if (*s == '\r' || *s == '\n') {
- *s = NUL;
- }
- // The text is utf-8 when a byte
- // above 127 is found and no
- // illegal byte sequence is found.
- if ((char_u)(*s) >= 0x80 && this_utf != kFalse) {
- this_utf = kTrue;
- const int l = utf_ptr2len(s);
- if (l == 1) {
- this_utf = kFalse;
- }
- s += l - 1;
- }
- ++s;
- }
- // The help file is latin1 or utf-8;
- // conversion to the current
- // 'encoding' may be required.
- vc.vc_type = CONV_NONE;
- convert_setup(&vc,
- (char_u *)(this_utf == kTrue ? "utf-8" : "latin1"),
- p_enc);
- if (vc.vc_type == CONV_NONE) {
- // No conversion needed.
- cp = (char *)IObuff;
- } else {
- // Do the conversion. If it fails
- // use the unconverted text.
- cp = (char *)string_convert(&vc, IObuff, NULL);
- if (cp == NULL) {
- cp = (char *)IObuff;
- }
- }
- convert_setup(&vc, NULL, NULL);
-
- ml_append(lnum, cp, (colnr_T)0, false);
- if ((char_u *)cp != IObuff) {
- xfree(cp);
- }
- lnum++;
- }
- fclose(fd);
- }
- FreeWild(fcount, fnames);
- }
- }
- xfree(rt);
- }
- break;
- }
- }
-}
-
-/// ":exusage"
-void ex_exusage(exarg_T *eap)
-{
- do_cmdline_cmd("help ex-cmd-index");
-}
-
-/// ":viusage"
-void ex_viusage(exarg_T *eap)
-{
- do_cmdline_cmd("help normal-index");
-}
-
-/// Generate tags in one help directory
-///
-/// @param dir Path to the doc directory
-/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.)
-/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for
-/// French)
-/// @param add_help_tags Whether to add the "help-tags" tag
-/// @param ignore_writeerr ignore write error
-static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags,
- bool ignore_writeerr)
- FUNC_ATTR_NONNULL_ALL
-{
- garray_T ga;
- int filecount;
- char **files;
- char *p1, *p2;
- char *s;
- TriState utf8 = kNone;
- bool mix = false; // detected mixed encodings
-
- // Find all *.txt files.
- size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff));
- if (dirlen >= MAXPATHL
- || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT
- || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- return;
- }
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char *buff_list[1] = { (char *)NameBuff };
- const int res = gen_expand_wildcards(1, buff_list, &filecount, &files,
- EW_FILE|EW_SILENT);
- if (res == FAIL || filecount == 0) {
- if (!got_int) {
- semsg(_("E151: No match: %s"), NameBuff);
- }
- if (res != FAIL) {
- FreeWild(filecount, files);
- }
- return;
- }
-
- //
- // Open the tags file for writing.
- // Do this before scanning through all the files.
- //
- memcpy(NameBuff, dir, dirlen + 1);
- if (!add_pathsep((char *)NameBuff)
- || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- return;
- }
-
- FILE *const fd_tags = os_fopen((char *)NameBuff, "w");
- if (fd_tags == NULL) {
- if (!ignore_writeerr) {
- semsg(_("E152: Cannot open %s for writing"), NameBuff);
- }
- FreeWild(filecount, files);
- return;
- }
-
- // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
- // add the "help-tags" tag.
- ga_init(&ga, (int)sizeof(char_u *), 100);
- if (add_help_tags
- || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) {
- size_t s_len = 18 + STRLEN(tagfname);
- s = xmalloc(s_len);
- snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname);
- GA_APPEND(char *, &ga, s);
- }
-
- // Go over all the files and extract the tags.
- for (int fi = 0; fi < filecount && !got_int; fi++) {
- FILE *const fd = os_fopen(files[fi], "r");
- if (fd == NULL) {
- semsg(_("E153: Unable to open %s for reading"), files[fi]);
- continue;
- }
- const char *const fname = files[fi] + dirlen + 1;
-
- bool firstline = true;
- while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
- if (firstline) {
- // Detect utf-8 file by a non-ASCII char in the first line.
- TriState this_utf8 = kNone;
- for (s = (char *)IObuff; *s != NUL; s++) {
- if ((char_u)(*s) >= 0x80) {
- this_utf8 = kTrue;
- const int l = utf_ptr2len(s);
- if (l == 1) {
- // Illegal UTF-8 byte sequence.
- this_utf8 = kFalse;
- break;
- }
- s += l - 1;
- }
- }
- if (this_utf8 == kNone) { // only ASCII characters found
- this_utf8 = kFalse;
- }
- if (utf8 == kNone) { // first file
- utf8 = this_utf8;
- } else if (utf8 != this_utf8) {
- semsg(_("E670: Mix of help file encodings within a language: %s"),
- files[fi]);
- mix = !got_int;
- got_int = TRUE;
- }
- firstline = false;
- }
- p1 = vim_strchr((char *)IObuff, '*'); // find first '*'
- while (p1 != NULL) {
- p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'.
- if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**".
- for (s = p1 + 1; s < p2; s++) {
- if (*s == ' ' || *s == '\t' || *s == '|') {
- break;
- }
- }
-
- // Only accept a *tag* when it consists of valid
- // characters, there is white space before it and is
- // followed by a white character or end-of-line.
- if (s == p2
- && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
- && (vim_strchr(" \t\n\r", s[1]) != NULL
- || s[1] == '\0')) {
- *p2 = '\0';
- p1++;
- size_t s_len= (size_t)(p2 - p1) + STRLEN(fname) + 2;
- s = xmalloc(s_len);
- GA_APPEND(char *, &ga, s);
- snprintf(s, s_len, "%s\t%s", p1, fname);
-
- // find next '*'
- p2 = vim_strchr(p2 + 1, '*');
- }
- }
- p1 = p2;
- }
- line_breakcheck();
- }
-
- fclose(fd);
- }
-
- FreeWild(filecount, files);
-
- if (!got_int && ga.ga_data != NULL) {
- // Sort the tags.
- sort_strings(ga.ga_data, ga.ga_len);
-
- // Check for duplicates.
- for (int i = 1; i < ga.ga_len; i++) {
- p1 = ((char **)ga.ga_data)[i - 1];
- p2 = ((char **)ga.ga_data)[i];
- while (*p1 == *p2) {
- if (*p2 == '\t') {
- *p2 = NUL;
- vim_snprintf((char *)NameBuff, MAXPATHL,
- _("E154: Duplicate tag \"%s\" in file %s/%s"),
- ((char_u **)ga.ga_data)[i], dir, p2 + 1);
- emsg((char *)NameBuff);
- *p2 = '\t';
- break;
- }
- ++p1;
- ++p2;
- }
- }
-
- if (utf8 == kTrue) {
- fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
- }
-
- // Write the tags into the file.
- for (int i = 0; i < ga.ga_len; i++) {
- s = ((char **)ga.ga_data)[i];
- if (STRNCMP(s, "help-tags\t", 10) == 0) {
- // help-tags entry was added in formatted form
- fputs(s, fd_tags);
- } else {
- fprintf(fd_tags, "%s\t/" "*", s);
- for (p1 = s; *p1 != '\t'; p1++) {
- // insert backslash before '\\' and '/'
- if (*p1 == '\\' || *p1 == '/') {
- putc('\\', fd_tags);
- }
- putc(*p1, fd_tags);
- }
- fprintf(fd_tags, "*\n");
- }
- }
- }
- if (mix) {
- got_int = false; // continue with other languages
- }
-
- GA_DEEP_CLEAR_PTR(&ga);
- fclose(fd_tags); // there is no check for an error...
-}
-
-/// Generate tags in one help directory, taking care of translations.
-static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
- FUNC_ATTR_NONNULL_ALL
-{
- int len;
- garray_T ga;
- char lang[2];
- char ext[5];
- char fname[8];
- int filecount;
- char **files;
-
- // Get a list of all files in the help directory and in subdirectories.
- STRLCPY(NameBuff, dirname, sizeof(NameBuff));
- if (!add_pathsep((char *)NameBuff)
- || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- return;
- }
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char *buff_list[1] = { (char *)NameBuff };
- if (gen_expand_wildcards(1, buff_list, &filecount, &files,
- EW_FILE|EW_SILENT) == FAIL
- || filecount == 0) {
- semsg(_("E151: No match: %s"), NameBuff);
- return;
- }
-
- // Go over all files in the directory to find out what languages are
- // present.
- int j;
- ga_init(&ga, 1, 10);
- for (int i = 0; i < filecount; i++) {
- len = (int)STRLEN(files[i]);
- if (len <= 4) {
- continue;
- }
-
- if (STRICMP(files[i] + len - 4, ".txt") == 0) {
- // ".txt" -> language "en"
- lang[0] = 'e';
- lang[1] = 'n';
- } else if (files[i][len - 4] == '.'
- && ASCII_ISALPHA(files[i][len - 3])
- && ASCII_ISALPHA(files[i][len - 2])
- && TOLOWER_ASC(files[i][len - 1]) == 'x') {
- // ".abx" -> language "ab"
- lang[0] = (char)TOLOWER_ASC(files[i][len - 3]);
- lang[1] = (char)TOLOWER_ASC(files[i][len - 2]);
- } else {
- continue;
- }
-
- // Did we find this language already?
- for (j = 0; j < ga.ga_len; j += 2) {
- if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) {
- break;
- }
- }
- if (j == ga.ga_len) {
- // New language, add it.
- ga_grow(&ga, 2);
- ((char *)ga.ga_data)[ga.ga_len++] = lang[0];
- ((char *)ga.ga_data)[ga.ga_len++] = lang[1];
- }
- }
-
- /*
- * Loop over the found languages to generate a tags file for each one.
- */
- for (j = 0; j < ga.ga_len; j += 2) {
- STRCPY(fname, "tags-xx");
- fname[5] = ((char *)ga.ga_data)[j];
- fname[6] = ((char *)ga.ga_data)[j + 1];
- if (fname[5] == 'e' && fname[6] == 'n') {
- // English is an exception: use ".txt" and "tags".
- fname[4] = NUL;
- STRCPY(ext, ".txt");
- } else {
- // Language "ab" uses ".abx" and "tags-ab".
- STRCPY(ext, ".xxx");
- ext[1] = fname[5];
- ext[2] = fname[6];
- }
- helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr);
- }
-
- ga_clear(&ga);
- FreeWild(filecount, files);
-}
-
-static void helptags_cb(char *fname, void *cookie)
- FUNC_ATTR_NONNULL_ALL
-{
- do_helptags(fname, *(bool *)cookie, true);
-}
-
-/// ":helptags"
-void ex_helptags(exarg_T *eap)
-{
- expand_T xpc;
- char *dirname;
- bool add_help_tags = false;
-
- // Check for ":helptags ++t {dir}".
- if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) {
- add_help_tags = true;
- eap->arg = skipwhite(eap->arg + 3);
- }
-
- if (STRCMP(eap->arg, "ALL") == 0) {
- do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
- } else {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_DIRECTORIES;
- dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL,
- WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
- if (dirname == NULL || !os_isdir((char_u *)dirname)) {
- semsg(_("E150: Not a directory: %s"), eap->arg);
- } else {
- do_helptags(dirname, add_help_tags, false);
- }
- xfree(dirname);
- }
-}
-
-/// ":helpclose": Close one help window
-void ex_helpclose(exarg_T *eap)
-{
- FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
- if (bt_help(win->w_buffer)) {
- win_close(win, false, eap->forceit);
- return;
- }
- }
-}
-
/// Shows the effects of the :substitute command being typed ('inccommand').
/// If inccommand=split, shows a preview window and later restores the layout.
///
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index a55e74a789..3aaba9ce42 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -20,9 +20,9 @@
#define ECMD_NOWINENTER 0x40 // do not trigger BufWinEnter
// for lnum argument in do_ecmd()
-#define ECMD_LASTL (linenr_T)0 // use last position in loaded file
-#define ECMD_LAST ((linenr_T) - 1) // use last position in all files
-#define ECMD_ONE (linenr_T)1 // use first line
+#define ECMD_LASTL (linenr_T)0 // use last position in loaded file
+#define ECMD_LAST ((linenr_T)(-1)) // use last position in all files
+#define ECMD_ONE (linenr_T)1 // use first line
/// Previous :substitute replacement string definition
typedef struct {
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index a5ba5e0b30..4bed1e94b9 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -107,6 +107,12 @@ module.cmds = {
func='ex_listdo',
},
{
+ command='argdedupe',
+ flags=TRLBAR,
+ addr_type='ADDR_NONE',
+ func='ex_argdedupe',
+ },
+ {
command='argedit',
flags=bit.bor(BANG, NEEDARG, RANGE, ZEROR, FILES, CMDARG, ARGOPT, TRLBAR),
addr_type='ADDR_ARGUMENTS',
@@ -3175,7 +3181,7 @@ module.cmds = {
},
{
command='wincmd',
- flags=bit.bor(NEEDARG, WORD1, RANGE, CMDWIN, LOCK_OK),
+ flags=bit.bor(NEEDARG, WORD1, RANGE, COUNT, CMDWIN, LOCK_OK),
addr_type='ADDR_OTHER',
func='ex_wincmd',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 730a2f1b69..54315a6417 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -11,30 +11,27 @@
#include <stdbool.h>
#include <string.h>
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/globals.h"
#include "nvim/vim.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
-#include "nvim/api/private/defs.h"
-#include "nvim/api/private/helpers.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
-#include "nvim/debugger.h"
-#include "nvim/eval/userfunc.h"
+#include "nvim/eval.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
-#include "nvim/garray.h"
-#include "nvim/lua/executor.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
@@ -42,101 +39,19 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os/fs_defs.h"
-#include "nvim/os/input.h"
#include "nvim/os/shell.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/profile.h"
#include "nvim/quickfix.h"
-#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/undo.h"
-#include "nvim/version.h"
#include "nvim/window.h"
-/// Growarray to store info about already sourced scripts.
-static garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL };
-#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
-
-// Struct used in sn_prl_ga for every line of a script.
-typedef struct sn_prl_S {
- int snp_count; ///< nr of times line was executed
- proftime_T sn_prl_total; ///< time spent in a line + children
- proftime_T sn_prl_self; ///< time spent in a line itself
-} sn_prl_T;
-
-/// Structure used to store info for each sourced file.
-/// It is shared between do_source() and getsourceline().
-/// This is required, because it needs to be handed to do_cmdline() and
-/// sourcing can be done recursively.
-struct source_cookie {
- FILE *fp; ///< opened file for sourcing
- char *nextline; ///< if not NULL: line that was read ahead
- linenr_T sourcing_lnum; ///< line number of the source file
- int finished; ///< ":finish" used
-#if defined(USE_CRNL)
- int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS
- bool error; ///< true if LF found after CR-LF
-#endif
- linenr_T breakpoint; ///< next line with breakpoint or zero
- char *fname; ///< name of sourced file
- int dbg_tick; ///< debug_tick when breakpoint was set
- int level; ///< top nesting level of sourced file
- vimconv_T conv; ///< type of conversion
-};
-
-#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds2.c.generated.h"
#endif
-static char *profile_fname = NULL;
-
-/// ":profile cmd args"
-void ex_profile(exarg_T *eap)
-{
- static proftime_T pause_time;
-
- char *e;
- int len;
-
- e = (char *)skiptowhite((char_u *)eap->arg);
- len = (int)(e - eap->arg);
- e = skipwhite(e);
-
- if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) {
- xfree(profile_fname);
- profile_fname = (char *)expand_env_save_opt((char_u *)e, true);
- do_profiling = PROF_YES;
- profile_set_wait(profile_zero());
- set_vim_var_nr(VV_PROFILING, 1L);
- } else if (do_profiling == PROF_NONE) {
- emsg(_("E750: First use \":profile start {fname}\""));
- } else if (STRCMP(eap->arg, "stop") == 0) {
- profile_dump();
- do_profiling = PROF_NONE;
- set_vim_var_nr(VV_PROFILING, 0L);
- profile_reset();
- } else if (STRCMP(eap->arg, "pause") == 0) {
- if (do_profiling == PROF_YES) {
- pause_time = profile_start();
- }
- do_profiling = PROF_PAUSED;
- } else if (STRCMP(eap->arg, "continue") == 0) {
- if (do_profiling == PROF_PAUSED) {
- pause_time = profile_end(pause_time);
- profile_set_wait(profile_add(profile_get_wait(), pause_time));
- }
- do_profiling = PROF_YES;
- } else if (STRCMP(eap->arg, "dump") == 0) {
- profile_dump();
- } else {
- // The rest is similar to ":breakadd".
- ex_breakadd(eap);
- }
-}
-
void ex_ruby(exarg_T *eap)
{
script_host_execute("ruby", eap);
@@ -182,273 +97,6 @@ void ex_perldo(exarg_T *eap)
script_host_do_range("perl", eap);
}
-// Command line expansion for :profile.
-static enum {
- PEXP_SUBCMD, ///< expand :profile sub-commands
- PEXP_FUNC, ///< expand :profile func {funcname}
-} pexpand_what;
-
-static char *pexpand_cmds[] = {
- "continue",
- "dump",
- "file",
- "func",
- "pause",
- "start",
- "stop",
- NULL
-};
-
-/// Function given to ExpandGeneric() to obtain the profile command
-/// specific expansion.
-char *get_profile_name(expand_T *xp, int idx)
- FUNC_ATTR_PURE
-{
- switch (pexpand_what) {
- case PEXP_SUBCMD:
- return pexpand_cmds[idx];
- // case PEXP_FUNC: TODO
- default:
- return NULL;
- }
-}
-
-/// Handle command line completion for :profile command.
-void set_context_in_profile_cmd(expand_T *xp, const char *arg)
-{
- // Default: expand subcommands.
- xp->xp_context = EXPAND_PROFILE;
- pexpand_what = PEXP_SUBCMD;
- xp->xp_pattern = (char *)arg;
-
- char_u *const end_subcmd = skiptowhite((const char_u *)arg);
- if (*end_subcmd == NUL) {
- return;
- }
-
- if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) {
- xp->xp_context = EXPAND_FILES;
- xp->xp_pattern = skipwhite((char *)end_subcmd);
- return;
- }
-
- // TODO(tarruda): expand function names after "func"
- xp->xp_context = EXPAND_NOTHING;
-}
-
-/// Dump the profiling info.
-void profile_dump(void)
-{
- FILE *fd;
-
- if (profile_fname != NULL) {
- fd = os_fopen(profile_fname, "w");
- if (fd == NULL) {
- semsg(_(e_notopen), profile_fname);
- } else {
- script_dump_profile(fd);
- func_dump_profile(fd);
- fclose(fd);
- }
- }
-}
-
-/// Reset all profiling information.
-static void profile_reset(void)
-{
- // Reset sourced files.
- for (int id = 1; id <= script_items.ga_len; id++) {
- scriptitem_T *si = &SCRIPT_ITEM(id);
- if (si->sn_prof_on) {
- si->sn_prof_on = false;
- si->sn_pr_force = false;
- si->sn_pr_child = profile_zero();
- si->sn_pr_nest = 0;
- si->sn_pr_count = 0;
- si->sn_pr_total = profile_zero();
- si->sn_pr_self = profile_zero();
- si->sn_pr_start = profile_zero();
- si->sn_pr_children = profile_zero();
- ga_clear(&si->sn_prl_ga);
- si->sn_prl_start = profile_zero();
- si->sn_prl_children = profile_zero();
- si->sn_prl_wait = profile_zero();
- si->sn_prl_idx = -1;
- si->sn_prl_execed = 0;
- }
- }
-
- // Reset functions.
- size_t n = func_hashtab.ht_used;
- hashitem_T *hi = func_hashtab.ht_array;
-
- for (; n > (size_t)0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- n--;
- ufunc_T *uf = HI2UF(hi);
- if (uf->uf_prof_initialized) {
- uf->uf_profiling = 0;
- uf->uf_tm_count = 0;
- uf->uf_tm_total = profile_zero();
- uf->uf_tm_self = profile_zero();
- uf->uf_tm_children = profile_zero();
-
- for (int i = 0; i < uf->uf_lines.ga_len; i++) {
- uf->uf_tml_count[i] = 0;
- uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
- }
-
- uf->uf_tml_start = profile_zero();
- uf->uf_tml_children = profile_zero();
- uf->uf_tml_wait = profile_zero();
- uf->uf_tml_idx = -1;
- uf->uf_tml_execed = 0;
- }
- }
- }
-
- XFREE_CLEAR(profile_fname);
-}
-
-/// Start profiling a script.
-static void profile_init(scriptitem_T *si)
-{
- si->sn_pr_count = 0;
- si->sn_pr_total = profile_zero();
- si->sn_pr_self = profile_zero();
-
- ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100);
- si->sn_prl_idx = -1;
- si->sn_prof_on = true;
- si->sn_pr_nest = 0;
-}
-
-/// Save time when starting to invoke another script or function.
-///
-/// @param tm place to store wait time
-void script_prof_save(proftime_T *tm)
-{
- scriptitem_T *si;
-
- if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on && si->sn_pr_nest++ == 0) {
- si->sn_pr_child = profile_start();
- }
- }
- *tm = profile_get_wait();
-}
-
-/// Count time spent in children after invoking another script or function.
-void script_prof_restore(proftime_T *tm)
-{
- scriptitem_T *si;
-
- if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on && --si->sn_pr_nest == 0) {
- si->sn_pr_child = profile_end(si->sn_pr_child);
- // don't count wait time
- si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child);
- si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child);
- si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child);
- }
- }
-}
-
-static proftime_T inchar_time;
-
-/// Called when starting to wait for the user to type a character.
-void prof_inchar_enter(void)
-{
- inchar_time = profile_start();
-}
-
-/// Called when finished waiting for the user to type a character.
-void prof_inchar_exit(void)
-{
- inchar_time = profile_end(inchar_time);
- profile_set_wait(profile_add(profile_get_wait(), inchar_time));
-}
-
-/// Dump the profiling results for all scripts in file "fd".
-static void script_dump_profile(FILE *fd)
-{
- scriptitem_T *si;
- FILE *sfd;
- sn_prl_T *pp;
-
- for (int id = 1; id <= script_items.ga_len; id++) {
- si = &SCRIPT_ITEM(id);
- if (si->sn_prof_on) {
- fprintf(fd, "SCRIPT %s\n", si->sn_name);
- if (si->sn_pr_count == 1) {
- fprintf(fd, "Sourced 1 time\n");
- } else {
- fprintf(fd, "Sourced %d times\n", si->sn_pr_count);
- }
- fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total));
- fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self));
- fprintf(fd, "\n");
- fprintf(fd, "count total (s) self (s)\n");
-
- sfd = os_fopen((char *)si->sn_name, "r");
- if (sfd == NULL) {
- fprintf(fd, "Cannot open file!\n");
- } else {
- // Keep going till the end of file, so that trailing
- // continuation lines are listed.
- for (int i = 0;; i++) {
- if (vim_fgets(IObuff, IOSIZE, sfd)) {
- break;
- }
- // When a line has been truncated, append NL, taking care
- // of multi-byte characters .
- if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) {
- int n = IOSIZE - 2;
-
- // Move to the first byte of this char.
- // utf_head_off() doesn't work, because it checks
- // for a truncated character.
- while (n > 0 && (IObuff[n] & 0xc0) == 0x80) {
- n--;
- }
-
- IObuff[n] = NL;
- IObuff[n + 1] = NUL;
- }
- if (i < si->sn_prl_ga.ga_len
- && (pp = &PRL_ITEM(si, i))->snp_count > 0) {
- fprintf(fd, "%5d ", pp->snp_count);
- if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) {
- fprintf(fd, " ");
- } else {
- fprintf(fd, "%s ", profile_msg(pp->sn_prl_total));
- }
- fprintf(fd, "%s ", profile_msg(pp->sn_prl_self));
- } else {
- fprintf(fd, " ");
- }
- fprintf(fd, "%s", IObuff);
- }
- fclose(sfd);
- }
- fprintf(fd, "\n");
- }
- }
-}
-
-/// @return true when a function defined in the current script should be
-/// profiled.
-bool prof_def_func(void)
- FUNC_ATTR_PURE
-{
- if (current_sctx.sc_sid > 0) {
- return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force;
- }
- return false;
-}
-
/// If 'autowrite' option set, try to write the file.
/// Careful: autocommands may make "buf" invalid!
///
@@ -796,483 +444,6 @@ int buf_write_all(buf_T *buf, int forceit)
return retval;
}
-/// Code to handle the argument list.
-
-#define AL_SET 1
-#define AL_ADD 2
-#define AL_DEL 3
-
-/// Isolate one argument, taking backticks.
-/// Changes the argument in-place, puts a NUL after it. Backticks remain.
-///
-/// @return a pointer to the start of the next argument.
-static char *do_one_arg(char *str)
-{
- char *p;
- bool inbacktick;
-
- inbacktick = false;
- for (p = str; *str; str++) {
- // When the backslash is used for escaping the special meaning of a
- // character we need to keep it until wildcard expansion.
- if (rem_backslash((char_u *)str)) {
- *p++ = *str++;
- *p++ = *str;
- } else {
- // An item ends at a space not in backticks
- if (!inbacktick && ascii_isspace(*str)) {
- break;
- }
- if (*str == '`') {
- inbacktick ^= true;
- }
- *p++ = *str;
- }
- }
- str = skipwhite(str);
- *p = NUL;
-
- return str;
-}
-
-/// Separate the arguments in "str" and return a list of pointers in the
-/// growarray "gap".
-static void get_arglist(garray_T *gap, char *str, int escaped)
-{
- ga_init(gap, (int)sizeof(char_u *), 20);
- while (*str != NUL) {
- GA_APPEND(char *, gap, str);
-
- // If str is escaped, don't handle backslashes or spaces
- if (!escaped) {
- return;
- }
-
- // Isolate one argument, change it in-place, put a NUL after it.
- str = do_one_arg(str);
- }
-}
-
-/// Parse a list of arguments (file names), expand them and return in
-/// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'.
-///
-/// @return FAIL or OK.
-int get_arglist_exp(char_u *str, int *fcountp, char ***fnamesp, bool wig)
-{
- garray_T ga;
- int i;
-
- get_arglist(&ga, (char *)str, true);
-
- if (wig) {
- i = expand_wildcards(ga.ga_len, ga.ga_data,
- fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD);
- } else {
- i = gen_expand_wildcards(ga.ga_len, ga.ga_data,
- fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD);
- }
-
- ga_clear(&ga);
- return i;
-}
-
-/// @param str
-/// @param what
-/// AL_SET: Redefine the argument list to 'str'.
-/// AL_ADD: add files in 'str' to the argument list after "after".
-/// AL_DEL: remove files in 'str' from the argument list.
-/// @param after
-/// 0 means before first one
-/// @param will_edit will edit added argument
-///
-/// @return FAIL for failure, OK otherwise.
-static int do_arglist(char *str, int what, int after, bool will_edit)
- FUNC_ATTR_NONNULL_ALL
-{
- garray_T new_ga;
- int exp_count;
- char **exp_files;
- char *p;
- int match;
- int arg_escaped = true;
-
- // Set default argument for ":argadd" command.
- if (what == AL_ADD && *str == NUL) {
- if (curbuf->b_ffname == NULL) {
- return FAIL;
- }
- str = curbuf->b_fname;
- arg_escaped = false;
- }
-
- // Collect all file name arguments in "new_ga".
- get_arglist(&new_ga, str, arg_escaped);
-
- if (what == AL_DEL) {
- regmatch_T regmatch;
- bool didone;
-
- // Delete the items: use each item as a regexp and find a match in the
- // argument list.
- regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set
- for (int i = 0; i < new_ga.ga_len && !got_int; i++) {
- p = ((char **)new_ga.ga_data)[i];
- p = file_pat_to_reg_pat(p, NULL, NULL, false);
- if (p == NULL) {
- break;
- }
- regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0);
- if (regmatch.regprog == NULL) {
- xfree(p);
- break;
- }
-
- didone = false;
- for (match = 0; match < ARGCOUNT; match++) {
- if (vim_regexec(&regmatch, alist_name(&ARGLIST[match]), (colnr_T)0)) {
- didone = true;
- xfree(ARGLIST[match].ae_fname);
- memmove(ARGLIST + match, ARGLIST + match + 1,
- (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T));
- ALIST(curwin)->al_ga.ga_len--;
- if (curwin->w_arg_idx > match) {
- curwin->w_arg_idx--;
- }
- match--;
- }
- }
-
- vim_regfree(regmatch.regprog);
- xfree(p);
- if (!didone) {
- semsg(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]);
- }
- }
- ga_clear(&new_ga);
- } else {
- int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data,
- &exp_count, &exp_files,
- EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND);
- ga_clear(&new_ga);
- if (i == FAIL || exp_count == 0) {
- emsg(_(e_nomatch));
- return FAIL;
- }
-
- if (what == AL_ADD) {
- alist_add_list(exp_count, exp_files, after, will_edit);
- xfree(exp_files);
- } else {
- assert(what == AL_SET);
- alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0);
- }
- }
-
- alist_check_arg_idx();
-
- return OK;
-}
-
-/// Check the validity of the arg_idx for each other window.
-static void alist_check_arg_idx(void)
-{
- FOR_ALL_TAB_WINDOWS(tp, win) {
- if (win->w_alist == curwin->w_alist) {
- check_arg_idx(win);
- }
- }
-}
-
-/// @return true if window "win" is editing the file at the current argument
-/// index.
-static bool editing_arg_idx(win_T *win)
-{
- return !(win->w_arg_idx >= WARGCOUNT(win)
- || (win->w_buffer->b_fnum
- != WARGLIST(win)[win->w_arg_idx].ae_fnum
- && (win->w_buffer->b_ffname == NULL
- || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]),
- win->w_buffer->b_ffname, true,
- true) & kEqualFiles))));
-}
-
-/// Check if window "win" is editing the w_arg_idx file in its argument list.
-void check_arg_idx(win_T *win)
-{
- if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) {
- // We are not editing the current entry in the argument list.
- // Set "arg_had_last" if we are editing the last one.
- win->w_arg_idx_invalid = true;
- if (win->w_arg_idx != WARGCOUNT(win) - 1
- && arg_had_last == false
- && ALIST(win) == &global_alist
- && GARGCOUNT > 0
- && win->w_arg_idx < GARGCOUNT
- && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum
- || (win->w_buffer->b_ffname != NULL
- && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]),
- win->w_buffer->b_ffname, true, true)
- & kEqualFiles)))) {
- arg_had_last = true;
- }
- } else {
- // We are editing the current entry in the argument list.
- // Set "arg_had_last" if it's also the last one
- win->w_arg_idx_invalid = false;
- if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) {
- arg_had_last = true;
- }
- }
-}
-
-/// ":args", ":argslocal" and ":argsglobal".
-void ex_args(exarg_T *eap)
-{
- if (eap->cmdidx != CMD_args) {
- alist_unlink(ALIST(curwin));
- if (eap->cmdidx == CMD_argglobal) {
- ALIST(curwin) = &global_alist;
- } else { // eap->cmdidx == CMD_arglocal
- alist_new();
- }
- }
-
- if (*eap->arg != NUL) {
- // ":args file ..": define new argument list, handle like ":next"
- // Also for ":argslocal file .." and ":argsglobal file ..".
- ex_next(eap);
- } else if (eap->cmdidx == CMD_args) {
- // ":args": list arguments.
- if (ARGCOUNT > 0) {
- char **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT);
- // Overwrite the command, for a short list there is no scrolling
- // required and no wait_return().
- gotocmdline(true);
- for (int i = 0; i < ARGCOUNT; i++) {
- items[i] = alist_name(&ARGLIST[i]);
- }
- list_in_columns((char_u **)items, ARGCOUNT, curwin->w_arg_idx);
- xfree(items);
- }
- } else if (eap->cmdidx == CMD_arglocal) {
- garray_T *gap = &curwin->w_alist->al_ga;
-
- // ":argslocal": make a local copy of the global argument list.
- ga_grow(gap, GARGCOUNT);
- for (int i = 0; i < GARGCOUNT; i++) {
- if (GARGLIST[i].ae_fname != NULL) {
- AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname =
- vim_strsave(GARGLIST[i].ae_fname);
- AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum =
- GARGLIST[i].ae_fnum;
- gap->ga_len++;
- }
- }
- }
-}
-
-/// ":previous", ":sprevious", ":Next" and ":sNext".
-void ex_previous(exarg_T *eap)
-{
- // If past the last one already, go to the last one.
- if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) {
- do_argfile(eap, ARGCOUNT - 1);
- } else {
- do_argfile(eap, curwin->w_arg_idx - (int)eap->line2);
- }
-}
-
-/// ":rewind", ":first", ":sfirst" and ":srewind".
-void ex_rewind(exarg_T *eap)
-{
- do_argfile(eap, 0);
-}
-
-/// ":last" and ":slast".
-void ex_last(exarg_T *eap)
-{
- do_argfile(eap, ARGCOUNT - 1);
-}
-
-/// ":argument" and ":sargument".
-void ex_argument(exarg_T *eap)
-{
- int i;
-
- if (eap->addr_count > 0) {
- i = (int)eap->line2 - 1;
- } else {
- i = curwin->w_arg_idx;
- }
- do_argfile(eap, i);
-}
-
-/// Edit file "argn" of the argument lists.
-void do_argfile(exarg_T *eap, int argn)
-{
- int other;
- char *p;
- int old_arg_idx = curwin->w_arg_idx;
-
- if (argn < 0 || argn >= ARGCOUNT) {
- if (ARGCOUNT <= 1) {
- emsg(_("E163: There is only one file to edit"));
- } else if (argn < 0) {
- emsg(_("E164: Cannot go before first file"));
- } else {
- emsg(_("E165: Cannot go beyond last file"));
- }
- } else {
- setpcmark();
-
- // split window or create new tab page first
- if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) {
- if (win_split(0, 0) == FAIL) {
- return;
- }
- RESET_BINDING(curwin);
- } else {
- // if 'hidden' set, only check for changed file when re-editing
- // the same buffer
- other = true;
- if (buf_hide(curbuf)) {
- p = fix_fname(alist_name(&ARGLIST[argn]));
- other = otherfile(p);
- xfree(p);
- }
- if ((!buf_hide(curbuf) || !other)
- && check_changed(curbuf, CCGD_AW
- | (other ? 0 : CCGD_MULTWIN)
- | (eap->forceit ? CCGD_FORCEIT : 0)
- | CCGD_EXCMD)) {
- return;
- }
- }
-
- curwin->w_arg_idx = argn;
- if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) {
- arg_had_last = true;
- }
-
- // Edit the file; always use the last known line number.
- // When it fails (e.g. Abort for already edited file) restore the
- // argument index.
- if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL,
- eap, ECMD_LAST,
- (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0)
- + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) {
- curwin->w_arg_idx = old_arg_idx;
- } else if (eap->cmdidx != CMD_argdo) {
- // like Vi: set the mark where the cursor is in the file.
- setmark('\'');
- }
- }
-}
-
-/// ":next", and commands that behave like it.
-void ex_next(exarg_T *eap)
-{
- int i;
-
- // check for changed buffer now, if this fails the argument list is not
- // redefined.
- if (buf_hide(curbuf)
- || eap->cmdidx == CMD_snext
- || !check_changed(curbuf, CCGD_AW
- | (eap->forceit ? CCGD_FORCEIT : 0)
- | CCGD_EXCMD)) {
- if (*eap->arg != NUL) { // redefine file list
- if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) {
- return;
- }
- i = 0;
- } else {
- i = curwin->w_arg_idx + (int)eap->line2;
- }
- do_argfile(eap, i);
- }
-}
-
-/// ":argedit"
-void ex_argedit(exarg_T *eap)
-{
- int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1;
- // Whether curbuf will be reused, curbuf->b_ffname will be set.
- bool curbuf_is_reusable = curbuf_reusable();
-
- if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) {
- return;
- }
- maketitle();
-
- if (curwin->w_arg_idx == 0
- && (curbuf->b_ml.ml_flags & ML_EMPTY)
- && (curbuf->b_ffname == NULL || curbuf_is_reusable)) {
- i = 0;
- }
- // Edit the argument.
- if (i < ARGCOUNT) {
- do_argfile(eap, i);
- }
-}
-
-/// ":argadd"
-void ex_argadd(exarg_T *eap)
-{
- do_arglist(eap->arg, AL_ADD,
- eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1,
- false);
- maketitle();
-}
-
-/// ":argdelete"
-void ex_argdelete(exarg_T *eap)
-{
- if (eap->addr_count > 0 || *eap->arg == NUL) {
- // ":argdel" works like ":.argdel"
- if (eap->addr_count == 0) {
- if (curwin->w_arg_idx >= ARGCOUNT) {
- emsg(_("E610: No argument to delete"));
- return;
- }
- eap->line1 = eap->line2 = curwin->w_arg_idx + 1;
- } else if (eap->line2 > ARGCOUNT) {
- // ":1,4argdel": Delete all arguments in the range.
- eap->line2 = ARGCOUNT;
- }
- linenr_T n = eap->line2 - eap->line1 + 1;
- if (*eap->arg != NUL) {
- // Can't have both a range and an argument.
- emsg(_(e_invarg));
- } else if (n <= 0) {
- // Don't give an error for ":%argdel" if the list is empty.
- if (eap->line1 != 1 || eap->line2 != 0) {
- emsg(_(e_invrange));
- }
- } else {
- for (linenr_T i = eap->line1; i <= eap->line2; i++) {
- xfree(ARGLIST[i - 1].ae_fname);
- }
- memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2,
- (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T));
- ALIST(curwin)->al_ga.ga_len -= (int)n;
- if (curwin->w_arg_idx >= eap->line2) {
- curwin->w_arg_idx -= (int)n;
- } else if (curwin->w_arg_idx > eap->line1) {
- curwin->w_arg_idx = (int)eap->line1;
- }
- if (ARGCOUNT == 0) {
- curwin->w_arg_idx = 0;
- } else if (curwin->w_arg_idx >= ARGCOUNT) {
- curwin->w_arg_idx = ARGCOUNT - 1;
- }
- }
- } else {
- do_arglist(eap->arg, AL_DEL, 0, false);
- }
- maketitle();
-}
-
/// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
void ex_listdo(exarg_T *eap)
{
@@ -1524,51 +695,6 @@ void ex_listdo(exarg_T *eap)
}
}
-/// Add files[count] to the arglist of the current window after arg "after".
-/// The file names in files[count] must have been allocated and are taken over.
-/// Files[] itself is not taken over.
-///
-/// @param after: where to add: 0 = before first one
-/// @param will_edit will edit adding argument
-static void alist_add_list(int count, char **files, int after, bool will_edit)
- FUNC_ATTR_NONNULL_ALL
-{
- int old_argcount = ARGCOUNT;
- ga_grow(&ALIST(curwin)->al_ga, count);
- {
- if (after < 0) {
- after = 0;
- }
- if (after > ARGCOUNT) {
- after = ARGCOUNT;
- }
- if (after < ARGCOUNT) {
- memmove(&(ARGLIST[after + count]), &(ARGLIST[after]),
- (size_t)(ARGCOUNT - after) * sizeof(aentry_T));
- }
- for (int i = 0; i < count; i++) {
- const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0);
- ARGLIST[after + i].ae_fname = (char_u *)files[i];
- ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags);
- }
- ALIST(curwin)->al_ga.ga_len += count;
- if (old_argcount > 0 && curwin->w_arg_idx >= after) {
- curwin->w_arg_idx += count;
- }
- return;
- }
-}
-
-// Function given to ExpandGeneric() to obtain the possible arguments of the
-// argedit and argdelete commands.
-char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
-{
- if (idx >= ARGCOUNT) {
- return NULL;
- }
- return alist_name(&ARGLIST[idx]);
-}
-
/// ":compiler[!] {name}"
void ex_compiler(exarg_T *eap)
{
@@ -1632,987 +758,6 @@ void ex_compiler(exarg_T *eap)
}
}
-/// ":options"
-void ex_options(exarg_T *eap)
-{
- char buf[500];
- bool multi_mods = 0;
-
- buf[0] = NUL;
- (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods);
-
- os_setenv("OPTWIN_CMD", buf, 1);
- cmd_source(SYS_OPTWIN_FILE, NULL);
-}
-
-/// ":source [{fname}]"
-void ex_source(exarg_T *eap)
-{
- cmd_source(eap->arg, eap);
-}
-
-static void cmd_source(char *fname, exarg_T *eap)
-{
- if (eap != NULL && *fname == NUL) {
- cmd_source_buffer(eap);
- } else if (eap != NULL && eap->forceit) {
- // ":source!": read Normal mode commands
- // Need to execute the commands directly. This is required at least
- // for:
- // - ":g" command busy
- // - after ":argdo", ":windo" or ":bufdo"
- // - another command follows
- // - inside a loop
- openscript((char_u *)fname, global_busy || listcmd_busy || eap->nextcmd != NULL
- || eap->cstack->cs_idx >= 0);
-
- // ":source" read ex commands
- } else if (do_source(fname, false, DOSO_NONE) == FAIL) {
- semsg(_(e_notopen), fname);
- }
-}
-
-/// Concatenate VimL line if it starts with a line continuation into a growarray
-/// (excluding the continuation chars and leading whitespace)
-///
-/// @note Growsize of the growarray may be changed to speed up concatenations!
-///
-/// @param ga the growarray to append to
-/// @param init_growsize the starting growsize value of the growarray
-/// @param p pointer to the beginning of the line to consider
-/// @param len the length of this line
-///
-/// @return true if this line did begin with a continuation (the next line
-/// should also be considered, if it exists); false otherwise
-static bool concat_continued_line(garray_T *const ga, const int init_growsize,
- const char_u *const p, size_t len)
- FUNC_ATTR_NONNULL_ALL
-{
- const char *const line = (char *)skipwhite_len(p, len);
- len -= (size_t)((char_u *)line - p);
- // Skip lines starting with '\" ', concat lines starting with '\'
- if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) {
- return true;
- } else if (len == 0 || line[0] != '\\') {
- return false;
- }
- if (ga->ga_len > init_growsize) {
- ga_set_growsize(ga, MIN(ga->ga_len, 8000));
- }
- ga_concat_len(ga, line + 1, len - 1);
- return true;
-}
-
-typedef struct {
- linenr_T curr_lnum;
- const linenr_T final_lnum;
-} GetBufferLineCookie;
-
-/// ":source" and associated commands.
-///
-/// @return address holding the next breakpoint line for a source cookie
-linenr_T *source_breakpoint(void *cookie)
-{
- return &((struct source_cookie *)cookie)->breakpoint;
-}
-
-/// @return the address holding the debug tick for a source cookie.
-int *source_dbg_tick(void *cookie)
-{
- return &((struct source_cookie *)cookie)->dbg_tick;
-}
-
-/// @return the nesting level for a source cookie.
-int source_level(void *cookie)
- FUNC_ATTR_PURE
-{
- return ((struct source_cookie *)cookie)->level;
-}
-
-/// Special function to open a file without handle inheritance.
-/// If possible the handle is closed on exec().
-static FILE *fopen_noinh_readbin(char *filename)
-{
-#ifdef WIN32
- int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0);
-#else
- int fd_tmp = os_open(filename, O_RDONLY, 0);
-#endif
-
- if (fd_tmp < 0) {
- return NULL;
- }
-
- (void)os_set_cloexec(fd_tmp);
-
- return fdopen(fd_tmp, READBIN);
-}
-
-typedef struct {
- char *buf;
- size_t offset;
-} GetStrLineCookie;
-
-/// Get one full line from a sourced string (in-memory, no file).
-/// Called by do_cmdline() when it's called from do_source_str().
-///
-/// @return pointer to allocated line, or NULL for end-of-file or
-/// some error.
-static char *get_str_line(int c, void *cookie, int indent, bool do_concat)
-{
- GetStrLineCookie *p = cookie;
- if (STRLEN(p->buf) <= p->offset) {
- return NULL;
- }
- const char *line = p->buf + p->offset;
- const char *eol = (char *)skip_to_newline((char_u *)line);
- garray_T ga;
- ga_init(&ga, sizeof(char_u), 400);
- ga_concat_len(&ga, line, (size_t)(eol - line));
- if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) {
- while (eol[0] != NUL) {
- line = eol + 1;
- const char_u *const next_eol = skip_to_newline((char_u *)line);
- if (!concat_continued_line(&ga, 400, (char_u *)line, (size_t)(next_eol - (char_u *)line))) {
- break;
- }
- eol = (char *)next_eol;
- }
- }
- ga_append(&ga, NUL);
- p->offset = (size_t)(eol - p->buf) + 1;
- return ga.ga_data;
-}
-
-/// Create a new script item and allocate script-local vars. @see new_script_vars
-///
-/// @param name File name of the script. NULL for anonymous :source.
-/// @param[out] sid_out SID of the new item.
-///
-/// @return pointer to the created script item.
-scriptitem_T *new_script_item(char *const name, scid_T *const sid_out)
-{
- static scid_T last_current_SID = 0;
- const scid_T sid = ++last_current_SID;
- if (sid_out != NULL) {
- *sid_out = sid;
- }
- ga_grow(&script_items, sid - script_items.ga_len);
- while (script_items.ga_len < sid) {
- script_items.ga_len++;
- SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
- SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false;
- }
- SCRIPT_ITEM(sid).sn_name = (char_u *)name;
- new_script_vars(sid); // Allocate the local script variables to use for this script.
- return &SCRIPT_ITEM(sid);
-}
-
-static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name)
-{
- char *save_sourcing_name = sourcing_name;
- linenr_T save_sourcing_lnum = sourcing_lnum;
- char sourcing_name_buf[256];
- if (save_sourcing_name == NULL) {
- sourcing_name = (char *)traceback_name;
- } else {
- snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
- "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name,
- save_sourcing_lnum);
- sourcing_name = sourcing_name_buf; // -V507 reassigned below, before return.
- }
- sourcing_lnum = 0;
-
- const sctx_T save_current_sctx = current_sctx;
- if (current_sctx.sc_sid != SID_LUA) {
- current_sctx.sc_sid = SID_STR;
- }
- current_sctx.sc_seq = 0;
- current_sctx.sc_lnum = save_sourcing_lnum;
- funccal_entry_T entry;
- save_funccal(&entry);
- int retval = do_cmdline(NULL, fgetline, cookie,
- DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
- sourcing_lnum = save_sourcing_lnum;
- sourcing_name = save_sourcing_name;
- current_sctx = save_current_sctx;
- restore_funccal();
- return retval;
-}
-
-static void cmd_source_buffer(const exarg_T *const eap)
- FUNC_ATTR_NONNULL_ALL
-{
- if (curbuf == NULL) {
- return;
- }
- garray_T ga;
- ga_init(&ga, sizeof(char_u), 400);
- const linenr_T final_lnum = eap->line2;
- // Copy the contents to be executed.
- for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) {
- // Adjust growsize to current length to speed up concatenating many lines.
- if (ga.ga_len > 400) {
- ga_set_growsize(&ga, MIN(ga.ga_len, 8000));
- }
- ga_concat(&ga, (char *)ml_get(curr_lnum));
- ga_append(&ga, NL);
- }
- ((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL;
- const GetStrLineCookie cookie = {
- .buf = ga.ga_data,
- .offset = 0,
- };
- if (curbuf->b_fname
- && path_with_extension((const char *)curbuf->b_fname, "lua")) {
- nlua_source_using_linegetter(get_str_line, (void *)&cookie,
- ":source (no file)");
- } else {
- source_using_linegetter((void *)&cookie, get_str_line,
- ":source (no file)");
- }
- ga_clear(&ga);
-}
-
-/// Executes lines in `src` as Ex commands.
-///
-/// @see do_source()
-int do_source_str(const char *cmd, const char *traceback_name)
-{
- GetStrLineCookie cookie = {
- .buf = (char *)cmd,
- .offset = 0,
- };
- return source_using_linegetter((void *)&cookie, get_str_line, traceback_name);
-}
-
-/// When fname is a 'lua' file nlua_exec_file() is invoked to source it.
-/// Otherwise reads the file `fname` and executes its lines as Ex commands.
-///
-/// This function may be called recursively!
-///
-/// @see do_source_str
-///
-/// @param fname
-/// @param check_other check for .vimrc and _vimrc
-/// @param is_vimrc DOSO_ value
-///
-/// @return FAIL if file could not be opened, OK otherwise
-int do_source(char *fname, int check_other, int is_vimrc)
-{
- struct source_cookie cookie;
- char *save_sourcing_name;
- linenr_T save_sourcing_lnum;
- char *p;
- char *fname_exp;
- uint8_t *firstline = NULL;
- int retval = FAIL;
- int save_debug_break_level = debug_break_level;
- scriptitem_T *si = NULL;
- proftime_T wait_start;
- bool trigger_source_post = false;
-
- p = expand_env_save(fname);
- if (p == NULL) {
- return retval;
- }
- fname_exp = fix_fname(p);
- xfree(p);
- if (fname_exp == NULL) {
- return retval;
- }
- if (os_isdir((char_u *)fname_exp)) {
- smsg(_("Cannot source a directory: \"%s\""), fname);
- goto theend;
- }
-
- // Apply SourceCmd autocommands, they should get the file and source it.
- if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL)
- && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp,
- false, curbuf)) {
- retval = aborting() ? FAIL : OK;
- if (retval == OK) {
- // Apply SourcePost autocommands.
- apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf);
- }
- goto theend;
- }
-
- // Apply SourcePre autocommands, they may get the file.
- apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf);
-
- cookie.fp = fopen_noinh_readbin(fname_exp);
- if (cookie.fp == NULL && check_other) {
- // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa,
- // and ".exrc" by "_exrc" or vice versa.
- p = path_tail(fname_exp);
- if ((*p == '.' || *p == '_')
- && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) {
- *p = (*p == '_') ? '.' : '_';
- cookie.fp = fopen_noinh_readbin(fname_exp);
- }
- }
-
- if (cookie.fp == NULL) {
- if (p_verbose > 1) {
- verbose_enter();
- if (sourcing_name == NULL) {
- smsg(_("could not source \"%s\""), fname);
- } else {
- smsg(_("line %" PRId64 ": could not source \"%s\""),
- (int64_t)sourcing_lnum, fname);
- }
- verbose_leave();
- }
- goto theend;
- }
-
- // The file exists.
- // - In verbose mode, give a message.
- // - For a vimrc file, may want to call vimrc_found().
- if (p_verbose > 1) {
- verbose_enter();
- if (sourcing_name == NULL) {
- smsg(_("sourcing \"%s\""), fname);
- } else {
- smsg(_("line %" PRId64 ": sourcing \"%s\""),
- (int64_t)sourcing_lnum, fname);
- }
- verbose_leave();
- }
- if (is_vimrc == DOSO_VIMRC) {
- vimrc_found(fname_exp, "MYVIMRC");
- }
-
-#ifdef USE_CRNL
- // If no automatic file format: Set default to CR-NL.
- if (*p_ffs == NUL) {
- cookie.fileformat = EOL_DOS;
- } else {
- cookie.fileformat = EOL_UNKNOWN;
- }
- cookie.error = false;
-#endif
-
- cookie.nextline = NULL;
- cookie.sourcing_lnum = 0;
- cookie.finished = false;
-
- // Check if this script has a breakpoint.
- cookie.breakpoint = dbg_find_breakpoint(true, (char_u *)fname_exp, (linenr_T)0);
- cookie.fname = fname_exp;
- cookie.dbg_tick = debug_tick;
-
- cookie.level = ex_nesting_level;
-
- // Keep the sourcing name/lnum, for recursive calls.
- save_sourcing_name = sourcing_name;
- sourcing_name = fname_exp;
- save_sourcing_lnum = sourcing_lnum;
- sourcing_lnum = 0;
-
- // start measuring script load time if --startuptime was passed and
- // time_fd was successfully opened afterwards.
- proftime_T rel_time;
- proftime_T start_time;
- FILE * const l_time_fd = time_fd;
- if (l_time_fd != NULL) {
- time_push(&rel_time, &start_time);
- }
-
- const int l_do_profiling = do_profiling;
- if (l_do_profiling == PROF_YES) {
- prof_child_enter(&wait_start); // entering a child now
- }
-
- // Don't use local function variables, if called from a function.
- // Also starts profiling timer for nested script.
- funccal_entry_T funccalp_entry;
- save_funccal(&funccalp_entry);
-
- const sctx_T save_current_sctx = current_sctx;
- si = get_current_script_id((char_u *)fname_exp, &current_sctx);
-
- if (l_do_profiling == PROF_YES) {
- bool forceit = false;
-
- // Check if we do profiling for this script.
- if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) {
- profile_init(si);
- si->sn_pr_force = forceit;
- }
- if (si->sn_prof_on) {
- si->sn_pr_count++;
- si->sn_pr_start = profile_start();
- si->sn_pr_children = profile_zero();
- }
- }
-
- cookie.conv.vc_type = CONV_NONE; // no conversion
-
- // Read the first line so we can check for a UTF-8 BOM.
- firstline = (uint8_t *)getsourceline(0, (void *)&cookie, 0, true);
- if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef
- && firstline[1] == 0xbb && firstline[2] == 0xbf) {
- // Found BOM; setup conversion, skip over BOM and recode the line.
- convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc);
- p = (char *)string_convert(&cookie.conv, (char_u *)firstline + 3, NULL);
- if (p == NULL) {
- p = xstrdup((char *)firstline + 3);
- }
- xfree(firstline);
- firstline = (uint8_t *)p;
- }
-
- if (path_with_extension((const char *)fname_exp, "lua")) {
- const sctx_T current_sctx_backup = current_sctx;
- const linenr_T sourcing_lnum_backup = sourcing_lnum;
- current_sctx.sc_sid = SID_LUA;
- current_sctx.sc_lnum = 0;
- sourcing_lnum = 0;
- // Source the file as lua
- nlua_exec_file((const char *)fname_exp);
- current_sctx = current_sctx_backup;
- sourcing_lnum = sourcing_lnum_backup;
- } else {
- // Call do_cmdline, which will call getsourceline() to get the lines.
- do_cmdline((char *)firstline, getsourceline, (void *)&cookie,
- DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT);
- }
- retval = OK;
-
- if (l_do_profiling == PROF_YES) {
- // Get "si" again, "script_items" may have been reallocated.
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on) {
- si->sn_pr_start = profile_end(si->sn_pr_start);
- si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start);
- si->sn_pr_total = profile_add(si->sn_pr_total, si->sn_pr_start);
- si->sn_pr_self = profile_self(si->sn_pr_self, si->sn_pr_start,
- si->sn_pr_children);
- }
- }
-
- if (got_int) {
- emsg(_(e_interr));
- }
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
- if (p_verbose > 1) {
- verbose_enter();
- smsg(_("finished sourcing %s"), fname);
- if (sourcing_name != NULL) {
- smsg(_("continuing in %s"), sourcing_name);
- }
- verbose_leave();
- }
-
- if (l_time_fd != NULL) {
- vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname);
- time_msg((char *)IObuff, &start_time);
- time_pop(rel_time);
- }
-
- if (!got_int) {
- trigger_source_post = true;
- }
-
- // After a "finish" in debug mode, need to break at first command of next
- // sourced file.
- if (save_debug_break_level > ex_nesting_level
- && debug_break_level == ex_nesting_level) {
- debug_break_level++;
- }
-
- current_sctx = save_current_sctx;
- restore_funccal();
- if (l_do_profiling == PROF_YES) {
- prof_child_exit(&wait_start); // leaving a child now
- }
- fclose(cookie.fp);
- xfree(cookie.nextline);
- xfree(firstline);
- convert_setup(&cookie.conv, NULL, NULL);
-
- if (trigger_source_post) {
- apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf);
- }
-
-theend:
- xfree(fname_exp);
- return retval;
-}
-
-/// Check if fname was sourced before to finds its SID.
-/// If it's new, generate a new SID.
-///
-/// @param[in] fname file path of script
-/// @param[out] ret_sctx sctx of this script
-scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx)
-{
- static int last_current_SID_seq = 0;
-
- sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq,
- .sc_lnum = 0,
- .sc_sid = 0 };
- scriptitem_T *si = NULL;
-
- assert(script_items.ga_len >= 0);
- for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) {
- // We used to check inode here, but that doesn't work:
- // - If a script is edited and written, it may get a different
- // inode number, even though to the user it is the same script.
- // - If a script is deleted and another script is written, with a
- // different name, the inode may be re-used.
- si = &SCRIPT_ITEM(script_sctx.sc_sid);
- if (si->sn_name != NULL && FNAMECMP(si->sn_name, fname) == 0) {
- // Found it!
- break;
- }
- }
- if (script_sctx.sc_sid == 0) {
- si = new_script_item((char *)vim_strsave(fname), &script_sctx.sc_sid);
- }
- if (ret_sctx != NULL) {
- *ret_sctx = script_sctx;
- }
-
- return si;
-}
-
-/// ":scriptnames"
-void ex_scriptnames(exarg_T *eap)
-{
- if (eap->addr_count > 0) {
- // :script {scriptId}: edit the script
- if (eap->line2 < 1 || eap->line2 > script_items.ga_len) {
- emsg(_(e_invarg));
- } else {
- eap->arg = (char *)SCRIPT_ITEM(eap->line2).sn_name;
- do_exedit(eap, NULL);
- }
- return;
- }
-
- for (int i = 1; i <= script_items.ga_len && !got_int; i++) {
- if (SCRIPT_ITEM(i).sn_name != NULL) {
- home_replace(NULL, (char *)SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true);
- vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff);
- if (!message_filtered(IObuff)) {
- msg_putchar('\n');
- msg_outtrans((char *)IObuff);
- line_breakcheck();
- }
- }
- }
-}
-
-#if defined(BACKSLASH_IN_FILENAME)
-/// Fix slashes in the list of script names for 'shellslash'.
-void scriptnames_slash_adjust(void)
-{
- for (int i = 1; i <= script_items.ga_len; i++) {
- if (SCRIPT_ITEM(i).sn_name != NULL) {
- slash_adjust(SCRIPT_ITEM(i).sn_name);
- }
- }
-}
-
-#endif
-
-/// Get a pointer to a script name. Used for ":verbose set".
-/// Message appended to "Last set from "
-char_u *get_scriptname(LastSet last_set, bool *should_free)
-{
- *should_free = false;
-
- switch (last_set.script_ctx.sc_sid) {
- case SID_MODELINE:
- return (char_u *)_("modeline");
- case SID_CMDARG:
- return (char_u *)_("--cmd argument");
- case SID_CARG:
- return (char_u *)_("-c argument");
- case SID_ENV:
- return (char_u *)_("environment variable");
- case SID_ERROR:
- return (char_u *)_("error handler");
- case SID_WINLAYOUT:
- return (char_u *)_("changed window size");
- case SID_LUA:
- return (char_u *)_("Lua");
- case SID_API_CLIENT:
- snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id);
- return IObuff;
- case SID_STR:
- return (char_u *)_("anonymous :source");
- default: {
- char *const sname = (char *)SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name;
- if (sname == NULL) {
- snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"),
- last_set.script_ctx.sc_sid);
- return IObuff;
- }
-
- *should_free = true;
- return (char_u *)home_replace_save(NULL, sname);
- }
- }
-}
-
-#if defined(EXITFREE)
-void free_scriptnames(void)
-{
- profile_reset();
-
-# define FREE_SCRIPTNAME(item) xfree((item)->sn_name)
- GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME);
-}
-#endif
-
-linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
- FUNC_ATTR_PURE
-{
- return fgetline == getsourceline
- ? ((struct source_cookie *)cookie)->sourcing_lnum
- : sourcing_lnum;
-}
-
-/// Get one full line from a sourced file.
-/// Called by do_cmdline() when it's called from do_source().
-///
-/// @return pointer to the line in allocated memory, or NULL for end-of-file or
-/// some error.
-char *getsourceline(int c, void *cookie, int indent, bool do_concat)
-{
- struct source_cookie *sp = (struct source_cookie *)cookie;
- char *line;
- char *p;
-
- // If breakpoints have been added/deleted need to check for it.
- if (sp->dbg_tick < debug_tick) {
- sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, sourcing_lnum);
- sp->dbg_tick = debug_tick;
- }
- if (do_profiling == PROF_YES) {
- script_line_end();
- }
- // Set the current sourcing line number.
- sourcing_lnum = sp->sourcing_lnum + 1;
- // Get current line. If there is a read-ahead line, use it, otherwise get
- // one now.
- if (sp->finished) {
- line = NULL;
- } else if (sp->nextline == NULL) {
- line = get_one_sourceline(sp);
- } else {
- line = sp->nextline;
- sp->nextline = NULL;
- sp->sourcing_lnum++;
- }
- if (line != NULL && do_profiling == PROF_YES) {
- script_line_start();
- }
-
- // Only concatenate lines starting with a \ when 'cpoptions' doesn't
- // contain the 'C' flag.
- if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
- // compensate for the one line read-ahead
- sp->sourcing_lnum--;
-
- // Get the next line and concatenate it when it starts with a
- // backslash. We always need to read the next line, keep it in
- // sp->nextline.
- // Also check for a comment in between continuation lines: "\ .
- sp->nextline = get_one_sourceline(sp);
- if (sp->nextline != NULL
- && (*(p = skipwhite(sp->nextline)) == '\\'
- || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) {
- garray_T ga;
-
- ga_init(&ga, (int)sizeof(char_u), 400);
- ga_concat(&ga, line);
- while (sp->nextline != NULL
- && concat_continued_line(&ga, 400, (char_u *)sp->nextline,
- STRLEN(sp->nextline))) {
- xfree(sp->nextline);
- sp->nextline = get_one_sourceline(sp);
- }
- ga_append(&ga, NUL);
- xfree(line);
- line = ga.ga_data;
- }
- }
-
- if (line != NULL && sp->conv.vc_type != CONV_NONE) {
- char *s;
-
- // Convert the encoding of the script line.
- s = (char *)string_convert(&sp->conv, (char_u *)line, NULL);
- if (s != NULL) {
- xfree(line);
- line = s;
- }
- }
-
- // Did we encounter a breakpoint?
- if (sp->breakpoint != 0 && sp->breakpoint <= sourcing_lnum) {
- dbg_breakpoint((char_u *)sp->fname, sourcing_lnum);
- // Find next breakpoint.
- sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, sourcing_lnum);
- sp->dbg_tick = debug_tick;
- }
-
- return line;
-}
-
-static char *get_one_sourceline(struct source_cookie *sp)
-{
- garray_T ga;
- int len;
- int c;
- char *buf;
-#ifdef USE_CRNL
- int has_cr; // CR-LF found
-#endif
- bool have_read = false;
-
- // use a growarray to store the sourced line
- ga_init(&ga, 1, 250);
-
- // Loop until there is a finished line (or end-of-file).
- sp->sourcing_lnum++;
- for (;;) {
- // make room to read at least 120 (more) characters
- ga_grow(&ga, 120);
- buf = ga.ga_data;
-
-retry:
- errno = 0;
- if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len,
- sp->fp) == NULL) {
- if (errno == EINTR) {
- goto retry;
- }
-
- break;
- }
- len = ga.ga_len + (int)STRLEN(buf + ga.ga_len);
-#ifdef USE_CRNL
- // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the
- // CTRL-Z by its own, or after a NL.
- if ((len == 1 || (len >= 2 && buf[len - 2] == '\n'))
- && sp->fileformat == EOL_DOS
- && buf[len - 1] == Ctrl_Z) {
- buf[len - 1] = NUL;
- break;
- }
-#endif
-
- have_read = true;
- ga.ga_len = len;
-
- // If the line was longer than the buffer, read more.
- if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') {
- continue;
- }
-
- if (len >= 1 && buf[len - 1] == '\n') { // remove trailing NL
-#ifdef USE_CRNL
- has_cr = (len >= 2 && buf[len - 2] == '\r');
- if (sp->fileformat == EOL_UNKNOWN) {
- if (has_cr) {
- sp->fileformat = EOL_DOS;
- } else {
- sp->fileformat = EOL_UNIX;
- }
- }
-
- if (sp->fileformat == EOL_DOS) {
- if (has_cr) { // replace trailing CR
- buf[len - 2] = '\n';
- len--;
- ga.ga_len--;
- } else { // lines like ":map xx yy^M" will have failed
- if (!sp->error) {
- msg_source(HL_ATTR(HLF_W));
- emsg(_("W15: Warning: Wrong line separator, ^M may be missing"));
- }
- sp->error = true;
- sp->fileformat = EOL_UNIX;
- }
- }
-#endif
- // The '\n' is escaped if there is an odd number of ^V's just
- // before it, first set "c" just before the 'V's and then check
- // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo
- for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {}
- if ((len & 1) != (c & 1)) { // escaped NL, read more
- sp->sourcing_lnum++;
- continue;
- }
-
- buf[len - 1] = NUL; // remove the NL
- }
-
- // Check for ^C here now and then, so recursive :so can be broken.
- line_breakcheck();
- break;
- }
-
- if (have_read) {
- return ga.ga_data;
- }
-
- xfree(ga.ga_data);
- return NULL;
-}
-
-/// Called when starting to read a script line.
-/// "sourcing_lnum" must be correct!
-/// When skipping lines it may not actually be executed, but we won't find out
-/// until later and we need to store the time now.
-void script_line_start(void)
-{
- scriptitem_T *si;
- sn_prl_T *pp;
-
- if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
- return;
- }
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on && sourcing_lnum >= 1) {
- // Grow the array before starting the timer, so that the time spent
- // here isn't counted.
- (void)ga_grow(&si->sn_prl_ga, sourcing_lnum - si->sn_prl_ga.ga_len);
- si->sn_prl_idx = sourcing_lnum - 1;
- while (si->sn_prl_ga.ga_len <= si->sn_prl_idx
- && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) {
- // Zero counters for a line that was not used before.
- pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
- pp->snp_count = 0;
- pp->sn_prl_total = profile_zero();
- pp->sn_prl_self = profile_zero();
- si->sn_prl_ga.ga_len++;
- }
- si->sn_prl_execed = false;
- si->sn_prl_start = profile_start();
- si->sn_prl_children = profile_zero();
- si->sn_prl_wait = profile_get_wait();
- }
-}
-
-/// Called when actually executing a function line.
-void script_line_exec(void)
-{
- scriptitem_T *si;
-
- if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
- return;
- }
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on && si->sn_prl_idx >= 0) {
- si->sn_prl_execed = true;
- }
-}
-
-/// Called when done with a function line.
-void script_line_end(void)
-{
- scriptitem_T *si;
- sn_prl_T *pp;
-
- if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
- return;
- }
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on && si->sn_prl_idx >= 0
- && si->sn_prl_idx < si->sn_prl_ga.ga_len) {
- if (si->sn_prl_execed) {
- pp = &PRL_ITEM(si, si->sn_prl_idx);
- pp->snp_count++;
- si->sn_prl_start = profile_end(si->sn_prl_start);
- si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start);
- pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start);
- pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start,
- si->sn_prl_children);
- }
- si->sn_prl_idx = -1;
- }
-}
-
-/// ":scriptencoding": Set encoding conversion for a sourced script.
-/// Without the multi-byte feature it's simply ignored.
-void ex_scriptencoding(exarg_T *eap)
-{
- struct source_cookie *sp;
- char *name;
-
- if (!getline_equal(eap->getline, eap->cookie, getsourceline)) {
- emsg(_("E167: :scriptencoding used outside of a sourced file"));
- return;
- }
-
- if (*eap->arg != NUL) {
- name = (char *)enc_canonize((char_u *)eap->arg);
- } else {
- name = eap->arg;
- }
-
- // Setup for conversion from the specified encoding to 'encoding'.
- sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie);
- convert_setup(&sp->conv, (char_u *)name, p_enc);
-
- if (name != eap->arg) {
- xfree(name);
- }
-}
-
-/// ":finish": Mark a sourced file as finished.
-void ex_finish(exarg_T *eap)
-{
- if (getline_equal(eap->getline, eap->cookie, getsourceline)) {
- do_finish(eap, false);
- } else {
- emsg(_("E168: :finish used outside of a sourced file"));
- }
-}
-
-/// Mark a sourced file as finished. Possibly makes the ":finish" pending.
-/// Also called for a pending finish at the ":endtry" or after returning from
-/// an extra do_cmdline(). "reanimate" is used in the latter case.
-void do_finish(exarg_T *eap, int reanimate)
-{
- int idx;
-
- if (reanimate) {
- ((struct source_cookie *)getline_cookie(eap->getline,
- eap->cookie))->finished = false;
- }
-
- // Cleanup (and deactivate) conditionals, but stop when a try conditional
- // not in its finally clause (which then is to be executed next) is found.
- // In this case, make the ":finish" pending for execution at the ":endtry".
- // Otherwise, finish normally.
- idx = cleanup_conditionals(eap->cstack, 0, true);
- if (idx >= 0) {
- eap->cstack->cs_pending[idx] = CSTP_FINISH;
- report_make_pending(CSTP_FINISH, NULL);
- } else {
- ((struct source_cookie *)getline_cookie(eap->getline,
- eap->cookie))->finished = true;
- }
-}
-
-/// @return true when a sourced file had the ":finish" command: Don't give error
-/// message for missing ":endif".
-/// false when not sourcing a file.
-bool source_finished(LineGetter fgetline, void *cookie)
-{
- return getline_equal(fgetline, cookie, getsourceline)
- && ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished;
-}
-
/// ":checktime [buffer]"
void ex_checktime(exarg_T *eap)
{
@@ -3007,7 +1152,7 @@ void ex_drop(exarg_T *eap)
// and mostly only one file is dropped.
// This also ignores wildcards, since it is very unlikely the user is
// editing a file name with a wildcard character.
- do_arglist(eap->arg, AL_SET, 0, false);
+ set_arglist(eap->arg);
// Expanding wildcards may result in an empty argument list. E.g. when
// editing "foo.pyc" and ".pyc" is in 'wildignore'. Assume that we
diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h
index c463bfa5ab..e454a30028 100644
--- a/src/nvim/ex_cmds2.h
+++ b/src/nvim/ex_cmds2.h
@@ -3,8 +3,7 @@
#include <stdbool.h>
-#include "nvim/ex_docmd.h"
-#include "nvim/runtime.h"
+#include "nvim/ex_cmds_defs.h"
//
// flags for check_changed()
@@ -15,27 +14,6 @@
#define CCGD_ALLBUF 8 // may write all buffers
#define CCGD_EXCMD 16 // may suggest using !
-typedef struct scriptitem_S {
- char_u *sn_name;
- bool sn_prof_on; ///< true when script is/was profiled
- bool sn_pr_force; ///< forceit: profile functions in this script
- proftime_T sn_pr_child; ///< time set when going into first child
- int sn_pr_nest; ///< nesting for sn_pr_child
- // profiling the script as a whole
- int sn_pr_count; ///< nr of times sourced
- proftime_T sn_pr_total; ///< time spent in script + children
- proftime_T sn_pr_self; ///< time spent in script itself
- proftime_T sn_pr_start; ///< time at script start
- proftime_T sn_pr_children; ///< time in children after script start
- // profiling the script per line
- garray_T sn_prl_ga; ///< things stored for every line
- proftime_T sn_prl_start; ///< start time for current line
- proftime_T sn_prl_children; ///< time spent in children for this line
- proftime_T sn_prl_wait; ///< wait start time for current line
- linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
- int sn_prl_execed; ///< line being timed was executed
-} scriptitem_T;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds2.h.generated.h"
#endif
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 4ac9847e53..afa8a276c8 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -9,14 +9,17 @@
#include <stdlib.h>
#include <string.h>
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/debugger.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
@@ -38,6 +41,7 @@
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/hardcopy.h"
+#include "nvim/help.h"
#include "nvim/highlight_group.h"
#include "nvim/if_cscope.h"
#include "nvim/input.h"
@@ -62,10 +66,10 @@
#include "nvim/os/time.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
+#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/sign.h"
@@ -100,16 +104,14 @@ typedef struct {
#define FREE_WCMD(wcmd) xfree((wcmd)->line)
-/*
- * Structure used to store info for line position in a while or for loop.
- * This is required, because do_one_cmd() may invoke ex_function(), which
- * reads more lines that may come from the while/for loop.
- */
+/// Structure used to store info for line position in a while or for loop.
+/// This is required, because do_one_cmd() may invoke ex_function(), which
+/// reads more lines that may come from the while/for loop.
struct loop_cookie {
garray_T *lines_gap; // growarray with line info
int current_line; // last read line from growarray
- int repeating; // TRUE when looping a second time
- // When "repeating" is FALSE use "getline" and "cookie" to get lines
+ int repeating; // true when looping a second time
+ // When "repeating" is false use "getline" and "cookie" to get lines
char *(*getline)(int, void *, int, bool);
void *cookie;
};
@@ -136,9 +138,7 @@ struct dbg_stuff {
# define ex_language ex_ni
#endif
-/*
- * Declare cmdnames[].
- */
+// Declare cmdnames[].
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds_defs.generated.h"
#endif
@@ -148,7 +148,7 @@ static char dollar_command[2] = { '$', 0 };
static void save_dbg_stuff(struct dbg_stuff *dsp)
{
dsp->trylevel = trylevel; trylevel = 0;
- dsp->force_abort = force_abort; force_abort = FALSE;
+ dsp->force_abort = force_abort; force_abort = false;
dsp->caught_stack = caught_stack; caught_stack = NULL;
dsp->vv_exception = v_exception(NULL);
dsp->vv_throwpoint = v_throwpoint(NULL);
@@ -179,11 +179,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp)
/// Repeatedly get commands for Ex mode, until the ":vi" command is given.
void do_exmode(void)
{
- int save_msg_scroll;
- int prev_msg_row;
- linenr_T prev_line;
- varnumber_T changedtick;
-
exmode_active = true;
State = MODE_NORMAL;
may_trigger_modechanged();
@@ -194,7 +189,7 @@ void do_exmode(void)
return;
}
- save_msg_scroll = msg_scroll;
+ int save_msg_scroll = msg_scroll;
RedrawingDisabled++; // don't redisplay the window
no_wait_return++; // don't wait for return
@@ -209,9 +204,9 @@ void do_exmode(void)
need_wait_return = false;
ex_pressedreturn = false;
ex_no_reprint = false;
- changedtick = buf_get_changedtick(curbuf);
- prev_msg_row = msg_row;
- prev_line = curwin->w_cursor.lnum;
+ varnumber_T changedtick = buf_get_changedtick(curbuf);
+ int prev_msg_row = msg_row;
+ linenr_T prev_line = curwin->w_cursor.lnum;
cmdline_row = msg_row;
do_cmdline(NULL, getexline, NULL, 0);
lines_left = Rows - 1;
@@ -232,7 +227,7 @@ void do_exmode(void)
}
}
msg_col = 0;
- print_line_no_prefix(curwin->w_cursor.lnum, FALSE, FALSE);
+ print_line_no_prefix(curwin->w_cursor.lnum, false, false);
msg_clr_eos();
}
} else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF
@@ -301,26 +296,26 @@ int do_cmdline_cmd(const char *cmd)
/// @return FAIL if cmdline could not be executed, OK otherwise
int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
{
- char *next_cmdline; // next cmd to execute
- char *cmdline_copy = NULL; // copy of cmd line
+ char *next_cmdline; // next cmd to execute
+ char *cmdline_copy = NULL; // copy of cmd line
bool used_getline = false; // used "fgetline" to obtain command
static int recursive = 0; // recursive depth
bool msg_didout_before_start = false;
int count = 0; // line number count
- int did_inc = FALSE; // incremented RedrawingDisabled
+ bool did_inc = false; // incremented RedrawingDisabled
int retval = OK;
cstack_T cstack = { // conditional stack
.cs_idx = -1,
};
garray_T lines_ga; // keep lines for ":while"/":for"
int current_line = 0; // active line in lines_ga
- char *fname = NULL; // function or script name
+ char *fname = NULL; // function or script name
linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie
- int *dbg_tick = NULL; // ptr to dbg_tick field in cookie
+ int *dbg_tick = NULL; // ptr to dbg_tick field in cookie
struct dbg_stuff debug_saved; // saved things for debug mode
int initial_trylevel;
- struct msglist **saved_msg_list = NULL;
- struct msglist *private_msg_list;
+ msglist_T **saved_msg_list = NULL;
+ msglist_T *private_msg_list;
// "fgetline" and "cookie" passed to do_one_cmd()
char *(*cmd_getline)(int, void *, int, bool);
@@ -361,7 +356,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// Inside a function use a higher nesting level.
getline_is_func = getline_equal(fgetline, cookie, get_func_line);
if (getline_is_func && ex_nesting_level == func_level(real_cookie)) {
- ++ex_nesting_level;
+ ex_nesting_level++;
}
// Get the function or script name and the address where the next breakpoint
@@ -371,14 +366,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
breakpoint = func_breakpoint(real_cookie);
dbg_tick = func_dbg_tick(real_cookie);
} else if (getline_equal(fgetline, cookie, getsourceline)) {
- fname = sourcing_name;
+ fname = SOURCING_NAME;
breakpoint = source_breakpoint(real_cookie);
dbg_tick = source_dbg_tick(real_cookie);
}
- /*
- * Initialize "force_abort" and "suppress_errthrow" at the top level.
- */
+ // Initialize "force_abort" and "suppress_errthrow" at the top level.
if (!recursive) {
force_abort = false;
suppress_errthrow = false;
@@ -390,13 +383,13 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
if (flags & DOCMD_EXCRESET) {
save_dbg_stuff(&debug_saved);
} else {
- memset(&debug_saved, 0, sizeof(debug_saved));
+ CLEAR_FIELD(debug_saved);
}
initial_trylevel = trylevel;
current_exception = NULL;
- // "did_emsg" will be set to TRUE when emsg() is used, in which case we
+ // "did_emsg" will be set to true when emsg() is used, in which case we
// cancel the whole command line, and any if/endif or loop.
// If force_abort is set, we cancel everything.
did_emsg = false;
@@ -408,12 +401,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
KeyTyped = false;
}
- /*
- * Continue executing command lines:
- * - when inside an ":if", ":while" or ":for"
- * - for multiple commands on one line, separated with '|'
- * - when repeating until there are no more lines (for ":source")
- */
+ // Continue executing command lines:
+ // - when inside an ":if", ":while" or ":for"
+ // - for multiple commands on one line, separated with '|'
+ // - when repeating until there are no more lines (for ":source")
next_cmdline = cmdline;
do {
getline_is_func = getline_equal(fgetline, cookie, get_func_line);
@@ -427,11 +418,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
did_emsg = false;
}
- /*
- * 1. If repeating a line in a loop, get a line from lines_ga.
- * 2. If no line given: Get an allocated line with fgetline().
- * 3. If a line is given: Make a copy, so we can mess with it.
- */
+ // 1. If repeating a line in a loop, get a line from lines_ga.
+ // 2. If no line given: Get an allocated line with fgetline().
+ // 3. If a line is given: Make a copy, so we can mess with it.
// 1. If repeating, get a previous line from lines_ga.
if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) {
@@ -464,20 +453,19 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
if (breakpoint != NULL && dbg_tick != NULL
&& *dbg_tick != debug_tick) {
*breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline),
- (char_u *)fname, sourcing_lnum);
+ (char_u *)fname, SOURCING_LNUM);
*dbg_tick = debug_tick;
}
next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line;
- sourcing_lnum = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum;
+ SOURCING_LNUM = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum;
// Did we encounter a breakpoint?
- if (breakpoint != NULL && *breakpoint != 0
- && *breakpoint <= sourcing_lnum) {
- dbg_breakpoint((char_u *)fname, sourcing_lnum);
+ if (breakpoint != NULL && *breakpoint != 0 && *breakpoint <= SOURCING_LNUM) {
+ dbg_breakpoint((char_u *)fname, SOURCING_LNUM);
// Find next breakpoint.
*breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline),
- (char_u *)fname, sourcing_lnum);
+ (char_u *)fname, SOURCING_LNUM);
*dbg_tick = debug_tick;
}
if (do_profiling == PROF_YES) {
@@ -509,10 +497,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// 2. If no line given, get an allocated line with fgetline().
if (next_cmdline == NULL) {
- /*
- * Need to set msg_didout for the first line after an ":if",
- * otherwise the ":if" will be overwritten.
- */
+ // Need to set msg_didout for the first line after an ":if",
+ // otherwise the ":if" will be overwritten.
if (count == 1 && getline_equal(fgetline, cookie, getexline)) {
msg_didout = true;
}
@@ -532,9 +518,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
used_getline = true;
- /*
- * Keep the first typed line. Clear it when more lines are typed.
- */
+ // Keep the first typed line. Clear it when more lines are typed.
if (flags & DOCMD_KEEPLINE) {
xfree(repeat_cmdline);
if (count == 0) {
@@ -549,13 +533,11 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
cmdline_copy = next_cmdline;
- /*
- * Save the current line when inside a ":while" or ":for", and when
- * the command looks like a ":while" or ":for", because we may need it
- * later. When there is a '|' and another command, it is stored
- * separately, because we need to be able to jump back to it from an
- * :endwhile/:endfor.
- */
+ // Save the current line when inside a ":while" or ":for", and when
+ // the command looks like a ":while" or ":for", because we may need it
+ // later. When there is a '|' and another command, it is stored
+ // separately, because we need to be able to jump back to it from an
+ // :endwhile/:endfor.
if (current_line == lines_ga.ga_len
&& (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) {
store_loop_line(&lines_ga, next_cmdline);
@@ -563,32 +545,28 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
did_endif = false;
if (count++ == 0) {
- /*
- * All output from the commands is put below each other, without
- * waiting for a return. Don't do this when executing commands
- * from a script or when being called recursive (e.g. for ":e
- * +command file").
- */
+ // All output from the commands is put below each other, without
+ // waiting for a return. Don't do this when executing commands
+ // from a script or when being called recursive (e.g. for ":e
+ // +command file").
if (!(flags & DOCMD_NOWAIT) && !recursive) {
msg_didout_before_start = msg_didout;
msg_didany = false; // no output yet
msg_start();
- msg_scroll = TRUE; // put messages below each other
- ++no_wait_return; // don't wait for return until finished
- ++RedrawingDisabled;
- did_inc = TRUE;
+ msg_scroll = true; // put messages below each other
+ no_wait_return++; // don't wait for return until finished
+ RedrawingDisabled++;
+ did_inc = true;
}
}
- if ((p_verbose >= 15 && sourcing_name != NULL) || p_verbose >= 16) {
- msg_verbose_cmd(sourcing_lnum, cmdline_copy);
+ if ((p_verbose >= 15 && SOURCING_NAME != NULL) || p_verbose >= 16) {
+ msg_verbose_cmd(SOURCING_LNUM, cmdline_copy);
}
- /*
- * 2. Execute one '|' separated command.
- * do_one_cmd() will return NULL if there is no trailing '|'.
- * "cmdline_copy" can change, e.g. for '%' and '#' expansion.
- */
+ // 2. Execute one '|' separated command.
+ // do_one_cmd() will return NULL if there is no trailing '|'.
+ // "cmdline_copy" can change, e.g. for '%' and '#' expansion.
recursive++;
next_cmdline = do_one_cmd(&cmdline_copy, flags, &cstack, cmd_getline, cmd_cookie);
recursive--;
@@ -601,10 +579,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
if (next_cmdline == NULL) {
XFREE_CLEAR(cmdline_copy);
- //
+
// If the command was typed, remember it for the ':' register.
// Do this AFTER executing the command to make :@: work.
- //
if (getline_equal(fgetline, cookie, getexline)
&& new_last_cmdline != NULL) {
xfree(last_cmdline);
@@ -622,18 +599,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
if (did_emsg && !force_abort
&& getline_equal(fgetline, cookie, get_func_line)
&& !func_has_abort(real_cookie)) {
- did_emsg = FALSE;
+ did_emsg = false;
}
if (cstack.cs_looplevel > 0) {
- ++current_line;
-
- /*
- * An ":endwhile", ":endfor" and ":continue" is handled here.
- * If we were executing commands, jump back to the ":while" or
- * ":for".
- * If we were not executing commands, decrement cs_looplevel.
- */
+ current_line++;
+
+ // An ":endwhile", ":endfor" and ":continue" is handled here.
+ // If we were executing commands, jump back to the ":while" or
+ // ":for".
+ // If we were not executing commands, decrement cs_looplevel.
if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) {
cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP);
@@ -667,34 +642,27 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
CSF_WHILE | CSF_FOR, &cstack.cs_looplevel);
}
}
- }
- /*
- * For a ":while" or ":for" we need to remember the line number.
- */
- else if (cstack.cs_lflags & CSL_HAD_LOOP) {
+ } else if (cstack.cs_lflags & CSL_HAD_LOOP) {
+ // For a ":while" or ":for" we need to remember the line number.
cstack.cs_lflags &= ~CSL_HAD_LOOP;
cstack.cs_line[cstack.cs_idx] = current_line - 1;
}
}
- /*
- * When not inside any ":while" loop, clear remembered lines.
- */
+ // When not inside any ":while" loop, clear remembered lines.
if (cstack.cs_looplevel == 0) {
if (!GA_EMPTY(&lines_ga)) {
- sourcing_lnum = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum;
+ SOURCING_LNUM = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum;
GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD);
}
current_line = 0;
}
- /*
- * A ":finally" makes did_emsg, got_int and current_exception pending for
- * being restored at the ":endtry". Reset them here and set the
- * ACTIVE and FINALLY flags, so that the finally clause gets executed.
- * This includes the case where a missing ":endif", ":endwhile" or
- * ":endfor" was detected by the ":finally" itself.
- */
+ // A ":finally" makes did_emsg, got_int and current_exception pending for
+ // being restored at the ":endtry". Reset them here and set the
+ // ACTIVE and FINALLY flags, so that the finally clause gets executed.
+ // This includes the case where a missing ":endif", ":endwhile" or
+ // ":endfor" was detected by the ":finally" itself.
if (cstack.cs_lflags & CSL_HAD_FINA) {
cstack.cs_lflags &= ~CSL_HAD_FINA;
report_make_pending((cstack.cs_pending[cstack.cs_idx]
@@ -720,38 +688,34 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// Convert an interrupt to an exception if appropriate.
(void)do_intthrow(&cstack);
- }
- /*
- * Continue executing command lines when:
- * - no CTRL-C typed, no aborting error, no exception thrown or try
- * conditionals need to be checked for executing finally clauses or
- * catching an interrupt exception
- * - didn't get an error message or lines are not typed
- * - there is a command after '|', inside a :if, :while, :for or :try, or
- * looping for ":source" command or function call.
- */
- while (!((got_int || (did_emsg && force_abort) || current_exception)
- && cstack.cs_trylevel == 0)
- && !(did_emsg
- // Keep going when inside try/catch, so that the error can be
- // deal with, except when it is a syntax error, it may cause
- // the :endtry to be missed.
- && (cstack.cs_trylevel == 0 || did_emsg_syntax)
- && used_getline
- && getline_equal(fgetline, cookie, getexline))
- && (next_cmdline != NULL
- || cstack.cs_idx >= 0
- || (flags & DOCMD_REPEAT)));
+
+ // Continue executing command lines when:
+ // - no CTRL-C typed, no aborting error, no exception thrown or try
+ // conditionals need to be checked for executing finally clauses or
+ // catching an interrupt exception
+ // - didn't get an error message or lines are not typed
+ // - there is a command after '|', inside a :if, :while, :for or :try, or
+ // looping for ":source" command or function call.
+ } while (!((got_int || (did_emsg && force_abort) || current_exception)
+ && cstack.cs_trylevel == 0)
+ && !(did_emsg
+ // Keep going when inside try/catch, so that the error can be
+ // deal with, except when it is a syntax error, it may cause
+ // the :endtry to be missed.
+ && (cstack.cs_trylevel == 0 || did_emsg_syntax)
+ && used_getline
+ && getline_equal(fgetline, cookie, getexline))
+ && (next_cmdline != NULL
+ || cstack.cs_idx >= 0
+ || (flags & DOCMD_REPEAT)));
xfree(cmdline_copy);
did_emsg_syntax = false;
GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD);
if (cstack.cs_idx >= 0) {
- /*
- * If a sourced file or executed function ran to its end, report the
- * unclosed conditional.
- */
+ // If a sourced file or executed function ran to its end, report the
+ // unclosed conditional.
if (!got_int && !current_exception
&& ((getline_equal(fgetline, cookie, getsourceline)
&& !source_finished(fgetline, cookie))
@@ -768,18 +732,16 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
}
- /*
- * Reset "trylevel" in case of a ":finish" or ":return" or a missing
- * ":endtry" in a sourced file or executed function. If the try
- * conditional is in its finally clause, ignore anything pending.
- * If it is in a catch clause, finish the caught exception.
- * Also cleanup any "cs_forinfo" structures.
- */
+ // Reset "trylevel" in case of a ":finish" or ":return" or a missing
+ // ":endtry" in a sourced file or executed function. If the try
+ // conditional is in its finally clause, ignore anything pending.
+ // If it is in a catch clause, finish the caught exception.
+ // Also cleanup any "cs_forinfo" structures.
do {
- int idx = cleanup_conditionals(&cstack, 0, TRUE);
+ int idx = cleanup_conditionals(&cstack, 0, true);
if (idx >= 0) {
- --idx; // remove try block not in its finally clause
+ idx--; // remove try block not in its finally clause
}
rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR,
&cstack.cs_looplevel);
@@ -799,17 +761,13 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// commands are executed.
if (current_exception) {
char *p = NULL;
- char *saved_sourcing_name;
- linenr_T saved_sourcing_lnum;
- struct msglist *messages = NULL;
- struct msglist *next;
-
- /*
- * If the uncaught exception is a user exception, report it as an
- * error. If it is an error exception, display the saved error
- * message now. For an interrupt exception, do nothing; the
- * interrupt message is given elsewhere.
- */
+ msglist_T *messages = NULL;
+ msglist_T *next;
+
+ // If the uncaught exception is a user exception, report it as an
+ // error. If it is an error exception, display the saved error
+ // message now. For an interrupt exception, do nothing; the
+ // interrupt message is given elsewhere.
switch (current_exception->type) {
case ET_USER:
vim_snprintf((char *)IObuff, IOSIZE,
@@ -825,10 +783,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
break;
}
- saved_sourcing_name = sourcing_name;
- saved_sourcing_lnum = sourcing_lnum;
- sourcing_name = current_exception->throw_name;
- sourcing_lnum = current_exception->throw_lnum;
+ estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum);
current_exception->throw_name = NULL;
discard_current_exception(); // uses IObuff if 'verbose'
@@ -848,9 +803,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
emsg(p);
xfree(p);
}
- xfree(sourcing_name);
- sourcing_name = saved_sourcing_name;
- sourcing_lnum = saved_sourcing_lnum;
+ xfree(SOURCING_NAME);
+ estack_pop();
} else if (got_int || (did_emsg && force_abort)) {
// On an interrupt or an aborting error not converted to an exception,
// disable the conversion of errors to exceptions. (Interrupts are not
@@ -880,25 +834,21 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
} else {
// When leaving a function, reduce nesting level.
if (getline_equal(fgetline, cookie, get_func_line)) {
- --ex_nesting_level;
+ ex_nesting_level--;
}
- /*
- * Go to debug mode when returning from a function in which we are
- * single-stepping.
- */
+ // Go to debug mode when returning from a function in which we are
+ // single-stepping.
if ((getline_equal(fgetline, cookie, getsourceline)
|| getline_equal(fgetline, cookie, get_func_line))
&& ex_nesting_level + 1 <= debug_break_level) {
do_debug(getline_equal(fgetline, cookie, getsourceline)
- ? (char_u *)_("End of sourced file")
- : (char_u *)_("End of function"));
+ ? (char_u *)_("End of sourced file")
+ : (char_u *)_("End of function"));
}
}
- /*
- * Restore the exception environment (done after returning from the
- * debugger).
- */
+ // Restore the exception environment (done after returning from the
+ // debugger).
if (flags & DOCMD_EXCRESET) {
restore_dbg_stuff(&debug_saved);
}
@@ -914,32 +864,26 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
}
- /*
- * If there was too much output to fit on the command line, ask the user to
- * hit return before redrawing the screen. With the ":global" command we do
- * this only once after the command is finished.
- */
+ // If there was too much output to fit on the command line, ask the user to
+ // hit return before redrawing the screen. With the ":global" command we do
+ // this only once after the command is finished.
if (did_inc) {
- --RedrawingDisabled;
- --no_wait_return;
- msg_scroll = FALSE;
-
- /*
- * When just finished an ":if"-":else" which was typed, no need to
- * wait for hit-return. Also for an error situation.
- */
+ RedrawingDisabled--;
+ no_wait_return--;
+ msg_scroll = false;
+
+ // When just finished an ":if"-":else" which was typed, no need to
+ // wait for hit-return. Also for an error situation.
if (retval == FAIL
|| (did_endif && KeyTyped && !did_emsg)) {
need_wait_return = false;
msg_didany = false; // don't wait when restarting edit
} else if (need_wait_return) {
- /*
- * The msg_start() above clears msg_didout. The wait_return we do
- * here should not overwrite the command that may be shown before
- * doing that.
- */
+ // The msg_start() above clears msg_didout. The wait_return we do
+ // here should not overwrite the command that may be shown before
+ // doing that.
msg_didout |= msg_didout_before_start;
- wait_return(FALSE);
+ wait_return(false);
}
}
@@ -954,13 +898,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
static char *get_loop_line(int c, void *cookie, int indent, bool do_concat)
{
struct loop_cookie *cp = (struct loop_cookie *)cookie;
- wcmd_T *wp;
- char *line;
if (cp->current_line + 1 >= cp->lines_gap->ga_len) {
if (cp->repeating) {
return NULL; // trying to read past ":endwhile"/":endfor"
}
+ char *line;
// First time inside the ":while"/":for": get line normally.
if (cp->getline == NULL) {
line = (char *)getcmdline(c, 0L, indent, do_concat);
@@ -969,7 +912,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat)
}
if (line != NULL) {
store_loop_line(cp->lines_gap, line);
- ++cp->current_line;
+ cp->current_line++;
}
return line;
@@ -977,8 +920,8 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat)
KeyTyped = false;
cp->current_line++;
- wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line;
- sourcing_lnum = wp->lnum;
+ wcmd_T *wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line;
+ SOURCING_LNUM = wp->lnum;
return xstrdup(wp->line);
}
@@ -987,23 +930,20 @@ static void store_loop_line(garray_T *gap, char *line)
{
wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap);
p->line = xstrdup(line);
- p->lnum = sourcing_lnum;
+ p->lnum = SOURCING_LNUM;
}
-/// If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals
-/// "func". * Otherwise return TRUE when "fgetline" equals "func".
+/// If "fgetline" is get_loop_line(), return true if the getline it uses equals
+/// "func". * Otherwise return true when "fgetline" equals "func".
///
/// @param cookie argument for fgetline()
-int getline_equal(LineGetter fgetline, void *cookie, LineGetter func)
+bool getline_equal(LineGetter fgetline, void *cookie, LineGetter func)
{
- LineGetter gp;
- struct loop_cookie *cp;
-
// When "fgetline" is "get_loop_line()" use the "cookie" to find the
// function that's originally used to obtain the lines. This may be
// nested several levels.
- gp = fgetline;
- cp = (struct loop_cookie *)cookie;
+ LineGetter gp = fgetline;
+ struct loop_cookie *cp = (struct loop_cookie *)cookie;
while (gp == get_loop_line) {
gp = cp->getline;
cp = cp->cookie;
@@ -1017,14 +957,11 @@ int getline_equal(LineGetter fgetline, void *cookie, LineGetter func)
/// @param cookie argument for fgetline()
void *getline_cookie(LineGetter fgetline, void *cookie)
{
- LineGetter gp;
- struct loop_cookie *cp;
-
// When "fgetline" is "get_loop_line()" use the "cookie" to find the
// cookie that's originally used to obtain the lines. This may be nested
// several levels.
- gp = fgetline;
- cp = (struct loop_cookie *)cookie;
+ LineGetter gp = fgetline;
+ struct loop_cookie *cp = (struct loop_cookie *)cookie;
while (gp == get_loop_line) {
gp = cp->getline;
cp = cp->cookie;
@@ -1038,11 +975,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie)
/// @return the buffer number.
static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, long offset)
{
- buf_T *buf;
buf_T *nextbuf;
long count = offset;
- buf = firstbuf;
+ buf_T *buf = firstbuf;
while (buf->b_next != NULL && buf->b_fnum < lnum) {
buf = buf->b_next;
}
@@ -1085,7 +1021,7 @@ static int current_win_nr(const win_T *win)
int nr = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- ++nr;
+ nr++;
if (wp == win) {
break;
}
@@ -1098,7 +1034,7 @@ static int current_tab_nr(tabpage_T *tab)
int nr = 0;
FOR_ALL_TABS(tp) {
- ++nr;
+ nr++;
if (tp == tab) {
break;
}
@@ -1385,12 +1321,11 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate)
// Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a
// count, it's a buffer name.
char *p;
- long n;
if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg)
&& (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL
|| ascii_iswhite(*p))) {
- n = getdigits_long(&eap->arg, false, -1);
+ long n = getdigits_long(&eap->arg, false, -1);
eap->arg = skipwhite(eap->arg);
if (n <= 0 && (eap->argt & EX_ZEROR) == 0) {
if (errormsg != NULL) {
@@ -1423,21 +1358,20 @@ bool is_cmd_ni(cmdidx_T cmdidx)
/// @return Success or failure
bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg)
{
- char *cmd;
- char *p;
char *after_modifier = NULL;
// Initialize cmdinfo
- memset(cmdinfo, 0, sizeof(*cmdinfo));
+ CLEAR_POINTER(cmdinfo);
// Initialize eap
- memset(eap, 0, sizeof(*eap));
- eap->line1 = 1;
- eap->line2 = 1;
- eap->cmd = cmdline;
- eap->cmdlinep = &cmdline;
- eap->getline = NULL;
- eap->cookie = NULL;
+ *eap = (exarg_T){
+ .line1 = 1,
+ .line2 = 1,
+ .cmd = cmdline,
+ .cmdlinep = &cmdline,
+ .getline = NULL,
+ .cookie = NULL,
+ };
const bool save_ex_pressedreturn = ex_pressedreturn;
// Parse command modifiers
@@ -1449,13 +1383,13 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
after_modifier = eap->cmd;
// Save location after command modifiers
- cmd = eap->cmd;
+ char *cmd = eap->cmd;
// Skip ranges to find command name since we need the command to know what kind of range it uses
eap->cmd = skip_range(eap->cmd, NULL);
if (*eap->cmd == '*') {
eap->cmd = skipwhite(eap->cmd + 1);
}
- p = find_ex_command(eap, NULL);
+ char *p = find_ex_command(eap, NULL);
if (p == NULL) {
*errormsg = _(e_ambiguous_use_of_user_defined_command);
goto err;
@@ -1719,7 +1653,7 @@ end:
static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie)
{
- // Count this line for profiling if skip is TRUE.
+ // Count this line for profiling if skip is true.
if (do_profiling == PROF_YES
&& (!eap->skip || cstack->cs_idx == 0
|| (cstack->cs_idx > 0
@@ -1855,7 +1789,7 @@ static bool skip_cmd(const exarg_T *eap)
/// Execute one Ex command.
///
-/// If 'sourcing' is TRUE, the command will be included in the error message.
+/// If 'sourcing' is true, the command will be included in the error message.
///
/// 1. skip comment lines and leading space
/// 2. handle command modifiers
@@ -1877,10 +1811,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
const int save_reg_executing = reg_executing;
const bool save_pending_end_reg_executing = pending_end_reg_executing;
- exarg_T ea;
- memset(&ea, 0, sizeof(ea));
- ea.line1 = 1;
- ea.line2 = 1;
+ exarg_T ea = {
+ .line1 = 1,
+ .line2 = 1,
+ };
ex_nesting_level++;
// When the last file has not been edited :q has to be typed twice.
@@ -1890,7 +1824,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// avoid that an autocommand, e.g. QuitPre, does this
&& !getline_equal(fgetline, cookie,
getnextac)) {
- --quitmore;
+ quitmore--;
}
// Reset browse, confirm, etc.. They are restored when returning, for
@@ -1941,7 +1875,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// used, throw an interrupt exception and skip the next command.
dbg_check_breakpoint(&ea);
if (!ea.skip && got_int) {
- ea.skip = TRUE;
+ ea.skip = true;
(void)do_intthrow(cstack);
}
@@ -2015,7 +1949,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
&& has_event(EVENT_CMDUNDEFINED)) {
p = ea.cmd;
while (ASCII_ISALNUM(*p)) {
- ++p;
+ p++;
}
p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd));
int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
@@ -2194,16 +2128,16 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
ea.arg = skipwhite(ea.arg + 1);
ea.append = true;
} else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { // :w !filter
- ++ea.arg;
- ea.usefilter = TRUE;
+ ea.arg++;
+ ea.usefilter = true;
}
} else if (ea.cmdidx == CMD_read) {
if (ea.forceit) {
- ea.usefilter = TRUE; // :r! filter if ea.forceit
- ea.forceit = FALSE;
+ ea.usefilter = true; // :r! filter if ea.forceit
+ ea.forceit = false;
} else if (*ea.arg == '!') { // :r !filter
- ++ea.arg;
- ea.usefilter = TRUE;
+ ea.arg++;
+ ea.usefilter = true;
}
} else if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) {
ea.amount = 1;
@@ -2294,10 +2228,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
do_throw(cstack);
} else if (check_cstack) {
if (source_finished(fgetline, cookie)) {
- do_finish(&ea, TRUE);
+ do_finish(&ea, true);
} else if (getline_equal(fgetline, cookie, get_func_line)
&& current_func_returned()) {
- do_return(&ea, TRUE, FALSE, NULL);
+ do_return(&ea, true, false, NULL);
}
}
need_rethrow = check_cstack = false;
@@ -2332,7 +2266,7 @@ doend:
ea.nextcmd = NULL;
}
- --ex_nesting_level;
+ ex_nesting_level--;
return ea.nextcmd;
}
@@ -2368,8 +2302,6 @@ char *ex_errmsg(const char *const msg, const char *const arg)
/// @return FAIL when the command is not to be executed.
int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool skip_only)
{
- char *p;
-
CLEAR_POINTER(cmod);
// Repeat until no more command modifiers are found.
@@ -2401,7 +2333,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool
return FAIL;
}
- p = skip_range(eap->cmd, NULL);
+ char *p = skip_range(eap->cmd, NULL);
switch (*p) {
// When adding an entry, also modify cmd_exists().
case 'a':
@@ -2840,12 +2772,12 @@ theend:
}
/// Check for an Ex command with optional tail.
-/// If there is a match advance "pp" to the argument and return TRUE.
+/// If there is a match advance "pp" to the argument and return true.
///
/// @param pp start of command
/// @param cmd name of command
/// @param len required length
-int checkforcmd(char **pp, char *cmd, int len)
+bool checkforcmd(char **pp, char *cmd, int len)
{
int i;
@@ -2858,7 +2790,7 @@ int checkforcmd(char **pp, char *cmd, int len)
*pp = skipwhite(*pp + i);
return true;
}
- return FALSE;
+ return false;
}
/// Append "cmd" to the error message in IObuff.
@@ -2886,7 +2818,7 @@ static void append_command(char *cmd)
} else if ((char_u *)d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) {
break;
} else {
- mb_copy_char((const char_u **)&s, (char_u **)&d);
+ mb_copy_char((const char **)&s, &d);
}
}
*d = NUL;
@@ -2895,29 +2827,23 @@ static void append_command(char *cmd)
/// Find an Ex command by its name, either built-in or user.
/// Start of the name can be found at eap->cmd.
/// Sets eap->cmdidx and returns a pointer to char after the command name.
-/// "full" is set to TRUE if the whole command name matched.
+/// "full" is set to true if the whole command name matched.
///
/// @return NULL for an ambiguous user command.
char *find_ex_command(exarg_T *eap, int *full)
FUNC_ATTR_NONNULL_ARG(1)
{
- int len;
- char *p;
- int i;
-
- /*
- * Isolate the command and search for it in the command table.
- * Exceptions:
- * - the 'k' command can directly be followed by any character.
- * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
- * but :sre[wind] is another command, as are :scr[iptnames],
- * :scs[cope], :sim[alt], :sig[ns] and :sil[ent].
- * - the "d" command can directly be followed by 'l' or 'p' flag.
- */
- p = eap->cmd;
+ // Isolate the command and search for it in the command table.
+ // Exceptions:
+ // - the 'k' command can directly be followed by any character.
+ // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
+ // but :sre[wind] is another command, as are :scr[iptnames],
+ // :scs[cope], :sim[alt], :sig[ns] and :sil[ent].
+ // - the "d" command can directly be followed by 'l' or 'p' flag.
+ char *p = eap->cmd;
if (*p == 'k') {
eap->cmdidx = CMD_k;
- ++p;
+ p++;
} else if (p[0] == 's'
&& ((p[1] == 'c'
&& (p[2] == NUL
@@ -2929,15 +2855,15 @@ char *find_ex_command(exarg_T *eap, int *full)
|| p[1] == 'I'
|| (p[1] == 'r' && p[2] != 'e'))) {
eap->cmdidx = CMD_substitute;
- ++p;
+ p++;
} else {
while (ASCII_ISALPHA(*p)) {
- ++p;
+ p++;
}
// for python 3.x support ":py3", ":python3", ":py3file", etc.
if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') {
while (ASCII_ISALNUM(*p)) {
- ++p;
+ p++;
}
}
@@ -2945,17 +2871,18 @@ char *find_ex_command(exarg_T *eap, int *full)
if (p == eap->cmd && vim_strchr("@!=><&~#", *p) != NULL) {
p++;
}
- len = (int)(p - eap->cmd);
+ int len = (int)(p - eap->cmd);
if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) {
// Check for ":dl", ":dell", etc. to ":deletel": that's
// :delete with the 'l' flag. Same for 'p'.
+ int i;
for (i = 0; i < len; i++) {
if (eap->cmd[i] != ("delete")[i]) {
break;
}
}
if (i == len - 1) {
- --len;
+ len--;
if (p[-1] == 'l') {
eap->flags |= EXFLAG_LIST;
} else {
@@ -2992,7 +2919,7 @@ char *find_ex_command(exarg_T *eap, int *full)
(size_t)len) == 0) {
if (full != NULL
&& cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) {
- *full = TRUE;
+ *full = true;
}
break;
}
@@ -3003,7 +2930,7 @@ char *find_ex_command(exarg_T *eap, int *full)
&& *eap->cmd >= 'A' && *eap->cmd <= 'Z') {
// User defined commands may contain digits.
while (ASCII_ISALNUM(*p)) {
- ++p;
+ p++;
}
p = find_ucmd(eap, p, full, NULL, NULL);
}
@@ -3075,9 +3002,6 @@ int modifier_len(char *cmd)
/// 3 if there is an ambiguous match.
int cmd_exists(const char *const name)
{
- exarg_T ea;
- char *p;
-
// Check command modifiers.
for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) {
int j;
@@ -3093,11 +3017,12 @@ int cmd_exists(const char *const name)
// Check built-in commands and user defined commands.
// For ":2match" and ":3match" we need to skip the number.
+ exarg_T ea;
ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name);
ea.cmdidx = (cmdidx_T)0;
ea.flags = 0;
int full = false;
- p = find_ex_command(&ea, &full);
+ char *p = find_ex_command(&ea, &full);
if (p == NULL) {
return 3;
}
@@ -3113,7 +3038,6 @@ int cmd_exists(const char *const name)
/// "fullcommand" function
void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- exarg_T ea;
char *name = argvars[0].vval.v_string;
rettv->v_type = VAR_STRING;
@@ -3127,6 +3051,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
name = skip_range(name, NULL);
+ exarg_T ea;
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
ea.cmdidx = (cmdidx_T)0;
ea.flags = 0;
@@ -3175,14 +3100,10 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
return NULL;
}
- /*
- * 3. parse a range specifier of the form: addr [,addr] [;addr] ..
- */
+ // 3. parse a range specifier of the form: addr [,addr] [;addr] ..
cmd = (const char *)skip_range(cmd, &xp->xp_context);
- /*
- * 4. parse command
- */
+ // 4. parse command
xp->xp_pattern = (char *)cmd;
if (*cmd == NUL) {
return NULL;
@@ -3195,13 +3116,11 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
if (*cmd == '|' || *cmd == '\n') {
return cmd + 1; // There's another command
}
- /*
- * Isolate the command and search for it in the command table.
- * Exceptions:
- * - the 'k' command can directly be followed by any character, but
- * do accept "keepmarks", "keepalt" and "keepjumps".
- * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
- */
+ // Isolate the command and search for it in the command table.
+ // Exceptions:
+ // - the 'k' command can directly be followed by any character, but
+ // do accept "keepmarks", "keepalt" and "keepjumps".
+ // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
const char *p;
if (*cmd == 'k' && cmd[1] != 'e') {
ea.cmdidx = CMD_k;
@@ -3281,9 +3200,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
p++;
}
- /*
- * 5. parse arguments
- */
+ // 5. parse arguments
if (!IS_USER_CMDIDX(ea.cmdidx)) {
ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt;
}
@@ -3341,10 +3258,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
arg = (const char *)skipwhite(arg);
}
- /*
- * Check for '|' to separate commands and '"' to start comments.
- * Don't do this for ":read !cmd" and ":write !cmd".
- */
+ // Check for '|' to separate commands and '"' to start comments.
+ // Don't do this for ":read !cmd" and ":write !cmd".
if ((ea.argt & EX_TRLBAR) && !usefilter) {
p = arg;
// ":redir @" is not the start of a comment
@@ -3392,18 +3307,15 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
}
if (ea.argt & EX_XFILE) {
- int c;
int in_quote = false;
const char *bow = NULL; // Beginning of word.
- /*
- * Allow spaces within back-quotes to count as part of the argument
- * being expanded.
- */
+ // Allow spaces within back-quotes to count as part of the argument
+ // being expanded.
xp->xp_pattern = skipwhite(arg);
p = (const char *)xp->xp_pattern;
while (*p != NUL) {
- c = utf_ptr2char(p);
+ int c = utf_ptr2char(p);
if (c == '\\' && p[1] != NUL) {
p++;
} else if (c == '`') {
@@ -3412,13 +3324,9 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
bow = p + 1;
}
in_quote = !in_quote;
- }
- /* An argument can contain just about everything, except
- * characters that end the command and white space. */
- else if (c == '|'
- || c == '\n'
- || c == '"'
- || ascii_iswhite(c)) {
+ // An argument can contain just about everything, except
+ // characters that end the command and white space.
+ } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) {
len = 0; // avoid getting stuck when space is in 'isfname'
while (*p != NUL) {
c = utf_ptr2char(p);
@@ -3438,10 +3346,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
MB_PTR_ADV(p);
}
- /*
- * If we are still inside the quotes, and we passed a space, just
- * expand from there.
- */
+ // If we are still inside the quotes, and we passed a space, just
+ // expand from there.
if (bow != NULL && in_quote) {
xp->xp_pattern = (char *)bow;
}
@@ -3450,7 +3356,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
// For a shell command more chars need to be escaped.
if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) {
#ifndef BACKSLASH_IN_FILENAME
- xp->xp_shell = TRUE;
+ xp->xp_shell = true;
#endif
// When still after the command name expand executables.
if (xp->xp_pattern == skipwhite(arg)) {
@@ -3483,14 +3389,12 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
if (*p == NUL && p > (const char *)xp->xp_pattern + 1
&& match_user((char_u *)xp->xp_pattern + 1) >= 1) {
xp->xp_context = EXPAND_USER;
- ++xp->xp_pattern;
+ xp->xp_pattern++;
}
}
}
- /*
- * 6. switch on command name
- */
+ // 6. switch on command name
switch (ea.cmdidx) {
case CMD_find:
case CMD_sfind:
@@ -3514,8 +3418,8 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
xp->xp_pattern = (char *)arg;
break;
- /* Command modifiers: return the argument.
- * Also for commands with an argument that is a command. */
+ // Command modifiers: return the argument.
+ // Also for commands with an argument that is a command.
case CMD_aboveleft:
case CMD_argdo:
case CMD_belowright:
@@ -3573,9 +3477,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
}
return (const char *)find_nextcmd((char_u *)arg);
- /*
- * All completion for the +cmdline_compl feature goes here.
- */
+ // All completion for the +cmdline_compl feature goes here.
case CMD_command:
return set_context_in_user_cmd(xp, arg);
@@ -3785,7 +3687,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
} else if (context == EXPAND_COMMANDS) {
return arg;
} else if (context == EXPAND_MAPPINGS) {
- return (const char *)set_context_in_map_cmd(xp, (char_u *)"map", (char_u *)arg, forceit,
+ return (const char *)set_context_in_map_cmd(xp, "map", (char_u *)arg, forceit,
false, false,
CMD_map);
}
@@ -3823,7 +3725,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
case CMD_snoremap:
case CMD_xmap:
case CMD_xnoremap:
- return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, false,
+ return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false,
false, ea.cmdidx);
case CMD_unmap:
case CMD_nunmap:
@@ -3834,7 +3736,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
case CMD_lunmap:
case CMD_sunmap:
case CMD_xunmap:
- return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, false,
+ return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false,
true, ea.cmdidx);
case CMD_mapclear:
case CMD_nmapclear:
@@ -3855,12 +3757,12 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
case CMD_cnoreabbrev:
case CMD_iabbrev:
case CMD_inoreabbrev:
- return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, true,
+ return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true,
false, ea.cmdidx);
case CMD_unabbreviate:
case CMD_cunabbrev:
case CMD_iunabbrev:
- return (const char *)set_context_in_map_cmd(xp, (char_u *)cmd, (char_u *)arg, forceit, true,
+ return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true,
true, ea.cmdidx);
case CMD_menu:
case CMD_noremenu:
@@ -3992,8 +3894,6 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
/// @return the "cmd" pointer advanced to beyond the range.
char *skip_range(const char *cmd, int *ctx)
{
- unsigned delim;
-
while (vim_strchr(" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) {
if (*cmd == '\\') {
if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') {
@@ -4006,10 +3906,10 @@ char *skip_range(const char *cmd, int *ctx)
*ctx = EXPAND_NOTHING;
}
} else if (*cmd == '/' || *cmd == '?') {
- delim = (unsigned)(*cmd++);
+ unsigned delim = (unsigned)(*cmd++);
while (*cmd != NUL && *cmd != (char)delim) {
if (*cmd++ == '\\' && *cmd != NUL) {
- ++cmd;
+ cmd++;
}
}
if (*cmd == NUL && ctx != NULL) {
@@ -4017,7 +3917,7 @@ char *skip_range(const char *cmd, int *ctx)
}
}
if (*cmd != NUL) {
- ++cmd;
+ cmd++;
}
}
@@ -4055,17 +3955,15 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
int c;
int i;
linenr_T n;
- char *cmd;
pos_T pos;
- linenr_T lnum;
buf_T *buf;
- cmd = skipwhite(*ptr);
- lnum = MAXLNUM;
+ char *cmd = skipwhite(*ptr);
+ linenr_T lnum = MAXLNUM;
do {
switch (*cmd) {
case '.': // '.' - Cursor position
- ++cmd;
+ cmd++;
switch (addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
@@ -4101,7 +3999,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
break;
case '$': // '$' - last line
- ++cmd;
+ cmd++;
switch (addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
@@ -4162,7 +4060,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
goto error;
}
if (skip) {
- ++cmd;
+ cmd++;
} else {
// Only accept a mark in another file when it is
// used by itself: ":'M".
@@ -4194,7 +4092,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
if (skip) { // skip "/pat/"
cmd = (char *)skip_regexp((char_u *)cmd, c, p_magic, NULL);
if (*cmd == c) {
- ++cmd;
+ cmd++;
}
} else {
int flags;
@@ -4234,7 +4132,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
break;
case '\\': // "\?", "\/" or "\&", repeat search
- ++cmd;
+ cmd++;
if (addr_type != ADDR_LINES) {
addr_error(addr_type);
cmd = NULL;
@@ -4265,7 +4163,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
goto error;
}
}
- ++cmd;
+ cmd++;
break;
default:
@@ -4560,45 +4458,35 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep)
/// @return FAIL for failure, OK otherwise.
int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
{
- int has_wildcards; // need to expand wildcards
- char *repl;
- size_t srclen;
- char *p;
- int escaped;
-
// Skip a regexp pattern for ":vimgrep[add] pat file..."
- p = skip_grep_pat(eap);
-
- /*
- * Decide to expand wildcards *before* replacing '%', '#', etc. If
- * the file name contains a wildcard it should not cause expanding.
- * (it will be expanded anyway if there is a wildcard before replacing).
- */
- has_wildcards = path_has_wildcard((char_u *)p);
+ char *p = skip_grep_pat(eap);
+
+ // Decide to expand wildcards *before* replacing '%', '#', etc. If
+ // the file name contains a wildcard it should not cause expanding.
+ // (it will be expanded anyway if there is a wildcard before replacing).
+ int has_wildcards = path_has_wildcard((char_u *)p);
while (*p != NUL) {
// Skip over `=expr`, wildcards in it are not expanded.
if (p[0] == '`' && p[1] == '=') {
p += 2;
(void)skip_expr(&p);
if (*p == '`') {
- ++p;
+ p++;
}
continue;
}
- /*
- * Quick check if this cannot be the start of a special string.
- * Also removes backslash before '%', '#' and '<'.
- */
+ // Quick check if this cannot be the start of a special string.
+ // Also removes backslash before '%', '#' and '<'.
if (vim_strchr("%#<", *p) == NULL) {
p++;
continue;
}
- /*
- * Try to find a match at this position.
- */
- repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum),
- errormsgp, &escaped);
+ // Try to find a match at this position.
+ size_t srclen;
+ int escaped;
+ char *repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum),
+ errormsgp, &escaped);
if (*errormsgp != NULL) { // error detected
return FAIL;
}
@@ -4668,20 +4556,16 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
xfree(repl);
}
- /*
- * One file argument: Expand wildcards.
- * Don't do this with ":r !command" or ":w !command".
- */
+ // One file argument: Expand wildcards.
+ // Don't do this with ":r !command" or ":w !command".
if ((eap->argt & EX_NOSPC) && !eap->usefilter) {
// Replace environment variables.
if (has_wildcards) {
- /*
- * May expand environment variables. This
- * can be done much faster with expand_env() than with
- * something else (e.g., calling a shell).
- * After expanding environment variables, check again
- * if there are still wildcards present.
- */
+ // May expand environment variables. This
+ // can be done much faster with expand_env() than with
+ // something else (e.g., calling a shell).
+ // After expanding environment variables, check again
+ // if there are still wildcards present.
if (vim_strchr(eap->arg, '$') != NULL
|| vim_strchr(eap->arg, '~') != NULL) {
expand_env_esc((char_u *)eap->arg, NameBuff, MAXPATHL, true, true, NULL);
@@ -4695,15 +4579,16 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
}
}
- /*
- * Halve the number of backslashes (this is Vi compatible).
- * For Unix, when wildcards are expanded, this is
- * done by ExpandOne() below.
- */
+ // Halve the number of backslashes (this is Vi compatible).
+ // For Unix, when wildcards are expanded, this is
+ // done by ExpandOne() below.
#ifdef UNIX
- if (!has_wildcards)
-#endif
+ if (!has_wildcards) {
+ backslash_halve((char_u *)eap->arg);
+ }
+#else
backslash_halve((char_u *)eap->arg);
+#endif
if (has_wildcards) {
expand_T xpc;
@@ -4733,11 +4618,9 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
/// @return a pointer to the character after the replaced string.
static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, char **cmdlinep)
{
- /*
- * The new command line is build in new_cmdline[].
- * First allocate it.
- * Careful: a "+cmd" argument may have been NUL terminated.
- */
+ // The new command line is build in new_cmdline[].
+ // First allocate it.
+ // Careful: a "+cmd" argument may have been NUL terminated.
size_t len = STRLEN(repl);
size_t i = (size_t)(src - *cmdlinep) + STRLEN(src + srclen) + len + 3;
if (eap->nextcmd != NULL) {
@@ -4746,12 +4629,10 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch
char *new_cmdline = xmalloc(i);
size_t offset = (size_t)(src - *cmdlinep);
- /*
- * Copy the stuff before the expanded part.
- * Copy the expanded stuff.
- * Copy what came after the expanded part.
- * Copy the next commands, if there are any.
- */
+ // Copy the stuff before the expanded part.
+ // Copy the expanded stuff.
+ // Copy what came after the expanded part.
+ // Copy the next commands, if there are any.
i = offset; // length of part before match
memmove(new_cmdline, *cmdlinep, i);
@@ -4847,12 +4728,12 @@ static char *getargcmd(char **argp)
char *command = NULL;
if (*arg == '+') { // +[command]
- ++arg;
+ arg++;
if (ascii_isspace(*arg) || *arg == '\0') {
command = (char *)dollar_command;
} else {
command = arg;
- arg = skip_cmd_arg(command, TRUE);
+ arg = skip_cmd_arg(command, true);
if (*arg != NUL) {
*arg++ = NUL; // terminate command with NUL
}
@@ -4866,7 +4747,7 @@ static char *getargcmd(char **argp)
/// Find end of "+command" argument. Skip over "\ " and "\\".
///
-/// @param rembs TRUE to halve the number of backslashes
+/// @param rembs true to halve the number of backslashes
static char *skip_cmd_arg(char *p, int rembs)
{
while (*p && !ascii_isspace(*p)) {
@@ -4874,7 +4755,7 @@ static char *skip_cmd_arg(char *p, int rembs)
if (rembs) {
STRMOVE(p, p + 1);
} else {
- ++p;
+ p++;
}
}
MB_PTR_ADV(p);
@@ -4905,7 +4786,6 @@ static int getargopt(exarg_T *eap)
char *arg = eap->arg + 2;
int *pp = NULL;
int bad_char_idx;
- char *p;
// ":edit ++[no]bin[ary] file"
if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) {
@@ -4964,7 +4844,7 @@ static int getargopt(exarg_T *eap)
eap->force_ff = (char_u)eap->cmd[eap->force_ff];
} else if (pp == &eap->force_enc) {
// Make 'fileencoding' lower case.
- for (p = eap->cmd + eap->force_enc; *p != NUL; p++) {
+ for (char *p = eap->cmd + eap->force_enc; *p != NUL; p++) {
*p = (char)TOLOWER_ASC(*p);
}
} else {
@@ -4989,7 +4869,6 @@ static int get_tabpage_arg(exarg_T *eap)
if (eap->arg && *eap->arg != NUL) {
char *p = eap->arg;
- char *p_save;
int relative = 0; // argument +N/-N means: go to N places to the
// right/left relative to the current position.
@@ -5001,7 +4880,7 @@ static int get_tabpage_arg(exarg_T *eap)
p++;
}
- p_save = p;
+ char *p_save = p;
tab_number = (int)getdigits(&p, false, tab_number);
if (relative == 0) {
@@ -5223,9 +5102,9 @@ char_u *check_nextcmd(char_u *p)
/// - and forceit not used
/// - and not repeated twice on a row
///
-/// @param message when FALSE check only, no messages
+/// @param message when false check only, no messages
///
-/// @return FAIL and give error message if 'message' TRUE, return OK otherwise
+/// @return FAIL and give error message if 'message' true, return OK otherwise
static int check_more(int message, bool forceit)
{
int n = ARGCOUNT - curwin->w_arg_idx - 1;
@@ -5266,10 +5145,9 @@ static void ex_colorscheme(exarg_T *eap)
{
if (*eap->arg == NUL) {
char *expr = xstrdup("g:colors_name");
- char *p = NULL;
emsg_off++;
- p = eval_to_string(expr, NULL, false);
+ char *p = eval_to_string(expr, NULL, false);
emsg_off--;
xfree(expr);
@@ -5473,16 +5351,15 @@ static void ex_pclose(exarg_T *eap)
/// @param tp NULL or the tab page "win" is in
void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
{
- int need_hide;
- buf_T *buf = win->w_buffer;
-
// Never close the autocommand window.
if (win == aucmd_win) {
emsg(_(e_autocmd_close));
return;
}
- need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
+ buf_T *buf = win->w_buffer;
+
+ bool need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
if (need_hide && !buf_hide(buf) && !forceit) {
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) {
bufref_T bufref;
@@ -5510,8 +5387,6 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
/// ":tabclose N": close tab page N.
static void ex_tabclose(exarg_T *eap)
{
- tabpage_T *tp;
-
if (cmdwin_type != 0) {
cmdwin_result = K_IGNORE;
} else if (first_tabpage->tp_next == NULL) {
@@ -5519,7 +5394,7 @@ static void ex_tabclose(exarg_T *eap)
} else {
int tab_number = get_tabpage_arg(eap);
if (eap->errmsg == NULL) {
- tp = find_tabpage(tab_number);
+ tabpage_T *tp = find_tabpage(tab_number);
if (tp == NULL) {
beep_flush();
return;
@@ -5591,7 +5466,6 @@ void tabpage_close(int forceit)
void tabpage_close_other(tabpage_T *tp, int forceit)
{
int done = 0;
- win_T *wp;
int h = tabline_height();
char prev_idx[NUMBUFLEN];
@@ -5599,7 +5473,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
// one. OK, so I'm paranoid...
while (++done < 1000) {
snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
- wp = tp->tp_lastwin;
+ win_T *wp = tp->tp_lastwin;
ex_win_close(forceit, wp, tp);
// Autocommands may delete the tab page under our fingers and we may
@@ -5619,10 +5493,9 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
static void ex_only(exarg_T *eap)
{
win_T *wp;
- linenr_T wnr;
if (eap->addr_count > 0) {
- wnr = eap->line2;
+ linenr_T wnr = eap->line2;
for (wp = firstwin; --wnr > 0;) {
if (wp->w_next == NULL) {
break;
@@ -5636,17 +5509,7 @@ static void ex_only(exarg_T *eap)
if (wp != curwin) {
win_goto(wp);
}
- close_others(TRUE, eap->forceit);
-}
-
-/// ":all" and ":sall".
-/// Also used for ":tab drop file ..." after setting the argument list.
-void ex_all(exarg_T *eap)
-{
- if (eap->addr_count == 0) {
- eap->line2 = 9999;
- }
- do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop);
+ close_others(true, eap->forceit);
}
static void ex_hide(exarg_T *eap)
@@ -5761,162 +5624,6 @@ static void ex_goto(exarg_T *eap)
goto_byte(eap->line2);
}
-/// Clear an argument list: free all file names and reset it to zero entries.
-void alist_clear(alist_T *al)
-{
-#define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname)
- GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME);
-}
-
-/// Init an argument list.
-void alist_init(alist_T *al)
-{
- ga_init(&al->al_ga, (int)sizeof(aentry_T), 5);
-}
-
-/// Remove a reference from an argument list.
-/// Ignored when the argument list is the global one.
-/// If the argument list is no longer used by any window, free it.
-void alist_unlink(alist_T *al)
-{
- if (al != &global_alist && --al->al_refcount <= 0) {
- alist_clear(al);
- xfree(al);
- }
-}
-
-/// Create a new argument list and use it for the current window.
-void alist_new(void)
-{
- curwin->w_alist = xmalloc(sizeof(*curwin->w_alist));
- curwin->w_alist->al_refcount = 1;
- curwin->w_alist->id = ++max_alist_id;
- alist_init(curwin->w_alist);
-}
-
-#if !defined(UNIX)
-
-/// Expand the file names in the global argument list.
-/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer
-/// numbers to be re-used.
-void alist_expand(int *fnum_list, int fnum_len)
-{
- char **old_arg_files;
- int old_arg_count;
- char **new_arg_files;
- int new_arg_file_count;
- char *save_p_su = p_su;
- int i;
-
- /* Don't use 'suffixes' here. This should work like the shell did the
- * expansion. Also, the vimrc file isn't read yet, thus the user
- * can't set the options. */
- p_su = empty_option;
- old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT);
- for (i = 0; i < GARGCOUNT; ++i) {
- old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname);
- }
- old_arg_count = GARGCOUNT;
- if (expand_wildcards(old_arg_count, old_arg_files,
- &new_arg_file_count, &new_arg_files,
- EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK
- && new_arg_file_count > 0) {
- alist_set(&global_alist, new_arg_file_count, new_arg_files,
- TRUE, fnum_list, fnum_len);
- FreeWild(old_arg_count, old_arg_files);
- }
- p_su = save_p_su;
-}
-#endif
-
-/// Set the argument list for the current window.
-/// Takes over the allocated files[] and the allocated fnames in it.
-void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len)
-{
- int i;
- static int recursive = 0;
-
- if (recursive) {
- emsg(_(e_au_recursive));
- return;
- }
- recursive++;
-
- alist_clear(al);
- ga_grow(&al->al_ga, count);
- {
- for (i = 0; i < count; ++i) {
- if (got_int) {
- /* When adding many buffers this can take a long time. Allow
- * interrupting here. */
- while (i < count) {
- xfree(files[i++]);
- }
- break;
- }
-
- /* May set buffer name of a buffer previously used for the
- * argument list, so that it's re-used by alist_add. */
- if (fnum_list != NULL && i < fnum_len) {
- buf_set_name(fnum_list[i], files[i]);
- }
-
- alist_add(al, files[i], use_curbuf ? 2 : 1);
- os_breakcheck();
- }
- xfree(files);
- }
-
- if (al == &global_alist) {
- arg_had_last = false;
- }
- recursive--;
-}
-
-/// Add file "fname" to argument list "al".
-/// "fname" must have been allocated and "al" must have been checked for room.
-///
-/// @param set_fnum 1: set buffer number; 2: re-use curbuf
-void alist_add(alist_T *al, char *fname, int set_fnum)
-{
- if (fname == NULL) { // don't add NULL file names
- return;
- }
-#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(fname);
-#endif
- AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname;
- if (set_fnum > 0) {
- AARGLIST(al)[al->al_ga.ga_len].ae_fnum =
- buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0));
- }
- ++al->al_ga.ga_len;
-}
-
-#if defined(BACKSLASH_IN_FILENAME)
-
-/// Adjust slashes in file names. Called after 'shellslash' was set.
-void alist_slash_adjust(void)
-{
- for (int i = 0; i < GARGCOUNT; ++i) {
- if (GARGLIST[i].ae_fname != NULL) {
- slash_adjust(GARGLIST[i].ae_fname);
- }
- }
-
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_alist != &global_alist) {
- for (int i = 0; i < WARGCOUNT(wp); ++i) {
- if (WARGLIST(wp)[i].ae_fname != NULL) {
- slash_adjust(WARGLIST(wp)[i].ae_fname);
- }
- }
- }
- }
-}
-
-#endif
-
/// ":preserve".
static void ex_preserve(exarg_T *eap)
{
@@ -5985,9 +5692,7 @@ void ex_splitview(exarg_T *eap)
eap->arg = fname;
}
- /*
- * Either open new tab page or split the window.
- */
+ // Either open new tab page or split the window.
if (use_tab) {
if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab : eap->addr_count == 0
? 0 : (int)eap->line2 + 1, (char_u *)eap->arg) != FAIL) {
@@ -6021,12 +5726,11 @@ theend:
/// Open a new tab page.
void tabpage_new(void)
{
- exarg_T ea;
-
- memset(&ea, 0, sizeof(ea));
- ea.cmdidx = CMD_tabnew;
- ea.cmd = "tabn";
- ea.arg = "";
+ exarg_T ea = {
+ .cmdidx = CMD_tabnew,
+ .cmd = "tabn",
+ .arg = "",
+ };
ex_splitview(&ea);
}
@@ -6092,7 +5796,7 @@ static void ex_tabs(exarg_T *eap)
int tabcount = 1;
msg_start();
- msg_scroll = TRUE;
+ msg_scroll = true;
win_T *lastused_win = valid_tabpage(lastused_tabpage)
? lastused_tabpage->tp_curwin
@@ -6147,15 +5851,14 @@ static void ex_mode(exarg_T *eap)
/// set, increment or decrement current window height
static void ex_resize(exarg_T *eap)
{
- int n;
win_T *wp = curwin;
if (eap->addr_count > 0) {
- n = (int)eap->line2;
+ int n = (int)eap->line2;
for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) {}
}
- n = (int)atol(eap->arg);
+ int n = (int)atol(eap->arg);
if (cmdmod.cmod_split & WSP_VERT) {
if (*eap->arg == '-' || *eap->arg == '+') {
n += wp->w_width;
@@ -6176,15 +5879,12 @@ static void ex_resize(exarg_T *eap)
/// ":find [+command] <file>" command.
static void ex_find(exarg_T *eap)
{
- char *fname;
- linenr_T count;
-
- fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg),
- FNAME_MESS, true, (char_u *)curbuf->b_ffname);
+ char *fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg),
+ FNAME_MESS, true, (char_u *)curbuf->b_ffname);
if (eap->addr_count > 0) {
// Repeat finding the file "count" times. This matters when it
// appears several times in the path.
- count = eap->line2;
+ linenr_T count = eap->line2;
while (fname != NULL && --count > 0) {
xfree(fname);
fname = (char *)find_file_in_path(NULL, 0, FNAME_MESS, false, (char_u *)curbuf->b_ffname);
@@ -6210,11 +5910,8 @@ static void ex_edit(exarg_T *eap)
void do_exedit(exarg_T *eap, win_T *old_curwin)
{
int n;
- int need_hide;
- /*
- * ":vi" command ends Ex mode.
- */
+ // ":vi" command ends Ex mode.
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
exmode_active = false;
@@ -6286,12 +5983,12 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
old_curwin == NULL ? curwin : NULL) == FAIL) {
// Editing the file failed. If the window was split, close it.
if (old_curwin != NULL) {
- need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1);
+ bool need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1);
if (!need_hide || buf_hide(curbuf)) {
cleanup_T cs;
// Reset the error/interrupt/exception state here so that
- // aborting() returns FALSE when closing a window.
+ // aborting() returns false when closing a window.
enter_cleanup(&cs);
win_close(curwin, !need_hide && !buf_hide(curbuf), false);
@@ -6320,10 +6017,8 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
}
}
- /*
- * if ":split file" worked, set alternate file name in old window to new
- * file
- */
+ // if ":split file" worked, set alternate file name in old window to new
+ // file
if (old_curwin != NULL
&& *eap->arg != NUL
&& curwin != old_curwin
@@ -6369,9 +6064,7 @@ static void ex_syncbind(exarg_T *eap)
setpcmark();
- /*
- * determine max topline
- */
+ // determine max topline
if (curwin->w_p_scb) {
topline = curwin->w_topline;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -6389,23 +6082,21 @@ static void ex_syncbind(exarg_T *eap)
topline = 1;
}
- /*
- * Set all scrollbind windows to the same topline.
- */
+ // Set all scrollbind windows to the same topline.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
curwin = wp;
if (curwin->w_p_scb) {
curbuf = curwin->w_buffer;
y = topline - curwin->w_topline;
if (y > 0) {
- scrollup(y, TRUE);
+ scrollup(y, true);
} else {
- scrolldown(-y, TRUE);
+ scrolldown(-y, true);
}
curwin->w_scbind_pos = topline;
redraw_later(curwin, VALID);
cursor_correct();
- curwin->w_redr_status = TRUE;
+ curwin->w_redr_status = true;
}
}
curwin = save_curwin;
@@ -6425,9 +6116,7 @@ static void ex_syncbind(exarg_T *eap)
static void ex_read(exarg_T *eap)
{
- int i;
int empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
- linenr_T lnum;
if (eap->usefilter) { // :r!cmd
do_bang(1, eap, false, false, true);
@@ -6435,6 +6124,7 @@ static void ex_read(exarg_T *eap)
if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) {
return;
}
+ int i;
if (*eap->arg == NUL) {
if (check_fname() == FAIL) { // check for no file name
@@ -6457,6 +6147,7 @@ static void ex_read(exarg_T *eap)
if (empty && exmode_active) {
// Delete the empty line that remains. Historically ex does
// this but vi doesn't.
+ linenr_T lnum;
if (eap->line2 == 0) {
lnum = curbuf->b_ml.ml_line_count;
} else {
@@ -6685,17 +6376,14 @@ static void ex_equal(exarg_T *eap)
static void ex_sleep(exarg_T *eap)
{
- int n;
- long len;
-
if (cursor_valid()) {
- n = curwin->w_winrow + curwin->w_wrow - msg_scrolled;
+ int n = curwin->w_winrow + curwin->w_wrow - msg_scrolled;
if (n >= 0) {
ui_cursor_goto(n, curwin->w_wincol + curwin->w_wcol);
}
}
- len = eap->line2;
+ long len = eap->line2;
switch (*eap->arg) {
case 'm':
break;
@@ -6815,7 +6503,7 @@ static void ex_operators(exarg_T *eap)
} else {
oa.op_type = OP_LSHIFT;
}
- op_shift(&oa, FALSE, eap->amount);
+ op_shift(&oa, false, eap->amount);
break;
}
virtual_op = kNone;
@@ -6828,7 +6516,7 @@ static void ex_put(exarg_T *eap)
// ":0put" works like ":1put!".
if (eap->line2 == 0) {
eap->line2 = 1;
- eap->forceit = TRUE;
+ eap->forceit = true;
}
curwin->w_cursor.lnum = eap->line2;
check_cursor_col();
@@ -6846,9 +6534,7 @@ static void ex_copymove(exarg_T *eap)
}
get_flags(eap);
- /*
- * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n'
- */
+ // move or copy lines from 'eap->line1'-'eap->line2' to below line 'n'
if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) {
emsg(_(e_invrange));
return;
@@ -6910,7 +6596,7 @@ static void ex_join(exarg_T *eap)
beep_flush();
return;
}
- ++eap->line2;
+ eap->line2++;
}
do_join((size_t)((ssize_t)eap->line2 - eap->line1 + 1), !eap->forceit, true, true, true);
beginline(BL_WHITE | BL_FIX);
@@ -6939,11 +6625,9 @@ static void ex_at(exarg_T *eap)
exec_from_reg = true;
- /*
- * Execute from the typeahead buffer.
- * Continue until the stuff buffer is empty and all added characters
- * have been consumed.
- */
+ // Execute from the typeahead buffer.
+ // Continue until the stuff buffer is empty and all added characters
+ // have been consumed.
while (!stuff_empty() || typebuf.tb_len > prev_len) {
(void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE);
}
@@ -7032,15 +6716,15 @@ static void ex_later(exarg_T *eap)
count = getdigits_long(&p, false, 0);
switch (*p) {
case 's':
- ++p; sec = true; break;
+ p++; sec = true; break;
case 'm':
- ++p; sec = true; count *= 60; break;
+ p++; sec = true; count *= 60; break;
case 'h':
- ++p; sec = true; count *= 60 * 60; break;
+ p++; sec = true; count *= 60 * 60; break;
case 'd':
- ++p; sec = true; count *= 24 * 60 * 60; break;
+ p++; sec = true; count *= 24 * 60 * 60; break;
case 'f':
- ++p; file = true; break;
+ p++; file = true; break;
}
}
@@ -7055,17 +6739,16 @@ static void ex_later(exarg_T *eap)
/// ":redir": start/stop redirection.
static void ex_redir(exarg_T *eap)
{
- char *mode;
- char *fname;
char *arg = eap->arg;
if (STRICMP(eap->arg, "END") == 0) {
close_redir();
} else {
if (*arg == '>') {
- ++arg;
+ arg++;
+ char *mode;
if (*arg == '>') {
- ++arg;
+ arg++;
mode = "a";
} else {
mode = "w";
@@ -7075,7 +6758,7 @@ static void ex_redir(exarg_T *eap)
close_redir();
// Expand environment variables and "~/".
- fname = expand_env_save(arg);
+ char *fname = expand_env_save(arg);
if (fname == NULL) {
return;
}
@@ -7085,7 +6768,7 @@ static void ex_redir(exarg_T *eap)
} else if (*arg == '@') {
// redirect to a register a-z (resp. A-Z for appending)
close_redir();
- ++arg;
+ arg++;
if (valid_yank_reg(*arg, true) && *arg != '_') {
redir_reg = (char_u)(*arg++);
if (*arg == '>' && arg[1] == '>') { // append
@@ -7114,10 +6797,10 @@ static void ex_redir(exarg_T *eap)
arg += 2;
if (*arg == '>') {
- ++arg;
- append = TRUE;
+ arg++;
+ append = true;
} else {
- append = FALSE;
+ append = false;
}
if (var_redir_start(skipwhite(arg), append) == OK) {
@@ -7146,14 +6829,15 @@ static void ex_redraw(exarg_T *eap)
int p = p_lz;
RedrawingDisabled = 0;
- p_lz = FALSE;
+ p_lz = false;
validate_cursor();
update_topline(curwin);
if (eap->forceit) {
redraw_all_later(NOT_VALID);
+ redraw_cmdline = true;
}
update_screen(eap->forceit ? NOT_VALID
- : VIsual_active ? INVERTED : 0);
+ : VIsual_active ? INVERTED : 0);
if (need_maketitle) {
maketitle();
}
@@ -7180,7 +6864,7 @@ static void ex_redrawstatus(exarg_T *eap)
int p = p_lz;
RedrawingDisabled = 0;
- p_lz = FALSE;
+ p_lz = false;
if (eap->forceit) {
status_redraw_all();
} else {
@@ -7245,8 +6929,6 @@ int vim_mkdir_emsg(const char *const name, const int prot)
/// @return file descriptor, or NULL on failure.
FILE *open_exfile(char_u *fname, int forceit, char *mode)
{
- FILE *fd;
-
#ifdef UNIX
// with Unix it is possible to open a directory
if (os_isdir(fname)) {
@@ -7259,6 +6941,7 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode)
return NULL;
}
+ FILE *fd;
if ((fd = os_fopen((char *)fname, mode)) == NULL) {
semsg(_("E190: Cannot open \"%s\" for writing"), fname);
}
@@ -7269,14 +6952,12 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode)
/// ":mark" and ":k".
static void ex_mark(exarg_T *eap)
{
- pos_T pos;
-
if (*eap->arg == NUL) { // No argument?
emsg(_(e_argreq));
} else if (eap->arg[1] != NUL) { // more than one character?
semsg(_(e_trailing_arg), eap->arg);
} else {
- pos = curwin->w_cursor; // save curwin->w_cursor
+ pos_T pos = curwin->w_cursor; // save curwin->w_cursor
curwin->w_cursor.lnum = eap->line2;
beginline(BL_WHITE | BL_FIX);
if (setmark(*eap->arg) == FAIL) { // set mark
@@ -7357,10 +7038,7 @@ static void ex_normal(exarg_T *eap)
emsg("Can't re-enter normal mode from terminal mode");
return;
}
- save_state_T save_state;
char *arg = NULL;
- int l;
- char *p;
if (ex_normal_lock > 0) {
emsg(_(e_secure));
@@ -7378,6 +7056,8 @@ static void ex_normal(exarg_T *eap)
int len = 0;
// Count the number of characters to be escaped.
+ int l;
+ char *p;
for (p = eap->arg; *p != NUL; p++) {
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
if (*++p == (char)K_SPECIAL) { // trailbyte K_SPECIAL
@@ -7403,6 +7083,7 @@ static void ex_normal(exarg_T *eap)
}
ex_normal_busy++;
+ save_state_T save_state;
if (save_current_state(&save_state)) {
// Repeat the :normal command for each line in the range. When no
// range given, execute it just once, without positioning the cursor
@@ -7521,8 +7202,6 @@ static void ex_psearch(exarg_T *eap)
static void ex_findpat(exarg_T *eap)
{
bool whole = true;
- long n;
- char *p;
int action;
switch (cmdnames[eap->cmdidx].cmd_name[2]) {
@@ -7544,7 +7223,7 @@ static void ex_findpat(exarg_T *eap)
break;
}
- n = 1;
+ long n = 1;
if (ascii_isdigit(*eap->arg)) { // get count
n = getdigits_long(&eap->arg, false, 0);
eap->arg = skipwhite(eap->arg);
@@ -7552,7 +7231,7 @@ static void ex_findpat(exarg_T *eap)
if (*eap->arg == '/') { // Match regexp, not just whole words
whole = false;
eap->arg++;
- p = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL);
+ char *p = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL);
if (*p) {
*p++ = NUL;
p = skipwhite(p);
@@ -7686,7 +7365,6 @@ enum {
ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
FUNC_ATTR_NONNULL_ALL
{
- size_t len;
static char *(spec_str[]) = {
[SPEC_PERC] = "%",
[SPEC_HASH] = "#",
@@ -7705,8 +7383,8 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
// [SPEC_CLIENT] = "<client>",
};
- for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) {
- len = STRLEN(spec_str[i]);
+ for (size_t i = 0; i < ARRAY_SIZE(spec_str); i++) {
+ size_t len = STRLEN(spec_str[i]);
if (STRNCMP(src, spec_str[i], len) == 0) {
*usedlen = len;
assert(i <= SSIZE_MAX);
@@ -7725,6 +7403,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
/// '<cexpr>' to C-expression under the cursor
/// '<cfile>' to path name under the cursor
/// '<sfile>' to sourced file name
+/// '<stack>' to call stack
/// '<slnum>' to sourced file line number
/// '<afile>' to file name for autocommand
/// '<abuf>' to buffer number for autocommand
@@ -7746,12 +7425,9 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg,
int *escaped)
{
- int i;
- char *s;
char *result;
char *resultbuf = NULL;
size_t resultlen;
- buf_T *buf;
int valid = VALID_HEAD | VALID_PATH; // Assume valid result.
bool tilde_file = false;
bool skip_mod = false;
@@ -7759,40 +7435,34 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
*errormsg = NULL;
if (escaped != NULL) {
- *escaped = FALSE;
+ *escaped = false;
}
- /*
- * Check if there is something to do.
- */
+ // Check if there is something to do.
ssize_t spec_idx = find_cmdline_var(src, usedlen);
if (spec_idx < 0) { // no match
*usedlen = 1;
return NULL;
}
- /*
- * Skip when preceded with a backslash "\%" and "\#".
- * Note: In "\\%" the % is also not recognized!
- */
+ // Skip when preceded with a backslash "\%" and "\#".
+ // Note: In "\\%" the % is also not recognized!
if (src > srcstart && src[-1] == '\\') {
*usedlen = 0;
STRMOVE(src - 1, src); // remove backslash
return NULL;
}
- /*
- * word or WORD under cursor
- */
+ // word or WORD under cursor
if (spec_idx == SPEC_CWORD
|| spec_idx == SPEC_CCWORD
|| spec_idx == SPEC_CEXPR) {
- resultlen = find_ident_under_cursor((char_u **)&result,
+ resultlen = find_ident_under_cursor(&result,
spec_idx == SPEC_CWORD
- ? (FIND_IDENT | FIND_STRING)
- : (spec_idx == SPEC_CEXPR
- ? (FIND_IDENT | FIND_STRING | FIND_EVAL)
- : FIND_STRING));
+ ? (FIND_IDENT | FIND_STRING)
+ : (spec_idx == SPEC_CEXPR
+ ? (FIND_IDENT | FIND_STRING | FIND_EVAL)
+ : FIND_STRING));
if (resultlen == 0) {
*errormsg = "";
return NULL;
@@ -7822,16 +7492,16 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
resultbuf = result;
*usedlen = 2;
if (escaped != NULL) {
- *escaped = TRUE;
+ *escaped = true;
}
skip_mod = true;
break;
}
- s = (char *)src + 1;
+ char *s = (char *)src + 1;
if (*s == '<') { // "#<99" uses v:oldfiles.
s++;
}
- i = getdigits_int(&s, false, 0);
+ int i = getdigits_int(&s, false, 0);
if ((char_u *)s == src + 2 && src[1] == '-') {
// just a minus sign, don't skip over it
s--;
@@ -7853,7 +7523,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
if (i == 0 && src[1] == '<' && *usedlen > 1) {
*usedlen = 1;
}
- buf = buflist_findnr(i);
+ buf_T *buf = buflist_findnr(i);
if (buf == NULL) {
*errormsg = _("E194: No alternate file name to substitute for '#'");
return NULL;
@@ -7918,29 +7588,33 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
break;
case SPEC_SFILE: // file name for ":so" command
- result = sourcing_name;
+ case SPEC_STACK: // call stack
+ result = estack_sfile(spec_idx == SPEC_SFILE ? ESTACK_SFILE : ESTACK_STACK);
if (result == NULL) {
- *errormsg = _("E498: no :source file name to substitute for \"<sfile>\"");
+ *errormsg = spec_idx == SPEC_SFILE
+ ? _("E498: no :source file name to substitute for \"<sfile>\"")
+ : _("E489: no call stack to substitute for \"<stack>\"");
return NULL;
}
+ resultbuf = result; // remember allocated string
break;
case SPEC_SLNUM: // line in file for ":so" command
- if (sourcing_name == NULL || sourcing_lnum == 0) {
+ if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) {
*errormsg = _("E842: no line number to use for \"<slnum>\"");
return NULL;
}
- snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, sourcing_lnum);
+ snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM);
result = strbuf;
break;
case SPEC_SFLNUM: // line in script file
- if (current_sctx.sc_lnum + sourcing_lnum == 0) {
+ if (current_sctx.sc_lnum + SOURCING_LNUM == 0) {
*errormsg = _("E961: no line number to use for \"<sflnum>\"");
return NULL;
}
snprintf((char *)strbuf, sizeof(strbuf), "%" PRIdLINENR,
- current_sctx.sc_lnum + sourcing_lnum);
+ current_sctx.sc_lnum + SOURCING_LNUM);
result = strbuf;
break;
@@ -7966,6 +7640,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
// Remove the file name extension.
if (src[*usedlen] == '<') {
(*usedlen)++;
+ char *s;
if ((s = (char *)STRRCHR(result, '.')) != NULL
&& s >= path_tail(result)) {
resultlen = (size_t)(s - result);
@@ -7995,88 +7670,21 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
return (char_u *)result;
}
-/// Concatenate all files in the argument list, separated by spaces, and return
-/// it in one allocated string.
-/// Spaces and backslashes in the file names are escaped with a backslash.
-static char *arg_all(void)
-{
- int len;
- int idx;
- char *retval = NULL;
- char *p;
-
- /*
- * Do this loop two times:
- * first time: compute the total length
- * second time: concatenate the names
- */
- for (;;) {
- len = 0;
- for (idx = 0; idx < ARGCOUNT; idx++) {
- p = alist_name(&ARGLIST[idx]);
- if (p == NULL) {
- continue;
- }
- if (len > 0) {
- // insert a space in between names
- if (retval != NULL) {
- retval[len] = ' ';
- }
- ++len;
- }
- for (; *p != NUL; p++) {
- if (*p == ' '
-#ifndef BACKSLASH_IN_FILENAME
- || *p == '\\'
-#endif
- || *p == '`') {
- // insert a backslash
- if (retval != NULL) {
- retval[len] = '\\';
- }
- len++;
- }
- if (retval != NULL) {
- retval[len] = *p;
- }
- len++;
- }
- }
-
- // second time: break here
- if (retval != NULL) {
- retval[len] = NUL;
- break;
- }
-
- // allocate memory
- retval = xmalloc((size_t)len + 1);
- }
-
- return retval;
-}
-
/// Expand the <sfile> string in "arg".
///
/// @return an allocated string, or NULL for any error.
char *expand_sfile(char *arg)
{
- char *errormsg;
- size_t len;
- char *result;
- char *newres;
- char *repl;
- size_t srclen;
- char *p;
-
- result = xstrdup(arg);
+ char *result = xstrdup(arg);
- for (p = result; *p;) {
+ for (char *p = result; *p;) {
if (STRNCMP(p, "<sfile>", 7) != 0) {
- ++p;
+ p++;
} else {
// replace "<sfile>" with the sourced file name, and do ":" stuff
- repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL);
+ size_t srclen;
+ char *errormsg;
+ char *repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL);
if (errormsg != NULL) {
if (*errormsg) {
emsg(errormsg);
@@ -8088,8 +7696,8 @@ char *expand_sfile(char *arg)
p += srclen;
continue;
}
- len = STRLEN(result) - srclen + STRLEN(repl) + 1;
- newres = xmalloc(len);
+ size_t len = STRLEN(result) - srclen + STRLEN(repl) + 1;
+ char *newres = xmalloc(len);
memmove(newres, result, (size_t)(p - result));
STRCPY(newres + (p - result), repl);
len = STRLEN(newres);
@@ -8107,9 +7715,7 @@ char *expand_sfile(char *arg)
/// ":rshada" and ":wshada".
static void ex_shada(exarg_T *eap)
{
- char *save_shada;
-
- save_shada = (char *)p_shada;
+ char *save_shada = (char *)p_shada;
if (*p_shada == NUL) {
p_shada = (char_u *)"'100";
}
@@ -8193,10 +7799,6 @@ static TriState filetype_indent = kNone;
/// indent off: load indoff.vim
static void ex_filetype(exarg_T *eap)
{
- char *arg = eap->arg;
- bool plugin = false;
- bool indent = false;
-
if (*eap->arg == NUL) {
// Print current status.
smsg("filetype detection:%s plugin:%s indent:%s",
@@ -8206,6 +7808,10 @@ static void ex_filetype(exarg_T *eap)
return;
}
+ char *arg = eap->arg;
+ bool plugin = false;
+ bool indent = false;
+
// Accept "plugin" and "indent" in any order.
for (;;) {
if (STRNCMP(arg, "plugin", 6) == 0) {
@@ -8342,7 +7948,7 @@ static void ex_foldopen(exarg_T *eap)
static void ex_folddo(exarg_T *eap)
{
// First set the marks for all lines closed/open.
- for (linenr_T lnum = eap->line1; lnum <= eap->line2; ++lnum) {
+ for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) {
ml_setmarked(lnum);
}
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index f67c8a6720..69d509abb7 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -16,12 +16,12 @@
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/vim.h"
@@ -151,8 +151,8 @@ int aborted_in_try(void)
bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
FUNC_ATTR_NONNULL_ALL
{
- struct msglist *elem;
- struct msglist **plist;
+ msglist_T *elem;
+ msglist_T **plist;
/*
* Do nothing when displaying the interrupt message or reporting an
@@ -254,7 +254,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
plist = &(*plist)->next;
}
- elem = xmalloc(sizeof(struct msglist));
+ elem = xmalloc(sizeof(msglist_T));
elem->msg = xstrdup(mesg);
elem->next = NULL;
elem->throw_msg = NULL;
@@ -275,20 +275,26 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
(*msg_list)->throw_msg = tmsg;
}
}
+
+ // Get the source name and lnum now, it may change before
+ // reaching do_errthrow().
+ elem->sfile = estack_sfile(ESTACK_NONE);
+ elem->slnum = SOURCING_LNUM;
}
return true;
}
}
/// Free a "msg_list" and the messages it contains.
-static void free_msglist(struct msglist *l)
+static void free_msglist(msglist_T *l)
{
- struct msglist *messages, *next;
+ msglist_T *messages, *next;
messages = l;
while (messages != NULL) {
next = messages->next;
xfree(messages->msg);
+ xfree(messages->sfile);
xfree(messages);
messages = next;
}
@@ -389,7 +395,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int *
if (type == ET_ERROR) {
*should_free = true;
- mesg = ((struct msglist *)value)->throw_msg;
+ mesg = ((msglist_T *)value)->throw_msg;
if (cmdname != NULL && *cmdname != NUL) {
size_t cmdlen = STRLEN(cmdname);
ret = xstrnsave("Vim(", 4 + cmdlen + 2 + STRLEN(mesg));
@@ -469,7 +475,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
if (type == ET_ERROR) {
// Store the original message and prefix the exception value with
// "Vim:" or, if a command name is given, "Vim(cmdname):".
- excp->messages = (struct msglist *)value;
+ excp->messages = (msglist_T *)value;
}
excp->value = get_exception_string(value, type, cmdname, &should_free);
@@ -478,8 +484,18 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
}
excp->type = type;
- excp->throw_name = xstrdup(sourcing_name == NULL ? "" : sourcing_name);
- excp->throw_lnum = sourcing_lnum;
+ if (type == ET_ERROR && ((msglist_T *)value)->sfile != NULL) {
+ msglist_T *entry = (msglist_T *)value;
+ excp->throw_name = entry->sfile;
+ entry->sfile = NULL;
+ excp->throw_lnum = entry->slnum;
+ } else {
+ excp->throw_name = estack_sfile(ESTACK_NONE);
+ if (excp->throw_name == NULL) {
+ excp->throw_name = xstrdup("");
+ }
+ excp->throw_lnum = SOURCING_LNUM;
+ }
if (p_verbose >= 13 || debug_break_level > 0) {
int save_msg_silent = msg_silent;
@@ -489,7 +505,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
} else {
verbose_enter();
}
- ++no_wait_return;
+ no_wait_return++;
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = TRUE; // always scroll up, don't overwrite
}
@@ -499,7 +515,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
if (debug_break_level > 0 || *p_vfile == NUL) {
cmdline_row = msg_row;
}
- --no_wait_return;
+ no_wait_return--;
if (debug_break_level > 0) {
msg_silent = save_msg_silent;
} else {
@@ -542,7 +558,7 @@ static void discard_exception(except_T *excp, bool was_finished)
} else {
verbose_enter();
}
- ++no_wait_return;
+ no_wait_return++;
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = TRUE; // always scroll up, don't overwrite
}
@@ -610,7 +626,7 @@ static void catch_exception(except_T *excp)
} else {
verbose_enter();
}
- ++no_wait_return;
+ no_wait_return++;
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = TRUE; // always scroll up, don't overwrite
}
@@ -620,7 +636,7 @@ static void catch_exception(except_T *excp)
if (debug_break_level > 0 || *p_vfile == NUL) {
cmdline_row = msg_row;
}
- --no_wait_return;
+ no_wait_return--;
if (debug_break_level > 0) {
msg_silent = save_msg_silent;
} else {
@@ -732,12 +748,12 @@ static void report_pending(int action, int pending, void *value)
if (debug_break_level > 0) {
msg_silent = FALSE; // display messages
}
- ++no_wait_return;
- msg_scroll = TRUE; // always scroll up, don't overwrite
+ no_wait_return++;
+ msg_scroll = true; // always scroll up, don't overwrite
smsg(mesg, s);
msg_puts("\n"); // don't overwrite this either
cmdline_row = msg_row;
- --no_wait_return;
+ no_wait_return--;
if (debug_break_level > 0) {
msg_silent = save_msg_silent;
}
@@ -2038,7 +2054,7 @@ int has_loop_cmd(char *p)
// skip modifiers, white space and ':'
for (;;) {
while (*p == ' ' || *p == '\t' || *p == ':') {
- ++p;
+ p++;
}
len = modifier_len(p);
if (len == 0) {
diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h
index 235875fb91..9e3ac5e9c1 100644
--- a/src/nvim/ex_eval.h
+++ b/src/nvim/ex_eval.h
@@ -2,81 +2,7 @@
#define NVIM_EX_EVAL_H
#include "nvim/ex_cmds_defs.h" // for exarg_T
-#include "nvim/pos.h" // for linenr_T
-
-/* There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if"
- * was used. */
-#define CSF_TRUE 0x0001 // condition was TRUE
-#define CSF_ACTIVE 0x0002 // current state is active
-#define CSF_ELSE 0x0004 // ":else" has been passed
-#define CSF_WHILE 0x0008 // is a ":while"
-#define CSF_FOR 0x0010 // is a ":for"
-
-#define CSF_TRY 0x0100 // is a ":try"
-#define CSF_FINALLY 0x0200 // ":finally" has been passed
-#define CSF_THROWN 0x0800 // exception thrown to this try conditional
-#define CSF_CAUGHT 0x1000 // exception caught by this try conditional
-#define CSF_FINISHED 0x2000 // CSF_CAUGHT was handled by finish_exception()
-#define CSF_SILENT 0x4000 // "emsg_silent" reset by ":try"
-// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset
-// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set.
-
-/*
- * What's pending for being reactivated at the ":endtry" of this try
- * conditional:
- */
-#define CSTP_NONE 0 // nothing pending in ":finally" clause
-#define CSTP_ERROR 1 // an error is pending
-#define CSTP_INTERRUPT 2 // an interrupt is pending
-#define CSTP_THROW 4 // a throw is pending
-#define CSTP_BREAK 8 // ":break" is pending
-#define CSTP_CONTINUE 16 // ":continue" is pending
-#define CSTP_RETURN 24 // ":return" is pending
-#define CSTP_FINISH 32 // ":finish" is pending
-
-/*
- * A list of error messages that can be converted to an exception. "throw_msg"
- * is only set in the first element of the list. Usually, it points to the
- * original message stored in that element, but sometimes it points to a later
- * message in the list. See cause_errthrow() below.
- */
-struct msglist {
- char *msg; // original message
- char *throw_msg; // msg to throw: usually original one
- struct msglist *next; // next of several messages in a row
-};
-
-// The exception types.
-typedef enum {
- ET_USER, // exception caused by ":throw" command
- ET_ERROR, // error exception
- ET_INTERRUPT, // interrupt exception triggered by Ctrl-C
-} except_type_T;
-
-/*
- * Structure describing an exception.
- * (don't use "struct exception", it's used by the math library).
- */
-typedef struct vim_exception except_T;
-struct vim_exception {
- except_type_T type; // exception type
- char *value; // exception value
- struct msglist *messages; // message(s) causing error exception
- char *throw_name; // name of the throw point
- linenr_T throw_lnum; // line number of the throw point
- except_T *caught; // next exception on the caught stack
-};
-
-/*
- * Structure to save the error/interrupt/exception state between calls to
- * enter_cleanup() and leave_cleanup(). Must be allocated as an automatic
- * variable by the (common) caller of these functions.
- */
-typedef struct cleanup_stuff cleanup_T;
-struct cleanup_stuff {
- int pending; // error/interrupt/exception state
- except_T *exception; // exception value
-};
+#include "nvim/ex_eval_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_eval.h.generated.h"
diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h
new file mode 100644
index 0000000000..9da0c9ad12
--- /dev/null
+++ b/src/nvim/ex_eval_defs.h
@@ -0,0 +1,79 @@
+#ifndef NVIM_EX_EVAL_DEFS_H
+#define NVIM_EX_EVAL_DEFS_H
+
+#include "nvim/pos.h" // for linenr_T
+
+/// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if"
+/// was used.
+enum {
+ CSF_TRUE = 0x0001, ///< condition was TRUE
+ CSF_ACTIVE = 0x0002, ///< current state is active
+ CSF_ELSE = 0x0004, ///< ":else" has been passed
+ CSF_WHILE = 0x0008, ///< is a ":while"
+ CSF_FOR = 0x0010, ///< is a ":for"
+
+ CSF_TRY = 0x0100, ///< is a ":try"
+ CSF_FINALLY = 0x0200, ///< ":finally" has been passed
+ CSF_THROWN = 0x0800, ///< exception thrown to this try conditional
+ CSF_CAUGHT = 0x1000, ///< exception caught by this try conditional
+ CSF_FINISHED = 0x2000, ///< CSF_CAUGHT was handled by finish_exception()
+ CSF_SILENT = 0x4000, ///< "emsg_silent" reset by ":try"
+};
+// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset
+// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set.
+
+/// What's pending for being reactivated at the ":endtry" of this try
+/// conditional:
+enum {
+ CSTP_NONE = 0, ///< nothing pending in ":finally" clause
+ CSTP_ERROR = 1, ///< an error is pending
+ CSTP_INTERRUPT = 2, ///< an interrupt is pending
+ CSTP_THROW = 4, ///< a throw is pending
+ CSTP_BREAK = 8, ///< ":break" is pending
+ CSTP_CONTINUE = 16, ///< ":continue" is pending
+ CSTP_RETURN = 24, ///< ":return" is pending
+ CSTP_FINISH = 32, ///< ":finish" is pending
+};
+
+/// A list of error messages that can be converted to an exception. "throw_msg"
+/// is only set in the first element of the list. Usually, it points to the
+/// original message stored in that element, but sometimes it points to a later
+/// message in the list. See cause_errthrow().
+typedef struct msglist msglist_T;
+struct msglist {
+ char *msg; ///< original message, allocated
+ char *throw_msg; ///< msg to throw: usually original one
+ char *sfile; ///< value from estack_sfile(), allocated
+ linenr_T slnum; ///< line number for "sfile"
+ msglist_T *next; ///< next of several messages in a row
+};
+
+/// The exception types.
+typedef enum {
+ ET_USER, ///< exception caused by ":throw" command
+ ET_ERROR, ///< error exception
+ ET_INTERRUPT, ///< interrupt exception triggered by Ctrl-C
+} except_type_T;
+
+/// Structure describing an exception.
+/// (don't use "struct exception", it's used by the math library).
+typedef struct vim_exception except_T;
+struct vim_exception {
+ except_type_T type; ///< exception type
+ char *value; ///< exception value
+ msglist_T *messages; ///< message(s) causing error exception
+ char *throw_name; ///< name of the throw point
+ linenr_T throw_lnum; ///< line number of the throw point
+ except_T *caught; ///< next exception on the caught stack
+};
+
+/// Structure to save the error/interrupt/exception state between calls to
+/// enter_cleanup() and leave_cleanup(). Must be allocated as an automatic
+/// variable by the (common) caller of these functions.
+typedef struct cleanup_stuff cleanup_T;
+struct cleanup_stuff {
+ int pending; ///< error/interrupt/exception state
+ except_T *exception; ///< exception value
+};
+
+#endif // NVIM_EX_EVAL_DEFS_H
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 07a0e68884..c15d85967d 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -15,13 +15,16 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/arabic.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/funcs.h"
@@ -37,6 +40,8 @@
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
+#include "nvim/grid.h"
+#include "nvim/help.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
@@ -63,9 +68,9 @@
#include "nvim/os/time.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sign.h"
#include "nvim/state.h"
@@ -143,7 +148,7 @@ struct cmdline_info {
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
-// Struct to store the viewstate during 'incsearch' highlighting.
+// Struct to store the viewstate during 'incsearch' highlighting and 'inccommand' preview.
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
@@ -195,6 +200,32 @@ typedef struct command_line_state {
long *b_im_ptr;
} CommandLineState;
+typedef struct cmdpreview_win_info {
+ win_T *win;
+ pos_T save_w_cursor;
+ viewstate_T save_viewstate;
+ int save_w_p_cul;
+ int save_w_p_cuc;
+} CpWinInfo;
+
+typedef struct cmdpreview_buf_info {
+ buf_T *buf;
+ time_t save_b_u_time_cur;
+ long save_b_u_seq_cur;
+ u_header_T *save_b_u_newhead;
+ long save_b_p_ul;
+ int save_b_changed;
+ varnumber_T save_changedtick;
+} CpBufInfo;
+
+typedef struct cmdpreview_info {
+ kvec_t(CpWinInfo) win_info;
+ kvec_t(CpBufInfo) buf_info;
+ bool save_hls;
+ cmdmod_T save_cmdmod;
+ garray_T save_view;
+} CpInfo;
+
typedef struct cmdline_info CmdlineInfo;
/// The current cmdline_info. It is initialized in getcmdline() and after that
@@ -214,12 +245,6 @@ static Array cmdline_block = ARRAY_DICT_INIT;
*/
typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *);
-static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
-static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; // lastused entry
-static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
-// identifying (unique) number of newest history entry
-static int hislen = 0; // actual length of history tables
-
/// Flag for command_line_handle_key to ignore <C-c>
///
/// Used if it was received while processing highlight function in order for
@@ -243,26 +268,26 @@ static long cmdpreview_ns = 0;
static int cmd_hkmap = 0; // Hebrew mapping during command line
-static void save_viewstate(viewstate_T *vs)
+static void save_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
- vs->vs_curswant = curwin->w_curswant;
- vs->vs_leftcol = curwin->w_leftcol;
- vs->vs_topline = curwin->w_topline;
- vs->vs_topfill = curwin->w_topfill;
- vs->vs_botline = curwin->w_botline;
- vs->vs_empty_rows = curwin->w_empty_rows;
+ vs->vs_curswant = wp->w_curswant;
+ vs->vs_leftcol = wp->w_leftcol;
+ vs->vs_topline = wp->w_topline;
+ vs->vs_topfill = wp->w_topfill;
+ vs->vs_botline = wp->w_botline;
+ vs->vs_empty_rows = wp->w_empty_rows;
}
-static void restore_viewstate(viewstate_T *vs)
+static void restore_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
- curwin->w_curswant = vs->vs_curswant;
- curwin->w_leftcol = vs->vs_leftcol;
- curwin->w_topline = vs->vs_topline;
- curwin->w_topfill = vs->vs_topfill;
- curwin->w_botline = vs->vs_botline;
- curwin->w_empty_rows = vs->vs_empty_rows;
+ wp->w_curswant = vs->vs_curswant;
+ wp->w_leftcol = vs->vs_leftcol;
+ wp->w_topline = vs->vs_topline;
+ wp->w_topfill = vs->vs_topfill;
+ wp->w_botline = vs->vs_botline;
+ wp->w_empty_rows = vs->vs_empty_rows;
}
static void init_incsearch_state(incsearch_state_T *s)
@@ -274,8 +299,8 @@ static void init_incsearch_state(incsearch_state_T *s)
clearpos(&s->match_end);
s->save_cursor = curwin->w_cursor; // may be restored later
s->search_start = curwin->w_cursor;
- save_viewstate(&s->init_viewstate);
- save_viewstate(&s->old_viewstate);
+ save_viewstate(curwin, &s->init_viewstate);
+ save_viewstate(curwin, &s->old_viewstate);
}
/// Completion for |:checkhealth| command.
@@ -316,7 +341,6 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
int delim;
char *end;
char *dummy;
- exarg_T ea;
pos_T save_cursor;
bool use_last_pat;
bool retval = false;
@@ -341,11 +365,12 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
}
emsg_off++;
- memset(&ea, 0, sizeof(ea));
- ea.line1 = 1;
- ea.line2 = 1;
- ea.cmd = (char *)ccline.cmdbuff;
- ea.addr_type = ADDR_LINES;
+ exarg_T ea = {
+ .line1 = 1,
+ .line2 = 1,
+ .cmd = (char *)ccline.cmdbuff,
+ .addr_type = ADDR_LINES,
+ };
cmdmod_T dummy_cmdmod;
parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true);
@@ -458,7 +483,6 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
{
pos_T end_pos;
proftime_T tm;
- searchit_arg_T sia;
int skiplen, patlen;
char_u next_char;
char_u use_last_pat;
@@ -521,8 +545,9 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
search_flags += SEARCH_START;
}
ccline.cmdbuff[skiplen + patlen] = NUL;
- memset(&sia, 0, sizeof(sia));
- sia.sa_tm = &tm;
+ searchit_arg_T sia = {
+ .sa_tm = &tm,
+ };
found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim,
ccline.cmdbuff + skiplen, count,
search_flags, &sia);
@@ -555,7 +580,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
- restore_viewstate(&s->old_viewstate);
+ restore_viewstate(curwin, &s->old_viewstate);
changed_cline_bef_curs();
update_topline(curwin);
@@ -665,7 +690,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
}
curwin->w_cursor = s->search_start; // -V519
}
- restore_viewstate(&s->old_viewstate);
+ restore_viewstate(curwin, &s->old_viewstate);
highlight_match = false;
// by default search all lines
@@ -736,7 +761,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init
save_cmdline(&save_ccline);
did_save_ccline = true;
} else if (init_ccline) {
- memset(&ccline, 0, sizeof(struct cmdline_info));
+ CLEAR_FIELD(ccline);
}
if (s->firstc == -1) {
@@ -869,7 +894,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init
may_trigger_modechanged();
init_history();
- s->hiscnt = hislen; // set hiscnt to impossible history value
+ s->hiscnt = get_hislen(); // set hiscnt to impossible history value
s->histype = hist_char2type(s->firstc);
do_digraph(-1); // init digraph typeahead
@@ -1664,7 +1689,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
update_topline(curwin);
validate_cursor();
highlight_match = true;
- save_viewstate(&s->old_viewstate);
+ save_viewstate(curwin, &s->old_viewstate);
update_screen(NOT_VALID);
highlight_match = false;
redrawcmdline();
@@ -1682,12 +1707,12 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
for (;;) {
// one step backwards
if (!next_match) {
- if (s->hiscnt == hislen) {
+ if (s->hiscnt == get_hislen()) {
// first time
- s->hiscnt = hisidx[s->histype];
- } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
- s->hiscnt = hislen - 1;
- } else if (s->hiscnt != hisidx[s->histype] + 1) {
+ s->hiscnt = *get_hisidx(s->histype);
+ } else if (s->hiscnt == 0 && *get_hisidx(s->histype) != get_hislen() - 1) {
+ s->hiscnt = get_hislen() - 1;
+ } else if (s->hiscnt != *get_hisidx(s->histype) + 1) {
s->hiscnt--;
} else {
// at top of list
@@ -1696,17 +1721,17 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
}
} else { // one step forwards
// on last entry, clear the line
- if (s->hiscnt == hisidx[s->histype]) {
- s->hiscnt = hislen;
+ if (s->hiscnt == *get_hisidx(s->histype)) {
+ s->hiscnt = get_hislen();
break;
}
// not on a history line, nothing to do
- if (s->hiscnt == hislen) {
+ if (s->hiscnt == get_hislen()) {
break;
}
- if (s->hiscnt == hislen - 1) {
+ if (s->hiscnt == get_hislen() - 1) {
// wrap around
s->hiscnt = 0;
} else {
@@ -1714,14 +1739,14 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
}
}
- if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
+ if (s->hiscnt < 0 || get_histentry(s->histype)[s->hiscnt].hisstr == NULL) {
s->hiscnt = s->save_hiscnt;
break;
}
if ((s->c != K_UP && s->c != K_DOWN)
|| s->hiscnt == s->save_hiscnt
- || STRNCMP(history[s->histype][s->hiscnt].hisstr,
+ || STRNCMP(get_histentry(s->histype)[s->hiscnt].hisstr,
s->lookfor, (size_t)j) == 0) {
break;
}
@@ -2103,7 +2128,7 @@ static int command_line_handle_key(CommandLineState *s)
case K_KPAGEUP:
case K_PAGEDOWN:
case K_KPAGEDOWN:
- if (s->histype == HIST_INVALID || hislen == 0 || s->firstc == NUL) {
+ if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) {
// no history
return command_line_not_changed(s);
}
@@ -2128,10 +2153,10 @@ static int command_line_handle_key(CommandLineState *s)
XFREE_CLEAR(ccline.cmdbuff);
s->xpc.xp_context = EXPAND_NOTHING;
- if (s->hiscnt == hislen) {
+ if (s->hiscnt == get_hislen()) {
p = s->lookfor; // back to the old one
} else {
- p = history[s->histype][s->hiscnt].hisstr;
+ p = get_histentry(s->histype)[s->hiscnt].hisstr;
}
if (s->histype == HIST_SEARCH
@@ -2159,14 +2184,14 @@ static int command_line_handle_key(CommandLineState *s)
if (i > 0) {
ccline.cmdbuff[len] = '\\';
}
- ++len;
+ len++;
}
if (i > 0) {
ccline.cmdbuff[len] = p[j];
}
}
- ++len;
+ len++;
}
if (i == 0) {
@@ -2396,6 +2421,126 @@ static void cmdpreview_close_win(void)
}
}
+/// Save current state and prepare windows and buffers for command preview.
+static void cmdpreview_prepare(CpInfo *cpinfo)
+{
+ kv_init(cpinfo->buf_info);
+ kv_init(cpinfo->win_info);
+
+ FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
+ buf_T *buf = win->w_buffer;
+
+ // Don't save state of command preview buffer or preview window.
+ if (buf->handle == cmdpreview_bufnr) {
+ continue;
+ }
+
+ CpBufInfo cp_bufinfo;
+ cp_bufinfo.buf = buf;
+
+ cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur;
+ cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur;
+ cp_bufinfo.save_b_u_newhead = buf->b_u_newhead;
+ cp_bufinfo.save_b_p_ul = buf->b_p_ul;
+ cp_bufinfo.save_b_changed = buf->b_changed;
+ cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
+
+ kv_push(cpinfo->buf_info, cp_bufinfo);
+
+ buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
+
+ CpWinInfo cp_wininfo;
+ cp_wininfo.win = win;
+
+ // Save window cursor position and viewstate
+ cp_wininfo.save_w_cursor = win->w_cursor;
+ save_viewstate(win, &cp_wininfo.save_viewstate);
+
+ // Save 'cursorline' and 'cursorcolumn'
+ cp_wininfo.save_w_p_cul = win->w_p_cul;
+ cp_wininfo.save_w_p_cuc = win->w_p_cuc;
+
+ kv_push(cpinfo->win_info, cp_wininfo);
+
+ win->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
+ win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
+ }
+
+ cpinfo->save_hls = p_hls;
+ cpinfo->save_cmdmod = cmdmod;
+ win_size_save(&cpinfo->save_view);
+ save_search_patterns();
+
+ p_hls = false; // Don't show search highlighting during live substitution
+ cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers
+ cmdmod.cmod_tab = 0; // Disable :tab modifier
+ cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer
+}
+
+// Restore the state of buffers and windows before command preview.
+static void cmdpreview_restore_state(CpInfo *cpinfo)
+{
+ for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
+ CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
+ buf_T *buf = cp_bufinfo.buf;
+
+ buf->b_changed = cp_bufinfo.save_b_changed;
+
+ if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) {
+ int count = 0;
+
+ // Calculate how many undo steps are necessary to restore earlier state.
+ for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead;
+ uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur;
+ uhp = uhp->uh_next.ptr, ++count) {}
+
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, buf);
+ // Undo invisibly. This also moves the cursor!
+ if (!u_undo_and_forget(count)) {
+ abort();
+ }
+ aucmd_restbuf(&aco);
+
+ // Restore newhead. It is meaningless when curhead is valid, but we must
+ // restore it so that undotree() is identical before/after the preview.
+ buf->b_u_newhead = cp_bufinfo.save_b_u_newhead;
+ buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur;
+ }
+ if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
+ buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
+ }
+
+ buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels'
+
+ // Clear preview highlights.
+ extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
+ }
+ for (size_t i = 0; i < cpinfo->win_info.size; i++) {
+ CpWinInfo cp_wininfo = cpinfo->win_info.items[i];
+ win_T *win = cp_wininfo.win;
+
+ // Restore window cursor position and viewstate
+ win->w_cursor = cp_wininfo.save_w_cursor;
+ restore_viewstate(win, &cp_wininfo.save_viewstate);
+
+ // Restore 'cursorline' and 'cursorcolumn'
+ win->w_p_cul = cp_wininfo.save_w_p_cul;
+ win->w_p_cuc = cp_wininfo.save_w_p_cuc;
+
+ update_topline(win);
+ }
+
+ cmdmod = cpinfo->save_cmdmod; // Restore cmdmod
+ p_hls = cpinfo->save_hls; // Restore 'hlsearch'
+ restore_search_patterns(); // Restore search patterns
+ win_size_restore(&cpinfo->save_view); // Restore window sizes
+
+ ga_clear(&cpinfo->save_view);
+ kv_destroy(cpinfo->win_info);
+ kv_destroy(cpinfo->buf_info);
+}
+
/// Show 'inccommand' preview if command is previewable. It works like this:
/// 1. Store current undo information so we can revert to current state later.
/// 2. Execute the preview callback with the parsed command, preview buffer number and preview
@@ -2440,35 +2585,18 @@ static bool cmdpreview_may_show(CommandLineState *s)
ea.line2 = lnum;
}
- time_t save_b_u_time_cur = curbuf->b_u_time_cur;
- long save_b_u_seq_cur = curbuf->b_u_seq_cur;
- u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
- long save_b_p_ul = curbuf->b_p_ul;
- int save_b_changed = curbuf->b_changed;
- int save_w_p_cul = curwin->w_p_cul;
- int save_w_p_cuc = curwin->w_p_cuc;
- bool save_hls = p_hls;
- varnumber_T save_changedtick = buf_get_changedtick(curbuf);
+ CpInfo cpinfo;
bool icm_split = *p_icm == 's'; // inccommand=split
buf_T *cmdpreview_buf;
win_T *cmdpreview_win;
- cmdmod_T save_cmdmod = cmdmod;
- cmdpreview = true;
emsg_silent++; // Block error reporting as the command may be incomplete,
// but still update v:errmsg
msg_silent++; // Block messages, namely ones that prompt
block_autocmds(); // Block events
- garray_T save_view;
- win_size_save(&save_view); // Save current window sizes
- save_search_patterns(); // Save search patterns
- curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
- curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
- curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
- p_hls = false; // Don't show search highlighting during live substitution
- cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers
- cmdmod.cmod_tab = 0; // Disable :tab modifier
- cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer
+
+ // Save current state and prepare for command preview.
+ cmdpreview_prepare(&cpinfo);
// Open preview buffer if inccommand=split.
if (!icm_split) {
@@ -2476,12 +2604,14 @@ static bool cmdpreview_may_show(CommandLineState *s)
} else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) {
abort();
}
-
// Setup preview namespace if it's not already set.
if (!cmdpreview_ns) {
cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT);
}
+ // Set cmdpreview state.
+ cmdpreview = true;
+
// Execute the preview callback and use its return value to determine whether to show preview or
// open the preview window. The preview callback also handles doing the changes and highlights for
// the preview.
@@ -2500,7 +2630,7 @@ static bool cmdpreview_may_show(CommandLineState *s)
cmdpreview_type = 1;
}
- // If preview callback is nonzero, update screen now.
+ // If preview callback return value is nonzero, update screen now.
if (cmdpreview_type != 0) {
int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
@@ -2512,44 +2642,13 @@ static bool cmdpreview_may_show(CommandLineState *s)
if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) {
cmdpreview_close_win();
}
- // Clear preview highlights.
- extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
- curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview
+ // Restore state.
+ cmdpreview_restore_state(&cpinfo);
- if (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
- // Undo invisibly. This also moves the cursor!
- while (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
- if (!u_undo_and_forget(1)) {
- abort();
- }
- }
- // Restore newhead. It is meaningless when curhead is valid, but we must
- // restore it so that undotree() is identical before/after the preview.
- curbuf->b_u_newhead = save_b_u_newhead;
- curbuf->b_u_time_cur = save_b_u_time_cur;
- }
- if (save_changedtick != buf_get_changedtick(curbuf)) {
- buf_set_changedtick(curbuf, save_changedtick);
- }
-
- cmdmod = save_cmdmod; // Restore cmdmod
- p_hls = save_hls; // Restore 'hlsearch'
- curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
- curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
- curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels'
- restore_search_patterns(); // Restore search patterns
- win_size_restore(&save_view); // Restore window sizes
- ga_clear(&save_view);
unblock_autocmds(); // Unblock events
msg_silent--; // Unblock messages
emsg_silent--; // Unblock error reporting
-
- // Restore the window "view".
- curwin->w_cursor = s->is_state.save_cursor;
- restore_viewstate(&s->is_state.old_viewstate);
- update_topline(curwin);
-
redrawcmdline();
end:
xfree(cmdline);
@@ -2682,7 +2781,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at
save_cmdline(&save_ccline);
did_save_ccline = true;
} else {
- memset(&ccline, 0, sizeof(struct cmdline_info));
+ CLEAR_FIELD(ccline);
}
ccline.prompt_id = last_prompt_id++;
ccline.cmdprompt = (char_u *)prompt;
@@ -3592,7 +3691,7 @@ void put_on_cmdline(char_u *str, int len, int redraw)
msg_col -= i;
if (msg_col < 0) {
msg_col += Columns;
- --msg_row;
+ msg_row--;
}
}
}
@@ -3645,7 +3744,7 @@ void put_on_cmdline(char_u *str, int len, int redraw)
static void save_cmdline(struct cmdline_info *ccp)
{
*ccp = ccline;
- memset(&ccline, 0, sizeof(struct cmdline_info));
+ CLEAR_FIELD(ccline);
ccline.prev_ccline = ccp;
ccline.cmdbuff = NULL; // signal that ccline is not in use
}
@@ -3669,7 +3768,7 @@ static void restore_cmdline(struct cmdline_info *ccp)
/// @returns FAIL for failure, OK otherwise
static bool cmdline_paste(int regname, bool literally, bool remcr)
{
- char_u *arg;
+ char *arg;
char_u *p;
bool allocated;
@@ -3702,7 +3801,7 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
// When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate
// part of the word.
- p = arg;
+ p = (char_u *)arg;
if (p_is && regname == Ctrl_W) {
char_u *w;
int len;
@@ -4138,9 +4237,9 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode
if (findex == -1) {
findex = xp->xp_numfiles;
}
- --findex;
+ findex--;
} else { // mode == WILD_NEXT
- ++findex;
+ findex++;
}
/*
@@ -4880,7 +4979,7 @@ char_u *addstar(char_u *fname, size_t len, int context)
&& vim_strchr((char *)retval, '`') == NULL) {
retval[len++] = '*';
} else if (len > 0 && retval[len - 1] == '$') {
- --len;
+ len--;
}
retval[len] = NUL;
}
@@ -5028,58 +5127,6 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char ***
return EXPAND_OK;
}
-// Cleanup matches for help tags:
-// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first
-// tag matches it. Otherwise remove "@en" if "en" is the only language.
-static void cleanup_help_tags(int num_file, char **file)
-{
- char_u buf[4];
- char_u *p = buf;
-
- if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) {
- *p++ = '@';
- *p++ = p_hlg[0];
- *p++ = p_hlg[1];
- }
- *p = NUL;
-
- for (int i = 0; i < num_file; i++) {
- int len = (int)STRLEN(file[i]) - 3;
- if (len <= 0) {
- continue;
- }
- if (STRCMP(file[i] + len, "@en") == 0) {
- // Sorting on priority means the same item in another language may
- // be anywhere. Search all items for a match up to the "@en".
- int j;
- for (j = 0; j < num_file; j++) {
- if (j != i
- && (int)STRLEN(file[j]) == len + 3
- && STRNCMP(file[i], file[j], len + 1) == 0) {
- break;
- }
- }
- if (j == num_file) {
- // item only exists with @en, remove it
- file[i][len] = NUL;
- }
- }
- }
-
- if (*buf != NUL) {
- for (int i = 0; i < num_file; i++) {
- int len = (int)STRLEN(file[i]) - 3;
- if (len <= 0) {
- continue;
- }
- if (STRCMP(file[i] + len, buf) == 0) {
- // remove the default language
- file[i][len] = NUL;
- }
- }
- }
-}
-
typedef char *(*ExpandFunc)(expand_T *, int);
/// Do the expansion based on xp->xp_context and "pat".
@@ -5289,8 +5336,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f
{ EXPAND_SYNTAX, get_syntax_name, true, true },
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
- { EXPAND_EVENTS, expand_get_event_name, true, true },
- { EXPAND_AUGROUP, expand_get_augroup_name, true, true },
+ { EXPAND_EVENTS, expand_get_event_name, true, false },
+ { EXPAND_AUGROUP, expand_get_augroup_name, true, false },
{ EXPAND_CSCOPE, get_cscope_name, true, true },
{ EXPAND_SIGN, get_sign_name, true, true },
{ EXPAND_PROFILE, get_profile_name, true, true },
@@ -5521,7 +5568,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl
}
}
if (*e != NUL) {
- ++e;
+ e++;
}
}
*file = ga.ga_data;
@@ -5674,163 +5721,9 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file)
return OK;
}
-/// Expand color scheme, compiler or filetype names.
-/// Search from 'runtimepath':
-/// 'runtimepath'/{dirnames}/{pat}.vim
-/// When "flags" has DIP_START: search also from 'start' of 'packpath':
-/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim
-/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath':
-/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim
-/// When "flags" has DIP_LUA: search also performed for .lua files
-/// "dirnames" is an array with one or more directory names.
-static int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirnames[])
-{
- *num_file = 0;
- *file = NULL;
- size_t pat_len = STRLEN(pat);
-
- garray_T ga;
- ga_init(&ga, (int)sizeof(char *), 10);
-
- // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic.
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = STRLEN(dirnames[i]) + pat_len + 7;
- char_u *s = xmalloc(size);
- snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat);
- globpath(p_rtp, s, &ga, 0);
- if (flags & DIP_LUA) {
- snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat);
- globpath(p_rtp, s, &ga, 0);
- }
- xfree(s);
- }
-
- if (flags & DIP_START) {
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = STRLEN(dirnames[i]) + pat_len + 22;
- char_u *s = xmalloc(size);
- snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- if (flags & DIP_LUA) {
- snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- }
- xfree(s);
- }
-
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = STRLEN(dirnames[i]) + pat_len + 22;
- char_u *s = xmalloc(size);
- snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- if (flags & DIP_LUA) {
- snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- }
- xfree(s);
- }
- }
-
- if (flags & DIP_OPT) {
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = STRLEN(dirnames[i]) + pat_len + 20;
- char_u *s = xmalloc(size);
- snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- if (flags & DIP_LUA) {
- snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- }
- xfree(s);
- }
-
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = STRLEN(dirnames[i]) + pat_len + 20;
- char_u *s = xmalloc(size);
- snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- if (flags & DIP_LUA) {
- snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- }
- xfree(s);
- }
- }
-
- for (int i = 0; i < ga.ga_len; i++) {
- char_u *match = ((char_u **)ga.ga_data)[i];
- char_u *s = match;
- char_u *e = s + STRLEN(s);
- if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0
- || ((flags & DIP_LUA)
- && STRNICMP(e - 4, ".lua", 4) == 0))) {
- e -= 4;
- for (s = e; s > match; MB_PTR_BACK(match, s)) {
- if (vim_ispathsep(*s)) {
- break;
- }
- }
- s++;
- *e = NUL;
- assert((e - s) + 1 >= 0);
- memmove(match, s, (size_t)(e - s) + 1);
- }
- }
-
- if (GA_EMPTY(&ga)) {
- return FAIL;
- }
-
- /* Sort and remove duplicates which can happen when specifying multiple
- * directories in dirnames. */
- ga_remove_duplicate_strings(&ga);
-
- *file = ga.ga_data;
- *num_file = ga.ga_len;
- return OK;
-}
-
-/// Expand loadplugin names:
-/// 'packpath'/pack/ * /opt/{pat}
-static int ExpandPackAddDir(char_u *pat, int *num_file, char ***file)
-{
- garray_T ga;
-
- *num_file = 0;
- *file = NULL;
- size_t pat_len = STRLEN(pat);
- ga_init(&ga, (int)sizeof(char *), 10);
-
- size_t buflen = pat_len + 26;
- char_u *s = xmalloc(buflen);
- snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- xfree(s);
-
- for (int i = 0; i < ga.ga_len; i++) {
- char_u *match = ((char_u **)ga.ga_data)[i];
- s = (char_u *)path_tail((char *)match);
- memmove(match, s, STRLEN(s) + 1);
- }
-
- if (GA_EMPTY(&ga)) {
- return FAIL;
- }
-
- // Sort and remove duplicates which can happen when specifying multiple
- // directories in dirnames.
- ga_remove_duplicate_strings(&ga);
-
- *file = ga.ga_data;
- *num_file = ga.ga_len;
- return OK;
-}
-
/// Expand `file` for all comma-separated directories in `path`.
/// Adds matches to `ga`.
-void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
+void globpath(char *path, char_u *file, garray_T *ga, int expand_options)
{
expand_T xpc;
ExpandInit(&xpc);
@@ -5841,7 +5734,7 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
// Loop over all entries in {path}.
while (*path != NUL) {
// Copy one item of the path to buf[] and concatenate the file name.
- copy_option_part((char **)&path, (char *)buf, MAXPATHL, ",");
+ copy_option_part(&path, (char *)buf, MAXPATHL, ",");
if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) {
add_pathsep((char *)buf);
STRCAT(buf, file); // NOLINT
@@ -5868,309 +5761,6 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
xfree(buf);
}
-/*********************************
-* Command line history stuff *
-*********************************/
-
-/// Translate a history character to the associated type number
-static HistoryType hist_char2type(const int c)
- FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
-{
- switch (c) {
- case ':':
- return HIST_CMD;
- case '=':
- return HIST_EXPR;
- case '@':
- return HIST_INPUT;
- case '>':
- return HIST_DEBUG;
- case NUL:
- case '/':
- case '?':
- return HIST_SEARCH;
- default:
- return HIST_INVALID;
- }
- // Silence -Wreturn-type
- return 0;
-}
-
-/*
- * Table of history names.
- * These names are used in :history and various hist...() functions.
- * It is sufficient to give the significant prefix of a history name.
- */
-
-static char *(history_names[]) =
-{
- "cmd",
- "search",
- "expr",
- "input",
- "debug",
- NULL
-};
-
-/*
- * Function given to ExpandGeneric() to obtain the possible first
- * arguments of the ":history command.
- */
-static char *get_history_arg(expand_T *xp, int idx)
-{
- static char_u compl[2] = { NUL, NUL };
- char *short_names = ":=@>?/";
- int short_names_count = (int)STRLEN(short_names);
- int history_name_count = ARRAY_SIZE(history_names) - 1;
-
- if (idx < short_names_count) {
- compl[0] = (char_u)short_names[idx];
- return (char *)compl;
- }
- if (idx < short_names_count + history_name_count) {
- return history_names[idx - short_names_count];
- }
- if (idx == short_names_count + history_name_count) {
- return "all";
- }
- return NULL;
-}
-
-/// Initialize command line history.
-/// Also used to re-allocate history tables when size changes.
-void init_history(void)
-{
- assert(p_hi >= 0 && p_hi <= INT_MAX);
- int newlen = (int)p_hi;
- int oldlen = hislen;
-
- // If history tables size changed, reallocate them.
- // Tables are circular arrays (current position marked by hisidx[type]).
- // On copying them to the new arrays, we take the chance to reorder them.
- if (newlen != oldlen) {
- for (int type = 0; type < HIST_COUNT; type++) {
- histentry_T *temp = (newlen
- ? xmalloc((size_t)newlen * sizeof(*temp))
- : NULL);
-
- int j = hisidx[type];
- if (j >= 0) {
- // old array gets partitioned this way:
- // [0 , i1 ) --> newest entries to be deleted
- // [i1 , i1 + l1) --> newest entries to be copied
- // [i1 + l1 , i2 ) --> oldest entries to be deleted
- // [i2 , i2 + l2) --> oldest entries to be copied
- int l1 = MIN(j + 1, newlen); // how many newest to copy
- int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy
- int i1 = j + 1 - l1; // copy newest from here
- int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here
-
- // copy as much entries as they fit to new table, reordering them
- if (newlen) {
- // copy oldest entries
- memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
- // copy newest entries
- memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
- }
-
- // delete entries that don't fit in newlen, if any
- for (int i = 0; i < i1; i++) {
- hist_free_entry(history[type] + i);
- }
- for (int i = i1 + l1; i < i2; i++) {
- hist_free_entry(history[type] + i);
- }
- }
-
- // clear remaining space, if any
- int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries
- if (newlen) {
- memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
- }
-
- hisidx[type] = l3 - 1;
- xfree(history[type]);
- history[type] = temp;
- }
- hislen = newlen;
- }
-}
-
-static inline void hist_free_entry(histentry_T *hisptr)
- FUNC_ATTR_NONNULL_ALL
-{
- xfree(hisptr->hisstr);
- tv_list_unref(hisptr->additional_elements);
- clear_hist_entry(hisptr);
-}
-
-static inline void clear_hist_entry(histentry_T *hisptr)
- FUNC_ATTR_NONNULL_ALL
-{
- memset(hisptr, 0, sizeof(*hisptr));
-}
-
-/// Check if command line 'str' is already in history.
-/// If 'move_to_front' is TRUE, matching entry is moved to end of history.
-///
-/// @param move_to_front Move the entry to the front if it exists
-static int in_history(int type, char_u *str, int move_to_front, int sep)
-{
- int i;
- int last_i = -1;
- char_u *p;
-
- if (hisidx[type] < 0) {
- return FALSE;
- }
- i = hisidx[type];
- do {
- if (history[type][i].hisstr == NULL) {
- return FALSE;
- }
-
- /* For search history, check that the separator character matches as
- * well. */
- p = history[type][i].hisstr;
- if (STRCMP(str, p) == 0
- && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
- if (!move_to_front) {
- return TRUE;
- }
- last_i = i;
- break;
- }
- if (--i < 0) {
- i = hislen - 1;
- }
- } while (i != hisidx[type]);
-
- if (last_i >= 0) {
- list_T *const list = history[type][i].additional_elements;
- str = history[type][i].hisstr;
- while (i != hisidx[type]) {
- if (++i >= hislen) {
- i = 0;
- }
- history[type][last_i] = history[type][i];
- last_i = i;
- }
- tv_list_unref(list);
- history[type][i].hisnum = ++hisnum[type];
- history[type][i].hisstr = str;
- history[type][i].timestamp = os_time();
- history[type][i].additional_elements = NULL;
- return true;
- }
- return false;
-}
-
-/// Convert history name to its HIST_ equivalent
-///
-/// Names are taken from the table above. When `name` is empty returns currently
-/// active history or HIST_DEFAULT, depending on `return_default` argument.
-///
-/// @param[in] name Converted name.
-/// @param[in] len Name length.
-/// @param[in] return_default Determines whether HIST_DEFAULT should be
-/// returned or value based on `ccline.cmdfirstc`.
-///
-/// @return Any value from HistoryType enum, including HIST_INVALID. May not
-/// return HIST_DEFAULT unless return_default is true.
-HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- // No argument: use current history.
- if (len == 0) {
- return return_default ? HIST_DEFAULT : hist_char2type(ccline.cmdfirstc);
- }
-
- for (HistoryType i = 0; history_names[i] != NULL; i++) {
- if (STRNICMP(name, history_names[i], len) == 0) {
- return i;
- }
- }
-
- if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) {
- return hist_char2type(name[0]);
- }
-
- return HIST_INVALID;
-}
-
-static int last_maptick = -1; // last seen maptick
-
-/// Add the given string to the given history. If the string is already in the
-/// history then it is moved to the front. "histype" may be one of the HIST_
-/// values.
-///
-/// @parma in_map consider maptick when inside a mapping
-/// @param sep separator character used (search hist)
-void add_to_history(int histype, char_u *new_entry, int in_map, int sep)
-{
- histentry_T *hisptr;
-
- if (hislen == 0 || histype == HIST_INVALID) { // no history
- return;
- }
- assert(histype != HIST_DEFAULT);
-
- if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) {
- return;
- }
-
- /*
- * Searches inside the same mapping overwrite each other, so that only
- * the last line is kept. Be careful not to remove a line that was moved
- * down, only lines that were added.
- */
- if (histype == HIST_SEARCH && in_map) {
- if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
- // Current line is from the same mapping, remove it
- hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
- hist_free_entry(hisptr);
- --hisnum[histype];
- if (--hisidx[HIST_SEARCH] < 0) {
- hisidx[HIST_SEARCH] = hislen - 1;
- }
- }
- last_maptick = -1;
- }
- if (!in_history(histype, new_entry, true, sep)) {
- if (++hisidx[histype] == hislen) {
- hisidx[histype] = 0;
- }
- hisptr = &history[histype][hisidx[histype]];
- hist_free_entry(hisptr);
-
- // Store the separator after the NUL of the string.
- size_t len = STRLEN(new_entry);
- hisptr->hisstr = vim_strnsave(new_entry, len + 2);
- hisptr->timestamp = os_time();
- hisptr->additional_elements = NULL;
- hisptr->hisstr[len + 1] = (char_u)sep;
-
- hisptr->hisnum = ++hisnum[histype];
- if (histype == HIST_SEARCH && in_map) {
- last_maptick = maptick;
- }
- }
-}
-
-/*
- * Get identifier of newest history entry.
- * "histype" may be one of the HIST_ values.
- */
-int get_history_idx(int histype)
-{
- if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
- || hisidx[histype] < 0) {
- return -1;
- }
-
- return history[histype][hisidx[histype]].hisnum;
-}
-
/// Get pointer to the command line info to use. save_cmdline() may clear
/// ccline and put the previous value in ccline.prev_ccline.
static struct cmdline_info *get_ccline_ptr(void)
@@ -6292,168 +5882,10 @@ int get_cmdline_type(void)
return p->cmdfirstc;
}
-/*
- * Calculate history index from a number:
- * num > 0: seen as identifying number of a history entry
- * num < 0: relative position in history wrt newest entry
- * "histype" may be one of the HIST_ values.
- */
-static int calc_hist_idx(int histype, int num)
-{
- int i;
- histentry_T *hist;
- int wrapped = FALSE;
-
- if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
- || (i = hisidx[histype]) < 0 || num == 0) {
- return -1;
- }
-
- hist = history[histype];
- if (num > 0) {
- while (hist[i].hisnum > num) {
- if (--i < 0) {
- if (wrapped) {
- break;
- }
- i += hislen;
- wrapped = TRUE;
- }
- }
- if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) {
- return i;
- }
- } else if (-num <= hislen) {
- i += num + 1;
- if (i < 0) {
- i += hislen;
- }
- if (hist[i].hisstr != NULL) {
- return i;
- }
- }
- return -1;
-}
-
-/*
- * Get a history entry by its index.
- * "histype" may be one of the HIST_ values.
- */
-char_u *get_history_entry(int histype, int idx)
-{
- idx = calc_hist_idx(histype, idx);
- if (idx >= 0) {
- return history[histype][idx].hisstr;
- } else {
- return (char_u *)"";
- }
-}
-
-/// Clear all entries in a history
-///
-/// @param[in] histype One of the HIST_ values.
-///
-/// @return OK if there was something to clean and histype was one of HIST_
-/// values, FAIL otherwise.
-int clr_history(const int histype)
-{
- if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
- histentry_T *hisptr = history[histype];
- for (int i = hislen; i--; hisptr++) {
- hist_free_entry(hisptr);
- }
- hisidx[histype] = -1; // mark history as cleared
- hisnum[histype] = 0; // reset identifier counter
- return OK;
- }
- return FAIL;
-}
-
-/*
- * Remove all entries matching {str} from a history.
- * "histype" may be one of the HIST_ values.
- */
-int del_history_entry(int histype, char_u *str)
+/// Return the first character of the current command line.
+int get_cmdline_firstc(void)
{
- regmatch_T regmatch;
- histentry_T *hisptr;
- int idx;
- int i;
- int last;
- bool found = false;
-
- regmatch.regprog = NULL;
- regmatch.rm_ic = FALSE; // always match case
- if (hislen != 0
- && histype >= 0
- && histype < HIST_COUNT
- && *str != NUL
- && (idx = hisidx[histype]) >= 0
- && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING))
- != NULL) {
- i = last = idx;
- do {
- hisptr = &history[histype][i];
- if (hisptr->hisstr == NULL) {
- break;
- }
- if (vim_regexec(&regmatch, (char *)hisptr->hisstr, (colnr_T)0)) {
- found = true;
- hist_free_entry(hisptr);
- } else {
- if (i != last) {
- history[histype][last] = *hisptr;
- clear_hist_entry(hisptr);
- }
- if (--last < 0) {
- last += hislen;
- }
- }
- if (--i < 0) {
- i += hislen;
- }
- } while (i != idx);
- if (history[histype][idx].hisstr == NULL) {
- hisidx[histype] = -1;
- }
- }
- vim_regfree(regmatch.regprog);
- return found;
-}
-
-/*
- * Remove an indexed entry from a history.
- * "histype" may be one of the HIST_ values.
- */
-int del_history_idx(int histype, int idx)
-{
- int i, j;
-
- i = calc_hist_idx(histype, idx);
- if (i < 0) {
- return FALSE;
- }
- idx = hisidx[histype];
- hist_free_entry(&history[histype][i]);
-
- /* When deleting the last added search string in a mapping, reset
- * last_maptick, so that the last added search string isn't deleted again.
- */
- if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
- last_maptick = -1;
- }
-
- while (i != idx) {
- j = (i + 1) % hislen;
- history[histype][i] = history[histype][j];
- i = j;
- }
- clear_hist_entry(&history[histype][idx]);
- if (--i < 0) {
- i += hislen;
- }
- hisidx[histype] = i;
- return TRUE;
+ return ccline.cmdfirstc;
}
/// Get indices that specify a range within a list (not a range of text lines
@@ -6464,26 +5896,26 @@ int del_history_idx(int histype, int idx)
/// @param num2 to
///
/// @return OK if parsed successfully, otherwise FAIL.
-int get_list_range(char_u **str, int *num1, int *num2)
+int get_list_range(char **str, int *num1, int *num2)
{
int len;
int first = false;
varnumber_T num;
- *str = (char_u *)skipwhite((char *)(*str));
+ *str = skipwhite((*str));
if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range
- vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
+ vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false);
*str += len;
*num1 = (int)num;
first = true;
}
- *str = (char_u *)skipwhite((char *)(*str));
+ *str = skipwhite((*str));
if (**str == ',') { // parse "to" part of range
- *str = (char_u *)skipwhite((char *)(*str) + 1);
- vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
+ *str = skipwhite((*str) + 1);
+ vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false);
if (len > 0) {
*num2 = (int)num;
- *str = (char_u *)skipwhite((char *)(*str) + len);
+ *str = skipwhite((*str) + len);
} else if (!first) { // no number given at all
return FAIL;
}
@@ -6493,118 +5925,9 @@ int get_list_range(char_u **str, int *num1, int *num2)
return OK;
}
-/*
- * :history command - print a history
- */
-void ex_history(exarg_T *eap)
-{
- histentry_T *hist;
- int histype1 = HIST_CMD;
- int histype2 = HIST_CMD;
- int hisidx1 = 1;
- int hisidx2 = -1;
- int idx;
- int i, j, k;
- char_u *end;
- char_u *arg = (char_u *)eap->arg;
-
- if (hislen == 0) {
- msg(_("'history' option is zero"));
- return;
- }
-
- if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
- end = arg;
- while (ASCII_ISALPHA(*end)
- || vim_strchr(":=@>/?", *end) != NULL) {
- end++;
- }
- histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false);
- if (histype1 == HIST_INVALID) {
- if (STRNICMP(arg, "all", end - arg) == 0) {
- histype1 = 0;
- histype2 = HIST_COUNT - 1;
- } else {
- semsg(_(e_trailing_arg), arg);
- return;
- }
- } else {
- histype2 = histype1;
- }
- } else {
- end = arg;
- }
- if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
- semsg(_(e_trailing_arg), end);
- return;
- }
-
- for (; !got_int && histype1 <= histype2; ++histype1) {
- STRCPY(IObuff, "\n # ");
- assert(history_names[histype1] != NULL);
- STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
- msg_puts_title((char *)IObuff);
- idx = hisidx[histype1];
- hist = history[histype1];
- j = hisidx1;
- k = hisidx2;
- if (j < 0) {
- j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
- }
- if (k < 0) {
- k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
- }
- if (idx >= 0 && j <= k) {
- for (i = idx + 1; !got_int; ++i) {
- if (i == hislen) {
- i = 0;
- }
- if (hist[i].hisstr != NULL
- && hist[i].hisnum >= j && hist[i].hisnum <= k) {
- msg_putchar('\n');
- snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
- hist[i].hisnum);
- if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) {
- trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff),
- Columns - 10, IOSIZE - (int)STRLEN(IObuff));
- } else {
- STRCAT(IObuff, hist[i].hisstr);
- }
- msg_outtrans((char *)IObuff);
- ui_flush();
- }
- if (i == idx) {
- break;
- }
- }
- }
- }
-}
-
-/// Translate a history type number to the associated character
-int hist_type2char(int type)
- FUNC_ATTR_CONST
-{
- switch (type) {
- case HIST_CMD:
- return ':';
- case HIST_SEARCH:
- return '/';
- case HIST_EXPR:
- return '=';
- case HIST_INPUT:
- return '@';
- case HIST_DEBUG:
- return '>';
- default:
- abort();
- }
- return NUL;
-}
-
void cmdline_init(void)
{
- memset(&ccline, 0, sizeof(struct cmdline_info));
+ CLEAR_FIELD(ccline);
}
/// Open a window on the current command line and history. Allow editing in
@@ -6688,18 +6011,18 @@ static int open_cmdwin(void)
// Fill the buffer with the history.
init_history();
- if (hislen > 0 && histtype != HIST_INVALID) {
- i = hisidx[histtype];
+ if (get_hislen() > 0 && histtype != HIST_INVALID) {
+ i = *get_hisidx(histtype);
if (i >= 0) {
lnum = 0;
do {
- if (++i == hislen) {
+ if (++i == get_hislen()) {
i = 0;
}
- if (history[histtype][i].hisstr != NULL) {
- ml_append(lnum++, (char *)history[histtype][i].hisstr, (colnr_T)0, false);
+ if (get_histentry(histtype)[i].hisstr != NULL) {
+ ml_append(lnum++, (char *)get_histentry(histtype)[i].hisstr, (colnr_T)0, false);
}
- } while (i != hisidx[histtype]);
+ } while (i != *get_hisidx(histtype));
}
}
@@ -6904,90 +6227,6 @@ char *script_get(exarg_T *const eap, size_t *const lenp)
return (char *)ga.ga_data;
}
-/// Iterate over history items
-///
-/// @warning No history-editing functions must be run while iteration is in
-/// progress.
-///
-/// @param[in] iter Pointer to the last history entry.
-/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
-/// parameter is not NULL.
-/// @param[in] zero If true then zero (but not free) returned items.
-///
-/// @warning When using this parameter user is
-/// responsible for calling clr_history()
-/// itself after iteration is over. If
-/// clr_history() is not called behaviour is
-/// undefined. No functions that work with
-/// history must be called during iteration
-/// in this case.
-/// @param[out] hist Next history entry.
-///
-/// @return Pointer used in next iteration or NULL to indicate that iteration
-/// was finished.
-const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
- histentry_T *const hist)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
-{
- *hist = (histentry_T) {
- .hisstr = NULL
- };
- if (hisidx[history_type] == -1) {
- return NULL;
- }
- histentry_T *const hstart = &(history[history_type][0]);
- histentry_T *const hlast = (
- &(history[history_type][hisidx[history_type]]));
- const histentry_T *const hend = &(history[history_type][hislen - 1]);
- histentry_T *hiter;
- if (iter == NULL) {
- histentry_T *hfirst = hlast;
- do {
- hfirst++;
- if (hfirst > hend) {
- hfirst = hstart;
- }
- if (hfirst->hisstr != NULL) {
- break;
- }
- } while (hfirst != hlast);
- hiter = hfirst;
- } else {
- hiter = (histentry_T *)iter;
- }
- if (hiter == NULL) {
- return NULL;
- }
- *hist = *hiter;
- if (zero) {
- memset(hiter, 0, sizeof(*hiter));
- }
- if (hiter == hlast) {
- return NULL;
- }
- hiter++;
- return (const void *)((hiter > hend) ? hstart : hiter);
-}
-
-/// Get array of history items
-///
-/// @param[in] history_type Type of the history to get array for.
-/// @param[out] new_hisidx Location where last index in the new array should
-/// be saved.
-/// @param[out] new_hisnum Location where last history number in the new
-/// history should be saved.
-///
-/// @return Pointer to the array or NULL.
-histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
- int **const new_hisnum)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
-{
- init_history();
- *new_hisidx = &(hisidx[history_type]);
- *new_hisnum = &(hisnum[history_type]);
- return history[history_type];
-}
-
static void set_search_match(pos_T *t)
{
// First move cursor to end of match, then to the start. This
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 1cc6faf87c..2f6929924d 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -3,8 +3,6 @@
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/os/time.h"
#include "nvim/regexp_defs.h"
// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning.
@@ -39,30 +37,8 @@
#define VSE_SHELL 1 ///< escape for a shell command
#define VSE_BUFFER 2 ///< escape for a ":buffer" command
-/// Present history tables
-typedef enum {
- HIST_DEFAULT = -2, ///< Default (current) history.
- HIST_INVALID = -1, ///< Unknown history.
- HIST_CMD = 0, ///< Colon commands.
- HIST_SEARCH, ///< Search commands.
- HIST_EXPR, ///< Expressions (e.g. from entering = register).
- HIST_INPUT, ///< input() lines.
- HIST_DEBUG, ///< Debug commands.
-} HistoryType;
-
-/// Number of history tables
-#define HIST_COUNT (HIST_DEBUG + 1)
-
typedef char *(*CompleteListItemGetter)(expand_T *, int);
-/// History entry definition
-typedef struct hist_entry {
- int hisnum; ///< Entry identifier number.
- char_u *hisstr; ///< Actual entry, separator char after the NUL.
- Timestamp timestamp; ///< Time when entry was added.
- list_T *additional_elements; ///< Additional entries from ShaDa file.
-} histentry_T;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h"
#endif
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 1d670afa6d..9495ae1c4b 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -13,12 +13,12 @@
#include <stdlib.h>
#include <string.h>
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
@@ -34,6 +34,7 @@
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
+#include "nvim/runtime.h"
#include "nvim/vim.h"
#include "nvim/window.h"
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index ebefd1157c..2d09e7aa71 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -50,6 +50,7 @@
#include <string.h>
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/file_search.h"
@@ -306,7 +307,7 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le
search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len);
}
if (*++path != NUL) {
- ++path;
+ path++;
}
} else if (*path == NUL || !vim_isAbsName(path)) {
#ifdef BACKSLASH_IN_FILENAME
@@ -583,7 +584,7 @@ char_u *vim_findfile(void *search_ctx_arg)
ff_stack_T *stackp = NULL;
size_t len;
char_u *p;
- char_u *suf;
+ char *suf;
ff_search_ctx_T *search_ctx;
if (search_ctx_arg == NULL) {
@@ -824,9 +825,9 @@ char_u *vim_findfile(void *search_ctx_arg)
*/
len = STRLEN(file_path);
if (search_ctx->ffsc_tagfile) {
- suf = (char_u *)"";
+ suf = "";
} else {
- suf = curbuf->b_p_sua;
+ suf = (char *)curbuf->b_p_sua;
}
for (;;) {
// if file exists and we didn't already find it
@@ -891,7 +892,7 @@ char_u *vim_findfile(void *search_ctx_arg)
break;
}
assert(MAXPATHL >= len);
- copy_option_part((char **)&suf, (char *)file_path + len, MAXPATHL - len, ",");
+ copy_option_part(&suf, (char *)file_path + len, MAXPATHL - len, ",");
}
}
} else {
@@ -1401,11 +1402,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
char_u *path_option, int find_what, char_u *rel_fname,
char_u *suffixes)
{
- static char_u *dir;
- static int did_findfile_init = FALSE;
+ static char *dir;
+ static int did_findfile_init = false;
char_u save_char;
char_u *file_name = NULL;
- char_u *buf = NULL;
+ char *buf = NULL;
int rel_to_curdir;
if (rel_fname != NULL && path_with_url((const char *)rel_fname)) {
@@ -1482,7 +1483,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
/* When the file doesn't exist, try adding parts of
* 'suffixesadd'. */
- buf = suffixes;
+ buf = (char *)suffixes;
for (;;) {
if (
(os_path_exists(NameBuff)
@@ -1496,7 +1497,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
break;
}
assert(MAXPATHL >= l);
- copy_option_part((char **)&buf, (char *)NameBuff + l, MAXPATHL - l, ",");
+ copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ",");
}
}
}
@@ -1509,8 +1510,8 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
if (first == TRUE) {
// vim_findfile_free_visited can handle a possible NULL pointer
vim_findfile_free_visited(fdip_search_ctx);
- dir = path_option;
- did_findfile_init = FALSE;
+ dir = (char *)path_option;
+ did_findfile_init = false;
}
for (;;) {
@@ -1536,13 +1537,13 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
// copy next path
buf[0] = 0;
- copy_option_part((char **)&dir, (char *)buf, MAXPATHL, " ,");
+ copy_option_part(&dir, buf, MAXPATHL, " ,");
// get the stopdir string
- r_ptr = vim_findfile_stopdir(buf);
- fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
- r_ptr, 100, FALSE, find_what,
- fdip_search_ctx, FALSE, rel_fname);
+ r_ptr = vim_findfile_stopdir((char_u *)buf);
+ fdip_search_ctx = vim_findfile_init((char_u *)buf, ff_file_to_find,
+ r_ptr, 100, false, find_what,
+ fdip_search_ctx, false, rel_fname);
if (fdip_search_ctx != NULL) {
did_findfile_init = TRUE;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index b98984017b..d29a0f0a08 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -18,7 +18,9 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
@@ -48,7 +50,6 @@
#include "nvim/path.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sha256.h"
#include "nvim/shada.h"
@@ -631,7 +632,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
}
if (aborting()) { // autocmds may abort script processing
- --no_wait_return;
+ no_wait_return--;
msg_scroll = msg_save;
curbuf->b_p_ro = TRUE; // must use "w!" now
return FAIL;
@@ -1196,11 +1197,11 @@ retry:
}
// Deal with a bad byte and continue with the next.
- ++fromp;
- --from_size;
+ fromp++;
+ from_size--;
if (bad_char_behavior == BAD_KEEP) {
*top++ = *(fromp - 1);
- --to_size;
+ to_size--;
} else if (bad_char_behavior != BAD_DROP) {
*top++ = (char)bad_char_behavior;
to_size--;
@@ -1238,7 +1239,7 @@ retry:
// Check for a trailing incomplete UTF-8 sequence
tail = ptr + size - 1;
while (tail > ptr && (*tail & 0xc0) == 0x80) {
- --tail;
+ tail--;
}
if (tail + utf_byte2len(*tail) <= ptr + size) {
tail = NULL;
@@ -1556,7 +1557,7 @@ rewind_retry:
* Keep it fast!
*/
if (fileformat == EOL_MAC) {
- --ptr;
+ ptr--;
while (++ptr, --size >= 0) {
// catch most common case first
if ((c = *ptr) != NUL && c != CAR && c != NL) {
@@ -1577,20 +1578,20 @@ rewind_retry:
if (read_undo_file) {
sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len);
}
- ++lnum;
+ lnum++;
if (--read_count == 0) {
error = true; // break loop
line_start = ptr; // nothing left to write
break;
}
} else {
- --skip_count;
+ skip_count--;
}
line_start = ptr + 1;
}
}
} else {
- --ptr;
+ ptr--;
while (++ptr, --size >= 0) {
if ((c = *ptr) != NUL && c != NL) { // catch most common case
continue;
@@ -1633,14 +1634,14 @@ rewind_retry:
if (read_undo_file) {
sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len);
}
- ++lnum;
+ lnum++;
if (--read_count == 0) {
error = true; // break loop
line_start = ptr; // nothing left to write
break;
}
} else {
- --skip_count;
+ skip_count--;
}
line_start = ptr + 1;
}
@@ -1999,7 +2000,7 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp)
lnum = curbuf->b_ml.ml_line_count - linecnt + 1;
for (s = p; s < endp; ++s) {
if (*s == '\n') {
- ++lnum;
+ lnum++;
}
}
return lnum;
@@ -2477,7 +2478,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
} else { // less lines
end -= old_line_count - buf->b_ml.ml_line_count;
if (end < start) {
- --no_wait_return;
+ no_wait_return--;
msg_scroll = msg_save;
emsg(_("E204: Autocommand changed number of lines in unexpected way"));
return FAIL;
@@ -4819,8 +4820,8 @@ int check_timestamps(int focus)
}
}
}
- --no_wait_return;
- need_check_timestamps = FALSE;
+ no_wait_return--;
+ need_check_timestamps = false;
if (need_wait_return && didit == 2) {
// make sure msg isn't overwritten
msg_puts("\n");
@@ -5122,7 +5123,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
// file, not reset the syntax highlighting, clear marks, diff status, etc.
// Force the fileformat and encoding to be the same.
if (reload_options) {
- memset(&ea, 0, sizeof(ea));
+ CLEAR_FIELD(ea);
} else {
prep_exarg(&ea, buf);
}
@@ -5580,14 +5581,14 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname)
char_u *regpat;
char allow_dirs;
bool match;
- char_u *p;
+ char *p;
tail = (char_u *)path_tail((char *)sfname);
// try all patterns in 'wildignore'
- p = list;
+ p = (char *)list;
while (*p) {
- copy_option_part((char **)&p, (char *)buf, ARRAY_SIZE(buf), ",");
+ copy_option_part(&p, (char *)buf, ARRAY_SIZE(buf), ",");
regpat = (char_u *)file_pat_to_reg_pat((char *)buf, NULL, &allow_dirs, false);
if (regpat == NULL) {
break;
@@ -5680,7 +5681,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
reg_pat[i++] = '.';
reg_pat[i++] = '*';
while (p[1] == '*') { // "**" matches like "*"
- ++p;
+ p++;
}
break;
case '.':
@@ -5768,7 +5769,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
case '}':
reg_pat[i++] = '\\';
reg_pat[i++] = ')';
- --nested;
+ nested--;
break;
case ',':
if (nested) {
diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h
index 62d8b6142e..650977deac 100644
--- a/src/nvim/fileio.h
+++ b/src/nvim/fileio.h
@@ -1,7 +1,6 @@
#ifndef NVIM_FILEIO_H
#define NVIM_FILEIO_H
-#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/garray.h"
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 8f26e03a94..8ce24fd378 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -14,6 +14,7 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_session.h"
@@ -31,7 +32,7 @@
#include "nvim/option.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
+#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/undo.h"
@@ -247,7 +248,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp
// foldLevel() {{{2
/// @return fold level at line number "lnum" in the current window.
-int foldLevel(linenr_T lnum)
+static int foldLevel(linenr_T lnum)
{
// While updating the folds lines between invalid_top and invalid_bot have
// an undefined fold level. Otherwise update the folds first.
@@ -862,7 +863,7 @@ int foldMoveTo(const bool updown, const int dir, const long count)
if (fp - (fold_T *)gap->ga_data >= gap->ga_len) {
break;
}
- --fp;
+ fp--;
} else {
if (fp == (fold_T *)gap->ga_data) {
break;
@@ -1786,13 +1787,13 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin
// foldtext_cleanup() {{{2
/// Remove 'foldmarker' and 'commentstring' from "str" (in-place).
-void foldtext_cleanup(char_u *str)
+static void foldtext_cleanup(char_u *str)
{
// Ignore leading and trailing white space in 'commentstring'.
char_u *cms_start = (char_u *)skipwhite((char *)curbuf->b_p_cms);
size_t cms_slen = STRLEN(cms_start);
while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) {
- --cms_slen;
+ cms_slen--;
}
// locate "%s" in 'commentstring', use the part before and after it.
@@ -1804,7 +1805,7 @@ void foldtext_cleanup(char_u *str)
// exclude white space before "%s"
while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) {
- --cms_slen;
+ cms_slen--;
}
// skip "%s" and white space after it
@@ -3190,3 +3191,119 @@ static int put_fold_open_close(FILE *fd, fold_T *fp, linenr_T off)
}
// }}}1
+
+/// "foldclosed()" and "foldclosedend()" functions
+static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ linenr_T first;
+ linenr_T last;
+ if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
+ if (end) {
+ rettv->vval.v_number = (varnumber_T)last;
+ } else {
+ rettv->vval.v_number = (varnumber_T)first;
+ }
+ return;
+ }
+ }
+ rettv->vval.v_number = -1;
+}
+
+/// "foldclosed()" function
+void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ foldclosed_both(argvars, rettv, false);
+}
+
+/// "foldclosedend()" function
+void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ foldclosed_both(argvars, rettv, true);
+}
+
+/// "foldlevel()" function
+void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ rettv->vval.v_number = foldLevel(lnum);
+ }
+}
+
+/// "foldtext()" function
+void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ linenr_T foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
+ linenr_T foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
+ char_u *dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES);
+ if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) {
+ // Find first non-empty line in the fold.
+ linenr_T lnum;
+ for (lnum = foldstart; lnum < foldend; lnum++) {
+ if (!linewhite(lnum)) {
+ break;
+ }
+ }
+
+ // Find interesting text in this line.
+ char_u *s = (char_u *)skipwhite((char *)ml_get(lnum));
+ // skip C comment-start
+ if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
+ s = (char_u *)skipwhite((char *)s + 2);
+ if (*skipwhite((char *)s) == NUL && lnum + 1 < foldend) {
+ s = (char_u *)skipwhite((char *)ml_get(lnum + 1));
+ if (*s == '*') {
+ s = (char_u *)skipwhite((char *)s + 1);
+ }
+ }
+ }
+ int count = foldend - foldstart + 1;
+ char *txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
+ size_t len = STRLEN(txt)
+ + STRLEN(dashes) // for %s
+ + 20 // for %3ld
+ + STRLEN(s); // concatenated
+ char_u *r = xmalloc(len);
+ snprintf((char *)r, len, txt, dashes, count);
+ len = STRLEN(r);
+ STRCAT(r, s);
+ // remove 'foldmarker' and 'commentstring'
+ foldtext_cleanup(r + len);
+ rettv->vval.v_string = (char *)r;
+ }
+}
+
+/// "foldtextresult(lnum)" function
+void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u buf[FOLD_TEXT_LEN];
+ static bool entered = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (entered) {
+ return; // reject recursive use
+ }
+ entered = true;
+ linenr_T lnum = tv_get_lnum(argvars);
+ // Treat illegal types and illegal string values for {lnum} the same.
+ if (lnum < 0) {
+ lnum = 0;
+ }
+
+ foldinfo_T info = fold_info(curwin, lnum);
+ if (info.fi_lines > 0) {
+ char_u *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf);
+ if (text == buf) {
+ text = vim_strsave(text);
+ }
+ rettv->vval.v_string = (char *)text;
+ }
+
+ entered = false;
+}
diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua
index aa96c97bc1..36553f4649 100644
--- a/src/nvim/generators/gen_unicode_tables.lua
+++ b/src/nvim/generators/gen_unicode_tables.lua
@@ -12,8 +12,8 @@
-- 2 then interval applies only to first, third, fifth, … character in range.
-- Fourth value is number that should be added to the codepoint to yield
-- folded/lower/upper codepoint.
--- 4. emoji_width and emoji_all tables: sorted lists of non-overlapping closed
--- intervals of Emoji characters. emoji_width contains all the characters
+-- 4. emoji_wide and emoji_all tables: sorted lists of non-overlapping closed
+-- intervals of Emoji characters. emoji_wide contains all the characters
-- which don't have ambiguous or double width, and emoji_all has all Emojis.
if arg[1] == '--help' then
print('Usage:')
@@ -288,7 +288,7 @@ local build_emoji_table = function(ut_fp, emojiprops, doublewidth, ambiwidth)
end
ut_fp:write('};\n')
- ut_fp:write('static const struct interval emoji_width[] = {\n')
+ ut_fp:write('static const struct interval emoji_wide[] = {\n')
for _, p in ipairs(emojiwidth) do
ut_fp:write(make_range(p[1], p[2]))
end
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 2b2889d4d6..a3af7dc372 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -15,7 +15,10 @@
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
@@ -40,7 +43,6 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
@@ -1336,7 +1338,7 @@ static void closescript(void)
file_free(scriptin[curscript], false);
scriptin[curscript] = NULL;
if (curscript > 0) {
- --curscript;
+ curscript--;
}
}
@@ -1695,6 +1697,149 @@ int char_avail(void)
return retval != NUL;
}
+/// "getchar()" and "getcharstr()" functions
+static void getchar_common(typval_T *argvars, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ varnumber_T n;
+ bool error = false;
+
+ no_mapping++;
+ allow_keys++;
+ for (;;) {
+ // Position the cursor. Needed after a message that ends in a space,
+ // or if event processing caused a redraw.
+ ui_cursor_goto(msg_row, msg_col);
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // getchar(): blocking wait.
+ // TODO(bfredl): deduplicate shared logic with state_enter ?
+ if (!char_avail()) {
+ // flush output before waiting
+ ui_flush();
+ (void)os_inchar(NULL, 0, -1, 0, main_loop.events);
+ if (!multiqueue_empty(main_loop.events)) {
+ state_handle_k_event();
+ continue;
+ }
+ }
+ n = safe_vgetc();
+ } else if (tv_get_number_chk(&argvars[0], &error) == 1) {
+ // getchar(1): only check if char avail
+ n = vpeekc_any();
+ } else if (error || vpeekc_any() == NUL) {
+ // illegal argument or getchar(0) and no char avail: return zero
+ n = 0;
+ } else {
+ // getchar(0) and char avail() != NUL: get a character.
+ // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE.
+ n = safe_vgetc();
+ }
+
+ if (n == K_IGNORE
+ || n == K_MOUSEMOVE
+ || n == K_VER_SCROLLBAR
+ || n == K_HOR_SCROLLBAR) {
+ continue;
+ }
+ break;
+ }
+ no_mapping--;
+ allow_keys--;
+
+ if (!ui_has_messages()) {
+ // redraw the screen after getchar()
+ update_screen(CLEAR);
+ }
+
+ set_vim_var_nr(VV_MOUSE_WIN, 0);
+ set_vim_var_nr(VV_MOUSE_WINID, 0);
+ set_vim_var_nr(VV_MOUSE_LNUM, 0);
+ set_vim_var_nr(VV_MOUSE_COL, 0);
+
+ rettv->vval.v_number = n;
+ if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) {
+ char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1
+ int i = 0;
+
+ // Turn a special key into three bytes, plus modifier.
+ if (mod_mask != 0) {
+ temp[i++] = K_SPECIAL;
+ temp[i++] = KS_MODIFIER;
+ temp[i++] = (char_u)mod_mask;
+ }
+ if (IS_SPECIAL(n)) {
+ temp[i++] = K_SPECIAL;
+ temp[i++] = (char_u)K_SECOND(n);
+ temp[i++] = K_THIRD(n);
+ } else {
+ i += utf_char2bytes((int)n, (char *)temp + i);
+ }
+ assert(i < 10);
+ temp[i++] = NUL;
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char *)vim_strsave(temp);
+
+ if (is_mouse_key((int)n)) {
+ int row = mouse_row;
+ int col = mouse_col;
+ int grid = mouse_grid;
+ linenr_T lnum;
+ win_T *wp;
+ int winnr = 1;
+
+ if (row >= 0 && col >= 0) {
+ // Find the window at the mouse coordinates and compute the
+ // text position.
+ win_T *const win = mouse_find_win(&grid, &row, &col);
+ if (win == NULL) {
+ return;
+ }
+ (void)mouse_comp_pos(win, &row, &col, &lnum);
+ for (wp = firstwin; wp != win; wp = wp->w_next) {
+ winnr++;
+ }
+ set_vim_var_nr(VV_MOUSE_WIN, winnr);
+ set_vim_var_nr(VV_MOUSE_WINID, wp->handle);
+ set_vim_var_nr(VV_MOUSE_LNUM, lnum);
+ set_vim_var_nr(VV_MOUSE_COL, col + 1);
+ }
+ }
+ }
+}
+
+/// "getchar()" function
+void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getchar_common(argvars, rettv);
+}
+
+/// "getcharstr()" function
+void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getchar_common(argvars, rettv);
+
+ if (rettv->v_type == VAR_NUMBER) {
+ char temp[7]; // mbyte-char: 6, NUL: 1
+ const varnumber_T n = rettv->vval.v_number;
+ int i = 0;
+
+ if (n != 0) {
+ i += utf_char2bytes((int)n, (char *)temp);
+ }
+ assert(i < 7);
+ temp[i++] = NUL;
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xstrdup(temp);
+ }
+}
+
+/// "getcharmod()" function
+void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = mod_mask;
+}
+
typedef enum {
map_result_fail, // failed, break loop
map_result_get, // get a character from typeahead
@@ -2251,7 +2396,7 @@ static int vgetorpeek(bool advance)
return NUL;
}
- ++vgetc_busy;
+ vgetc_busy++;
if (advance) {
KeyStuffed = FALSE;
@@ -2638,7 +2783,7 @@ static int vgetorpeek(bool advance)
gotchars(nop_buf, 3);
}
- --vgetc_busy;
+ vgetc_busy--;
return c;
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 317423ffa0..954b62883e 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -6,12 +6,14 @@
#include "nvim/ascii.h"
#include "nvim/event/loop.h"
-#include "nvim/ex_eval.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_eval_defs.h"
#include "nvim/iconv.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
-#include "nvim/menu.h"
+#include "nvim/menu_defs.h"
#include "nvim/os/os_defs.h"
+#include "nvim/runtime.h"
#include "nvim/syntax_defs.h"
#include "nvim/types.h"
@@ -94,9 +96,6 @@ EXTERN struct nvim_stats_s {
EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen
EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen
-EXTERN NS ns_hl_active INIT(= 0); // current ns that defines highlights
-EXTERN bool ns_hl_changed INIT(= false); // highlight need update
-
// We use 64-bit file functions here, if available. E.g. ftello() returns
// off_t instead of long, which helps if long is 32 bit and off_t is 64 bit.
// We assume that when fseeko() is available then ftello() is too.
@@ -143,6 +142,7 @@ EXTERN int vgetc_char INIT(= 0);
EXTERN int cmdline_row;
EXTERN bool redraw_cmdline INIT(= false); // cmdline must be redrawn
+EXTERN bool redraw_mode INIT(= false); // mode must be redrawn
EXTERN bool clear_cmdline INIT(= false); // cmdline must be cleared
EXTERN bool mode_displayed INIT(= false); // mode is being displayed
EXTERN int cmdline_star INIT(= false); // cmdline is encrypted
@@ -248,9 +248,6 @@ EXTERN int lines_left INIT(= -1); // lines left for listing
EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate
// messages
-EXTERN char *sourcing_name INIT(= NULL); // name of error message source
-EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file
-
EXTERN int ex_nesting_level INIT(= 0); // nesting level
EXTERN int debug_break_level INIT(= -1); // break below this level
EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message
@@ -296,7 +293,7 @@ EXTERN int force_abort INIT(= false);
/// same as the "msg" field of that element, but can be identical to the "msg"
/// field of a later list element, when the "emsg_severe" flag was set when the
/// emsg() call was made.
-EXTERN struct msglist **msg_list INIT(= NULL);
+EXTERN msglist_T **msg_list INIT(= NULL);
/// When set, don't convert an error to an exception. Used when displaying the
/// interrupt message or reporting an exception that is still uncaught at the
@@ -348,8 +345,8 @@ EXTERN bool did_source_packages INIT(= false);
// provider function call
EXTERN struct caller_scope {
sctx_T script_ctx;
- char *sourcing_name, *autocmd_fname, *autocmd_match;
- linenr_T sourcing_lnum;
+ estack_T es_entry;
+ char *autocmd_fname, *autocmd_match;
int autocmd_bufnr;
void *funccalp;
} provider_caller_scope;
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index 72e85c425d..f95ef3e705 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -1,10 +1,19 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+// Most of the routines in this file perform screen (grid) manipulations. The
+// given operation is performed physically on the screen. The corresponding
+// change is also made to the internal screen image. In this way, the editor
+// anticipates the effect of editing changes on the appearance of the screen.
+// That way, when we call update_screen() a complete redraw isn't usually
+// necessary. Another advantage is that we can keep adding code to anticipate
+// screen changes, and in the meantime, everything still works.
+//
+// The grid_*() functions write to the screen and handle updating grid->lines[].
+
#include "nvim/arabic.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
-#include "nvim/screen.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
@@ -780,3 +789,123 @@ void grid_assign_handle(ScreenGrid *grid)
grid->handle = ++last_grid_handle;
}
}
+
+/// insert lines on the screen and move the existing lines down
+/// 'line_count' is the number of lines to be inserted.
+/// 'end' is the line after the scrolled part. Normally it is Rows.
+/// 'col' is the column from with we start inserting.
+//
+/// 'row', 'col' and 'end' are relative to the start of the region.
+void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
+{
+ int i;
+ int j;
+ unsigned temp;
+
+ int row_off = 0;
+ grid_adjust(&grid, &row_off, &col);
+ row += row_off;
+ end += row_off;
+
+ if (line_count <= 0) {
+ return;
+ }
+
+ // Shift line_offset[] line_count down to reflect the inserted lines.
+ // Clear the inserted lines.
+ for (i = 0; i < line_count; i++) {
+ if (width != grid->cols) {
+ // need to copy part of a line
+ j = end - 1 - i;
+ while ((j -= line_count) >= row) {
+ linecopy(grid, j + line_count, j, col, width);
+ }
+ j += line_count;
+ grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
+ grid->line_wraps[j] = false;
+ } else {
+ j = end - 1 - i;
+ temp = (unsigned)grid->line_offset[j];
+ while ((j -= line_count) >= row) {
+ grid->line_offset[j + line_count] = grid->line_offset[j];
+ grid->line_wraps[j + line_count] = grid->line_wraps[j];
+ }
+ grid->line_offset[j + line_count] = temp;
+ grid->line_wraps[j + line_count] = false;
+ grid_clear_line(grid, temp, grid->cols, false);
+ }
+ }
+
+ if (!grid->throttled) {
+ ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0);
+ }
+}
+
+/// delete lines on the screen and move lines up.
+/// 'end' is the line after the scrolled part. Normally it is Rows.
+/// When scrolling region used 'off' is the offset from the top for the region.
+/// 'row' and 'end' are relative to the start of the region.
+void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
+{
+ int j;
+ int i;
+ unsigned temp;
+
+ int row_off = 0;
+ grid_adjust(&grid, &row_off, &col);
+ row += row_off;
+ end += row_off;
+
+ if (line_count <= 0) {
+ return;
+ }
+
+ // Now shift line_offset[] line_count up to reflect the deleted lines.
+ // Clear the inserted lines.
+ for (i = 0; i < line_count; i++) {
+ if (width != grid->cols) {
+ // need to copy part of a line
+ j = row + i;
+ while ((j += line_count) <= end - 1) {
+ linecopy(grid, j - line_count, j, col, width);
+ }
+ j -= line_count;
+ grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
+ grid->line_wraps[j] = false;
+ } else {
+ // whole width, moving the line pointers is faster
+ j = row + i;
+ temp = (unsigned)grid->line_offset[j];
+ while ((j += line_count) <= end - 1) {
+ grid->line_offset[j - line_count] = grid->line_offset[j];
+ grid->line_wraps[j - line_count] = grid->line_wraps[j];
+ }
+ grid->line_offset[j - line_count] = temp;
+ grid->line_wraps[j - line_count] = false;
+ grid_clear_line(grid, temp, grid->cols, false);
+ }
+ }
+
+ if (!grid->throttled) {
+ ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0);
+ }
+}
+
+static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
+{
+ unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col);
+ unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col);
+
+ memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T));
+ memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T));
+}
+
+win_T *get_win_by_grid_handle(handle_T handle)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_grid_alloc.handle == handle) {
+ return wp;
+ }
+ }
+ return NULL;
+}
diff --git a/src/nvim/grid.h b/src/nvim/grid.h
index c38748940d..6a93bf3d90 100644
--- a/src/nvim/grid.h
+++ b/src/nvim/grid.h
@@ -6,6 +6,7 @@
#include "nvim/ascii.h"
#include "nvim/buffer_defs.h"
#include "nvim/grid_defs.h"
+#include "nvim/mbyte.h"
/// By default, all windows are drawn on a single rectangular grid, represented by
/// this ScreenGrid instance. In multigrid mode each window will have its own
@@ -18,6 +19,9 @@ EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT);
#define DEFAULT_GRID_HANDLE 1 // handle for the default_grid
+/// While resizing the screen this flag is set.
+EXTERN bool resizing_screen INIT(= 0);
+
EXTERN schar_T *linebuf_char INIT(= NULL);
EXTERN sattr_T *linebuf_attr INIT(= NULL);
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index d7f7b8eb92..6a1bbcb089 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -17,12 +17,13 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
+#include "nvim/grid.h"
#include "nvim/hardcopy.h"
#include "nvim/highlight_group.h"
+#include "nvim/indent.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -31,7 +32,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -292,8 +293,8 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_
char *ret = NULL;
char_u *stringp;
char_u *colonp;
- char_u *commap;
- char_u *p;
+ char *commap;
+ char *p;
size_t idx = 0; // init for GCC
int len;
@@ -315,9 +316,9 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_
ret = N_("E550: Missing colon");
break;
}
- commap = (char_u *)vim_strchr((char *)stringp, ',');
+ commap = vim_strchr((char *)stringp, ',');
if (commap == NULL) {
- commap = option_str + STRLEN(option_str);
+ commap = (char *)option_str + STRLEN(option_str);
}
len = (int)(colonp - stringp);
@@ -333,8 +334,8 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_
break;
}
- p = colonp + 1;
- table[idx].present = TRUE;
+ p = (char *)colonp + 1;
+ table[idx].present = true;
if (table[idx].hasnum) {
if (!ascii_isdigit(*p)) {
@@ -342,15 +343,15 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_
break;
}
- table[idx].number = getdigits_int((char **)&p, false, 0);
+ table[idx].number = getdigits_int(&p, false, 0);
}
- table[idx].string = p;
+ table[idx].string = (char_u *)p;
table[idx].strlen = (int)(commap - p);
- stringp = commap;
+ stringp = (char_u *)commap;
if (*stringp == ',') {
- ++stringp;
+ stringp++;
}
}
@@ -632,8 +633,8 @@ void ex_hardcopy(exarg_T *eap)
int page_line;
int jobsplit;
- memset(&settings, 0, sizeof(prt_settings_T));
- settings.has_color = TRUE;
+ CLEAR_FIELD(settings);
+ settings.has_color = true;
if (*eap->arg == '>') {
char *errormsg = NULL;
@@ -734,7 +735,7 @@ void ex_hardcopy(exarg_T *eap)
prt_pos_T page_prtpos; // print position at page start
int side;
- memset(&page_prtpos, 0, sizeof(prt_pos_T));
+ CLEAR_FIELD(page_prtpos);
page_prtpos.file_line = eap->line1;
prtpos = page_prtpos;
@@ -837,7 +838,7 @@ void ex_hardcopy(exarg_T *eap)
}
}
if (settings.duplex && prtpos.file_line <= eap->line2) {
- ++page_count;
+ page_count++;
}
// Remember the position where the next page starts.
@@ -900,7 +901,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
}
// syntax highlighting stuff.
if (psettings->do_syntax) {
- id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, FALSE);
+ id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, false);
if (id > 0) {
id = syn_get_final_id(id);
} else {
@@ -1718,7 +1719,7 @@ static bool prt_open_resource(struct prt_ps_resource_S *resource)
semsg(_("E624: Can't open file \"%s\""), resource->filename);
return false;
}
- memset(prt_resfile.buffer, NUL, PRT_FILE_BUFFER_LEN);
+ CLEAR_FIELD(prt_resfile.buffer);
// Parse first line to ensure valid resource file
prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u),
diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c
index 95ae7a152c..951e72ea52 100644
--- a/src/nvim/hashtab.c
+++ b/src/nvim/hashtab.c
@@ -45,7 +45,7 @@ char hash_removed;
void hash_init(hashtab_T *ht)
{
// This zeroes all "ht_" entries and all the "hi_key" in "ht_smallarray".
- memset(ht, 0, sizeof(hashtab_T));
+ CLEAR_POINTER(ht);
ht->ht_array = ht->ht_smallarray;
ht->ht_mask = HT_INIT_SIZE - 1;
}
@@ -342,11 +342,13 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems)
hashitem_T *oldarray = keep_smallarray
? memcpy(temparray, ht->ht_smallarray, sizeof(temparray))
: ht->ht_array;
+
+ if (newarray_is_small) {
+ CLEAR_FIELD(ht->ht_smallarray);
+ }
hashitem_T *newarray = newarray_is_small
? ht->ht_smallarray
- : xmalloc(sizeof(hashitem_T) * newsize);
-
- memset(newarray, 0, sizeof(hashitem_T) * newsize);
+ : xcalloc(newsize, sizeof(hashitem_T));
// Move all the items from the old array to the new one, placing them in
// the right spot. The new array won't have any removed items, thus this
diff --git a/src/nvim/help.c b/src/nvim/help.c
new file mode 100644
index 0000000000..172d389e74
--- /dev/null
+++ b/src/nvim/help.c
@@ -0,0 +1,1178 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// help.c: functions for Vim help
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/fileio.h"
+#include "nvim/garray.h"
+#include "nvim/globals.h"
+#include "nvim/help.h"
+#include "nvim/memory.h"
+#include "nvim/option.h"
+#include "nvim/os/input.h"
+#include "nvim/path.h"
+#include "nvim/strings.h"
+#include "nvim/syntax.h"
+#include "nvim/tag.h"
+#include "nvim/vim.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "help.c.generated.h"
+#endif
+
+/// ":help": open a read-only window on a help file
+void ex_help(exarg_T *eap)
+{
+ char *arg;
+ char *tag;
+ FILE *helpfd; // file descriptor of help file
+ int n;
+ int i;
+ win_T *wp;
+ int num_matches;
+ char **matches;
+ char *p;
+ int empty_fnum = 0;
+ int alt_fnum = 0;
+ buf_T *buf;
+ int len;
+ char *lang;
+ const bool old_KeyTyped = KeyTyped;
+
+ if (eap != NULL) {
+ // A ":help" command ends at the first LF, or at a '|' that is
+ // followed by some text. Set nextcmd to the following command.
+ for (arg = eap->arg; *arg; arg++) {
+ if (*arg == '\n' || *arg == '\r'
+ || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) {
+ *arg++ = NUL;
+ eap->nextcmd = arg;
+ break;
+ }
+ }
+ arg = eap->arg;
+
+ if (eap->forceit && *arg == NUL && !curbuf->b_help) {
+ emsg(_("E478: Don't panic!"));
+ return;
+ }
+
+ if (eap->skip) { // not executing commands
+ return;
+ }
+ } else {
+ arg = "";
+ }
+
+ // remove trailing blanks
+ p = arg + STRLEN(arg) - 1;
+ while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') {
+ *p-- = NUL;
+ }
+
+ // Check for a specified language
+ lang = check_help_lang(arg);
+
+ // When no argument given go to the index.
+ if (*arg == NUL) {
+ arg = "help.txt";
+ }
+
+ // Check if there is a match for the argument.
+ n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit);
+
+ i = 0;
+ if (n != FAIL && lang != NULL) {
+ // Find first item with the requested language.
+ for (i = 0; i < num_matches; i++) {
+ len = (int)STRLEN(matches[i]);
+ if (len > 3 && matches[i][len - 3] == '@'
+ && STRICMP(matches[i] + len - 2, lang) == 0) {
+ break;
+ }
+ }
+ }
+ if (i >= num_matches || n == FAIL) {
+ if (lang != NULL) {
+ semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg);
+ } else {
+ semsg(_("E149: Sorry, no help for %s"), arg);
+ }
+ if (n != FAIL) {
+ FreeWild(num_matches, matches);
+ }
+ return;
+ }
+
+ // The first match (in the requested language) is the best match.
+ tag = xstrdup(matches[i]);
+ FreeWild(num_matches, matches);
+
+ // Re-use an existing help window or open a new one.
+ // Always open a new one for ":tab help".
+ if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) {
+ if (cmdmod.cmod_tab != 0) {
+ wp = NULL;
+ } else {
+ wp = NULL;
+ FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
+ if (bt_help(wp2->w_buffer)) {
+ wp = wp2;
+ break;
+ }
+ }
+ }
+ if (wp != NULL && wp->w_buffer->b_nwindows > 0) {
+ win_enter(wp, true);
+ } else {
+ // There is no help window yet.
+ // Try to open the file specified by the "helpfile" option.
+ if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) {
+ smsg(_("Sorry, help file \"%s\" not found"), p_hf);
+ goto erret;
+ }
+ fclose(helpfd);
+
+ // Split off help window; put it at far top if no position
+ // specified, the current window is vertically split and
+ // narrow.
+ n = WSP_HELP;
+ if (cmdmod.cmod_split == 0 && curwin->w_width != Columns
+ && curwin->w_width < 80) {
+ n |= WSP_TOP;
+ }
+ if (win_split(0, n) == FAIL) {
+ goto erret;
+ }
+
+ if (curwin->w_height < p_hh) {
+ win_setheight((int)p_hh);
+ }
+
+ // Open help file (do_ecmd() will set b_help flag, readfile() will
+ // set b_p_ro flag).
+ // Set the alternate file to the previously edited file.
+ alt_fnum = curbuf->b_fnum;
+ (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL,
+ ECMD_HIDE + ECMD_SET_HELP,
+ NULL); // buffer is still open, don't store info
+
+ if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
+ curwin->w_alt_fnum = alt_fnum;
+ }
+ empty_fnum = curbuf->b_fnum;
+ }
+ }
+
+ restart_edit = 0; // don't want insert mode in help file
+
+ // Restore KeyTyped, setting 'filetype=help' may reset it.
+ // It is needed for do_tag top open folds under the cursor.
+ KeyTyped = old_KeyTyped;
+
+ do_tag((char_u *)tag, DT_HELP, 1, false, true);
+
+ // Delete the empty buffer if we're not using it. Careful: autocommands
+ // may have jumped to another window, check that the buffer is not in a
+ // window.
+ if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) {
+ buf = buflist_findnr(empty_fnum);
+ if (buf != NULL && buf->b_nwindows == 0) {
+ wipe_buffer(buf, true);
+ }
+ }
+
+ // keep the previous alternate file
+ if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum
+ && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
+ curwin->w_alt_fnum = alt_fnum;
+ }
+
+erret:
+ xfree(tag);
+}
+
+/// ":helpclose": Close one help window
+void ex_helpclose(exarg_T *eap)
+{
+ FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
+ if (bt_help(win->w_buffer)) {
+ win_close(win, false, eap->forceit);
+ return;
+ }
+ }
+}
+
+/// In an argument search for a language specifiers in the form "@xx".
+/// Changes the "@" to NUL if found, and returns a pointer to "xx".
+///
+/// @return NULL if not found.
+char *check_help_lang(char *arg)
+{
+ int len = (int)STRLEN(arg);
+
+ if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2])
+ && ASCII_ISALPHA(arg[len - 1])) {
+ arg[len - 3] = NUL; // remove the '@'
+ return arg + len - 2;
+ }
+ return NULL;
+}
+
+/// Return a heuristic indicating how well the given string matches. The
+/// smaller the number, the better the match. This is the order of priorities,
+/// from best match to worst match:
+/// - Match with least alphanumeric characters is better.
+/// - Match with least total characters is better.
+/// - Match towards the start is better.
+/// - Match starting with "+" is worse (feature instead of command)
+/// Assumption is made that the matched_string passed has already been found to
+/// match some string for which help is requested. webb.
+///
+/// @param offset offset for match
+/// @param wrong_case no matching case
+///
+/// @return a heuristic indicating how well the given string matches.
+int help_heuristic(char *matched_string, int offset, int wrong_case)
+ FUNC_ATTR_PURE
+{
+ int num_letters;
+ char *p;
+
+ num_letters = 0;
+ for (p = matched_string; *p; p++) {
+ if (ASCII_ISALNUM(*p)) {
+ num_letters++;
+ }
+ }
+
+ // Multiply the number of letters by 100 to give it a much bigger
+ // weighting than the number of characters.
+ // If there only is a match while ignoring case, add 5000.
+ // If the match starts in the middle of a word, add 10000 to put it
+ // somewhere in the last half.
+ // If the match is more than 2 chars from the start, multiply by 200 to
+ // put it after matches at the start.
+ if (offset > 0
+ && ASCII_ISALNUM(matched_string[offset])
+ && ASCII_ISALNUM(matched_string[offset - 1])) {
+ offset += 10000;
+ } else if (offset > 2) {
+ offset *= 200;
+ }
+ if (wrong_case) {
+ offset += 5000;
+ }
+ // Features are less interesting than the subjects themselves, but "+"
+ // alone is not a feature.
+ if (matched_string[0] == '+' && matched_string[1] != NUL) {
+ offset += 100;
+ }
+ return 100 * num_letters + (int)STRLEN(matched_string) + offset;
+}
+
+/// Compare functions for qsort() below, that checks the help heuristics number
+/// that has been put after the tagname by find_tags().
+static int help_compare(const void *s1, const void *s2)
+{
+ char *p1;
+ char *p2;
+
+ p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
+ p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
+
+ // Compare by help heuristic number first.
+ int cmp = strcmp(p1, p2);
+ if (cmp != 0) {
+ return cmp;
+ }
+
+ // Compare by strings as tie-breaker when same heuristic number.
+ return strcmp(*(char **)s1, *(char **)s2);
+}
+
+/// Find all help tags matching "arg", sort them and return in matches[], with
+/// the number of matches in num_matches.
+/// The matches will be sorted with a "best" match algorithm.
+/// When "keep_lang" is true try keeping the language of the current buffer.
+int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang)
+{
+ int i;
+
+ // Specific tags that either have a specific replacement or won't go
+ // through the generic rules.
+ static char *(except_tbl[][2]) = {
+ { "*", "star" },
+ { "g*", "gstar" },
+ { "[*", "[star" },
+ { "]*", "]star" },
+ { ":*", ":star" },
+ { "/*", "/star" }, // NOLINT
+ { "/\\*", "/\\\\star" },
+ { "\"*", "quotestar" },
+ { "**", "starstar" },
+ { "cpo-*", "cpo-star" },
+ { "/\\(\\)", "/\\\\(\\\\)" },
+ { "/\\%(\\)", "/\\\\%(\\\\)" },
+ { "?", "?" },
+ { "??", "??" },
+ { ":?", ":?" },
+ { "?<CR>", "?<CR>" },
+ { "g?", "g?" },
+ { "g?g?", "g?g?" },
+ { "g??", "g??" },
+ { "-?", "-?" },
+ { "q?", "q?" },
+ { "v_g?", "v_g?" },
+ { "/\\?", "/\\\\?" },
+ { "/\\z(\\)", "/\\\\z(\\\\)" },
+ { "\\=", "\\\\=" },
+ { ":s\\=", ":s\\\\=" },
+ { "[count]", "\\[count]" },
+ { "[quotex]", "\\[quotex]" },
+ { "[range]", "\\[range]" },
+ { ":[range]", ":\\[range]" },
+ { "[pattern]", "\\[pattern]" },
+ { "\\|", "\\\\bar" },
+ { "\\%$", "/\\\\%\\$" },
+ { "s/\\~", "s/\\\\\\~" },
+ { "s/\\U", "s/\\\\U" },
+ { "s/\\L", "s/\\\\L" },
+ { "s/\\1", "s/\\\\1" },
+ { "s/\\2", "s/\\\\2" },
+ { "s/\\3", "s/\\\\3" },
+ { "s/\\9", "s/\\\\9" },
+ { NULL, NULL }
+ };
+
+ static const char *(expr_table[]) = {
+ "!=?", "!~?", "<=?", "<?", "==?", "=~?",
+ ">=?", ">?", "is?", "isnot?"
+ };
+ char *d = (char *)IObuff; // assume IObuff is long enough!
+ d[0] = NUL;
+
+ if (STRNICMP(arg, "expr-", 5) == 0) {
+ // When the string starting with "expr-" and containing '?' and matches
+ // the table, it is taken literally (but ~ is escaped). Otherwise '?'
+ // is recognized as a wildcard.
+ for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) {
+ if (STRCMP(arg + 5, expr_table[i]) == 0) {
+ for (int si = 0, di = 0;; si++) {
+ if (arg[si] == '~') {
+ d[di++] = '\\';
+ }
+ d[di++] = arg[si];
+ if (arg[si] == NUL) {
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ // Recognize a few exceptions to the rule. Some strings that contain
+ // '*'are changed to "star", otherwise '*' is recognized as a wildcard.
+ for (i = 0; except_tbl[i][0] != NULL; i++) {
+ if (STRCMP(arg, except_tbl[i][0]) == 0) {
+ STRCPY(d, except_tbl[i][1]);
+ break;
+ }
+ }
+ }
+
+ if (d[0] == NUL) { // no match in table
+ // Replace "\S" with "/\\S", etc. Otherwise every tag is matched.
+ // Also replace "\%^" and "\%(", they match every tag too.
+ // Also "\zs", "\z1", etc.
+ // Also "\@<", "\@=", "\@<=", etc.
+ // And also "\_$" and "\_^".
+ if (arg[0] == '\\'
+ && ((arg[1] != NUL && arg[2] == NUL)
+ || (vim_strchr("%_z@", arg[1]) != NULL
+ && arg[2] != NUL))) {
+ vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1);
+ // Check for "/\\_$", should be "/\\_\$"
+ if (d[3] == '_' && d[4] == '$') {
+ STRCPY(d + 4, "\\$");
+ }
+ } else {
+ // Replace:
+ // "[:...:]" with "\[:...:]"
+ // "[++...]" with "\[++...]"
+ // "\{" with "\\{" -- matching "} \}"
+ if ((arg[0] == '[' && (arg[1] == ':'
+ || (arg[1] == '+' && arg[2] == '+')))
+ || (arg[0] == '\\' && arg[1] == '{')) {
+ *d++ = '\\';
+ }
+
+ // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'.
+ if (*arg == '(' && arg[1] == '\'') {
+ arg++;
+ }
+ for (const char *s = arg; *s; s++) {
+ // Replace "|" with "bar" and '"' with "quote" to match the name of
+ // the tags for these commands.
+ // Replace "*" with ".*" and "?" with "." to match command line
+ // completion.
+ // Insert a backslash before '~', '$' and '.' to avoid their
+ // special meaning.
+ if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!?
+ break;
+ }
+ switch (*s) {
+ case '|':
+ STRCPY(d, "bar");
+ d += 3;
+ continue;
+ case '"':
+ STRCPY(d, "quote");
+ d += 5;
+ continue;
+ case '*':
+ *d++ = '.';
+ break;
+ case '?':
+ *d++ = '.';
+ continue;
+ case '$':
+ case '.':
+ case '~':
+ *d++ = '\\';
+ break;
+ }
+
+ // Replace "^x" by "CTRL-X". Don't do this for "^_" to make
+ // ":help i_^_CTRL-D" work.
+ // Insert '-' before and after "CTRL-X" when applicable.
+ if (*s < ' '
+ || (*s == '^' && s[1]
+ && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) {
+ if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') {
+ *d++ = '_'; // prepend a '_' to make x_CTRL-x
+ }
+ STRCPY(d, "CTRL-");
+ d += 5;
+ if (*s < ' ') {
+ *d++ = (char)(*s + '@');
+ if (d[-1] == '\\') {
+ *d++ = '\\'; // double a backslash
+ }
+ } else {
+ *d++ = *++s;
+ }
+ if (s[1] != NUL && s[1] != '_') {
+ *d++ = '_'; // append a '_'
+ }
+ continue;
+ } else if (*s == '^') { // "^" or "CTRL-^" or "^_"
+ *d++ = '\\';
+ } else if (s[0] == '\\' && s[1] != '\\' && *arg == '/' && s == arg + 1) {
+ // Insert a backslash before a backslash after a slash, for search
+ // pattern tags: "/\|" --> "/\\|".
+ *d++ = '\\';
+ }
+
+ // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in
+ // "CTRL-\_CTRL-N"
+ if (STRNICMP(s, "CTRL-\\_", 7) == 0) {
+ STRCPY(d, "CTRL-\\\\");
+ d += 7;
+ s += 6;
+ }
+
+ *d++ = *s;
+
+ // If tag contains "({" or "([", tag terminates at the "(".
+ // This is for help on functions, e.g.: abs({expr}).
+ if (*s == '(' && (s[1] == '{' || s[1] == '[')) {
+ break;
+ }
+
+ // If tag starts with ', toss everything after a second '. Fixes
+ // CTRL-] on 'option'. (would include the trailing '.').
+ if (*s == '\'' && s > arg && *arg == '\'') {
+ break;
+ }
+ // Also '{' and '}'. Fixes CTRL-] on '{address}'.
+ if (*s == '}' && s > arg && *arg == '{') {
+ break;
+ }
+ }
+ *d = NUL;
+
+ if (*IObuff == '`') {
+ if ((char_u *)d > IObuff + 2 && d[-1] == '`') {
+ // remove the backticks from `command`
+ memmove(IObuff, IObuff + 1, STRLEN(IObuff));
+ d[-2] = NUL;
+ } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') {
+ // remove the backticks and comma from `command`,
+ memmove(IObuff, IObuff + 1, STRLEN(IObuff));
+ d[-3] = NUL;
+ } else if ((char_u *)d > IObuff + 4 && d[-3] == '`'
+ && d[-2] == '\\' && d[-1] == '.') {
+ // remove the backticks and dot from `command`\.
+ memmove(IObuff, IObuff + 1, STRLEN(IObuff));
+ d[-4] = NUL;
+ }
+ }
+ }
+ }
+
+ *matches = NULL;
+ *num_matches = 0;
+ int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
+ if (keep_lang) {
+ flags |= TAG_KEEP_LANG;
+ }
+ if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK
+ && *num_matches > 0) {
+ // Sort the matches found on the heuristic number that is after the
+ // tag name.
+ qsort((void *)(*matches), (size_t)(*num_matches),
+ sizeof(char_u *), help_compare);
+ // Delete more than TAG_MANY to reduce the size of the listing.
+ while (*num_matches > TAG_MANY) {
+ xfree((*matches)[--*num_matches]);
+ }
+ }
+ return OK;
+}
+
+/// Cleanup matches for help tags:
+/// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first
+/// tag matches it. Otherwise remove "@en" if "en" is the only language.
+void cleanup_help_tags(int num_file, char **file)
+{
+ char_u buf[4];
+ char_u *p = buf;
+
+ if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) {
+ *p++ = '@';
+ *p++ = p_hlg[0];
+ *p++ = p_hlg[1];
+ }
+ *p = NUL;
+
+ for (int i = 0; i < num_file; i++) {
+ int len = (int)STRLEN(file[i]) - 3;
+ if (len <= 0) {
+ continue;
+ }
+ if (STRCMP(file[i] + len, "@en") == 0) {
+ // Sorting on priority means the same item in another language may
+ // be anywhere. Search all items for a match up to the "@en".
+ int j;
+ for (j = 0; j < num_file; j++) {
+ if (j != i
+ && (int)STRLEN(file[j]) == len + 3
+ && STRNCMP(file[i], file[j], len + 1) == 0) {
+ break;
+ }
+ }
+ if (j == num_file) {
+ // item only exists with @en, remove it
+ file[i][len] = NUL;
+ }
+ }
+ }
+
+ if (*buf != NUL) {
+ for (int i = 0; i < num_file; i++) {
+ int len = (int)STRLEN(file[i]) - 3;
+ if (len <= 0) {
+ continue;
+ }
+ if (STRCMP(file[i] + len, buf) == 0) {
+ // remove the default language
+ file[i][len] = NUL;
+ }
+ }
+ }
+}
+
+/// Called when starting to edit a buffer for a help file.
+void prepare_help_buffer(void)
+{
+ curbuf->b_help = true;
+ set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0);
+
+ // Always set these options after jumping to a help tag, because the
+ // user may have an autocommand that gets in the way.
+ // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and
+ // latin1 word characters (for translated help files).
+ // Only set it when needed, buf_init_chartab() is some work.
+ char *p = "!-~,^*,^|,^\",192-255";
+ if (STRCMP(curbuf->b_p_isk, p) != 0) {
+ set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
+ check_buf_options(curbuf);
+ (void)buf_init_chartab(curbuf, false);
+ }
+
+ // Don't use the global foldmethod.
+ set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0);
+
+ curbuf->b_p_ts = 8; // 'tabstop' is 8.
+ curwin->w_p_list = false; // No list mode.
+
+ curbuf->b_p_ma = false; // Not modifiable.
+ curbuf->b_p_bin = false; // Reset 'bin' before reading file.
+ curwin->w_p_nu = 0; // No line numbers.
+ curwin->w_p_rnu = 0; // No relative line numbers.
+ RESET_BINDING(curwin); // No scroll or cursor binding.
+ curwin->w_p_arab = false; // No arabic mode.
+ curwin->w_p_rl = false; // Help window is left-to-right.
+ curwin->w_p_fen = false; // No folding in the help window.
+ curwin->w_p_diff = false; // No 'diff'.
+ curwin->w_p_spell = false; // No spell checking.
+
+ set_buflisted(false);
+}
+
+/// After reading a help file: May cleanup a help buffer when syntax
+/// highlighting is not used.
+void fix_help_buffer(void)
+{
+ linenr_T lnum;
+ char *line;
+ bool in_example = false;
+
+ // Set filetype to "help".
+ if (STRCMP(curbuf->b_p_ft, "help") != 0) {
+ curbuf->b_ro_locked++;
+ set_option_value("ft", 0L, "help", OPT_LOCAL);
+ curbuf->b_ro_locked--;
+ }
+
+ if (!syntax_present(curwin)) {
+ for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
+ line = (char *)ml_get_buf(curbuf, lnum, false);
+ const size_t len = STRLEN(line);
+ if (in_example && len > 0 && !ascii_iswhite(line[0])) {
+ // End of example: non-white or '<' in first column.
+ if (line[0] == '<') {
+ // blank-out a '<' in the first column
+ line = (char *)ml_get_buf(curbuf, lnum, true);
+ line[0] = ' ';
+ }
+ in_example = false;
+ }
+ if (!in_example && len > 0) {
+ if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) {
+ // blank-out a '>' in the last column (start of example)
+ line = (char *)ml_get_buf(curbuf, lnum, true);
+ line[len - 1] = ' ';
+ in_example = true;
+ } else if (line[len - 1] == '~') {
+ // blank-out a '~' at the end of line (header marker)
+ line = (char *)ml_get_buf(curbuf, lnum, true);
+ line[len - 1] = ' ';
+ }
+ }
+ }
+ }
+
+ // In the "help.txt" and "help.abx" file, add the locally added help
+ // files. This uses the very first line in the help file.
+ char *const fname = path_tail(curbuf->b_fname);
+ if (FNAMECMP(fname, "help.txt") == 0
+ || (FNAMENCMP(fname, "help.", 5) == 0
+ && ASCII_ISALPHA(fname[5])
+ && ASCII_ISALPHA(fname[6])
+ && TOLOWER_ASC(fname[7]) == 'x'
+ && fname[8] == NUL)) {
+ for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
+ line = (char *)ml_get_buf(curbuf, lnum, false);
+ if (strstr(line, "*local-additions*") == NULL) {
+ continue;
+ }
+
+ // Go through all directories in 'runtimepath', skipping
+ // $VIMRUNTIME.
+ char *p = (char *)p_rtp;
+ while (*p != NUL) {
+ copy_option_part(&p, (char *)NameBuff, MAXPATHL, ",");
+ char *const rt = vim_getenv("VIMRUNTIME");
+ if (rt != NULL
+ && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) {
+ int fcount;
+ char **fnames;
+ char *s;
+ vimconv_T vc;
+ char *cp;
+
+ // Find all "doc/ *.txt" files in this directory.
+ if (!add_pathsep((char *)NameBuff)
+ || STRLCAT(NameBuff, "doc/*.??[tx]", // NOLINT
+ sizeof(NameBuff)) >= MAXPATHL) {
+ emsg(_(e_fnametoolong));
+ continue;
+ }
+
+ // Note: We cannot just do `&NameBuff` because it is a statically sized array
+ // so `NameBuff == &NameBuff` according to C semantics.
+ char *buff_list[1] = { (char *)NameBuff };
+ if (gen_expand_wildcards(1, buff_list, &fcount,
+ &fnames, EW_FILE|EW_SILENT) == OK
+ && fcount > 0) {
+ // If foo.abx is found use it instead of foo.txt in
+ // the same directory.
+ for (int i1 = 0; i1 < fcount; i1++) {
+ for (int i2 = 0; i2 < fcount; i2++) {
+ if (i1 == i2) {
+ continue;
+ }
+ if (fnames[i1] == NULL || fnames[i2] == NULL) {
+ continue;
+ }
+ const char *const f1 = fnames[i1];
+ const char *const f2 = fnames[i2];
+ const char *const t1 = path_tail(f1);
+ const char *const t2 = path_tail(f2);
+ const char *const e1 = (char *)STRRCHR(t1, '.');
+ const char *const e2 = (char *)STRRCHR(t2, '.');
+ if (e1 == NULL || e2 == NULL) {
+ continue;
+ }
+ if (FNAMECMP(e1, ".txt") != 0
+ && FNAMECMP(e1, fname + 4) != 0) {
+ // Not .txt and not .abx, remove it.
+ XFREE_CLEAR(fnames[i1]);
+ continue;
+ }
+ if (e1 - f1 != e2 - f2
+ || FNAMENCMP(f1, f2, e1 - f1) != 0) {
+ continue;
+ }
+ if (FNAMECMP(e1, ".txt") == 0
+ && FNAMECMP(e2, fname + 4) == 0) {
+ // use .abx instead of .txt
+ XFREE_CLEAR(fnames[i1]);
+ }
+ }
+ }
+ for (int fi = 0; fi < fcount; fi++) {
+ if (fnames[fi] == NULL) {
+ continue;
+ }
+
+ FILE *const fd = os_fopen(fnames[fi], "r");
+ if (fd == NULL) {
+ continue;
+ }
+ vim_fgets(IObuff, IOSIZE, fd);
+ if (IObuff[0] == '*'
+ && (s = vim_strchr((char *)IObuff + 1, '*'))
+ != NULL) {
+ TriState this_utf = kNone;
+ // Change tag definition to a
+ // reference and remove <CR>/<NL>.
+ IObuff[0] = '|';
+ *s = '|';
+ while (*s != NUL) {
+ if (*s == '\r' || *s == '\n') {
+ *s = NUL;
+ }
+ // The text is utf-8 when a byte
+ // above 127 is found and no
+ // illegal byte sequence is found.
+ if ((char_u)(*s) >= 0x80 && this_utf != kFalse) {
+ this_utf = kTrue;
+ const int l = utf_ptr2len(s);
+ if (l == 1) {
+ this_utf = kFalse;
+ }
+ s += l - 1;
+ }
+ s++;
+ }
+ // The help file is latin1 or utf-8;
+ // conversion to the current
+ // 'encoding' may be required.
+ vc.vc_type = CONV_NONE;
+ convert_setup(&vc,
+ (char_u *)(this_utf == kTrue ? "utf-8" : "latin1"),
+ p_enc);
+ if (vc.vc_type == CONV_NONE) {
+ // No conversion needed.
+ cp = (char *)IObuff;
+ } else {
+ // Do the conversion. If it fails
+ // use the unconverted text.
+ cp = (char *)string_convert(&vc, IObuff, NULL);
+ if (cp == NULL) {
+ cp = (char *)IObuff;
+ }
+ }
+ convert_setup(&vc, NULL, NULL);
+
+ ml_append(lnum, cp, (colnr_T)0, false);
+ if ((char_u *)cp != IObuff) {
+ xfree(cp);
+ }
+ lnum++;
+ }
+ fclose(fd);
+ }
+ FreeWild(fcount, fnames);
+ }
+ }
+ xfree(rt);
+ }
+ break;
+ }
+ }
+}
+
+/// ":exusage"
+void ex_exusage(exarg_T *eap)
+{
+ do_cmdline_cmd("help ex-cmd-index");
+}
+
+/// ":viusage"
+void ex_viusage(exarg_T *eap)
+{
+ do_cmdline_cmd("help normal-index");
+}
+
+/// Generate tags in one help directory
+///
+/// @param dir Path to the doc directory
+/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.)
+/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for
+/// French)
+/// @param add_help_tags Whether to add the "help-tags" tag
+/// @param ignore_writeerr ignore write error
+static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags,
+ bool ignore_writeerr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ garray_T ga;
+ int filecount;
+ char **files;
+ char *p1, *p2;
+ char *s;
+ TriState utf8 = kNone;
+ bool mix = false; // detected mixed encodings
+
+ // Find all *.txt files.
+ size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff));
+ if (dirlen >= MAXPATHL
+ || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT
+ || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) {
+ emsg(_(e_fnametoolong));
+ return;
+ }
+
+ // Note: We cannot just do `&NameBuff` because it is a statically sized array
+ // so `NameBuff == &NameBuff` according to C semantics.
+ char *buff_list[1] = { (char *)NameBuff };
+ const int res = gen_expand_wildcards(1, buff_list, &filecount, &files,
+ EW_FILE|EW_SILENT);
+ if (res == FAIL || filecount == 0) {
+ if (!got_int) {
+ semsg(_("E151: No match: %s"), NameBuff);
+ }
+ if (res != FAIL) {
+ FreeWild(filecount, files);
+ }
+ return;
+ }
+
+ // Open the tags file for writing.
+ // Do this before scanning through all the files.
+ memcpy(NameBuff, dir, dirlen + 1);
+ if (!add_pathsep((char *)NameBuff)
+ || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) {
+ emsg(_(e_fnametoolong));
+ return;
+ }
+
+ FILE *const fd_tags = os_fopen((char *)NameBuff, "w");
+ if (fd_tags == NULL) {
+ if (!ignore_writeerr) {
+ semsg(_("E152: Cannot open %s for writing"), NameBuff);
+ }
+ FreeWild(filecount, files);
+ return;
+ }
+
+ // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
+ // add the "help-tags" tag.
+ ga_init(&ga, (int)sizeof(char_u *), 100);
+ if (add_help_tags
+ || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) {
+ size_t s_len = 18 + STRLEN(tagfname);
+ s = xmalloc(s_len);
+ snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname);
+ GA_APPEND(char *, &ga, s);
+ }
+
+ // Go over all the files and extract the tags.
+ for (int fi = 0; fi < filecount && !got_int; fi++) {
+ FILE *const fd = os_fopen(files[fi], "r");
+ if (fd == NULL) {
+ semsg(_("E153: Unable to open %s for reading"), files[fi]);
+ continue;
+ }
+ const char *const fname = files[fi] + dirlen + 1;
+
+ bool firstline = true;
+ while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
+ if (firstline) {
+ // Detect utf-8 file by a non-ASCII char in the first line.
+ TriState this_utf8 = kNone;
+ for (s = (char *)IObuff; *s != NUL; s++) {
+ if ((char_u)(*s) >= 0x80) {
+ this_utf8 = kTrue;
+ const int l = utf_ptr2len(s);
+ if (l == 1) {
+ // Illegal UTF-8 byte sequence.
+ this_utf8 = kFalse;
+ break;
+ }
+ s += l - 1;
+ }
+ }
+ if (this_utf8 == kNone) { // only ASCII characters found
+ this_utf8 = kFalse;
+ }
+ if (utf8 == kNone) { // first file
+ utf8 = this_utf8;
+ } else if (utf8 != this_utf8) {
+ semsg(_("E670: Mix of help file encodings within a language: %s"),
+ files[fi]);
+ mix = !got_int;
+ got_int = true;
+ }
+ firstline = false;
+ }
+ p1 = vim_strchr((char *)IObuff, '*'); // find first '*'
+ while (p1 != NULL) {
+ p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'.
+ if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**".
+ for (s = p1 + 1; s < p2; s++) {
+ if (*s == ' ' || *s == '\t' || *s == '|') {
+ break;
+ }
+ }
+
+ // Only accept a *tag* when it consists of valid
+ // characters, there is white space before it and is
+ // followed by a white character or end-of-line.
+ if (s == p2
+ && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
+ && (vim_strchr(" \t\n\r", s[1]) != NULL
+ || s[1] == '\0')) {
+ *p2 = '\0';
+ p1++;
+ size_t s_len= (size_t)(p2 - p1) + STRLEN(fname) + 2;
+ s = xmalloc(s_len);
+ GA_APPEND(char *, &ga, s);
+ snprintf(s, s_len, "%s\t%s", p1, fname);
+
+ // find next '*'
+ p2 = vim_strchr(p2 + 1, '*');
+ }
+ }
+ p1 = p2;
+ }
+ line_breakcheck();
+ }
+
+ fclose(fd);
+ }
+
+ FreeWild(filecount, files);
+
+ if (!got_int && ga.ga_data != NULL) {
+ // Sort the tags.
+ sort_strings(ga.ga_data, ga.ga_len);
+
+ // Check for duplicates.
+ for (int i = 1; i < ga.ga_len; i++) {
+ p1 = ((char **)ga.ga_data)[i - 1];
+ p2 = ((char **)ga.ga_data)[i];
+ while (*p1 == *p2) {
+ if (*p2 == '\t') {
+ *p2 = NUL;
+ vim_snprintf((char *)NameBuff, MAXPATHL,
+ _("E154: Duplicate tag \"%s\" in file %s/%s"),
+ ((char_u **)ga.ga_data)[i], dir, p2 + 1);
+ emsg((char *)NameBuff);
+ *p2 = '\t';
+ break;
+ }
+ p1++;
+ p2++;
+ }
+ }
+
+ if (utf8 == kTrue) {
+ fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
+ }
+
+ // Write the tags into the file.
+ for (int i = 0; i < ga.ga_len; i++) {
+ s = ((char **)ga.ga_data)[i];
+ if (STRNCMP(s, "help-tags\t", 10) == 0) {
+ // help-tags entry was added in formatted form
+ fputs(s, fd_tags);
+ } else {
+ fprintf(fd_tags, "%s\t/" "*", s);
+ for (p1 = s; *p1 != '\t'; p1++) {
+ // insert backslash before '\\' and '/'
+ if (*p1 == '\\' || *p1 == '/') {
+ putc('\\', fd_tags);
+ }
+ putc(*p1, fd_tags);
+ }
+ fprintf(fd_tags, "*\n");
+ }
+ }
+ }
+ if (mix) {
+ got_int = false; // continue with other languages
+ }
+
+ GA_DEEP_CLEAR_PTR(&ga);
+ fclose(fd_tags); // there is no check for an error...
+}
+
+/// Generate tags in one help directory, taking care of translations.
+static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int len;
+ garray_T ga;
+ char lang[2];
+ char ext[5];
+ char fname[8];
+ int filecount;
+ char **files;
+
+ // Get a list of all files in the help directory and in subdirectories.
+ STRLCPY(NameBuff, dirname, sizeof(NameBuff));
+ if (!add_pathsep((char *)NameBuff)
+ || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) {
+ emsg(_(e_fnametoolong));
+ return;
+ }
+
+ // Note: We cannot just do `&NameBuff` because it is a statically sized array
+ // so `NameBuff == &NameBuff` according to C semantics.
+ char *buff_list[1] = { (char *)NameBuff };
+ if (gen_expand_wildcards(1, buff_list, &filecount, &files,
+ EW_FILE|EW_SILENT) == FAIL
+ || filecount == 0) {
+ semsg(_("E151: No match: %s"), NameBuff);
+ return;
+ }
+
+ // Go over all files in the directory to find out what languages are
+ // present.
+ int j;
+ ga_init(&ga, 1, 10);
+ for (int i = 0; i < filecount; i++) {
+ len = (int)STRLEN(files[i]);
+ if (len <= 4) {
+ continue;
+ }
+
+ if (STRICMP(files[i] + len - 4, ".txt") == 0) {
+ // ".txt" -> language "en"
+ lang[0] = 'e';
+ lang[1] = 'n';
+ } else if (files[i][len - 4] == '.'
+ && ASCII_ISALPHA(files[i][len - 3])
+ && ASCII_ISALPHA(files[i][len - 2])
+ && TOLOWER_ASC(files[i][len - 1]) == 'x') {
+ // ".abx" -> language "ab"
+ lang[0] = (char)TOLOWER_ASC(files[i][len - 3]);
+ lang[1] = (char)TOLOWER_ASC(files[i][len - 2]);
+ } else {
+ continue;
+ }
+
+ // Did we find this language already?
+ for (j = 0; j < ga.ga_len; j += 2) {
+ if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) {
+ break;
+ }
+ }
+ if (j == ga.ga_len) {
+ // New language, add it.
+ ga_grow(&ga, 2);
+ ((char *)ga.ga_data)[ga.ga_len++] = lang[0];
+ ((char *)ga.ga_data)[ga.ga_len++] = lang[1];
+ }
+ }
+
+ // Loop over the found languages to generate a tags file for each one.
+ for (j = 0; j < ga.ga_len; j += 2) {
+ STRCPY(fname, "tags-xx");
+ fname[5] = ((char *)ga.ga_data)[j];
+ fname[6] = ((char *)ga.ga_data)[j + 1];
+ if (fname[5] == 'e' && fname[6] == 'n') {
+ // English is an exception: use ".txt" and "tags".
+ fname[4] = NUL;
+ STRCPY(ext, ".txt");
+ } else {
+ // Language "ab" uses ".abx" and "tags-ab".
+ STRCPY(ext, ".xxx");
+ ext[1] = fname[5];
+ ext[2] = fname[6];
+ }
+ helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr);
+ }
+
+ ga_clear(&ga);
+ FreeWild(filecount, files);
+}
+
+static void helptags_cb(char *fname, void *cookie)
+ FUNC_ATTR_NONNULL_ALL
+{
+ do_helptags(fname, *(bool *)cookie, true);
+}
+
+/// ":helptags"
+void ex_helptags(exarg_T *eap)
+{
+ expand_T xpc;
+ char *dirname;
+ bool add_help_tags = false;
+
+ // Check for ":helptags ++t {dir}".
+ if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) {
+ add_help_tags = true;
+ eap->arg = skipwhite(eap->arg + 3);
+ }
+
+ if (STRCMP(eap->arg, "ALL") == 0) {
+ do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
+ } else {
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_DIRECTORIES;
+ dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL,
+ WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
+ if (dirname == NULL || !os_isdir((char_u *)dirname)) {
+ semsg(_("E150: Not a directory: %s"), eap->arg);
+ } else {
+ do_helptags(dirname, add_help_tags, false);
+ }
+ xfree(dirname);
+ }
+}
diff --git a/src/nvim/help.h b/src/nvim/help.h
new file mode 100644
index 0000000000..21e11392ee
--- /dev/null
+++ b/src/nvim/help.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_HELP_H
+#define NVIM_HELP_H
+
+#include <stdbool.h>
+
+#include "nvim/ex_cmds_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "help.h.generated.h"
+#endif
+#endif // NVIM_HELP_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 71c7194479..c26b00df79 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -6,6 +6,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/decoration_provider.h"
+#include "nvim/drawscreen.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
@@ -13,8 +14,7 @@
#include "nvim/map.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
@@ -32,7 +32,9 @@ static Map(int, int) blend_attr_entries = MAP_INIT;
static Map(int, int) blendthrough_attr_entries = MAP_INIT;
/// highlight entries private to a namespace
-static Map(ColorKey, ColorItem) ns_hl;
+static Map(ColorKey, ColorItem) ns_hls;
+typedef int NSHlAttr[HLF_COUNT + 1];
+static PMap(handle_T) ns_hl_attr;
void highlight_init(void)
{
@@ -147,42 +149,46 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict)
{
- if ((attrs.rgb_ae_attr & HL_DEFAULT)
- && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) {
- return;
- }
if (ns_id == 0) {
assert(dict);
// set in global (':highlight') namespace
set_hl_group(hl_id, attrs, dict, link_id);
return;
}
+ if ((attrs.rgb_ae_attr & HL_DEFAULT)
+ && map_has(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id))) {
+ return;
+ }
DecorProvider *p = get_decor_provider(ns_id, true);
int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs);
ColorItem it = { .attr_id = attr_id,
.link_id = link_id,
.version = p->hl_valid,
- .is_default = (attrs.rgb_ae_attr & HL_DEFAULT) };
- map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it);
+ .is_default = (attrs.rgb_ae_attr & HL_DEFAULT),
+ .link_global = (attrs.rgb_ae_attr & HL_GLOBAL) };
+ map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it);
+ p->hl_cached = false;
}
-int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
+int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
{
static int recursive = 0;
- if (ns_id < 0) {
+ if (*ns_hl < 0) {
if (ns_hl_active <= 0) {
return -1;
}
- ns_id = ns_hl_active;
+ *ns_hl = ns_hl_active;
}
+ int ns_id = *ns_hl;
+
DecorProvider *p = get_decor_provider(ns_id, true);
- ColorItem it = map_get(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id));
+ ColorItem it = map_get(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id));
// TODO(bfredl): map_ref true even this?
- bool valid_cache = it.version >= p->hl_valid;
+ bool valid_item = it.version >= p->hl_valid;
- if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) {
+ if (!valid_item && p->hl_def != LUA_NOREF && !recursive) {
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ((Integer)ns_id));
ADD_C(args, STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id))));
@@ -215,44 +221,76 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs);
it.version = p->hl_valid - tmp;
it.is_default = attrs.rgb_ae_attr & HL_DEFAULT;
- map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it);
+ it.link_global = attrs.rgb_ae_attr & HL_GLOBAL;
+ map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it);
+ valid_item = true;
}
- if (it.is_default && nodefault) {
+ if ((it.is_default && nodefault) || !valid_item) {
return -1;
}
if (link) {
- return it.attr_id >= 0 ? 0 : it.link_id;
+ if (it.attr_id >= 0) {
+ return 0;
+ } else {
+ if (it.link_global) {
+ *ns_hl = 0;
+ }
+ return it.link_id;
+ }
} else {
return it.attr_id;
}
}
-bool win_check_ns_hl(win_T *wp)
+bool hl_check_ns(void)
{
- if (ns_hl_changed) {
- highlight_changed();
- if (wp) {
- update_window_hl(wp, true);
+ int ns = 0;
+ if (ns_hl_fast > 0) {
+ ns = ns_hl_fast;
+ } else if (ns_hl_win >= 0) {
+ ns = ns_hl_win;
+ } else {
+ ns = ns_hl_global;
+ }
+ if (ns_hl_active == ns) {
+ return false;
+ }
+
+ ns_hl_active = ns;
+ hl_attr_active = highlight_attr;
+ if (ns > 0) {
+ update_ns_hl(ns);
+ NSHlAttr *hl_def = (NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns);
+ if (hl_def) {
+ hl_attr_active = *hl_def;
}
- ns_hl_changed = false;
- return true;
}
- return false;
+ need_highlight_changed = true;
+ return true;
+}
+
+/// prepare for drawing window `wp` or global elements if NULL
+///
+/// Note: pum should be drawn in the context of the current window!
+bool win_check_ns_hl(win_T *wp)
+{
+ ns_hl_win = wp ? wp->w_ns_hl : -1;
+ return hl_check_ns();
}
/// Get attribute code for a builtin highlight group.
///
/// The final syntax group could be modified by hi-link or 'winhighlight'.
-int hl_get_ui_attr(int idx, int final_id, bool optional)
+int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
{
HlAttrs attrs = HLATTRS_INIT;
bool available = false;
if (final_id > 0) {
- int syn_attr = syn_id2attr(final_id);
- if (syn_attr != 0) {
+ int syn_attr = syn_ns_id2attr(ns_id, final_id, optional);
+ if (syn_attr > 0) {
attrs = syn_attr2entry(syn_attr);
available = true;
}
@@ -265,8 +303,6 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
if (pum_drawn()) {
must_redraw_pum = true;
}
- } else if (idx == HLF_MSG) {
- msg_grid.blending = attrs.hl_blend > -1;
}
if (optional && !available) {
@@ -278,6 +314,21 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
void update_window_hl(win_T *wp, bool invalid)
{
+ int ns_id = wp->w_ns_hl;
+
+ update_ns_hl(ns_id);
+ if (ns_id != wp->w_ns_hl_active || wp->w_ns_hl_attr == NULL) {
+ wp->w_ns_hl_active = ns_id;
+
+ wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns_id);
+ if (!wp->w_ns_hl_attr) {
+ // No specific highlights, use the defaults.
+ wp->w_ns_hl_attr = highlight_attr;
+ }
+ }
+
+ int *hl_def = wp->w_ns_hl_attr;
+
if (!wp->w_hl_needs_update && !invalid) {
return;
}
@@ -285,34 +336,17 @@ void update_window_hl(win_T *wp, bool invalid)
// If a floating window is blending it always have a named
// wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named.
- bool has_blend = wp->w_floating && wp->w_p_winbl != 0;
// determine window specific background set in 'winhighlight'
bool float_win = wp->w_floating && !wp->w_float_config.external;
- if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) {
- wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE,
- wp->w_hl_ids[HLF_INACTIVE],
- !has_blend);
- } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) {
- wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT,
- wp->w_hl_ids[HLF_NFLOAT], !has_blend);
- } else if (wp->w_hl_id_normal != 0) {
- wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend);
+ if (float_win && hl_def[HLF_NFLOAT] != 0) {
+ wp->w_hl_attr_normal = hl_def[HLF_NFLOAT];
+ } else if (hl_def[HLF_COUNT] > 0) {
+ wp->w_hl_attr_normal = hl_def[HLF_COUNT];
} else {
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
}
- // NOOOO! You cannot just pretend that "Normal" is just like any other
- // syntax group! It needs at least 10 layers of special casing! Noooooo!
- //
- // haha, theme engine go brrr
- int normality = syn_check_group(S_LEN("Normal"));
- int ns_attr = ns_get_hl(-1, normality, false, false);
- if (ns_attr > 0) {
- // TODO(bfredl): hantera NormalNC and so on
- wp->w_hl_attr_normal = ns_attr;
- }
-
// if blend= attribute is not set, 'winblend' value overrides it.
if (wp->w_floating && wp->w_p_winbl > 0) {
HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal);
@@ -322,28 +356,13 @@ void update_window_hl(win_T *wp, bool invalid)
}
}
- if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) {
- wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
- wp->w_hl_attr_normal);
- }
-
- for (int hlf = 0; hlf < HLF_COUNT; hlf++) {
- int attr;
- if (wp->w_hl_ids[hlf] != 0) {
- attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false);
- } else {
- attr = HL_ATTR(hlf);
- }
- wp->w_hl_attrs[hlf] = attr;
- }
-
wp->w_float_config.shadow = false;
if (wp->w_floating && wp->w_float_config.border) {
for (int i = 0; i < 8; i++) {
- int attr = wp->w_hl_attrs[HLF_BORDER];
+ int attr = hl_def[HLF_BORDER];
if (wp->w_float_config.border_hl_ids[i]) {
- attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i],
- false);
+ attr = hl_get_ui_attr(ns_id, HLF_BORDER,
+ wp->w_float_config.border_hl_ids[i], false);
HlAttrs a = syn_attr2entry(attr);
if (a.hl_blend) {
wp->w_float_config.shadow = true;
@@ -355,6 +374,65 @@ void update_window_hl(win_T *wp, bool invalid)
// shadow might cause blending
check_blending(wp);
+
+ // TODO(bfredl): this a bit ad-hoc. move it from highlight ns logic to 'winhl'
+ // implementation?
+ if (hl_def[HLF_INACTIVE] == 0) {
+ wp->w_hl_attr_normalnc = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
+ wp->w_hl_attr_normal);
+ } else {
+ wp->w_hl_attr_normalnc = hl_def[HLF_INACTIVE];
+ }
+}
+
+void update_ns_hl(int ns_id)
+{
+ if (ns_id <= 0) {
+ return;
+ }
+ DecorProvider *p = get_decor_provider(ns_id, true);
+ if (p->hl_cached) {
+ return;
+ }
+
+ NSHlAttr **alloc = (NSHlAttr **)pmap_ref(handle_T)(&ns_hl_attr, ns_id, true);
+ if (*alloc == NULL) {
+ *alloc = xmalloc(sizeof(**alloc));
+ }
+ int *hl_attrs = **alloc;
+
+ for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
+ int id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf]));
+ bool optional = (hlf == HLF_INACTIVE || hlf == HLF_NFLOAT);
+ hl_attrs[hlf] = hl_get_ui_attr(ns_id, hlf, id, optional);
+ }
+
+ // NOOOO! You cannot just pretend that "Normal" is just like any other
+ // syntax group! It needs at least 10 layers of special casing! Noooooo!
+ //
+ // haha, tema engine go brrr
+ int normality = syn_check_group(S_LEN("Normal"));
+ hl_attrs[HLF_COUNT] = hl_get_ui_attr(ns_id, -1, normality, true);
+
+ // hl_get_ui_attr might have invalidated the decor provider
+ p = get_decor_provider(ns_id, true);
+ p->hl_cached = true;
+}
+
+int win_bg_attr(win_T *wp)
+{
+ if (ns_hl_fast < 0) {
+ int local = (wp == curwin) ? wp->w_hl_attr_normal : wp->w_hl_attr_normalnc;
+ if (local) {
+ return local;
+ }
+ }
+
+ if (wp == curwin || hl_attr_active[HLF_INACTIVE] == 0) {
+ return hl_attr_active[HLF_COUNT];
+ } else {
+ return hl_attr_active[HLF_INACTIVE];
+ }
}
/// Gets HL_UNDERLINE highlight.
@@ -403,7 +481,7 @@ void clear_hl_tables(bool reinit)
map_destroy(int, int)(&combine_attr_entries);
map_destroy(int, int)(&blend_attr_entries);
map_destroy(int, int)(&blendthrough_attr_entries);
- map_destroy(ColorKey, ColorItem)(&ns_hl);
+ map_destroy(ColorKey, ColorItem)(&ns_hls);
}
}
@@ -437,52 +515,52 @@ int hl_combine_attr(int char_attr, int prim_attr)
}
HlAttrs char_aep = syn_attr2entry(char_attr);
- HlAttrs spell_aep = syn_attr2entry(prim_attr);
+ HlAttrs prim_aep = syn_attr2entry(prim_attr);
// start with low-priority attribute, and override colors if present below.
HlAttrs new_en = char_aep;
- if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) {
- new_en.cterm_ae_attr = spell_aep.cterm_ae_attr;
+ if (prim_aep.cterm_ae_attr & HL_NOCOMBINE) {
+ new_en.cterm_ae_attr = prim_aep.cterm_ae_attr;
} else {
- new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
+ new_en.cterm_ae_attr |= prim_aep.cterm_ae_attr;
}
- if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) {
- new_en.rgb_ae_attr = spell_aep.rgb_ae_attr;
+ if (prim_aep.rgb_ae_attr & HL_NOCOMBINE) {
+ new_en.rgb_ae_attr = prim_aep.rgb_ae_attr;
} else {
- new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
+ new_en.rgb_ae_attr |= prim_aep.rgb_ae_attr;
}
- if (spell_aep.cterm_fg_color > 0) {
- new_en.cterm_fg_color = spell_aep.cterm_fg_color;
+ if (prim_aep.cterm_fg_color > 0) {
+ new_en.cterm_fg_color = prim_aep.cterm_fg_color;
new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
- | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
+ | (prim_aep.rgb_ae_attr & HL_FG_INDEXED));
}
- if (spell_aep.cterm_bg_color > 0) {
- new_en.cterm_bg_color = spell_aep.cterm_bg_color;
+ if (prim_aep.cterm_bg_color > 0) {
+ new_en.cterm_bg_color = prim_aep.cterm_bg_color;
new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
- | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
+ | (prim_aep.rgb_ae_attr & HL_BG_INDEXED));
}
- if (spell_aep.rgb_fg_color >= 0) {
- new_en.rgb_fg_color = spell_aep.rgb_fg_color;
+ if (prim_aep.rgb_fg_color >= 0) {
+ new_en.rgb_fg_color = prim_aep.rgb_fg_color;
new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
- | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
+ | (prim_aep.rgb_ae_attr & HL_FG_INDEXED));
}
- if (spell_aep.rgb_bg_color >= 0) {
- new_en.rgb_bg_color = spell_aep.rgb_bg_color;
+ if (prim_aep.rgb_bg_color >= 0) {
+ new_en.rgb_bg_color = prim_aep.rgb_bg_color;
new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
- | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
+ | (prim_aep.rgb_ae_attr & HL_BG_INDEXED));
}
- if (spell_aep.rgb_sp_color >= 0) {
- new_en.rgb_sp_color = spell_aep.rgb_sp_color;
+ if (prim_aep.rgb_sp_color >= 0) {
+ new_en.rgb_sp_color = prim_aep.rgb_sp_color;
}
- if (spell_aep.hl_blend >= 0) {
- new_en.hl_blend = spell_aep.hl_blend;
+ if (prim_aep.hl_blend >= 0) {
+ new_en.hl_blend = prim_aep.hl_blend;
}
id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine,
@@ -852,7 +930,6 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH);
CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
- CHECK_FLAG(dict, mask, global, , HL_GLOBAL);
if (HAS_KEY(dict->fg)) {
fg = object_to_color(dict->fg, "fg", true, err);
@@ -895,14 +972,21 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
return hlattrs;
}
- if (HAS_KEY(dict->link)) {
+ if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) {
if (link_id) {
- *link_id = object_to_hl_id(dict->link, "link", err);
+ if (HAS_KEY(dict->global_link)) {
+ *link_id = object_to_hl_id(dict->global_link, "link", err);
+ mask |= HL_GLOBAL;
+ } else {
+ *link_id = object_to_hl_id(dict->link, "link", err);
+ }
+
if (ERROR_SET(err)) {
return hlattrs;
}
} else {
- api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'");
+ api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'",
+ HAS_KEY(dict->global_link) ? "global_link" : "link");
}
}
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index ae63f83d65..50299bb91c 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include "nvim/api/private/defs.h"
+#include "nvim/buffer_defs.h"
#include "nvim/highlight_defs.h"
#include "nvim/ui.h"
@@ -11,6 +12,13 @@
# include "highlight.h.generated.h"
#endif
+static inline int win_hl_attr(win_T *wp, int hlf)
+{
+ // wp->w_ns_hl_attr might be null if we check highlights
+ // prior to entering redraw
+ return ((wp->w_ns_hl_attr && ns_hl_fast < 0) ? wp->w_ns_hl_attr : hl_attr_active)[hlf];
+}
+
#define HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp) \
do { \
bool dark_ = (*p_bg == 'd'); \
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index f41f980054..ffcb0f3f22 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -180,7 +180,7 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_CU] = "Cursor",
});
-EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context.
+EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context.
EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups
EXTERN int highlight_user[9]; // User[1-9] attributes
EXTERN int highlight_stlnc[9]; // On top of user
@@ -190,6 +190,13 @@ EXTERN RgbValue normal_fg INIT(= -1);
EXTERN RgbValue normal_bg INIT(= -1);
EXTERN RgbValue normal_sp INIT(= -1);
+EXTERN NS ns_hl_global INIT(= 0); // global highlight namespace
+EXTERN NS ns_hl_win INIT(= -1); // highlight namespace for the current window
+EXTERN NS ns_hl_fast INIT(= -1); // highlight namespace specified in a fast callback
+EXTERN NS ns_hl_active INIT(= 0); // currently active/cached namespace
+
+EXTERN int *hl_attr_active INIT(= highlight_attr);
+
typedef enum {
kHlUnknown,
kHlUI,
@@ -219,8 +226,15 @@ typedef struct {
int link_id;
int version;
bool is_default;
+ bool link_global;
} ColorItem;
-#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, \
- .version = -1, .is_default = false }
+#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1, \
+ .is_default = false, .link_global = false }
+
+/// highlight attributes with associated priorities
+typedef struct {
+ int attr_id;
+ int priority;
+} HlPriAttr;
#endif // NVIM_HIGHLIGHT_DEFS_H
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index f6ec03fb14..77424de3b8 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -9,7 +9,10 @@
#include "nvim/autocmd.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/eval/vars.h"
+#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
@@ -17,7 +20,6 @@
#include "nvim/match.h"
#include "nvim/option.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
/// \addtogroup SG_SET
/// @{
@@ -689,14 +691,14 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
g->sg_cleared = false;
g->sg_link = link_id;
g->sg_script_ctx = current_sctx;
- g->sg_script_ctx.sc_lnum += sourcing_lnum;
+ g->sg_script_ctx.sc_lnum += SOURCING_LNUM;
g->sg_set |= SG_LINK;
if (is_default) {
g->sg_deflink = link_id;
g->sg_deflink_sctx = current_sctx;
- g->sg_deflink_sctx.sc_lnum += sourcing_lnum;
+ g->sg_deflink_sctx.sc_lnum += SOURCING_LNUM;
}
- return;
+ goto update;
}
g->sg_cleared = false;
@@ -733,7 +735,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
g->sg_blend = attrs.hl_blend;
g->sg_script_ctx = current_sctx;
- g->sg_script_ctx.sc_lnum += sourcing_lnum;
+ g->sg_script_ctx.sc_lnum += SOURCING_LNUM;
g->sg_attr = hl_get_syn_attr(0, id, attrs);
@@ -751,6 +753,12 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
ui_mode_info_set();
}
}
+
+update:
+ if (!updating_screen) {
+ redraw_all_later(NOT_VALID);
+ }
+ need_highlight_changed = true;
}
/// Handle ":highlight" command
@@ -861,7 +869,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (dodefault && (forceit || hlgroup->sg_deflink == 0)) {
hlgroup->sg_deflink = to_id;
hlgroup->sg_deflink_sctx = current_sctx;
- hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum;
+ hlgroup->sg_deflink_sctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&hlgroup->sg_deflink_sctx);
}
}
@@ -871,7 +879,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// for the group, unless '!' is used
if (to_id > 0 && !forceit && !init
&& hl_has_settings(from_id - 1, dodefault)) {
- if (sourcing_name == NULL && !dodefault) {
+ if (SOURCING_NAME == NULL && !dodefault) {
emsg(_("E414: group has settings, highlight link ignored"));
}
} else if (hlgroup->sg_link != to_id
@@ -882,7 +890,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
hlgroup->sg_link = to_id;
hlgroup->sg_script_ctx = current_sctx;
- hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum;
+ hlgroup->sg_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&hlgroup->sg_script_ctx);
hlgroup->sg_cleared = false;
redraw_all_later(SOME_VALID);
@@ -1272,7 +1280,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
set_hl_attr(idx);
}
hl_table[idx].sg_script_ctx = current_sctx;
- hl_table[idx].sg_script_ctx.sc_lnum += sourcing_lnum;
+ hl_table[idx].sg_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&hl_table[idx].sg_script_ctx);
// Only call highlight_changed() once, after a sequence of highlight
@@ -1765,7 +1773,7 @@ static int syn_add_group(const char *name, size_t len)
// Append another syntax_highlight entry.
HlGroup *hlgp = GA_APPEND_VIA_PTR(HlGroup, &highlight_ga);
- memset(hlgp, 0, sizeof(*hlgp));
+ CLEAR_POINTER(hlgp);
hlgp->sg_name = (char_u *)arena_memdupz(&highlight_arena, name, len);
hlgp->sg_rgb_bg = -1;
hlgp->sg_rgb_fg = -1;
@@ -1788,11 +1796,18 @@ static int syn_add_group(const char *name, size_t len)
/// @see syn_attr2entry
int syn_id2attr(int hl_id)
{
- hl_id = syn_get_final_id(hl_id);
+ return syn_ns_id2attr(-1, hl_id, false);
+}
+
+int syn_ns_id2attr(int ns_id, int hl_id, bool optional)
+{
+ hl_id = syn_ns_get_final_id(&ns_id, hl_id);
HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one
- int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set);
- if (attr >= 0) {
+ int attr = ns_get_hl(&ns_id, hl_id, false, sgp->sg_set);
+
+ // if a highlight group is optional, don't use the global value
+ if (attr >= 0 || (optional && ns_id > 0)) {
return attr;
}
return sgp->sg_attr;
@@ -1801,10 +1816,16 @@ int syn_id2attr(int hl_id)
/// Translate a group ID to the final group ID (following links).
int syn_get_final_id(int hl_id)
{
+ int id = curwin->w_ns_hl_active;
+ return syn_ns_get_final_id(&id, hl_id);
+}
+
+int syn_ns_get_final_id(int *ns_id, int hl_id)
+{
int count;
if (hl_id > highlight_ga.ga_len || hl_id < 1) {
- return 0; // Can be called from eval!!
+ return 0; // Can be called from eval!!
}
// Follow links until there is no more.
@@ -1812,10 +1833,10 @@ int syn_get_final_id(int hl_id)
for (count = 100; --count >= 0;) {
HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one
- // ACHTUNG: when using "tmp" attribute (no link) the function might be
+ // TODO(bfredl): when using "tmp" attribute (no link) the function might be
// called twice. it needs be smart enough to remember attr only to
// syn_id2attr time
- int check = ns_get_hl(-1, hl_id, true, sgp->sg_set);
+ int check = ns_get_hl(ns_id, hl_id, true, sgp->sg_set);
if (check == 0) {
return hl_id; // how dare! it broke the link!
} else if (check > 0) {
@@ -1863,7 +1884,7 @@ static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int
HlGroup *const hlt = hl_table;
if (id_alt == 0) {
- memset(&hlt[hlcnt + i], 0, sizeof(HlGroup));
+ CLEAR_POINTER(&hlt[hlcnt + i]);
hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
} else {
@@ -1913,19 +1934,22 @@ void highlight_changed(void)
if (id == 0) {
abort();
}
- int final_id = syn_get_final_id(id);
+ int ns_id = -1;
+ int final_id = syn_ns_get_final_id(&ns_id, id);
if (hlf == HLF_SNC) {
id_SNC = final_id;
} else if (hlf == HLF_S) {
id_S = final_id;
}
- highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id,
+ highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id,
(hlf == HLF_INACTIVE || hlf == HLF_LC));
if (highlight_attr[hlf] != highlight_attr_last[hlf]) {
if (hlf == HLF_MSG) {
clear_cmdline = true;
+ HlAttrs attrs = syn_attr2entry(highlight_attr[hlf]);
+ msg_grid.blending = attrs.hl_blend > -1;
}
ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]),
highlight_attr[hlf]);
@@ -1933,6 +1957,9 @@ void highlight_changed(void)
}
}
+ // sentinel value. used when no hightlight namespace is active
+ highlight_attr[HLF_COUNT] = 0;
+
//
// Setup the user highlights
//
@@ -1945,7 +1972,7 @@ void highlight_changed(void)
hlcnt = highlight_ga.ga_len;
if (id_S == -1) {
// Make sure id_S is always valid to simplify code below. Use the last entry
- memset(&hl_table[hlcnt + 9], 0, sizeof(HlGroup));
+ CLEAR_POINTER(&hl_table[hlcnt + 9]);
id_S = hlcnt + 10;
}
for (int i = 0; i < 9; i++) {
diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h
index 1474588889..bf6bad1a86 100644
--- a/src/nvim/highlight_group.h
+++ b/src/nvim/highlight_group.h
@@ -1,7 +1,8 @@
#ifndef NVIM_HIGHLIGHT_GROUP_H
#define NVIM_HIGHLIGHT_GROUP_H
-#include "nvim/eval.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/highlight_defs.h"
#include "nvim/types.h"
#define MAX_HL_ID 20000 // maximum value for a highlight ID.
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index 8d08c2fc19..689d1fce0d 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -18,9 +18,12 @@
#include <sys/types.h>
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/eval.h"
#include "nvim/event/stream.h"
+#include "nvim/ex_eval.h"
#include "nvim/fileio.h"
#include "nvim/if_cscope.h"
#include "nvim/memory.h"
@@ -415,16 +418,15 @@ static int cs_add_common(char *arg1, char *arg2, char *flags)
char *fname2 = NULL;
char *ppath = NULL;
size_t usedlen = 0;
- char_u *fbuf = NULL;
+ char *fbuf = NULL;
// get the filename (arg1), expand it, and try to stat it
fname = xmalloc(MAXPATHL + 1);
expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL);
size_t len = STRLEN(fname);
- fbuf = (char_u *)fname;
- (void)modify_fname(":p", false, &usedlen,
- &fname, (char **)&fbuf, &len);
+ fbuf = fname;
+ (void)modify_fname(":p", false, &usedlen, &fname, &fbuf, &len);
if (fname == NULL) {
goto add_err;
}
@@ -661,7 +663,7 @@ static char *cs_create_cmd(char *csoption, char *pattern)
pat = pattern;
if (search != 4 && search != 6) {
while (ascii_iswhite(*pat)) {
- ++pat;
+ pat++;
}
}
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 271498d41a..f18a6d7b32 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -11,6 +11,7 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/extmark.h"
#include "nvim/indent.h"
@@ -30,6 +31,313 @@
# include "indent.c.generated.h"
#endif
+/// Set the integer values corresponding to the string setting of 'vartabstop'.
+/// "array" will be set, caller must free it if needed.
+/// Return false for an error.
+bool tabstop_set(char_u *var, long **array)
+{
+ long valcount = 1;
+ int t;
+ char_u *cp;
+
+ if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) {
+ *array = NULL;
+ return true;
+ }
+
+ for (cp = var; *cp != NUL; cp++) {
+ if (cp == var || cp[-1] == ',') {
+ char *end;
+
+ if (strtol((char *)cp, &end, 10) <= 0) {
+ if (cp != (char_u *)end) {
+ emsg(_(e_positive));
+ } else {
+ semsg(_(e_invarg2), cp);
+ }
+ return false;
+ }
+ }
+
+ if (ascii_isdigit(*cp)) {
+ continue;
+ }
+ if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) {
+ valcount++;
+ continue;
+ }
+ semsg(_(e_invarg2), var);
+ return false;
+ }
+
+ *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long));
+ (*array)[0] = valcount;
+
+ t = 1;
+ for (cp = var; *cp != NUL;) {
+ int n = atoi((char *)cp);
+
+ // Catch negative values, overflow and ridiculous big values.
+ if (n <= 0 || n > TABSTOP_MAX) {
+ semsg(_(e_invarg2), cp);
+ XFREE_CLEAR(*array);
+ return false;
+ }
+ (*array)[t++] = n;
+ while (*cp != NUL && *cp != ',') {
+ cp++;
+ }
+ if (*cp != NUL) {
+ cp++;
+ }
+ }
+
+ return true;
+}
+
+/// Calculate the number of screen spaces a tab will occupy.
+/// If "vts" is set then the tab widths are taken from that array,
+/// otherwise the value of ts is used.
+int tabstop_padding(colnr_T col, long ts_arg, long *vts)
+{
+ long ts = ts_arg == 0 ? 8 : ts_arg;
+ colnr_T tabcol = 0;
+ int t;
+ long padding = 0;
+
+ if (vts == NULL || vts[0] == 0) {
+ return (int)(ts - (col % ts));
+ }
+
+ const long tabcount = vts[0];
+
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > col) {
+ padding = tabcol - col;
+ break;
+ }
+ }
+ if (t > tabcount) {
+ padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]);
+ }
+
+ return (int)padding;
+}
+
+/// Find the size of the tab that covers a particular column.
+int tabstop_at(colnr_T col, long ts, long *vts)
+{
+ colnr_T tabcol = 0;
+ int t;
+ long tab_size = 0;
+
+ if (vts == NULL || vts[0] == 0) {
+ return (int)ts;
+ }
+
+ const long tabcount = vts[0];
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > col) {
+ tab_size = vts[t];
+ break;
+ }
+ }
+ if (t > tabcount) {
+ tab_size = vts[tabcount];
+ }
+
+ return (int)tab_size;
+}
+
+/// Find the column on which a tab starts.
+colnr_T tabstop_start(colnr_T col, long ts, long *vts)
+{
+ colnr_T tabcol = 0;
+ int t;
+
+ if (vts == NULL || vts[0] == 0) {
+ return (int)((col / ts) * ts);
+ }
+
+ const long tabcount = vts[0];
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > col) {
+ return (int)(tabcol - vts[t]);
+ }
+ }
+
+ const int excess = (int)(tabcol % vts[tabcount]);
+ return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
+}
+
+/// Find the number of tabs and spaces necessary to get from one column
+/// to another.
+void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, int *ntabs,
+ int *nspcs)
+{
+ int spaces = end_col - start_col;
+ colnr_T tabcol = 0;
+ long padding = 0;
+ int t;
+ long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg;
+
+ if (vts == NULL || vts[0] == 0) {
+ int tabs = 0;
+
+ const int initspc = (int)(ts - (start_col % ts));
+ if (spaces >= initspc) {
+ spaces -= initspc;
+ tabs++;
+ }
+ tabs += (int)(spaces / ts);
+ spaces -= (int)((spaces / ts) * ts);
+
+ *ntabs = tabs;
+ *nspcs = spaces;
+ return;
+ }
+
+ // Find the padding needed to reach the next tabstop.
+ const long tabcount = vts[0];
+ for (t = 1; t <= tabcount; t++) {
+ tabcol += (colnr_T)vts[t];
+ if (tabcol > start_col) {
+ padding = tabcol - start_col;
+ break;
+ }
+ }
+ if (t > tabcount) {
+ padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]);
+ }
+
+ // If the space needed is less than the padding no tabs can be used.
+ if (spaces < padding) {
+ *ntabs = 0;
+ *nspcs = spaces;
+ return;
+ }
+
+ *ntabs = 1;
+ spaces -= (int)padding;
+
+ // At least one tab has been used. See if any more will fit.
+ while (spaces != 0 && ++t <= tabcount) {
+ padding = vts[t];
+ if (spaces < padding) {
+ *nspcs = spaces;
+ return;
+ }
+ *ntabs += 1;
+ spaces -= (int)padding;
+ }
+
+ *ntabs += spaces / (int)vts[tabcount];
+ *nspcs = spaces % (int)vts[tabcount];
+}
+
+/// See if two tabstop arrays contain the same values.
+bool tabstop_eq(long *ts1, long *ts2)
+{
+ int t;
+
+ if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) {
+ return false;
+ }
+ if (ts1 == ts2) {
+ return true;
+ }
+ if (ts1[0] != ts2[0]) {
+ return false;
+ }
+
+ for (t = 1; t <= ts1[0]; t++) {
+ if (ts1[t] != ts2[t]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/// Copy a tabstop array, allocating space for the new array.
+int *tabstop_copy(long *oldts)
+{
+ long *newts;
+ int t;
+
+ if (oldts == 0) {
+ return 0;
+ }
+
+ newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long));
+ for (t = 0; t <= oldts[0]; t++) {
+ newts[t] = oldts[t];
+ }
+
+ return (int *)newts;
+}
+
+/// Return a count of the number of tabstops.
+int tabstop_count(long *ts)
+{
+ return ts != NULL ? (int)ts[0] : 0;
+}
+
+/// Return the first tabstop, or 8 if there are no tabstops defined.
+int tabstop_first(long *ts)
+{
+ return ts != NULL ? (int)ts[1] : 8;
+}
+
+/// Return the effective shiftwidth value for current buffer, using the
+/// 'tabstop' value when 'shiftwidth' is zero.
+int get_sw_value(buf_T *buf)
+{
+ long result = get_sw_value_col(buf, 0);
+ assert(result >= 0 && result <= INT_MAX);
+ return (int)result;
+}
+
+/// Idem, using "pos".
+long get_sw_value_pos(buf_T *buf, pos_T *pos)
+{
+ pos_T save_cursor = curwin->w_cursor;
+ long sw_value;
+
+ curwin->w_cursor = *pos;
+ sw_value = get_sw_value_col(buf, get_nolist_virtcol());
+ curwin->w_cursor = save_cursor;
+ return sw_value;
+}
+
+/// Idem, using the first non-black in the current line.
+long get_sw_value_indent(buf_T *buf)
+{
+ pos_T pos = curwin->w_cursor;
+
+ pos.col = (colnr_T)getwhitecols_curline();
+ return get_sw_value_pos(buf, &pos);
+}
+
+/// Idem, using virtual column "col".
+long get_sw_value_col(buf_T *buf, colnr_T col)
+{
+ return buf->b_p_sw ? buf->b_p_sw
+ : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
+}
+
+/// Return the effective softtabstop value for the current buffer,
+/// using the shiftwidth value when 'softtabstop' is negative.
+int get_sts_value(void)
+{
+ long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
+ assert(result >= 0 && result <= INT_MAX);
+ return (int)result;
+}
+
// Count the size (in window cells) of the indent in the current line.
int get_indent(void)
{
@@ -769,10 +1077,10 @@ static int lisp_match(char_u *p)
{
char_u buf[LSIZE];
int len;
- char_u *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords;
+ char *word = (char *)(*curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords);
while (*word != NUL) {
- (void)copy_option_part((char **)&word, (char *)buf, LSIZE, ",");
+ (void)copy_option_part(&word, (char *)buf, LSIZE, ",");
len = (int)STRLEN(buf);
if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) {
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index c5e030ce25..34a3de4f78 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -223,8 +223,8 @@ bool cin_is_cinword(const char_u *line)
char_u *cinw_buf = xmalloc(cinw_len);
line = (char_u *)skipwhite((char *)line);
- for (char_u *cinw = curbuf->b_p_cinw; *cinw;) {
- size_t len = copy_option_part((char **)&cinw, (char *)cinw_buf, cinw_len, ",");
+ for (char *cinw = (char *)curbuf->b_p_cinw; *cinw;) {
+ size_t len = copy_option_part(&cinw, (char *)cinw_buf, cinw_len, ",");
if (STRNCMP(line, cinw_buf, len) == 0
&& (!vim_iswordc(line[len]) || !vim_iswordc(line[len - 1]))) {
retval = true;
@@ -317,17 +317,17 @@ static bool cin_has_js_key(const char_u *text)
if (*s == '\'' || *s == '"') {
// can be 'key': or "key":
quote = *s;
- ++s;
+ s++;
}
if (!vim_isIDc(*s)) { // need at least one ID character
return false;
}
while (vim_isIDc(*s)) {
- ++s;
+ s++;
}
if (*s && *s == quote) {
- ++s;
+ s++;
}
s = cin_skipcomment(s);
@@ -519,8 +519,8 @@ bool cin_isscopedecl(const char_u *p)
bool found = false;
- for (char_u *cinsd = curbuf->b_p_cinsd; *cinsd;) {
- const size_t len = copy_option_part((char **)&cinsd, (char *)cinsd_buf, cinsd_len, ",");
+ for (char *cinsd = (char *)curbuf->b_p_cinsd; *cinsd;) {
+ const size_t len = copy_option_part(&cinsd, (char *)cinsd_buf, cinsd_len, ",");
if (STRNCMP(s, cinsd_buf, len) == 0) {
const char_u *skip = cin_skipcomment(s + len);
if (*skip == ':' && skip[1] != ':') {
@@ -1601,8 +1601,8 @@ static int find_last_paren(const char_u *l, int start, int end)
*/
void parse_cino(buf_T *buf)
{
- char_u *p;
- char_u *l;
+ char *p;
+ char *l;
int divider;
int fraction = 0;
int sw = get_sw_value(buf);
@@ -1740,16 +1740,16 @@ void parse_cino(buf_T *buf)
// Handle C #pragma directives
buf->b_ind_pragma = 0;
- for (p = buf->b_p_cino; *p;) {
+ for (p = (char *)buf->b_p_cino; *p;) {
l = p++;
if (*p == '-') {
p++;
}
- char_u *digits_start = p; // remember where the digits start
- int n = getdigits_int((char **)&p, true, 0);
+ char *digits_start = p; // remember where the digits start
+ int n = getdigits_int(&p, true, 0);
divider = 0;
if (*p == '.') { // ".5s" means a fraction.
- fraction = atoi((char *)++p);
+ fraction = atoi(++p);
while (ascii_isdigit(*p)) {
p++;
if (divider) {
@@ -1768,7 +1768,7 @@ void parse_cino(buf_T *buf)
n += (sw * fraction + divider / 2) / divider;
}
}
- ++p;
+ p++;
}
if (l[1] == '-') {
n = -n;
@@ -2052,7 +2052,7 @@ int get_c_indent(void)
char lead_start[COM_MAX_LEN]; // start-comment string
char lead_middle[COM_MAX_LEN]; // middle-comment string
char lead_end[COM_MAX_LEN]; // end-comment string
- char_u *p;
+ char *p;
int start_align = 0;
int start_off = 0;
int done = FALSE;
@@ -2063,7 +2063,7 @@ int get_c_indent(void)
*lead_start = NUL;
*lead_middle = NUL;
- p = curbuf->b_p_com;
+ p = (char *)curbuf->b_p_com;
while (*p != NUL) {
int align = 0;
int off = 0;
@@ -2071,11 +2071,11 @@ int get_c_indent(void)
while (*p != NUL && *p != ':') {
if (*p == COM_START || *p == COM_END || *p == COM_MIDDLE) {
- what = *p++;
+ what = (unsigned char)(*p++);
} else if (*p == COM_LEFT || *p == COM_RIGHT) {
- align = *p++;
+ align = (unsigned char)(*p++);
} else if (ascii_isdigit(*p) || *p == '-') {
- off = getdigits_int((char **)&p, true, 0);
+ off = getdigits_int(&p, true, 0);
} else {
p++;
}
@@ -2084,7 +2084,7 @@ int get_c_indent(void)
if (*p == ':') {
p++;
}
- (void)copy_option_part((char **)&p, lead_end, COM_MAX_LEN, ",");
+ (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
if (what == COM_START) {
STRCPY(lead_start, lead_end);
lead_start_len = (int)STRLEN(lead_start);
@@ -3334,7 +3334,7 @@ int get_c_indent(void)
amount += curbuf->b_ind_open_extra;
}
}
- ++whilelevel;
+ whilelevel++;
}
/*
* We are after a "normal" statement.
@@ -3848,7 +3848,7 @@ static int find_match(int lookfor, linenr_T ourscope)
* another "do", so increment whilelevel. XXX
*/
if (cin_iswhileofdo(look, curwin->w_cursor.lnum)) {
- ++whilelevel;
+ whilelevel++;
continue;
}
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index a64d8e8f00..c525a49bc3 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -13,10 +13,12 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
@@ -33,9 +35,8 @@
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/state.h"
@@ -2139,7 +2140,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
}
} else {
word = tv_get_string_chk(tv);
- memset(cptext, 0, sizeof(cptext));
+ CLEAR_FIELD(cptext);
}
if (word == NULL || (!empty && *word == NUL)) {
for (size_t i = 0; i < CPT_COUNT; i++) {
@@ -2473,7 +2474,7 @@ static int ins_compl_get_exp(pos_T *ini)
{
static pos_T first_match_pos;
static pos_T last_match_pos;
- static char_u *e_cpt = (char_u *)""; // curr. entry in 'complete'
+ static char *e_cpt = ""; // curr. entry in 'complete'
static bool found_all = false; // Found all matches of a
// certain type.
static buf_T *ins_buf = NULL; // buffer being scanned
@@ -2502,8 +2503,7 @@ static int ins_compl_get_exp(pos_T *ini)
}
found_all = false;
ins_buf = curbuf;
- e_cpt = (compl_cont_status & CONT_LOCAL)
- ? (char_u *)"." : curbuf->b_p_cpt;
+ e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : (char *)curbuf->b_p_cpt;
last_match_pos = first_match_pos = *ini;
} else if (ins_buf != curbuf && !buf_valid(ins_buf)) {
ins_buf = curbuf; // In case the buffer was wiped out.
@@ -2583,7 +2583,7 @@ static int ins_compl_get_exp(pos_T *ini)
type = CTRL_X_THESAURUS;
}
if (*++e_cpt != ',' && *e_cpt != NUL) {
- dict = e_cpt;
+ dict = (char_u *)e_cpt;
dict_f = DICT_FIRST;
}
} else if (*e_cpt == 'i') {
@@ -2600,7 +2600,7 @@ static int ins_compl_get_exp(pos_T *ini)
}
// in any case e_cpt is advanced to the next entry
- (void)copy_option_part((char **)&e_cpt, (char *)IObuff, IOSIZE, ",");
+ (void)copy_option_part(&e_cpt, (char *)IObuff, IOSIZE, ",");
found_all = true;
if (type == -1) {
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 4d6e6090b8..49d49f76b9 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -59,7 +59,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
size_t other_keys_num = 0; // Number of keys that are not string, integral
// or type keys.
LuaTableProps ret;
- memset(&ret, 0, sizeof(ret));
+ CLEAR_FIELD(ret);
if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) {
semsg(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 2);
ret.type = kObjectTypeNil;
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index 1c9e60e4b2..f6a85900ba 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -6,7 +6,7 @@
#include <stdint.h>
#include "nvim/api/private/defs.h"
-#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
#include "nvim/func_attr.h"
typedef struct {
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 661dbfc4c2..5d97f90bb1 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -15,12 +15,13 @@
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
#include "nvim/func_attr.h"
@@ -37,7 +38,7 @@
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/os.h"
#include "nvim/profile.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
#include "nvim/version.h"
@@ -447,7 +448,7 @@ static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread)
FUNC_ATTR_NONNULL_ALL
{
nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state));
- memset(ref_state, 0, sizeof(*ref_state));
+ CLEAR_POINTER(ref_state);
ref_state->nil_ref = LUA_NOREF;
ref_state->empty_dict_ref = LUA_NOREF;
if (!is_thread) {
@@ -1313,12 +1314,11 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name
int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
{
- const linenr_T save_sourcing_lnum = sourcing_lnum;
const sctx_T save_current_sctx = current_sctx;
current_sctx.sc_sid = SID_STR;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = 0;
- sourcing_lnum = 0;
+ estack_push(ETYPE_SCRIPT, name, 0);
garray_T ga;
char_u *line = NULL;
@@ -1331,7 +1331,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
size_t len = strlen(code);
nlua_typval_exec(code, len, name, NULL, 0, false, NULL);
- sourcing_lnum = save_sourcing_lnum;
+ estack_pop();
current_sctx = save_current_sctx;
ga_clear_strings(&ga);
xfree(code);
@@ -1906,7 +1906,7 @@ void nlua_set_sctx(sctx_T *current)
break;
}
char *source_path = fix_fname(info->source + 1);
- get_current_script_id((char_u *)source_path, current);
+ get_current_script_id(&source_path, current);
xfree(source_path);
current->sc_lnum = info->currentline;
current->sc_seq = -1;
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 2afbbebfe7..78346fd81f 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -24,6 +24,8 @@ typedef struct {
#endif
} nlua_ref_state_t;
+#define NLUA_EXEC_STATIC(cstr, arg, err) nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, err)
+
#define NLUA_CLEAR_REF(x) \
do { \
/* Take the address to avoid double evaluation. #1375 */ \
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 6ba0056f48..5a82ae30b5 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -16,10 +16,11 @@
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
+#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
-#include "nvim/ex_cmds2.h"
+#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
#include "nvim/func_attr.h"
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index 71f85385b6..b2b5dfedee 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -269,9 +269,9 @@ int nlua_xdl_diff(lua_State *lstate)
xpparam_t params;
xdemitcb_t ecb;
- memset(&cfg, 0, sizeof(cfg));
- memset(&params, 0, sizeof(params));
- memset(&ecb, 0, sizeof(ecb));
+ CLEAR_FIELD(cfg);
+ CLEAR_FIELD(params);
+ CLEAR_FIELD(ecb);
NluaXdiffMode mode = kNluaXdiffModeUnified;
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 494ff0b4af..fd31ba6c66 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -8,6 +8,7 @@
#include <stdint.h>
#include <string.h>
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
@@ -16,6 +17,7 @@
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -57,10 +59,10 @@
#include "nvim/os/time.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/shada.h"
#include "nvim/sign.h"
#include "nvim/state.h"
@@ -159,6 +161,7 @@ bool event_teardown(void)
void early_init(mparm_T *paramp)
{
env_init();
+ estack_init();
cmdline_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
@@ -1242,8 +1245,8 @@ static void command_line_scan(mparm_T *parmp)
} else if (argv[0][0] == '-') {
// "-S" followed by another option: use default session file.
a = SESSION_FILE;
- ++argc;
- --argv;
+ argc++;
+ argv--;
} else {
a = argv[0];
}
@@ -1418,7 +1421,7 @@ scripterror:
* copied, so that they can be changed. */
static void init_params(mparm_T *paramp, int argc, char **argv)
{
- memset(paramp, 0, sizeof(*paramp));
+ CLEAR_POINTER(paramp);
paramp->argc = argc;
paramp->argv = argv;
paramp->use_debug_break_level = -1;
@@ -1613,9 +1616,9 @@ static void create_windows(mparm_T *parmp)
// Watch out for autocommands that delete a window.
//
// Don't execute Win/Buf Enter/Leave autocommands here
- ++autocmd_no_enter;
- ++autocmd_no_leave;
- dorewind = TRUE;
+ autocmd_no_enter++;
+ autocmd_no_leave++;
+ dorewind = true;
while (done++ < 1000) {
if (dorewind) {
if (parmp->window_layout == WIN_TABS) {
@@ -1677,8 +1680,8 @@ static void create_windows(mparm_T *parmp)
curwin = firstwin;
}
curbuf = curwin->w_buffer;
- --autocmd_no_enter;
- --autocmd_no_leave;
+ autocmd_no_enter--;
+ autocmd_no_leave--;
}
}
@@ -1695,8 +1698,8 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
/*
* Don't execute Win/Buf Enter/Leave autocommands here
*/
- ++autocmd_no_enter;
- ++autocmd_no_leave;
+ autocmd_no_enter++;
+ autocmd_no_leave++;
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
@@ -1782,7 +1785,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
if (parmp->window_layout == WIN_TABS) {
goto_tabpage(1);
}
- --autocmd_no_enter;
+ autocmd_no_enter--;
// make the first window the current window
win = firstwin;
@@ -1796,7 +1799,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
}
win_enter(win, false);
- --autocmd_no_leave;
+ autocmd_no_leave--;
TIME_MSG("editing files in windows");
if (parmp->window_count > 1 && parmp->window_layout != WIN_TABS) {
win_equal(curwin, false, 'b'); // adjust heights
@@ -1814,12 +1817,12 @@ static void exe_pre_commands(mparm_T *parmp)
if (cnt > 0) {
curwin->w_cursor.lnum = 0; // just in case..
- sourcing_name = _("pre-vimrc command line");
+ estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0);
current_sctx.sc_sid = SID_CMDARG;
for (i = 0; i < cnt; i++) {
do_cmdline_cmd(cmds[i]);
}
- sourcing_name = NULL;
+ estack_pop();
current_sctx.sc_sid = 0;
TIME_MSG("--cmd commands");
}
@@ -1841,7 +1844,7 @@ static void exe_commands(mparm_T *parmp)
if (parmp->tagname == NULL && curwin->w_cursor.lnum <= 1) {
curwin->w_cursor.lnum = 0;
}
- sourcing_name = "command line";
+ estack_push(ETYPE_ARGS, "command line", 0);
current_sctx.sc_sid = SID_CARG;
current_sctx.sc_seq = 0;
for (i = 0; i < parmp->n_commands; i++) {
@@ -1850,7 +1853,7 @@ static void exe_commands(mparm_T *parmp)
xfree(parmp->commands[i]);
}
}
- sourcing_name = NULL;
+ estack_pop();
current_sctx.sc_sid = 0;
if (curwin->w_cursor.lnum == 0) {
curwin->w_cursor.lnum = 1;
@@ -2059,17 +2062,14 @@ static int execute_env(char *env)
{
const char *initstr = os_getenv(env);
if (initstr != NULL) {
- char_u *save_sourcing_name = (char_u *)sourcing_name;
- linenr_T save_sourcing_lnum = sourcing_lnum;
- sourcing_name = env;
- sourcing_lnum = 0;
+ estack_push(ETYPE_ENV, env, 0);
const sctx_T save_current_sctx = current_sctx;
current_sctx.sc_sid = SID_ENV;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = 0;
do_cmdline_cmd((char *)initstr);
- sourcing_name = (char *)save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
+
+ estack_pop();
current_sctx = save_current_sctx;
return OK;
}
diff --git a/src/nvim/main.h b/src/nvim/main.h
index d5384ecc95..0c497a7c0e 100644
--- a/src/nvim/main.h
+++ b/src/nvim/main.h
@@ -2,7 +2,6 @@
#define NVIM_MAIN_H
#include "nvim/event/loop.h"
-#include "nvim/normal.h"
// Maximum number of commands from + or -c arguments.
#define MAX_ARG_CMDS 10
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index 342b1b0d47..64a798a27b 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -29,6 +29,7 @@
#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
@@ -333,7 +334,7 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma
{
const char_u *to_parse = strargs;
to_parse = (char_u *)skipwhite((char *)to_parse);
- memset(mapargs, 0, sizeof(*mapargs));
+ CLEAR_POINTER(mapargs);
// Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in
// any order.
@@ -469,7 +470,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
mp->m_script_ctx.sc_lnum = lnum;
} else {
mp->m_script_ctx = current_sctx;
- mp->m_script_ctx.sc_lnum += sourcing_lnum;
+ mp->m_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&mp->m_script_ctx);
}
mp->m_desc = NULL;
@@ -776,7 +777,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
mp->m_expr = args->expr;
mp->m_replace_keycodes = args->replace_keycodes;
mp->m_script_ctx = current_sctx;
- mp->m_script_ctx.sc_lnum += sourcing_lnum;
+ mp->m_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&mp->m_script_ctx);
if (args->desc != NULL) {
mp->m_desc = xstrdup(args->desc);
@@ -964,7 +965,7 @@ static int get_map_mode(char **cmdp, bool forceit)
/// Clear all mappings (":mapclear") or abbreviations (":abclear").
/// "abbr" should be false for mappings, true for abbreviations.
/// This function used to be called map_clear().
-static void do_mapclear(char_u *cmdp, char_u *arg, int forceit, int abbr)
+static void do_mapclear(char *cmdp, char_u *arg, int forceit, int abbr)
{
int mode;
int local;
@@ -975,7 +976,7 @@ static void do_mapclear(char_u *cmdp, char_u *arg, int forceit, int abbr)
return;
}
- mode = get_map_mode((char **)&cmdp, forceit);
+ mode = get_map_mode(&cmdp, forceit);
map_clear_mode(curbuf, mode, local, abbr);
}
@@ -1051,9 +1052,9 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
int mode = 0;
int retval;
- char_u *buf = NULL;
+ char *buf = NULL;
const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str),
- (char **)&buf, REPTERM_DO_LT,
+ &buf, REPTERM_DO_LT,
NULL, CPO_TO_CPO_FLAGS);
#define MAPMODE(mode, modechars, chr, modeflags) \
@@ -1194,14 +1195,14 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
/// @param forceit true if '!' given
/// @param isabbrev true if abbreviation
/// @param isunmap true if unmap/unabbrev command
-char_u *set_context_in_map_cmd(expand_T *xp, char_u *cmd, char_u *arg, bool forceit, bool isabbrev,
+char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forceit, bool isabbrev,
bool isunmap, cmdidx_T cmdidx)
{
if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) {
xp->xp_context = EXPAND_NOTHING;
} else {
if (isunmap) {
- expand_mapmodes = get_map_mode((char **)&cmd, forceit || isabbrev);
+ expand_mapmodes = get_map_mode(&cmd, forceit || isabbrev);
} else {
expand_mapmodes = MODE_INSERT | MODE_CMDLINE;
if (!isabbrev) {
@@ -2079,7 +2080,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
}
char *keys_buf = NULL;
- char_u *alt_keys_buf = NULL;
+ char *alt_keys_buf = NULL;
bool did_simplify = false;
const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
const int mode = get_map_mode((char **)&which, 0);
@@ -2096,9 +2097,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
// preferred for printing, like in do_map().
(void)replace_termcodes(keys,
STRLEN(keys),
- (char **)&alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL,
+ &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL,
CPO_TO_CPO_FLAGS);
- rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
+ rhs = check_map((char_u *)alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
}
if (!get_dict) {
@@ -2439,13 +2440,13 @@ void ex_unmap(exarg_T *eap)
/// ":mapclear" and friends.
void ex_mapclear(exarg_T *eap)
{
- do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, eap->forceit, false);
+ do_mapclear(eap->cmd, (char_u *)eap->arg, eap->forceit, false);
}
/// ":abclear" and friends.
void ex_abclear(exarg_T *eap)
{
- do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, true, true);
+ do_mapclear(eap->cmd, (char_u *)eap->arg, true, true);
}
/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 1fe3327b29..593275d489 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -19,7 +19,6 @@
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
#include "nvim/extmark.h"
-#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -86,7 +85,7 @@ void clear_fmark(fmark_T *fm)
FUNC_ATTR_NONNULL_ALL
{
free_fmark(*fm);
- memset(fm, 0, sizeof(*fm));
+ CLEAR_POINTER(fm);
}
/*
@@ -1741,7 +1740,7 @@ void free_all_marks(void)
free_xfmark(namedfm[i]);
}
}
- memset(&namedfm[0], 0, sizeof(namedfm));
+ CLEAR_FIELD(namedfm);
}
#endif
diff --git a/src/nvim/match.c b/src/nvim/match.c
index ba587c4141..1c34c9f004 100644
--- a/src/nvim/match.c
+++ b/src/nvim/match.c
@@ -7,14 +7,19 @@
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/eval/funcs.h"
+#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/match.h"
#include "nvim/memline.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
+#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "match.c.generated.h"
@@ -692,7 +697,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match
}
// Highlight the match were the cursor is using the CurSearch
// group.
- if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC])) {
+ if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC))) {
shl->attr_cur = win_hl_attr(wp, HLF_LC) ? win_hl_attr(wp, HLF_LC) : HL_ATTR(HLF_LC);
} else {
shl->attr_cur = shl->attr;
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 223b4d6845..af9e214d92 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -39,6 +39,7 @@
#include "nvim/arabic.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/fileio.h"
#include "nvim/func_attr.h"
@@ -49,7 +50,6 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/screen.h"
@@ -74,6 +74,19 @@ struct interval {
# include "unicode_tables.generated.h"
#endif
+static char e_list_item_nr_is_not_list[]
+ = N_("E1109: List item %d is not a List");
+static char e_list_item_nr_does_not_contain_3_numbers[]
+ = N_("E1110: List item %d does not contain 3 numbers");
+static char e_list_item_nr_range_invalid[]
+ = N_("E1111: List item %d range invalid");
+static char e_list_item_nr_cell_width_invalid[]
+ = N_("E1112: List item %d cell width invalid");
+static char e_overlapping_ranges_for_nr[]
+ = N_("E1113: Overlapping ranges for 0x%lx");
+static char e_only_values_of_0x100_and_higher_supported[]
+ = N_("E1114: Only values of 0x100 and higher supported");
+
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes
// which are illegal when used as the first byte have a 1. The NUL byte has
@@ -472,13 +485,18 @@ static bool intable(const struct interval *table, size_t n_items, int c)
int utf_char2cells(int c)
{
if (c >= 0x100) {
+ int n = cw_value(c);
+ if (n != 0) {
+ return n;
+ }
+
if (!utf_printable(c)) {
return 6; // unprintable, displays <xxxx>
}
if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) {
return 2;
}
- if (p_emoji && intable(emoji_width, ARRAY_SIZE(emoji_width), c)) {
+ if (p_emoji && intable(emoji_wide, ARRAY_SIZE(emoji_wide), c)) {
return 2;
}
} else if (c >= 0x80 && !vim_isprintc(c)) {
@@ -872,9 +890,9 @@ int utf_ptr2len_len(const char_u *p, int size)
return len;
}
-/// Return the number of bytes occupied by a UTF-8 character in a string
-///
+/// Return the number of bytes occupied by a UTF-8 character in a string.
/// This includes following composing characters.
+/// Returns zero for NUL.
int utfc_ptr2len(const char *const p_in)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
@@ -988,8 +1006,9 @@ int utf_char2len(const int c)
/// Convert Unicode character to UTF-8 string
///
-/// @param c character to convert to \p buf
-/// @param[out] buf UTF-8 string generated from \p c, does not add \0
+/// @param c character to convert to UTF-8 string in \p buf
+/// @param[out] buf UTF-8 string generated from \p c, does not add \0
+/// must have room for at least 6 bytes
/// @return Number of bytes (1-6).
int utf_char2bytes(const int c, char *const buf)
{
@@ -1164,6 +1183,11 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
return 1; // punctuation
}
+ // emoji
+ if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) {
+ return 3;
+ }
+
// binary search in table
while (top >= bot) {
mid = (bot + top) / 2;
@@ -1176,11 +1200,6 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
}
}
- // emoji
- if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) {
- return 3;
- }
-
// most other characters are "word" characters
return 2;
}
@@ -1587,7 +1606,7 @@ void show_utf8(void)
}
sprintf((char *)IObuff + rlen, "%02x ",
(line[i] == NL) ? NUL : line[i]); // NUL is stored as NL
- --clen;
+ clen--;
rlen += (int)STRLEN(IObuff + rlen);
if (rlen > IOSIZE - 20) {
break;
@@ -1620,7 +1639,7 @@ int utf_head_off(const char_u *base, const char_u *p)
// Move q to the first byte of this char.
while (q > base && (*q & 0xc0) == 0x80) {
- --q;
+ q--;
}
// Check for illegal sequence. Do allow an illegal byte after where we
// started.
@@ -1641,10 +1660,10 @@ int utf_head_off(const char_u *base, const char_u *p)
if (arabic_maycombine(c)) {
// Advance to get a sneak-peak at the next char
const char_u *j = q;
- --j;
+ j--;
// Move j to the first byte of this char.
while (j > base && (*j & 0xc0) == 0x80) {
- --j;
+ j--;
}
if (arabic_combine(utf_ptr2char((char *)j), c)) {
continue;
@@ -1800,9 +1819,9 @@ bool utf_allow_break(int cc, int ncc)
///
/// @param[in,out] fp Source of the character to copy.
/// @param[in,out] tp Destination to copy to.
-void mb_copy_char(const char_u **const fp, char_u **const tp)
+void mb_copy_char(const char **const fp, char **const tp)
{
- const size_t l = (size_t)utfc_ptr2len((char *)(*fp));
+ const size_t l = (size_t)utfc_ptr2len(*fp);
memmove(*tp, *fp, l);
*tp += l;
@@ -2678,3 +2697,174 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp
return retval;
}
+
+/// Table set by setcellwidths().
+typedef struct {
+ long first;
+ long last;
+ char width;
+} cw_interval_T;
+
+static cw_interval_T *cw_table = NULL;
+static size_t cw_table_size = 0;
+
+/// Return the value of the cellwidth table for the character `c`.
+///
+/// @param c The source character.
+/// @return 1 or 2 when `c` is in the cellwidth table, 0 if not.
+static int cw_value(int c)
+{
+ if (cw_table == NULL) {
+ return 0;
+ }
+
+ // first quick check for Latin1 etc. characters
+ if (c < cw_table[0].first) {
+ return 0;
+ }
+
+ // binary search in table
+ int bot = 0;
+ int top = (int)cw_table_size - 1;
+ while (top >= bot) {
+ int mid = (bot + top) / 2;
+ if (cw_table[mid].last < c) {
+ bot = mid + 1;
+ } else if (cw_table[mid].first > c) {
+ top = mid - 1;
+ } else {
+ return cw_table[mid].width;
+ }
+ }
+ return 0;
+}
+
+static int tv_nr_compare(const void *a1, const void *a2)
+{
+ const listitem_T *const li1 = tv_list_first(*(const list_T **)a1);
+ const listitem_T *const li2 = tv_list_first(*(const list_T **)a2);
+
+ return (int)(TV_LIST_ITEM_TV(li1)->vval.v_number - TV_LIST_ITEM_TV(li2)->vval.v_number);
+}
+
+/// "setcellwidths()" function
+void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) {
+ emsg(_(e_listreq));
+ return;
+ }
+ const list_T *const l = argvars[0].vval.v_list;
+ if (tv_list_len(l) == 0) {
+ // Clearing the table.
+ xfree(cw_table);
+ cw_table = NULL;
+ cw_table_size = 0;
+ return;
+ }
+
+ // Note: use list_T instead of listitem_T so that TV_LIST_ITEM_NEXT can be used properly below.
+ const list_T **ptrs = xmalloc(sizeof(const list_T *) * (size_t)tv_list_len(l));
+
+ // Check that all entries are a list with three numbers, the range is
+ // valid and the cell width is valid.
+ int item = 0;
+ TV_LIST_ITER_CONST(l, li, {
+ const typval_T *const li_tv = TV_LIST_ITEM_TV(li);
+
+ if (li_tv->v_type != VAR_LIST || li_tv->vval.v_list == NULL) {
+ semsg(_(e_list_item_nr_is_not_list), item);
+ xfree(ptrs);
+ return;
+ }
+
+ const list_T *const li_l = li_tv->vval.v_list;
+ ptrs[item] = li_l;
+ const listitem_T *lili = tv_list_first(li_l);
+ int i;
+ varnumber_T n1;
+ for (i = 0; lili != NULL; lili = TV_LIST_ITEM_NEXT(li_l, lili), i++) {
+ const typval_T *const lili_tv = TV_LIST_ITEM_TV(lili);
+ if (lili_tv->v_type != VAR_NUMBER) {
+ break;
+ }
+ if (i == 0) {
+ n1 = lili_tv->vval.v_number;
+ if (n1 < 0x100) {
+ emsg(_(e_only_values_of_0x100_and_higher_supported));
+ xfree(ptrs);
+ return;
+ }
+ } else if (i == 1 && lili_tv->vval.v_number < n1) {
+ semsg(_(e_list_item_nr_range_invalid), item);
+ xfree(ptrs);
+ return;
+ } else if (i == 2 && (lili_tv->vval.v_number < 1 || lili_tv->vval.v_number > 2)) {
+ semsg(_(e_list_item_nr_cell_width_invalid), item);
+ xfree(ptrs);
+ return;
+ }
+ }
+
+ if (i != 3) {
+ semsg(_(e_list_item_nr_does_not_contain_3_numbers), item);
+ xfree(ptrs);
+ return;
+ }
+
+ item++;
+ });
+
+ // Sort the list on the first number.
+ qsort((void *)ptrs, (size_t)tv_list_len(l), sizeof(const list_T *), tv_nr_compare);
+
+ cw_interval_T *table = xmalloc(sizeof(cw_interval_T) * (size_t)tv_list_len(l));
+
+ // Store the items in the new table.
+ for (item = 0; item < tv_list_len(l); item++) {
+ const list_T *const li_l = ptrs[item];
+ const listitem_T *lili = tv_list_first(li_l);
+ const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number;
+ if (item > 0 && n1 <= table[item - 1].last) {
+ semsg(_(e_overlapping_ranges_for_nr), (long)n1);
+ xfree(ptrs);
+ xfree(table);
+ return;
+ }
+ table[item].first = n1;
+ lili = TV_LIST_ITEM_NEXT(li_l, lili);
+ table[item].last = TV_LIST_ITEM_TV(lili)->vval.v_number;
+ lili = TV_LIST_ITEM_NEXT(li_l, lili);
+ table[item].width = (char)TV_LIST_ITEM_TV(lili)->vval.v_number;
+ }
+
+ xfree(ptrs);
+
+ cw_interval_T *const cw_table_save = cw_table;
+ const size_t cw_table_size_save = cw_table_size;
+ cw_table = table;
+ cw_table_size = (size_t)tv_list_len(l);
+
+ // Check that the new value does not conflict with 'listchars' or
+ // 'fillchars'.
+ const char *const error = check_chars_options();
+ if (error != NULL) {
+ emsg(_(error));
+ cw_table = cw_table_save;
+ cw_table_size = cw_table_size_save;
+ xfree(table);
+ return;
+ }
+
+ xfree(cw_table_save);
+ redraw_all_later(NOT_VALID);
+}
+
+void f_charclass(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (tv_check_for_string(&argvars[0]) == FAIL
+ || argvars[0].vval.v_string == NULL) {
+ return;
+ }
+ rettv->vval.v_number = mb_get_class((const char_u *)argvars[0].vval.v_string);
+}
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index 1e5e332ad9..2a9afcbd03 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -5,8 +5,9 @@
#include <stdint.h>
#include <string.h>
+#include "nvim/eval/typval.h"
#include "nvim/func_attr.h"
-#include "nvim/iconv.h"
+#include "nvim/mbyte_defs.h"
#include "nvim/os/os_defs.h" // For indirect
#include "nvim/types.h" // for char_u
@@ -19,52 +20,6 @@
#define MB_BYTE2LEN(b) utf8len_tab[b]
#define MB_BYTE2LEN_CHECK(b) (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b])
-// max length of an unicode char
-#define MB_MAXCHAR 6
-
-// properties used in enc_canon_table[] (first three mutually exclusive)
-#define ENC_8BIT 0x01
-#define ENC_DBCS 0x02
-#define ENC_UNICODE 0x04
-
-#define ENC_ENDIAN_B 0x10 // Unicode: Big endian
-#define ENC_ENDIAN_L 0x20 // Unicode: Little endian
-
-#define ENC_2BYTE 0x40 // Unicode: UCS-2
-#define ENC_4BYTE 0x80 // Unicode: UCS-4
-#define ENC_2WORD 0x100 // Unicode: UTF-16
-
-#define ENC_LATIN1 0x200 // Latin1
-#define ENC_LATIN9 0x400 // Latin9
-#define ENC_MACROMAN 0x800 // Mac Roman (not Macro Man! :-)
-
-/// Flags for vimconv_T
-typedef enum {
- CONV_NONE = 0,
- CONV_TO_UTF8 = 1,
- CONV_9_TO_UTF8 = 2,
- CONV_TO_LATIN1 = 3,
- CONV_TO_LATIN9 = 4,
- CONV_ICONV = 5,
-} ConvFlags;
-
-#define MBYTE_NONE_CONV { \
- .vc_type = CONV_NONE, \
- .vc_factor = 1, \
- .vc_fail = false, \
-}
-
-/// Structure used for string conversions
-typedef struct {
- int vc_type; ///< Zero or more ConvFlags.
- int vc_factor; ///< Maximal expansion factor.
-#ifdef HAVE_ICONV
- iconv_t vc_fd; ///< Value for CONV_ICONV.
-#endif
- bool vc_fail; ///< What to do with invalid characters: if true, fail,
- ///< otherwise use '?'.
-} vimconv_T;
-
extern const uint8_t utf8len_tab_zero[256];
extern const uint8_t utf8len_tab[256];
diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h
new file mode 100644
index 0000000000..53b01a211f
--- /dev/null
+++ b/src/nvim/mbyte_defs.h
@@ -0,0 +1,56 @@
+#ifndef NVIM_MBYTE_DEFS_H
+#define NVIM_MBYTE_DEFS_H
+
+#include <stdbool.h>
+
+#include "nvim/iconv.h"
+
+/// max length of an unicode char
+enum { MB_MAXCHAR = 6, };
+
+/// properties used in enc_canon_table[] (first three mutually exclusive)
+enum {
+ ENC_8BIT = 0x01,
+ ENC_DBCS = 0x02,
+ ENC_UNICODE = 0x04,
+
+ ENC_ENDIAN_B = 0x10, ///< Unicode: Big endian
+ ENC_ENDIAN_L = 0x20, ///< Unicode: Little endian
+
+ ENC_2BYTE = 0x40, ///< Unicode: UCS-2
+ ENC_4BYTE = 0x80, ///< Unicode: UCS-4
+ ENC_2WORD = 0x100, ///< Unicode: UTF-16
+
+ ENC_LATIN1 = 0x200, ///< Latin1
+ ENC_LATIN9 = 0x400, ///< Latin9
+ ENC_MACROMAN = 0x800, ///< Mac Roman (not Macro Man! :-)
+};
+
+/// Flags for vimconv_T
+typedef enum {
+ CONV_NONE = 0,
+ CONV_TO_UTF8 = 1,
+ CONV_9_TO_UTF8 = 2,
+ CONV_TO_LATIN1 = 3,
+ CONV_TO_LATIN9 = 4,
+ CONV_ICONV = 5,
+} ConvFlags;
+
+#define MBYTE_NONE_CONV { \
+ .vc_type = CONV_NONE, \
+ .vc_factor = 1, \
+ .vc_fail = false, \
+}
+
+/// Structure used for string conversions
+typedef struct {
+ int vc_type; ///< Zero or more ConvFlags.
+ int vc_factor; ///< Maximal expansion factor.
+#ifdef HAVE_ICONV
+ iconv_t vc_fd; ///< Value for CONV_ICONV.
+#endif
+ bool vc_fail; ///< What to do with invalid characters: if true, fail,
+ ///< otherwise use '?'.
+} vimconv_T;
+
+#endif // NVIM_MBYTE_DEFS_H
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index c828334eaf..216ee26620 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -821,7 +821,7 @@ static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags)
/// Initialize an empty hash table.
static void mf_hash_init(mf_hashtab_T *mht)
{
- memset(mht, 0, sizeof(mf_hashtab_T));
+ CLEAR_POINTER(mht);
mht->mht_buckets = mht->mht_small_buckets;
mht->mht_mask = MHT_INIT_SIZE - 1;
}
@@ -924,7 +924,7 @@ static void mf_hash_grow(mf_hashtab_T *mht)
/// a power of two.
mf_hashitem_T *tails[MHT_GROWTH_FACTOR];
- memset(tails, 0, sizeof(tails));
+ CLEAR_FIELD(tails);
for (mf_hashitem_T *mhi = mht->mht_buckets[i];
mhi != NULL; mhi = mhi->mhi_next) {
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index fa3a400a68..23bc5d59c8 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -44,9 +44,11 @@
#include <string.h>
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/fileio.h"
#include "nvim/func_attr.h"
@@ -65,7 +67,6 @@
#include "nvim/os/process.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/screen.h"
#include "nvim/sha256.h"
#include "nvim/spell.h"
#include "nvim/strings.h"
@@ -383,7 +384,7 @@ void ml_setname(buf_T *buf)
bool success = false;
memfile_T *mfp;
char_u *fname;
- char_u *dirp;
+ char *dirp;
mfp = buf->b_ml.ml_mfp;
if (mfp->mf_fd < 0) { // there is no swap file yet
@@ -397,17 +398,14 @@ void ml_setname(buf_T *buf)
return;
}
- /*
- * Try all directories in the 'directory' option.
- */
- dirp = p_dir;
+ // Try all directories in the 'directory' option.
+ dirp = (char *)p_dir;
bool found_existing_dir = false;
for (;;) {
if (*dirp == NUL) { // tried all directories, fail
break;
}
- fname = (char_u *)findswapname(buf, (char **)&dirp, (char *)mfp->mf_fname,
- &found_existing_dir);
+ fname = (char_u *)findswapname(buf, &dirp, (char *)mfp->mf_fname, &found_existing_dir);
// alloc's fname
if (dirp == NULL) { // out of memory
break;
@@ -472,7 +470,7 @@ void ml_open_file(buf_T *buf)
{
memfile_T *mfp;
char_u *fname;
- char_u *dirp;
+ char *dirp;
mfp = buf->b_ml.ml_mfp;
if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf
@@ -491,10 +489,8 @@ void ml_open_file(buf_T *buf)
return;
}
- /*
- * Try all directories in 'directory' option.
- */
- dirp = p_dir;
+ // Try all directories in 'directory' option.
+ dirp = (char *)p_dir;
bool found_existing_dir = false;
for (;;) {
if (*dirp == NUL) {
@@ -503,8 +499,7 @@ void ml_open_file(buf_T *buf)
// There is a small chance that between choosing the swap file name
// and creating it, another Vim creates the file. In that case the
// creation will fail and we will use another directory.
- fname = (char_u *)findswapname(buf, (char **)&dirp, NULL,
- &found_existing_dir);
+ fname = (char_u *)findswapname(buf, &dirp, NULL, &found_existing_dir);
if (dirp == NULL) {
break; // out of memory
}
@@ -1141,7 +1136,7 @@ void ml_recover(bool checkext)
if (txt_start <= (int)HEADER_SIZE
|| txt_start >= (int)dp->db_txt_end) {
p = (char_u *)"???";
- ++error;
+ error++;
} else {
p = (char_u *)dp + txt_start;
}
@@ -1210,10 +1205,10 @@ void ml_recover(bool checkext)
if (got_int) {
emsg(_("E311: Recovery Interrupted"));
} else if (error) {
- ++no_wait_return;
+ no_wait_return++;
msg(">>>>>>>>>>>>>");
emsg(_("E312: Errors detected while recovering; look for lines starting with ???"));
- --no_wait_return;
+ no_wait_return--;
msg(_("See \":help E312\" for more information."));
msg(">>>>>>>>>>>>>");
} else {
@@ -1272,7 +1267,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out)
int num_files;
int file_count = 0;
char **files;
- char_u *dirp;
+ char *dirp;
char_u *dir_name;
char_u *fname_res = NULL;
#ifdef HAVE_READLINK
@@ -1299,12 +1294,12 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out)
// Do the loop for every directory in 'directory'.
// First allocate some memory to put the directory name in.
dir_name = xmalloc(STRLEN(p_dir) + 1);
- dirp = p_dir;
+ dirp = (char *)p_dir;
while (*dirp) {
// Isolate a directory name from *dirp and put it in dir_name (we know
// it is large enough, so use 31000 for length).
// Advance dirp to next directory name.
- (void)copy_option_part((char **)&dirp, (char *)dir_name, 31000, ",");
+ (void)copy_option_part(&dirp, (char *)dir_name, 31000, ",");
if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir
if (fname == NULL) {
@@ -1395,7 +1390,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out)
file_count += num_files;
if (nr <= file_count) {
*fname_out = vim_strsave((char_u *)files[nr - 1 + num_files - file_count]);
- dirp = (char_u *)""; // stop searching
+ dirp = ""; // stop searching
}
} else if (list) {
if (dir_name[0] == '.' && dir_name[1] == NUL) {
@@ -1660,12 +1655,12 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot)
p += i; // file name has been expanded to full path
}
if (STRCMP(p, names[num_names]) != 0) {
- ++num_names;
+ num_names++;
} else {
xfree(names[num_names]);
}
} else {
- ++num_names;
+ num_names++;
}
return num_names;
@@ -2184,7 +2179,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b
memmove((char *)dp_right + dp_right->db_txt_start,
line, (size_t)len);
- ++line_count_right;
+ line_count_right++;
}
/*
* may move lines from the left/old block to the right/new one.
@@ -2224,7 +2219,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b
}
memmove((char *)dp_left + dp_left->db_txt_start,
line, (size_t)len);
- ++line_count_left;
+ line_count_left++;
}
if (db_idx < 0) { // left block is new
@@ -2998,9 +2993,9 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
* update high for insert/delete
*/
if (action == ML_INSERT) {
- ++high;
+ high++;
} else if (action == ML_DELETE) {
- --high;
+ high--;
}
dp = hp->bh_data;
@@ -3304,7 +3299,7 @@ static void attention_message(buf_T *buf, char_u *fname)
{
assert(buf->b_fname != NULL);
- ++no_wait_return;
+ no_wait_return++;
(void)emsg(_("E325: ATTENTION"));
msg_puts(_("\nFound a swap file by the name \""));
msg_home_replace(fname);
@@ -3339,7 +3334,7 @@ static void attention_message(buf_T *buf, char_u *fname)
msg_outtrans((char *)fname);
msg_puts(_("\"\n to avoid this message.\n"));
cmdline_row = msg_row;
- --no_wait_return;
+ no_wait_return--;
}
/// Trigger the SwapExists autocommands.
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 5ae7f7bbe3..fb36d4ccf4 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -9,6 +9,7 @@
#include <string.h>
#include "nvim/api/extmark.h"
+#include "nvim/arglist.h"
#include "nvim/context.h"
#include "nvim/decoration_provider.h"
#include "nvim/eval.h"
@@ -617,8 +618,10 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
#if defined(EXITFREE)
+# include "nvim/autocmd.h"
# include "nvim/buffer.h"
# include "nvim/charset.h"
+# include "nvim/cmdhist.h"
# include "nvim/diff.h"
# include "nvim/edit.h"
# include "nvim/eval/typval.h"
@@ -626,9 +629,9 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/ex_docmd.h"
# include "nvim/ex_getln.h"
# include "nvim/file_search.h"
-# include "nvim/fileio.h"
# include "nvim/fold.h"
# include "nvim/getchar.h"
+# include "nvim/grid.h"
# include "nvim/mark.h"
# include "nvim/mbyte.h"
# include "nvim/memline.h"
@@ -640,7 +643,6 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/path.h"
# include "nvim/quickfix.h"
# include "nvim/regexp.h"
-# include "nvim/screen.h"
# include "nvim/search.h"
# include "nvim/spell.h"
# include "nvim/syntax.h"
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 16802a4e50..c3cf4457fc 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -23,7 +23,7 @@
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/strings.h"
@@ -952,7 +952,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
}
while (*p != NUL && ascii_iswhite(*p)) {
- ++p;
+ p++;
}
arg = after_dot = p;
@@ -1659,26 +1659,19 @@ void ex_emenu(exarg_T *eap)
if (arg[0] && ascii_iswhite(arg[1])) {
switch (arg[0]) {
case 'n':
- mode_idx = MENU_INDEX_NORMAL;
- break;
+ mode_idx = MENU_INDEX_NORMAL; break;
case 'v':
- mode_idx = MENU_INDEX_VISUAL;
- break;
+ mode_idx = MENU_INDEX_VISUAL; break;
case 's':
- mode_idx = MENU_INDEX_SELECT;
- break;
+ mode_idx = MENU_INDEX_SELECT; break;
case 'o':
- mode_idx = MENU_INDEX_OP_PENDING;
- break;
+ mode_idx = MENU_INDEX_OP_PENDING; break;
case 't':
- mode_idx = MENU_INDEX_TERMINAL;
- break;
+ mode_idx = MENU_INDEX_TERMINAL; break;
case 'i':
- mode_idx = MENU_INDEX_INSERT;
- break;
+ mode_idx = MENU_INDEX_INSERT; break;
case 'c':
- mode_idx = MENU_INDEX_CMDLINE;
- break;
+ mode_idx = MENU_INDEX_CMDLINE; break;
default:
semsg(_(e_invarg2), arg);
return;
@@ -1814,9 +1807,9 @@ static char *menu_skip_part(char *p)
{
while (*p != NUL && *p != '.' && !ascii_iswhite(*p)) {
if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) {
- ++p;
+ p++;
}
- ++p;
+ p++;
}
return p;
}
diff --git a/src/nvim/menu.h b/src/nvim/menu.h
index 9a60ebfb83..be294a1831 100644
--- a/src/nvim/menu.h
+++ b/src/nvim/menu.h
@@ -4,28 +4,9 @@
#include <stdbool.h> // for bool
#include "nvim/ex_cmds_defs.h" // for exarg_T
+#include "nvim/menu_defs.h"
#include "nvim/types.h" // for char_u and expand_T
-/// @}
-/// note MENU_INDEX_TIP is not a 'real' mode
-
-/// Menu modes
-/// \addtogroup MENU_MODES
-/// @{
-#define MENU_NORMAL_MODE (1 << MENU_INDEX_NORMAL)
-#define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL)
-#define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT)
-#define MENU_OP_PENDING_MODE (1 << MENU_INDEX_OP_PENDING)
-#define MENU_INSERT_MODE (1 << MENU_INDEX_INSERT)
-#define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE)
-#define MENU_TERMINAL_MODE (1 << MENU_INDEX_TERMINAL)
-#define MENU_TIP_MODE (1 << MENU_INDEX_TIP)
-#define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1)
-/// @}
-
-/// Start a menu name with this to not include it on the main menu bar
-#define MNU_HIDDEN_CHAR ']'
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "menu.h.generated.h"
#endif
diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h
new file mode 100644
index 0000000000..5fdb222bde
--- /dev/null
+++ b/src/nvim/menu_defs.h
@@ -0,0 +1,64 @@
+#ifndef NVIM_MENU_DEFS_H
+#define NVIM_MENU_DEFS_H
+
+#include <stdbool.h> // for bool
+
+/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode
+/// \addtogroup MENU_INDEX
+/// @{
+enum {
+ MENU_INDEX_INVALID = -1,
+ MENU_INDEX_NORMAL = 0,
+ MENU_INDEX_VISUAL = 1,
+ MENU_INDEX_SELECT = 2,
+ MENU_INDEX_OP_PENDING = 3,
+ MENU_INDEX_INSERT = 4,
+ MENU_INDEX_CMDLINE = 5,
+ MENU_INDEX_TERMINAL = 6,
+ MENU_INDEX_TIP = 7,
+ MENU_MODES = 8,
+};
+/// @}
+
+/// Menu modes
+/// \addtogroup MENU_MODES
+/// @{
+enum {
+ MENU_NORMAL_MODE = 1 << MENU_INDEX_NORMAL,
+ MENU_VISUAL_MODE = 1 << MENU_INDEX_VISUAL,
+ MENU_SELECT_MODE = 1 << MENU_INDEX_SELECT,
+ MENU_OP_PENDING_MODE = 1 << MENU_INDEX_OP_PENDING,
+ MENU_INSERT_MODE = 1 << MENU_INDEX_INSERT,
+ MENU_CMDLINE_MODE = 1 << MENU_INDEX_CMDLINE,
+ MENU_TERMINAL_MODE = 1 << MENU_INDEX_TERMINAL,
+ MENU_TIP_MODE = 1 << MENU_INDEX_TIP,
+ MENU_ALL_MODES = (1 << MENU_INDEX_TIP) - 1,
+};
+/// @}
+/// note MENU_INDEX_TIP is not a 'real' mode
+
+/// Start a menu name with this to not include it on the main menu bar
+#define MNU_HIDDEN_CHAR ']'
+
+typedef struct VimMenu vimmenu_T;
+
+struct VimMenu {
+ int modes; ///< Which modes is this menu visible for
+ int enabled; ///< for which modes the menu is enabled
+ char *name; ///< Name of menu, possibly translated
+ char *dname; ///< Displayed Name ("name" without '&')
+ char *en_name; ///< "name" untranslated, NULL when
+ ///< was not translated
+ char *en_dname; ///< NULL when "dname" untranslated
+ int mnemonic; ///< mnemonic key (after '&')
+ char *actext; ///< accelerator text (after TAB)
+ long priority; ///< Menu order priority
+ char *strings[MENU_MODES]; ///< Mapped string for each mode
+ int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
+ bool silent[MENU_MODES]; ///< A silent flag for each mode
+ vimmenu_T *children; ///< Children of sub-menu
+ vimmenu_T *parent; ///< Parent of menu
+ vimmenu_T *next; ///< Next item in menu
+};
+
+#endif // NVIM_MENU_DEFS_H
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 7cccd046c9..684cf7207c 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -15,6 +15,7 @@
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
@@ -23,7 +24,9 @@
#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
+#include "nvim/indent.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
#include "nvim/main.h"
@@ -38,7 +41,7 @@
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -318,7 +321,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
if (entered >= 3) {
return TRUE;
}
- ++entered;
+ entered++;
// Add message to history (unless it's a repeated kept message or a
// truncated message)
@@ -355,7 +358,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
need_fileinfo = false;
xfree(buf);
- --entered;
+ entered--;
return retval;
}
@@ -524,7 +527,7 @@ int smsg_attr_keep(int attr, const char *s, ...)
* isn't printed each time when it didn't change.
*/
static int last_sourcing_lnum = 0;
-static char_u *last_sourcing_name = NULL;
+static char *last_sourcing_name = NULL;
/// Reset the last used sourcing name/lnum. Makes sure it is displayed again
/// for the next error message;
@@ -534,16 +537,16 @@ void reset_last_sourcing(void)
last_sourcing_lnum = 0;
}
-/// @return TRUE if "sourcing_name" differs from "last_sourcing_name".
-static int other_sourcing_name(void)
+/// @return true if "SOURCING_NAME" differs from "last_sourcing_name".
+static bool other_sourcing_name(void)
{
- if (sourcing_name != NULL) {
+ if (SOURCING_NAME != NULL) {
if (last_sourcing_name != NULL) {
- return STRCMP(sourcing_name, last_sourcing_name) != 0;
+ return strcmp(SOURCING_NAME, last_sourcing_name) != 0;
}
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
/// Get the message about the source, as used for an error message
@@ -553,11 +556,19 @@ static int other_sourcing_name(void)
static char *get_emsg_source(void)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (sourcing_name != NULL && other_sourcing_name()) {
+ if (SOURCING_NAME != NULL && other_sourcing_name()) {
+ char *sname = estack_sfile(ESTACK_NONE);
+ char *tofree = sname;
+
+ if (sname == NULL) {
+ sname = SOURCING_NAME;
+ }
+
const char *const p = _("Error detected while processing %s:");
- const size_t buf_len = STRLEN(sourcing_name) + strlen(p) + 1;
+ const size_t buf_len = STRLEN(sname) + strlen(p) + 1;
char *const buf = xmalloc(buf_len);
- snprintf(buf, buf_len, p, sourcing_name);
+ snprintf(buf, buf_len, p, sname);
+ xfree(tofree);
return buf;
}
return NULL;
@@ -572,13 +583,13 @@ static char *get_emsg_lnum(void)
{
// lnum is 0 when executing a command from the command line
// argument, we don't want a line number then
- if (sourcing_name != NULL
- && (other_sourcing_name() || sourcing_lnum != last_sourcing_lnum)
- && sourcing_lnum != 0) {
+ if (SOURCING_NAME != NULL
+ && (other_sourcing_name() || SOURCING_LNUM != last_sourcing_lnum)
+ && SOURCING_LNUM != 0) {
const char *const p = _("line %4ld:");
const size_t buf_len = 20 + strlen(p);
char *const buf = xmalloc(buf_len);
- snprintf(buf, buf_len, p, (long)sourcing_lnum);
+ snprintf(buf, buf_len, p, (long)SOURCING_LNUM);
return buf;
}
return NULL;
@@ -589,6 +600,15 @@ static char *get_emsg_lnum(void)
/// is only displayed if it changed.
void msg_source(int attr)
{
+ static bool recursive = false;
+
+ // Bail out if something called here causes an error.
+ if (recursive) {
+ return;
+ }
+ recursive = true;
+
+ msg_scroll = true; // this will take more than one line
no_wait_return++;
char *p = get_emsg_source();
if (p != NULL) {
@@ -599,19 +619,19 @@ void msg_source(int attr)
if (p != NULL) {
msg_attr(p, HL_ATTR(HLF_N));
xfree(p);
- last_sourcing_lnum = sourcing_lnum; // only once for each line
+ last_sourcing_lnum = SOURCING_LNUM; // only once for each line
}
// remember the last sourcing name printed, also when it's empty
- if (sourcing_name == NULL || other_sourcing_name()) {
- xfree(last_sourcing_name);
- if (sourcing_name == NULL) {
- last_sourcing_name = NULL;
- } else {
- last_sourcing_name = vim_strsave((char_u *)sourcing_name);
+ if (SOURCING_NAME == NULL || other_sourcing_name()) {
+ XFREE_CLEAR(last_sourcing_name);
+ if (SOURCING_NAME != NULL) {
+ last_sourcing_name = xstrdup(SOURCING_NAME);
}
}
- --no_wait_return;
+ no_wait_return--;
+
+ recursive = false;
}
/// @return TRUE if not giving error messages right now:
@@ -686,9 +706,9 @@ static bool emsg_multiline(const char *s, bool multiline)
}
// Log (silent) errors as debug messages.
- if (sourcing_name != NULL && sourcing_lnum != 0) {
+ if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
DLOG("(:silent) %s (%s (line %ld))",
- s, sourcing_name, (long)sourcing_lnum);
+ s, SOURCING_NAME, (long)SOURCING_LNUM);
} else {
DLOG("(:silent) %s", s);
}
@@ -697,8 +717,8 @@ static bool emsg_multiline(const char *s, bool multiline)
}
// Log editor errors as INFO.
- if (sourcing_name != NULL && sourcing_lnum != 0) {
- ILOG("%s (%s (line %ld))", s, sourcing_name, (long)sourcing_lnum);
+ if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
+ ILOG("%s (%s (line %ld))", s, SOURCING_NAME, (long)SOURCING_LNUM);
} else {
ILOG("%s", s);
}
@@ -722,7 +742,6 @@ static bool emsg_multiline(const char *s, bool multiline)
}
emsg_on_display = true; // remember there is an error message
- msg_scroll++; // don't overwrite a previous message
attr = HL_ATTR(HLF_E); // set highlight mode for error messages
if (msg_scrolled != 0) {
need_wait_return = true; // needed in case emsg() is called after
@@ -733,9 +752,8 @@ static bool emsg_multiline(const char *s, bool multiline)
msg_ext_set_kind("emsg");
}
- /*
- * Display name and line number for the source of the error.
- */
+ // Display name and line number for the source of the error.
+ // Sets "msg_scroll".
msg_source(attr);
// Display the error message itself.
@@ -812,8 +830,15 @@ static bool semsgv(const char *fmt, va_list ap)
/// detected when fuzzing vim.
void iemsg(const char *s)
{
+ if (emsg_not_now()) {
+ return;
+ }
+
emsg(s);
#ifdef ABORT_ON_INTERNAL_ERROR
+ set_vim_var_string(VV_ERRMSG, s, -1);
+ msg_putchar('\n'); // avoid overwriting the error message
+ ui_flush();
abort();
#endif
}
@@ -823,11 +848,17 @@ void iemsg(const char *s)
/// detected when fuzzing vim.
void siemsg(const char *s, ...)
{
+ if (emsg_not_now()) {
+ return;
+ }
+
va_list ap;
va_start(ap, s);
(void)semsgv(s, ap);
va_end(ap);
#ifdef ABORT_ON_INTERNAL_ERROR
+ msg_putchar('\n'); // avoid overwriting the error message
+ ui_flush();
abort();
#endif
}
@@ -999,7 +1030,7 @@ int delete_first_msg(void)
xfree(p->msg);
hl_msg_free(p->multiattr);
xfree(p);
- --msg_hist_len;
+ msg_hist_len--;
return OK;
}
@@ -1964,13 +1995,13 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr)
msg_col -= cw;
if (msg_col == 0) {
msg_col = Columns;
- ++msg_row;
+ msg_row++;
}
} else {
msg_col += cw;
if (msg_col >= Columns) {
msg_col = 0;
- ++msg_row;
+ msg_row++;
}
}
return s + l;
@@ -2123,7 +2154,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
int t_col = 0; // Screen cells todo, 0 when "t_s" not used.
int l;
int cw;
- const char_u *sb_str = str;
+ const char *sb_str = (char *)str;
int sb_col = msg_col;
int wrap;
int did_last_char;
@@ -2208,7 +2239,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
if (p_more) {
// Store text for scrolling back.
- store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true);
+ store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true);
}
inc_msg_scrolled();
@@ -2223,7 +2254,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
* for a character.
*/
if (lines_left > 0) {
- --lines_left;
+ lines_left--;
}
if (p_more && lines_left == 0 && State != MODE_HITRETURN
&& !msg_no_more && !exmode_active) {
@@ -2255,7 +2286,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
if (wrap && p_more && !recurse) {
// Store text for scrolling back.
- store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true);
+ store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true);
}
if (*s == '\n') { // go to next line
@@ -2272,7 +2303,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
msg_col = 0;
} else if (*s == '\b') { // go to previous char
if (msg_col) {
- --msg_col;
+ msg_col--;
}
} else if (*s == TAB) { // translate Tab into spaces
do {
@@ -2306,7 +2337,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
s += l - 1;
}
}
- ++s;
+ s++;
}
// Output any postponed text.
@@ -2314,7 +2345,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs
t_puts(&t_col, t_s, s, attr);
}
if (p_more && !recurse) {
- store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, false);
+ store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, false);
}
msg_check();
@@ -2457,7 +2488,7 @@ void msg_reset_scroll(void)
static void inc_msg_scrolled(void)
{
if (*get_vim_var_str(VV_SCROLLSTART) == NUL) {
- char *p = sourcing_name;
+ char *p = SOURCING_NAME;
char *tofree = NULL;
// v:scrollstart is empty, set it to the script/function name and line
@@ -2468,7 +2499,7 @@ static void inc_msg_scrolled(void)
size_t len = strlen(p) + 40;
tofree = xmalloc(len);
vim_snprintf(tofree, len, _("%s line %" PRId64),
- p, (int64_t)sourcing_lnum);
+ p, (int64_t)SOURCING_LNUM);
p = tofree;
}
set_vim_var_string(VV_SCROLLSTART, p, -1);
@@ -2497,7 +2528,7 @@ static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE;
/// @param sb_str start of string
/// @param s just after string
/// @param finish line ends
-static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int finish)
+static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int finish)
{
msgchunk_T *mp;
@@ -2654,7 +2685,7 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
msg_col = mp->sb_msg_col;
p = mp->sb_text;
if (*p == '\n') { // don't display the line break
- ++p;
+ p++;
}
msg_puts_display(p, -1, mp->sb_attr, TRUE);
if (mp->sb_eol || mp->sb_next == NULL) {
@@ -2683,7 +2714,7 @@ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr)
}
if (msg_col >= Columns) {
msg_col = 0;
- ++msg_row;
+ msg_row++;
}
}
@@ -2938,7 +2969,7 @@ static int do_more_prompt(int typed_char)
HL_ATTR(HLF_MSG));
for (i = 0; mp != NULL && i < Rows - 1; i++) {
mp = disp_sb_line(i, mp);
- ++msg_scrolled;
+ msg_scrolled++;
}
to_redraw = false;
}
@@ -3037,12 +3068,12 @@ static void msg_screen_putchar(int c, int attr)
if (cmdmsg_rl) {
if (--msg_col == 0) {
msg_col = Columns;
- ++msg_row;
+ msg_row++;
}
} else {
if (++msg_col >= Columns) {
msg_col = 0;
- ++msg_row;
+ msg_row++;
}
}
}
@@ -3339,7 +3370,7 @@ int redirecting(void)
void verbose_enter(void)
{
if (*p_vfile != NUL) {
- ++msg_silent;
+ msg_silent++;
}
}
@@ -3358,7 +3389,7 @@ void verbose_leave(void)
void verbose_enter_scroll(void)
{
if (*p_vfile != NUL) {
- ++msg_silent;
+ msg_silent++;
} else {
// always scroll up, don't overwrite
msg_scroll = TRUE;
@@ -3520,7 +3551,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl
* Since we wait for a keypress, don't make the
* user press RETURN as well afterwards.
*/
- ++no_wait_return;
+ no_wait_return++;
hotkeys = msg_show_console_dialog(message, buttons, dfltbutton);
for (;;) {
@@ -3569,7 +3600,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl
msg_silent = save_msg_silent;
State = oldState;
setmouse();
- --no_wait_return;
+ no_wait_return--;
msg_end_prompt();
return retval;
@@ -3723,7 +3754,7 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int def
}
} else if (*r == DLG_HOTKEY_CHAR || first_hotkey) {
if (*r == DLG_HOTKEY_CHAR) {
- ++r;
+ r++;
}
first_hotkey = false;
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index a4a521fa80..a8d0b3b584 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -8,13 +8,14 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/fold.h"
+#include "nvim/grid.h"
#include "nvim/memline.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/os_unix.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
@@ -386,7 +387,7 @@ retnomove:
count = 0;
for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count;) {
if (curwin->w_topfill > 0) {
- ++count;
+ count++;
} else {
count += plines_win(curwin, curwin->w_topline, true);
}
@@ -514,7 +515,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
break; // past end of file
}
row -= count;
- ++lnum;
+ lnum++;
}
if (!retval) {
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 6d4eb8ef49..1ed7acd012 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -21,16 +21,18 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
+#include "nvim/grid.h"
+#include "nvim/highlight.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/plines.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/window.h"
@@ -114,7 +116,7 @@ static void redraw_for_cursorcolumn(win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) {
- if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC]) && using_hlsearch())) {
+ if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) {
// When 'cursorcolumn' is set or "CurSearch" is in use
// need to redraw with SOME_VALID.
redraw_later(wp, SOME_VALID);
@@ -1056,7 +1058,7 @@ bool scrolldown(long line_count, int byfold)
// A sequence of folded lines only counts for one logical line
linenr_T first;
if (hasFolding(curwin->w_topline, &first, NULL)) {
- ++done;
+ done++;
if (!byfold) {
line_count -= curwin->w_topline - first - 1;
}
@@ -1092,7 +1094,7 @@ bool scrolldown(long line_count, int byfold)
while (wrow >= curwin->w_height_inner && curwin->w_cursor.lnum > 1) {
linenr_T first;
if (hasFolding(curwin->w_cursor.lnum, &first, NULL)) {
- --wrow;
+ wrow--;
if (first == 1) {
curwin->w_cursor.lnum = 1;
} else {
@@ -1406,8 +1408,8 @@ void scroll_cursor_top(int min_scroll, int always)
}
if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) {
- --top;
- ++bot;
+ top--;
+ bot++;
} else {
top = curwin->w_cursor.lnum - 1;
bot = curwin->w_cursor.lnum + 1;
@@ -1453,8 +1455,8 @@ void scroll_cursor_top(int min_scroll, int always)
extra += i;
new_topline = top;
- --top;
- ++bot;
+ top--;
+ bot++;
}
/*
@@ -1664,7 +1666,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
for (i = 0; i < scrolled && boff.lnum < curwin->w_botline;) {
botline_forw(curwin, &boff);
i += boff.height;
- ++line_count;
+ line_count++;
}
if (i < scrolled) { // below curwin->w_botline, don't scroll
line_count = 9999;
@@ -1726,7 +1728,7 @@ void scroll_cursor_halfway(int atend)
} else {
++below; // count a "~" line
if (atend) {
- ++used;
+ used++;
}
}
}
@@ -1835,7 +1837,7 @@ void cursor_correct(void)
if (topline < botline) {
above += win_get_fill(curwin, topline + 1);
}
- ++topline;
+ topline++;
}
}
if (topline == botline || botline == 0) {
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2d752eade7..c87c0cbb6e 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -18,10 +18,13 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/ex_cmds.h"
@@ -32,7 +35,8 @@
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
+#include "nvim/help.h"
#include "nvim/indent.h"
#include "nvim/keycodes.h"
#include "nvim/log.h"
@@ -41,6 +45,7 @@
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
+#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
@@ -50,11 +55,12 @@
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
+#include "nvim/profile.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
+#include "nvim/spellsuggest.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
@@ -465,7 +471,7 @@ void normal_enter(bool cmdwin, bool noexmode)
static void normal_prepare(NormalState *s)
{
- memset(&s->ca, 0, sizeof(s->ca)); // also resets ca.retval
+ CLEAR_FIELD(s->ca); // also resets s->ca.retval
s->ca.oap = &s->oa;
// Use a count remembered from before entering an operator. After typing "3d"
@@ -1276,10 +1282,10 @@ static void normal_redraw(NormalState *s)
if (VIsual_active) {
redraw_curbuf_later(INVERTED); // update inverted part
- update_screen(INVERTED);
+ update_screen(0);
} else if (must_redraw) {
update_screen(0);
- } else if (redraw_cmdline || clear_cmdline) {
+ } else if (redraw_cmdline || clear_cmdline || redraw_mode) {
showmode();
}
@@ -1832,7 +1838,8 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
}
if (jump_flags) {
jump_flags = jump_to_mouse(jump_flags, NULL, which_button);
- update_curbuf(VIsual_active ? INVERTED : VALID);
+ redraw_curbuf_later(VIsual_active ? INVERTED : VALID);
+ update_screen(0);
setcursor();
ui_flush(); // Update before showing popup menu
}
@@ -2383,7 +2390,7 @@ static bool find_is_eval_item(const char_u *const ptr, int *const colp, int *con
///
/// If text is found, a pointer to the text is put in "*text". This
/// points into the current buffer line and is not always NUL terminated.
-size_t find_ident_under_cursor(char_u **text, int find_type)
+size_t find_ident_under_cursor(char **text, int find_type)
FUNC_ATTR_NONNULL_ARG(1)
{
return find_ident_at_pos(curwin, curwin->w_cursor.lnum,
@@ -2394,7 +2401,7 @@ size_t find_ident_under_cursor(char_u **text, int find_type)
/// However: Uses 'iskeyword' from the current window!.
///
/// @param textcol column where "text" starts, can be NULL
-size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char_u **text, int *textcol,
+size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text, int *textcol,
int find_type)
FUNC_ATTR_NONNULL_ARG(1, 4)
{
@@ -2470,7 +2477,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char_u **te
return 0;
}
ptr += col;
- *text = ptr;
+ *text = (char *)ptr;
if (textcol != NULL) {
*textcol = col;
}
@@ -3035,9 +3042,9 @@ static void nv_page(cmdarg_T *cap)
static void nv_gd(oparg_T *oap, int nchar, int thisblock)
{
size_t len;
- char_u *ptr;
+ char *ptr;
if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0
- || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) {
+ || !find_decl((char_u *)ptr, len, nchar == 'd', thisblock, SEARCH_START)) {
clearopbeep(oap);
} else {
if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) {
@@ -3557,7 +3564,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
if (checkclearop(cap->oap)) {
return OK;
}
- char_u *ptr = NULL;
+ char *ptr = NULL;
size_t len;
if (VIsual_active && !get_visual_text(cap, &ptr, &len)) {
return FAIL;
@@ -3572,7 +3579,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
len = spell_move_to(curwin, FORWARD, true, true, NULL);
emsg_off--;
if (len != 0 && curwin->w_cursor.col <= pos.col) {
- ptr = ml_get_pos(&curwin->w_cursor);
+ ptr = (char *)ml_get_pos(&curwin->w_cursor);
}
curwin->w_cursor = pos;
}
@@ -3581,7 +3588,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
return FAIL;
}
assert(len <= INT_MAX);
- spell_add_word(ptr, (int)len,
+ spell_add_word((char_u *)ptr, (int)len,
nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD,
(nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1,
undo);
@@ -4148,7 +4155,7 @@ void do_nv_ident(int c1, int c2)
cmdarg_T ca;
clear_oparg(&oa);
- memset(&ca, 0, sizeof(ca));
+ CLEAR_FIELD(ca);
ca.oap = &oa;
ca.cmdchar = c1;
ca.nchar = c2;
@@ -4157,7 +4164,7 @@ void do_nv_ident(int c1, int c2)
/// 'K' normal-mode command. Get the command to lookup the keyword under the
/// cursor.
-static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char_u **ptr_arg,
+static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char **ptr_arg,
size_t n, char *buf, size_t buf_size)
{
if (kp_help) {
@@ -4176,7 +4183,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c
return n;
}
- char_u *ptr = *ptr_arg;
+ char *ptr = *ptr_arg;
// An external command will probably use an argument starting
// with "-" as an option. To avoid trouble we skip the "-".
@@ -4226,7 +4233,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c
/// g ']' :tselect for current identifier
static void nv_ident(cmdarg_T *cap)
{
- char_u *ptr = NULL;
+ char *ptr = NULL;
char_u *p;
size_t n = 0; // init for GCC
int cmdchar;
@@ -4272,7 +4279,7 @@ static void nv_ident(cmdarg_T *cap)
assert(*kp != NUL); // option.c:do_set() should default to ":help" if empty.
bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command
bool kp_help = (STRCMP(kp, ":he") == 0 || STRCMP(kp, ":help") == 0);
- if (kp_help && *skipwhite((char *)ptr) == NUL) {
+ if (kp_help && *skipwhite(ptr) == NUL) {
emsg(_(e_noident)); // found white space only
return;
}
@@ -4288,9 +4295,9 @@ static void nv_ident(cmdarg_T *cap)
// Call setpcmark() first, so "*``" puts the cursor back where
// it was.
setpcmark();
- curwin->w_cursor.col = (colnr_T)(ptr - get_cursor_line_ptr());
+ curwin->w_cursor.col = (colnr_T)(ptr - (char *)get_cursor_line_ptr());
- if (!g_cmd && vim_iswordp(ptr)) {
+ if (!g_cmd && vim_iswordp((char_u *)ptr)) {
STRCPY(buf, "\\<");
}
no_smartcase = true; // don't use 'smartcase' now
@@ -4327,13 +4334,13 @@ static void nv_ident(cmdarg_T *cap)
// Now grab the chars in the identifier
if (cmdchar == 'K' && !kp_help) {
- ptr = vim_strnsave(ptr, n);
+ ptr = xstrnsave(ptr, n);
if (kp_ex) {
// Escape the argument properly for an Ex command
p = (char_u *)vim_strsave_fnameescape((const char *)ptr, VSE_NONE);
} else {
// Escape the argument properly for a shell command
- p = vim_strsave_shellescape(ptr, true, true);
+ p = vim_strsave_shellescape((char_u *)ptr, true, true);
}
xfree(ptr);
char *newbuf = xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1);
@@ -4364,11 +4371,11 @@ static void nv_ident(cmdarg_T *cap)
}
// When current byte is a part of multibyte character, copy all
// bytes of that character.
- const size_t len = (size_t)(utfc_ptr2len((char *)ptr) - 1);
+ const size_t len = (size_t)(utfc_ptr2len(ptr) - 1);
for (size_t i = 0; i < len && n > 0; i++, n--) {
- *p++ = *ptr++;
+ *p++ = (char_u)(*ptr++);
}
- *p++ = *ptr++;
+ *p++ = (char_u)(*ptr++);
}
*p = NUL;
}
@@ -4376,12 +4383,14 @@ static void nv_ident(cmdarg_T *cap)
// Execute the command.
if (cmdchar == '*' || cmdchar == '#') {
if (!g_cmd
- && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) {
+ && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), (char_u *)ptr))) {
STRCAT(buf, "\\>");
}
+
// put pattern in search history
init_history();
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
+
(void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
NULL);
} else {
@@ -4406,7 +4415,7 @@ static void nv_ident(cmdarg_T *cap)
/// @param lenp return: length of selected text
///
/// @return false if more than one line selected.
-bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp)
+bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp)
{
if (VIsual_mode != 'V') {
unadjust_for_sel();
@@ -4418,14 +4427,14 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp)
return false;
}
if (VIsual_mode == 'V') {
- *pp = get_cursor_line_ptr();
+ *pp = (char *)get_cursor_line_ptr();
*lenp = STRLEN(*pp);
} else {
if (lt(curwin->w_cursor, VIsual)) {
- *pp = ml_get_pos(&curwin->w_cursor);
+ *pp = (char *)ml_get_pos(&curwin->w_cursor);
*lenp = (size_t)VIsual.col - (size_t)curwin->w_cursor.col + 1;
} else {
- *pp = ml_get_pos(&VIsual);
+ *pp = (char *)ml_get_pos(&VIsual);
*lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1;
}
if (**pp == NUL) {
@@ -4433,7 +4442,7 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp)
}
if (*lenp > 0) {
// Correct the length to include all bytes of the last character.
- *lenp += (size_t)(utfc_ptr2len((char *)(*pp) + (*lenp - 1)) - 1);
+ *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
}
}
reset_VIsual_and_resel();
@@ -4842,7 +4851,7 @@ static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrap
cap->oap->use_reg_one = true;
curwin->w_set_curswant = true;
- memset(&sia, 0, sizeof(sia));
+ CLEAR_FIELD(sia);
i = do_search(cap->oap, dir, dir, pat, cap->count1,
opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia);
if (wrapped != NULL) {
@@ -5044,15 +5053,15 @@ static void nv_brackets(cmdarg_T *cap)
// fwd bwd fwd bwd fwd bwd
// identifier "]i" "[i" "]I" "[I" "]^I" "[^I"
// define "]d" "[d" "]D" "[D" "]^D" "[^D"
- char_u *ptr;
+ char *ptr;
size_t len;
if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) {
clearop(cap->oap);
} else {
// Make a copy, if the line was changed it will be freed.
- ptr = vim_strnsave(ptr, len);
- find_pattern_in_path(ptr, 0, len, true,
+ ptr = xstrnsave(ptr, len);
+ find_pattern_in_path((char_u *)ptr, 0, len, true,
cap->count0 == 0 ? !isupper(cap->nchar) : false,
(((cap->nchar & 0xf) == ('d' & 0xf))
? FIND_DEFINE
@@ -6923,6 +6932,10 @@ static void nv_esc(cmdarg_T *cap)
}
}
+ if (restart_edit != 0) {
+ redraw_mode = true; // remove "-- (insert) --"
+ }
+
restart_edit = 0;
if (cmdwin_type != 0) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 142a19e6d3..e121118fe9 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -17,6 +17,7 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
@@ -25,7 +26,6 @@
#include "nvim/ex_cmds2.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
-#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
@@ -48,7 +48,6 @@
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
@@ -1177,14 +1176,14 @@ static int stuff_yank(int regname, char_u *p)
}
yankreg_T *reg = get_yank_register(regname, YREG_YANK);
if (is_append_register(regname) && reg->y_array != NULL) {
- char_u **pp = (char_u **)&(reg->y_array[reg->y_size - 1]);
+ char **pp = &(reg->y_array[reg->y_size - 1]);
char_u *lp = xmalloc(STRLEN(*pp) + STRLEN(p) + 1);
STRCPY(lp, *pp);
// TODO(philix): use xstpcpy() in stuff_yank()
STRCAT(lp, p);
xfree(p);
xfree(*pp);
- *pp = lp;
+ *pp = (char *)lp;
} else {
free_register(reg);
set_yreg_additional_data(reg, NULL);
@@ -1459,7 +1458,7 @@ int insert_reg(int regname, bool literally_arg)
return FAIL;
}
- char_u *arg;
+ char *arg;
if (regname == '.') { // Insert last inserted text.
retval = stuff_inserted(NUL, 1L, true);
} else if (get_spec_reg(regname, &arg, &allocated, true)) {
@@ -1524,15 +1523,6 @@ static void stuffescaped(const char *arg, bool literally)
}
}
-/// If "regname" is a special register, return true and store a pointer to its
-/// value in "argp".
-///
-/// @param allocated return: true when value was allocated
-/// @param errmsg give error message when failing
-///
-/// @return true if "regname" is a special register,
-bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg);
-
/// Converts a yankreg to a dict which can be used as an argument to the
// userregfunc.
static dict_T* yankreg_to_dict(yankreg_T* yankreg) {
@@ -1606,14 +1596,14 @@ static int eval_yank_userreg(const char_u *ufn, int regname, yankreg_T *reg)
return ret;
}
-// If "regname" is a special register, return true and store a pointer to its
-// value in "argp".
-bool get_spec_reg(
- int regname,
- char_u **argp,
- bool *allocated, // return: true when value was allocated
- bool errmsg // give error message when failing
-)
+/// If "regname" is a special register, return true and store a pointer to its
+/// value in "argp".
+///
+/// @param allocated return: true when value was allocated
+/// @param errmsg give error message when failing
+///
+/// @return true if "regname" is a special register,
+bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
{
size_t cnt;
@@ -1624,15 +1614,15 @@ bool get_spec_reg(
if (errmsg) {
check_fname(); // will give emsg if not set
}
- *argp = (char_u *)curbuf->b_fname;
+ *argp = curbuf->b_fname;
return true;
case '#': // alternate file name
- *argp = (char_u *)getaltfname(errmsg); // may give emsg if not set
+ *argp = getaltfname(errmsg); // may give emsg if not set
return true;
case '=': // result of expression
- *argp = get_expr_line();
+ *argp = (char *)get_expr_line();
*allocated = true;
return true;
@@ -1640,18 +1630,18 @@ bool get_spec_reg(
if (last_cmdline == NULL && errmsg) {
emsg(_(e_nolastcmd));
}
- *argp = last_cmdline;
+ *argp = (char *)last_cmdline;
return true;
case '/': // last search-pattern
if (last_search_pat() == NULL && errmsg) {
emsg(_(e_noprevre));
}
- *argp = last_search_pat();
+ *argp = (char *)last_search_pat();
return true;
case '.': // last inserted text
- *argp = get_last_insert_save();
+ *argp = (char *)get_last_insert_save();
*allocated = true;
if (*argp == NULL && errmsg) {
emsg(_(e_noinstext));
@@ -1663,8 +1653,9 @@ bool get_spec_reg(
if (!errmsg) {
return false;
}
- *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0),
- 1L, NULL);
+ *argp
+ = (char *)file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0),
+ 1L, NULL);
*allocated = true;
return true;
@@ -1676,7 +1667,7 @@ bool get_spec_reg(
cnt = find_ident_under_cursor(argp, (regname == Ctrl_W
? (FIND_IDENT|FIND_STRING)
: FIND_STRING));
- *argp = cnt ? vim_strnsave(*argp, cnt) : NULL;
+ *argp = cnt ? xstrnsave(*argp, cnt) : NULL;
*allocated = true;
return true;
@@ -1685,11 +1676,11 @@ bool get_spec_reg(
return false;
}
- *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false);
+ *argp = (char *)ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false);
return true;
case '_': // black hole: always empty
- *argp = (char_u *)"";
+ *argp = "";
return true;
default: /* User-defined registers. */
@@ -2202,8 +2193,8 @@ static int op_replace(oparg_T *oap, int c)
// times.
if (utf_char2cells(c) > 1) {
if ((numc & 1) && !bd.is_short) {
- ++bd.endspaces;
- ++n;
+ bd.endspaces++;
+ n++;
}
numc = numc / 2;
}
@@ -3259,7 +3250,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
int delcount;
int incr = 0;
struct block_def bd;
- char_u **y_array = NULL;
+ char **y_array = NULL;
linenr_T nr_lines = 0;
pos_T new_cursor;
int indent;
@@ -3268,7 +3259,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
bool first_indent = true;
int lendiff = 0;
pos_T old_pos;
- char_u *insert_string = NULL;
+ char *insert_string = NULL;
bool allocated = false;
long cnt;
const pos_T orig_start = curbuf->b_op_start;
@@ -3391,10 +3382,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
* Loop twice: count the number of lines and save them. */
for (;;) {
y_size = 0;
- ptr = insert_string;
+ ptr = (char_u *)insert_string;
while (ptr != NULL) {
if (y_array != NULL) {
- y_array[y_size] = ptr;
+ y_array[y_size] = (char *)ptr;
}
y_size++;
ptr = (char_u *)vim_strchr((char *)ptr, '\n');
@@ -3402,7 +3393,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (y_array != NULL) {
*ptr = NUL;
}
- ++ptr;
+ ptr++;
// A trailing '\n' makes the register linewise.
if (*ptr == NUL) {
y_type = kMTLineWise;
@@ -3413,7 +3404,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (y_array != NULL) {
break;
}
- y_array = (char_u **)xmalloc(y_size * sizeof(char_u *));
+ y_array = xmalloc(y_size * sizeof(char_u *));
}
} else {
y_size = 1; // use fake one-line yank register
@@ -3434,7 +3425,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
y_type = reg->y_type;
y_width = reg->y_width;
y_size = reg->y_size;
- y_array = (char_u **)reg->y_array;
+ y_array = reg->y_array;
}
if (curbuf->terminal) {
@@ -3643,7 +3634,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// block
spaces = y_width + 1;
for (int j = 0; j < yanklen; j++) {
- spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0);
+ spaces -= lbr_chartabsize(NULL, (char_u *)(&y_array[i][j]), 0);
}
if (spaces < 0) {
spaces = 0;
@@ -3744,12 +3735,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
curbuf->b_op_start = curwin->w_cursor;
- }
- /*
- * Line mode: BACKWARD is the same as FORWARD on the previous line
- */
- else if (dir == BACKWARD) {
- --lnum;
+ } else if (dir == BACKWARD) {
+ // Line mode: BACKWARD is the same as FORWARD on the previous line
+ lnum--;
}
new_cursor = curwin->w_cursor;
@@ -3882,13 +3870,13 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
for (; i < y_size; i++) {
if ((y_type != kMTCharWise || i < y_size - 1)) {
- if (ml_append(lnum, (char *)y_array[i], (colnr_T)0, false) == FAIL) {
+ if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) {
goto error;
}
new_lnum++;
}
lnum++;
- ++nr_lines;
+ nr_lines++;
if (flags & PUT_FIXINDENT) {
old_pos = curwin->w_cursor;
curwin->w_cursor.lnum = lnum;
@@ -3972,8 +3960,8 @@ error:
if (col > 1) {
curbuf->b_op_end.col = col - 1;
if (len > 0) {
- curbuf->b_op_end.col -= utf_head_off(y_array[y_size - 1],
- y_array[y_size - 1] + len - 1);
+ curbuf->b_op_end.col -= utf_head_off((char_u *)y_array[y_size - 1],
+ (char_u *)y_array[y_size - 1] + len - 1);
}
} else {
curbuf->b_op_end.col = 0;
@@ -4264,9 +4252,9 @@ static void dis_msg(const char_u *p, bool skip_esc)
/// comment.
char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_comment)
{
- char_u *comment_flags = NULL;
+ char *comment_flags = NULL;
int lead_len;
- int leader_offset = get_last_leader_offset((char *)line, (char **)&comment_flags);
+ int leader_offset = get_last_leader_offset((char *)line, &comment_flags);
*is_comment = false;
if (leader_offset != -1) {
@@ -4288,7 +4276,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co
return line;
}
- lead_len = get_leader_len((char *)line, (char **)&comment_flags, false, include_space);
+ lead_len = get_leader_len((char *)line, &comment_flags, false, include_space);
if (lead_len == 0) {
return line;
@@ -4303,7 +4291,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co
|| *comment_flags == ':') {
break;
}
- ++comment_flags;
+ comment_flags++;
}
// If we found a colon, it means that we are not processing a line
@@ -4587,7 +4575,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in
}
} else {
while (ascii_iswhite(line1[idx1])) {
- ++idx1;
+ idx1++;
}
}
}
@@ -5000,7 +4988,7 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags,
*/
flags = *leader_flags;
while (*flags && *flags != ':' && *flags != COM_END) {
- ++flags;
+ flags++;
}
}
@@ -5759,16 +5747,16 @@ void *get_reg_contents(int regname, int flags)
return NULL;
}
- char_u *retval;
+ char *retval;
bool allocated;
if (get_spec_reg(regname, &retval, &allocated, false)) {
if (retval == NULL) {
return NULL;
}
if (allocated) {
- return get_reg_wrap_one_line(retval, flags);
+ return get_reg_wrap_one_line((char_u *)retval, flags);
}
- return get_reg_wrap_one_line(vim_strsave(retval), flags);
+ return get_reg_wrap_one_line(vim_strsave((char_u *)retval), flags);
}
yankreg_T *reg = get_yank_register(regname, YREG_PASTE);
@@ -5863,11 +5851,11 @@ void write_reg_contents(int name, const char_u *str, ssize_t len, int must_appen
write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0L);
}
-void write_reg_contents_lst(int name, char_u **strings, bool must_append, MotionType yank_type,
+void write_reg_contents_lst(int name, char **strings, bool must_append, MotionType yank_type,
colnr_T block_len)
{
if (name == '/' || name == '=') {
- char_u *s = strings[0];
+ char_u *s = (char_u *)strings[0];
if (strings[0] == NULL) {
s = (char_u *)"";
} else if (strings[1] != NULL) {
@@ -5889,7 +5877,7 @@ void write_reg_contents_lst(int name, char_u **strings, bool must_append, Motion
return;
}
- str_to_reg(reg, yank_type, (char_u *)strings, STRLEN((char_u *)strings),
+ str_to_reg(reg, yank_type, (char *)strings, STRLEN((char_u *)strings),
block_len, true);
finish_write_reg(name, reg, old_y_previous);
}
@@ -5977,7 +5965,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a
if (!(reg = init_write_reg(name, &old_y_previous, must_append))) {
return;
}
- str_to_reg(reg, yank_type, str, (size_t)len, block_len, false);
+ str_to_reg(reg, yank_type, (char *)str, (size_t)len, block_len, false);
finish_write_reg(name, reg, old_y_previous);
}
@@ -5991,7 +5979,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a
/// @param len length of the string (Ignored when str_list=true.)
/// @param blocklen width of visual block, or -1 for "I don't know."
/// @param str_list True if str is `char_u **`.
-static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str, size_t len,
+static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, size_t len,
colnr_T blocklen, bool str_list)
FUNC_ATTR_NONNULL_ALL
{
@@ -6033,9 +6021,8 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str
}
// Grow the register array to hold the pointers to the new lines.
- char_u **pp = xrealloc(y_ptr->y_array,
- (y_ptr->y_size + newlines) * sizeof(char_u *));
- y_ptr->y_array = (char **)pp;
+ char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char_u *));
+ y_ptr->y_array = pp;
size_t lnum = y_ptr->y_size; // The current line number.
@@ -6053,7 +6040,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str
}
} else {
size_t line_len;
- for (const char_u *start = str, *end = str + len;
+ for (const char_u *start = (char_u *)str, *end = (char_u *)str + len;
start < end + extraline;
start += line_len + 1, lnum++) {
assert(end - start >= 0);
@@ -6076,7 +6063,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str
xfree(pp[lnum]);
append = false; // only first line is appended
}
- pp[lnum] = (char_u *)s;
+ pp[lnum] = s;
// Convert NULs to '\n' to prevent truncation.
memchrsub(pp[lnum], NUL, '\n', s_len);
@@ -6095,7 +6082,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str
void clear_oparg(oparg_T *oap)
{
- memset(oap, 0, sizeof(oparg_T));
+ CLEAR_POINTER(oap);
}
/// Count the number of bytes, characters and "words" in a line.
diff --git a/src/nvim/option.c b/src/nvim/option.c
index ba77d12a3d..9fe007a453 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -27,13 +27,16 @@
#include <stdlib.h>
#include <string.h>
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
+#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
@@ -49,6 +52,7 @@
#include "nvim/hardcopy.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
+#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
@@ -66,12 +70,13 @@
#include "nvim/os/os.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
+#include "nvim/popupmenu.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
+#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -82,7 +87,9 @@
#ifdef WIN32
# include "nvim/os/pty_conpty_win.h"
#endif
+#include "nvim/api/extmark.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
#include "nvim/lua/executor.h"
#include "nvim/os/input.h"
#include "nvim/os/lang.h"
@@ -978,7 +985,7 @@ int do_set(char *arg, int opt_flags)
varnumber_T value;
int key;
uint32_t flags; // flags for current option
- char_u *varp = NULL; // pointer to variable for current option
+ char *varp = NULL; // pointer to variable for current option
int did_show = false; // already showed one value
int adding; // "opt+=arg"
int prepending; // "opt^=arg"
@@ -1106,7 +1113,7 @@ int do_set(char *arg, int opt_flags)
}
flags = options[opt_idx].flags;
- varp = get_varp_scope(&(options[opt_idx]), opt_flags);
+ varp = (char *)get_varp_scope(&(options[opt_idx]), opt_flags);
} else {
flags = P_STRING;
}
@@ -1188,7 +1195,7 @@ int do_set(char *arg, int opt_flags)
showoneopt(&options[opt_idx], opt_flags);
if (p_verbose > 0) {
// Mention where the option was last set.
- if (varp == options[opt_idx].var) {
+ if (varp == (char *)options[opt_idx].var) {
option_last_set_msg(options[opt_idx].last_set);
} else if ((int)options[opt_idx].indir & PV_WIN) {
option_last_set_msg(curwin->w_p_script_ctx[
@@ -1250,8 +1257,7 @@ int do_set(char *arg, int opt_flags)
}
}
- errmsg = set_bool_option(opt_idx, varp, (int)value,
- opt_flags);
+ errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags);
} else { // Numeric or string.
if (vim_strchr("=:&<", nextchar) == NULL
|| prefix != 1) {
@@ -1310,7 +1316,7 @@ int do_set(char *arg, int opt_flags)
if (removing) {
value = *(long *)varp - value;
}
- errmsg = set_num_option(opt_idx, varp, (long)value,
+ errmsg = set_num_option(opt_idx, (char_u *)varp, (long)value,
errbuf, sizeof(errbuf),
opt_flags);
} else if (opt_idx >= 0) { // String.
@@ -1333,7 +1339,7 @@ int do_set(char *arg, int opt_flags)
// reset, use the global value here.
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
&& ((int)options[opt_idx].indir & PV_BOTH)) {
- varp = options[opt_idx].var;
+ varp = (char *)options[opt_idx].var;
}
// The old value is kept until we are sure that the
@@ -1382,23 +1388,16 @@ int do_set(char *arg, int opt_flags)
} else {
arg++; // jump to after the '=' or ':'
- /*
- * Set 'keywordprg' to ":help" if an empty
- * value was passed to :set by the user.
- * Misuse errbuf[] for the resulting string.
- */
- if (varp == (char_u *)&p_kp
- && (*arg == NUL || *arg == ' ')) {
+ // Set 'keywordprg' to ":help" if an empty
+ // value was passed to :set by the user.
+ // Misuse errbuf[] for the resulting string.
+ if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) {
STRCPY(errbuf, ":help");
save_arg = (char_u *)arg;
arg = errbuf;
- }
- /*
- * Convert 'backspace' number to string, for
- * adding, prepending and removing string.
- */
- else if (varp == (char_u *)&p_bs
- && ascii_isdigit(**(char_u **)varp)) {
+ } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) {
+ // Convert 'backspace' number to string, for
+ // adding, prepending and removing string.
i = getdigits_int((char **)varp, true, 0);
switch (i) {
case 0:
@@ -1425,14 +1424,10 @@ int do_set(char *arg, int opt_flags)
origval_g = *(char_u **)varp;
}
oldval = *(char_u **)varp;
- }
- /*
- * Convert 'whichwrap' number to string, for
- * backwards compatibility with Vim 3.0.
- * Misuse errbuf[] for the resulting string.
- */
- else if (varp == (char_u *)&p_ww
- && ascii_isdigit(*arg)) {
+ } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) {
+ // Convert 'whichwrap' number to string, for
+ // backwards compatibility with Vim 3.0.
+ // Misuse errbuf[] for the resulting string.
*errbuf = NUL;
i = getdigits_int(&arg, true, 0);
if (i & 1) {
@@ -1452,14 +1447,11 @@ int do_set(char *arg, int opt_flags)
}
save_arg = (char_u *)arg;
arg = errbuf;
- }
- /*
- * Remove '>' before 'dir' and 'bdir', for
- * backwards compatibility with version 3.0
- */
- else if (*arg == '>'
- && (varp == (char_u *)&p_dir
- || varp == (char_u *)&p_bdir)) {
+ } else if (*arg == '>'
+ && (varp == (char *)&p_dir
+ || varp == (char *)&p_bdir)) {
+ // Remove '>' before 'dir' and 'bdir', for
+ // backwards compatibility with version 3.0
arg++;
}
@@ -1993,7 +1985,7 @@ static void didset_options(void)
(void)check_cedit();
// initialize the table for 'breakat'.
fill_breakat_flags();
- didset_window_options(curwin);
+ didset_window_options(curwin, true);
}
// More side effects of setting options.
@@ -2070,6 +2062,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_cpt);
check_string_option(&buf->b_p_cfu);
check_string_option(&buf->b_p_ofu);
+ check_string_option(&buf->b_p_urf);
check_string_option(&buf->b_p_keymap);
check_string_option(&buf->b_p_gp);
check_string_option(&buf->b_p_mp);
@@ -2324,7 +2317,7 @@ static char *set_string_option(const int opt_idx, const char *const value, const
/// Return true if "val" is a valid name: only consists of alphanumeric ASCII
/// characters or characters in "allowed".
-static bool valid_name(const char_u *val, const char *allowed)
+bool valid_name(const char_u *val, const char *allowed)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
for (const char_u *s = val; *s != NUL; s++) {
@@ -2344,25 +2337,6 @@ static bool valid_filetype(const char_u *val)
return valid_name(val, ".-_");
}
-/// Return true if "val" is a valid 'spelllang' value.
-bool valid_spelllang(const char_u *val)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return valid_name(val, ".-_,@");
-}
-
-/// Return true if "val" is a valid 'spellfile' value.
-static bool valid_spellfile(const char_u *val)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- for (const char_u *s = val; *s != NUL; s++) {
- if (!vim_isfilec(*s) && *s != ',' && *s != ' ') {
- return false;
- }
- }
- return true;
-}
-
/// Handle setting 'mousescroll'.
/// @return error message, NULL if it's OK.
static char *check_mousescroll(char *string)
@@ -2441,7 +2415,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c
size_t errbuflen, int opt_flags, int *value_checked)
{
char *errmsg = NULL;
- char_u *s, *p;
+ char *s, *p;
int did_chartab = false;
char_u **gvarp;
bool free_oldval = (options[opt_idx].flags & P_ALLOCED);
@@ -2530,7 +2504,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c
errmsg = check_colorcolumn(curwin);
} else if (varp == &p_hlg) { // 'helplang'
// Check for "", "ab", "ab,cd", etc.
- for (s = p_hlg; *s != NUL; s += 3) {
+ for (s = (char *)p_hlg; *s != NUL; s += 3) {
if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) {
errmsg = e_invarg;
break;
@@ -2578,18 +2552,7 @@ static char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, c
if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
errmsg = e_invarg;
} else {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
- errmsg = _("E834: Conflicts with value of 'listchars'");
- goto ambw_end;
- }
- if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
- errmsg = _("E835: Conflicts with value of 'fillchars'");
- goto ambw_end;
- }
- }
-ambw_end:
- {} // clint prefers {} over ; as an empty statement
+ errmsg = check_chars_options();
}
} else if (varp == &p_bg) { // 'background'
if (check_opt_strings(p_bg, p_bg_values, false) == OK) {
@@ -2647,9 +2610,9 @@ ambw_end:
if (errmsg == NULL) {
// canonize the value, so that STRCMP() can be used on it
- p = enc_canonize(*varp);
+ p = (char *)enc_canonize(*varp);
xfree(*varp);
- *varp = p;
+ *varp = (char_u *)p;
if (varp == &p_enc) {
// only encoding=utf-8 allowed
if (STRCMP(p_enc, "utf-8") != 0) {
@@ -2661,9 +2624,9 @@ ambw_end:
}
} else if (varp == &p_penc) {
// Canonize printencoding if VIM standard one
- p = enc_canonize(p_penc);
+ p = (char *)enc_canonize(p_penc);
xfree(p_penc);
- p_penc = p;
+ p_penc = (char_u *)p;
} else if (varp == &curbuf->b_p_keymap) {
if (!valid_filetype(*varp)) {
errmsg = e_invarg;
@@ -2726,17 +2689,17 @@ ambw_end:
errmsg = e_invarg;
}
} else if (gvarp == &p_mps) { // 'matchpairs'
- for (p = *varp; *p != NUL; p++) {
+ for (p = (char *)(*varp); *p != NUL; p++) {
int x2 = -1;
int x3 = -1;
- p += utfc_ptr2len((char *)p);
+ p += utfc_ptr2len(p);
if (*p != NUL) {
- x2 = *p++;
+ x2 = (unsigned char)(*p++);
}
if (*p != NUL) {
- x3 = utf_ptr2char((char *)p);
- p += utfc_ptr2len((char *)p);
+ x3 = utf_ptr2char(p);
+ p += utfc_ptr2len(p);
}
if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) {
errmsg = e_invarg;
@@ -2747,7 +2710,7 @@ ambw_end:
}
}
} else if (gvarp == &p_com) { // 'comments'
- for (s = *varp; *s;) {
+ for (s = (char *)(*varp); *s;) {
while (*s && *s != ':') {
if (vim_strchr(COM_ALL, *s) == NULL
&& !ascii_isdigit(*s) && *s != '-') {
@@ -2770,7 +2733,7 @@ ambw_end:
}
s++;
}
- s = skip_to_option_part(s);
+ s = (char *)skip_to_option_part((char_u *)s);
}
} else if (varp == &p_lcs) { // global 'listchars'
errmsg = set_chars_option(curwin, varp, false);
@@ -2826,7 +2789,7 @@ ambw_end:
// there would be a disconnect between the check for P_ALLOCED at the start
// of the function and the set of P_ALLOCED at the end of the function.
free_oldval = (options[opt_idx].flags & P_ALLOCED);
- for (s = p_shada; *s;) {
+ for (s = (char *)p_shada; *s;) {
// Check it's a valid character
if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) {
errmsg = illegal_char(errbuf, errbuflen, *s);
@@ -2871,8 +2834,8 @@ ambw_end:
errmsg = N_("E528: Must specify a ' value");
}
} else if (gvarp == &p_sbr) { // 'showbreak'
- for (s = *varp; *s;) {
- if (ptr2cells((char *)s) != 1) {
+ for (s = (char *)(*varp); *s;) {
+ if (ptr2cells(s) != 1) {
errmsg = N_("E595: 'showbreak' contains unprintable or wide character");
}
MB_PTR_ADV(s);
@@ -2996,13 +2959,13 @@ ambw_end:
if (varp == &p_ruf) { // reset ru_wid first
ru_wid = 0;
}
- s = *varp;
+ s = (char *)(*varp);
if (varp == &p_ruf && *s == '%') {
// set ru_wid if 'ruf' starts with "%99("
if (*++s == '-') { // ignore a '-'
s++;
}
- wid = getdigits_int((char **)&s, true, 0);
+ wid = getdigits_int(&s, true, 0);
if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) {
ru_wid = wid;
} else {
@@ -3010,7 +2973,7 @@ ambw_end:
}
} else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') {
// check 'statusline', 'winbar' or 'tabline' only if it doesn't start with "%!"
- errmsg = check_stl_option((char *)s);
+ errmsg = check_stl_option(s);
}
if (varp == &p_ruf && errmsg == NULL) {
comp_col();
@@ -3021,7 +2984,7 @@ ambw_end:
}
} else if (gvarp == &p_cpt) {
// check if it is a valid value for 'complete' -- Acevedo
- for (s = *varp; *s;) {
+ for (s = (char *)(*varp); *s;) {
while (*s == ',' || *s == ' ') {
s++;
}
@@ -3090,11 +3053,11 @@ ambw_end:
p = NULL;
(void)replace_termcodes((char *)p_pt,
STRLEN(p_pt),
- (char **)&p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
+ &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
CPO_TO_CPO_FLAGS);
if (p != NULL) {
free_string_option(p_pt);
- p_pt = p;
+ p_pt = (char_u *)p;
}
}
} else if (varp == &p_bs) { // 'backspace'
@@ -3113,10 +3076,10 @@ ambw_end:
unsigned int *flags;
if (opt_flags & OPT_LOCAL) {
- p = curbuf->b_p_tc;
+ p = (char *)curbuf->b_p_tc;
flags = &curbuf->b_tc_flags;
} else {
- p = p_tc;
+ p = (char *)p_tc;
flags = &tc_flags;
}
@@ -3124,7 +3087,7 @@ ambw_end:
// make the local value empty: use the global value
*flags = 0;
} else if (*p == NUL
- || opt_strings_flags(p, p_tc_values, flags, false) != OK) {
+ || opt_strings_flags((char_u *)p, p_tc_values, flags, false) != OK) {
errmsg = e_invarg;
}
} else if (varp == &p_cmp) { // 'casemap'
@@ -3150,10 +3113,10 @@ ambw_end:
foldUpdateAll(curwin);
}
} else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker'
- p = (char_u *)vim_strchr((char *)(*varp), ',');
+ p = vim_strchr((char *)(*varp), ',');
if (p == NULL) {
errmsg = N_("E536: comma required");
- } else if (p == *varp || p[1] == NUL) {
+ } else if ((char_u *)p == *varp || p[1] == NUL) {
errmsg = e_invarg;
} else if (foldmethodIsMarker(curwin)) {
foldUpdateAll(curwin);
@@ -3198,7 +3161,7 @@ ambw_end:
}
} else if (varp == &p_csqf) {
if (p_csqf != NULL) {
- p = p_csqf;
+ p = (char *)p_csqf;
while (*p != NUL) {
if (vim_strchr(CSQF_CMDS, *p) == NULL
|| p[1] == NUL
@@ -3310,22 +3273,22 @@ ambw_end:
// Options that are a list of flags.
p = NULL;
if (varp == &p_ww) { // 'whichwrap'
- p = (char_u *)WW_ALL;
+ p = WW_ALL;
}
if (varp == &p_shm) { // 'shortmess'
- p = (char_u *)SHM_ALL;
+ p = (char *)SHM_ALL;
} else if (varp == (char_u **)&(p_cpo)) { // 'cpoptions'
- p = (char_u *)CPO_VI;
+ p = CPO_VI;
} else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions'
- p = (char_u *)FO_ALL;
+ p = FO_ALL;
} else if (varp == &curwin->w_p_cocu) { // 'concealcursor'
- p = (char_u *)COCU_ALL;
+ p = COCU_ALL;
} else if (varp == &p_mouse) { // 'mouse'
- p = (char_u *)MOUSE_ALL;
+ p = MOUSE_ALL;
}
if (p != NULL) {
- for (s = *varp; *s; s++) {
- if (vim_strchr((char *)p, *s) == NULL) {
+ for (s = (char *)(*varp); *s; s++) {
+ if (vim_strchr(p, *s) == NULL) {
errmsg = illegal_char(errbuf, errbuflen, *s);
break;
}
@@ -3360,7 +3323,7 @@ ambw_end:
&& ((int)options[opt_idx].indir & PV_BOTH)) {
/* global option with local value set to use global value; free
* the local value and make it empty */
- p = get_varp_scope(&(options[opt_idx]), OPT_LOCAL);
+ p = (char *)get_varp_scope(&(options[opt_idx]), OPT_LOCAL);
free_string_option(*(char_u **)p);
*(char_u **)p = empty_option;
} else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) {
@@ -3423,14 +3386,14 @@ ambw_end:
* Use the first name in 'spelllang' up to '_region' or
* '.encoding'.
*/
- for (p = q; *p != NUL; p++) {
+ for (p = (char *)q; *p != NUL; p++) {
if (!ASCII_ISALNUM(*p) && *p != '-') {
break;
}
}
- if (p > q) {
+ if (p > (char *)q) {
vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim",
- (int)(p - q), q);
+ (int)(p - (char *)q), q);
source_runtime((char *)fname, DIP_ALL);
}
}
@@ -3450,12 +3413,6 @@ ambw_end:
return errmsg;
}
-/// Simple int comparison function for use with qsort()
-static int int_cmp(const void *a, const void *b)
-{
- return *(const int *)a - *(const int *)b;
-}
-
/// Handle setting 'signcolumn' for value 'val'
///
/// @return OK when the value is valid, FAIL otherwise
@@ -3486,356 +3443,12 @@ int check_signcolumn(char_u *val)
return FAIL;
}
-/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
-///
-/// @return error message, NULL if it's OK.
-char *check_colorcolumn(win_T *wp)
-{
- char_u *s;
- int col;
- unsigned int count = 0;
- int color_cols[256];
- int j = 0;
-
- if (wp->w_buffer == NULL) {
- return NULL; // buffer was closed
- }
-
- for (s = wp->w_p_cc; *s != NUL && count < 255;) {
- if (*s == '-' || *s == '+') {
- // -N and +N: add to 'textwidth'
- col = (*s == '-') ? -1 : 1;
- s++;
- if (!ascii_isdigit(*s)) {
- return e_invarg;
- }
- col = col * getdigits_int((char **)&s, true, 0);
- if (wp->w_buffer->b_p_tw == 0) {
- goto skip; // 'textwidth' not set, skip this item
- }
- assert((col >= 0
- && wp->w_buffer->b_p_tw <= INT_MAX - col
- && wp->w_buffer->b_p_tw + col >= INT_MIN)
- || (col < 0
- && wp->w_buffer->b_p_tw >= INT_MIN - col
- && wp->w_buffer->b_p_tw + col <= INT_MAX));
- col += (int)wp->w_buffer->b_p_tw;
- if (col < 0) {
- goto skip;
- }
- } else if (ascii_isdigit(*s)) {
- col = getdigits_int((char **)&s, true, 0);
- } else {
- return e_invarg;
- }
- color_cols[count++] = col - 1; // 1-based to 0-based
-skip:
- if (*s == NUL) {
- break;
- }
- if (*s != ',') {
- return e_invarg;
- }
- if (*++s == NUL) {
- return e_invarg; // illegal trailing comma as in "set cc=80,"
- }
- }
-
- xfree(wp->w_p_cc_cols);
- if (count == 0) {
- wp->w_p_cc_cols = NULL;
- } else {
- wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1));
- /* sort the columns for faster usage on screen redraw inside
- * win_line() */
- qsort(color_cols, count, sizeof(int), int_cmp);
-
- for (unsigned int i = 0; i < count; i++) {
- // skip duplicates
- if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) {
- wp->w_p_cc_cols[j++] = color_cols[i];
- }
- }
- wp->w_p_cc_cols[j] = -1; // end marker
- }
-
- return NULL; // no error
-}
-
void check_blending(win_T *wp)
{
wp->w_grid_alloc.blending =
wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow);
}
-/// Calls mb_cptr2char_adv(p) and returns the character.
-/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
-/// Returns 0 for invalid hex or invalid UTF-8 byte.
-static int get_encoded_char_adv(char_u **p)
-{
- char_u *s = *p;
-
- if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
- int64_t num = 0;
- int bytes;
- int n;
- for (bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
- *p += 2;
- n = hexhex2nr(*p);
- if (n < 0) {
- return 0;
- }
- num = num * 256 + n;
- }
- *p += 2;
- return (int)num;
- }
-
- // TODO(bfredl): use schar_T representation and utfc_ptr2len
- int clen = utf_ptr2len((char *)s);
- int c = mb_cptr2char_adv((const char_u **)p);
- if (clen == 1 && c > 127) { // Invalid UTF-8 byte
- return 0;
- }
- return c;
-}
-
-/// Handle setting 'listchars' or 'fillchars'.
-/// Assume monocell characters
-///
-/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs
-/// @return error message, NULL if it's OK.
-static char *set_chars_option(win_T *wp, char_u **varp, bool set)
-{
- int round, i, len, len2, entries;
- char_u *p, *s;
- int c1;
- int c2 = 0;
- int c3 = 0;
- char_u *last_multispace = NULL; // Last occurrence of "multispace:"
- char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
- int multispace_len = 0; // Length of lcs-multispace string
- int lead_multispace_len = 0; // Length of lcs-leadmultispace string
-
- struct chars_tab {
- int *cp; ///< char value
- char *name; ///< char id
- int def; ///< default value
- };
- struct chars_tab *tab;
-
- struct chars_tab fcs_tab[] = {
- { &wp->w_p_fcs_chars.colorcol, "colorcol", NUL },
- { &wp->w_p_fcs_chars.stl, "stl", ' ' },
- { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' },
- { &wp->w_p_fcs_chars.wbr, "wbr", ' ' },
- { &wp->w_p_fcs_chars.horiz, "horiz", 9472 }, // ─
- { &wp->w_p_fcs_chars.horizup, "horizup", 9524 }, // ┴
- { &wp->w_p_fcs_chars.horizdown, "horizdown", 9516 }, // ┬
- { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │
- { &wp->w_p_fcs_chars.vertleft, "vertleft", 9508 }, // ┤
- { &wp->w_p_fcs_chars.vertright, "vertright", 9500 }, // ├
- { &wp->w_p_fcs_chars.verthoriz, "verthoriz", 9532 }, // ┼
- { &wp->w_p_fcs_chars.fold, "fold", 183 }, // ·
- { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' },
- { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' },
- { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │
- { &wp->w_p_fcs_chars.diff, "diff", '-' },
- { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' },
- { &wp->w_p_fcs_chars.eob, "eob", '~' },
- };
- struct chars_tab lcs_tab[] = {
- { &wp->w_p_lcs_chars.eol, "eol", NUL },
- { &wp->w_p_lcs_chars.ext, "extends", NUL },
- { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL },
- { &wp->w_p_lcs_chars.prec, "precedes", NUL },
- { &wp->w_p_lcs_chars.space, "space", NUL },
- { &wp->w_p_lcs_chars.tab2, "tab", NUL },
- { &wp->w_p_lcs_chars.lead, "lead", NUL },
- { &wp->w_p_lcs_chars.trail, "trail", NUL },
- { &wp->w_p_lcs_chars.conceal, "conceal", NUL },
- };
-
- if (varp == &p_lcs || varp == &wp->w_p_lcs) {
- tab = lcs_tab;
- entries = ARRAY_SIZE(lcs_tab);
- if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
- varp = &p_lcs;
- }
- } else {
- tab = fcs_tab;
- entries = ARRAY_SIZE(fcs_tab);
- if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
- varp = &p_fcs;
- }
- if (*p_ambw == 'd') {
- // XXX: If ambiwidth=double then some characters take 2 columns,
- // which is forbidden (TUI limitation?). Set old defaults.
- fcs_tab[3].def = '-';
- fcs_tab[4].def = '-';
- fcs_tab[5].def = '-';
- fcs_tab[6].def = '|';
- fcs_tab[7].def = '|';
- fcs_tab[8].def = '|';
- fcs_tab[9].def = '+';
- fcs_tab[10].def = '-';
- fcs_tab[13].def = '|';
- }
- }
-
- // first round: check for valid value, second round: assign values
- for (round = 0; round <= (set ? 1 : 0); round++) {
- if (round > 0) {
- // After checking that the value is valid: set defaults
- for (i = 0; i < entries; i++) {
- if (tab[i].cp != NULL) {
- *(tab[i].cp) = tab[i].def;
- }
- }
- if (varp == &p_lcs || varp == &wp->w_p_lcs) {
- wp->w_p_lcs_chars.tab1 = NUL;
- wp->w_p_lcs_chars.tab3 = NUL;
-
- xfree(wp->w_p_lcs_chars.multispace);
- if (multispace_len > 0) {
- wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
- wp->w_p_lcs_chars.multispace[multispace_len] = NUL;
- } else {
- wp->w_p_lcs_chars.multispace = NULL;
- }
-
- xfree(wp->w_p_lcs_chars.leadmultispace);
- if (lead_multispace_len > 0) {
- wp->w_p_lcs_chars.leadmultispace
- = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
- wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL;
- } else {
- wp->w_p_lcs_chars.leadmultispace = NULL;
- }
- }
- }
- p = *varp;
- while (*p) {
- for (i = 0; i < entries; i++) {
- len = (int)STRLEN(tab[i].name);
- if (STRNCMP(p, tab[i].name, len) == 0
- && p[len] == ':'
- && p[len + 1] != NUL) {
- c2 = c3 = 0;
- s = p + len + 1;
- c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
- if (*s == NUL) {
- return e_invarg;
- }
- c2 = get_encoded_char_adv(&s);
- if (c2 == 0 || char2cells(c2) > 1) {
- return e_invarg;
- }
- if (!(*s == ',' || *s == NUL)) {
- c3 = get_encoded_char_adv(&s);
- if (c3 == 0 || char2cells(c3) > 1) {
- return e_invarg;
- }
- }
- }
- if (*s == ',' || *s == NUL) {
- if (round > 0) {
- if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
- wp->w_p_lcs_chars.tab1 = c1;
- wp->w_p_lcs_chars.tab2 = c2;
- wp->w_p_lcs_chars.tab3 = c3;
- } else if (tab[i].cp != NULL) {
- *(tab[i].cp) = c1;
- }
- }
- p = s;
- break;
- }
- }
- }
-
- if (i == entries) {
- len = (int)STRLEN("multispace");
- len2 = (int)STRLEN("leadmultispace");
- if ((varp == &p_lcs || varp == &wp->w_p_lcs)
- && STRNCMP(p, "multispace", len) == 0
- && p[len] == ':'
- && p[len + 1] != NUL) {
- s = p + len + 1;
- if (round == 0) {
- // Get length of lcs-multispace string in the first round
- last_multispace = p;
- multispace_len = 0;
- while (*s != NUL && *s != ',') {
- c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- multispace_len++;
- }
- if (multispace_len == 0) {
- // lcs-multispace cannot be an empty string
- return e_invarg;
- }
- p = s;
- } else {
- int multispace_pos = 0;
- while (*s != NUL && *s != ',') {
- c1 = get_encoded_char_adv(&s);
- if (p == last_multispace) {
- wp->w_p_lcs_chars.multispace[multispace_pos++] = c1;
- }
- }
- p = s;
- }
- } else if ((varp == &p_lcs || varp == &wp->w_p_lcs)
- && STRNCMP(p, "leadmultispace", len2) == 0
- && p[len2] == ':'
- && p[len2 + 1] != NUL) {
- s = p + len2 + 1;
- if (round == 0) {
- // get length of lcs-leadmultispace string in first round
- last_lmultispace = p;
- lead_multispace_len = 0;
- while (*s != NUL && *s != ',') {
- c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- lead_multispace_len++;
- }
- if (lead_multispace_len == 0) {
- // lcs-leadmultispace cannot be an empty string
- return e_invarg;
- }
- p = s;
- } else {
- int multispace_pos = 0;
- while (*s != NUL && *s != ',') {
- c1 = get_encoded_char_adv(&s);
- if (p == last_lmultispace) {
- wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1;
- }
- }
- p = s;
- }
- } else {
- return e_invarg;
- }
- }
- if (*p == ',') {
- p++;
- }
- }
- }
-
- return NULL; // no error
-}
-
/// Check validity of options with the 'statusline' format.
/// Return an untranslated error message or NULL.
char *check_stl_option(char *s)
@@ -3906,63 +3519,30 @@ char *check_stl_option(char *s)
return NULL;
}
-static char *did_set_spell_option(bool is_spellfile)
+/// Handle setting `winhighlight' in window "wp"
+bool parse_winhl_opt(win_T *wp)
{
- char *errmsg = NULL;
+ const char *p = (const char *)wp->w_p_winhl;
- if (is_spellfile) {
- int l = (int)STRLEN(curwin->w_s->b_p_spf);
- if (l > 0
- && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) {
- errmsg = e_invarg;
+ if (!*p) {
+ if (wp->w_ns_hl_winhl && wp->w_ns_hl == wp->w_ns_hl_winhl) {
+ wp->w_ns_hl = 0;
+ wp->w_hl_needs_update = true;
}
- }
- if (errmsg == NULL) {
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == curbuf && wp->w_p_spell) {
- errmsg = did_set_spelllang(wp);
- break;
- }
- }
+ return true;
}
- return errmsg;
-}
-
-/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
-/// Return error message when failed, NULL when OK.
-static char *compile_cap_prog(synblock_T *synblock)
- FUNC_ATTR_NONNULL_ALL
-{
- regprog_T *rp = synblock->b_cap_prog;
- char_u *re;
-
- if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) {
- synblock->b_cap_prog = NULL;
+ if (wp->w_ns_hl_winhl == 0) {
+ wp->w_ns_hl_winhl = (int)nvim_create_namespace(NULL_STRING);
} else {
- // Prepend a ^ so that we only match at one column
- re = concat_str((char_u *)"^", synblock->b_p_spc);
- synblock->b_cap_prog = vim_regcomp((char *)re, RE_MAGIC);
- xfree(re);
- if (synblock->b_cap_prog == NULL) {
- synblock->b_cap_prog = rp; // restore the previous program
- return e_invarg;
- }
+ // namespace already exist. invalidate existing items
+ DecorProvider *dp = get_decor_provider(wp->w_ns_hl_winhl, true);
+ dp->hl_valid++;
}
+ wp->w_ns_hl = wp->w_ns_hl_winhl;
+ int ns_hl = wp->w_ns_hl;
- vim_regfree(rp);
- return NULL;
-}
-
-/// Handle setting `winhighlight' in window "wp"
-static bool parse_winhl_opt(win_T *wp)
-{
- int w_hl_id_normal = 0;
- int w_hl_ids[HLF_COUNT] = { 0 };
- int hlf;
-
- const char *p = (const char *)wp->w_p_winhl;
while (*p) {
char *colon = strchr(p, ':');
if (!colon) {
@@ -3973,27 +3553,15 @@ static bool parse_winhl_opt(win_T *wp)
char *commap = xstrchrnul(hi, ',');
size_t len = (size_t)(commap - hi);
int hl_id = len ? syn_check_group(hi, len) : -1;
+ int hl_id_link = nlen ? syn_check_group(p, nlen) : 0;
- if (strncmp("Normal", p, nlen) == 0) {
- w_hl_id_normal = hl_id;
- } else {
- for (hlf = 0; hlf < HLF_COUNT; hlf++) {
- if (strlen(hlf_names[hlf]) == nlen
- && strncmp(hlf_names[hlf], p, nlen) == 0) {
- w_hl_ids[hlf] = hl_id;
- break;
- }
- }
- if (hlf == HLF_COUNT) {
- return false;
- }
- }
+ HlAttrs attrs = HLATTRS_INIT;
+ attrs.rgb_ae_attr |= HL_GLOBAL;
+ ns_hl_def(ns_hl, hl_id_link, attrs, hl_id, NULL);
p = *commap ? commap + 1 : "";
}
- wp->w_hl_id_normal = w_hl_id_normal;
- memcpy(wp->w_hl_ids, w_hl_ids, sizeof(w_hl_ids));
wp->w_hl_needs_update = true;
return true;
}
@@ -4009,7 +3577,7 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
.script_ctx = {
script_ctx.sc_sid,
script_ctx.sc_seq,
- script_ctx.sc_lnum + sourcing_lnum
+ script_ctx.sc_lnum + SOURCING_LNUM
},
current_channel_id
};
@@ -5667,7 +5235,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6
char_u *s;
char_u *buf = NULL;
char_u *part = NULL;
- char_u *p;
+ char *p;
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
@@ -5703,14 +5271,14 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6
if (put_eol(fd) == FAIL) {
goto fail;
}
- p = buf;
+ p = (char *)buf;
while (*p != NUL) {
// for each comma separated option part, append value to
// the option, :set rtp+=value
if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
goto fail;
}
- (void)copy_option_part((char **)&p, (char *)part, size, ",");
+ (void)copy_option_part(&p, (char *)part, size, ",");
if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) {
goto fail;
}
@@ -5771,47 +5339,6 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
return OK;
}
-/// Compute columns for ruler and shown command. 'sc_col' is also used to
-/// decide what the maximum length of a message on the status line can be.
-/// If there is a status line for the last window, 'sc_col' is independent
-/// of 'ru_col'.
-
-#define COL_RULER 17 // columns needed by standard ruler
-
-void comp_col(void)
-{
- int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW));
-
- sc_col = 0;
- ru_col = 0;
- if (p_ru) {
- ru_col = (ru_wid ? ru_wid : COL_RULER) + 1;
- // no last status line, adjust sc_col
- if (!last_has_status) {
- sc_col = ru_col;
- }
- }
- if (p_sc) {
- sc_col += SHOWCMD_COLS;
- if (!p_ru || last_has_status) { // no need for separating space
- sc_col++;
- }
- }
- assert(sc_col >= 0
- && INT_MIN + sc_col <= Columns);
- sc_col = Columns - sc_col;
- assert(ru_col >= 0
- && INT_MIN + ru_col <= Columns);
- ru_col = Columns - ru_col;
- if (sc_col <= 0) { // screen too narrow, will become a mess
- sc_col = 1;
- }
- if (ru_col <= 0) {
- ru_col = 1;
- }
- set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
-}
-
// Unset local option value, similar to ":set opt<".
void unset_global_local_option(char *name, void *from)
{
@@ -6157,6 +5684,7 @@ static char_u *get_varp(vimoption_T *p)
return (char_u *)&(curwin->w_p_cocu);
case PV_COLE:
return (char_u *)&(curwin->w_p_cole);
+
case PV_AI:
return (char_u *)&(curbuf->b_p_ai);
case PV_BIN:
@@ -6317,7 +5845,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
- didset_window_options(wp_to);
+ didset_window_options(wp_to, true);
}
/// Copy the options from one winopt_T to another.
@@ -6442,7 +5970,7 @@ void clear_winopt(winopt_T *wop)
clear_string_option((char_u **)&wop->wo_wbr);
}
-void didset_window_options(win_T *wp)
+void didset_window_options(win_T *wp, bool valid_cursor)
{
check_colorcolumn(wp);
briopt_check(wp);
@@ -6451,7 +5979,7 @@ void didset_window_options(win_T *wp)
set_chars_option(wp, &wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
check_blending(wp);
- set_winbar_win(wp, false);
+ set_winbar_win(wp, false, valid_cursor);
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
@@ -6514,7 +6042,7 @@ void buf_copy_options(buf_T *buf, int flags)
}
if (should_copy || (flags & BCO_ALWAYS)) {
- memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx));
+ CLEAR_FIELD(buf->b_p_script_ctx);
init_buf_opt_idx();
// Don't copy the options specific to a help buffer when
// BCO_NOHELP is given or the options were initialized already
@@ -7569,313 +7097,6 @@ int check_ff_value(char_u *p)
return check_opt_strings(p, p_ff_values, false);
}
-// Set the integer values corresponding to the string setting of 'vartabstop'.
-// "array" will be set, caller must free it if needed.
-// Return false for an error.
-bool tabstop_set(char_u *var, long **array)
-{
- long valcount = 1;
- int t;
- char_u *cp;
-
- if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) {
- *array = NULL;
- return true;
- }
-
- for (cp = var; *cp != NUL; cp++) {
- if (cp == var || cp[-1] == ',') {
- char_u *end;
-
- if (strtol((char *)cp, (char **)&end, 10) <= 0) {
- if (cp != end) {
- emsg(_(e_positive));
- } else {
- semsg(_(e_invarg2), cp);
- }
- return false;
- }
- }
-
- if (ascii_isdigit(*cp)) {
- continue;
- }
- if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) {
- valcount++;
- continue;
- }
- semsg(_(e_invarg2), var);
- return false;
- }
-
- *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long));
- (*array)[0] = valcount;
-
- t = 1;
- for (cp = var; *cp != NUL;) {
- int n = atoi((char *)cp);
-
- // Catch negative values, overflow and ridiculous big values.
- if (n <= 0 || n > TABSTOP_MAX) {
- semsg(_(e_invarg2), cp);
- XFREE_CLEAR(*array);
- return false;
- }
- (*array)[t++] = n;
- while (*cp != NUL && *cp != ',') {
- cp++;
- }
- if (*cp != NUL) {
- cp++;
- }
- }
-
- return true;
-}
-
-// Calculate the number of screen spaces a tab will occupy.
-// If "vts" is set then the tab widths are taken from that array,
-// otherwise the value of ts is used.
-int tabstop_padding(colnr_T col, long ts_arg, long *vts)
-{
- long ts = ts_arg == 0 ? 8 : ts_arg;
- colnr_T tabcol = 0;
- int t;
- long padding = 0;
-
- if (vts == NULL || vts[0] == 0) {
- return (int)(ts - (col % ts));
- }
-
- const long tabcount = vts[0];
-
- for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
- if (tabcol > col) {
- padding = tabcol - col;
- break;
- }
- }
- if (t > tabcount) {
- padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]);
- }
-
- return (int)padding;
-}
-
-// Find the size of the tab that covers a particular column.
-int tabstop_at(colnr_T col, long ts, long *vts)
-{
- colnr_T tabcol = 0;
- int t;
- long tab_size = 0;
-
- if (vts == NULL || vts[0] == 0) {
- return (int)ts;
- }
-
- const long tabcount = vts[0];
- for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
- if (tabcol > col) {
- tab_size = vts[t];
- break;
- }
- }
- if (t > tabcount) {
- tab_size = vts[tabcount];
- }
-
- return (int)tab_size;
-}
-
-// Find the column on which a tab starts.
-colnr_T tabstop_start(colnr_T col, long ts, long *vts)
-{
- colnr_T tabcol = 0;
- int t;
-
- if (vts == NULL || vts[0] == 0) {
- return (int)((col / ts) * ts);
- }
-
- const long tabcount = vts[0];
- for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
- if (tabcol > col) {
- return (int)(tabcol - vts[t]);
- }
- }
-
- const int excess = (int)(tabcol % vts[tabcount]);
- return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
-}
-
-// Find the number of tabs and spaces necessary to get from one column
-// to another.
-void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, int *ntabs,
- int *nspcs)
-{
- int spaces = end_col - start_col;
- colnr_T tabcol = 0;
- long padding = 0;
- int t;
- long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg;
-
- if (vts == NULL || vts[0] == 0) {
- int tabs = 0;
-
- const int initspc = (int)(ts - (start_col % ts));
- if (spaces >= initspc) {
- spaces -= initspc;
- tabs++;
- }
- tabs += (int)(spaces / ts);
- spaces -= (int)((spaces / ts) * ts);
-
- *ntabs = tabs;
- *nspcs = spaces;
- return;
- }
-
- // Find the padding needed to reach the next tabstop.
- const long tabcount = vts[0];
- for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
- if (tabcol > start_col) {
- padding = tabcol - start_col;
- break;
- }
- }
- if (t > tabcount) {
- padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]);
- }
-
- // If the space needed is less than the padding no tabs can be used.
- if (spaces < padding) {
- *ntabs = 0;
- *nspcs = spaces;
- return;
- }
-
- *ntabs = 1;
- spaces -= (int)padding;
-
- // At least one tab has been used. See if any more will fit.
- while (spaces != 0 && ++t <= tabcount) {
- padding = vts[t];
- if (spaces < padding) {
- *nspcs = spaces;
- return;
- }
- *ntabs += 1;
- spaces -= (int)padding;
- }
-
- *ntabs += spaces / (int)vts[tabcount];
- *nspcs = spaces % (int)vts[tabcount];
-}
-
-// See if two tabstop arrays contain the same values.
-bool tabstop_eq(long *ts1, long *ts2)
-{
- int t;
-
- if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) {
- return false;
- }
- if (ts1 == ts2) {
- return true;
- }
- if (ts1[0] != ts2[0]) {
- return false;
- }
-
- for (t = 1; t <= ts1[0]; t++) {
- if (ts1[t] != ts2[t]) {
- return false;
- }
- }
-
- return true;
-}
-
-// Copy a tabstop array, allocating space for the new array.
-int *tabstop_copy(long *oldts)
-{
- long *newts;
- int t;
-
- if (oldts == 0) {
- return 0;
- }
-
- newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long));
- for (t = 0; t <= oldts[0]; t++) {
- newts[t] = oldts[t];
- }
-
- return (int *)newts;
-}
-
-// Return a count of the number of tabstops.
-int tabstop_count(long *ts)
-{
- return ts != NULL ? (int)ts[0] : 0;
-}
-
-// Return the first tabstop, or 8 if there are no tabstops defined.
-int tabstop_first(long *ts)
-{
- return ts != NULL ? (int)ts[1] : 8;
-}
-
-/// Return the effective shiftwidth value for current buffer, using the
-/// 'tabstop' value when 'shiftwidth' is zero.
-int get_sw_value(buf_T *buf)
-{
- long result = get_sw_value_col(buf, 0);
- assert(result >= 0 && result <= INT_MAX);
- return (int)result;
-}
-
-// Idem, using the first non-black in the current line.
-long get_sw_value_indent(buf_T *buf)
-{
- pos_T pos = curwin->w_cursor;
-
- pos.col = (colnr_T)getwhitecols_curline();
- return get_sw_value_pos(buf, &pos);
-}
-
-// Idem, using "pos".
-long get_sw_value_pos(buf_T *buf, pos_T *pos)
-{
- pos_T save_cursor = curwin->w_cursor;
- long sw_value;
-
- curwin->w_cursor = *pos;
- sw_value = get_sw_value_col(buf, get_nolist_virtcol());
- curwin->w_cursor = save_cursor;
- return sw_value;
-}
-
-// Idem, using virtual column "col".
-long get_sw_value_col(buf_T *buf, colnr_T col)
-{
- return buf->b_p_sw ? buf->b_p_sw
- : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
-}
-
-/// Return the effective softtabstop value for the current buffer,
-/// using the shiftwidth value when 'softtabstop' is negative.
-int get_sts_value(void)
-{
- long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
- assert(result >= 0 && result <= INT_MAX);
- return (int)result;
-}
-
/// This is called when 'breakindentopt' is changed and when a window is
/// initialized
static bool briopt_check(win_T *wp)
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index eaa56ffe63..98ef4bd0f6 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -10,7 +10,6 @@
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/ex_getln.h"
-#include "nvim/fileio.h"
#include "nvim/macros.h"
#include "nvim/map.h"
#include "nvim/memory.h"
@@ -580,18 +579,18 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo
int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix);
- char_u *src = (char_u *)skipwhite((char *)srcp);
+ char *src = skipwhite((char *)srcp);
dstlen--; // leave one char space for "\,"
while (*src && dstlen > 0) {
// Skip over `=expr`.
if (src[0] == '`' && src[1] == '=') {
- var = src;
+ var = (char_u *)src;
src += 2;
- (void)skip_expr((char **)&src);
+ (void)skip_expr(&src);
if (*src == '`') {
src++;
}
- size_t len = (size_t)(src - var);
+ size_t len = (size_t)(src - (char *)var);
if (len > (size_t)dstlen) {
len = (size_t)dstlen;
}
@@ -608,7 +607,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo
// The variable name is copied into dst temporarily, because it may
// be a string in read-only memory and a NUL needs to be appended.
if (*src != '~') { // environment var
- tail = src + 1;
+ tail = (char_u *)src + 1;
var = dst;
int c = dstlen - 1;
@@ -646,11 +645,11 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo
|| vim_ispathsep(src[1])
|| vim_strchr(" ,\t\n", src[1]) != NULL) {
var = (char_u *)homedir;
- tail = src + 1;
+ tail = (char_u *)src + 1;
} else { // user directory
#if defined(UNIX)
// Copy ~user to dst[], so we can put a NUL after it.
- tail = src;
+ tail = (char_u *)src;
var = dst;
int c = dstlen - 1;
while (c-- > 0
@@ -723,7 +722,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo
tail++;
}
dst += c;
- src = tail;
+ src = (char *)tail;
copy_char = false;
}
if (mustfree) {
@@ -737,17 +736,17 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo
// ":edit foo ~ foo".
at_start = false;
if (src[0] == '\\' && src[1] != NUL) {
- *dst++ = *src++;
+ *dst++ = (char_u)(*src++);
dstlen--;
} else if ((src[0] == ' ' || src[0] == ',') && !one) {
at_start = true;
}
if (dstlen > 0) {
- *dst++ = *src++;
+ *dst++ = (char_u)(*src++);
dstlen--;
if (prefix != NULL
- && src - prefix_len >= srcp
+ && src - prefix_len >= (char *)srcp
&& STRNCMP(src - prefix_len, prefix, prefix_len) == 0) {
at_start = true;
}
@@ -1069,9 +1068,8 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
must_free = true;
size_t usedlen = 0;
size_t flen = strlen(homedir_env_mod);
- char_u *fbuf = NULL;
- (void)modify_fname(":p", false, &usedlen,
- &homedir_env_mod, (char **)&fbuf, &flen);
+ char *fbuf = NULL;
+ (void)modify_fname(":p", false, &usedlen, &homedir_env_mod, &fbuf, &flen);
flen = strlen(homedir_env_mod);
assert(homedir_env_mod != homedir_env);
if (vim_ispathsep(homedir_env_mod[flen - 1])) {
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 901a1bc5a6..0d62a5f5f9 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -144,25 +144,6 @@ bool os_isdir(const char_u *name)
return true;
}
-/// Check if the given path is a directory and is executable.
-/// Gives the same results as `os_isdir()` on Windows.
-///
-/// @return `true` if `name` is a directory and executable.
-bool os_isdir_executable(const char *name)
- FUNC_ATTR_NONNULL_ALL
-{
- int32_t mode = os_getperm(name);
- if (mode < 0) {
- return false;
- }
-
-#ifdef WIN32
- return (S_ISDIR(mode));
-#else
- return (S_ISDIR(mode) && (S_IXUSR & mode));
-#endif
-}
-
/// Check what `name` is:
/// @return NODE_NORMAL: file or directory (or doesn't exist)
/// NODE_WRITABLE: writable device, socket, fifo, etc.
@@ -339,7 +320,7 @@ static bool is_executable_ext(const char *name, char **abspath)
const char *ext_end = ext;
size_t ext_len =
- copy_option_part((char_u **)&ext_end, (char_u *)buf_end,
+ copy_option_part(&ext_end, (char_u *)buf_end,
sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR);
if (ext_len != 0) {
bool in_pathext = nameext_len == ext_len
@@ -1051,7 +1032,7 @@ int os_remove(const char *path)
bool os_fileinfo(const char *path, FileInfo *file_info)
FUNC_ATTR_NONNULL_ARG(2)
{
- memset(file_info, 0, sizeof(*file_info));
+ CLEAR_POINTER(file_info);
return os_stat(path, &(file_info->stat)) == kLibuvSuccess;
}
@@ -1063,7 +1044,7 @@ bool os_fileinfo(const char *path, FileInfo *file_info)
bool os_fileinfo_link(const char *path, FileInfo *file_info)
FUNC_ATTR_NONNULL_ARG(2)
{
- memset(file_info, 0, sizeof(*file_info));
+ CLEAR_POINTER(file_info);
if (path == NULL) {
return false;
}
@@ -1087,7 +1068,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- memset(file_info, 0, sizeof(*file_info));
+ CLEAR_POINTER(file_info);
fs_loop_lock();
bool ok = uv_fs_fstat(&fs_loop,
&request,
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index c47a891c18..bfe6d59dc6 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -8,10 +8,9 @@
#include "nvim/api/private/defs.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/event/loop.h"
#include "nvim/event/rstream.h"
-#include "nvim/ex_cmds2.h"
-#include "nvim/fileio.h"
#include "nvim/getchar.h"
#include "nvim/keycodes.h"
#include "nvim/main.h"
@@ -19,6 +18,7 @@
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/input.h"
+#include "nvim/profile.h"
#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/ui.h"
diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h
index c243db4fa5..15e7c3da0c 100644
--- a/src/nvim/os/pty_conpty_win.h
+++ b/src/nvim/os/pty_conpty_win.h
@@ -1,6 +1,9 @@
#ifndef NVIM_OS_PTY_CONPTY_WIN_H
#define NVIM_OS_PTY_CONPTY_WIN_H
+#include "nvim/lib/kvec.h"
+#include "nvim/os/input.h"
+
#ifndef HPCON
# define HPCON VOID *
#endif
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 8f2018c1f4..dd44cb1ce7 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -25,6 +25,7 @@
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/path.h"
+#include "nvim/profile.h"
#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index 581f025a0f..e592570966 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -9,10 +9,10 @@
#endif
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/eval.h"
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
-#include "nvim/fileio.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
diff --git a/src/nvim/path.c b/src/nvim/path.c
index a0b09bcec2..caea11debd 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -227,7 +227,7 @@ char_u *get_past_head(const char_u *path)
#endif
while (vim_ispathsep(*retval)) {
- ++retval;
+ retval++;
}
return (char_u *)retval;
@@ -666,8 +666,8 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff,
for (p = buf + wildoff; p < s; ++p) {
if (rem_backslash(p)) {
STRMOVE(p, p + 1);
- --e;
- --s;
+ e--;
+ s--;
}
}
@@ -695,11 +695,11 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff,
regmatch.rm_ic = true; // Always ignore case on Windows.
#endif
if (flags & (EW_NOERROR | EW_NOTWILD)) {
- ++emsg_silent;
+ emsg_silent++;
}
regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
if (flags & (EW_NOERROR | EW_NOTWILD)) {
- --emsg_silent;
+ emsg_silent--;
}
xfree(pat);
@@ -742,9 +742,9 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff,
* find matches. */
STRCPY(buf + len, "/**");
STRCPY(buf + len + 3, path_end);
- ++stardepth;
+ stardepth++;
(void)do_path_expand(gap, buf, len + 1, flags, true);
- --stardepth;
+ stardepth--;
}
STRCPY(buf + len, path_end);
@@ -1141,7 +1141,7 @@ static int expand_in_path(garray_T *const gap, char_u *const pattern, const int
if (flags & EW_ADDSLASH) {
glob_flags |= WILD_ADD_SLASH;
}
- globpath(paths, pattern, gap, glob_flags);
+ globpath((char *)paths, pattern, gap, glob_flags);
xfree(paths);
return gap->ga_len;
@@ -1401,7 +1401,7 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags)
cmd = skipwhite(cmd); // skip over white space
p = cmd;
while (*p != NUL && *p != '\r' && *p != '\n') { // skip over entry
- ++p;
+ p++;
}
// add an entry if it is not empty
if (p > cmd) {
@@ -1409,11 +1409,11 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags)
*p = NUL;
addfile(gap, (char_u *)cmd, flags);
*p = i;
- ++cnt;
+ cnt++;
}
cmd = p;
while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n')) {
- ++cmd;
+ cmd++;
}
}
@@ -1656,12 +1656,12 @@ void simplify_filename(char_u *filename)
*p = NUL;
} else {
if (p > start && tail[-1] == '.') {
- --p;
+ p--;
}
STRMOVE(p, tail); // strip previous component
}
- --components;
+ components--;
}
} else if (p == start && !relative) { // leading "/.." or "/../"
STRMOVE(p, tail); // strip ".." or "../"
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmenu.c
index 4accddfce0..92db2db735 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmenu.c
@@ -1,12 +1,10 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/// @file popupmnu.c
+/// @file popupmenu.c
///
/// Popup menu (PUM)
-#include "nvim/popupmnu.h"
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -15,9 +13,11 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/insexpand.h"
#include "nvim/memline.h"
@@ -25,8 +25,7 @@
#include "nvim/menu.h"
#include "nvim/move.h"
#include "nvim/option.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
@@ -59,7 +58,7 @@ static bool pum_external = false;
static bool pum_invalid = false; // the screen was just cleared
#ifdef INCLUDE_GENERATED_DECLARATIONS
-#include "popupmnu.c.generated.h"
+# include "popupmenu.c.generated.h"
#endif
#define PUM_DEF_HEIGHT 10
#define PUM_DEF_WIDTH 15
@@ -472,7 +471,7 @@ void pum_redraw(void)
/ (pum_size - pum_height);
}
- for (i = 0; i < pum_height; ++i) {
+ for (i = 0; i < pum_height; i++) {
idx = i + pum_first;
attr = (idx == pum_selected) ? attr_select : attr_norm;
@@ -492,7 +491,7 @@ void pum_redraw(void)
grid_col = col_off;
totwidth = 0;
- for (round = 1; round <= 3; ++round) {
+ for (round = 1; round <= 3; round++) {
width = 0;
s = NULL;
@@ -663,11 +662,11 @@ void pum_redraw(void)
/// @param n
/// @param repeat
///
-/// @returns TRUE when the window was resized and the location of the popup
+/// @returns true when the window was resized and the location of the popup
/// menu must be recomputed.
-static int pum_set_selected(int n, int repeat)
+static bool pum_set_selected(int n, int repeat)
{
- int resized = FALSE;
+ int resized = false;
int context = pum_height / 2;
pum_selected = n;
@@ -800,12 +799,12 @@ static int pum_set_selected(int n, int repeat)
if (curwin->w_height < lnum) {
win_setheight((int)lnum);
- resized = TRUE;
+ resized = true;
}
}
curbuf->b_changed = false;
- curbuf->b_p_ma = FALSE;
+ curbuf->b_p_ma = false;
curwin->w_cursor.lnum = 1;
curwin->w_cursor.col = 0;
@@ -819,7 +818,7 @@ static int pum_set_selected(int n, int repeat)
// window is not resized, skip the preview window's
// status line redrawing.
if (ins_compl_active() && !resized) {
- curwin->w_redr_status = FALSE;
+ curwin->w_redr_status = false;
}
// Return cursor to where we were
@@ -928,7 +927,7 @@ void pum_recompose(void)
/// Gets the height of the menu.
///
/// @return the height of the popup menu, the number of entries visible.
-/// Only valid when pum_visible() returns TRUE!
+/// Only valid when pum_visible() returns true!
int pum_get_height(void)
{
if (pum_external) {
@@ -1051,7 +1050,7 @@ static void pum_execute_menu(vimmenu_T *menu, int mode)
for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) {
if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected) {
- memset(&ea, 0, sizeof(ea));
+ CLEAR_FIELD(ea);
execute_menu(&ea, mp, -1);
break;
}
diff --git a/src/nvim/popupmnu.h b/src/nvim/popupmenu.h
index 7d3f4c6f51..851ad31486 100644
--- a/src/nvim/popupmnu.h
+++ b/src/nvim/popupmenu.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_POPUPMNU_H
-#define NVIM_POPUPMNU_H
+#ifndef NVIM_POPUPMENU_H
+#define NVIM_POPUPMENU_H
#include "nvim/grid_defs.h"
#include "nvim/macros.h"
@@ -17,6 +17,6 @@ typedef struct {
EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT);
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "popupmnu.h.generated.h"
+# include "popupmenu.h.generated.h"
#endif
-#endif // NVIM_POPUPMNU_H
+#endif // NVIM_POPUPMENU_H
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index fe7bd2e912..d4f3756f4d 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -6,16 +6,32 @@
#include <stdio.h>
#include "nvim/assert.h"
+#include "nvim/charset.h"
+#include "nvim/debugger.h"
+#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/fileio.h"
#include "nvim/func_attr.h"
#include "nvim/globals.h" // for the global `time_fd` (startuptime)
-#include "nvim/os/os_defs.h"
+#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/profile.h"
+#include "nvim/runtime.h"
+#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "profile.c.generated.h"
#endif
+/// Struct used in sn_prl_ga for every line of a script.
+typedef struct sn_prl_S {
+ int snp_count; ///< nr of times line was executed
+ proftime_T sn_prl_total; ///< time spent in a line + children
+ proftime_T sn_prl_self; ///< time spent in a line itself
+} sn_prl_T;
+
+#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
+
static proftime_T prof_wait_time;
/// Gets the current time.
@@ -195,6 +211,652 @@ int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
return profile_signed(tm2 - tm1) < 0 ? -1 : 1;
}
+static char *profile_fname = NULL;
+
+/// Reset all profiling information.
+void profile_reset(void)
+{
+ // Reset sourced files.
+ for (int id = 1; id <= script_items.ga_len; id++) {
+ scriptitem_T *si = &SCRIPT_ITEM(id);
+ if (si->sn_prof_on) {
+ si->sn_prof_on = false;
+ si->sn_pr_force = false;
+ si->sn_pr_child = profile_zero();
+ si->sn_pr_nest = 0;
+ si->sn_pr_count = 0;
+ si->sn_pr_total = profile_zero();
+ si->sn_pr_self = profile_zero();
+ si->sn_pr_start = profile_zero();
+ si->sn_pr_children = profile_zero();
+ ga_clear(&si->sn_prl_ga);
+ si->sn_prl_start = profile_zero();
+ si->sn_prl_children = profile_zero();
+ si->sn_prl_wait = profile_zero();
+ si->sn_prl_idx = -1;
+ si->sn_prl_execed = 0;
+ }
+ }
+
+ // Reset functions.
+ hashtab_T *const functbl = func_tbl_get();
+ size_t todo = functbl->ht_used;
+ hashitem_T *hi = functbl->ht_array;
+
+ for (; todo > (size_t)0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ ufunc_T *uf = HI2UF(hi);
+ if (uf->uf_prof_initialized) {
+ uf->uf_profiling = 0;
+ uf->uf_tm_count = 0;
+ uf->uf_tm_total = profile_zero();
+ uf->uf_tm_self = profile_zero();
+ uf->uf_tm_children = profile_zero();
+
+ for (int i = 0; i < uf->uf_lines.ga_len; i++) {
+ uf->uf_tml_count[i] = 0;
+ uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
+ }
+
+ uf->uf_tml_start = profile_zero();
+ uf->uf_tml_children = profile_zero();
+ uf->uf_tml_wait = profile_zero();
+ uf->uf_tml_idx = -1;
+ uf->uf_tml_execed = 0;
+ }
+ }
+ }
+
+ XFREE_CLEAR(profile_fname);
+}
+
+/// ":profile cmd args"
+void ex_profile(exarg_T *eap)
+{
+ static proftime_T pause_time;
+
+ char *e;
+ int len;
+
+ e = (char *)skiptowhite((char_u *)eap->arg);
+ len = (int)(e - eap->arg);
+ e = skipwhite(e);
+
+ if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) {
+ xfree(profile_fname);
+ profile_fname = (char *)expand_env_save_opt((char_u *)e, true);
+ do_profiling = PROF_YES;
+ profile_set_wait(profile_zero());
+ set_vim_var_nr(VV_PROFILING, 1L);
+ } else if (do_profiling == PROF_NONE) {
+ emsg(_("E750: First use \":profile start {fname}\""));
+ } else if (STRCMP(eap->arg, "stop") == 0) {
+ profile_dump();
+ do_profiling = PROF_NONE;
+ set_vim_var_nr(VV_PROFILING, 0L);
+ profile_reset();
+ } else if (STRCMP(eap->arg, "pause") == 0) {
+ if (do_profiling == PROF_YES) {
+ pause_time = profile_start();
+ }
+ do_profiling = PROF_PAUSED;
+ } else if (STRCMP(eap->arg, "continue") == 0) {
+ if (do_profiling == PROF_PAUSED) {
+ pause_time = profile_end(pause_time);
+ profile_set_wait(profile_add(profile_get_wait(), pause_time));
+ }
+ do_profiling = PROF_YES;
+ } else if (STRCMP(eap->arg, "dump") == 0) {
+ profile_dump();
+ } else {
+ // The rest is similar to ":breakadd".
+ ex_breakadd(eap);
+ }
+}
+
+/// Command line expansion for :profile.
+static enum {
+ PEXP_SUBCMD, ///< expand :profile sub-commands
+ PEXP_FUNC, ///< expand :profile func {funcname}
+} pexpand_what;
+
+static char *pexpand_cmds[] = {
+ "continue",
+ "dump",
+ "file",
+ "func",
+ "pause",
+ "start",
+ "stop",
+ NULL
+};
+
+/// Function given to ExpandGeneric() to obtain the profile command
+/// specific expansion.
+char *get_profile_name(expand_T *xp, int idx)
+ FUNC_ATTR_PURE
+{
+ switch (pexpand_what) {
+ case PEXP_SUBCMD:
+ return pexpand_cmds[idx];
+ // case PEXP_FUNC: TODO
+ default:
+ return NULL;
+ }
+}
+
+/// Handle command line completion for :profile command.
+void set_context_in_profile_cmd(expand_T *xp, const char *arg)
+{
+ // Default: expand subcommands.
+ xp->xp_context = EXPAND_PROFILE;
+ pexpand_what = PEXP_SUBCMD;
+ xp->xp_pattern = (char *)arg;
+
+ char_u *const end_subcmd = skiptowhite((const char_u *)arg);
+ if (*end_subcmd == NUL) {
+ return;
+ }
+
+ if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) {
+ xp->xp_context = EXPAND_FILES;
+ xp->xp_pattern = skipwhite((char *)end_subcmd);
+ return;
+ }
+
+ // TODO(tarruda): expand function names after "func"
+ xp->xp_context = EXPAND_NOTHING;
+}
+
+static proftime_T inchar_time;
+
+/// Called when starting to wait for the user to type a character.
+void prof_inchar_enter(void)
+{
+ inchar_time = profile_start();
+}
+
+/// Called when finished waiting for the user to type a character.
+void prof_inchar_exit(void)
+{
+ inchar_time = profile_end(inchar_time);
+ profile_set_wait(profile_add(profile_get_wait(), inchar_time));
+}
+
+/// @return true when a function defined in the current script should be
+/// profiled.
+bool prof_def_func(void)
+ FUNC_ATTR_PURE
+{
+ if (current_sctx.sc_sid > 0) {
+ return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force;
+ }
+ return false;
+}
+
+/// Print the count and times for one function or function line.
+///
+/// @param prefer_self when equal print only self time
+static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self,
+ bool prefer_self)
+{
+ if (count > 0) {
+ fprintf(fd, "%5d ", count);
+ if (prefer_self && profile_equal(*total, *self)) {
+ fprintf(fd, " ");
+ } else {
+ fprintf(fd, "%s ", profile_msg(*total));
+ }
+ if (!prefer_self && profile_equal(*total, *self)) {
+ fprintf(fd, " ");
+ } else {
+ fprintf(fd, "%s ", profile_msg(*self));
+ }
+ } else {
+ fprintf(fd, " ");
+ }
+}
+
+/// @param prefer_self when equal print only self time
+static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self)
+{
+ int i;
+ ufunc_T *fp;
+
+ fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title);
+ fprintf(fd, "count total (s) self (s) function\n");
+ for (i = 0; i < 20 && i < st_len; i++) {
+ fp = sorttab[i];
+ prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self,
+ prefer_self);
+ if (fp->uf_name[0] == K_SPECIAL) {
+ fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3);
+ } else {
+ fprintf(fd, " %s()\n", fp->uf_name);
+ }
+ }
+ fprintf(fd, "\n");
+}
+
+/// Compare function for total time sorting.
+static int prof_total_cmp(const void *s1, const void *s2)
+{
+ ufunc_T *p1 = *(ufunc_T **)s1;
+ ufunc_T *p2 = *(ufunc_T **)s2;
+ return profile_cmp(p1->uf_tm_total, p2->uf_tm_total);
+}
+
+/// Compare function for self time sorting.
+static int prof_self_cmp(const void *s1, const void *s2)
+{
+ ufunc_T *p1 = *(ufunc_T **)s1;
+ ufunc_T *p2 = *(ufunc_T **)s2;
+ return profile_cmp(p1->uf_tm_self, p2->uf_tm_self);
+}
+
+/// Start profiling function "fp".
+void func_do_profile(ufunc_T *fp)
+{
+ int len = fp->uf_lines.ga_len;
+
+ if (!fp->uf_prof_initialized) {
+ if (len == 0) {
+ len = 1; // avoid getting error for allocating zero bytes
+ }
+ fp->uf_tm_count = 0;
+ fp->uf_tm_self = profile_zero();
+ fp->uf_tm_total = profile_zero();
+
+ if (fp->uf_tml_count == NULL) {
+ fp->uf_tml_count = xcalloc((size_t)len, sizeof(int));
+ }
+
+ if (fp->uf_tml_total == NULL) {
+ fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T));
+ }
+
+ if (fp->uf_tml_self == NULL) {
+ fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T));
+ }
+
+ fp->uf_tml_idx = -1;
+ fp->uf_prof_initialized = true;
+ }
+
+ fp->uf_profiling = true;
+}
+
+/// Prepare profiling for entering a child or something else that is not
+/// counted for the script/function itself.
+/// Should always be called in pair with prof_child_exit().
+///
+/// @param tm place to store waittime
+void prof_child_enter(proftime_T *tm)
+{
+ funccall_T *fc = get_current_funccal();
+
+ if (fc != NULL && fc->func->uf_profiling) {
+ fc->prof_child = profile_start();
+ }
+
+ script_prof_save(tm);
+}
+
+/// Take care of time spent in a child.
+/// Should always be called after prof_child_enter().
+///
+/// @param tm where waittime was stored
+void prof_child_exit(proftime_T *tm)
+{
+ funccall_T *fc = get_current_funccal();
+
+ if (fc != NULL && fc->func->uf_profiling) {
+ fc->prof_child = profile_end(fc->prof_child);
+ // don't count waiting time
+ fc->prof_child = profile_sub_wait(*tm, fc->prof_child);
+ fc->func->uf_tm_children =
+ profile_add(fc->func->uf_tm_children, fc->prof_child);
+ fc->func->uf_tml_children =
+ profile_add(fc->func->uf_tml_children, fc->prof_child);
+ }
+ script_prof_restore(tm);
+}
+
+/// Called when starting to read a function line.
+/// "sourcing_lnum" must be correct!
+/// When skipping lines it may not actually be executed, but we won't find out
+/// until later and we need to store the time now.
+void func_line_start(void *cookie)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+ ufunc_T *fp = fcp->func;
+
+ if (fp->uf_profiling && SOURCING_LNUM >= 1 && SOURCING_LNUM <= fp->uf_lines.ga_len) {
+ fp->uf_tml_idx = SOURCING_LNUM - 1;
+ // Skip continuation lines.
+ while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) {
+ fp->uf_tml_idx--;
+ }
+ fp->uf_tml_execed = false;
+ fp->uf_tml_start = profile_start();
+ fp->uf_tml_children = profile_zero();
+ fp->uf_tml_wait = profile_get_wait();
+ }
+}
+
+/// Called when actually executing a function line.
+void func_line_exec(void *cookie)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+ ufunc_T *fp = fcp->func;
+
+ if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
+ fp->uf_tml_execed = true;
+ }
+}
+
+/// Called when done with a function line.
+void func_line_end(void *cookie)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+ ufunc_T *fp = fcp->func;
+
+ if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
+ if (fp->uf_tml_execed) {
+ fp->uf_tml_count[fp->uf_tml_idx]++;
+ fp->uf_tml_start = profile_end(fp->uf_tml_start);
+ fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start);
+ fp->uf_tml_total[fp->uf_tml_idx] =
+ profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start);
+ fp->uf_tml_self[fp->uf_tml_idx] =
+ profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start,
+ fp->uf_tml_children);
+ }
+ fp->uf_tml_idx = -1;
+ }
+}
+
+/// Dump the profiling results for all functions in file "fd".
+static void func_dump_profile(FILE *fd)
+{
+ hashtab_T *const functbl = func_tbl_get();
+ hashitem_T *hi;
+ int todo;
+ ufunc_T *fp;
+ ufunc_T **sorttab;
+ int st_len = 0;
+
+ todo = (int)functbl->ht_used;
+ if (todo == 0) {
+ return; // nothing to dump
+ }
+
+ sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
+
+ for (hi = functbl->ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ fp = HI2UF(hi);
+ if (fp->uf_prof_initialized) {
+ sorttab[st_len++] = fp;
+
+ if (fp->uf_name[0] == K_SPECIAL) {
+ fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3);
+ } else {
+ fprintf(fd, "FUNCTION %s()\n", fp->uf_name);
+ }
+ if (fp->uf_script_ctx.sc_sid != 0) {
+ bool should_free;
+ const LastSet last_set = (LastSet){
+ .script_ctx = fp->uf_script_ctx,
+ .channel_id = 0,
+ };
+ char *p = (char *)get_scriptname(last_set, &should_free);
+ fprintf(fd, " Defined: %s:%" PRIdLINENR "\n",
+ p, fp->uf_script_ctx.sc_lnum);
+ if (should_free) {
+ xfree(p);
+ }
+ }
+ if (fp->uf_tm_count == 1) {
+ fprintf(fd, "Called 1 time\n");
+ } else {
+ fprintf(fd, "Called %d times\n", fp->uf_tm_count);
+ }
+ fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total));
+ fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self));
+ fprintf(fd, "\n");
+ fprintf(fd, "count total (s) self (s)\n");
+
+ for (int i = 0; i < fp->uf_lines.ga_len; i++) {
+ if (FUNCLINE(fp, i) == NULL) {
+ continue;
+ }
+ prof_func_line(fd, fp->uf_tml_count[i],
+ &fp->uf_tml_total[i], &fp->uf_tml_self[i], true);
+ fprintf(fd, "%s\n", FUNCLINE(fp, i));
+ }
+ fprintf(fd, "\n");
+ }
+ }
+ }
+
+ if (st_len > 0) {
+ qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
+ prof_total_cmp);
+ prof_sort_list(fd, sorttab, st_len, "TOTAL", false);
+ qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
+ prof_self_cmp);
+ prof_sort_list(fd, sorttab, st_len, "SELF", true);
+ }
+
+ xfree(sorttab);
+}
+
+/// Start profiling a script.
+void profile_init(scriptitem_T *si)
+{
+ si->sn_pr_count = 0;
+ si->sn_pr_total = profile_zero();
+ si->sn_pr_self = profile_zero();
+
+ ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100);
+ si->sn_prl_idx = -1;
+ si->sn_prof_on = true;
+ si->sn_pr_nest = 0;
+}
+
+/// Save time when starting to invoke another script or function.
+///
+/// @param tm place to store wait time
+void script_prof_save(proftime_T *tm)
+{
+ scriptitem_T *si;
+
+ if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
+ si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ if (si->sn_prof_on && si->sn_pr_nest++ == 0) {
+ si->sn_pr_child = profile_start();
+ }
+ }
+ *tm = profile_get_wait();
+}
+
+/// Count time spent in children after invoking another script or function.
+void script_prof_restore(proftime_T *tm)
+{
+ scriptitem_T *si;
+
+ if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
+ si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ if (si->sn_prof_on && --si->sn_pr_nest == 0) {
+ si->sn_pr_child = profile_end(si->sn_pr_child);
+ // don't count wait time
+ si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child);
+ si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child);
+ si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child);
+ }
+ }
+}
+
+/// Dump the profiling results for all scripts in file "fd".
+static void script_dump_profile(FILE *fd)
+{
+ scriptitem_T *si;
+ FILE *sfd;
+ sn_prl_T *pp;
+
+ for (int id = 1; id <= script_items.ga_len; id++) {
+ si = &SCRIPT_ITEM(id);
+ if (si->sn_prof_on) {
+ fprintf(fd, "SCRIPT %s\n", si->sn_name);
+ if (si->sn_pr_count == 1) {
+ fprintf(fd, "Sourced 1 time\n");
+ } else {
+ fprintf(fd, "Sourced %d times\n", si->sn_pr_count);
+ }
+ fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total));
+ fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self));
+ fprintf(fd, "\n");
+ fprintf(fd, "count total (s) self (s)\n");
+
+ sfd = os_fopen((char *)si->sn_name, "r");
+ if (sfd == NULL) {
+ fprintf(fd, "Cannot open file!\n");
+ } else {
+ // Keep going till the end of file, so that trailing
+ // continuation lines are listed.
+ for (int i = 0;; i++) {
+ if (vim_fgets(IObuff, IOSIZE, sfd)) {
+ break;
+ }
+ // When a line has been truncated, append NL, taking care
+ // of multi-byte characters .
+ if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) {
+ int n = IOSIZE - 2;
+
+ // Move to the first byte of this char.
+ // utf_head_off() doesn't work, because it checks
+ // for a truncated character.
+ while (n > 0 && (IObuff[n] & 0xc0) == 0x80) {
+ n--;
+ }
+
+ IObuff[n] = NL;
+ IObuff[n + 1] = NUL;
+ }
+ if (i < si->sn_prl_ga.ga_len
+ && (pp = &PRL_ITEM(si, i))->snp_count > 0) {
+ fprintf(fd, "%5d ", pp->snp_count);
+ if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) {
+ fprintf(fd, " ");
+ } else {
+ fprintf(fd, "%s ", profile_msg(pp->sn_prl_total));
+ }
+ fprintf(fd, "%s ", profile_msg(pp->sn_prl_self));
+ } else {
+ fprintf(fd, " ");
+ }
+ fprintf(fd, "%s", IObuff);
+ }
+ fclose(sfd);
+ }
+ fprintf(fd, "\n");
+ }
+ }
+}
+
+/// Dump the profiling info.
+void profile_dump(void)
+{
+ FILE *fd;
+
+ if (profile_fname != NULL) {
+ fd = os_fopen(profile_fname, "w");
+ if (fd == NULL) {
+ semsg(_(e_notopen), profile_fname);
+ } else {
+ script_dump_profile(fd);
+ func_dump_profile(fd);
+ fclose(fd);
+ }
+ }
+}
+
+/// Called when starting to read a script line.
+/// "sourcing_lnum" must be correct!
+/// When skipping lines it may not actually be executed, but we won't find out
+/// until later and we need to store the time now.
+void script_line_start(void)
+{
+ scriptitem_T *si;
+ sn_prl_T *pp;
+
+ if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
+ return;
+ }
+ si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ if (si->sn_prof_on && SOURCING_LNUM >= 1) {
+ // Grow the array before starting the timer, so that the time spent
+ // here isn't counted.
+ (void)ga_grow(&si->sn_prl_ga, SOURCING_LNUM - si->sn_prl_ga.ga_len);
+ si->sn_prl_idx = SOURCING_LNUM - 1;
+ while (si->sn_prl_ga.ga_len <= si->sn_prl_idx
+ && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) {
+ // Zero counters for a line that was not used before.
+ pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
+ pp->snp_count = 0;
+ pp->sn_prl_total = profile_zero();
+ pp->sn_prl_self = profile_zero();
+ si->sn_prl_ga.ga_len++;
+ }
+ si->sn_prl_execed = false;
+ si->sn_prl_start = profile_start();
+ si->sn_prl_children = profile_zero();
+ si->sn_prl_wait = profile_get_wait();
+ }
+}
+
+/// Called when actually executing a function line.
+void script_line_exec(void)
+{
+ scriptitem_T *si;
+
+ if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
+ return;
+ }
+ si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ if (si->sn_prof_on && si->sn_prl_idx >= 0) {
+ si->sn_prl_execed = true;
+ }
+}
+
+/// Called when done with a function line.
+void script_line_end(void)
+{
+ scriptitem_T *si;
+ sn_prl_T *pp;
+
+ if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
+ return;
+ }
+ si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ if (si->sn_prof_on && si->sn_prl_idx >= 0
+ && si->sn_prl_idx < si->sn_prl_ga.ga_len) {
+ if (si->sn_prl_execed) {
+ pp = &PRL_ITEM(si, si->sn_prl_idx);
+ pp->snp_count++;
+ si->sn_prl_start = profile_end(si->sn_prl_start);
+ si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start);
+ pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start);
+ pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start,
+ si->sn_prl_children);
+ }
+ si->sn_prl_idx = -1;
+ }
+}
+
/// globals for use in the startuptime related functionality (time_*).
static proftime_T g_start_time;
static proftime_T g_prev_time;
diff --git a/src/nvim/profile.h b/src/nvim/profile.h
index 17c35c5eb7..547d11185f 100644
--- a/src/nvim/profile.h
+++ b/src/nvim/profile.h
@@ -4,7 +4,8 @@
#include <stdint.h>
#include <time.h>
-typedef uint64_t proftime_T;
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/runtime.h"
#define TIME_MSG(s) do { \
if (time_fd != NULL) time_msg(s, NULL); \
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 99129bd15e..1c416a872b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -9,10 +9,12 @@
#include <string.h>
#include "nvim/api/private/helpers.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
@@ -22,6 +24,7 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/help.h"
#include "nvim/highlight_group.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -37,7 +40,6 @@
#include "nvim/path.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
@@ -1224,7 +1226,7 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title)
qi->qf_curlist = qi->qf_listcount++;
}
qf_list_T *qfl = qf_get_curlist(qi);
- memset(qfl, 0, sizeof(qf_list_T));
+ CLEAR_POINTER(qfl);
qf_store_title(qfl, qf_title);
qfl->qfl_type = qi->qfl_type;
qfl->qf_id = ++last_qf_id;
@@ -3119,7 +3121,7 @@ void qf_list(exarg_T *eap)
}
int idx1 = 1;
int idx2 = -1;
- if (!get_list_range((char_u **)&arg, &idx1, &idx2) || *arg != NUL) {
+ if (!get_list_range(&arg, &idx1, &idx2) || *arg != NUL) {
semsg(_(e_trailing_arg), arg);
return;
}
@@ -5299,7 +5301,7 @@ static bool existing_swapfile(const buf_T *buf)
/// :{count}vimgrep /{pattern}/[g][j] {file} ...
static int vgr_process_args(exarg_T *eap, vgr_args_T *args)
{
- memset(args, 0, sizeof(*args));
+ CLEAR_POINTER(args);
args->regmatch.regprog = NULL;
args->qf_title = xstrdup(qf_cmdtitle(*eap->cmdlinep));
@@ -5779,7 +5781,7 @@ static int get_qfline_items(qfline_T *qfp, list_T *list)
/// If qf_idx is -1, use the current list. Otherwise, use the specified list.
/// If eidx is not 0, then return only the specified entry. Otherwise return
/// all the entries.
-int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, list_T *list)
+static int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, list_T *list)
{
qf_info_T *qi = qi_arg;
@@ -6149,7 +6151,7 @@ static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict)
/// Return quickfix/location list details (title) as a dictionary.
/// 'what' contains the details to return. If 'list_idx' is -1,
/// then current list is used. Otherwise the specified list is used.
-int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
+static int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
{
qf_info_T *qi = &ql_info;
dictitem_T *di = NULL;
@@ -7157,3 +7159,137 @@ void ex_helpgrep(exarg_T *eap)
}
}
}
+
+static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv)
+{
+ if (what_arg->v_type == VAR_UNKNOWN) {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ if (is_qf || wp != NULL) {
+ (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list);
+ }
+ } else {
+ tv_dict_alloc_ret(rettv);
+ if (is_qf || wp != NULL) {
+ if (what_arg->v_type == VAR_DICT) {
+ dict_T *d = what_arg->vval.v_dict;
+
+ if (d != NULL) {
+ qf_get_properties(wp, d, rettv->vval.v_dict);
+ }
+ } else {
+ emsg(_(e_dictreq));
+ }
+ }
+ }
+}
+
+/// "getloclist()" function
+void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ get_qf_loc_list(false, wp, &argvars[1], rettv);
+}
+
+/// "getqflist()" functions
+void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_qf_loc_list(true, NULL, &argvars[0], rettv);
+}
+
+/// Create quickfix/location list from VimL values
+///
+/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
+/// args argument in which case errors out, including VAR_UNKNOWN parameters.
+///
+/// @param[in,out] wp Window to create location list for. May be NULL in
+/// which case quickfix list will be created.
+/// @param[in] args [list, action, what]
+/// @param[in] args[0] Quickfix list contents.
+/// @param[in] args[1] Optional. Action to perform:
+/// append to an existing list, replace its content,
+/// or create a new one.
+/// @param[in] args[2] Optional. Quickfix list properties or title.
+/// Defaults to caller function name.
+/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
+static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
+{
+ static char *e_invact = N_("E927: Invalid action: '%s'");
+ const char *title = NULL;
+ char action = ' ';
+ static int recursive = 0;
+ rettv->vval.v_number = -1;
+ dict_T *what = NULL;
+
+ typval_T *list_arg = &args[0];
+ if (list_arg->v_type != VAR_LIST) {
+ emsg(_(e_listreq));
+ return;
+ } else if (recursive != 0) {
+ emsg(_(e_au_recursive));
+ return;
+ }
+
+ typval_T *action_arg = &args[1];
+ if (action_arg->v_type == VAR_UNKNOWN) {
+ // Option argument was not given.
+ goto skip_args;
+ } else if (action_arg->v_type != VAR_STRING) {
+ emsg(_(e_stringreq));
+ return;
+ }
+ const char *const act = tv_get_string_chk(action_arg);
+ if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
+ && act[1] == NUL) {
+ action = *act;
+ } else {
+ semsg(_(e_invact), act);
+ return;
+ }
+
+ typval_T *const what_arg = &args[2];
+ if (what_arg->v_type == VAR_UNKNOWN) {
+ // Option argument was not given.
+ goto skip_args;
+ } else if (what_arg->v_type == VAR_STRING) {
+ title = tv_get_string_chk(what_arg);
+ if (!title) {
+ // Type error. Error already printed by tv_get_string_chk().
+ return;
+ }
+ } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) {
+ what = what_arg->vval.v_dict;
+ } else {
+ emsg(_(e_dictreq));
+ return;
+ }
+
+skip_args:
+ if (!title) {
+ title = (wp ? ":setloclist()" : ":setqflist()");
+ }
+
+ recursive++;
+ list_T *const l = list_arg->vval.v_list;
+ if (set_errorlist(wp, l, action, (char *)title, what) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ recursive--;
+}
+
+/// "setloclist()" function
+void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ win_T *win = find_win_by_nr_or_id(&argvars[0]);
+ if (win != NULL) {
+ set_qf_ll_list(win, &argvars[1], rettv);
+ }
+}
+
+/// "setqflist()" function
+void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_qf_ll_list(NULL, argvars, rettv);
+}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index fbbf904f8b..b7ec4bf94e 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -20,7 +20,6 @@
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/garray.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
@@ -28,6 +27,7 @@
#include "nvim/message.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
#include "nvim/vim.h"
@@ -481,16 +481,14 @@ static char_u *skip_anyof(char *p)
return (char_u *)p;
}
-/*
- * Skip past regular expression.
- * Stop at end of "startp" or where "dirc" is found ('/', '?', etc).
- * Take care of characters with a backslash in front of it.
- * Skip strings inside [ and ].
- * When "newp" is not NULL and "dirc" is '?', make an allocated copy of the
- * expression and change "\?" to "?". If "*newp" is not NULL the expression
- * is changed in-place.
- */
-char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp)
+/// Skip past regular expression.
+/// Stop at end of "startp" or where "dirc" is found ('/', '?', etc).
+/// Take care of characters with a backslash in front of it.
+/// Skip strings inside [ and ].
+/// When "newp" is not NULL and "dirc" is '?', make an allocated copy of the
+/// expression and change "\?" to "?". If "*newp" is not NULL the expression
+/// is changed in-place.
+char_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp)
{
int mymagic;
char_u *p = startp;
@@ -516,8 +514,8 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp)
if (dirc == '?' && newp != NULL && p[1] == '?') {
// change "\?" to "?", make a copy first.
if (*newp == NULL) {
- *newp = vim_strsave(startp);
- p = *newp + (p - startp);
+ *newp = (char *)vim_strsave(startp);
+ p = (char_u *)(*newp) + (p - startp);
}
STRMOVE(p, p + 1);
} else {
@@ -823,7 +821,7 @@ static int64_t gethexchrs(int maxinputlen)
}
nr <<= 4;
nr |= hex2nr(c);
- ++regparse;
+ regparse++;
}
if (i == 0) {
@@ -880,7 +878,7 @@ static int64_t getoctchrs(void)
}
nr <<= 3;
nr |= hex2nr(c);
- ++regparse;
+ regparse++;
}
if (i == 0) {
@@ -2097,8 +2095,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
dst++;
}
- ++s;
- --len;
+ s++;
+ len--;
}
}
}
diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c
index fff5ae9b7f..769d2ceeef 100644
--- a/src/nvim/regexp_bt.c
+++ b/src/nvim/regexp_bt.c
@@ -466,7 +466,7 @@ static void regcomp_start(char_u *expr, int re_flags) //
num_complex_braces = 0;
regnpar = 1;
- memset(had_endbrace, 0, sizeof(had_endbrace));
+ CLEAR_FIELD(had_endbrace);
regnzpar = 1;
re_has_z = 0;
regsize = 0L;
@@ -2189,7 +2189,7 @@ collection:
while (*regparse != NUL && *regparse != ']') {
if (*regparse == '-') {
- ++regparse;
+ regparse++;
// The '-' is not used for a range at the end and
// after or before a '\n'.
if (*regparse == ']' || *regparse == NUL
@@ -2619,7 +2619,7 @@ static char_u *regpiece(int *flagp)
regoptail(ret, regnode(BACK));
regoptail(ret, ret);
reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
- ++num_complex_braces;
+ num_complex_braces++;
}
if (minval > 0 && maxval > 0) {
*flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH)));
@@ -2792,7 +2792,7 @@ static char_u *reg(int paren, int *flagp)
EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL);
}
parno = regnpar;
- ++regnpar;
+ regnpar++;
ret = regnode(MOPEN + parno);
} else if (paren == REG_NPAREN) {
// Make a NOPEN node.
@@ -3181,7 +3181,7 @@ static int regrepeat(char_u *p, long maxcount)
} else {
break;
}
- ++count;
+ count++;
}
break;
@@ -3299,7 +3299,7 @@ do_class:
} else {
break;
}
- ++count;
+ count++;
}
break;
@@ -3415,7 +3415,7 @@ do_class:
break;
}
scan += len;
- ++count;
+ count++;
}
}
}
@@ -3453,7 +3453,7 @@ do_class:
}
scan++;
}
- ++count;
+ count++;
}
break;
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 09f244c2f6..b313dfe877 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -15,7 +15,6 @@
#include <stdbool.h>
#include "nvim/pos.h"
-#include "nvim/profile.h"
#include "nvim/types.h"
/*
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 1a5c250664..554def5b8a 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -5055,7 +5055,7 @@ skip_add:
// avoid compiler warnings
save_ptr = NULL;
- memset(&save_multipos, 0, sizeof(save_multipos));
+ CLEAR_FIELD(save_multipos);
// Set the position (with "off" added) in the subexpression. Save
// and restore it when it was in use. Otherwise fill any gap.
@@ -5180,7 +5180,7 @@ skip_add:
save_ptr = sub->list.line[subidx].end;
sub->list.line[subidx].end = rex.input + off;
// avoid compiler warnings
- memset(&save_multipos, 0, sizeof(save_multipos));
+ CLEAR_FIELD(save_multipos);
}
subs = addstate(l, state->out, subs, pim, off_arg);
@@ -5488,7 +5488,7 @@ static void nfa_save_listids(nfa_regprog_T *prog, int *list)
for (i = prog->nstate; --i >= 0;) {
list[i] = p->lastlist[1];
p->lastlist[1] = 0;
- ++p;
+ p++;
}
}
@@ -5503,7 +5503,7 @@ static void nfa_restore_listids(nfa_regprog_T *prog, int *list)
p = &prog->state[0];
for (i = prog->nstate; --i >= 0;) {
p->lastlist[1] = list[i];
- ++p;
+ p++;
}
}
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 5d28d624fe..edcaa27e2b 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -7,20 +7,155 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/charset.h"
+#include "nvim/debugger.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_eval.h"
+#include "nvim/ex_getln.h"
#include "nvim/lua/executor.h"
+#include "nvim/memline.h"
#include "nvim/option.h"
+#include "nvim/os/input.h"
#include "nvim/os/os.h"
+#include "nvim/profile.h"
#include "nvim/runtime.h"
#include "nvim/vim.h"
+/// Structure used to store info for each sourced file.
+/// It is shared between do_source() and getsourceline().
+/// This is required, because it needs to be handed to do_cmdline() and
+/// sourcing can be done recursively.
+struct source_cookie {
+ FILE *fp; ///< opened file for sourcing
+ char *nextline; ///< if not NULL: line that was read ahead
+ linenr_T sourcing_lnum; ///< line number of the source file
+ int finished; ///< ":finish" used
+#if defined(USE_CRNL)
+ int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS
+ bool error; ///< true if LF found after CR-LF
+#endif
+ linenr_T breakpoint; ///< next line with breakpoint or zero
+ char *fname; ///< name of sourced file
+ int dbg_tick; ///< debug_tick when breakpoint was set
+ int level; ///< top nesting level of sourced file
+ vimconv_T conv; ///< type of conversion
+};
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "runtime.c.generated.h"
#endif
+garray_T exestack = { 0, 0, sizeof(estack_T), 50, NULL };
+garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL };
+
+/// Initialize the execution stack.
+void estack_init(void)
+{
+ ga_grow(&exestack, 10);
+ estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len;
+ entry->es_type = ETYPE_TOP;
+ entry->es_name = NULL;
+ entry->es_lnum = 0;
+ entry->es_info.ufunc = NULL;
+ exestack.ga_len++;
+}
+
+/// Add an item to the execution stack.
+/// @return the new entry
+estack_T *estack_push(etype_T type, char *name, linenr_T lnum)
+{
+ ga_grow(&exestack, 1);
+ estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len;
+ entry->es_type = type;
+ entry->es_name = name;
+ entry->es_lnum = lnum;
+ entry->es_info.ufunc = NULL;
+ exestack.ga_len++;
+ return entry;
+}
+
+/// Add a user function to the execution stack.
+void estack_push_ufunc(ufunc_T *ufunc, linenr_T lnum)
+{
+ estack_T *entry = estack_push(ETYPE_UFUNC,
+ (char *)(ufunc->uf_name_exp != NULL
+ ? ufunc->uf_name_exp : ufunc->uf_name),
+ lnum);
+ if (entry != NULL) {
+ entry->es_info.ufunc = ufunc;
+ }
+}
+
+/// Take an item off of the execution stack.
+void estack_pop(void)
+{
+ if (exestack.ga_len > 1) {
+ exestack.ga_len--;
+ }
+}
+
+/// Get the current value for <sfile> in allocated memory.
+/// @param which ESTACK_SFILE for <sfile> and ESTACK_STACK for <stack>.
+char *estack_sfile(estack_arg_T which)
+{
+ estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1;
+ if (which == ESTACK_SFILE && entry->es_type != ETYPE_UFUNC) {
+ if (entry->es_name == NULL) {
+ return NULL;
+ }
+ return xstrdup(entry->es_name);
+ }
+
+ // Give information about each stack entry up to the root.
+ // For a function we compose the call stack, as it was done in the past:
+ // "function One[123]..Two[456]..Three"
+ garray_T ga;
+ ga_init(&ga, sizeof(char), 100);
+ etype_T last_type = ETYPE_SCRIPT;
+ for (int idx = 0; idx < exestack.ga_len; idx++) {
+ entry = ((estack_T *)exestack.ga_data) + idx;
+ if (entry->es_name != NULL) {
+ size_t len = strlen(entry->es_name) + 15;
+ char *type_name = "";
+ if (entry->es_type != last_type) {
+ switch (entry->es_type) {
+ case ETYPE_SCRIPT:
+ type_name = "script "; break;
+ case ETYPE_UFUNC:
+ type_name = "function "; break;
+ default:
+ type_name = ""; break;
+ }
+ last_type = entry->es_type;
+ }
+ len += strlen(type_name);
+ ga_grow(&ga, (int)len);
+ linenr_T lnum = idx == exestack.ga_len - 1
+ ? which == ESTACK_STACK ? SOURCING_LNUM : 0
+ : entry->es_lnum;
+ char *dots = idx == exestack.ga_len - 1 ? "" : "..";
+ if (lnum == 0) {
+ // For the bottom entry of <sfile>: do not add the line number,
+ // it is used in <slnum>. Also leave it out when the number is
+ // not set.
+ vim_snprintf((char *)ga.ga_data + ga.ga_len, len, "%s%s%s",
+ type_name, entry->es_name, dots);
+ } else {
+ vim_snprintf((char *)ga.ga_data + ga.ga_len, len, "%s%s[%" PRIdLINENR "]%s",
+ type_name, entry->es_name, lnum, dots);
+ }
+ ga.ga_len += (int)strlen((char *)ga.ga_data + ga.ga_len);
+ }
+ }
+
+ return (char *)ga.ga_data;
+}
+
static bool runtime_search_path_valid = false;
static int *runtime_search_path_ref = NULL;
static RuntimeSearchPath runtime_search_path;
@@ -90,10 +225,10 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback,
}
// Loop over all entries in 'runtimepath'.
- char_u *rtp = rtp_copy;
+ char *rtp = (char *)rtp_copy;
while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) {
// Copy the path from 'runtimepath' to buf[].
- copy_option_part((char **)&rtp, buf, MAXPATHL, ",");
+ copy_option_part(&rtp, buf, MAXPATHL, ",");
size_t buflen = STRLEN(buf);
// Skip after or non-after directories.
@@ -114,12 +249,11 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback,
tail = (char_u *)buf + STRLEN(buf);
// Loop over all patterns in "name"
- char_u *np = (char_u *)name;
+ char *np = name;
while (*np != NUL && ((flags & DIP_ALL) || !did_one)) {
// Append the pattern from "name" to buf[].
assert(MAXPATHL >= (tail - (char_u *)buf));
- copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)),
- "\t ");
+ copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)), "\t ");
if (p_verbose > 10) {
verbose_enter();
@@ -248,11 +382,11 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void
tail = buf + STRLEN(buf);
// Loop over all patterns in "name"
- char_u *np = name;
+ char *np = (char *)name;
while (*np != NUL && ((flags & DIP_ALL) || !did_one)) {
// Append the pattern from "name" to buf[].
assert(MAXPATHL >= (tail - buf));
- copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t ");
+ copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t ");
if (p_verbose > 10) {
verbose_enter();
@@ -991,6 +1125,160 @@ void ex_packadd(exarg_T *eap)
}
}
+/// Expand color scheme, compiler or filetype names.
+/// Search from 'runtimepath':
+/// 'runtimepath'/{dirnames}/{pat}.vim
+/// When "flags" has DIP_START: search also from 'start' of 'packpath':
+/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim
+/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath':
+/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim
+/// When "flags" has DIP_LUA: search also performed for .lua files
+/// "dirnames" is an array with one or more directory names.
+int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirnames[])
+{
+ *num_file = 0;
+ *file = NULL;
+ size_t pat_len = STRLEN(pat);
+
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char *), 10);
+
+ // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic.
+ for (int i = 0; dirnames[i] != NULL; i++) {
+ size_t size = STRLEN(dirnames[i]) + pat_len + 7;
+ char_u *s = xmalloc(size);
+ snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat);
+ globpath((char *)p_rtp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat);
+ globpath((char *)p_rtp, s, &ga, 0);
+ }
+ xfree(s);
+ }
+
+ if (flags & DIP_START) {
+ for (int i = 0; dirnames[i] != NULL; i++) {
+ size_t size = STRLEN(dirnames[i]) + pat_len + 22;
+ char_u *s = xmalloc(size);
+ snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ }
+ xfree(s);
+ }
+
+ for (int i = 0; dirnames[i] != NULL; i++) {
+ size_t size = STRLEN(dirnames[i]) + pat_len + 22;
+ char_u *s = xmalloc(size);
+ snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ }
+ xfree(s);
+ }
+ }
+
+ if (flags & DIP_OPT) {
+ for (int i = 0; dirnames[i] != NULL; i++) {
+ size_t size = STRLEN(dirnames[i]) + pat_len + 20;
+ char_u *s = xmalloc(size);
+ snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ }
+ xfree(s);
+ }
+
+ for (int i = 0; dirnames[i] != NULL; i++) {
+ size_t size = STRLEN(dirnames[i]) + pat_len + 20;
+ char_u *s = xmalloc(size);
+ snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ }
+ xfree(s);
+ }
+ }
+
+ for (int i = 0; i < ga.ga_len; i++) {
+ char_u *match = ((char_u **)ga.ga_data)[i];
+ char_u *s = match;
+ char_u *e = s + STRLEN(s);
+ if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0
+ || ((flags & DIP_LUA)
+ && STRNICMP(e - 4, ".lua", 4) == 0))) {
+ e -= 4;
+ for (s = e; s > match; MB_PTR_BACK(match, s)) {
+ if (vim_ispathsep(*s)) {
+ break;
+ }
+ }
+ s++;
+ *e = NUL;
+ assert((e - s) + 1 >= 0);
+ memmove(match, s, (size_t)(e - s) + 1);
+ }
+ }
+
+ if (GA_EMPTY(&ga)) {
+ return FAIL;
+ }
+
+ // Sort and remove duplicates which can happen when specifying multiple
+ // directories in dirnames.
+ ga_remove_duplicate_strings(&ga);
+
+ *file = ga.ga_data;
+ *num_file = ga.ga_len;
+ return OK;
+}
+
+/// Expand loadplugin names:
+/// 'packpath'/pack/ * /opt/{pat}
+int ExpandPackAddDir(char_u *pat, int *num_file, char ***file)
+{
+ garray_T ga;
+
+ *num_file = 0;
+ *file = NULL;
+ size_t pat_len = STRLEN(pat);
+ ga_init(&ga, (int)sizeof(char *), 10);
+
+ size_t buflen = pat_len + 26;
+ char_u *s = xmalloc(buflen);
+ snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT
+ globpath((char *)p_pp, s, &ga, 0);
+ xfree(s);
+
+ for (int i = 0; i < ga.ga_len; i++) {
+ char_u *match = ((char_u **)ga.ga_data)[i];
+ s = (char_u *)path_tail((char *)match);
+ memmove(match, s, STRLEN(s) + 1);
+ }
+
+ if (GA_EMPTY(&ga)) {
+ return FAIL;
+ }
+
+ // Sort and remove duplicates which can happen when specifying multiple
+ // directories in dirnames.
+ ga_remove_duplicate_strings(&ga);
+
+ *file = ga.ga_data;
+ *num_file = ga.ga_len;
+ return OK;
+}
+
/// Append string with escaped commas
static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
@@ -1282,3 +1570,901 @@ freeall:
return rtp;
}
#undef NVIM_SIZE
+
+static void cmd_source(char *fname, exarg_T *eap)
+{
+ if (eap != NULL && *fname == NUL) {
+ cmd_source_buffer(eap);
+ } else if (eap != NULL && eap->forceit) {
+ // ":source!": read Normal mode commands
+ // Need to execute the commands directly. This is required at least
+ // for:
+ // - ":g" command busy
+ // - after ":argdo", ":windo" or ":bufdo"
+ // - another command follows
+ // - inside a loop
+ openscript((char_u *)fname, global_busy || listcmd_busy || eap->nextcmd != NULL
+ || eap->cstack->cs_idx >= 0);
+
+ // ":source" read ex commands
+ } else if (do_source(fname, false, DOSO_NONE) == FAIL) {
+ semsg(_(e_notopen), fname);
+ }
+}
+
+/// ":source [{fname}]"
+void ex_source(exarg_T *eap)
+{
+ cmd_source(eap->arg, eap);
+}
+
+/// ":options"
+void ex_options(exarg_T *eap)
+{
+ char buf[500];
+ bool multi_mods = 0;
+
+ buf[0] = NUL;
+ (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods);
+
+ os_setenv("OPTWIN_CMD", buf, 1);
+ cmd_source(SYS_OPTWIN_FILE, NULL);
+}
+
+/// ":source" and associated commands.
+///
+/// @return address holding the next breakpoint line for a source cookie
+linenr_T *source_breakpoint(void *cookie)
+{
+ return &((struct source_cookie *)cookie)->breakpoint;
+}
+
+/// @return the address holding the debug tick for a source cookie.
+int *source_dbg_tick(void *cookie)
+{
+ return &((struct source_cookie *)cookie)->dbg_tick;
+}
+
+/// @return the nesting level for a source cookie.
+int source_level(void *cookie)
+ FUNC_ATTR_PURE
+{
+ return ((struct source_cookie *)cookie)->level;
+}
+
+/// Special function to open a file without handle inheritance.
+/// If possible the handle is closed on exec().
+static FILE *fopen_noinh_readbin(char *filename)
+{
+#ifdef WIN32
+ int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0);
+#else
+ int fd_tmp = os_open(filename, O_RDONLY, 0);
+#endif
+
+ if (fd_tmp < 0) {
+ return NULL;
+ }
+
+ (void)os_set_cloexec(fd_tmp);
+
+ return fdopen(fd_tmp, READBIN);
+}
+
+/// Concatenate VimL line if it starts with a line continuation into a growarray
+/// (excluding the continuation chars and leading whitespace)
+///
+/// @note Growsize of the growarray may be changed to speed up concatenations!
+///
+/// @param ga the growarray to append to
+/// @param init_growsize the starting growsize value of the growarray
+/// @param p pointer to the beginning of the line to consider
+/// @param len the length of this line
+///
+/// @return true if this line did begin with a continuation (the next line
+/// should also be considered, if it exists); false otherwise
+static bool concat_continued_line(garray_T *const ga, const int init_growsize,
+ const char_u *const p, size_t len)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char *const line = (char *)skipwhite_len(p, len);
+ len -= (size_t)((char_u *)line - p);
+ // Skip lines starting with '\" ', concat lines starting with '\'
+ if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) {
+ return true;
+ } else if (len == 0 || line[0] != '\\') {
+ return false;
+ }
+ if (ga->ga_len > init_growsize) {
+ ga_set_growsize(ga, MIN(ga->ga_len, 8000));
+ }
+ ga_concat_len(ga, line + 1, len - 1);
+ return true;
+}
+
+typedef struct {
+ linenr_T curr_lnum;
+ const linenr_T final_lnum;
+} GetBufferLineCookie;
+
+typedef struct {
+ char *buf;
+ size_t offset;
+} GetStrLineCookie;
+
+/// Get one full line from a sourced string (in-memory, no file).
+/// Called by do_cmdline() when it's called from do_source_str().
+///
+/// @return pointer to allocated line, or NULL for end-of-file or
+/// some error.
+static char *get_str_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetStrLineCookie *p = cookie;
+ if (STRLEN(p->buf) <= p->offset) {
+ return NULL;
+ }
+ const char *line = p->buf + p->offset;
+ const char *eol = (char *)skip_to_newline((char_u *)line);
+ garray_T ga;
+ ga_init(&ga, sizeof(char_u), 400);
+ ga_concat_len(&ga, line, (size_t)(eol - line));
+ if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) {
+ while (eol[0] != NUL) {
+ line = eol + 1;
+ const char_u *const next_eol = skip_to_newline((char_u *)line);
+ if (!concat_continued_line(&ga, 400, (char_u *)line, (size_t)(next_eol - (char_u *)line))) {
+ break;
+ }
+ eol = (char *)next_eol;
+ }
+ }
+ ga_append(&ga, NUL);
+ p->offset = (size_t)(eol - p->buf) + 1;
+ return ga.ga_data;
+}
+
+/// Create a new script item and allocate script-local vars. @see new_script_vars
+///
+/// @param name File name of the script. NULL for anonymous :source.
+/// @param[out] sid_out SID of the new item.
+///
+/// @return pointer to the created script item.
+scriptitem_T *new_script_item(char *const name, scid_T *const sid_out)
+{
+ static scid_T last_current_SID = 0;
+ const scid_T sid = ++last_current_SID;
+ if (sid_out != NULL) {
+ *sid_out = sid;
+ }
+ ga_grow(&script_items, sid - script_items.ga_len);
+ while (script_items.ga_len < sid) {
+ script_items.ga_len++;
+ SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
+ SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false;
+ }
+ SCRIPT_ITEM(sid).sn_name = (char_u *)name;
+ new_script_vars(sid); // Allocate the local script variables to use for this script.
+ return &SCRIPT_ITEM(sid);
+}
+
+static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name)
+{
+ char *save_sourcing_name = SOURCING_NAME;
+ linenr_T save_sourcing_lnum = SOURCING_LNUM;
+ char sourcing_name_buf[256];
+ char *sname;
+ if (save_sourcing_name == NULL) {
+ sname = (char *)traceback_name;
+ } else {
+ snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ "%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name,
+ save_sourcing_lnum);
+ sname = sourcing_name_buf;
+ }
+ estack_push(ETYPE_SCRIPT, sname, 0);
+
+ const sctx_T save_current_sctx = current_sctx;
+ if (current_sctx.sc_sid != SID_LUA) {
+ current_sctx.sc_sid = SID_STR;
+ }
+ current_sctx.sc_seq = 0;
+ current_sctx.sc_lnum = save_sourcing_lnum;
+ funccal_entry_T entry;
+ save_funccal(&entry);
+ int retval = do_cmdline(NULL, fgetline, cookie,
+ DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
+ estack_pop();
+ current_sctx = save_current_sctx;
+ restore_funccal();
+ return retval;
+}
+
+static void cmd_source_buffer(const exarg_T *const eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (curbuf == NULL) {
+ return;
+ }
+ garray_T ga;
+ ga_init(&ga, sizeof(char_u), 400);
+ const linenr_T final_lnum = eap->line2;
+ // Copy the contents to be executed.
+ for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) {
+ // Adjust growsize to current length to speed up concatenating many lines.
+ if (ga.ga_len > 400) {
+ ga_set_growsize(&ga, MIN(ga.ga_len, 8000));
+ }
+ ga_concat(&ga, (char *)ml_get(curr_lnum));
+ ga_append(&ga, NL);
+ }
+ ((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL;
+ const GetStrLineCookie cookie = {
+ .buf = ga.ga_data,
+ .offset = 0,
+ };
+ if (curbuf->b_fname
+ && path_with_extension((const char *)curbuf->b_fname, "lua")) {
+ nlua_source_using_linegetter(get_str_line, (void *)&cookie,
+ ":source (no file)");
+ } else {
+ source_using_linegetter((void *)&cookie, get_str_line,
+ ":source (no file)");
+ }
+ ga_clear(&ga);
+}
+
+/// Executes lines in `src` as Ex commands.
+///
+/// @see do_source()
+int do_source_str(const char *cmd, const char *traceback_name)
+{
+ GetStrLineCookie cookie = {
+ .buf = (char *)cmd,
+ .offset = 0,
+ };
+ return source_using_linegetter((void *)&cookie, get_str_line, traceback_name);
+}
+
+/// When fname is a 'lua' file nlua_exec_file() is invoked to source it.
+/// Otherwise reads the file `fname` and executes its lines as Ex commands.
+///
+/// This function may be called recursively!
+///
+/// @see do_source_str
+///
+/// @param fname
+/// @param check_other check for .vimrc and _vimrc
+/// @param is_vimrc DOSO_ value
+///
+/// @return FAIL if file could not be opened, OK otherwise
+int do_source(char *fname, int check_other, int is_vimrc)
+{
+ struct source_cookie cookie;
+ char *p;
+ char *fname_exp;
+ uint8_t *firstline = NULL;
+ int retval = FAIL;
+ int save_debug_break_level = debug_break_level;
+ scriptitem_T *si = NULL;
+ proftime_T wait_start;
+ bool trigger_source_post = false;
+
+ p = expand_env_save(fname);
+ if (p == NULL) {
+ return retval;
+ }
+ fname_exp = fix_fname(p);
+ xfree(p);
+ if (fname_exp == NULL) {
+ return retval;
+ }
+ if (os_isdir((char_u *)fname_exp)) {
+ smsg(_("Cannot source a directory: \"%s\""), fname);
+ goto theend;
+ }
+
+ // Apply SourceCmd autocommands, they should get the file and source it.
+ if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL)
+ && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp,
+ false, curbuf)) {
+ retval = aborting() ? FAIL : OK;
+ if (retval == OK) {
+ // Apply SourcePost autocommands.
+ apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf);
+ }
+ goto theend;
+ }
+
+ // Apply SourcePre autocommands, they may get the file.
+ apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, false, curbuf);
+
+ cookie.fp = fopen_noinh_readbin(fname_exp);
+ if (cookie.fp == NULL && check_other) {
+ // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa,
+ // and ".exrc" by "_exrc" or vice versa.
+ p = path_tail(fname_exp);
+ if ((*p == '.' || *p == '_')
+ && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) {
+ *p = (*p == '_') ? '.' : '_';
+ cookie.fp = fopen_noinh_readbin(fname_exp);
+ }
+ }
+
+ if (cookie.fp == NULL) {
+ if (p_verbose > 1) {
+ verbose_enter();
+ if (SOURCING_NAME == NULL) {
+ smsg(_("could not source \"%s\""), fname);
+ } else {
+ smsg(_("line %" PRId64 ": could not source \"%s\""),
+ (int64_t)SOURCING_LNUM, fname);
+ }
+ verbose_leave();
+ }
+ goto theend;
+ }
+
+ // The file exists.
+ // - In verbose mode, give a message.
+ // - For a vimrc file, may want to call vimrc_found().
+ if (p_verbose > 1) {
+ verbose_enter();
+ if (SOURCING_NAME == NULL) {
+ smsg(_("sourcing \"%s\""), fname);
+ } else {
+ smsg(_("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname);
+ }
+ verbose_leave();
+ }
+ if (is_vimrc == DOSO_VIMRC) {
+ vimrc_found(fname_exp, "MYVIMRC");
+ }
+
+#ifdef USE_CRNL
+ // If no automatic file format: Set default to CR-NL.
+ if (*p_ffs == NUL) {
+ cookie.fileformat = EOL_DOS;
+ } else {
+ cookie.fileformat = EOL_UNKNOWN;
+ }
+ cookie.error = false;
+#endif
+
+ cookie.nextline = NULL;
+ cookie.sourcing_lnum = 0;
+ cookie.finished = false;
+
+ // Check if this script has a breakpoint.
+ cookie.breakpoint = dbg_find_breakpoint(true, (char_u *)fname_exp, (linenr_T)0);
+ cookie.fname = fname_exp;
+ cookie.dbg_tick = debug_tick;
+
+ cookie.level = ex_nesting_level;
+
+ // start measuring script load time if --startuptime was passed and
+ // time_fd was successfully opened afterwards.
+ proftime_T rel_time;
+ proftime_T start_time;
+ FILE * const l_time_fd = time_fd;
+ if (l_time_fd != NULL) {
+ time_push(&rel_time, &start_time);
+ }
+
+ const int l_do_profiling = do_profiling;
+ if (l_do_profiling == PROF_YES) {
+ prof_child_enter(&wait_start); // entering a child now
+ }
+
+ // Don't use local function variables, if called from a function.
+ // Also starts profiling timer for nested script.
+ funccal_entry_T funccalp_entry;
+ save_funccal(&funccalp_entry);
+
+ const sctx_T save_current_sctx = current_sctx;
+ si = get_current_script_id(&fname_exp, &current_sctx);
+
+ // Keep the sourcing name/lnum, for recursive calls.
+ estack_push(ETYPE_SCRIPT, (char *)si->sn_name, 0);
+
+ if (l_do_profiling == PROF_YES) {
+ bool forceit = false;
+
+ // Check if we do profiling for this script.
+ if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) {
+ profile_init(si);
+ si->sn_pr_force = forceit;
+ }
+ if (si->sn_prof_on) {
+ si->sn_pr_count++;
+ si->sn_pr_start = profile_start();
+ si->sn_pr_children = profile_zero();
+ }
+ }
+
+ cookie.conv.vc_type = CONV_NONE; // no conversion
+
+ if (path_with_extension((const char *)fname_exp, "lua")) {
+ const sctx_T current_sctx_backup = current_sctx;
+ current_sctx.sc_sid = SID_LUA;
+ current_sctx.sc_lnum = 0;
+ // Source the file as lua
+ nlua_exec_file((const char *)fname_exp);
+ current_sctx = current_sctx_backup;
+ } else {
+ // Read the first line so we can check for a UTF-8 BOM.
+ firstline = (uint8_t *)getsourceline(0, (void *)&cookie, 0, true);
+ if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef
+ && firstline[1] == 0xbb && firstline[2] == 0xbf) {
+ // Found BOM; setup conversion, skip over BOM and recode the line.
+ convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc);
+ p = (char *)string_convert(&cookie.conv, (char_u *)firstline + 3, NULL);
+ if (p == NULL) {
+ p = xstrdup((char *)firstline + 3);
+ }
+ xfree(firstline);
+ firstline = (uint8_t *)p;
+ }
+ // Call do_cmdline, which will call getsourceline() to get the lines.
+ do_cmdline((char *)firstline, getsourceline, (void *)&cookie,
+ DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT);
+ }
+ retval = OK;
+
+ if (l_do_profiling == PROF_YES) {
+ // Get "si" again, "script_items" may have been reallocated.
+ si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ if (si->sn_prof_on) {
+ si->sn_pr_start = profile_end(si->sn_pr_start);
+ si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start);
+ si->sn_pr_total = profile_add(si->sn_pr_total, si->sn_pr_start);
+ si->sn_pr_self = profile_self(si->sn_pr_self, si->sn_pr_start,
+ si->sn_pr_children);
+ }
+ }
+
+ if (got_int) {
+ emsg(_(e_interr));
+ }
+ estack_pop();
+ if (p_verbose > 1) {
+ verbose_enter();
+ smsg(_("finished sourcing %s"), fname);
+ if (SOURCING_NAME != NULL) {
+ smsg(_("continuing in %s"), SOURCING_NAME);
+ }
+ verbose_leave();
+ }
+
+ if (l_time_fd != NULL) {
+ vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname);
+ time_msg((char *)IObuff, &start_time);
+ time_pop(rel_time);
+ }
+
+ if (!got_int) {
+ trigger_source_post = true;
+ }
+
+ // After a "finish" in debug mode, need to break at first command of next
+ // sourced file.
+ if (save_debug_break_level > ex_nesting_level
+ && debug_break_level == ex_nesting_level) {
+ debug_break_level++;
+ }
+
+ current_sctx = save_current_sctx;
+ restore_funccal();
+ if (l_do_profiling == PROF_YES) {
+ prof_child_exit(&wait_start); // leaving a child now
+ }
+ fclose(cookie.fp);
+ xfree(cookie.nextline);
+ xfree(firstline);
+ convert_setup(&cookie.conv, NULL, NULL);
+
+ if (trigger_source_post) {
+ apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf);
+ }
+
+theend:
+ xfree(fname_exp);
+ return retval;
+}
+
+/// Check if fname was sourced before to finds its SID.
+/// If it's new, generate a new SID.
+///
+/// @param[in,out] fnamep pointer to file path of script
+/// @param[out] ret_sctx sctx of this script
+scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx)
+{
+ static int last_current_SID_seq = 0;
+
+ sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq,
+ .sc_lnum = 0,
+ .sc_sid = 0 };
+ scriptitem_T *si = NULL;
+
+ assert(script_items.ga_len >= 0);
+ for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) {
+ // We used to check inode here, but that doesn't work:
+ // - If a script is edited and written, it may get a different
+ // inode number, even though to the user it is the same script.
+ // - If a script is deleted and another script is written, with a
+ // different name, the inode may be re-used.
+ si = &SCRIPT_ITEM(script_sctx.sc_sid);
+ if (si->sn_name != NULL && FNAMECMP(si->sn_name, *fnamep) == 0) {
+ // Found it!
+ break;
+ }
+ }
+ if (script_sctx.sc_sid == 0) {
+ si = new_script_item(*fnamep, &script_sctx.sc_sid);
+ *fnamep = xstrdup((char *)si->sn_name);
+ }
+ if (ret_sctx != NULL) {
+ *ret_sctx = script_sctx;
+ }
+
+ return si;
+}
+
+/// ":scriptnames"
+void ex_scriptnames(exarg_T *eap)
+{
+ if (eap->addr_count > 0) {
+ // :script {scriptId}: edit the script
+ if (eap->line2 < 1 || eap->line2 > script_items.ga_len) {
+ emsg(_(e_invarg));
+ } else {
+ eap->arg = (char *)SCRIPT_ITEM(eap->line2).sn_name;
+ do_exedit(eap, NULL);
+ }
+ return;
+ }
+
+ for (int i = 1; i <= script_items.ga_len && !got_int; i++) {
+ if (SCRIPT_ITEM(i).sn_name != NULL) {
+ home_replace(NULL, (char *)SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true);
+ vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff);
+ if (!message_filtered(IObuff)) {
+ msg_putchar('\n');
+ msg_outtrans((char *)IObuff);
+ line_breakcheck();
+ }
+ }
+ }
+}
+
+#if defined(BACKSLASH_IN_FILENAME)
+/// Fix slashes in the list of script names for 'shellslash'.
+void scriptnames_slash_adjust(void)
+{
+ for (int i = 1; i <= script_items.ga_len; i++) {
+ if (SCRIPT_ITEM(i).sn_name != NULL) {
+ slash_adjust(SCRIPT_ITEM(i).sn_name);
+ }
+ }
+}
+
+#endif
+
+/// Get a pointer to a script name. Used for ":verbose set".
+/// Message appended to "Last set from "
+char_u *get_scriptname(LastSet last_set, bool *should_free)
+{
+ *should_free = false;
+
+ switch (last_set.script_ctx.sc_sid) {
+ case SID_MODELINE:
+ return (char_u *)_("modeline");
+ case SID_CMDARG:
+ return (char_u *)_("--cmd argument");
+ case SID_CARG:
+ return (char_u *)_("-c argument");
+ case SID_ENV:
+ return (char_u *)_("environment variable");
+ case SID_ERROR:
+ return (char_u *)_("error handler");
+ case SID_WINLAYOUT:
+ return (char_u *)_("changed window size");
+ case SID_LUA:
+ return (char_u *)_("Lua");
+ case SID_API_CLIENT:
+ snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id);
+ return IObuff;
+ case SID_STR:
+ return (char_u *)_("anonymous :source");
+ default: {
+ char *const sname = (char *)SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name;
+ if (sname == NULL) {
+ snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"),
+ last_set.script_ctx.sc_sid);
+ return IObuff;
+ }
+
+ *should_free = true;
+ return (char_u *)home_replace_save(NULL, sname);
+ }
+ }
+}
+
+#if defined(EXITFREE)
+void free_scriptnames(void)
+{
+ profile_reset();
+
+# define FREE_SCRIPTNAME(item) xfree((item)->sn_name)
+ GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME);
+}
+#endif
+
+linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
+ FUNC_ATTR_PURE
+{
+ return fgetline == getsourceline
+ ? ((struct source_cookie *)cookie)->sourcing_lnum
+ : SOURCING_LNUM;
+}
+
+/// Get one full line from a sourced file.
+/// Called by do_cmdline() when it's called from do_source().
+///
+/// @return pointer to the line in allocated memory, or NULL for end-of-file or
+/// some error.
+char *getsourceline(int c, void *cookie, int indent, bool do_concat)
+{
+ struct source_cookie *sp = (struct source_cookie *)cookie;
+ char *line;
+ char *p;
+
+ // If breakpoints have been added/deleted need to check for it.
+ if (sp->dbg_tick < debug_tick) {
+ sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM);
+ sp->dbg_tick = debug_tick;
+ }
+ if (do_profiling == PROF_YES) {
+ script_line_end();
+ }
+ // Set the current sourcing line number.
+ SOURCING_LNUM = sp->sourcing_lnum + 1;
+ // Get current line. If there is a read-ahead line, use it, otherwise get
+ // one now.
+ if (sp->finished) {
+ line = NULL;
+ } else if (sp->nextline == NULL) {
+ line = get_one_sourceline(sp);
+ } else {
+ line = sp->nextline;
+ sp->nextline = NULL;
+ sp->sourcing_lnum++;
+ }
+ if (line != NULL && do_profiling == PROF_YES) {
+ script_line_start();
+ }
+
+ // Only concatenate lines starting with a \ when 'cpoptions' doesn't
+ // contain the 'C' flag.
+ if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
+ // compensate for the one line read-ahead
+ sp->sourcing_lnum--;
+
+ // Get the next line and concatenate it when it starts with a
+ // backslash. We always need to read the next line, keep it in
+ // sp->nextline.
+ // Also check for a comment in between continuation lines: "\ .
+ sp->nextline = get_one_sourceline(sp);
+ if (sp->nextline != NULL
+ && (*(p = skipwhite(sp->nextline)) == '\\'
+ || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) {
+ garray_T ga;
+
+ ga_init(&ga, (int)sizeof(char_u), 400);
+ ga_concat(&ga, line);
+ while (sp->nextline != NULL
+ && concat_continued_line(&ga, 400, (char_u *)sp->nextline,
+ STRLEN(sp->nextline))) {
+ xfree(sp->nextline);
+ sp->nextline = get_one_sourceline(sp);
+ }
+ ga_append(&ga, NUL);
+ xfree(line);
+ line = ga.ga_data;
+ }
+ }
+
+ if (line != NULL && sp->conv.vc_type != CONV_NONE) {
+ char *s;
+
+ // Convert the encoding of the script line.
+ s = (char *)string_convert(&sp->conv, (char_u *)line, NULL);
+ if (s != NULL) {
+ xfree(line);
+ line = s;
+ }
+ }
+
+ // Did we encounter a breakpoint?
+ if (sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) {
+ dbg_breakpoint((char_u *)sp->fname, SOURCING_LNUM);
+ // Find next breakpoint.
+ sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM);
+ sp->dbg_tick = debug_tick;
+ }
+
+ return line;
+}
+
+static char *get_one_sourceline(struct source_cookie *sp)
+{
+ garray_T ga;
+ int len;
+ int c;
+ char *buf;
+#ifdef USE_CRNL
+ int has_cr; // CR-LF found
+#endif
+ bool have_read = false;
+
+ // use a growarray to store the sourced line
+ ga_init(&ga, 1, 250);
+
+ // Loop until there is a finished line (or end-of-file).
+ sp->sourcing_lnum++;
+ for (;;) {
+ // make room to read at least 120 (more) characters
+ ga_grow(&ga, 120);
+ buf = ga.ga_data;
+
+retry:
+ errno = 0;
+ if (fgets(buf + ga.ga_len, ga.ga_maxlen - ga.ga_len,
+ sp->fp) == NULL) {
+ if (errno == EINTR) {
+ goto retry;
+ }
+
+ break;
+ }
+ len = ga.ga_len + (int)STRLEN(buf + ga.ga_len);
+#ifdef USE_CRNL
+ // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the
+ // CTRL-Z by its own, or after a NL.
+ if ((len == 1 || (len >= 2 && buf[len - 2] == '\n'))
+ && sp->fileformat == EOL_DOS
+ && buf[len - 1] == Ctrl_Z) {
+ buf[len - 1] = NUL;
+ break;
+ }
+#endif
+
+ have_read = true;
+ ga.ga_len = len;
+
+ // If the line was longer than the buffer, read more.
+ if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') {
+ continue;
+ }
+
+ if (len >= 1 && buf[len - 1] == '\n') { // remove trailing NL
+#ifdef USE_CRNL
+ has_cr = (len >= 2 && buf[len - 2] == '\r');
+ if (sp->fileformat == EOL_UNKNOWN) {
+ if (has_cr) {
+ sp->fileformat = EOL_DOS;
+ } else {
+ sp->fileformat = EOL_UNIX;
+ }
+ }
+
+ if (sp->fileformat == EOL_DOS) {
+ if (has_cr) { // replace trailing CR
+ buf[len - 2] = '\n';
+ len--;
+ ga.ga_len--;
+ } else { // lines like ":map xx yy^M" will have failed
+ if (!sp->error) {
+ msg_source(HL_ATTR(HLF_W));
+ emsg(_("W15: Warning: Wrong line separator, ^M may be missing"));
+ }
+ sp->error = true;
+ sp->fileformat = EOL_UNIX;
+ }
+ }
+#endif
+ // The '\n' is escaped if there is an odd number of ^V's just
+ // before it, first set "c" just before the 'V's and then check
+ // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo
+ for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {}
+ if ((len & 1) != (c & 1)) { // escaped NL, read more
+ sp->sourcing_lnum++;
+ continue;
+ }
+
+ buf[len - 1] = NUL; // remove the NL
+ }
+
+ // Check for ^C here now and then, so recursive :so can be broken.
+ line_breakcheck();
+ break;
+ }
+
+ if (have_read) {
+ return ga.ga_data;
+ }
+
+ xfree(ga.ga_data);
+ return NULL;
+}
+
+/// ":scriptencoding": Set encoding conversion for a sourced script.
+/// Without the multi-byte feature it's simply ignored.
+void ex_scriptencoding(exarg_T *eap)
+{
+ struct source_cookie *sp;
+ char *name;
+
+ if (!getline_equal(eap->getline, eap->cookie, getsourceline)) {
+ emsg(_("E167: :scriptencoding used outside of a sourced file"));
+ return;
+ }
+
+ if (*eap->arg != NUL) {
+ name = (char *)enc_canonize((char_u *)eap->arg);
+ } else {
+ name = eap->arg;
+ }
+
+ // Setup for conversion from the specified encoding to 'encoding'.
+ sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie);
+ convert_setup(&sp->conv, (char_u *)name, p_enc);
+
+ if (name != eap->arg) {
+ xfree(name);
+ }
+}
+
+/// ":finish": Mark a sourced file as finished.
+void ex_finish(exarg_T *eap)
+{
+ if (getline_equal(eap->getline, eap->cookie, getsourceline)) {
+ do_finish(eap, false);
+ } else {
+ emsg(_("E168: :finish used outside of a sourced file"));
+ }
+}
+
+/// Mark a sourced file as finished. Possibly makes the ":finish" pending.
+/// Also called for a pending finish at the ":endtry" or after returning from
+/// an extra do_cmdline(). "reanimate" is used in the latter case.
+void do_finish(exarg_T *eap, int reanimate)
+{
+ int idx;
+
+ if (reanimate) {
+ ((struct source_cookie *)getline_cookie(eap->getline,
+ eap->cookie))->finished = false;
+ }
+
+ // Cleanup (and deactivate) conditionals, but stop when a try conditional
+ // not in its finally clause (which then is to be executed next) is found.
+ // In this case, make the ":finish" pending for execution at the ":endtry".
+ // Otherwise, finish normally.
+ idx = cleanup_conditionals(eap->cstack, 0, true);
+ if (idx >= 0) {
+ eap->cstack->cs_pending[idx] = CSTP_FINISH;
+ report_make_pending(CSTP_FINISH, NULL);
+ } else {
+ ((struct source_cookie *)getline_cookie(eap->getline,
+ eap->cookie))->finished = true;
+ }
+}
+
+/// @return true when a sourced file had the ":finish" command: Don't give error
+/// message for missing ":endif".
+/// false when not sourcing a file.
+bool source_finished(LineGetter fgetline, void *cookie)
+{
+ return getline_equal(fgetline, cookie, getsourceline)
+ && ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished;
+}
diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h
index d83ec00185..a255c6c096 100644
--- a/src/nvim/runtime.h
+++ b/src/nvim/runtime.h
@@ -3,7 +3,76 @@
#include <stdbool.h>
-#include "nvim/ex_docmd.h"
+#include "nvim/autocmd.h"
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_eval_defs.h"
+
+typedef enum {
+ ETYPE_TOP, ///< toplevel
+ ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx
+ ETYPE_UFUNC, ///< user function, use es_info.ufunc
+ ETYPE_AUCMD, ///< autocomand, use es_info.aucmd
+ ETYPE_MODELINE, ///< modeline, use es_info.sctx
+ ETYPE_EXCEPT, ///< exception, use es_info.exception
+ ETYPE_ARGS, ///< command line argument
+ ETYPE_ENV, ///< environment variable
+ ETYPE_INTERNAL, ///< internal operation
+ ETYPE_SPELL, ///< loading spell file
+} etype_T;
+
+/// Entry in the execution stack "exestack".
+typedef struct {
+ linenr_T es_lnum; ///< replaces "sourcing_lnum"
+ char *es_name; ///< replaces "sourcing_name"
+ etype_T es_type;
+ union {
+ sctx_T *sctx; ///< script and modeline info
+ ufunc_T *ufunc; ///< function info
+ AutoPatCmd *aucmd; ///< autocommand info
+ except_T *except; ///< exception info
+ } es_info;
+} estack_T;
+
+/// Stack of execution contexts. Each entry is an estack_T.
+/// Current context is at ga_len - 1.
+extern garray_T exestack;
+/// name of error message source
+#define SOURCING_NAME (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_name)
+/// line number in the message source or zero
+#define SOURCING_LNUM (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_lnum)
+
+/// Argument for estack_sfile().
+typedef enum {
+ ESTACK_NONE,
+ ESTACK_SFILE,
+ ESTACK_STACK,
+} estack_arg_T;
+
+typedef struct scriptitem_S {
+ char_u *sn_name;
+ bool sn_prof_on; ///< true when script is/was profiled
+ bool sn_pr_force; ///< forceit: profile functions in this script
+ proftime_T sn_pr_child; ///< time set when going into first child
+ int sn_pr_nest; ///< nesting for sn_pr_child
+ // profiling the script as a whole
+ int sn_pr_count; ///< nr of times sourced
+ proftime_T sn_pr_total; ///< time spent in script + children
+ proftime_T sn_pr_self; ///< time spent in script itself
+ proftime_T sn_pr_start; ///< time at script start
+ proftime_T sn_pr_children; ///< time in children after script start
+ // profiling the script per line
+ garray_T sn_prl_ga; ///< things stored for every line
+ proftime_T sn_prl_start; ///< start time for current line
+ proftime_T sn_prl_children; ///< time spent in children for this line
+ proftime_T sn_prl_wait; ///< wait start time for current line
+ linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
+ int sn_prl_execed; ///< line being timed was executed
+} scriptitem_T;
+
+/// Growarray to store info about already sourced scripts.
+extern garray_T script_items;
+#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
typedef void (*DoInRuntimepathCB)(char *, void *);
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 6f4400e531..b343b167f8 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -1,629 +1,52 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-// screen.c: code for displaying on the screen
-//
+// screen.c: Lower level code for displaying on the screen.
+// grid.c contains some other lower-level code.
+
// Output to the screen (console, terminal emulator or GUI window) is minimized
// by remembering what is already on the screen, and only updating the parts
// that changed.
-//
-// The grid_*() functions write to the screen and handle updating grid->lines[].
-//
-// update_screen() is the function that updates all windows and status lines.
-// It is called from the main loop when must_redraw is non-zero. It may be
-// called from other places when an immediate screen update is needed.
-//
-// The part of the buffer that is displayed in a window is set with:
-// - w_topline (first buffer line in window)
-// - w_topfill (filler lines above the first line)
-// - w_leftcol (leftmost window cell in window),
-// - w_skipcol (skipped window cells of first line)
-//
-// Commands that only move the cursor around in a window, do not need to take
-// action to update the display. The main loop will check if w_topline is
-// valid and update it (scroll the window) when needed.
-//
-// Commands that scroll a window change w_topline and must call
-// check_cursor() to move the cursor into the visible part of the window, and
-// call redraw_later(wp, VALID) to have the window displayed by update_screen()
-// later.
-//
-// Commands that change text in the buffer must call changed_bytes() or
-// changed_lines() to mark the area that changed and will require updating
-// later. The main loop will call update_screen(), which will update each
-// window that shows the changed buffer. This assumes text above the change
-// can remain displayed as it is. Text after the change may need updating for
-// scrolling, folding and syntax highlighting.
-//
-// Commands that change how a window is displayed (e.g., setting 'list') or
-// invalidate the contents of a window in another way (e.g., change fold
-// settings), must call redraw_later(wp, NOT_VALID) to have the whole window
-// redisplayed by update_screen() later.
-//
-// Commands that change how a buffer is displayed (e.g., setting 'tabstop')
-// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the
-// buffer redisplayed by update_screen() later.
-//
-// Commands that change highlighting and possibly cause a scroll too must call
-// redraw_later(wp, SOME_VALID) to update the whole window but still use
-// scrolling to avoid redrawing everything. But the length of displayed lines
-// must not change, use NOT_VALID then.
-//
-// Commands that move the window position must call redraw_later(wp, NOT_VALID).
-// TODO(neovim): should minimize redrawing by scrolling when possible.
-//
-// Commands that change everything (e.g., resizing the screen) must call
-// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR).
-//
-// Things that are handled indirectly:
-// - When messages scroll the screen up, msg_scrolled will be set and
-// update_screen() called to redraw.
-///
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
-#include "nvim/api/extmark.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/api/ui.h"
-#include "nvim/api/vim.h"
-#include "nvim/arabic.h"
-#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
-#include "nvim/cursor_shape.h"
-#include "nvim/decoration.h"
-#include "nvim/decoration_provider.h"
-#include "nvim/diff.h"
-#include "nvim/edit.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/indent.h"
-#include "nvim/insexpand.h"
-#include "nvim/lib/kvec.h"
-#include "nvim/log.h"
-#include "nvim/lua/executor.h"
-#include "nvim/main.h"
-#include "nvim/mark.h"
-#include "nvim/match.h"
-#include "nvim/mbyte.h"
-#include "nvim/memline.h"
-#include "nvim/memory.h"
#include "nvim/menu.h"
-#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/normal.h"
#include "nvim/option.h"
-#include "nvim/os/time.h"
-#include "nvim/os_unix.h"
-#include "nvim/path.h"
-#include "nvim/plines.h"
-#include "nvim/popupmnu.h"
-#include "nvim/quickfix.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/search.h"
-#include "nvim/sign.h"
-#include "nvim/spell.h"
#include "nvim/state.h"
-#include "nvim/strings.h"
-#include "nvim/syntax.h"
-#include "nvim/terminal.h"
-#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
-#include "nvim/version.h"
-#include "nvim/vim.h"
#include "nvim/window.h"
-#define MB_FILLER_CHAR '<' /* character used when a double-width character
- * doesn't fit. */
-
-static match_T search_hl; // used for 'hlsearch' highlight matching
-
-// for line_putchar. Contains the state that needs to be remembered from
-// putting one character to the next.
-typedef struct {
- const char *p;
- int prev_c; // previous Arabic character
- int prev_c1; // first composing char for prev_c
-} LineState;
-#define LINE_STATE(p) { p, 0, 0 }
-
-/// Whether to call "ui_call_grid_resize" in win_grid_alloc
-static bool send_grid_resize = false;
-
-static bool conceal_cursor_used = false;
-
-static bool redraw_popupmenu = false;
-static bool msg_grid_invalid = false;
-
-static bool resizing = false;
-
-typedef struct {
- NS ns_id;
- uint64_t mark_id;
- int win_row;
- int win_col;
-} WinExtmark;
-static kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE);
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "screen.c.generated.h"
#endif
-static char *provider_err = NULL;
-
-/// Redraw a window later, with update_screen(type).
-///
-/// Set must_redraw only if not already set to a higher value.
-/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing.
-void redraw_later(win_T *wp, int type)
- FUNC_ATTR_NONNULL_ALL
-{
- if (!exiting && wp->w_redr_type < type) {
- wp->w_redr_type = type;
- if (type >= NOT_VALID) {
- wp->w_lines_valid = 0;
- }
- if (must_redraw < type) { // must_redraw is the maximum of all windows
- must_redraw = type;
- }
- }
-}
-
-/*
- * Mark all windows to be redrawn later.
- */
-void redraw_all_later(int type)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- redraw_later(wp, type);
- }
- // This may be needed when switching tabs.
- if (must_redraw < type) {
- must_redraw = type;
- }
-}
-
-void screen_invalidate_highlights(void)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- redraw_later(wp, NOT_VALID);
- wp->w_grid_alloc.valid = false;
- }
-}
-
-/*
- * Mark all windows that are editing the current buffer to be updated later.
- */
-void redraw_curbuf_later(int type)
-{
- redraw_buf_later(curbuf, type);
-}
-
-void redraw_buf_later(buf_T *buf, int type)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf) {
- redraw_later(wp, type);
- }
- }
-}
-
-void redraw_buf_line_later(buf_T *buf, linenr_T line)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf
- && line >= wp->w_topline && line < wp->w_botline) {
- redrawWinline(wp, line);
- }
- }
-}
-
-void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf
- && lastline >= wp->w_topline && firstline < wp->w_botline) {
- if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) {
- wp->w_redraw_top = firstline;
- }
- if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) {
- wp->w_redraw_bot = lastline;
- }
- redraw_later(wp, VALID);
- }
- }
-}
-
-/*
- * Changed something in the current window, at buffer line "lnum", that
- * requires that line and possibly other lines to be redrawn.
- * Used when entering/leaving Insert mode with the cursor on a folded line.
- * Used to remove the "$" from a change command.
- * Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot
- * may become invalid and the whole window will have to be redrawn.
- */
-void redrawWinline(win_T *wp, linenr_T lnum)
- FUNC_ATTR_NONNULL_ALL
-{
- if (lnum >= wp->w_topline
- && lnum < wp->w_botline) {
- if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) {
- wp->w_redraw_top = lnum;
- }
- if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) {
- wp->w_redraw_bot = lnum;
- }
- redraw_later(wp, VALID);
- }
-}
-
-/// called when the status bars for the buffer 'buf' need to be updated
-void redraw_buf_status_later(buf_T *buf)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf && (wp->w_status_height || (wp == curwin && global_stl_height())
- || wp->w_winbar_height)) {
- wp->w_redr_status = true;
- if (must_redraw < VALID) {
- must_redraw = VALID;
- }
- }
- }
-}
-
-void redraw_win_signcol(win_T *wp)
-{
- // If we can compute a change in the automatic sizing of the sign column
- // under 'signcolumn=auto:X' and signs currently placed in the buffer, better
- // figuring it out here so we can redraw the entire screen for it.
- int scwidth = wp->w_scwidth;
- wp->w_scwidth = win_signcol_count(wp);
- if (wp->w_scwidth != scwidth) {
- changed_line_abv_curs_win(wp);
- }
-}
-
-/// Update all windows that are editing the current buffer.
-void update_curbuf(int type)
-{
- redraw_curbuf_later(type);
- update_screen(type);
-}
-
-/// Redraw the parts of the screen that is marked for redraw.
-///
-/// Most code shouldn't call this directly, rather use redraw_later() and
-/// and redraw_all_later() to mark parts of the screen as needing a redraw.
-///
-/// @param type set to a NOT_VALID to force redraw of entire screen
-int update_screen(int type)
-{
- static bool did_intro = false;
- bool is_stl_global = global_stl_height() > 0;
-
- // Don't do anything if the screen structures are (not yet) valid.
- // A VimResized autocmd can invoke redrawing in the middle of a resize,
- // which would bypass the checks in screen_resize for popupmenu etc.
- if (!default_grid.chars || resizing) {
- return FAIL;
- }
-
- // May have postponed updating diffs.
- if (need_diff_redraw) {
- diff_redraw(true);
- }
-
- if (must_redraw) {
- if (type < must_redraw) { // use maximal type
- type = must_redraw;
- }
-
- // must_redraw is reset here, so that when we run into some weird
- // reason to redraw while busy redrawing (e.g., asynchronous
- // scrolling), or update_topline() in win_update() will cause a
- // scroll, or a decoration provider requires a redraw, the screen
- // will be redrawn later or in win_update().
- must_redraw = 0;
- }
-
- // Need to update w_lines[].
- if (curwin->w_lines_valid == 0 && type < NOT_VALID) {
- type = NOT_VALID;
- }
-
- /* Postpone the redrawing when it's not needed and when being called
- * recursively. */
- if (!redrawing() || updating_screen) {
- must_redraw = type;
- if (type > INVERTED_ALL) {
- curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now
- }
- return FAIL;
- }
- updating_screen = 1;
-
- display_tick++; // let syntax code know we're in a next round of
- // display updating
-
- // Tricky: vim code can reset msg_scrolled behind our back, so need
- // separate bookkeeping for now.
- if (msg_did_scroll) {
- msg_did_scroll = false;
- msg_scrolled_at_flush = 0;
- }
-
- if (type >= CLEAR || !default_grid.valid) {
- ui_comp_set_screen_valid(false);
- }
-
- // if the screen was scrolled up when displaying a message, scroll it down
- if (msg_scrolled || msg_grid_invalid) {
- clear_cmdline = true;
- int valid = MAX(Rows - msg_scrollsize(), 0);
- if (msg_grid.chars) {
- // non-displayed part of msg_grid is considered invalid.
- for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) {
- grid_clear_line(&msg_grid, msg_grid.line_offset[i],
- msg_grid.cols, false);
- }
- }
- if (msg_use_msgsep()) {
- msg_grid.throttled = false;
- // CLEAR is already handled
- if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) {
- ui_comp_set_screen_valid(false);
- for (int i = valid; i < Rows - p_ch; i++) {
- grid_clear_line(&default_grid, default_grid.line_offset[i],
- Columns, false);
- }
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_floating) {
- continue;
- }
- if (W_ENDROW(wp) > valid) {
- wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID);
- }
- if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) {
- wp->w_redr_status = true;
- }
- }
- if (is_stl_global && Rows - p_ch - 1 > valid) {
- curwin->w_redr_status = true;
- }
- }
- msg_grid_set_pos(Rows - (int)p_ch, false);
- msg_grid_invalid = false;
- } else if (msg_scrolled > Rows - 5) { // clearing is faster
- type = CLEAR;
- } else if (type != CLEAR) {
- check_for_delay(false);
- grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns);
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_floating) {
- continue;
- }
- if (wp->w_winrow < msg_scrolled) {
- if (W_ENDROW(wp) > msg_scrolled
- && wp->w_redr_type < REDRAW_TOP
- && wp->w_lines_valid > 0
- && wp->w_topline == wp->w_lines[0].wl_lnum) {
- wp->w_upd_rows = msg_scrolled - wp->w_winrow;
- wp->w_redr_type = REDRAW_TOP;
- } else {
- wp->w_redr_type = NOT_VALID;
- if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) {
- wp->w_redr_status = true;
- }
- }
- }
- }
- if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) {
- curwin->w_redr_status = true;
- }
- redraw_cmdline = true;
- redraw_tabline = true;
- }
- msg_scrolled = 0;
- msg_scrolled_at_flush = 0;
- need_wait_return = false;
- }
-
- win_ui_flush();
- msg_ext_check_clear();
-
- // reset cmdline_row now (may have been changed temporarily)
- compute_cmdrow();
-
- bool hl_changed = false;
- // Check for changed highlighting
- if (need_highlight_changed) {
- highlight_changed();
- hl_changed = true;
- }
-
- if (type == CLEAR) { // first clear screen
- screenclear(); // will reset clear_cmdline
- cmdline_screen_cleared(); // clear external cmdline state
- type = NOT_VALID;
- // must_redraw may be set indirectly, avoid another redraw later
- must_redraw = 0;
- } else if (!default_grid.valid) {
- grid_invalidate(&default_grid);
- default_grid.valid = true;
- }
-
- // After disabling msgsep the grid might not have been deallocated yet,
- // hence we also need to check msg_grid.chars
- if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) {
- grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0);
- }
-
- ui_comp_set_screen_valid(true);
-
- DecorProviders providers;
- decor_providers_start(&providers, type, &provider_err);
-
- // "start" callback could have changed highlights for global elements
- if (win_check_ns_hl(NULL)) {
- redraw_cmdline = true;
- redraw_tabline = true;
- }
-
- if (clear_cmdline) { // going to clear cmdline (done below)
- check_for_delay(false);
- }
-
- /* Force redraw when width of 'number' or 'relativenumber' column
- * changes. */
- if (curwin->w_redr_type < NOT_VALID
- && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu)
- ? number_width(curwin) : 0)) {
- curwin->w_redr_type = NOT_VALID;
- }
-
- /*
- * Only start redrawing if there is really something to do.
- */
- if (type == INVERTED) {
- update_curswant();
- }
- if (curwin->w_redr_type < type
- && !((type == VALID
- && curwin->w_lines[0].wl_valid
- && curwin->w_topfill == curwin->w_old_topfill
- && curwin->w_botfill == curwin->w_old_botfill
- && curwin->w_topline == curwin->w_lines[0].wl_lnum)
- || (type == INVERTED
- && VIsual_active
- && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum
- && curwin->w_old_visual_mode == VIsual_mode
- && (curwin->w_valid & VALID_VIRTCOL)
- && curwin->w_old_curswant == curwin->w_curswant)
- )) {
- curwin->w_redr_type = type;
- }
-
- // Redraw the tab pages line if needed.
- if (redraw_tabline || type >= NOT_VALID) {
- update_window_hl(curwin, type >= NOT_VALID);
- FOR_ALL_TABS(tp) {
- if (tp != curtab) {
- update_window_hl(tp->tp_curwin, type >= NOT_VALID);
- }
- }
- draw_tabline();
- }
-
- /*
- * Correct stored syntax highlighting info for changes in each displayed
- * buffer. Each buffer must only be done once.
- */
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- update_window_hl(wp, type >= NOT_VALID || hl_changed);
-
- buf_T *buf = wp->w_buffer;
- if (buf->b_mod_set) {
- if (buf->b_mod_tick_syn < display_tick
- && syntax_present(wp)) {
- syn_stack_apply_changes(buf);
- buf->b_mod_tick_syn = display_tick;
- }
-
- if (buf->b_mod_tick_decor < display_tick) {
- decor_providers_invoke_buf(buf, &providers, &provider_err);
- buf->b_mod_tick_decor = display_tick;
- }
- }
- }
-
- /*
- * Go from top to bottom through the windows, redrawing the ones that need
- * it.
- */
- bool did_one = false;
- search_hl.rm.regprog = NULL;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) {
- grid_invalidate(&wp->w_grid_alloc);
- wp->w_redr_type = NOT_VALID;
- }
-
- // reallocate grid if needed.
- win_grid_alloc(wp);
-
- if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) {
- win_redr_border(wp);
- }
+static char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'");
+static char e_conflicts_with_value_of_fillchars[] = N_("E835: Conflicts with value of 'fillchars'");
- if (wp->w_redr_type != 0) {
- if (!did_one) {
- did_one = true;
- start_search_hl();
- }
- win_update(wp, &providers);
- }
-
- // redraw status line and window bar after the window to minimize cursor movement
- if (wp->w_redr_status) {
- win_redr_winbar(wp);
- win_redr_status(wp);
- }
- }
-
- end_search_hl();
-
- // May need to redraw the popup menu.
- if (pum_drawn() && must_redraw_pum) {
- pum_redraw();
- }
-
- /* Reset b_mod_set flags. Going through all windows is probably faster
- * than going through all buffers (there could be many buffers). */
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- wp->w_buffer->b_mod_set = false;
- }
-
- updating_screen = 0;
-
- /* Clear or redraw the command line. Done last, because scrolling may
- * mess up the command line. */
- if (clear_cmdline || redraw_cmdline) {
- showmode();
- }
-
- // May put up an introductory message when not editing a file
- if (!did_intro) {
- maybe_intro_message();
- }
- did_intro = true;
-
- decor_providers_invoke_end(&providers, &provider_err);
- kvi_destroy(providers);
-
- // either cmdline is cleared, not drawn or mode is last drawn
- cmdline_was_last_drawn = false;
- return OK;
-}
-
-// Return true if the cursor line in window "wp" may be concealed, according
-// to the 'concealcursor' option.
+/// Return true if the cursor line in window "wp" may be concealed, according
+/// to the 'concealcursor' option.
bool conceal_cursor_line(const win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
@@ -646,20 +69,6 @@ bool conceal_cursor_line(const win_T *wp)
return vim_strchr((char *)wp->w_p_cocu, c) != NULL;
}
-// Check if the cursor line needs to be redrawn because of 'concealcursor'.
-//
-// When cursor is moved at the same time, both lines will be redrawn regardless.
-void conceal_check_cursor_line(void)
-{
- bool should_conceal = conceal_cursor_line(curwin);
- if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) {
- redrawWinline(curwin, curwin->w_cursor.lnum);
- // Need to recompute cursor column, e.g., when starting Visual mode
- // without concealing.
- curs_columns(curwin, true);
- }
-}
-
/// Whether cursorline is drawn in a special way
///
/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows.
@@ -669,1081 +78,6 @@ bool win_cursorline_standout(const win_T *wp)
return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
}
-/*
- * Update a single window.
- *
- * This may cause the windows below it also to be redrawn (when clearing the
- * screen or scrolling lines).
- *
- * How the window is redrawn depends on wp->w_redr_type. Each type also
- * implies the one below it.
- * NOT_VALID redraw the whole window
- * SOME_VALID redraw the whole window but do scroll when possible
- * REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID
- * INVERTED redraw the changed part of the Visual area
- * INVERTED_ALL redraw the whole Visual area
- * VALID 1. scroll up/down to adjust for a changed w_topline
- * 2. update lines at the top when scrolled down
- * 3. redraw changed text:
- * - if wp->w_buffer->b_mod_set set, update lines between
- * b_mod_top and b_mod_bot.
- * - if wp->w_redraw_top non-zero, redraw lines between
- * wp->w_redraw_top and wp->w_redr_bot.
- * - continue redrawing when syntax status is invalid.
- * 4. if scrolled up, update lines at the bottom.
- * This results in three areas that may need updating:
- * top: from first row to top_end (when scrolled down)
- * mid: from mid_start to mid_end (update inversion or changed text)
- * bot: from bot_start to last row (when scrolled up)
- */
-static void win_update(win_T *wp, DecorProviders *providers)
-{
- bool called_decor_providers = false;
-win_update_start:
- ;
- buf_T *buf = wp->w_buffer;
- int type;
- int top_end = 0; /* Below last row of the top area that needs
- updating. 0 when no top area updating. */
- int mid_start = 999; /* first row of the mid area that needs
- updating. 999 when no mid area updating. */
- int mid_end = 0; /* Below last row of the mid area that needs
- updating. 0 when no mid area updating. */
- int bot_start = 999; /* first row of the bot area that needs
- updating. 999 when no bot area updating */
- bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit
- bool top_to_mod = false; // redraw above mod_top
-
- int row; // current window row to display
- linenr_T lnum; // current buffer lnum to display
- int idx; // current index in w_lines[]
- int srow; // starting row of the current line
-
- bool eof = false; // if true, we hit the end of the file
- bool didline = false; // if true, we finished the last line
- int i;
- long j;
- static bool recursive = false; // being called recursively
- const linenr_T old_botline = wp->w_botline;
- // Remember what happened to the previous line.
-#define DID_NONE 1 // didn't update a line
-#define DID_LINE 2 // updated a normal line
-#define DID_FOLD 3 // updated a folded line
- int did_update = DID_NONE;
- linenr_T syntax_last_parsed = 0; // last parsed text line
- linenr_T mod_top = 0;
- linenr_T mod_bot = 0;
- int save_got_int;
-
- type = wp->w_redr_type;
-
- if (type >= NOT_VALID) {
- wp->w_redr_status = true;
- wp->w_lines_valid = 0;
- }
-
- // Window is zero-height: Only need to draw the separator
- if (wp->w_grid.rows == 0) {
- // draw the horizontal separator below this window
- draw_hsep_win(wp);
- draw_sep_connectors_win(wp);
- wp->w_redr_type = 0;
- return;
- }
-
- // Window is zero-width: Only need to draw the separator.
- if (wp->w_grid.cols == 0) {
- // draw the vertical separator right of this window
- draw_vsep_win(wp);
- draw_sep_connectors_win(wp);
- wp->w_redr_type = 0;
- return;
- }
-
- redraw_win_signcol(wp);
-
- init_search_hl(wp, &search_hl);
-
- /* Force redraw when width of 'number' or 'relativenumber' column
- * changes. */
- i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0;
- if (wp->w_nrwidth != i) {
- type = NOT_VALID;
- wp->w_nrwidth = i;
-
- if (buf->terminal) {
- terminal_check_size(buf->terminal);
- }
- } else if (buf->b_mod_set
- && buf->b_mod_xlines != 0
- && wp->w_redraw_top != 0) {
- // When there are both inserted/deleted lines and specific lines to be
- // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw
- // everything (only happens when redrawing is off for while).
- type = NOT_VALID;
- } else {
- /*
- * Set mod_top to the first line that needs displaying because of
- * changes. Set mod_bot to the first line after the changes.
- */
- mod_top = wp->w_redraw_top;
- if (wp->w_redraw_bot != 0) {
- mod_bot = wp->w_redraw_bot + 1;
- } else {
- mod_bot = 0;
- }
- if (buf->b_mod_set) {
- if (mod_top == 0 || mod_top > buf->b_mod_top) {
- mod_top = buf->b_mod_top;
- /* Need to redraw lines above the change that may be included
- * in a pattern match. */
- if (syntax_present(wp)) {
- mod_top -= buf->b_s.b_syn_sync_linebreaks;
- if (mod_top < 1) {
- mod_top = 1;
- }
- }
- }
- if (mod_bot == 0 || mod_bot < buf->b_mod_bot) {
- mod_bot = buf->b_mod_bot;
- }
-
- // When 'hlsearch' is on and using a multi-line search pattern, a
- // change in one line may make the Search highlighting in a
- // previous line invalid. Simple solution: redraw all visible
- // lines above the change.
- // Same for a match pattern.
- if (search_hl.rm.regprog != NULL
- && re_multiline(search_hl.rm.regprog)) {
- top_to_mod = true;
- } else {
- const matchitem_T *cur = wp->w_match_head;
- while (cur != NULL) {
- if (cur->match.regprog != NULL
- && re_multiline(cur->match.regprog)) {
- top_to_mod = true;
- break;
- }
- cur = cur->next;
- }
- }
- }
- if (mod_top != 0 && hasAnyFolding(wp)) {
- linenr_T lnumt, lnumb;
-
- /*
- * A change in a line can cause lines above it to become folded or
- * unfolded. Find the top most buffer line that may be affected.
- * If the line was previously folded and displayed, get the first
- * line of that fold. If the line is folded now, get the first
- * folded line. Use the minimum of these two.
- */
-
- /* Find last valid w_lines[] entry above mod_top. Set lnumt to
- * the line below it. If there is no valid entry, use w_topline.
- * Find the first valid w_lines[] entry below mod_bot. Set lnumb
- * to this line. If there is no valid entry, use MAXLNUM. */
- lnumt = wp->w_topline;
- lnumb = MAXLNUM;
- for (i = 0; i < wp->w_lines_valid; ++i) {
- if (wp->w_lines[i].wl_valid) {
- if (wp->w_lines[i].wl_lastlnum < mod_top) {
- lnumt = wp->w_lines[i].wl_lastlnum + 1;
- }
- if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) {
- lnumb = wp->w_lines[i].wl_lnum;
- // When there is a fold column it might need updating
- // in the next line ("J" just above an open fold).
- if (compute_foldcolumn(wp, 0) > 0) {
- lnumb++;
- }
- }
- }
- }
-
- (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL);
- if (mod_top > lnumt) {
- mod_top = lnumt;
- }
-
- // Now do the same for the bottom line (one above mod_bot).
- mod_bot--;
- (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL);
- mod_bot++;
- if (mod_bot < lnumb) {
- mod_bot = lnumb;
- }
- }
-
- /* When a change starts above w_topline and the end is below
- * w_topline, start redrawing at w_topline.
- * If the end of the change is above w_topline: do like no change was
- * made, but redraw the first line to find changes in syntax. */
- if (mod_top != 0 && mod_top < wp->w_topline) {
- if (mod_bot > wp->w_topline) {
- mod_top = wp->w_topline;
- } else if (syntax_present(wp)) {
- top_end = 1;
- }
- }
-
- /* When line numbers are displayed need to redraw all lines below
- * inserted/deleted lines. */
- if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) {
- mod_bot = MAXLNUM;
- }
- }
- wp->w_redraw_top = 0; // reset for next time
- wp->w_redraw_bot = 0;
-
- /*
- * When only displaying the lines at the top, set top_end. Used when
- * window has scrolled down for msg_scrolled.
- */
- if (type == REDRAW_TOP) {
- j = 0;
- for (i = 0; i < wp->w_lines_valid; ++i) {
- j += wp->w_lines[i].wl_size;
- if (j >= wp->w_upd_rows) {
- top_end = (int)j;
- break;
- }
- }
- if (top_end == 0) {
- // not found (cannot happen?): redraw everything
- type = NOT_VALID;
- } else {
- // top area defined, the rest is VALID
- type = VALID;
- }
- }
-
- /*
- * If there are no changes on the screen that require a complete redraw,
- * handle three cases:
- * 1: we are off the top of the screen by a few lines: scroll down
- * 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up
- * 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in
- * w_lines[] that needs updating.
- */
- if ((type == VALID || type == SOME_VALID
- || type == INVERTED || type == INVERTED_ALL)
- && !wp->w_botfill && !wp->w_old_botfill) {
- if (mod_top != 0
- && wp->w_topline == mod_top
- && (!wp->w_lines[0].wl_valid
- || wp->w_topline == wp->w_lines[0].wl_lnum)) {
- // w_topline is the first changed line and window is not scrolled,
- // the scrolling from changed lines will be done further down.
- } else if (wp->w_lines[0].wl_valid
- && (wp->w_topline < wp->w_lines[0].wl_lnum
- || (wp->w_topline == wp->w_lines[0].wl_lnum
- && wp->w_topfill > wp->w_old_topfill)
- )) {
- /*
- * New topline is above old topline: May scroll down.
- */
- if (hasAnyFolding(wp)) {
- linenr_T ln;
-
- /* count the number of lines we are off, counting a sequence
- * of folded lines as one */
- j = 0;
- for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) {
- j++;
- if (j >= wp->w_grid.rows - 2) {
- break;
- }
- (void)hasFoldingWin(wp, ln, NULL, &ln, true, NULL);
- }
- } else {
- j = wp->w_lines[0].wl_lnum - wp->w_topline;
- }
- if (j < wp->w_grid.rows - 2) { // not too far off
- i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);
- // insert extra lines for previously invisible filler lines
- if (wp->w_lines[0].wl_lnum != wp->w_topline) {
- i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill;
- }
- if (i != 0 && i < wp->w_grid.rows - 2) { // less than a screen off
- // Try to insert the correct number of lines.
- // If not the last window, delete the lines at the bottom.
- // win_ins_lines may fail when the terminal can't do it.
- win_scroll_lines(wp, 0, i);
- if (wp->w_lines_valid != 0) {
- // Need to update rows that are new, stop at the
- // first one that scrolled down.
- top_end = i;
- scrolled_down = true;
-
- // Move the entries that were scrolled, disable
- // the entries for the lines to be redrawn.
- if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) {
- wp->w_lines_valid = wp->w_grid.rows;
- }
- for (idx = wp->w_lines_valid; idx - j >= 0; idx--) {
- wp->w_lines[idx] = wp->w_lines[idx - j];
- }
- while (idx >= 0) {
- wp->w_lines[idx--].wl_valid = false;
- }
- }
- } else {
- mid_start = 0; // redraw all lines
- }
- } else {
- mid_start = 0; // redraw all lines
- }
- } else {
- /*
- * New topline is at or below old topline: May scroll up.
- * When topline didn't change, find first entry in w_lines[] that
- * needs updating.
- */
-
- // try to find wp->w_topline in wp->w_lines[].wl_lnum
- j = -1;
- row = 0;
- for (i = 0; i < wp->w_lines_valid; i++) {
- if (wp->w_lines[i].wl_valid
- && wp->w_lines[i].wl_lnum == wp->w_topline) {
- j = i;
- break;
- }
- row += wp->w_lines[i].wl_size;
- }
- if (j == -1) {
- /* if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all
- * lines */
- mid_start = 0;
- } else {
- /*
- * Try to delete the correct number of lines.
- * wp->w_topline is at wp->w_lines[i].wl_lnum.
- */
- /* If the topline didn't change, delete old filler lines,
- * otherwise delete filler lines of the new topline... */
- if (wp->w_lines[0].wl_lnum == wp->w_topline) {
- row += wp->w_old_topfill;
- } else {
- row += win_get_fill(wp, wp->w_topline);
- }
- // ... but don't delete new filler lines.
- row -= wp->w_topfill;
- if (row > 0) {
- win_scroll_lines(wp, 0, -row);
- bot_start = wp->w_grid.rows - row;
- }
- if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) {
- /*
- * Skip the lines (below the deleted lines) that are still
- * valid and don't need redrawing. Copy their info
- * upwards, to compensate for the deleted lines. Set
- * bot_start to the first row that needs redrawing.
- */
- bot_start = 0;
- idx = 0;
- for (;;) {
- wp->w_lines[idx] = wp->w_lines[j];
- /* stop at line that didn't fit, unless it is still
- * valid (no lines deleted) */
- if (row > 0 && bot_start + row
- + (int)wp->w_lines[j].wl_size > wp->w_grid.rows) {
- wp->w_lines_valid = idx + 1;
- break;
- }
- bot_start += wp->w_lines[idx++].wl_size;
-
- // stop at the last valid entry in w_lines[].wl_size
- if (++j >= wp->w_lines_valid) {
- wp->w_lines_valid = idx;
- break;
- }
- }
-
- // Correct the first entry for filler lines at the top
- // when it won't get updated below.
- if (win_may_fill(wp) && bot_start > 0) {
- wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true)
- + wp->w_topfill);
- }
- }
- }
- }
-
- // When starting redraw in the first line, redraw all lines.
- if (mid_start == 0) {
- mid_end = wp->w_grid.rows;
- }
- } else {
- // Not VALID or INVERTED: redraw all lines.
- mid_start = 0;
- mid_end = wp->w_grid.rows;
- }
-
- if (type == SOME_VALID) {
- // SOME_VALID: redraw all lines.
- mid_start = 0;
- mid_end = wp->w_grid.rows;
- type = NOT_VALID;
- }
-
- // check if we are updating or removing the inverted part
- if ((VIsual_active && buf == curwin->w_buffer)
- || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) {
- linenr_T from, to;
-
- if (VIsual_active) {
- if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) {
- // If the type of Visual selection changed, redraw the whole
- // selection. Also when the ownership of the X selection is
- // gained or lost.
- if (curwin->w_cursor.lnum < VIsual.lnum) {
- from = curwin->w_cursor.lnum;
- to = VIsual.lnum;
- } else {
- from = VIsual.lnum;
- to = curwin->w_cursor.lnum;
- }
- // redraw more when the cursor moved as well
- if (wp->w_old_cursor_lnum < from) {
- from = wp->w_old_cursor_lnum;
- }
- if (wp->w_old_cursor_lnum > to) {
- to = wp->w_old_cursor_lnum;
- }
- if (wp->w_old_visual_lnum < from) {
- from = wp->w_old_visual_lnum;
- }
- if (wp->w_old_visual_lnum > to) {
- to = wp->w_old_visual_lnum;
- }
- } else {
- /*
- * Find the line numbers that need to be updated: The lines
- * between the old cursor position and the current cursor
- * position. Also check if the Visual position changed.
- */
- if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) {
- from = curwin->w_cursor.lnum;
- to = wp->w_old_cursor_lnum;
- } else {
- from = wp->w_old_cursor_lnum;
- to = curwin->w_cursor.lnum;
- if (from == 0) { // Visual mode just started
- from = to;
- }
- }
-
- if (VIsual.lnum != wp->w_old_visual_lnum
- || VIsual.col != wp->w_old_visual_col) {
- if (wp->w_old_visual_lnum < from
- && wp->w_old_visual_lnum != 0) {
- from = wp->w_old_visual_lnum;
- }
- if (wp->w_old_visual_lnum > to) {
- to = wp->w_old_visual_lnum;
- }
- if (VIsual.lnum < from) {
- from = VIsual.lnum;
- }
- if (VIsual.lnum > to) {
- to = VIsual.lnum;
- }
- }
- }
-
- /*
- * If in block mode and changed column or curwin->w_curswant:
- * update all lines.
- * First compute the actual start and end column.
- */
- if (VIsual_mode == Ctrl_V) {
- colnr_T fromc, toc;
- unsigned int save_ve_flags = curwin->w_ve_flags;
-
- if (curwin->w_p_lbr) {
- curwin->w_ve_flags = VE_ALL;
- }
-
- getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc);
- toc++;
- curwin->w_ve_flags = save_ve_flags;
- // Highlight to the end of the line, unless 'virtualedit' has
- // "block".
- if (curwin->w_curswant == MAXCOL) {
- if (get_ve_flags() & VE_BLOCK) {
- pos_T pos;
- int cursor_above = curwin->w_cursor.lnum < VIsual.lnum;
-
- // Need to find the longest line.
- toc = 0;
- pos.coladd = 0;
- for (pos.lnum = curwin->w_cursor.lnum;
- cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum;
- pos.lnum += cursor_above ? 1 : -1) {
- colnr_T t;
-
- pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false));
- getvvcol(wp, &pos, NULL, NULL, &t);
- if (toc < t) {
- toc = t;
- }
- }
- toc++;
- } else {
- toc = MAXCOL;
- }
- }
-
- if (fromc != wp->w_old_cursor_fcol
- || toc != wp->w_old_cursor_lcol) {
- if (from > VIsual.lnum) {
- from = VIsual.lnum;
- }
- if (to < VIsual.lnum) {
- to = VIsual.lnum;
- }
- }
- wp->w_old_cursor_fcol = fromc;
- wp->w_old_cursor_lcol = toc;
- }
- } else {
- // Use the line numbers of the old Visual area.
- if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) {
- from = wp->w_old_cursor_lnum;
- to = wp->w_old_visual_lnum;
- } else {
- from = wp->w_old_visual_lnum;
- to = wp->w_old_cursor_lnum;
- }
- }
-
- /*
- * There is no need to update lines above the top of the window.
- */
- if (from < wp->w_topline) {
- from = wp->w_topline;
- }
-
- /*
- * If we know the value of w_botline, use it to restrict the update to
- * the lines that are visible in the window.
- */
- if (wp->w_valid & VALID_BOTLINE) {
- if (from >= wp->w_botline) {
- from = wp->w_botline - 1;
- }
- if (to >= wp->w_botline) {
- to = wp->w_botline - 1;
- }
- }
-
- /*
- * Find the minimal part to be updated.
- * Watch out for scrolling that made entries in w_lines[] invalid.
- * E.g., CTRL-U makes the first half of w_lines[] invalid and sets
- * top_end; need to redraw from top_end to the "to" line.
- * A middle mouse click with a Visual selection may change the text
- * above the Visual area and reset wl_valid, do count these for
- * mid_end (in srow).
- */
- if (mid_start > 0) {
- lnum = wp->w_topline;
- idx = 0;
- srow = 0;
- if (scrolled_down) {
- mid_start = top_end;
- } else {
- mid_start = 0;
- }
- while (lnum < from && idx < wp->w_lines_valid) { // find start
- if (wp->w_lines[idx].wl_valid) {
- mid_start += wp->w_lines[idx].wl_size;
- } else if (!scrolled_down) {
- srow += wp->w_lines[idx].wl_size;
- }
- ++idx;
- if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) {
- lnum = wp->w_lines[idx].wl_lnum;
- } else {
- ++lnum;
- }
- }
- srow += mid_start;
- mid_end = wp->w_grid.rows;
- for (; idx < wp->w_lines_valid; idx++) { // find end
- if (wp->w_lines[idx].wl_valid
- && wp->w_lines[idx].wl_lnum >= to + 1) {
- // Only update until first row of this line
- mid_end = srow;
- break;
- }
- srow += wp->w_lines[idx].wl_size;
- }
- }
- }
-
- if (VIsual_active && buf == curwin->w_buffer) {
- wp->w_old_visual_mode = (char)VIsual_mode;
- wp->w_old_cursor_lnum = curwin->w_cursor.lnum;
- wp->w_old_visual_lnum = VIsual.lnum;
- wp->w_old_visual_col = VIsual.col;
- wp->w_old_curswant = curwin->w_curswant;
- } else {
- wp->w_old_visual_mode = 0;
- wp->w_old_cursor_lnum = 0;
- wp->w_old_visual_lnum = 0;
- wp->w_old_visual_col = 0;
- }
-
- // reset got_int, otherwise regexp won't work
- save_got_int = got_int;
- got_int = 0;
- // Set the time limit to 'redrawtime'.
- proftime_T syntax_tm = profile_setlimit(p_rdt);
- syn_set_timeout(&syntax_tm);
-
- /*
- * Update all the window rows.
- */
- idx = 0; // first entry in w_lines[].wl_size
- row = 0;
- srow = 0;
- lnum = wp->w_topline; // first line shown in window
-
- win_extmark_arr.size = 0;
-
- decor_redraw_reset(buf, &decor_state);
-
- DecorProviders line_providers;
- decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
- (void)win_signcol_count(wp); // check if provider changed signcol width
- if (must_redraw != 0) {
- must_redraw = 0;
- if (!called_decor_providers) {
- called_decor_providers = true;
- goto win_update_start;
- }
- }
-
- bool cursorline_standout = win_cursorline_standout(wp);
-
- for (;;) {
- /* stop updating when reached the end of the window (check for _past_
- * the end of the window is at the end of the loop) */
- if (row == wp->w_grid.rows) {
- didline = true;
- break;
- }
-
- // stop updating when hit the end of the file
- if (lnum > buf->b_ml.ml_line_count) {
- eof = true;
- break;
- }
-
- /* Remember the starting row of the line that is going to be dealt
- * with. It is used further down when the line doesn't fit. */
- srow = row;
-
- // Update a line when it is in an area that needs updating, when it
- // has changes or w_lines[idx] is invalid.
- // "bot_start" may be halfway a wrapped line after using
- // win_scroll_lines(), check if the current line includes it.
- // When syntax folding is being used, the saved syntax states will
- // already have been updated, we can't see where the syntax state is
- // the same again, just update until the end of the window.
- if (row < top_end
- || (row >= mid_start && row < mid_end)
- || top_to_mod
- || idx >= wp->w_lines_valid
- || (row + wp->w_lines[idx].wl_size > bot_start)
- || (mod_top != 0
- && (lnum == mod_top
- || (lnum >= mod_top
- && (lnum < mod_bot
- || did_update == DID_FOLD
- || (did_update == DID_LINE
- && syntax_present(wp)
- && ((foldmethodIsSyntax(wp)
- && hasAnyFolding(wp))
- || syntax_check_changed(lnum)))
- // match in fixed position might need redraw
- // if lines were inserted or deleted
- || (wp->w_match_head != NULL
- && buf->b_mod_xlines != 0)))))
- || (cursorline_standout && lnum == wp->w_cursor.lnum)
- || lnum == wp->w_last_cursorline) {
- if (lnum == mod_top) {
- top_to_mod = false;
- }
-
- /*
- * When at start of changed lines: May scroll following lines
- * up or down to minimize redrawing.
- * Don't do this when the change continues until the end.
- * Don't scroll when dollar_vcol >= 0, keep the "$".
- * Don't scroll when redrawing the top, scrolled already above.
- */
- if (lnum == mod_top
- && mod_bot != MAXLNUM
- && !(dollar_vcol >= 0 && mod_bot == mod_top + 1)
- && row >= top_end) {
- int old_rows = 0;
- int new_rows = 0;
- int xtra_rows;
- linenr_T l;
-
- /* Count the old number of window rows, using w_lines[], which
- * should still contain the sizes for the lines as they are
- * currently displayed. */
- for (i = idx; i < wp->w_lines_valid; ++i) {
- /* Only valid lines have a meaningful wl_lnum. Invalid
- * lines are part of the changed area. */
- if (wp->w_lines[i].wl_valid
- && wp->w_lines[i].wl_lnum == mod_bot) {
- break;
- }
- old_rows += wp->w_lines[i].wl_size;
- if (wp->w_lines[i].wl_valid
- && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) {
- /* Must have found the last valid entry above mod_bot.
- * Add following invalid entries. */
- ++i;
- while (i < wp->w_lines_valid
- && !wp->w_lines[i].wl_valid) {
- old_rows += wp->w_lines[i++].wl_size;
- }
- break;
- }
- }
-
- if (i >= wp->w_lines_valid) {
- /* We can't find a valid line below the changed lines,
- * need to redraw until the end of the window.
- * Inserting/deleting lines has no use. */
- bot_start = 0;
- } else {
- /* Able to count old number of rows: Count new window
- * rows, and may insert/delete lines */
- j = idx;
- for (l = lnum; l < mod_bot; l++) {
- if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) {
- new_rows++;
- } else if (l == wp->w_topline) {
- new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill;
- } else {
- new_rows += plines_win(wp, l, true);
- }
- j++;
- if (new_rows > wp->w_grid.rows - row - 2) {
- // it's getting too much, must redraw the rest
- new_rows = 9999;
- break;
- }
- }
- xtra_rows = new_rows - old_rows;
- if (xtra_rows < 0) {
- /* May scroll text up. If there is not enough
- * remaining text or scrolling fails, must redraw the
- * rest. If scrolling works, must redraw the text
- * below the scrolled text. */
- if (row - xtra_rows >= wp->w_grid.rows - 2) {
- mod_bot = MAXLNUM;
- } else {
- win_scroll_lines(wp, row, xtra_rows);
- bot_start = wp->w_grid.rows + xtra_rows;
- }
- } else if (xtra_rows > 0) {
- /* May scroll text down. If there is not enough
- * remaining text of scrolling fails, must redraw the
- * rest. */
- if (row + xtra_rows >= wp->w_grid.rows - 2) {
- mod_bot = MAXLNUM;
- } else {
- win_scroll_lines(wp, row + old_rows, xtra_rows);
- if (top_end > row + old_rows) {
- // Scrolled the part at the top that requires
- // updating down.
- top_end += xtra_rows;
- }
- }
- }
-
- /* When not updating the rest, may need to move w_lines[]
- * entries. */
- if (mod_bot != MAXLNUM && i != j) {
- if (j < i) {
- int x = row + new_rows;
-
- // move entries in w_lines[] upwards
- for (;;) {
- // stop at last valid entry in w_lines[]
- if (i >= wp->w_lines_valid) {
- wp->w_lines_valid = (int)j;
- break;
- }
- wp->w_lines[j] = wp->w_lines[i];
- // stop at a line that won't fit
- if (x + (int)wp->w_lines[j].wl_size
- > wp->w_grid.rows) {
- wp->w_lines_valid = (int)j + 1;
- break;
- }
- x += wp->w_lines[j++].wl_size;
- ++i;
- }
- if (bot_start > x) {
- bot_start = x;
- }
- } else { // j > i
- // move entries in w_lines[] downwards
- j -= i;
- wp->w_lines_valid += (linenr_T)j;
- if (wp->w_lines_valid > wp->w_grid.rows) {
- wp->w_lines_valid = wp->w_grid.rows;
- }
- for (i = wp->w_lines_valid; i - j >= idx; i--) {
- wp->w_lines[i] = wp->w_lines[i - j];
- }
-
- /* The w_lines[] entries for inserted lines are
- * now invalid, but wl_size may be used above.
- * Reset to zero. */
- while (i >= idx) {
- wp->w_lines[i].wl_size = 0;
- wp->w_lines[i--].wl_valid = FALSE;
- }
- }
- }
- }
- }
-
- /*
- * When lines are folded, display one line for all of them.
- * Otherwise, display normally (can be several display lines when
- * 'wrap' is on).
- */
- foldinfo_T foldinfo = fold_info(wp, lnum);
-
- if (foldinfo.fi_lines == 0
- && idx < wp->w_lines_valid
- && wp->w_lines[idx].wl_valid
- && wp->w_lines[idx].wl_lnum == lnum
- && lnum > wp->w_topline
- && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
- && srow + wp->w_lines[idx].wl_size > wp->w_grid.rows
- && win_get_fill(wp, lnum) == 0) {
- // This line is not going to fit. Don't draw anything here,
- // will draw "@ " lines below.
- row = wp->w_grid.rows + 1;
- } else {
- prepare_search_hl(wp, &search_hl, lnum);
- // Let the syntax stuff know we skipped a few lines.
- if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum
- && syntax_present(wp)) {
- syntax_end_parsing(syntax_last_parsed + 1);
- }
-
- // Display one line
- row = win_line(wp, lnum, srow,
- foldinfo.fi_lines ? srow : wp->w_grid.rows,
- mod_top == 0, false, foldinfo, &line_providers);
-
- if (foldinfo.fi_lines == 0) {
- wp->w_lines[idx].wl_folded = false;
- wp->w_lines[idx].wl_lastlnum = lnum;
- did_update = DID_LINE;
- syntax_last_parsed = lnum;
- } else {
- foldinfo.fi_lines--;
- wp->w_lines[idx].wl_folded = true;
- wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
- did_update = DID_FOLD;
- }
- }
-
- wp->w_lines[idx].wl_lnum = lnum;
- wp->w_lines[idx].wl_valid = true;
-
- if (row > wp->w_grid.rows) { // past end of grid
- // we may need the size of that too long line later on
- if (dollar_vcol == -1) {
- wp->w_lines[idx].wl_size = (uint16_t)plines_win(wp, lnum, true);
- }
- idx++;
- break;
- }
- if (dollar_vcol == -1) {
- wp->w_lines[idx].wl_size = (uint16_t)(row - srow);
- }
- idx++;
- lnum += foldinfo.fi_lines + 1;
- } else {
- if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) {
- // 'relativenumber' set and cursor moved vertically: The
- // text doesn't need to be drawn, but the number column does.
- foldinfo_T info = fold_info(wp, lnum);
- (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true,
- info, &line_providers);
- }
-
- // This line does not need to be drawn, advance to the next one.
- row += wp->w_lines[idx++].wl_size;
- if (row > wp->w_grid.rows) { // past end of screen
- break;
- }
- lnum = wp->w_lines[idx - 1].wl_lastlnum + 1;
- did_update = DID_NONE;
- }
-
- if (lnum > buf->b_ml.ml_line_count) {
- eof = true;
- break;
- }
- }
- /*
- * End of loop over all window lines.
- */
-
- // Now that the window has been redrawn with the old and new cursor line,
- // update w_last_cursorline.
- wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0;
-
- wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0;
-
- if (idx > wp->w_lines_valid) {
- wp->w_lines_valid = idx;
- }
-
- /*
- * Let the syntax stuff know we stop parsing here.
- */
- if (syntax_last_parsed != 0 && syntax_present(wp)) {
- syntax_end_parsing(syntax_last_parsed + 1);
- }
-
- /*
- * If we didn't hit the end of the file, and we didn't finish the last
- * line we were working on, then the line didn't fit.
- */
- wp->w_empty_rows = 0;
- wp->w_filler_rows = 0;
- if (!eof && !didline) {
- int at_attr = hl_combine_attr(wp->w_hl_attr_normal,
- win_hl_attr(wp, HLF_AT));
- if (lnum == wp->w_topline) {
- /*
- * Single line that does not fit!
- * Don't overwrite it, it can be edited.
- */
- wp->w_botline = lnum + 1;
- } else if (win_get_fill(wp, lnum) >= wp->w_grid.rows - srow) {
- // Window ends in filler lines.
- wp->w_botline = lnum;
- wp->w_filler_rows = wp->w_grid.rows - srow;
- } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate"
- int scr_row = wp->w_grid.rows - 1;
-
- // Last line isn't finished: Display "@@@" in the last screen line.
- grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr);
-
- grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols,
- '@', ' ', at_attr);
- set_empty_rows(wp, srow);
- wp->w_botline = lnum;
- } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline"
- int start_col = wp->w_grid.cols - 3;
-
- // Last line isn't finished: Display "@@@" at the end.
- grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows,
- MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr);
- set_empty_rows(wp, srow);
- wp->w_botline = lnum;
- } else {
- win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT);
- wp->w_botline = lnum;
- }
- } else {
- if (eof) { // we hit the end of the file
- wp->w_botline = buf->b_ml.ml_line_count + 1;
- j = win_get_fill(wp, wp->w_botline);
- if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) {
- // Display filler text below last line. win_line() will check
- // for ml_line_count+1 and only draw filler lines
- foldinfo_T info = FOLDINFO_INIT;
- row = win_line(wp, wp->w_botline, row, wp->w_grid.rows,
- false, false, info, &line_providers);
- }
- } else if (dollar_vcol == -1) {
- wp->w_botline = lnum;
- }
-
- // make sure the rest of the screen is blank
- // write the 'eob' character to rows that aren't part of the file.
- win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows,
- HLF_EOB);
- }
-
- kvi_destroy(line_providers);
-
- if (wp->w_redr_type >= REDRAW_TOP) {
- draw_vsep_win(wp);
- draw_hsep_win(wp);
- draw_sep_connectors_win(wp);
- }
- syn_set_timeout(NULL);
-
- // Reset the type of redrawing required, the window has been updated.
- wp->w_redr_type = 0;
- wp->w_old_topfill = wp->w_topfill;
- wp->w_old_botfill = wp->w_botfill;
-
- // Send win_extmarks if needed
- for (size_t n = 0; n < kv_size(win_extmark_arr); n++) {
- ui_call_win_extmark(wp->w_grid_alloc.handle, wp->handle,
- kv_A(win_extmark_arr, n).ns_id, (Integer)kv_A(win_extmark_arr, n).mark_id,
- kv_A(win_extmark_arr, n).win_row, kv_A(win_extmark_arr, n).win_col);
- }
-
- if (dollar_vcol == -1) {
- /*
- * There is a trick with w_botline. If we invalidate it on each
- * change that might modify it, this will cause a lot of expensive
- * calls to plines_win() in update_topline() each time. Therefore the
- * value of w_botline is often approximated, and this value is used to
- * compute the value of w_topline. If the value of w_botline was
- * wrong, check that the value of w_topline is correct (cursor is on
- * the visible part of the text). If it's not, we need to redraw
- * again. Mostly this just means scrolling up a few lines, so it
- * doesn't look too bad. Only do this for the current window (where
- * changes are relevant).
- */
- wp->w_valid |= VALID_BOTLINE;
- wp->w_viewport_invalid = true;
- if (wp == curwin && wp->w_botline != old_botline && !recursive) {
- recursive = true;
- curwin->w_valid &= ~VALID_TOPLINE;
- update_topline(curwin); // may invalidate w_botline again
- if (must_redraw != 0) {
- // Don't update for changes in buffer again.
- i = curbuf->b_mod_set;
- curbuf->b_mod_set = false;
- win_update(curwin, providers);
- must_redraw = 0;
- curbuf->b_mod_set = i;
- }
- recursive = false;
- }
- }
-
- // restore got_int, unless CTRL-C was hit while redrawing
- if (!got_int) {
- got_int = save_got_int;
- }
-}
-
/// Returns width of the signcolumn that should be used for the whole window
///
/// @param wp window we want signcolumn width from
@@ -1783,7 +117,7 @@ static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row,
/// Clear lines near the end of the window and mark the unused lines with "c1".
/// Use "c2" as filler character.
/// When "draw_margin" is true, then draw the sign/fold/number columns.
-static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl)
+void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl)
{
assert(hl >= 0 && hl < HLF_COUNT);
int n = 0;
@@ -1808,7 +142,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i
}
}
- int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, (int)hl));
+ int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl));
if (wp->w_p_rl) {
grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n,
@@ -1822,20 +156,9 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i
set_empty_rows(wp, row);
}
-/// Advance **color_cols
-///
-/// @return true when there are columns to draw.
-static bool advance_color_col(int vcol, int **color_cols)
-{
- while (**color_cols >= 0 && vcol > **color_cols) {
- ++*color_cols;
- }
- return **color_cols >= 0;
-}
-
-// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
-// space is available for window "wp", minus "col".
-static int compute_foldcolumn(win_T *wp, int col)
+/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
+/// space is available for window "wp", minus "col".
+int compute_foldcolumn(win_T *wp, int col)
{
int fdc = win_fdccol_count(wp);
int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw;
@@ -1847,63 +170,6 @@ static int compute_foldcolumn(win_T *wp, int col)
return fdc;
}
-/// Put a single char from an UTF-8 buffer into a line buffer.
-///
-/// Handles composing chars and arabic shaping state.
-static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol)
-{
- const char_u *p = (char_u *)s->p;
- int cells = utf_ptr2cells((char *)p);
- int c_len = utfc_ptr2len((char *)p);
- int u8c, u8cc[MAX_MCO];
- if (cells > maxcells) {
- return -1;
- }
- u8c = utfc_ptr2char(p, u8cc);
- if (*p == TAB) {
- cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
- for (int c = 0; c < cells; c++) {
- schar_from_ascii(dest[c], ' ');
- }
- goto done;
- } else if (*p < 0x80 && u8cc[0] == 0) {
- schar_from_ascii(dest[0], (char)(*p));
- s->prev_c = u8c;
- } else {
- if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
- int firstbyte = *p;
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (rl) {
- pc = s->prev_c;
- pc1 = s->prev_c1;
- nc = utf_ptr2char((char *)p + c_len);
- s->prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(p + c_len, pcc);
- nc = s->prev_c;
- pc1 = pcc[0];
- }
- s->prev_c = u8c;
-
- u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
- } else {
- s->prev_c = u8c;
- }
- schar_from_cc(dest[0], u8c, u8cc);
- }
- if (cells > 1) {
- dest[1][0] = 0;
- }
-done:
- s->p += c_len;
- return cells;
-}
-
/// Fills the foldcolumn at "p" for window "wp".
/// Only to be called when 'foldcolumn' > 0.
///
@@ -1913,7 +179,7 @@ done:
///
/// Assume monocell characters
/// @return number of chars added to \param p
-static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
+size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
{
int i = 0;
int level;
@@ -1968,2634 +234,43 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_
return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
}
-static inline void provider_err_virt_text(linenr_T lnum, char *err)
-{
- Decoration err_decor = DECORATION_INIT;
- int hl_err = syn_check_group(S_LEN("ErrorMsg"));
- kv_push(err_decor.virt_text,
- ((VirtTextChunk){ .text = provider_err,
- .hl_id = hl_err }));
- err_decor.virt_text_width = (int)mb_string2cells(err);
- decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0);
-}
-
-static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len)
-{
- long num;
- char *fmt = "%*ld ";
-
- if (wp->w_p_nu && !wp->w_p_rnu) {
- // 'number' + 'norelativenumber'
- num = (long)lnum;
- } else {
- // 'relativenumber', don't use negative numbers
- num = labs((long)get_cursor_rel_lnum(wp, lnum));
- if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
- // 'number' + 'relativenumber'
- num = lnum;
- fmt = "%-*ld ";
- }
- }
-
- snprintf((char *)buf, buf_len, fmt, number_width(wp), num);
-}
-
-/// Display line "lnum" of window 'wp' on the screen.
-/// wp->w_virtcol needs to be valid.
-///
-/// @param lnum line to display
-/// @param startrow first row relative to window grid
-/// @param endrow last grid row to be redrawn
-/// @param nochange not updating for changed text
-/// @param number_only only update the number column
-/// @param foldinfo fold info for this line
-/// @param[in, out] providers decoration providers active this line
-/// items will be disables if they cause errors
-/// or explicitly return `false`.
-///
-/// @return the number of last row the line occupies.
-static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
- bool number_only, foldinfo_T foldinfo, DecorProviders *providers)
-{
- int c = 0; // init for GCC
- long vcol = 0; // virtual column (for tabs)
- long vcol_sbr = -1; // virtual column after showbreak
- long vcol_prev = -1; // "vcol" of previous character
- char_u *line; // current line
- char_u *ptr; // current position in "line"
- int row; // row in the window, excl w_winrow
- ScreenGrid *grid = &wp->w_grid; // grid specific to the window
-
- char_u extra[57]; // sign, line number and 'fdc' must
- // fit in here
- int n_extra = 0; // number of extra chars
- char_u *p_extra = NULL; // string of extra chars, plus NUL
- char_u *p_extra_free = NULL; // p_extra needs to be freed
- int c_extra = NUL; // extra chars, all the same
- int c_final = NUL; // final char, mandatory if set
- int extra_attr = 0; // attributes when n_extra != 0
- static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying
- // curwin->w_p_lcs_chars.eol at
- // end-of-line
- int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
- int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
- bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
-
- // saved "extra" items for when draw_state becomes WL_LINE (again)
- int saved_n_extra = 0;
- char_u *saved_p_extra = NULL;
- int saved_c_extra = 0;
- int saved_c_final = 0;
- int saved_char_attr = 0;
-
- int n_attr = 0; // chars with special attr
- int saved_attr2 = 0; // char_attr saved for n_attr
- int n_attr3 = 0; // chars with overruling special attr
- int saved_attr3 = 0; // char_attr saved for n_attr3
-
- int n_skip = 0; // nr of chars to skip for 'nowrap'
-
- int fromcol = -10; // start of inverting
- int tocol = MAXCOL; // end of inverting
- int fromcol_prev = -2; // start of inverting after cursor
- bool noinvcur = false; // don't invert the cursor
- bool lnum_in_visual_area = false;
- pos_T pos;
- long v;
-
- int char_attr = 0; // attributes for next character
- bool attr_pri = false; // char_attr has priority
- bool area_highlighting = false; // Visual or incsearch highlighting in this line
- int attr = 0; // attributes for area highlighting
- int area_attr = 0; // attributes desired by highlighting
- int search_attr = 0; // attributes desired by 'hlsearch'
- int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
- int syntax_attr = 0; // attributes desired by syntax
- int has_syntax = FALSE; // this buffer has syntax highl.
- int save_did_emsg;
- int eol_hl_off = 0; // 1 if highlighted char after EOL
- bool draw_color_col = false; // highlight colorcolumn
- int *color_cols = NULL; // pointer to according columns array
- bool has_spell = false; // this buffer has spell checking
-#define SPWORDLEN 150
- char_u nextline[SPWORDLEN * 2]; // text with start of the next line
- int nextlinecol = 0; // column where nextline[] starts
- int nextline_idx = 0; /* index in nextline[] where next line
- starts */
- int spell_attr = 0; // attributes desired by spelling
- int word_end = 0; // last byte with same spell_attr
- static linenr_T checked_lnum = 0; // line number for "checked_col"
- static int checked_col = 0; /* column in "checked_lnum" up to which
- * there are no spell errors */
- static int cap_col = -1; // column to check for Cap word
- static linenr_T capcol_lnum = 0; // line number where "cap_col"
- int cur_checked_col = 0; // checked column for current line
- int extra_check = 0; // has syntax or linebreak
- int multi_attr = 0; // attributes desired by multibyte
- int mb_l = 1; // multi-byte byte length
- int mb_c = 0; // decoded multi-byte character
- bool mb_utf8 = false; // screen char is UTF-8 char
- int u8cc[MAX_MCO]; // composing UTF-8 chars
- int filler_lines; // nr of filler lines to be drawn
- int filler_todo; // nr of filler lines still to do + 1
- hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting
- int change_start = MAXCOL; // first col of changed area
- int change_end = -1; // last col of changed area
- colnr_T trailcol = MAXCOL; // start of trailing spaces
- colnr_T leadcol = 0; // start of leading spaces
- bool in_multispace = false; // in multiple consecutive spaces
- int multispace_pos = 0; // position in lcs-multispace string
- bool need_showbreak = false; // overlong line, skip first x chars
- sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs
- int num_signs; // number of signs for line
- int line_attr = 0; // attribute for the whole line
- int line_attr_save;
- int line_attr_lowprio = 0; // low-priority attribute for the line
- int line_attr_lowprio_save;
- int prev_c = 0; // previous Arabic character
- int prev_c1 = 0; // first composing char for prev_c
-
- bool search_attr_from_match = false; // if search_attr is from :match
- bool has_decor = false; // this buffer has decoration
- int win_col_offset = 0; // offset for window columns
-
- char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext
-
- bool area_active = false;
-
- int cul_attr = 0; // set when 'cursorline' active
- // 'cursorlineopt' has "screenline" and cursor is in this line
- bool cul_screenline = false;
- // margin columns for the screen line, needed for when 'cursorlineopt'
- // contains "screenline"
- int left_curline_col = 0;
- int right_curline_col = 0;
-
- // draw_state: items that are drawn in sequence:
-#define WL_START 0 // nothing done yet
-#define WL_CMDLINE (WL_START + 1) // cmdline window column
-#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn'
-#define WL_SIGN (WL_FOLD + 1) // column for signs
-#define WL_NR (WL_SIGN + 1) // line number
-#define WL_BRI (WL_NR + 1) // 'breakindent'
-#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff'
-#define WL_LINE (WL_SBR + 1) // text in the line
- int draw_state = WL_START; // what to draw next
-
- int syntax_flags = 0;
- int syntax_seqnr = 0;
- int prev_syntax_id = 0;
- int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
- bool is_concealing = false;
- int boguscols = 0; ///< nonexistent columns added to
- ///< force wrapping
- int vcol_off = 0; ///< offset for concealed characters
- int did_wcol = false;
- int match_conc = 0; ///< cchar for match functions
- int old_boguscols = 0;
-#define VCOL_HLC (vcol - vcol_off)
-#define FIX_FOR_BOGUSCOLS \
- { \
- n_extra += vcol_off; \
- vcol -= vcol_off; \
- vcol_off = 0; \
- col -= boguscols; \
- old_boguscols = boguscols; \
- boguscols = 0; \
- }
-
- if (startrow > endrow) { // past the end already!
- return startrow;
- }
-
- row = startrow;
-
- buf_T *buf = wp->w_buffer;
- bool end_fill = (lnum == buf->b_ml.ml_line_count + 1);
-
- if (!number_only) {
- // To speed up the loop below, set extra_check when there is linebreak,
- // trailing white space and/or syntax processing to be done.
- extra_check = wp->w_p_lbr;
- if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow
- && !has_fold && !end_fill) {
- // Prepare for syntax highlighting in this line. When there is an
- // error, stop syntax highlighting.
- save_did_emsg = did_emsg;
- did_emsg = false;
- syntax_start(wp, lnum);
- if (did_emsg) {
- wp->w_s->b_syn_error = true;
- } else {
- did_emsg = save_did_emsg;
- if (!wp->w_s->b_syn_slow) {
- has_syntax = true;
- extra_check = true;
- }
- }
- }
-
- has_decor = decor_redraw_line(buf, lnum - 1, &decor_state);
-
- providers_invoke_line(wp, providers, lnum - 1, &has_decor, &provider_err);
-
- if (provider_err) {
- provider_err_virt_text(lnum, provider_err);
- has_decor = true;
- provider_err = NULL;
- }
-
- if (has_decor) {
- extra_check = true;
- }
-
- // Check for columns to display for 'colorcolumn'.
- color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
- if (color_cols != NULL) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
- }
-
- if (wp->w_p_spell
- && !has_fold
- && !end_fill
- && *wp->w_s->b_p_spl != NUL
- && !GA_EMPTY(&wp->w_s->b_langp)
- && *(char **)(wp->w_s->b_langp.ga_data) != NULL) {
- // Prepare for spell checking.
- has_spell = true;
- extra_check = true;
-
- // Get the start of the next line, so that words that wrap to the next
- // line are found too: "et<line-break>al.".
- // Trick: skip a few chars for C/shell/Vim comments
- nextline[SPWORDLEN] = NUL;
- if (lnum < wp->w_buffer->b_ml.ml_line_count) {
- line = ml_get_buf(wp->w_buffer, lnum + 1, false);
- spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
- }
-
- // When a word wrapped from the previous line the start of the current
- // line is valid.
- if (lnum == checked_lnum) {
- cur_checked_col = checked_col;
- }
- checked_lnum = 0;
-
- // When there was a sentence end in the previous line may require a
- // word starting with capital in this line. In line 1 always check
- // the first word.
- if (lnum != capcol_lnum) {
- cap_col = -1;
- }
- if (lnum == 1) {
- cap_col = 0;
- }
- capcol_lnum = 0;
- }
-
- // handle Visual active in this window
- if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
- pos_T *top, *bot;
-
- if (ltoreq(curwin->w_cursor, VIsual)) {
- // Visual is after curwin->w_cursor
- top = &curwin->w_cursor;
- bot = &VIsual;
- } else {
- // Visual is before curwin->w_cursor
- top = &VIsual;
- bot = &curwin->w_cursor;
- }
- lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum);
- if (VIsual_mode == Ctrl_V) {
- // block mode
- if (lnum_in_visual_area) {
- fromcol = wp->w_old_cursor_fcol;
- tocol = wp->w_old_cursor_lcol;
- }
- } else {
- // non-block mode
- if (lnum > top->lnum && lnum <= bot->lnum) {
- fromcol = 0;
- } else if (lnum == top->lnum) {
- if (VIsual_mode == 'V') { // linewise
- fromcol = 0;
- } else {
- getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL);
- if (gchar_pos(top) == NUL) {
- tocol = fromcol + 1;
- }
- }
- }
- if (VIsual_mode != 'V' && lnum == bot->lnum) {
- if (*p_sel == 'e' && bot->col == 0
- && bot->coladd == 0) {
- fromcol = -10;
- tocol = MAXCOL;
- } else if (bot->col == MAXCOL) {
- tocol = MAXCOL;
- } else {
- pos = *bot;
- if (*p_sel == 'e') {
- getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL);
- } else {
- getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol);
- tocol++;
- }
- }
- }
- }
-
- // Check if the char under the cursor should be inverted (highlighted).
- if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin
- && cursor_is_block_during_visual(*p_sel == 'e')) {
- noinvcur = true;
- }
-
- // if inverting in this line set area_highlighting
- if (fromcol >= 0) {
- area_highlighting = true;
- attr = win_hl_attr(wp, HLF_V);
- }
- // handle 'incsearch' and ":s///c" highlighting
- } else if (highlight_match
- && wp == curwin
- && !has_fold
- && lnum >= curwin->w_cursor.lnum
- && lnum <= curwin->w_cursor.lnum + search_match_lines) {
- if (lnum == curwin->w_cursor.lnum) {
- getvcol(curwin, &(curwin->w_cursor),
- (colnr_T *)&fromcol, NULL, NULL);
- } else {
- fromcol = 0;
- }
- if (lnum == curwin->w_cursor.lnum + search_match_lines) {
- pos.lnum = lnum;
- pos.col = search_match_endcol;
- getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
- }
- // do at least one character; happens when past end of line
- if (fromcol == tocol && search_match_endcol) {
- tocol = fromcol + 1;
- }
- area_highlighting = true;
- attr = win_hl_attr(wp, HLF_I);
- }
- }
-
- filler_lines = diff_check(wp, lnum);
- if (filler_lines < 0) {
- if (filler_lines == -1) {
- if (diff_find_change(wp, lnum, &change_start, &change_end)) {
- diff_hlf = HLF_ADD; // added line
- } else if (change_start == 0) {
- diff_hlf = HLF_TXD; // changed text
- } else {
- diff_hlf = HLF_CHD; // changed line
- }
- } else {
- diff_hlf = HLF_ADD; // added line
- }
- filler_lines = 0;
- area_highlighting = true;
- }
- VirtLines virt_lines = KV_INITIAL_VALUE;
- int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines);
- filler_lines += n_virt_lines;
- if (lnum == wp->w_topline) {
- filler_lines = wp->w_topfill;
- n_virt_lines = MIN(n_virt_lines, filler_lines);
- }
- filler_todo = filler_lines;
-
- // Cursor line highlighting for 'cursorline' in the current window.
- if (lnum == wp->w_cursor.lnum) {
- // Do not show the cursor line in the text when Visual mode is active,
- // because it's not clear what is selected then.
- if (wp->w_p_cul && !(wp == curwin && VIsual_active)
- && wp->w_p_culopt_flags != CULOPT_NBR) {
- cul_screenline = (wp->w_p_wrap
- && (wp->w_p_culopt_flags & CULOPT_SCRLINE));
- if (!cul_screenline) {
- cul_attr = win_hl_attr(wp, HLF_CUL);
- HlAttrs ae = syn_attr2entry(cul_attr);
- // We make a compromise here (#7383):
- // * low-priority CursorLine if fg is not set
- // * high-priority ("same as Vim" priority) CursorLine if fg is set
- if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
- line_attr_lowprio = cul_attr;
- } else {
- if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer)
- && qf_current_entry(wp) == lnum) {
- line_attr = hl_combine_attr(cul_attr, line_attr);
- } else {
- line_attr = cul_attr;
- }
- }
- } else {
- margin_columns_win(wp, &left_curline_col, &right_curline_col);
- }
- area_highlighting = true;
- }
- }
-
- memset(sattrs, 0, sizeof(sattrs));
- num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs);
- decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs);
-
- // If this line has a sign with line highlighting set line_attr.
- // TODO(bfredl, vigoux): this should not take priority over decoration!
- sign_attrs_T *sattr = sign_get_attr(SIGN_LINEHL, sattrs, 0, 1);
- if (sattr != NULL) {
- line_attr = sattr->sat_linehl;
- }
-
- // Highlight the current line in the quickfix window.
- if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) {
- line_attr = win_hl_attr(wp, HLF_QFL);
- }
-
- if (line_attr_lowprio || line_attr) {
- area_highlighting = true;
- }
-
- if (cul_screenline) {
- line_attr_save = line_attr;
- line_attr_lowprio_save = line_attr_lowprio;
- }
-
- line = end_fill ? (char_u *)"" : ml_get_buf(wp->w_buffer, lnum, false);
- ptr = line;
-
- if (has_spell && !number_only) {
- // For checking first word with a capital skip white space.
- if (cap_col == 0) {
- cap_col = (int)getwhitecols(line);
- }
-
- /* To be able to spell-check over line boundaries copy the end of the
- * current line into nextline[]. Above the start of the next line was
- * copied to nextline[SPWORDLEN]. */
- if (nextline[SPWORDLEN] == NUL) {
- // No next line or it is empty.
- nextlinecol = MAXCOL;
- nextline_idx = 0;
- } else {
- v = (long)STRLEN(line);
- if (v < SPWORDLEN) {
- /* Short line, use it completely and append the start of the
- * next line. */
- nextlinecol = 0;
- memmove(nextline, line, (size_t)v);
- STRMOVE(nextline + v, nextline + SPWORDLEN);
- nextline_idx = (int)v + 1;
- } else {
- // Long line, use only the last SPWORDLEN bytes.
- nextlinecol = (int)v - SPWORDLEN;
- memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512
- nextline_idx = SPWORDLEN + 1;
- }
- }
- }
-
- if (wp->w_p_list && !has_fold && !end_fill) {
- if (wp->w_p_lcs_chars.space
- || wp->w_p_lcs_chars.multispace != NULL
- || wp->w_p_lcs_chars.leadmultispace != NULL
- || wp->w_p_lcs_chars.trail
- || wp->w_p_lcs_chars.lead
- || wp->w_p_lcs_chars.nbsp) {
- extra_check = true;
- }
- // find start of trailing whitespace
- if (wp->w_p_lcs_chars.trail) {
- trailcol = (colnr_T)STRLEN(ptr);
- while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) {
- trailcol--;
- }
- trailcol += (colnr_T)(ptr - line);
- }
- // find end of leading whitespace
- if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
- leadcol = 0;
- while (ascii_iswhite(ptr[leadcol])) {
- leadcol++;
- }
- if (ptr[leadcol] == NUL) {
- // in a line full of spaces all of them are treated as trailing
- leadcol = (colnr_T)0;
- } else {
- // keep track of the first column not filled with spaces
- leadcol += (colnr_T)(ptr - line) + 1;
- }
- }
- }
-
- /*
- * 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
- * first character to be displayed.
- */
- if (wp->w_p_wrap) {
- v = wp->w_skipcol;
- } else {
- v = wp->w_leftcol;
- }
- if (v > 0 && !number_only) {
- char_u *prev_ptr = ptr;
- while (vcol < v && *ptr != NUL) {
- c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL);
- vcol += c;
- prev_ptr = ptr;
- MB_PTR_ADV(ptr);
- }
-
- // When:
- // - 'cuc' is set, or
- // - 'colorcolumn' is set, or
- // - 'virtualedit' is set, or
- // - the visual mode is active,
- // the end of the line may be before the start of the displayed part.
- if (vcol < v && (wp->w_p_cuc
- || draw_color_col
- || virtual_active()
- || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
- vcol = v;
- }
-
- /* Handle a character that's not completely on the screen: Put ptr at
- * that character but skip the first few screen characters. */
- if (vcol > v) {
- vcol -= c;
- ptr = prev_ptr;
- // If the character fits on the screen, don't need to skip it.
- // Except for a TAB.
- if (utf_ptr2cells((char *)ptr) >= c || *ptr == TAB) {
- n_skip = (int)(v - vcol);
- }
- }
-
- /*
- * Adjust for when the inverted text is before the screen,
- * and when the start of the inverted text is before the screen.
- */
- if (tocol <= vcol) {
- fromcol = 0;
- } else if (fromcol >= 0 && fromcol < vcol) {
- fromcol = (int)vcol;
- }
-
- // When w_skipcol is non-zero, first line needs 'showbreak'
- if (wp->w_p_wrap) {
- need_showbreak = true;
- }
- // When spell checking a word we need to figure out the start of the
- // word and if it's badly spelled or not.
- if (has_spell) {
- size_t len;
- colnr_T linecol = (colnr_T)(ptr - line);
- hlf_T spell_hlf = HLF_COUNT;
-
- pos = wp->w_cursor;
- wp->w_cursor.lnum = lnum;
- wp->w_cursor.col = linecol;
- len = spell_move_to(wp, FORWARD, true, true, &spell_hlf);
-
- // spell_move_to() may call ml_get() and make "line" invalid
- line = ml_get_buf(wp->w_buffer, lnum, false);
- ptr = line + linecol;
-
- if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
- /* no bad word found at line start, don't check until end of a
- * word */
- spell_hlf = HLF_COUNT;
- word_end = (int)(spell_to_word_end(ptr, wp) - line + 1);
- } else {
- // bad word found, use attributes until end of word
- assert(len <= INT_MAX);
- word_end = wp->w_cursor.col + (int)len + 1;
-
- // Turn index into actual attributes.
- if (spell_hlf != HLF_COUNT) {
- spell_attr = highlight_attr[spell_hlf];
- }
- }
- wp->w_cursor = pos;
-
- // Need to restart syntax highlighting for this line.
- if (has_syntax) {
- syntax_start(wp, lnum);
- }
- }
- }
-
- /*
- * Correct highlighting for cursor that can't be disabled.
- * Avoids having to check this for each character.
- */
- if (fromcol >= 0) {
- if (noinvcur) {
- if ((colnr_T)fromcol == wp->w_virtcol) {
- /* highlighting starts at cursor, let it start just after the
- * cursor */
- fromcol_prev = fromcol;
- fromcol = -1;
- } else if ((colnr_T)fromcol < wp->w_virtcol) {
- // restart highlighting after the cursor
- fromcol_prev = wp->w_virtcol;
- }
- }
- if (fromcol >= tocol) {
- fromcol = -1;
- }
- }
-
- if (!number_only && !has_fold && !end_fill) {
- v = ptr - line;
- area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v,
- &line, &search_hl, &search_attr,
- &search_attr_from_match);
- ptr = line + v; // "line" may have been updated
- }
-
- int off = 0; // Offset relative start of line
- int col = 0; // Visual column on screen.
- if (wp->w_p_rl) {
- // Rightleft window: process the text in the normal direction, but put
- // it in linebuf_char[off] from right to left. Start at the
- // rightmost column of the window.
- col = grid->cols - 1;
- off += col;
- }
-
- // won't highlight after TERM_ATTRS_MAX columns
- int term_attrs[TERM_ATTRS_MAX] = { 0 };
- if (wp->w_buffer->terminal) {
- terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs);
- extra_check = true;
- }
-
- int sign_idx = 0;
- // Repeat for the whole displayed line.
- for (;;) {
- int has_match_conc = 0; ///< match wants to conceal
- int decor_conceal = 0;
-
- bool did_decrement_ptr = false;
-
- // Skip this quickly when working on the text.
- if (draw_state != WL_LINE) {
- if (cul_screenline) {
- cul_attr = 0;
- line_attr = line_attr_save;
- line_attr_lowprio = line_attr_lowprio_save;
- }
-
- if (draw_state == WL_CMDLINE - 1 && n_extra == 0) {
- draw_state = WL_CMDLINE;
- if (cmdwin_type != 0 && wp == curwin) {
- // Draw the cmdline character.
- n_extra = 1;
- c_extra = cmdwin_type;
- c_final = NUL;
- char_attr = win_hl_attr(wp, HLF_AT);
- }
- }
-
- if (draw_state == WL_FOLD - 1 && n_extra == 0) {
- int fdc = compute_foldcolumn(wp, 0);
-
- draw_state = WL_FOLD;
- if (fdc > 0) {
- // Draw the 'foldcolumn'. Allocate a buffer, "extra" may
- // already be in use.
- xfree(p_extra_free);
- p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1);
- n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
- p_extra_free[n_extra] = NUL;
- p_extra = p_extra_free;
- c_extra = NUL;
- c_final = NUL;
- if (use_cursor_line_sign(wp, lnum)) {
- char_attr = win_hl_attr(wp, HLF_CLF);
- } else {
- char_attr = win_hl_attr(wp, HLF_FC);
- }
- }
- }
-
- // sign column, this is hit until sign_idx reaches count
- if (draw_state == WL_SIGN - 1 && n_extra == 0) {
- draw_state = WL_SIGN;
- /* Show the sign column when there are any signs in this
- * buffer or when using Netbeans. */
- if (wp->w_scwidth > 0) {
- get_sign_display_info(false, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo,
- &c_extra, &c_final, extra, sizeof(extra),
- &p_extra, &n_extra, &char_attr, sign_idx);
- sign_idx++;
- if (sign_idx < wp->w_scwidth) {
- draw_state = WL_SIGN - 1;
- } else {
- sign_idx = 0;
- }
- }
- }
-
- if (draw_state == WL_NR - 1 && n_extra == 0) {
- draw_state = WL_NR;
- /* Display the absolute or relative line number. After the
- * first fill with blanks when the 'n' flag isn't in 'cpo' */
- if ((wp->w_p_nu || wp->w_p_rnu)
- && (row == startrow + filler_lines
- || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) {
- // If 'signcolumn' is set to 'number' and a sign is present
- // in 'lnum', then display the sign instead of the line
- // number.
- if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'
- && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) {
- get_sign_display_info(true, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo,
- &c_extra, &c_final, extra, sizeof(extra),
- &p_extra, &n_extra, &char_attr, sign_idx);
- } else {
- // Draw the line number (empty space after wrapping).
- if (row == startrow + filler_lines) {
- get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra));
- if (wp->w_skipcol > 0) {
- for (p_extra = extra; *p_extra == ' '; p_extra++) {
- *p_extra = '-';
- }
- }
- if (wp->w_p_rl) { // reverse line numbers
- // like rl_mirror(), but keep the space at the end
- char_u *p2 = (char_u *)skipwhite((char *)extra);
- p2 = skiptowhite(p2) - 1;
- for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) {
- const char_u t = *p1;
- *p1 = *p2;
- *p2 = t;
- }
- }
- p_extra = extra;
- c_extra = NUL;
- } else {
- c_extra = ' ';
- }
- c_final = NUL;
- n_extra = number_width(wp) + 1;
- char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines, sattrs);
- }
- }
- }
-
- if (draw_state == WL_NR && n_extra == 0) {
- win_col_offset = off;
- }
-
- if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
- && n_extra == 0 && *get_showbreak_value(wp) != NUL) {
- // draw indent after showbreak value
- draw_state = WL_BRI;
- } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) {
- // after the showbreak, draw the breakindent
- draw_state = WL_BRI - 1;
- }
-
- // draw 'breakindent': indent wrapped text accordingly
- if (draw_state == WL_BRI - 1 && n_extra == 0) {
- draw_state = WL_BRI;
- // if need_showbreak is set, breakindent also applies
- if (wp->w_p_bri && (row != startrow || need_showbreak)
- && filler_lines == 0) {
- char_attr = 0;
-
- if (diff_hlf != (hlf_T)0) {
- char_attr = win_hl_attr(wp, (int)diff_hlf);
- }
- p_extra = NULL;
- c_extra = ' ';
- c_final = NUL;
- n_extra =
- get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false));
- if (row == startrow) {
- n_extra -= win_col_off2(wp);
- if (n_extra < 0) {
- n_extra = 0;
- }
- }
- if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
- need_showbreak = false;
- }
- // Correct end of highlighted area for 'breakindent',
- // required wen 'linebreak' is also set.
- if (tocol == vcol) {
- tocol += n_extra;
- }
- }
- }
-
- if (draw_state == WL_SBR - 1 && n_extra == 0) {
- draw_state = WL_SBR;
- if (filler_todo > filler_lines - n_virt_lines) {
- // TODO(bfredl): check this doesn't inhibit TUI-style
- // clear-to-end-of-line.
- c_extra = ' ';
- c_final = NUL;
- if (wp->w_p_rl) {
- n_extra = col + 1;
- } else {
- n_extra = grid->cols - col;
- }
- char_attr = 0;
- } else if (filler_todo > 0) {
- // draw "deleted" diff line(s)
- if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
- c_extra = '-';
- c_final = NUL;
- } else {
- c_extra = wp->w_p_fcs_chars.diff;
- c_final = NUL;
- }
- if (wp->w_p_rl) {
- n_extra = col + 1;
- } else {
- n_extra = grid->cols - col;
- }
- char_attr = win_hl_attr(wp, HLF_DED);
- }
- char_u *const sbr = get_showbreak_value(wp);
- if (*sbr != NUL && need_showbreak) {
- // Draw 'showbreak' at the start of each broken line.
- p_extra = sbr;
- c_extra = NUL;
- c_final = NUL;
- n_extra = (int)STRLEN(sbr);
- char_attr = win_hl_attr(wp, HLF_AT);
- if (wp->w_skipcol == 0 || !wp->w_p_wrap) {
- need_showbreak = false;
- }
- vcol_sbr = vcol + mb_charlen(sbr);
- // Correct end of highlighted area for 'showbreak',
- // required when 'linebreak' is also set.
- if (tocol == vcol) {
- tocol += n_extra;
- }
- // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
- if (cul_attr) {
- char_attr = hl_combine_attr(cul_attr, char_attr);
- }
- }
- }
-
- if (draw_state == WL_LINE - 1 && n_extra == 0) {
- sign_idx = 0;
- draw_state = WL_LINE;
-
- if (has_decor && row == startrow + filler_lines) {
- // hide virt_text on text hidden by 'nowrap'
- decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state);
- }
-
- if (saved_n_extra) {
- // Continue item from end of wrapped line.
- n_extra = saved_n_extra;
- c_extra = saved_c_extra;
- c_final = saved_c_final;
- p_extra = saved_p_extra;
- char_attr = saved_char_attr;
- } else {
- char_attr = 0;
- }
- }
- }
-
- if (cul_screenline && draw_state == WL_LINE
- && vcol >= left_curline_col
- && vcol < right_curline_col) {
- cul_attr = win_hl_attr(wp, HLF_CUL);
- HlAttrs ae = syn_attr2entry(cul_attr);
- if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
- line_attr_lowprio = cul_attr;
- } else {
- if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer)
- && qf_current_entry(wp) == lnum) {
- line_attr = hl_combine_attr(cul_attr, line_attr);
- } else {
- line_attr = cul_attr;
- }
- }
- }
-
- // When still displaying '$' of change command, stop at cursor
- if (((dollar_vcol >= 0
- && wp == curwin
- && lnum == wp->w_cursor.lnum
- && vcol >= (long)wp->w_virtcol)
- || (number_only && draw_state > WL_NR))
- && filler_todo <= 0) {
- draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
- grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp,
- wp->w_hl_attr_normal, false);
- // Pretend we have finished updating the window. Except when
- // 'cursorcolumn' is set.
- if (wp->w_p_cuc) {
- row = wp->w_cline_row + wp->w_cline_height;
- } else {
- row = grid->rows;
- }
- break;
- }
-
- if (draw_state == WL_LINE
- && has_fold
- && col == win_col_offset
- && n_extra == 0
- && row == startrow) {
- char_attr = win_hl_attr(wp, HLF_FL);
-
- linenr_T lnume = lnum + foldinfo.fi_lines - 1;
- memset(buf_fold, ' ', FOLD_TEXT_LEN);
- p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold);
- n_extra = (int)STRLEN(p_extra);
-
- if (p_extra != buf_fold) {
- xfree(p_extra_free);
- p_extra_free = p_extra;
- }
- c_extra = NUL;
- c_final = NUL;
- p_extra[n_extra] = NUL;
- }
-
- if (draw_state == WL_LINE
- && has_fold
- && col < grid->cols
- && n_extra == 0
- && row == startrow) {
- // fill rest of line with 'fold'
- c_extra = wp->w_p_fcs_chars.fold;
- c_final = NUL;
-
- n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col);
- }
-
- if (draw_state == WL_LINE
- && has_fold
- && col >= grid->cols
- && n_extra != 0
- && row == startrow) {
- // Truncate the folding.
- n_extra = 0;
- }
-
- if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
- // handle Visual or match highlighting in this line
- if (vcol == fromcol
- || (vcol + 1 == fromcol && n_extra == 0
- && utf_ptr2cells((char *)ptr) > 1)
- || ((int)vcol_prev == fromcol_prev
- && vcol_prev < vcol // not at margin
- && vcol < tocol)) {
- area_attr = attr; // start highlighting
- if (area_highlighting) {
- area_active = true;
- }
- } else if (area_attr != 0 && (vcol == tocol
- || (noinvcur
- && (colnr_T)vcol == wp->w_virtcol))) {
- area_attr = 0; // stop highlighting
- area_active = false;
- }
-
- if (!n_extra) {
- // Check for start/end of 'hlsearch' and other matches.
- // After end, check for start/end of next match.
- // When another match, have to check for start again.
- v = (ptr - line);
- search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &search_hl, &has_match_conc,
- &match_conc, lcs_eol_one, &search_attr_from_match);
- ptr = line + v; // "line" may have been changed
-
- // Do not allow a conceal over EOL otherwise EOL will be missed
- // and bad things happen.
- if (*ptr == NUL) {
- has_match_conc = 0;
- }
- }
-
- if (diff_hlf != (hlf_T)0) {
- if (diff_hlf == HLF_CHD && ptr - line >= change_start
- && n_extra == 0) {
- diff_hlf = HLF_TXD; // changed text
- }
- if (diff_hlf == HLF_TXD && ptr - line > change_end
- && n_extra == 0) {
- diff_hlf = HLF_CHD; // changed line
- }
- line_attr = win_hl_attr(wp, (int)diff_hlf);
- // Overlay CursorLine onto diff-mode highlight.
- if (cul_attr) {
- line_attr = 0 != line_attr_lowprio // Low-priority CursorLine
- ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr),
- hl_get_underline())
- : hl_combine_attr(line_attr, cul_attr);
- }
- }
-
- // Decide which of the highlight attributes to use.
- attr_pri = true;
-
- if (area_attr != 0) {
- char_attr = hl_combine_attr(line_attr, area_attr);
- if (!highlight_match) {
- // let search highlight show in Visual area if possible
- char_attr = hl_combine_attr(search_attr, char_attr);
- }
- } else if (search_attr != 0) {
- char_attr = hl_combine_attr(line_attr, search_attr);
- }
- // Use line_attr when not in the Visual or 'incsearch' area
- // (area_attr may be 0 when "noinvcur" is set).
- else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL)
- || vcol < fromcol || vcol_prev < fromcol_prev
- || vcol >= tocol)) {
- char_attr = line_attr;
- } else {
- attr_pri = false;
- if (has_syntax) {
- char_attr = syntax_attr;
- } else {
- char_attr = 0;
- }
- }
- }
-
- // Get the next character to put on the screen.
- //
- // The "p_extra" points to the extra stuff that is inserted to
- // represent special characters (non-printable stuff) and other
- // things. When all characters are the same, c_extra is used.
- // If c_final is set, it will compulsorily be used at the end.
- // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past
- // "p_extra[n_extra]".
- // For the '$' of the 'list' option, n_extra == 1, p_extra == "".
- if (n_extra > 0) {
- if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) {
- c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra;
- mb_c = c; // doesn't handle non-utf-8 multi-byte!
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false;
- }
- } else {
- assert(p_extra != NULL);
- c = *p_extra;
- mb_c = c;
- // If the UTF-8 character is more than one byte:
- // Decode it into "mb_c".
- mb_l = utfc_ptr2len((char *)p_extra);
- mb_utf8 = false;
- if (mb_l > n_extra) {
- mb_l = 1;
- } else if (mb_l > 1) {
- mb_c = utfc_ptr2char(p_extra, u8cc);
- mb_utf8 = true;
- c = 0xc0;
- }
- if (mb_l == 0) { // at the NUL at end-of-line
- mb_l = 1;
- }
-
- // If a double-width char doesn't fit display a '>' in the last column.
- if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1))
- && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
- mb_l = 1;
- (void)mb_l;
- multi_attr = win_hl_attr(wp, HLF_AT);
-
- if (cul_attr) {
- multi_attr = 0 != line_attr_lowprio
- ? hl_combine_attr(cul_attr, multi_attr)
- : hl_combine_attr(multi_attr, cul_attr);
- }
-
- // put the pointer back to output the double-width
- // character at the start of the next line.
- n_extra++;
- p_extra--;
- } else {
- n_extra -= mb_l - 1;
- p_extra += mb_l - 1;
- }
- p_extra++;
- }
- n_extra--;
- } else if (foldinfo.fi_lines > 0) {
- // skip writing the buffer line itself
- c = NUL;
- XFREE_CLEAR(p_extra_free);
- } else {
- int c0;
-
- XFREE_CLEAR(p_extra_free);
-
- // Get a character from the line itself.
- c0 = c = *ptr;
- mb_c = c;
- // If the UTF-8 character is more than one byte: Decode it
- // into "mb_c".
- mb_l = utfc_ptr2len((char *)ptr);
- mb_utf8 = false;
- if (mb_l > 1) {
- mb_c = utfc_ptr2char(ptr, u8cc);
- // Overlong encoded ASCII or ASCII with composing char
- // is displayed normally, except a NUL.
- if (mb_c < 0x80) {
- c0 = c = mb_c;
- }
- mb_utf8 = true;
-
- // At start of the line we can have a composing char.
- // Draw it as a space with a composing char.
- if (utf_iscomposing(mb_c)) {
- int i;
-
- for (i = MAX_MCO - 1; i > 0; i--) {
- u8cc[i] = u8cc[i - 1];
- }
- u8cc[0] = mb_c;
- mb_c = ' ';
- }
- }
-
- if ((mb_l == 1 && c >= 0x80)
- || (mb_l >= 1 && mb_c == 0)
- || (mb_l > 1 && (!vim_isprintc(mb_c)))) {
- // Illegal UTF-8 byte: display as <xx>.
- // Non-BMP character : display as ? or fullwidth ?.
- transchar_hex((char *)extra, mb_c);
- if (wp->w_p_rl) { // reverse
- rl_mirror(extra);
- }
-
- p_extra = extra;
- c = *p_extra;
- mb_c = mb_ptr2char_adv((const char_u **)&p_extra);
- mb_utf8 = (c >= 0x80);
- n_extra = (int)STRLEN(p_extra);
- c_extra = NUL;
- c_final = NUL;
- if (area_attr == 0 && search_attr == 0) {
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_8);
- saved_attr2 = char_attr; // save current attr
- }
- } else if (mb_l == 0) { // at the NUL at end-of-line
- mb_l = 1;
- } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (wp->w_p_rl) {
- pc = prev_c;
- pc1 = prev_c1;
- nc = utf_ptr2char((char *)ptr + mb_l);
- prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(ptr + mb_l, pcc);
- nc = prev_c;
- pc1 = pcc[0];
- }
- prev_c = mb_c;
-
- mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc);
- } else {
- prev_c = mb_c;
- }
- // If a double-width char doesn't fit display a '>' in the
- // last column; the character is displayed at the start of the
- // next line.
- if ((wp->w_p_rl ? (col <= 0) :
- (col >= grid->cols - 1))
- && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
- mb_utf8 = false;
- mb_l = 1;
- multi_attr = win_hl_attr(wp, HLF_AT);
- // Put pointer back so that the character will be
- // displayed at the start of the next line.
- ptr--;
- did_decrement_ptr = true;
- } else if (*ptr != NUL) {
- ptr += mb_l - 1;
- }
-
- // If a double-width char doesn't fit at the left side display a '<' in
- // the first column. Don't do this for unprintable characters.
- if (n_skip > 0 && mb_l > 1 && n_extra == 0) {
- n_extra = 1;
- c_extra = MB_FILLER_CHAR;
- c_final = NUL;
- c = ' ';
- if (area_attr == 0 && search_attr == 0) {
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_AT);
- saved_attr2 = char_attr; // save current attr
- }
- mb_c = c;
- mb_utf8 = false;
- mb_l = 1;
- }
- ptr++;
-
- if (extra_check) {
- bool can_spell = true;
-
- /* Get syntax attribute, unless still at the start of the line
- * (double-wide char that doesn't fit). */
- v = (ptr - line);
- if (has_syntax && v > 0) {
- /* Get the syntax attribute for the character. If there
- * is an error, disable syntax highlighting. */
- save_did_emsg = did_emsg;
- did_emsg = FALSE;
-
- syntax_attr = get_syntax_attr((colnr_T)v - 1,
- has_spell ? &can_spell : NULL, false);
-
- if (did_emsg) {
- wp->w_s->b_syn_error = TRUE;
- has_syntax = FALSE;
- } else {
- did_emsg = save_did_emsg;
- }
-
- if (wp->w_s->b_syn_slow) {
- has_syntax = false;
- }
-
- // Need to get the line again, a multi-line regexp may
- // have made it invalid.
- line = ml_get_buf(wp->w_buffer, lnum, false);
- ptr = line + v;
-
- if (!attr_pri) {
- if (cul_attr) {
- char_attr = 0 != line_attr_lowprio
- ? hl_combine_attr(cul_attr, syntax_attr)
- : hl_combine_attr(syntax_attr, cul_attr);
- } else {
- char_attr = syntax_attr;
- }
- } else {
- char_attr = hl_combine_attr(syntax_attr, char_attr);
- }
- // no concealing past the end of the line, it interferes
- // with line highlighting.
- if (c == NUL) {
- syntax_flags = 0;
- } else {
- syntax_flags = get_syntax_info(&syntax_seqnr);
- }
- } else if (!attr_pri) {
- char_attr = 0;
- }
-
- /* Check spelling (unless at the end of the line).
- * Only do this when there is no syntax highlighting, the
- * @Spell cluster is not used or the current syntax item
- * contains the @Spell cluster. */
- v = (ptr - line);
- if (has_spell && v >= word_end && v > cur_checked_col) {
- spell_attr = 0;
- if (!attr_pri) {
- char_attr = syntax_attr;
- }
- if (c != 0 && (!has_syntax || can_spell)) {
- char_u *prev_ptr;
- char_u *p;
- int len;
- hlf_T spell_hlf = HLF_COUNT;
- prev_ptr = ptr - mb_l;
- v -= mb_l - 1;
-
- /* Use nextline[] if possible, it has the start of the
- * next line concatenated. */
- if ((prev_ptr - line) - nextlinecol >= 0) {
- p = nextline + ((prev_ptr - line) - nextlinecol);
- } else {
- p = prev_ptr;
- }
- cap_col -= (int)(prev_ptr - line);
- size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange);
- assert(tmplen <= INT_MAX);
- len = (int)tmplen;
- word_end = (int)v + len;
-
- /* In Insert mode only highlight a word that
- * doesn't touch the cursor. */
- if (spell_hlf != HLF_COUNT
- && (State & MODE_INSERT)
- && wp->w_cursor.lnum == lnum
- && wp->w_cursor.col >=
- (colnr_T)(prev_ptr - line)
- && wp->w_cursor.col < (colnr_T)word_end) {
- spell_hlf = HLF_COUNT;
- spell_redraw_lnum = lnum;
- }
-
- if (spell_hlf == HLF_COUNT && p != prev_ptr
- && (p - nextline) + len > nextline_idx) {
- /* Remember that the good word continues at the
- * start of the next line. */
- checked_lnum = lnum + 1;
- checked_col = (int)((p - nextline) + len - nextline_idx);
- }
-
- // Turn index into actual attributes.
- if (spell_hlf != HLF_COUNT) {
- spell_attr = highlight_attr[spell_hlf];
- }
-
- if (cap_col > 0) {
- if (p != prev_ptr
- && (p - nextline) + cap_col >= nextline_idx) {
- /* Remember that the word in the next line
- * must start with a capital. */
- capcol_lnum = lnum + 1;
- cap_col = (int)((p - nextline) + cap_col
- - nextline_idx);
- } else {
- // Compute the actual column.
- cap_col += (int)(prev_ptr - line);
- }
- }
- }
- }
- if (spell_attr != 0) {
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, spell_attr);
- } else {
- char_attr = hl_combine_attr(spell_attr, char_attr);
- }
- }
-
- if (wp->w_buffer->terminal) {
- char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
- }
-
- if (has_decor && v > 0) {
- bool selected = (area_active || (area_highlighting && noinvcur
- && (colnr_T)vcol == wp->w_virtcol));
- int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off,
- selected, &decor_state);
- if (extmark_attr != 0) {
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, extmark_attr);
- } else {
- char_attr = hl_combine_attr(extmark_attr, char_attr);
- }
- }
-
- decor_conceal = decor_state.conceal;
- if (decor_conceal && decor_state.conceal_char) {
- decor_conceal = 2; // really??
- }
- }
-
- // Found last space before word: check for line break.
- if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
- && !vim_isbreak((int)(*ptr))) {
- int mb_off = utf_head_off(line, ptr - 1);
- char_u *p = ptr - (mb_off + 1);
- // TODO: is passing p for start of the line OK?
- n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1;
-
- // We have just drawn the showbreak value, no need to add
- // space for it again.
- if (vcol == vcol_sbr) {
- n_extra -= mb_charlen(get_showbreak_value(wp));
- if (n_extra < 0) {
- n_extra = 0;
- }
- }
-
- if (c == TAB && n_extra + col > grid->cols) {
- n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts,
- wp->w_buffer->b_p_vts_array) - 1;
- }
- c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
- c_final = NUL;
- if (ascii_iswhite(c)) {
- if (c == TAB) {
- // See "Tab alignment" below.
- FIX_FOR_BOGUSCOLS;
- }
- if (!wp->w_p_list) {
- c = ' ';
- }
- }
- }
-
- in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
- if (!in_multispace) {
- multispace_pos = 0;
- }
-
- // 'list': Change char 160 to 'nbsp' and space to 'space'.
- // But not when the character is followed by a composing
- // character (use mb_l to check that).
- if (wp->w_p_list
- && ((((c == 160 && mb_l == 1)
- || (mb_utf8
- && ((mb_c == 160 && mb_l == 2)
- || (mb_c == 0x202f && mb_l == 3))))
- && wp->w_p_lcs_chars.nbsp)
- || (c == ' '
- && mb_l == 1
- && (wp->w_p_lcs_chars.space
- || (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
- && ptr - line >= leadcol
- && ptr - line <= trailcol))) {
- if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
- c = wp->w_p_lcs_chars.multispace[multispace_pos++];
- if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
- multispace_pos = 0;
- }
- } else {
- c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
- }
- n_attr = 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false;
- }
- }
-
- if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol)
- || (leadcol != 0 && ptr < line + leadcol))) {
- if (leadcol != 0 && in_multispace && ptr < line + leadcol
- && wp->w_p_lcs_chars.leadmultispace != NULL) {
- c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
- if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
- multispace_pos = 0;
- }
- } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
- c = wp->w_p_lcs_chars.trail;
- } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
- c = wp->w_p_lcs_chars.lead;
- } else if (leadcol != 0 && wp->w_p_lcs_chars.space) {
- c = wp->w_p_lcs_chars.space;
- }
-
- n_attr = 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false;
- }
- }
- }
-
- /*
- * Handling of non-printable characters.
- */
- if (!vim_isprintc(c)) {
- // when getting a character from the file, we may have to
- // turn it into something else on the way to putting it on the screen.
- if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
- int tab_len = 0;
- long vcol_adjusted = vcol; // removed showbreak length
- char_u *const sbr = get_showbreak_value(wp);
-
- // Only adjust the tab_len, when at the first column after the
- // showbreak value was drawn.
- if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) {
- vcol_adjusted = vcol - mb_charlen(sbr);
- }
- // tab amount depends on current column
- tab_len = tabstop_padding((colnr_T)vcol_adjusted,
- wp->w_buffer->b_p_ts,
- wp->w_buffer->b_p_vts_array) - 1;
-
- if (!wp->w_p_lbr || !wp->w_p_list) {
- n_extra = tab_len;
- } else {
- char_u *p;
- int i;
- int saved_nextra = n_extra;
-
- if (vcol_off > 0) {
- // there are characters to conceal
- tab_len += vcol_off;
- }
- // boguscols before FIX_FOR_BOGUSCOLS macro from above.
- if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0
- && n_extra > tab_len) {
- tab_len += n_extra - tab_len;
- }
-
- // If n_extra > 0, it gives the number of chars
- // to use for a tab, else we need to calculate the width
- // for a tab.
- int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2));
- if (wp->w_p_lcs_chars.tab3) {
- len += utf_char2len(wp->w_p_lcs_chars.tab3);
- }
- if (n_extra > 0) {
- len += n_extra - tab_len;
- }
- c = wp->w_p_lcs_chars.tab1;
- p = xmalloc((size_t)len + 1);
- memset(p, ' ', (size_t)len);
- p[len] = NUL;
- xfree(p_extra_free);
- p_extra_free = p;
- for (i = 0; i < tab_len; i++) {
- if (*p == NUL) {
- tab_len = i;
- break;
- }
- int lcs = wp->w_p_lcs_chars.tab2;
-
- // if tab3 is given, use it for the last char
- if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
- lcs = wp->w_p_lcs_chars.tab3;
- }
- p += utf_char2bytes(lcs, (char *)p);
- n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
- }
- p_extra = p_extra_free;
-
- // n_extra will be increased by FIX_FOX_BOGUSCOLS
- // macro below, so need to adjust for that here
- if (vcol_off > 0) {
- n_extra -= vcol_off;
- }
- }
-
- {
- int vc_saved = vcol_off;
-
- // Tab alignment should be identical regardless of
- // 'conceallevel' value. So tab compensates of all
- // previous concealed characters, and thus resets
- // vcol_off and boguscols accumulated so far in the
- // line. Note that the tab can be longer than
- // 'tabstop' when there are concealed characters.
- FIX_FOR_BOGUSCOLS;
-
- // Make sure, the highlighting for the tab char will be
- // correctly set further below (effectively reverts the
- // FIX_FOR_BOGSUCOLS macro).
- if (n_extra == tab_len + vc_saved && wp->w_p_list
- && wp->w_p_lcs_chars.tab1) {
- tab_len += vc_saved;
- }
- }
-
- mb_utf8 = false; // don't draw as UTF-8
- if (wp->w_p_list) {
- c = (n_extra == 0 && wp->w_p_lcs_chars.tab3)
- ? wp->w_p_lcs_chars.tab3
- : wp->w_p_lcs_chars.tab1;
- if (wp->w_p_lbr) {
- c_extra = NUL; // using p_extra from above
- } else {
- c_extra = wp->w_p_lcs_chars.tab2;
- }
- c_final = wp->w_p_lcs_chars.tab3;
- n_attr = tab_len + 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- }
- } else {
- c_final = NUL;
- c_extra = ' ';
- c = ' ';
- }
- } else if (c == NUL
- && (wp->w_p_list
- || ((fromcol >= 0 || fromcol_prev >= 0)
- && tocol > vcol
- && VIsual_mode != Ctrl_V
- && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))
- && !(noinvcur
- && lnum == wp->w_cursor.lnum
- && (colnr_T)vcol == wp->w_virtcol)))
- && lcs_eol_one > 0) {
- // Display a '$' after the line or highlight an extra
- // character if the line break is included.
- // For a diff line the highlighting continues after the "$".
- if (diff_hlf == (hlf_T)0
- && line_attr == 0
- && line_attr_lowprio == 0) {
- // In virtualedit, visual selections may extend beyond end of line
- if (area_highlighting && virtual_active()
- && tocol != MAXCOL && vcol < tocol) {
- n_extra = 0;
- } else {
- p_extra = at_end_str;
- n_extra = 1;
- c_extra = NUL;
- c_final = NUL;
- }
- }
- if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
- c = wp->w_p_lcs_chars.eol;
- } else {
- c = ' ';
- }
- lcs_eol_one = -1;
- ptr--; // put it back at the NUL
- extra_attr = win_hl_attr(wp, HLF_AT);
- n_attr = 1;
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false; // don't draw as UTF-8
- }
- } else if (c != NUL) {
- p_extra = transchar_buf(wp->w_buffer, c);
- if (n_extra == 0) {
- n_extra = byte2cells(c) - 1;
- }
- if ((dy_flags & DY_UHEX) && wp->w_p_rl) {
- rl_mirror(p_extra); // reverse "<12>"
- }
- c_extra = NUL;
- c_final = NUL;
- if (wp->w_p_lbr) {
- char_u *p;
-
- c = *p_extra;
- p = xmalloc((size_t)n_extra + 1);
- memset(p, ' ', (size_t)n_extra);
- STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); // NOLINT(runtime/printf)
- p[n_extra] = NUL;
- xfree(p_extra_free);
- p_extra_free = p_extra = p;
- } else {
- n_extra = byte2cells(c) - 1;
- c = *p_extra++;
- }
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_8);
- saved_attr2 = char_attr; // save current attr
- mb_utf8 = false; // don't draw as UTF-8
- } else if (VIsual_active
- && (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
- && virtual_active()
- && tocol != MAXCOL
- && vcol < tocol
- && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) {
- c = ' ';
- ptr--; // put it back at the NUL
- }
- }
-
- if (wp->w_p_cole > 0
- && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp))
- && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0)
- && !(lnum_in_visual_area && vim_strchr((char *)wp->w_p_cocu, 'v') == NULL)) {
- char_attr = conceal_attr;
- if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0)
- || has_match_conc > 1 || decor_conceal > 1)
- && (syn_get_sub_char() != NUL
- || (has_match_conc && match_conc)
- || (decor_conceal && decor_state.conceal_char)
- || wp->w_p_cole == 1)
- && wp->w_p_cole != 3) {
- // First time at this concealed item: display one
- // character.
- if (has_match_conc && match_conc) {
- c = match_conc;
- } else if (decor_conceal && decor_state.conceal_char) {
- c = decor_state.conceal_char;
- if (decor_state.conceal_attr) {
- char_attr = decor_state.conceal_attr;
- }
- } else if (syn_get_sub_char() != NUL) {
- c = syn_get_sub_char();
- } else if (wp->w_p_lcs_chars.conceal != NUL) {
- c = wp->w_p_lcs_chars.conceal;
- } else {
- c = ' ';
- }
-
- prev_syntax_id = syntax_seqnr;
-
- if (n_extra > 0) {
- vcol_off += n_extra;
- }
- vcol += n_extra;
- if (wp->w_p_wrap && n_extra > 0) {
- if (wp->w_p_rl) {
- col -= n_extra;
- boguscols -= n_extra;
- } else {
- boguscols += n_extra;
- col += n_extra;
- }
- }
- n_extra = 0;
- n_attr = 0;
- } else if (n_skip == 0) {
- is_concealing = true;
- n_skip = 1;
- }
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false; // don't draw as UTF-8
- }
- } else {
- prev_syntax_id = 0;
- is_concealing = false;
- }
-
- if (n_skip > 0 && did_decrement_ptr) {
- // not showing the '>', put pointer back to avoid getting stuck
- ptr++;
- }
- } // end of printing from buffer content
-
- /* In the cursor line and we may be concealing characters: correct
- * the cursor column when we reach its position. */
- if (!did_wcol && draw_state == WL_LINE
- && wp == curwin && lnum == wp->w_cursor.lnum
- && conceal_cursor_line(wp)
- && (int)wp->w_virtcol <= vcol + n_skip) {
- if (wp->w_p_rl) {
- wp->w_wcol = grid->cols - col + boguscols - 1;
- } else {
- wp->w_wcol = col - boguscols;
- }
- wp->w_wrow = row;
- did_wcol = true;
- wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
- }
-
- // Don't override visual selection highlighting.
- if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) {
- char_attr = hl_combine_attr(char_attr, extra_attr);
- }
-
- // Handle the case where we are in column 0 but not on the first
- // character of the line and the user wants us to show us a
- // special character (via 'listchars' option "precedes:<char>".
- if (lcs_prec_todo != NUL
- && wp->w_p_list
- && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
- && filler_todo <= 0
- && draw_state > WL_NR
- && c != NUL) {
- c = wp->w_p_lcs_chars.prec;
- lcs_prec_todo = NUL;
- if (utf_char2cells(mb_c) > 1) {
- // Double-width character being overwritten by the "precedes"
- // character, need to fill up half the character.
- c_extra = MB_FILLER_CHAR;
- c_final = NUL;
- n_extra = 1;
- n_attr = 2;
- extra_attr = win_hl_attr(wp, HLF_AT);
- }
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false; // don't draw as UTF-8
- }
- saved_attr3 = char_attr; // save current attr
- char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
- n_attr3 = 1;
- }
-
- // At end of the text line or just after the last character.
- if (c == NUL && eol_hl_off == 0) {
- // flag to indicate whether prevcol equals startcol of search_hl or
- // one of the matches
- bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &search_hl,
- (long)(ptr - line) - 1);
-
- // Invert at least one char, used for Visual and empty line or
- // highlight match at end of line. If it's beyond the last
- // char on the screen, just overwrite that one (tricky!) Not
- // needed when a '$' was displayed for 'list'.
- if (wp->w_p_lcs_chars.eol == lcs_eol_one
- && ((area_attr != 0 && vcol == fromcol
- && (VIsual_mode != Ctrl_V
- || lnum == VIsual.lnum
- || lnum == curwin->w_cursor.lnum))
- // highlight 'hlsearch' match at end of line
- || prevcol_hl_flag)) {
- int n = 0;
-
- if (wp->w_p_rl) {
- if (col < 0) {
- n = 1;
- }
- } else {
- if (col >= grid->cols) {
- n = -1;
- }
- }
- if (n != 0) {
- // At the window boundary, highlight the last character
- // instead (better than nothing).
- off += n;
- col += n;
- } else {
- // Add a blank character to highlight.
- schar_from_ascii(linebuf_char[off], ' ');
- }
- if (area_attr == 0 && !has_fold) {
- // Use attributes from match with highest priority among
- // 'search_hl' and the match list.
- get_search_match_hl(wp, &search_hl, (long)(ptr - line), &char_attr);
- }
-
- int eol_attr = char_attr;
- if (cul_attr) {
- eol_attr = hl_combine_attr(cul_attr, eol_attr);
- }
- linebuf_attr[off] = eol_attr;
- if (wp->w_p_rl) {
- --col;
- --off;
- } else {
- ++col;
- ++off;
- }
- ++vcol;
- eol_hl_off = 1;
- }
- // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
- if (wp->w_p_wrap) {
- v = wp->w_skipcol;
- } else {
- v = wp->w_leftcol;
- }
-
- // check if line ends before left margin
- if (vcol < v + col - win_col_off(wp)) {
- vcol = v + col - win_col_off(wp);
- }
- // Get rid of the boguscols now, we want to draw until the right
- // edge for 'cursorcolumn'.
- col -= boguscols;
- // boguscols = 0; // Disabled because value never read after this
-
- if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
- }
-
- bool has_virttext = false;
- // Make sure alignment is the same regardless
- // if listchars=eol:X is used or not.
- int eol_skip = (wp->w_p_lcs_chars.eol == lcs_eol_one && eol_hl_off == 0
- ? 1 : 0);
-
- if (has_decor) {
- has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
- col + eol_skip);
- }
-
- if (((wp->w_p_cuc
- && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
- && (int)wp->w_virtcol <
- (long)grid->cols * (row - startrow + 1) + v
- && lnum != wp->w_cursor.lnum)
- || draw_color_col || line_attr_lowprio || line_attr
- || diff_hlf != (hlf_T)0 || has_virttext)) {
- int rightmost_vcol = 0;
- int i;
-
- if (wp->w_p_cuc) {
- rightmost_vcol = wp->w_virtcol;
- }
-
- if (draw_color_col) {
- // determine rightmost colorcolumn to possibly draw
- for (i = 0; color_cols[i] >= 0; i++) {
- if (rightmost_vcol < color_cols[i]) {
- rightmost_vcol = color_cols[i];
- }
- }
- }
-
- int cuc_attr = win_hl_attr(wp, HLF_CUC);
- int mc_attr = win_hl_attr(wp, HLF_MC);
-
- int diff_attr = 0;
- if (diff_hlf == HLF_TXD) {
- diff_hlf = HLF_CHD;
- }
- if (diff_hlf != 0) {
- diff_attr = win_hl_attr(wp, (int)diff_hlf);
- }
-
- int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
- if (base_attr || line_attr || has_virttext) {
- rightmost_vcol = INT_MAX;
- }
-
- int col_stride = wp->w_p_rl ? -1 : 1;
-
- while (wp->w_p_rl ? col >= 0 : col < grid->cols) {
- schar_from_ascii(linebuf_char[off], ' ');
- col += col_stride;
- if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
- }
-
- int col_attr = base_attr;
-
- if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
- col_attr = cuc_attr;
- } else if (draw_color_col && VCOL_HLC == *color_cols) {
- col_attr = mc_attr;
- // Draw the colorcolumn character.
- c = wp->w_p_fcs_chars.colorcol;
- schar_from_char(linebuf_char[off], c);
- }
-
- col_attr = hl_combine_attr(col_attr, line_attr);
-
- linebuf_attr[off] = col_attr;
- off += col_stride;
-
- if (VCOL_HLC >= rightmost_vcol) {
- break;
- }
-
- vcol += 1;
- }
- }
-
- // TODO(bfredl): integrate with the common beyond-the-end-loop
- if (wp->w_buffer->terminal) {
- // terminal buffers may need to highlight beyond the end of the
- // logical line
- int n = wp->w_p_rl ? -1 : 1;
- while (col >= 0 && col < grid->cols) {
- schar_from_ascii(linebuf_char[off], ' ');
- linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol];
- off += n;
- vcol += n;
- col += n;
- }
- }
-
- draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
- grid_put_linebuf(grid, row, 0, col, grid->cols, wp->w_p_rl, wp,
- wp->w_hl_attr_normal, false);
- row++;
-
- /*
- * Update w_cline_height and w_cline_folded if the cursor line was
- * updated (saves a call to plines_win() later).
- */
- if (wp == curwin && lnum == curwin->w_cursor.lnum) {
- curwin->w_cline_row = startrow;
- curwin->w_cline_height = row - startrow;
- curwin->w_cline_folded = foldinfo.fi_lines > 0;
- curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
- conceal_cursor_used = conceal_cursor_line(curwin);
- }
- break;
- }
-
- // Show "extends" character from 'listchars' if beyond the line end and
- // 'list' is set.
- if (wp->w_p_lcs_chars.ext != NUL
- && draw_state == WL_LINE
- && wp->w_p_list
- && !wp->w_p_wrap
- && filler_todo <= 0
- && (wp->w_p_rl ? col == 0 : col == grid->cols - 1)
- && !has_fold
- && (*ptr != NUL
- || lcs_eol_one > 0
- || (n_extra && (c_extra != NUL || *p_extra != NUL)))) {
- c = wp->w_p_lcs_chars.ext;
- char_attr = win_hl_attr(wp, HLF_AT);
- mb_c = c;
- if (utf_char2len(c) > 1) {
- mb_utf8 = true;
- u8cc[0] = 0;
- c = 0xc0;
- } else {
- mb_utf8 = false;
- }
- }
-
- // advance to the next 'colorcolumn'
- if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
- }
-
- // Highlight the cursor column if 'cursorcolumn' is set. But don't
- // highlight the cursor position itself.
- // Also highlight the 'colorcolumn' if it is different than
- // 'cursorcolumn'
- // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak'
- // options are set
- vcol_save_attr = -1;
- if ((draw_state == WL_LINE
- || draw_state == WL_BRI
- || draw_state == WL_SBR)
- && !lnum_in_visual_area
- && search_attr == 0
- && area_attr == 0
- && filler_todo <= 0) {
- if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
- && lnum != wp->w_cursor.lnum) {
- vcol_save_attr = char_attr;
- char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);
- } else if (draw_color_col && VCOL_HLC == *color_cols) {
- vcol_save_attr = char_attr;
- char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr);
- }
- }
-
- // Apply lowest-priority line attr now, so everything can override it.
- if (draw_state == WL_LINE) {
- char_attr = hl_combine_attr(line_attr_lowprio, char_attr);
- }
-
- // Store character to be displayed.
- // Skip characters that are left of the screen for 'nowrap'.
- vcol_prev = vcol;
- if (draw_state < WL_LINE || n_skip <= 0) {
- //
- // Store the character.
- //
- if (wp->w_p_rl && utf_char2cells(mb_c) > 1) {
- // A double-wide character is: put first half in left cell.
- off--;
- col--;
- }
- if (mb_utf8) {
- schar_from_cc(linebuf_char[off], mb_c, u8cc);
- } else {
- schar_from_ascii(linebuf_char[off], (char)c);
- }
- if (multi_attr) {
- linebuf_attr[off] = multi_attr;
- multi_attr = 0;
- } else {
- linebuf_attr[off] = char_attr;
- }
-
- if (utf_char2cells(mb_c) > 1) {
- // Need to fill two screen columns.
- off++;
- col++;
- // UTF-8: Put a 0 in the second screen char.
- linebuf_char[off][0] = 0;
- if (draw_state > WL_NR && filler_todo <= 0) {
- vcol++;
- }
- // When "tocol" is halfway through a character, set it to the end of
- // the character, otherwise highlighting won't stop.
- if (tocol == vcol) {
- tocol++;
- }
- if (wp->w_p_rl) {
- // now it's time to backup one cell
- --off;
- --col;
- }
- }
- if (wp->w_p_rl) {
- --off;
- --col;
- } else {
- ++off;
- ++col;
- }
- } else if (wp->w_p_cole > 0 && is_concealing) {
- --n_skip;
- ++vcol_off;
- if (n_extra > 0) {
- vcol_off += n_extra;
- }
- if (wp->w_p_wrap) {
- /*
- * Special voodoo required if 'wrap' is on.
- *
- * Advance the column indicator to force the line
- * drawing to wrap early. This will make the line
- * take up the same screen space when parts are concealed,
- * so that cursor line computations aren't messed up.
- *
- * To avoid the fictitious advance of 'col' causing
- * trailing junk to be written out of the screen line
- * we are building, 'boguscols' keeps track of the number
- * of bad columns we have advanced.
- */
- if (n_extra > 0) {
- vcol += n_extra;
- if (wp->w_p_rl) {
- col -= n_extra;
- boguscols -= n_extra;
- } else {
- col += n_extra;
- boguscols += n_extra;
- }
- n_extra = 0;
- n_attr = 0;
- }
-
- if (utf_char2cells(mb_c) > 1) {
- // Need to fill two screen columns.
- if (wp->w_p_rl) {
- --boguscols;
- --col;
- } else {
- ++boguscols;
- ++col;
- }
- }
-
- if (wp->w_p_rl) {
- --boguscols;
- --col;
- } else {
- ++boguscols;
- ++col;
- }
- } else {
- if (n_extra > 0) {
- vcol += n_extra;
- n_extra = 0;
- n_attr = 0;
- }
- }
- } else {
- --n_skip;
- }
-
- /* Only advance the "vcol" when after the 'number' or 'relativenumber'
- * column. */
- if (draw_state > WL_NR
- && filler_todo <= 0) {
- ++vcol;
- }
-
- if (vcol_save_attr >= 0) {
- char_attr = vcol_save_attr;
- }
-
- // restore attributes after "predeces" in 'listchars'
- if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) {
- char_attr = saved_attr3;
- }
-
- // restore attributes after last 'listchars' or 'number' char
- if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) {
- char_attr = saved_attr2;
- }
-
- /*
- * At end of screen line and there is more to come: Display the line
- * so far. If there is no more to display it is caught above.
- */
- if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols))
- && foldinfo.fi_lines == 0
- && (draw_state != WL_LINE
- || *ptr != NUL
- || filler_todo > 0
- || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
- && p_extra != at_end_str)
- || (n_extra != 0
- && (c_extra != NUL || *p_extra != NUL)))) {
- bool wrap = wp->w_p_wrap // Wrapping enabled.
- && filler_todo <= 0 // Not drawing diff filler lines.
- && lcs_eol_one != -1 // Haven't printed the lcs_eol character.
- && row != endrow - 1 // Not the last line being displayed.
- && (grid->cols == Columns // Window spans the width of the screen,
- || ui_has(kUIMultigrid)) // or has dedicated grid.
- && !wp->w_p_rl; // Not right-to-left.
-
- int draw_col = col - boguscols;
- if (filler_todo > 0) {
- int index = filler_todo - (filler_lines - n_virt_lines);
- if (index > 0) {
- int i = (int)kv_size(virt_lines) - index;
- assert(i >= 0);
- int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset;
- draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line,
- kHlModeReplace, grid->cols, offset);
- }
- } else {
- draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row);
- }
-
- grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl,
- wp, wp->w_hl_attr_normal, wrap);
- if (wrap) {
- ScreenGrid *current_grid = grid;
- int current_row = row, dummy_col = 0; // dummy_col unused
- grid_adjust(&current_grid, &current_row, &dummy_col);
-
- // Force a redraw of the first column of the next line.
- current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1;
-
- // Remember that the line wraps, used for modeless copy.
- current_grid->line_wraps[current_row] = true;
- }
-
- boguscols = 0;
- row++;
-
- /* When not wrapping and finished diff lines, or when displayed
- * '$' and highlighting until last column, break here. */
- if ((!wp->w_p_wrap
- && filler_todo <= 0
- ) || lcs_eol_one == -1) {
- break;
- }
-
- // When the window is too narrow draw all "@" lines.
- if (draw_state != WL_LINE && filler_todo <= 0) {
- win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT);
- row = endrow;
- }
-
- // When line got too long for screen break here.
- if (row == endrow) {
- ++row;
- break;
- }
-
- col = 0;
- off = 0;
- if (wp->w_p_rl) {
- col = grid->cols - 1; // col is not used if breaking!
- off += col;
- }
-
- // reset the drawing state for the start of a wrapped line
- draw_state = WL_START;
- saved_n_extra = n_extra;
- saved_p_extra = p_extra;
- saved_c_extra = c_extra;
- saved_c_final = c_final;
- saved_char_attr = char_attr;
- n_extra = 0;
- lcs_prec_todo = wp->w_p_lcs_chars.prec;
- if (filler_todo <= 0) {
- need_showbreak = true;
- }
- filler_todo--;
- // When the filler lines are actually below the last line of the
- // file, don't draw the line itself, break here.
- if (filler_todo == 0 && (wp->w_botfill || end_fill)) {
- break;
- }
- }
- } // for every character in the line
-
- // After an empty line check first word for capital.
- if (*skipwhite((char *)line) == NUL) {
- capcol_lnum = lnum + 1;
- cap_col = 0;
- }
-
- kv_destroy(virt_lines);
- xfree(p_extra_free);
- return row;
-}
-
-void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, int win_row)
-{
- DecorState *state = &decor_state;
- int right_pos = max_col;
- bool do_eol = state->eol_col > -1;
- for (size_t i = 0; i < kv_size(state->active); i++) {
- DecorRange *item = &kv_A(state->active, i);
- if (!(item->start_row == state->row
- && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) {
- continue;
- }
- if (item->win_col == -1) {
- if (item->decor.virt_text_pos == kVTRightAlign) {
- right_pos -= item->decor.virt_text_width;
- item->win_col = right_pos;
- } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
- item->win_col = state->eol_col;
- } else if (item->decor.virt_text_pos == kVTWinCol) {
- item->win_col = MAX(item->decor.col + col_off, 0);
- }
- }
- if (item->win_col < 0) {
- continue;
- }
- int col;
- if (item->decor.ui_watched) {
- // send mark position to UI
- col = item->win_col;
- WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col };
- kv_push(win_extmark_arr, m);
- }
- if (kv_size(item->decor.virt_text)) {
- col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text,
- item->decor.hl_mode, max_col, item->win_col - col_off);
- }
- item->win_col = -2; // deactivate
- if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
- state->eol_col = col + 1;
- }
-
- *end_col = MAX(*end_col, col);
- }
-}
-
-static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col,
- int vcol)
-{
- LineState s = LINE_STATE("");
- int virt_attr = 0;
- size_t virt_pos = 0;
-
- while (col < max_col) {
- if (!*s.p) {
- if (virt_pos >= kv_size(vt)) {
- break;
- }
- virt_attr = 0;
- do {
- s.p = kv_A(vt, virt_pos).text;
- int hl_id = kv_A(vt, virt_pos).hl_id;
- virt_attr = hl_combine_attr(virt_attr,
- hl_id > 0 ? syn_id2attr(hl_id) : 0);
- virt_pos++;
- } while (!s.p && virt_pos < kv_size(vt));
- if (!s.p) {
- break;
- }
- }
- if (!*s.p) {
- continue;
- }
- int attr;
- bool through = false;
- if (hl_mode == kHlModeCombine) {
- attr = hl_combine_attr(linebuf_attr[col], virt_attr);
- } else if (hl_mode == kHlModeBlend) {
- through = (*s.p == ' ');
- attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
- } else {
- attr = virt_attr;
- }
- schar_T dummy[2];
- int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col],
- max_col - col, false, vcol);
- // if we failed to emit a char, we still need to advance
- cells = MAX(cells, 1);
-
- for (int c = 0; c < cells; c++) {
- linebuf_attr[col++] = attr;
- }
- vcol += cells;
- }
- return col;
-}
-
-// Return true if CursorLineSign highlight is to be used.
-static bool use_cursor_line_sign(win_T *wp, linenr_T lnum)
-{
- return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
- && (wp->w_p_culopt_flags & CULOPT_NBR);
-}
-
-/// Return true if CursorLineNr highlight is to be used for the number column.
-///
-/// - 'cursorline' must be set
-/// - lnum must be the cursor line
-/// - 'cursorlineopt' has "number"
-/// - don't highlight filler lines (when in diff mode)
-/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
-/// itself on the first screenline of the wrapped line, otherwise highlight the number column of
-/// all screenlines of the wrapped line.
-static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
-{
- return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
- && (wp->w_p_culopt_flags & CULOPT_NBR)
- && (row == startrow + filler_lines
- || (row > startrow + filler_lines
- && (wp->w_p_culopt_flags & CULOPT_LINE)));
-}
-
-static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines,
- sign_attrs_T *sattrs)
-{
- sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1);
- if (num_sattr != NULL) {
- // :sign defined with "numhl" highlight.
- return num_sattr->sat_numhl;
- }
-
- if (wp->w_p_rnu) {
- if (lnum < wp->w_cursor.lnum) {
- // Use LineNrAbove
- return win_hl_attr(wp, HLF_LNA);
- }
- if (lnum > wp->w_cursor.lnum) {
- // Use LineNrBelow
- return win_hl_attr(wp, HLF_LNB);
- }
- }
-
- if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) {
- // TODO(vim): Can we use CursorLine instead of CursorLineNr
- // when CursorLineNr isn't set?
- return win_hl_attr(wp, HLF_CLN);
- }
-
- return win_hl_attr(wp, HLF_N);
-}
-
-// Get information needed to display the sign in line 'lnum' in window 'wp'.
-// If 'nrcol' is TRUE, the sign is going to be displayed in the number column.
-// Otherwise the sign is going to be displayed in the sign column.
-//
-// @param count max number of signs
-// @param[out] n_extrap number of characters from pp_extra to display
-// @param sign_idxp Index of the displayed sign
-static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[],
- int row, int startrow, int filler_lines, int filler_todo,
- int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size,
- char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx)
-{
- // Draw cells with the sign value or blank.
- *c_extrap = ' ';
- *c_finalp = NUL;
- if (nrcol) {
- *n_extrap = number_width(wp) + 1;
- } else {
- if (use_cursor_line_sign(wp, lnum)) {
- *char_attrp = win_hl_attr(wp, HLF_CLS);
- } else {
- *char_attrp = win_hl_attr(wp, HLF_SC);
- }
- *n_extrap = win_signcol_width(wp);
- }
-
- if (row == startrow + filler_lines && filler_todo <= 0) {
- sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, sign_idx, wp->w_scwidth);
- if (sattr != NULL) {
- *pp_extra = sattr->sat_text;
- if (*pp_extra != NULL) {
- *c_extrap = NUL;
- *c_finalp = NUL;
-
- if (nrcol) {
- int n, width = number_width(wp) - 2;
- for (n = 0; n < width; n++) {
- extra[n] = ' ';
- }
- extra[n] = NUL;
- STRCAT(extra, *pp_extra);
- STRCAT(extra, " ");
- *pp_extra = extra;
- *n_extrap = (int)STRLEN(*pp_extra);
- } else {
- int symbol_blen = (int)STRLEN(*pp_extra);
-
- // TODO(oni-link): Is sign text already extended to
- // full cell width?
- assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra)));
- // symbol(s) bytes + (filling spaces) (one byte each)
- *n_extrap = symbol_blen + win_signcol_width(wp) -
- (int)mb_string2cells((char *)(*pp_extra));
-
- assert(extra_size > (size_t)symbol_blen);
- memset(extra, ' ', extra_size);
- memcpy(extra, *pp_extra, (size_t)symbol_blen);
-
- *pp_extra = extra;
- (*pp_extra)[*n_extrap] = NUL;
- }
- }
-
- if (use_cursor_line_sign(wp, lnum) && sattr->sat_culhl > 0) {
- *char_attrp = sattr->sat_culhl;
- } else {
- *char_attrp = sattr->sat_texthl;
- }
- }
- }
-}
-
-/*
- * Mirror text "str" for right-left displaying.
- * Only works for single-byte characters (e.g., numbers).
- */
+/// Mirror text "str" for right-left displaying.
+/// Only works for single-byte characters (e.g., numbers).
void rl_mirror(char_u *str)
{
char_u *p1, *p2;
char_u t;
- for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; ++p1, --p2) {
+ for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; p1++, p2--) {
t = *p1;
*p1 = *p2;
*p2 = t;
}
}
-/// Mark all status lines and window bars for redraw; used after first :cd
-void status_redraw_all(void)
-{
- bool is_stl_global = global_stl_height() != 0;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin)
- || wp->w_winbar_height) {
- wp->w_redr_status = true;
- redraw_later(wp, VALID);
- }
- }
-}
-
-/// Marks all status lines and window bars of the current buffer for redraw.
-void status_redraw_curbuf(void)
+/// Get the length of an item as it will be shown in the status line.
+static int status_match_len(expand_T *xp, char_u *s)
{
- status_redraw_buf(curbuf);
-}
+ int len = 0;
-/// Marks all status lines and window bars of the given buffer for redraw.
-void status_redraw_buf(buf_T *buf)
-{
- bool is_stl_global = global_stl_height() != 0;
+ int emenu = (xp->xp_context == EXPAND_MENUS
+ || xp->xp_context == EXPAND_MENUNAMES);
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height)
- || (is_stl_global && wp == curwin) || wp->w_winbar_height)) {
- wp->w_redr_status = true;
- redraw_later(wp, VALID);
- }
+ // Check for menu separators - replace with '|'.
+ if (emenu && menu_is_separator((char *)s)) {
+ return 1;
}
-}
-/*
- * Redraw all status lines that need to be redrawn.
- */
-void redraw_statuslines(void)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_redr_status) {
- win_redr_winbar(wp);
- win_redr_status(wp);
- }
- }
- if (redraw_tabline) {
- draw_tabline();
+ while (*s != NUL) {
+ s += skip_status_match_char(xp, s);
+ len += ptr2cells((char *)s);
+ MB_PTR_ADV(s);
}
+
+ return len;
}
-/*
- * Redraw all status lines at the bottom of frame "frp".
- */
+/// Redraw all status lines at the bottom of frame "frp".
void win_redraw_last_status(const frame_T *frp)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -4615,131 +290,8 @@ void win_redraw_last_status(const frame_T *frp)
}
}
-/// Draw the vertical separator right of window "wp"
-static void draw_vsep_win(win_T *wp)
-{
- int hl;
- int c;
-
- if (wp->w_vsep_width) {
- // draw the vertical separator right of this window
- c = fillchar_vsep(wp, &hl);
- grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp),
- W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl);
- }
-}
-
-/// Draw the horizontal separator below window "wp"
-static void draw_hsep_win(win_T *wp)
-{
- int hl;
- int c;
-
- if (wp->w_hsep_height) {
- // draw the horizontal separator below this window
- c = fillchar_hsep(wp, &hl);
- grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1,
- wp->w_wincol, W_ENDCOL(wp), c, c, hl);
- }
-}
-
-/// Get the separator connector for specified window corner of window "wp"
-static int get_corner_sep_connector(win_T *wp, WindowCorner corner)
-{
- // It's impossible for windows to be connected neither vertically nor horizontally
- // So if they're not vertically connected, assume they're horizontally connected
- if (vsep_connected(wp, corner)) {
- if (hsep_connected(wp, corner)) {
- return wp->w_p_fcs_chars.verthoriz;
- } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) {
- return wp->w_p_fcs_chars.vertright;
- } else {
- return wp->w_p_fcs_chars.vertleft;
- }
- } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) {
- return wp->w_p_fcs_chars.horizdown;
- } else {
- return wp->w_p_fcs_chars.horizup;
- }
-}
-
-/// Draw separator connecting characters on the corners of window "wp"
-static void draw_sep_connectors_win(win_T *wp)
-{
- // Don't draw separator connectors unless global statusline is enabled and the window has
- // either a horizontal or vertical separator
- if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) {
- return;
- }
-
- int hl = win_hl_attr(wp, HLF_C);
-
- // Determine which edges of the screen the window is located on so we can avoid drawing separators
- // on corners contained in those edges
- bool win_at_top;
- bool win_at_bottom = wp->w_hsep_height == 0;
- bool win_at_left;
- bool win_at_right = wp->w_vsep_width == 0;
- frame_T *frp;
-
- for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) {
- if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) {
- break;
- }
- }
- win_at_top = frp->fr_parent == NULL;
- for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) {
- if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) {
- break;
- }
- }
- win_at_left = frp->fr_parent == NULL;
-
- // Draw the appropriate separator connector in every corner where drawing them is necessary
- if (!(win_at_top || win_at_left)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT),
- wp->w_winrow - 1, wp->w_wincol - 1, hl);
- }
- if (!(win_at_top || win_at_right)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT),
- wp->w_winrow - 1, W_ENDCOL(wp), hl);
- }
- if (!(win_at_bottom || win_at_left)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT),
- W_ENDROW(wp), wp->w_wincol - 1, hl);
- }
- if (!(win_at_bottom || win_at_right)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT),
- W_ENDROW(wp), W_ENDCOL(wp), hl);
- }
-}
-
-/// Get the length of an item as it will be shown in the status line.
-static int status_match_len(expand_T *xp, char_u *s)
-{
- int len = 0;
-
- int emenu = (xp->xp_context == EXPAND_MENUS
- || xp->xp_context == EXPAND_MENUNAMES);
-
- // Check for menu separators - replace with '|'.
- if (emenu && menu_is_separator((char *)s)) {
- return 1;
- }
-
- while (*s != NUL) {
- s += skip_status_match_char(xp, s);
- len += ptr2cells((char *)s);
- MB_PTR_ADV(s);
- }
-
- return len;
-}
-
-/*
- * Return the number of characters that should be skipped in a status match.
- * These are backslashes used for escaping. Do show backslashes in help tags.
- */
+/// Return the number of characters that should be skipped in a status match.
+/// These are backslashes used for escaping. Do show backslashes in help tags.
static int skip_status_match_char(expand_T *xp, char_u *s)
{
if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP)
@@ -4864,7 +416,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
len += l;
clen += l;
} else {
- for (; *s != NUL; ++s) {
+ for (; *s != NUL; s++) {
s += skip_status_match_char(xp, s);
clen += ptr2cells((char *)s);
if ((l = utfc_ptr2len((char *)s)) > 1) {
@@ -4891,7 +443,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
if (i != num_matches) {
*(buf + len++) = '>';
- ++clen;
+ clen++;
}
buf[len] = NUL;
@@ -4944,190 +496,6 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int
xfree(buf);
}
-/// Redraw the status line of window `wp`.
-///
-/// If inversion is possible we use it. Else '=' characters are used.
-static void win_redr_status(win_T *wp)
-{
- int row;
- int col;
- char_u *p;
- int len;
- int fillchar;
- int attr;
- int width;
- int this_ru_col;
- bool is_stl_global = global_stl_height() > 0;
- static int busy = false;
-
- // May get here recursively when 'statusline' (indirectly)
- // invokes ":redrawstatus". Simply ignore the call then.
- if (busy
- // Also ignore if wildmenu is showing.
- || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) {
- return;
- }
- busy = true;
-
- wp->w_redr_status = false;
- if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) {
- // no status line, either global statusline is enabled or the window is a last window
- redraw_cmdline = true;
- } else if (!redrawing()) {
- // Don't redraw right now, do it later. Don't update status line when
- // popup menu is visible and may be drawn over it
- wp->w_redr_status = true;
- } else if (*p_stl != NUL || *wp->w_p_stl != NUL) {
- // redraw custom status line
- redraw_custom_statusline(wp);
- } else {
- fillchar = fillchar_status(&attr, wp);
- width = is_stl_global ? Columns : wp->w_width;
-
- get_trans_bufname(wp->w_buffer);
- p = NameBuff;
- len = (int)STRLEN(p);
-
- if (bt_help(wp->w_buffer)
- || wp->w_p_pvw
- || bufIsChanged(wp->w_buffer)
- || wp->w_buffer->b_p_ro) {
- *(p + len++) = ' ';
- }
- if (bt_help(wp->w_buffer)) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]"));
- len += (int)STRLEN(p + len);
- }
- if (wp->w_p_pvw) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]"));
- len += (int)STRLEN(p + len);
- }
- if (bufIsChanged(wp->w_buffer)) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]");
- len += (int)STRLEN(p + len);
- }
- if (wp->w_buffer->b_p_ro) {
- snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]"));
- // len += (int)STRLEN(p + len); // dead assignment
- }
-
- this_ru_col = ru_col - (Columns - width);
- if (this_ru_col < (width + 1) / 2) {
- this_ru_col = (width + 1) / 2;
- }
- if (this_ru_col <= 1) {
- p = (char_u *)"<"; // No room for file name!
- len = 1;
- } else {
- int clen = 0, i;
-
- // Count total number of display cells.
- clen = (int)mb_string2cells((char *)p);
-
- // Find first character that will fit.
- // Going from start to end is much faster for DBCS.
- for (i = 0; p[i] != NUL && clen >= this_ru_col - 1;
- i += utfc_ptr2len((char *)p + i)) {
- clen -= utf_ptr2cells((char *)p + i);
- }
- len = clen;
- if (i > 0) {
- p = p + i - 1;
- *p = '<';
- ++len;
- }
- }
-
- row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
- col = is_stl_global ? 0 : wp->w_wincol;
- grid_puts(&default_grid, p, row, col, attr);
- grid_fill(&default_grid, row, row + 1, len + col,
- this_ru_col + col, fillchar, fillchar, attr);
-
- if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL)
- && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) {
- grid_puts(&default_grid, NameBuff, row,
- (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr);
- }
-
- win_redr_ruler(wp, true);
- }
-
- /*
- * May need to draw the character below the vertical separator.
- */
- if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
- if (stl_connected(wp)) {
- fillchar = fillchar_status(&attr, wp);
- } else {
- fillchar = fillchar_vsep(wp, &attr);
- }
- grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
- }
- busy = FALSE;
-}
-
-/*
- * Redraw the status line according to 'statusline' and take care of any
- * errors encountered.
- */
-static void redraw_custom_statusline(win_T *wp)
-{
- static bool entered = false;
- int saved_did_emsg = did_emsg;
-
- /* When called recursively return. This can happen when the statusline
- * contains an expression that triggers a redraw. */
- if (entered) {
- return;
- }
- entered = true;
-
- did_emsg = false;
- win_redr_custom(wp, false, false);
- if (did_emsg) {
- // When there is an error disable the statusline, otherwise the
- // display is messed up with errors and a redraw triggers the problem
- // again and again.
- set_string_option_direct("statusline", -1, "",
- OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
- entered = false;
-}
-
-static void win_redr_winbar(win_T *wp)
-{
- static bool entered = false;
-
- // Return when called recursively. This can happen when the winbar contains an expression
- // that triggers a redraw.
- if (entered) {
- return;
- }
- entered = true;
-
- if (wp->w_winbar_height == 0 || !redrawing()) {
- // Do nothing.
- } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) {
- int saved_did_emsg = did_emsg;
-
- did_emsg = false;
- win_redr_custom(wp, true, false);
- if (did_emsg) {
- // When there is an error disable the winbar, otherwise the
- // display is messed up with errors and a redraw triggers the problem
- // again and again.
- set_string_option_direct("winbar", -1, "",
- OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
- }
- entered = false;
-}
-
/// Only call if (wp->w_vsep_width != 0).
///
/// @return true if the status line of window "wp" is connected to the status
@@ -5152,77 +520,6 @@ bool stl_connected(win_T *wp)
return false;
}
-/// Check if horizontal separator of window "wp" at specified window corner is connected to the
-/// horizontal separator of another window
-/// Assumes global statusline is enabled
-static bool hsep_connected(win_T *wp, WindowCorner corner)
-{
- bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT);
- int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT)
- ? wp->w_winrow - 1 : W_ENDROW(wp);
- frame_T *fr = wp->w_frame;
-
- while (fr->fr_parent != NULL) {
- if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) {
- fr = before ? fr->fr_prev : fr->fr_next;
- break;
- }
- fr = fr->fr_parent;
- }
- if (fr->fr_parent == NULL) {
- return false;
- }
- while (fr->fr_layout != FR_LEAF) {
- fr = fr->fr_child;
- if (fr->fr_parent->fr_layout == FR_ROW && before) {
- while (fr->fr_next != NULL) {
- fr = fr->fr_next;
- }
- } else {
- while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) {
- fr = fr->fr_next;
- }
- }
- }
-
- return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win));
-}
-
-/// Check if vertical separator of window "wp" at specified window corner is connected to the
-/// vertical separator of another window
-static bool vsep_connected(win_T *wp, WindowCorner corner)
-{
- bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT);
- int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT)
- ? wp->w_wincol - 1 : W_ENDCOL(wp);
- frame_T *fr = wp->w_frame;
-
- while (fr->fr_parent != NULL) {
- if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) {
- fr = before ? fr->fr_prev : fr->fr_next;
- break;
- }
- fr = fr->fr_parent;
- }
- if (fr->fr_parent == NULL) {
- return false;
- }
- while (fr->fr_layout != FR_LEAF) {
- fr = fr->fr_child;
- if (fr->fr_parent->fr_layout == FR_COL && before) {
- while (fr->fr_next != NULL) {
- fr = fr->fr_next;
- }
- } else {
- while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) {
- fr = fr->fr_next;
- }
- }
- }
-
- return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win));
-}
-
/// Get the value to show for the language mappings, active 'keymap'.
///
/// @param fmt format string containing one %s item
@@ -5266,7 +563,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
/// Redraw the status line, window bar or ruler of window "wp".
/// When "wp" is NULL redraw the tab pages line from 'tabline'.
-static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
+void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
{
static bool entered = false;
int attr;
@@ -5290,9 +587,9 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
ScreenGrid *grid = &default_grid;
- /* There is a tiny chance that this gets called recursively: When
- * redrawing a status line triggers redrawing the ruler or tabline.
- * Avoid trouble by not allowing recursion. */
+ // There is a tiny chance that this gets called recursively: When
+ // redrawing a status line triggers redrawing the ruler or tabline.
+ // Avoid trouble by not allowing recursion.
if (entered) {
return;
}
@@ -5389,14 +686,14 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
goto theend;
}
- /* Temporarily reset 'cursorbind', we don't want a side effect from moving
- * the cursor away and back. */
+ // Temporarily reset 'cursorbind', we don't want a side effect from moving
+ // the cursor away and back.
ewp = wp == NULL ? curwin : wp;
p_crb_save = ewp->w_p_crb;
- ewp->w_p_crb = FALSE;
+ ewp->w_p_crb = false;
- /* Make a copy, because the statusline may include a function call that
- * might change the option value and free the memory. */
+ // Make a copy, because the statusline may include a function call that
+ // might change the option value and free the memory.
stl = vim_strsave(stl);
width =
build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox,
@@ -5417,9 +714,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
}
buf[len] = NUL;
- /*
- * Draw each snippet with the specified highlighting.
- */
+ // Draw each snippet with the specified highlighting.
grid_puts_line_start(grid, row);
curattr = attr;
@@ -5483,134 +778,23 @@ theend:
entered = false;
}
-static void win_redr_border(win_T *wp)
-{
- wp->w_redr_border = false;
- if (!(wp->w_floating && wp->w_float_config.border)) {
- return;
- }
-
- ScreenGrid *grid = &wp->w_grid_alloc;
-
- schar_T *chars = wp->w_float_config.border_chars;
- int *attrs = wp->w_float_config.border_attr;
-
- int *adj = wp->w_border_adj;
- int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner;
-
- char* title = wp->w_float_config.title;
- size_t n_title = wp->w_float_config.n_title;
- stl_hlrec_t* title_hl = wp->w_float_config.title_hl;
-
- int m8[MAX_MCO + 1];
- int cc;
- int len;
- int t_attr = title_hl != NULL && title_hl->userhl
- ? syn_id2attr(title_hl->userhl)
- : 0;
- t_attr = hl_combine_attr(attrs[1], t_attr);
-
- int title_pos = 2;
- switch (wp->w_float_config.title_pos) {
- case kTitleLeft:
- title_pos = 2;
- break;
- case kTitleRight:
- title_pos = icol - 2 - vim_strsize(title);
- break;
- case kTitleCenter:
- title_pos = (icol - vim_strsize(title)) / 2 - 1;
- break;
- }
- title_pos = title_pos < 2 ? 2 : title_pos;
-
- if (adj[0]) {
- grid_puts_line_start(grid, 0);
- if (adj[3]) {
- grid_put_schar(grid, 0, 0, chars[0], attrs[0]);
- }
- for (int i = 0; i < icol; i++) {
- schar_T ch;
- int attr;
- // Draw the title if in the correct position.
- if (i > title_pos && n_title > 0 && i < icol - 2) {
- cc = utfc_ptr2char((char_u*) title, m8);
- len = utfc_ptr2len(title);
- n_title -= len;
- title += len;
-
- while (title_hl != NULL &&
- (title_hl + 1)->start != NULL &&
- (title_hl + 1)->start < title) {
- ++ title_hl;
- t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl));
- }
-
- schar_from_cc(ch, cc, m8);
- attr = t_attr;
- } else {
- memcpy(ch, chars[1], sizeof(schar_T));
- attr = attrs[1];
- }
- grid_put_schar(grid, 0, i + adj[3], ch, attr);
- }
- if (adj[1]) {
- grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]);
- }
- grid_puts_line_flush(false);
- }
-
- for (int i = 0; i < irow; i++) {
- if (adj[3]) {
- grid_puts_line_start(grid, i + adj[0]);
- grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]);
- grid_puts_line_flush(false);
- }
- if (adj[1]) {
- int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
- grid_puts_line_start(grid, i + adj[0]);
- grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]);
- grid_puts_line_flush(false);
- }
- }
-
- if (adj[2]) {
- grid_puts_line_start(grid, irow + adj[0]);
- if (adj[3]) {
- grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]);
- }
- for (int i = 0; i < icol; i++) {
- int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
- grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]);
- }
- if (adj[1]) {
- grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]);
- }
- grid_puts_line_flush(false);
- }
-}
-
-/*
- * Prepare for 'hlsearch' highlighting.
- */
-static void start_search_hl(void)
+/// Prepare for 'hlsearch' highlighting.
+void start_search_hl(void)
{
if (p_hls && !no_hlsearch) {
end_search_hl(); // just in case it wasn't called before
- last_pat_prog(&search_hl.rm);
+ last_pat_prog(&screen_search_hl.rm);
// Set the time limit to 'redrawtime'.
- search_hl.tm = profile_setlimit(p_rdt);
+ screen_search_hl.tm = profile_setlimit(p_rdt);
}
}
-/*
- * Clean up for 'hlsearch' highlighting.
- */
-static void end_search_hl(void)
+/// Clean up for 'hlsearch' highlighting.
+void end_search_hl(void)
{
- if (search_hl.rm.regprog != NULL) {
- vim_regfree(search_hl.rm.regprog);
- search_hl.rm.regprog = NULL;
+ if (screen_search_hl.rm.regprog != NULL) {
+ vim_regfree(screen_search_hl.rm.regprog);
+ screen_search_hl.rm.regprog = NULL;
}
}
@@ -5630,105 +814,6 @@ void check_for_delay(bool check_msg_scroll)
}
}
-/// Resize the screen to Rows and Columns.
-///
-/// Allocate default_grid.chars[] and other grid arrays.
-///
-/// There may be some time between setting Rows and Columns and (re)allocating
-/// default_grid arrays. This happens when starting up and when
-/// (manually) changing the screen size. Always use default_grid.rows and
-/// default_grid.Columns to access items in default_grid.chars[]. Use Rows
-/// and Columns for positioning text etc. where the final size of the screen is
-/// needed.
-void screenalloc(void)
-{
- // It's possible that we produce an out-of-memory message below, which
- // will cause this function to be called again. To break the loop, just
- // return here.
- if (resizing) {
- return;
- }
- resizing = true;
-
- int retry_count = 0;
-
-retry:
- // Allocation of the screen buffers is done only when the size changes and
- // when Rows and Columns have been set and we have started doing full
- // screen stuff.
- if ((default_grid.chars != NULL
- && Rows == default_grid.rows
- && Columns == default_grid.cols
- )
- || Rows == 0
- || Columns == 0
- || (!full_screen && default_grid.chars == NULL)) {
- resizing = false;
- return;
- }
-
- /*
- * Note that the window sizes are updated before reallocating the arrays,
- * thus we must not redraw here!
- */
- ++RedrawingDisabled;
-
- // win_new_screensize will recompute floats position, but tell the
- // compositor to not redraw them yet
- ui_comp_set_screen_valid(false);
- if (msg_grid.chars) {
- msg_grid_invalid = true;
- }
-
- win_new_screensize(); // fit the windows in the new sized screen
-
- comp_col(); // recompute columns for shown command and ruler
-
- // We're changing the size of the screen.
- // - Allocate new arrays for default_grid
- // - Move lines from the old arrays into the new arrays, clear extra
- // lines (unless the screen is going to be cleared).
- // - Free the old arrays.
- //
- // If anything fails, make grid arrays NULL, so we don't do anything!
- // Continuing with the old arrays may result in a crash, because the
- // size is wrong.
-
- grid_alloc(&default_grid, Rows, Columns, true, true);
- StlClickDefinition *new_tab_page_click_defs =
- xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs));
-
- stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size);
- xfree(tab_page_click_defs);
-
- tab_page_click_defs = new_tab_page_click_defs;
- tab_page_click_defs_size = Columns;
-
- default_grid.comp_height = Rows;
- default_grid.comp_width = Columns;
-
- default_grid.row_offset = 0;
- default_grid.col_offset = 0;
- default_grid.handle = DEFAULT_GRID_HANDLE;
-
- must_redraw = CLEAR; // need to clear the screen later
-
- RedrawingDisabled--;
-
- /*
- * Do not apply autocommands more than 3 times to avoid an endless loop
- * in case applying autocommands always changes Rows or Columns.
- */
- if (starting == 0 && ++retry_count <= 3) {
- apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf);
- // In rare cases, autocommands may have altered Rows or Columns,
- // jump back to check if we need to allocate the screen again.
- goto retry;
- }
-
- resizing = false;
-}
-
/// Clear status line, window bar or tab page line click definition table
///
/// @param[out] tpcd Table to clear.
@@ -5745,67 +830,6 @@ void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click
}
}
-void screenclear(void)
-{
- check_for_delay(false);
- screenalloc(); // allocate screen buffers if size changed
-
- int i;
-
- if (starting == NO_SCREEN || default_grid.chars == NULL) {
- return;
- }
-
- // blank out the default grid
- for (i = 0; i < default_grid.rows; i++) {
- grid_clear_line(&default_grid, default_grid.line_offset[i],
- default_grid.cols, true);
- default_grid.line_wraps[i] = false;
- }
-
- ui_call_grid_clear(1); // clear the display
- ui_comp_set_screen_valid(true);
-
- clear_cmdline = false;
- mode_displayed = false;
-
- redraw_all_later(NOT_VALID);
- redraw_cmdline = true;
- redraw_tabline = true;
- redraw_popupmenu = true;
- pum_invalidate();
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_floating) {
- wp->w_redr_type = CLEAR;
- }
- }
- if (must_redraw == CLEAR) {
- must_redraw = NOT_VALID; // no need to clear again
- }
- compute_cmdrow();
- msg_row = cmdline_row; // put cursor on last line for messages
- msg_col = 0;
- msg_scrolled = 0; // can't scroll back
- msg_didany = false;
- msg_didout = false;
- if (HL_ATTR(HLF_MSG) > 0 && msg_use_grid() && msg_grid.chars) {
- grid_invalidate(&msg_grid);
- msg_grid_validate();
- msg_grid_invalid = false;
- clear_cmdline = true;
- }
-}
-
-/// Copy part of a grid line for vertically split window.
-static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
-{
- unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col);
- unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col);
-
- memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T));
- memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T));
-}
-
/// Set cursor to its position in the current window.
void setcursor(void)
{
@@ -5835,9 +859,9 @@ void setcursor_mayforce(bool force)
}
}
-/// Scroll 'line_count' lines at 'row' in window 'wp'.
+/// Scroll `line_count` lines at 'row' in window 'wp'.
///
-/// Positive `line_count' means scrolling down, so that more space is available
+/// Positive `line_count` means scrolling down, so that more space is available
/// at 'row'. Negative `line_count` implies deleting lines at `row`.
void win_scroll_lines(win_T *wp, int row, int line_count)
{
@@ -5859,126 +883,29 @@ void win_scroll_lines(win_T *wp, int row, int line_count)
}
}
-/*
- * The rest of the routines in this file perform screen manipulations. The
- * given operation is performed physically on the screen. The corresponding
- * change is also made to the internal screen image. In this way, the editor
- * anticipates the effect of editing changes on the appearance of the screen.
- * That way, when we call screenupdate a complete redraw isn't usually
- * necessary. Another advantage is that we can keep adding code to anticipate
- * screen changes, and in the meantime, everything still works.
- */
-
-/// insert lines on the screen and move the existing lines down
-/// 'line_count' is the number of lines to be inserted.
-/// 'end' is the line after the scrolled part. Normally it is Rows.
-/// 'col' is the column from with we start inserting.
-//
-/// 'row', 'col' and 'end' are relative to the start of the region.
-void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
-{
- int i;
- int j;
- unsigned temp;
-
- int row_off = 0;
- grid_adjust(&grid, &row_off, &col);
- row += row_off;
- end += row_off;
-
- if (line_count <= 0) {
- return;
- }
-
- // Shift line_offset[] line_count down to reflect the inserted lines.
- // Clear the inserted lines.
- for (i = 0; i < line_count; i++) {
- if (width != grid->cols) {
- // need to copy part of a line
- j = end - 1 - i;
- while ((j -= line_count) >= row) {
- linecopy(grid, j + line_count, j, col, width);
- }
- j += line_count;
- grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
- grid->line_wraps[j] = false;
- } else {
- j = end - 1 - i;
- temp = (unsigned)grid->line_offset[j];
- while ((j -= line_count) >= row) {
- grid->line_offset[j + line_count] = grid->line_offset[j];
- grid->line_wraps[j + line_count] = grid->line_wraps[j];
- }
- grid->line_offset[j + line_count] = temp;
- grid->line_wraps[j + line_count] = false;
- grid_clear_line(grid, temp, grid->cols, false);
- }
- }
-
- if (!grid->throttled) {
- ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0);
- }
-}
-
-/// delete lines on the screen and move lines up.
-/// 'end' is the line after the scrolled part. Normally it is Rows.
-/// When scrolling region used 'off' is the offset from the top for the region.
-/// 'row' and 'end' are relative to the start of the region.
-void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
+/// @return true when postponing displaying the mode message: when not redrawing
+/// or inside a mapping.
+bool skip_showmode(void)
{
- int j;
- int i;
- unsigned temp;
-
- int row_off = 0;
- grid_adjust(&grid, &row_off, &col);
- row += row_off;
- end += row_off;
-
- if (line_count <= 0) {
- return;
- }
-
- // Now shift line_offset[] line_count up to reflect the deleted lines.
- // Clear the inserted lines.
- for (i = 0; i < line_count; i++) {
- if (width != grid->cols) {
- // need to copy part of a line
- j = row + i;
- while ((j += line_count) <= end - 1) {
- linecopy(grid, j - line_count, j, col, width);
- }
- j -= line_count;
- grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
- grid->line_wraps[j] = false;
- } else {
- // whole width, moving the line pointers is faster
- j = row + i;
- temp = (unsigned)grid->line_offset[j];
- while ((j += line_count) <= end - 1) {
- grid->line_offset[j - line_count] = grid->line_offset[j];
- grid->line_wraps[j - line_count] = grid->line_wraps[j];
- }
- grid->line_offset[j - line_count] = temp;
- grid->line_wraps[j - line_count] = false;
- grid_clear_line(grid, temp, grid->cols, false);
- }
- }
-
- if (!grid->throttled) {
- ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0);
+ // Call char_avail() only when we are going to show something, because it
+ // takes a bit of time. redrawing() may also call char_avail().
+ if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) {
+ redraw_mode = true; // show mode later
+ return true;
}
+ return false;
}
-// Show the current mode and ruler.
-//
-// If clear_cmdline is true, clear the rest of the cmdline.
-// If clear_cmdline is false there may be a message there that needs to be
-// cleared only if a mode is shown.
-// Return the length of the message (0 if no message).
+/// Show the current mode and ruler.
+///
+/// If clear_cmdline is true, clear the rest of the cmdline.
+/// If clear_cmdline is false there may be a message there that needs to be
+/// cleared only if a mode is shown.
+/// If redraw_mode is true show or clear the mode.
+/// @return the length of the message (0 if no message).
int showmode(void)
{
- int need_clear;
+ bool need_clear;
int length = 0;
int do_mode;
int attr;
@@ -5999,12 +926,8 @@ int showmode(void)
|| restart_edit != NUL
|| VIsual_active));
if (do_mode || reg_recording != 0) {
- // Don't show mode right now, when not redrawing or inside a mapping.
- // Call char_avail() only when we are going to show something, because
- // it takes a bit of time.
- if (!redrawing() || (char_avail() && !KeyTyped) || msg_silent != 0) {
- redraw_cmdline = true; // show mode later
- return 0;
+ if (skip_showmode()) {
+ return 0; // show mode later
}
bool nwr_save = need_wait_return;
@@ -6128,7 +1051,7 @@ int showmode(void)
msg_puts_attr(" --", attr);
}
- need_clear = TRUE;
+ need_clear = true;
}
if (reg_recording != 0
&& edit_submode == NULL // otherwise it gets too long
@@ -6138,7 +1061,7 @@ int showmode(void)
}
mode_displayed = true;
- if (need_clear || clear_cmdline) {
+ if (need_clear || clear_cmdline || redraw_mode) {
msg_clr_eos();
}
msg_didout = false; // overwrite this message
@@ -6150,6 +1073,9 @@ int showmode(void)
} else if (clear_cmdline && msg_silent == 0) {
// Clear the whole command line. Will reset "clear_cmdline".
msg_clr_cmdline();
+ } else if (redraw_mode) {
+ msg_pos_mode();
+ msg_clr_eos();
}
// NB: also handles clearing the showmode if it was empty or disabled
@@ -6167,14 +1093,13 @@ int showmode(void)
win_redr_ruler(last, true);
}
redraw_cmdline = false;
+ redraw_mode = false;
clear_cmdline = false;
return length;
}
-/*
- * Position for a mode message.
- */
+/// Position for a mode message.
static void msg_pos_mode(void)
{
msg_col = 0;
@@ -6216,19 +1141,13 @@ static void recording_mode(int attr)
{
msg_puts_attr(_("recording"), attr);
if (!shortmess(SHM_RECORDING)) {
- char s[10];
- int len = (*utf_char2len)(reg_recording);
- utf_char2bytes(reg_recording, s + 2);
- s[0] = ' ';
- s[1] = '@';
- s[len + 2] = 0;
+ char s[4];
+ snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
msg_puts_attr(s, attr);
}
}
-/*
- * Draw the tab pages line at the top of the Vim window.
- */
+/// Draw the tab pages line at the top of the Vim window.
void draw_tabline(void)
{
int tabcount = 0;
@@ -6246,8 +1165,7 @@ void draw_tabline(void)
int attr_fill = HL_ATTR(HLF_TPF);
char_u *p;
int room;
- int use_sep_chars = (t_colors < 8
- );
+ int use_sep_chars = (t_colors < 8);
if (default_grid.chars == NULL) {
return;
@@ -6281,7 +1199,7 @@ void draw_tabline(void)
did_emsg |= saved_did_emsg;
} else {
FOR_ALL_TABS(tp) {
- ++tabcount;
+ tabcount++;
}
if (tabcount > 0) {
@@ -6403,7 +1321,7 @@ void draw_tabline(void)
redraw_tabline = false;
}
-void ui_ext_tabline_update(void)
+static void ui_ext_tabline_update(void)
{
Arena arena = ARENA_EMPTY;
arena_start(&arena, &ui_ext_fixblk);
@@ -6450,10 +1368,6 @@ void ui_ext_tabline_update(void)
arena_mem_free(arena_finish(&arena), &ui_ext_fixblk);
}
-/*
- * Get buffer name for "buf" into NameBuff[].
- * Takes care of special buffer names and translates special characters.
- */
void get_trans_bufname(buf_T *buf)
{
if (buf_spname(buf) != NULL) {
@@ -6464,9 +1378,7 @@ void get_trans_bufname(buf_T *buf)
trans_characters((char *)NameBuff, MAXPATHL);
}
-/*
- * Get the character to use in a status line. Get its attributes in "*attr".
- */
+/// Get the character to use in a status line. Get its attributes in "*attr".
int fillchar_status(int *attr, win_T *wp)
{
int fill;
@@ -6494,7 +1406,7 @@ int fillchar_status(int *attr, win_T *wp)
/// Get the character to use in a separator between vertically split windows.
/// Get its attributes in "*attr".
-static int fillchar_vsep(win_T *wp, int *attr)
+int fillchar_vsep(win_T *wp, int *attr)
{
*attr = win_hl_attr(wp, HLF_C);
return wp->w_p_fcs_chars.vert;
@@ -6502,59 +1414,26 @@ static int fillchar_vsep(win_T *wp, int *attr)
/// Get the character to use in a separator between horizontally split windows.
/// Get its attributes in "*attr".
-static int fillchar_hsep(win_T *wp, int *attr)
+int fillchar_hsep(win_T *wp, int *attr)
{
*attr = win_hl_attr(wp, HLF_C);
return wp->w_p_fcs_chars.horiz;
}
-/*
- * Return TRUE if redrawing should currently be done.
- */
-int redrawing(void)
+/// Return true if redrawing should currently be done.
+bool redrawing(void)
{
return !RedrawingDisabled
&& !(p_lz && char_avail() && !KeyTyped && !do_redraw);
}
-/*
- * Return TRUE if printing messages should currently be done.
- */
-int messaging(void)
+/// Return true if printing messages should currently be done.
+bool messaging(void)
{
return !(p_lz && char_avail() && !KeyTyped) && ui_has_messages();
}
-/// Show current status info in ruler and various other places
-///
-/// @param always if false, only show ruler if position has changed.
-void showruler(bool always)
-{
- if (!always && !redrawing()) {
- return;
- }
- if ((*p_stl != NUL || *curwin->w_p_stl != NUL)
- && (curwin->w_status_height || global_stl_height())) {
- redraw_custom_statusline(curwin);
- } else {
- win_redr_ruler(curwin, always);
- }
- if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) {
- win_redr_winbar(curwin);
- }
-
- if (need_maketitle
- || (p_icon && (stl_syntax & STL_IN_ICON))
- || (p_title && (stl_syntax & STL_IN_TITLE))) {
- maketitle();
- }
- // Redraw the tab pages line if needed.
- if (redraw_tabline) {
- draw_tabline();
- }
-}
-
-static void win_redr_ruler(win_T *wp, bool always)
+void win_redr_ruler(win_T *wp, bool always)
{
bool is_stl_global = global_stl_height() > 0;
static bool did_show_ext_ruler = false;
@@ -6564,10 +1443,8 @@ static void win_redr_ruler(win_T *wp, bool always)
return;
}
- /*
- * Check if cursor.lnum is valid, since win_redr_ruler() may be called
- * after deleting lines, before cursor.lnum is corrected.
- */
+ // Check if cursor.lnum is valid, since win_redr_ruler() may be called
+ // after deleting lines, before cursor.lnum is corrected.
if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
return;
}
@@ -6595,9 +1472,7 @@ static void win_redr_ruler(win_T *wp, bool always)
empty_line = true;
}
- /*
- * Only draw the ruler when something changed.
- */
+ // Only draw the ruler when something changed.
validate_virtcol_win(wp);
if (redraw_cmdline
|| always
@@ -6651,23 +1526,19 @@ static void win_redr_ruler(win_T *wp, bool always)
#define RULER_BUF_LEN 70
char buffer[RULER_BUF_LEN];
- /*
- * Some sprintfs return the length, some return a pointer.
- * To avoid portability problems we use strlen() here.
- */
+ // Some sprintfs return the length, some return a pointer.
+ // To avoid portability problems we use strlen() here.
vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
- (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L
- : (int64_t)wp->w_cursor.lnum);
+ (wp->w_buffer->b_ml.ml_flags &
+ ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum);
size_t len = STRLEN(buffer);
col_print(buffer + len, RULER_BUF_LEN - len,
empty_line ? 0 : (int)wp->w_cursor.col + 1,
(int)virtcol + 1);
- /*
- * Add a "50%" if there is room for it.
- * On the last line, don't print in the last column (scrolls the
- * screen up on some terminals).
- */
+ // Add a "50%" if there is room for it.
+ // On the last line, don't print in the last column (scrolls the
+ // screen up on some terminals).
int i = (int)STRLEN(buffer);
get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
int o = i + vim_strsize(buffer + i + 1);
@@ -6731,11 +1602,49 @@ static void win_redr_ruler(win_T *wp, bool always)
}
}
-/*
- * Return the width of the 'number' and 'relativenumber' column.
- * Caller may need to check if 'number' or 'relativenumber' is set.
- * Otherwise it depends on 'numberwidth' and the line count.
- */
+#define COL_RULER 17 // columns needed by standard ruler
+
+/// Compute columns for ruler and shown command. 'sc_col' is also used to
+/// decide what the maximum length of a message on the status line can be.
+/// If there is a status line for the last window, 'sc_col' is independent
+/// of 'ru_col'.
+void comp_col(void)
+{
+ int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW));
+
+ sc_col = 0;
+ ru_col = 0;
+ if (p_ru) {
+ ru_col = (ru_wid ? ru_wid : COL_RULER) + 1;
+ // no last status line, adjust sc_col
+ if (!last_has_status) {
+ sc_col = ru_col;
+ }
+ }
+ if (p_sc) {
+ sc_col += SHOWCMD_COLS;
+ if (!p_ru || last_has_status) { // no need for separating space
+ sc_col++;
+ }
+ }
+ assert(sc_col >= 0
+ && INT_MIN + sc_col <= Columns);
+ sc_col = Columns - sc_col;
+ assert(ru_col >= 0
+ && INT_MIN + ru_col <= Columns);
+ ru_col = Columns - ru_col;
+ if (sc_col <= 0) { // screen too narrow, will become a mess
+ sc_col = 1;
+ }
+ if (ru_col <= 0) {
+ ru_col = 1;
+ }
+ set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
+}
+
+/// Return the width of the 'number' and 'relativenumber' column.
+/// Caller may need to check if 'number' or 'relativenumber' is set.
+/// Otherwise it depends on 'numberwidth' and the line count.
int number_width(win_T *wp)
{
int n;
@@ -6757,7 +1666,7 @@ int number_width(win_T *wp)
n = 0;
do {
lnum /= 10;
- ++n;
+ n++;
} while (lnum > 0);
// 'numberwidth' gives the minimal width plus one
@@ -6776,155 +1685,283 @@ int number_width(win_T *wp)
return n;
}
-/// Used when 'cursorlineopt' contains "screenline": compute the margins between
-/// which the highlighting is used.
-static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
+/// Calls mb_cptr2char_adv(p) and returns the character.
+/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
+/// Returns 0 for invalid hex or invalid UTF-8 byte.
+static int get_encoded_char_adv(char_u **p)
{
- // cache previous calculations depending on w_virtcol
- static int saved_w_virtcol;
- static win_T *prev_wp;
- static int prev_left_col;
- static int prev_right_col;
- static int prev_col_off;
-
- int cur_col_off = win_col_off(wp);
- int width1;
- int width2;
-
- if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp
- && prev_col_off == cur_col_off) {
- *right_col = prev_right_col;
- *left_col = prev_left_col;
- return;
- }
-
- width1 = wp->w_width - cur_col_off;
- width2 = width1 + win_col_off2(wp);
-
- *left_col = 0;
- *right_col = width1;
+ char_u *s = *p;
- if (wp->w_virtcol >= (colnr_T)width1) {
- *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2;
- }
- if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
- *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1;
+ if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
+ int64_t num = 0;
+ int bytes;
+ int n;
+ for (bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
+ *p += 2;
+ n = hexhex2nr(*p);
+ if (n < 0) {
+ return 0;
+ }
+ num = num * 256 + n;
+ }
+ *p += 2;
+ return (int)num;
}
- // cache values
- prev_left_col = *left_col;
- prev_right_col = *right_col;
- prev_wp = wp;
- saved_w_virtcol = wp->w_virtcol;
- prev_col_off = cur_col_off;
+ // TODO(bfredl): use schar_T representation and utfc_ptr2len
+ int clen = utf_ptr2len((char *)s);
+ int c = mb_cptr2char_adv((const char_u **)p);
+ if (clen == 1 && c > 127) { // Invalid UTF-8 byte
+ return 0;
+ }
+ return c;
}
-/// Set dimensions of the Nvim application "screen".
-void screen_resize(int width, int height)
-{
- // Avoid recursiveness, can happen when setting the window size causes
- // another window-changed signal.
- if (updating_screen || resizing_screen) {
- return;
- }
+/// Handle setting 'listchars' or 'fillchars'.
+/// Assume monocell characters
+///
+/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs
+/// @return error message, NULL if it's OK.
+char *set_chars_option(win_T *wp, char_u **varp, bool set)
+{
+ int round, i, len, len2, entries;
+ char_u *p, *s;
+ int c1;
+ int c2 = 0;
+ int c3 = 0;
+ char_u *last_multispace = NULL; // Last occurrence of "multispace:"
+ char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
+ int multispace_len = 0; // Length of lcs-multispace string
+ int lead_multispace_len = 0; // Length of lcs-leadmultispace string
+
+ struct chars_tab {
+ int *cp; ///< char value
+ char *name; ///< char id
+ int def; ///< default value
+ };
+ struct chars_tab *tab;
+
+ // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case.
+ struct chars_tab fcs_tab[] = {
+ { &wp->w_p_fcs_chars.stl, "stl", ' ' },
+ { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' },
+ { &wp->w_p_fcs_chars.wbr, "wbr", ' ' },
+ { &wp->w_p_fcs_chars.horiz, "horiz", char2cells(0x2500) == 1 ? 0x2500 : '-' }, // ─
+ { &wp->w_p_fcs_chars.horizup, "horizup", char2cells(0x2534) == 1 ? 0x2534 : '-' }, // ┴
+ { &wp->w_p_fcs_chars.horizdown, "horizdown", char2cells(0x252c) == 1 ? 0x252c : '-' }, // ┬
+ { &wp->w_p_fcs_chars.vert, "vert", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │
+ { &wp->w_p_fcs_chars.vertleft, "vertleft", char2cells(0x2524) == 1 ? 0x2524 : '|' }, // ┤
+ { &wp->w_p_fcs_chars.vertright, "vertright", char2cells(0x251c) == 1 ? 0x251c : '|' }, // ├
+ { &wp->w_p_fcs_chars.verthoriz, "verthoriz", char2cells(0x253c) == 1 ? 0x253c : '+' }, // ┼
+ { &wp->w_p_fcs_chars.fold, "fold", char2cells(0x00b7) == 1 ? 0x00b7 : '-' }, // ·
+ { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' },
+ { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' },
+ { &wp->w_p_fcs_chars.foldsep, "foldsep", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │
+ { &wp->w_p_fcs_chars.diff, "diff", '-' },
+ { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' },
+ { &wp->w_p_fcs_chars.eob, "eob", '~' },
+ { &wp->w_p_fcs_chars.colorcol, "colorcol", ' ' },
+ };
+ struct chars_tab lcs_tab[] = {
+ { &wp->w_p_lcs_chars.eol, "eol", NUL },
+ { &wp->w_p_lcs_chars.ext, "extends", NUL },
+ { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL },
+ { &wp->w_p_lcs_chars.prec, "precedes", NUL },
+ { &wp->w_p_lcs_chars.space, "space", NUL },
+ { &wp->w_p_lcs_chars.tab2, "tab", NUL },
+ { &wp->w_p_lcs_chars.lead, "lead", NUL },
+ { &wp->w_p_lcs_chars.trail, "trail", NUL },
+ { &wp->w_p_lcs_chars.conceal, "conceal", NUL },
+ };
- if (width < 0 || height < 0) { // just checking...
- return;
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
+ tab = lcs_tab;
+ entries = ARRAY_SIZE(lcs_tab);
+ if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
+ varp = &p_lcs;
+ }
+ } else {
+ tab = fcs_tab;
+ entries = ARRAY_SIZE(fcs_tab);
+ if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
+ varp = &p_fcs;
+ }
}
- if (State == MODE_HITRETURN || State == MODE_SETWSIZE) {
- // postpone the resizing
- State = MODE_SETWSIZE;
- return;
- }
+ // first round: check for valid value, second round: assign values
+ for (round = 0; round <= (set ? 1 : 0); round++) {
+ if (round > 0) {
+ // After checking that the value is valid: set defaults
+ for (i = 0; i < entries; i++) {
+ if (tab[i].cp != NULL) {
+ *(tab[i].cp) = tab[i].def;
+ }
+ }
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
+ wp->w_p_lcs_chars.tab1 = NUL;
+ wp->w_p_lcs_chars.tab3 = NUL;
- /* curwin->w_buffer can be NULL when we are closing a window and the
- * buffer has already been closed and removing a scrollbar causes a resize
- * event. Don't resize then, it will happen after entering another buffer.
- */
- if (curwin->w_buffer == NULL) {
- return;
- }
+ xfree(wp->w_p_lcs_chars.multispace);
+ if (multispace_len > 0) {
+ wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
+ wp->w_p_lcs_chars.multispace[multispace_len] = NUL;
+ } else {
+ wp->w_p_lcs_chars.multispace = NULL;
+ }
- resizing_screen = true;
+ xfree(wp->w_p_lcs_chars.leadmultispace);
+ if (lead_multispace_len > 0) {
+ wp->w_p_lcs_chars.leadmultispace
+ = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
+ wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL;
+ } else {
+ wp->w_p_lcs_chars.leadmultispace = NULL;
+ }
+ }
+ }
+ p = *varp;
+ while (*p) {
+ for (i = 0; i < entries; i++) {
+ len = (int)STRLEN(tab[i].name);
+ if (STRNCMP(p, tab[i].name, len) == 0
+ && p[len] == ':'
+ && p[len + 1] != NUL) {
+ c2 = c3 = 0;
+ s = p + len + 1;
+ c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
+ if (*s == NUL) {
+ return e_invarg;
+ }
+ c2 = get_encoded_char_adv(&s);
+ if (c2 == 0 || char2cells(c2) > 1) {
+ return e_invarg;
+ }
+ if (!(*s == ',' || *s == NUL)) {
+ c3 = get_encoded_char_adv(&s);
+ if (c3 == 0 || char2cells(c3) > 1) {
+ return e_invarg;
+ }
+ }
+ }
+ if (*s == ',' || *s == NUL) {
+ if (round > 0) {
+ if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
+ wp->w_p_lcs_chars.tab1 = c1;
+ wp->w_p_lcs_chars.tab2 = c2;
+ wp->w_p_lcs_chars.tab3 = c3;
+ } else if (tab[i].cp != NULL) {
+ *(tab[i].cp) = c1;
+ }
+ }
+ p = s;
+ break;
+ }
+ }
+ }
- Rows = height;
- Columns = width;
- check_screensize();
- int max_p_ch = Rows - min_rows() + 1;
- if (!ui_has(kUIMessages) && p_ch > 0 && p_ch > max_p_ch) {
- p_ch = max_p_ch ? max_p_ch : 1;
+ if (i == entries) {
+ len = (int)STRLEN("multispace");
+ len2 = (int)STRLEN("leadmultispace");
+ if ((varp == &p_lcs || varp == &wp->w_p_lcs)
+ && STRNCMP(p, "multispace", len) == 0
+ && p[len] == ':'
+ && p[len + 1] != NUL) {
+ s = p + len + 1;
+ if (round == 0) {
+ // Get length of lcs-multispace string in the first round
+ last_multispace = p;
+ multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ multispace_len++;
+ }
+ if (multispace_len == 0) {
+ // lcs-multispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = get_encoded_char_adv(&s);
+ if (p == last_multispace) {
+ wp->w_p_lcs_chars.multispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ } else if ((varp == &p_lcs || varp == &wp->w_p_lcs)
+ && STRNCMP(p, "leadmultispace", len2) == 0
+ && p[len2] == ':'
+ && p[len2 + 1] != NUL) {
+ s = p + len2 + 1;
+ if (round == 0) {
+ // get length of lcs-leadmultispace string in first round
+ last_lmultispace = p;
+ lead_multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ lead_multispace_len++;
+ }
+ if (lead_multispace_len == 0) {
+ // lcs-leadmultispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ c1 = get_encoded_char_adv(&s);
+ if (p == last_lmultispace) {
+ wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ } else {
+ return e_invarg;
+ }
+ }
+ if (*p == ',') {
+ p++;
+ }
+ }
}
- height = Rows;
- width = Columns;
- p_lines = Rows;
- p_columns = Columns;
- ui_call_grid_resize(1, width, height);
-
- send_grid_resize = true;
- /// The window layout used to be adjusted here, but it now happens in
- /// screenalloc() (also invoked from screenclear()). That is because the
- /// recursize "resizing_screen" check above may skip this, but not screenalloc().
+ return NULL; // no error
+}
- if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) {
- screenclear();
+/// Check all global and local values of 'listchars' and 'fillchars'.
+/// May set different defaults in case character widths change.
+///
+/// @return an untranslated error message if any of them is invalid, NULL otherwise.
+char *check_chars_options(void)
+{
+ if (set_chars_option(curwin, &p_lcs, false) != NULL) {
+ return e_conflicts_with_value_of_listchars;
}
-
- if (starting != NO_SCREEN) {
- maketitle();
- changed_line_abv_curs();
- invalidate_botline();
-
- /*
- * We only redraw when it's needed:
- * - While at the more prompt or executing an external command, don't
- * redraw, but position the cursor.
- * - While editing the command line, only redraw that.
- * - in Ex mode, don't redraw anything.
- * - Otherwise, redraw right now, and position the cursor.
- * Always need to call update_screen() or screenalloc(), to make
- * sure Rows/Columns and the size of the screen is correct!
- */
- if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM
- || exmode_active) {
- screenalloc();
- if (msg_grid.chars) {
- msg_grid_validate();
- }
- // TODO(bfredl): sometimes messes up the output. Implement clear+redraw
- // also for the pager? (or: what if the pager was just a modal window?)
- ui_comp_set_screen_valid(true);
- repeat_message();
- } else {
- if (curwin->w_p_scb) {
- do_check_scrollbind(true);
- }
- if (State & MODE_CMDLINE) {
- redraw_popupmenu = false;
- update_screen(NOT_VALID);
- redrawcmdline();
- if (pum_drawn()) {
- cmdline_pum_display(false);
- }
- } else {
- update_topline(curwin);
- if (pum_drawn()) {
- // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first.
- // For now make sure the nested update_screen(0) won't redraw the
- // pum at the old position. Try to untangle this later.
- redraw_popupmenu = false;
- ins_compl_show_pum();
- }
- update_screen(NOT_VALID);
- if (redrawing()) {
- setcursor();
- }
- }
+ if (set_chars_option(curwin, &p_fcs, false) != NULL) {
+ return e_conflicts_with_value_of_fillchars;
+ }
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
+ return e_conflicts_with_value_of_listchars;
+ }
+ if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
+ return e_conflicts_with_value_of_fillchars;
}
- ui_flush();
}
- resizing_screen = false;
+ return NULL;
}
/// Check if the new Nvim application "screen" dimensions are valid.
@@ -6945,13 +1982,3 @@ void check_screensize(void)
Columns = 10000;
}
}
-
-win_T *get_win_by_grid_handle(handle_T handle)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_grid_alloc.handle == handle) {
- return wp;
- }
- }
- return NULL;
-}
diff --git a/src/nvim/screen.h b/src/nvim/screen.h
index 9eda5223f1..ea1c58cd80 100644
--- a/src/nvim/screen.h
+++ b/src/nvim/screen.h
@@ -4,31 +4,10 @@
#include <stdbool.h>
#include "nvim/buffer_defs.h"
-#include "nvim/grid.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/fold.h"
+#include "nvim/grid_defs.h"
-// flags for update_screen()
-// The higher the value, the higher the priority
-#define VALID 10 // buffer not changed, or changes marked
- // with b_mod_*
-#define INVERTED 20 // redisplay inverted part that changed
-#define INVERTED_ALL 25 // redisplay whole inverted part
-#define REDRAW_TOP 30 // display first w_upd_rows screen lines
-#define SOME_VALID 35 // like NOT_VALID but may scroll
-#define NOT_VALID 40 // buffer needs complete redraw
-#define CLEAR 50 // screen messed up, clear it
-
-/// corner value flags for hsep_connected and vsep_connected
-typedef enum {
- WC_TOP_LEFT = 0,
- WC_TOP_RIGHT,
- WC_BOTTOM_LEFT,
- WC_BOTTOM_RIGHT,
-} WindowCorner;
-
-// Maximum columns for terminal highlight attributes
-#define TERM_ATTRS_MAX 1024
+EXTERN match_T screen_search_hl; // used for 'hlsearch' highlight matching
/// Array defining what should be done when tabline is clicked
EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL);
@@ -39,13 +18,6 @@ EXTERN long tab_page_click_defs_size INIT(= 0);
#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width)
#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height)
-// While redrawing the screen this flag is set. It means the screen size
-// ('lines' and 'rows') must not be changed.
-EXTERN bool updating_screen INIT(= 0);
-
-// While resizing the screen this flag is set.
-EXTERN bool resizing_screen INIT(= 0);
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "screen.h.generated.h"
#endif
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 403e2f3aa4..c820817a71 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -15,12 +15,14 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/funcs.h"
#include "nvim/ex_cmds.h"
-#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
@@ -42,8 +44,8 @@
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
@@ -271,7 +273,7 @@ void free_search_patterns(void)
free_spat(&spats[0]);
free_spat(&spats[1]);
- memset(spats, 0, sizeof(spats));
+ CLEAR_FIELD(spats);
if (mr_pattern_alloced) {
xfree(mr_pattern);
@@ -431,7 +433,7 @@ void set_last_csearch(int c, char_u *s, int len)
if (len) {
memcpy(lastc_bytes, s, (size_t)len);
} else {
- memset(lastc_bytes, 0, sizeof(lastc_bytes));
+ CLEAR_FIELD(lastc_bytes);
}
}
@@ -513,7 +515,7 @@ void last_pat_prog(regmmatch_T *regmatch)
}
++emsg_off; // So it doesn't beep if bad expr
(void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch);
- --emsg_off;
+ emsg_off--;
}
/// Lowest level search function.
@@ -1036,7 +1038,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count,
char_u *p;
long c;
char_u *dircp;
- char_u *strcopy = NULL;
+ char *strcopy = NULL;
char_u *ps;
char_u *msgbuf = NULL;
size_t len;
@@ -1123,13 +1125,13 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count,
* Find end of regular expression.
* If there is a matching '/' or '?', toss it.
*/
- ps = strcopy;
+ ps = (char_u *)strcopy;
p = skip_regexp(pat, search_delim, p_magic, &strcopy);
- if (strcopy != ps) {
+ if (strcopy != (char *)ps) {
// made a copy of "pat" to change "\?" to "?"
searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy));
- pat = strcopy;
- searchstr = strcopy;
+ pat = (char_u *)strcopy;
+ searchstr = (char_u *)strcopy;
}
if (*p == search_delim) {
dircp = p; // remember where we put the NUL
@@ -1160,9 +1162,9 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count,
} else { // single '+'
spats[0].off.off = 1;
}
- ++p;
+ p++;
while (ascii_isdigit(*p)) { // skip number
- ++p;
+ p++;
}
}
@@ -1426,7 +1428,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count,
emsg(_("E386: Expected '?' or '/' after ';'"));
goto end_do_search;
}
- ++pat;
+ pat++;
}
if (options & SEARCH_MARK) {
@@ -2136,10 +2138,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
}
if (*ptr == '"'
&& (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\'')) {
- ++do_quotes;
+ do_quotes++;
}
if (*ptr == '\\' && ptr[1] != NUL) {
- ++ptr;
+ ptr++;
}
}
do_quotes &= 1; // result is 1 with even number of quotes
@@ -2342,7 +2344,7 @@ int check_linecomment(const char_u *line)
&& !is_pos_in_string(line, (colnr_T)(p - line))) {
break;
}
- ++p;
+ p++;
}
}
@@ -2629,7 +2631,7 @@ bool findpar(bool *pincl, int dir, long count, int what, int both)
}
setpcmark();
if (both && *ml_get(curr) == '}') { // include line with '}'
- ++curr;
+ curr++;
}
curwin->w_cursor.lnum = curr;
if (curr == curbuf->b_ml.ml_line_count && what != '}') {
@@ -2667,7 +2669,7 @@ static int inmacro(char_u *opt, char_u *s)
&& (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) {
break;
}
- ++macro;
+ macro++;
if (macro[0] == NUL) {
break;
}
@@ -3110,7 +3112,7 @@ int current_word(oparg_T *oap, long count, int include, int bigword)
oap->start = start_pos;
oap->motion_type = kMTCharWise;
}
- --count;
+ count--;
}
/*
@@ -3160,7 +3162,7 @@ int current_word(oparg_T *oap, long count, int include, int bigword)
}
}
}
- --count;
+ count--;
}
if (include_white && (cls() != 0
@@ -3323,7 +3325,7 @@ extend:
} else {
ncount = count;
if (start_blank) {
- --ncount;
+ ncount--;
}
}
if (ncount > 0) {
@@ -3851,7 +3853,7 @@ extend:
break;
}
}
- --start_lnum;
+ start_lnum--;
}
/*
@@ -3859,13 +3861,13 @@ extend:
*/
end_lnum = start_lnum;
while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) {
- ++end_lnum;
+ end_lnum++;
}
end_lnum--;
i = (int)count;
if (!include && white_in_front) {
- --i;
+ i--;
}
while (i--) {
if (end_lnum == curbuf->b_ml.ml_line_count) {
@@ -3877,14 +3879,12 @@ extend:
}
if (include || !do_white) {
- ++end_lnum;
- /*
- * skip to end of paragraph
- */
+ end_lnum++;
+ // skip to end of paragraph
while (end_lnum < curbuf->b_ml.ml_line_count
&& !linewhite(end_lnum + 1)
&& !startPS(end_lnum + 1, 0, 0)) {
- ++end_lnum;
+ end_lnum++;
}
}
@@ -3898,7 +3898,7 @@ extend:
if (include || do_white) {
while (end_lnum < curbuf->b_ml.ml_line_count
&& linewhite(end_lnum + 1)) {
- ++end_lnum;
+ end_lnum++;
}
}
}
@@ -3909,7 +3909,7 @@ extend:
*/
if (!white_in_front && !linewhite(end_lnum) && include) {
while (start_lnum > 1 && linewhite(start_lnum - 1)) {
- --start_lnum;
+ start_lnum--;
}
}
@@ -3983,7 +3983,7 @@ static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *e
if (escape != NULL) {
while (col_start - n > 0 && vim_strchr((char *)escape,
line[col_start - n - 1]) != NULL) {
- ++n;
+ n++;
}
}
if (n & 1) {
@@ -4169,11 +4169,11 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar)
if (include) {
if (ascii_iswhite(line[col_end + 1])) {
while (ascii_iswhite(line[col_end + 1])) {
- ++col_end;
+ col_end++;
}
} else {
while (col_start > 0 && ascii_iswhite(line[col_start - 1])) {
- --col_start;
+ col_start--;
}
}
}
@@ -4551,7 +4551,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst
static buf_T *lbuf = NULL;
proftime_T start;
- memset(stat, 0, sizeof(searchstat_T));
+ CLEAR_POINTER(stat);
if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) {
stat->cur = cur;
@@ -5467,7 +5467,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo
}
did_show = true;
while (depth_displayed < depth && !got_int) {
- ++depth_displayed;
+ depth_displayed++;
for (i = 0; i < depth_displayed; i++) {
msg_puts(" ");
}
@@ -5509,11 +5509,11 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo
// Avoid checking before the start of the line, can
// happen if \zs appears in the regexp.
if (p[-1] == '"' || p[-1] == '<') {
- --p;
- ++i;
+ p--;
+ i++;
}
if (p[i] == '"' || p[i] == '>') {
- ++i;
+ i++;
}
}
save_char = p[i];
@@ -5561,7 +5561,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo
// Something wrong. We will forget one of our already visited files
// now.
xfree(files[old_files].name);
- ++old_files;
+ old_files++;
}
files[depth].name = curr_fname = new_fname;
files[depth].lnum = 0;
@@ -5860,7 +5860,7 @@ exit_matched:
while (depth >= 0 && !already
&& vim_fgets(line = file_line, LSIZE, files[depth].fp)) {
fclose(files[depth].fp);
- --old_files;
+ old_files--;
files[old_files].name = files[depth].name;
files[old_files].matched = files[depth].matched;
depth--;
@@ -5948,10 +5948,10 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action,
if (fp != NULL) {
// We used fgets(), so get rid of newline at end
if (p >= line && *p == '\n') {
- --p;
+ p--;
}
if (p >= line && *p == '\r') {
- --p;
+ p--;
}
*(p + 1) = NUL;
}
@@ -5996,7 +5996,7 @@ void get_search_pattern(SearchPattern *const pat)
void get_substitute_pattern(SearchPattern *const pat)
{
memcpy(pat, &(spats[1]), sizeof(spats[1]));
- memset(&(pat->off), 0, sizeof(pat->off));
+ CLEAR_FIELD(pat->off);
}
/// Set last search pattern
@@ -6012,7 +6012,7 @@ void set_substitute_pattern(const SearchPattern pat)
{
free_spat(&spats[1]);
memcpy(&(spats[1]), &pat, sizeof(spats[1]));
- memset(&(spats[1].off), 0, sizeof(spats[1].off));
+ CLEAR_FIELD(spats[1].off);
}
/// Set last used search pattern
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index cd3b967a9f..0927473be0 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -16,11 +16,12 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/cmdhist.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
-#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
@@ -2188,7 +2189,7 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
if (kh_ret > 0) {
- memset(filemarks, 0, sizeof(*filemarks));
+ CLEAR_POINTER(filemarks);
}
if (entry.timestamp > filemarks->greatest_timestamp) {
filemarks->greatest_timestamp = entry.timestamp;
@@ -2451,6 +2452,27 @@ static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
}
}
+/// Translate a history type number to the associated character
+static int hist_type2char(const int type)
+ FUNC_ATTR_CONST
+{
+ switch (type) {
+ case HIST_CMD:
+ return ':';
+ case HIST_SEARCH:
+ return '/';
+ case HIST_EXPR:
+ return '=';
+ case HIST_INPUT:
+ return '@';
+ case HIST_DEBUG:
+ return '>';
+ default:
+ abort();
+ }
+ return NUL;
+}
+
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.
@@ -2730,7 +2752,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
if (kh_ret > 0) {
- memset(filemarks, 0, sizeof(*filemarks));
+ CLEAR_POINTER(filemarks);
}
do {
fmark_T fm;
@@ -3456,7 +3478,7 @@ shada_read_next_item_start:
// data union are NULL so they are safe to xfree(). This is needed in case
// somebody calls goto shada_read_next_item_error before anything is set in
// the switch.
- memset(entry, 0, sizeof(*entry));
+ CLEAR_POINTER(entry);
if (sd_reader->eof) {
return kSDReadStatusFinished;
}
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 7b6b55fede..f1ddbfd147 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -9,6 +9,7 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/funcs.h"
#include "nvim/ex_docmd.h"
@@ -16,7 +17,6 @@
#include "nvim/highlight_group.h"
#include "nvim/move.h"
#include "nvim/option.h"
-#include "nvim/screen.h"
#include "nvim/sign.h"
#include "nvim/syntax.h"
#include "nvim/vim.h"
@@ -442,27 +442,24 @@ static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group
/// @param max_signs the number of signs, with priority for the ones
/// with the highest Ids.
/// @return Attrs of the matching sign, or NULL
-sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int max_signs)
+SignTextAttrs *sign_get_attr(int idx, SignTextAttrs sattrs[], int max_signs)
{
- sign_attrs_T *matches[SIGN_SHOW_MAX];
- int nr_matches = 0;
+ SignTextAttrs *matches[SIGN_SHOW_MAX];
+ int sattr_matches = 0;
for (int i = 0; i < SIGN_SHOW_MAX; i++) {
- if ((type == SIGN_TEXT && sattrs[i].sat_text != NULL)
- || (type == SIGN_LINEHL && sattrs[i].sat_linehl != 0)
- || (type == SIGN_NUMHL && sattrs[i].sat_numhl != 0)) {
- matches[nr_matches] = &sattrs[i];
- nr_matches++;
+ if (sattrs[i].text != NULL) {
+ matches[sattr_matches++] = &sattrs[i];
// attr list is sorted with most important (priority, id), thus we
// may stop as soon as we have max_signs matches
- if (nr_matches >= max_signs) {
+ if (sattr_matches >= max_signs) {
break;
}
}
}
- if (nr_matches > idx) {
- return matches[nr_matches - idx - 1];
+ if (sattr_matches > idx) {
+ return matches[sattr_matches - idx - 1];
}
return NULL;
@@ -474,12 +471,12 @@ sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int m
/// @param lnum Line in which to search
/// @param sattrs Output array for attrs
/// @return Number of signs of which attrs were found
-int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[])
+int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriAttr *num_attrs,
+ HlPriAttr *line_attrs, HlPriAttr *cul_attrs)
{
sign_entry_T *sign;
- sign_T *sp;
- int nr_matches = 0;
+ int sattr_matches = 0;
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
if (sign->se_lnum > lnum) {
@@ -488,37 +485,39 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[])
break;
}
- if (sign->se_lnum == lnum) {
- sign_attrs_T sattr;
- memset(&sattr, 0, sizeof(sattr));
- sattr.sat_typenr = sign->se_typenr;
- sp = find_sign_by_typenr(sign->se_typenr);
- if (sp != NULL) {
- sattr.sat_text = sp->sn_text;
- if (sattr.sat_text != NULL && sp->sn_text_hl != 0) {
- sattr.sat_texthl = syn_id2attr(sp->sn_text_hl);
- }
- if (sp->sn_line_hl != 0) {
- sattr.sat_linehl = syn_id2attr(sp->sn_line_hl);
- }
- if (sp->sn_cul_hl != 0) {
- sattr.sat_culhl = syn_id2attr(sp->sn_cul_hl);
- }
- if (sp->sn_num_hl != 0) {
- sattr.sat_numhl = syn_id2attr(sp->sn_num_hl);
- }
- // Store the priority so we can mesh in extmark signs later
- sattr.sat_prio = sign->se_priority;
- }
+ if (sign->se_lnum < lnum) {
+ continue;
+ }
- sattrs[nr_matches] = sattr;
- nr_matches++;
- if (nr_matches == SIGN_SHOW_MAX) {
- break;
+ sign_T *sp = find_sign_by_typenr(sign->se_typenr);
+ if (sp == NULL) {
+ continue;
+ }
+
+ if (sp->sn_text != NULL && sattr_matches < SIGN_SHOW_MAX) {
+ sattrs[sattr_matches++] = (SignTextAttrs) {
+ .text = sp->sn_text,
+ .hl_attr_id = sp->sn_text_hl == 0 ? 0 : syn_id2attr(sp->sn_text_hl),
+ .priority = sign->se_priority
+ };
+ }
+
+ struct { HlPriAttr *dest; int hl; } cattrs[] = {
+ { line_attrs, sp->sn_line_hl },
+ { num_attrs, sp->sn_num_hl },
+ { cul_attrs, sp->sn_cul_hl },
+ { NULL, -1 },
+ };
+ for (int i = 0; cattrs[i].dest; i++) {
+ if (cattrs[i].hl != 0 && sign->se_priority >= cattrs[i].dest->priority) {
+ *cattrs[i].dest = (HlPriAttr) {
+ .attr_id = syn_id2attr(cattrs[i].hl),
+ .priority = sign->se_priority
+ };
}
}
}
- return nr_matches;
+ return sattr_matches;
}
/// Delete sign 'id' in group 'group' from buffer 'buf'.
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index e4ece71846..a4fb325ec8 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -33,15 +33,11 @@ struct sign_entry {
};
/// Sign attributes. Used by the screen refresh routines.
-typedef struct sign_attrs_S {
- int sat_typenr;
- char_u *sat_text;
- int sat_texthl;
- int sat_linehl;
- int sat_culhl;
- int sat_numhl;
- int sat_prio; // Used for inserting extmark signs
-} sign_attrs_T;
+typedef struct {
+ char_u *text;
+ int hl_attr_id;
+ int priority;
+} SignTextAttrs;
#define SIGN_SHOW_MAX 9
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index ceb35af4b8..1e44d328b3 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -56,18 +56,6 @@
// Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a
// specific word.
-// Use this to adjust the score after finding suggestions, based on the
-// suggested word sounding like the bad word. This is much faster than doing
-// it for every possible suggestion.
-// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@"
-// vs "ht") and goes down in the list.
-// Used when 'spellsuggest' is set to "best".
-#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4)
-
-// Do the opposite: based on a maximum end score and a known sound score,
-// compute the maximum word score that can be used.
-#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3)
-
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
@@ -84,15 +72,12 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
-#include "nvim/fileio.h"
#include "nvim/func_attr.h"
#include "nvim/garray.h"
-#include "nvim/getchar.h"
#include "nvim/hashtab.h"
#include "nvim/input.h"
#include "nvim/insexpand.h"
@@ -108,20 +93,14 @@
#include "nvim/os_unix.h"
#include "nvim/path.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
+#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/ui.h"
#include "nvim/undo.h"
-// only used for su_badflags
-#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI
-
-#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP)
-
// Result values. Lower number is accepted over higher one.
#define SP_BANNED (-1)
#define SP_RARE 0
@@ -136,104 +115,6 @@ slang_T *first_lang = NULL;
// file used for "zG" and "zW"
char_u *int_wordlist = NULL;
-typedef struct wordcount_S {
- uint16_t wc_count; // nr of times word was seen
- char_u wc_word[1]; // word, actually longer
-} wordcount_T;
-
-#define WC_KEY_OFF offsetof(wordcount_T, wc_word)
-#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF))
-#define MAXWORDCOUNT 0xffff
-
-// Information used when looking for suggestions.
-typedef struct suginfo_S {
- garray_T su_ga; // suggestions, contains "suggest_T"
- int su_maxcount; // max. number of suggestions displayed
- int su_maxscore; // maximum score for adding to su_ga
- int su_sfmaxscore; // idem, for when doing soundfold words
- garray_T su_sga; // like su_ga, sound-folded scoring
- char_u *su_badptr; // start of bad word in line
- int su_badlen; // length of detected bad word in line
- int su_badflags; // caps flags for bad word
- char_u su_badword[MAXWLEN]; // bad word truncated at su_badlen
- char_u su_fbadword[MAXWLEN]; // su_badword case-folded
- char_u su_sal_badword[MAXWLEN]; // su_badword soundfolded
- hashtab_T su_banned; // table with banned words
- slang_T *su_sallang; // default language for sound folding
-} suginfo_T;
-
-// One word suggestion. Used in "si_ga".
-typedef struct {
- char_u *st_word; // suggested word, allocated string
- int st_wordlen; // STRLEN(st_word)
- int st_orglen; // length of replaced text
- int st_score; // lower is better
- int st_altscore; // used when st_score compares equal
- bool st_salscore; // st_score is for soundalike
- bool st_had_bonus; // bonus already included in score
- slang_T *st_slang; // language used for sound folding
-} suggest_T;
-
-#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i])
-
-// True if a word appears in the list of banned words.
-#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word)))
-
-// Number of suggestions kept when cleaning up. We need to keep more than
-// what is displayed, because when rescore_suggestions() is called the score
-// may change and wrong suggestions may be removed later.
-#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \
- 130 ? 150 : (su)->su_maxcount + 20)
-
-// Threshold for sorting and cleaning up suggestions. Don't want to keep lots
-// of suggestions that are not going to be displayed.
-#define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50)
-
-// score for various changes
-#define SCORE_SPLIT 149 // split bad word
-#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS
-#define SCORE_ICASE 52 // slightly different case
-#define SCORE_REGION 200 // word is for different region
-#define SCORE_RARE 180 // rare word
-#define SCORE_SWAP 75 // swap two characters
-#define SCORE_SWAP3 110 // swap two characters in three
-#define SCORE_REP 65 // REP replacement
-#define SCORE_SUBST 93 // substitute a character
-#define SCORE_SIMILAR 33 // substitute a similar character
-#define SCORE_SUBCOMP 33 // substitute a composing character
-#define SCORE_DEL 94 // delete a character
-#define SCORE_DELDUP 66 // delete a duplicated character
-#define SCORE_DELCOMP 28 // delete a composing character
-#define SCORE_INS 96 // insert a character
-#define SCORE_INSDUP 67 // insert a duplicate character
-#define SCORE_INSCOMP 30 // insert a composing character
-#define SCORE_NONWORD 103 // change non-word to word char
-
-#define SCORE_FILE 30 // suggestion from a file
-#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower.
- // 350 allows for about three changes.
-
-#define SCORE_COMMON1 30 // subtracted for words seen before
-#define SCORE_COMMON2 40 // subtracted for words often seen
-#define SCORE_COMMON3 50 // subtracted for words very often seen
-#define SCORE_THRES2 10 // word count threshold for COMMON2
-#define SCORE_THRES3 100 // word count threshold for COMMON3
-
-// When trying changed soundfold words it becomes slow when trying more than
-// two changes. With less than two changes it's slightly faster but we miss a
-// few good suggestions. In rare cases we need to try three of four changes.
-#define SCORE_SFMAX1 200 // maximum score for first try
-#define SCORE_SFMAX2 300 // maximum score for second try
-#define SCORE_SFMAX3 400 // maximum score for third try
-
-#define SCORE_BIG (SCORE_INS * 3) // big difference
-#define SCORE_MAXMAX 999999 // accept any score
-#define SCORE_LIMITMAX 350 // for spell_edit_score_limit()
-
-// for spell_edit_score_limit() we need to know the minimum value of
-// SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS
-#define SCORE_EDIT_MIN SCORE_SIMILAR
-
// Structure to store info for word matching.
typedef struct matchinf_S {
langp_T *mi_lp; // info for language and region
@@ -289,38 +170,10 @@ typedef struct syl_item_S {
spelltab_T spelltab;
int did_set_spelltab;
-// structure used to store soundfolded words that add_sound_suggest() has
-// handled already.
-typedef struct {
- int16_t sft_score; // lowest score used
- char_u sft_word[1]; // soundfolded word, actually longer
-} sftword_T;
-
-typedef struct {
- int badi;
- int goodi;
- int score;
-} limitscore_T;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spell.c.generated.h"
#endif
-// values for ts_isdiff
-#define DIFF_NONE 0 // no different byte (yet)
-#define DIFF_YES 1 // different byte found
-#define DIFF_INSERT 2 // inserting character
-
-// values for ts_flags
-#define TSF_PREFIXOK 1 // already checked that prefix is OK
-#define TSF_DIDSPLIT 2 // tried split at this point
-#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index
-
-// special values ts_prefixdepth
-#define PFD_NOPREFIX 0xff // not using prefixes
-#define PFD_PREFIXTREE 0xfe // walking through the prefix tree
-#define PFD_NOTSPECIAL 0xfd // highest value that's not special
-
// mode values for find_word
#define FIND_FOLDWORD 0 // find word case-folded
#define FIND_KEEPWORD 1 // find keep-case word
@@ -331,8 +184,8 @@ typedef struct {
char *e_format = N_("E759: Format error in spell file");
// Remember what "z?" replaced.
-static char_u *repl_from = NULL;
-static char_u *repl_to = NULL;
+char_u *repl_from = NULL;
+char_u *repl_to = NULL;
/// Main spell-checking function.
/// "ptr" points to a character that could be the start of a word.
@@ -374,7 +227,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou
return 1;
}
- memset(&mi, 0, sizeof(matchinf_T));
+ CLEAR_FIELD(mi);
// A number is always OK. Also skip hexadecimal numbers 0xFF99 and
// 0X99FF. But always do check spelling to find "3GPP" and "11
@@ -639,13 +492,13 @@ static void find_word(matchinf_T *mip, int mode)
}
endlen[endidxcnt] = wlen;
endidx[endidxcnt++] = arridx++;
- --len;
+ len--;
// Skip over the zeros, there can be several flag/region
// combinations.
while (len > 0 && byts[arridx] == 0) {
- ++arridx;
- --len;
+ arridx++;
+ len--;
}
if (len == 0) {
break; // no children, word must end here
@@ -683,8 +536,8 @@ static void find_word(matchinf_T *mip, int mode)
// Continue at the child (if there is one).
arridx = idxs[lo];
- ++wlen;
- --flen;
+ wlen++;
+ flen--;
// One space in the good word may stand for several spaces in the
// checked word.
@@ -696,8 +549,8 @@ static void find_word(matchinf_T *mip, int mode)
if (ptr[wlen] != ' ' && ptr[wlen] != TAB) {
break;
}
- ++wlen;
- --flen;
+ wlen++;
+ flen--;
}
}
}
@@ -708,7 +561,7 @@ static void find_word(matchinf_T *mip, int mode)
// Verify that one of the possible endings is valid. Try the longest
// first.
while (endidxcnt > 0) {
- --endidxcnt;
+ endidxcnt--;
arridx = endidx[endidxcnt];
wlen = endlen[endidxcnt];
@@ -1050,7 +903,7 @@ static void find_word(matchinf_T *mip, int mode)
/// end of ptr[wlen] and the second part matches after it.
///
/// @param gap &sl_comppat
-static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap)
+bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap)
{
char_u *p;
int len;
@@ -1072,7 +925,7 @@ static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap)
// Returns true if "flags" is a valid sequence of compound flags and "word"
// does not have too many syllables.
-static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags)
+bool can_compound(slang_T *slang, const char_u *word, const char_u *flags)
FUNC_ATTR_NONNULL_ALL
{
char_u uflags[MAXWLEN * 2] = { 0 };
@@ -1101,37 +954,11 @@ static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags
return true;
}
-// Returns true when the sequence of flags in "compflags" plus "flag" can
-// possibly form a valid compounded word. This also checks the COMPOUNDRULE
-// lines if they don't contain wildcards.
-static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag)
-{
- // If the flag doesn't appear in sl_compstartflags or sl_compallflags
- // then it can't possibly compound.
- if (!byte_in_str(sp->ts_complen == sp->ts_compsplit
- ? slang->sl_compstartflags : slang->sl_compallflags, flag)) {
- return false;
- }
-
- // If there are no wildcards, we can check if the flags collected so far
- // possibly can form a match with COMPOUNDRULE patterns. This only
- // makes sense when we have two or more words.
- if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) {
- compflags[sp->ts_complen] = (char_u)flag;
- compflags[sp->ts_complen + 1] = NUL;
- bool v = match_compoundrule(slang, compflags + sp->ts_compsplit);
- compflags[sp->ts_complen] = NUL;
- return v;
- }
-
- return true;
-}
-
// Returns true if the compound flags in compflags[] match the start of any
// compound rule. This is used to stop trying a compound if the flags
// collected so far can't possibly match any compound rule.
// Caller must check that slang->sl_comprules is not NULL.
-static bool match_compoundrule(slang_T *slang, char_u *compflags)
+bool match_compoundrule(slang_T *slang, char_u *compflags)
{
char_u *p;
int i;
@@ -1154,7 +981,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags)
bool match = false;
// compare against all the flags in []
- ++p;
+ p++;
while (*p != ']' && *p != NUL) {
if (*p++ == c) {
match = true;
@@ -1166,7 +993,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags)
} else if (*p != c) {
break; // flag of word doesn't match flag in pattern
}
- ++p;
+ p++;
}
// Skip to the next "/", where the next pattern starts.
@@ -1188,8 +1015,8 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags)
/// @param totprefcnt nr of prefix IDs
/// @param arridx idx in sl_pidxs[]
/// @param cond_req only use prefixes with a condition
-static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang,
- bool cond_req)
+int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang,
+ bool cond_req)
{
int prefcnt;
int pidx;
@@ -1282,8 +1109,8 @@ static void find_prefix(matchinf_T *mip, int mode)
mip->mi_prefarridx = arridx;
mip->mi_prefcnt = len;
while (len > 0 && byts[arridx] == 0) {
- ++arridx;
- --len;
+ arridx++;
+ len--;
}
mip->mi_prefcnt -= len;
@@ -1332,8 +1159,8 @@ static void find_prefix(matchinf_T *mip, int mode)
// Continue at the child (if there is one).
arridx = idxs[lo];
- ++wlen;
- --flen;
+ wlen++;
+ flen--;
}
}
@@ -1368,7 +1195,7 @@ static int fold_more(matchinf_T *mip)
///
/// @param wordflags Flags for the checked word.
/// @param treeflags Flags for the word in the spell tree.
-static bool spell_valid_case(int wordflags, int treeflags)
+bool spell_valid_case(int wordflags, int treeflags)
{
return (wordflags == WF_ALLCAP && (treeflags & WF_FIXCAP) == 0)
|| ((treeflags & (WF_ALLCAP | WF_KEEPCAP)) == 0
@@ -1377,7 +1204,7 @@ static bool spell_valid_case(int wordflags, int treeflags)
}
// Returns true if spell checking is not enabled.
-static bool no_spell_checking(win_T *wp)
+bool no_spell_checking(win_T *wp)
{
if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL
|| GA_EMPTY(&wp->w_s->b_langp)) {
@@ -1581,7 +1408,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
capcol = -1;
} else {
if (lnum < wp->w_buffer->b_ml.ml_line_count) {
- ++lnum;
+ lnum++;
} else if (!p_ws) {
break; // at first line and 'nowrapscan'
} else {
@@ -1609,7 +1436,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
}
// Capcol skips over the inserted space.
- --capcol;
+ capcol--;
// But after empty line check first word in next line
if (empty_line) {
@@ -1903,38 +1730,6 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count)
}
}
-/// Adjust the score of common words.
-///
-/// @param split word was split, less bonus
-static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split)
-{
- wordcount_T *wc;
- int bonus;
- int newscore;
-
- hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word);
- if (!HASHITEM_EMPTY(hi)) {
- wc = HI2WC(hi);
- if (wc->wc_count < SCORE_THRES2) {
- bonus = SCORE_COMMON1;
- } else if (wc->wc_count < SCORE_THRES3) {
- bonus = SCORE_COMMON2;
- } else {
- bonus = SCORE_COMMON3;
- }
- if (split) {
- newscore = score - bonus / 2;
- } else {
- newscore = score - bonus;
- }
- if (newscore < 0) {
- return 0;
- }
- return newscore;
- }
- return score;
-}
-
// Returns true if byte "n" appears in "str".
// Like strchr() but independent of locale.
bool byte_in_str(char_u *str, int n)
@@ -2016,7 +1811,7 @@ static int count_syllables(slang_T *slang, const char_u *word)
}
}
if (len != 0) { // found a match, count syllable
- ++cnt;
+ cnt++;
skip = false;
} else {
// No recognized syllable item, at least a syllable char then?
@@ -2038,7 +1833,7 @@ static int count_syllables(slang_T *slang, const char_u *word)
char *did_set_spelllang(win_T *wp)
{
garray_T ga;
- char_u *splp;
+ char *splp;
char_u *region;
char_u region_cp[3];
bool filename;
@@ -2050,7 +1845,7 @@ char *did_set_spelllang(win_T *wp)
int len;
char_u *p;
int round;
- char_u *spf;
+ char *spf;
char_u *use_region = NULL;
bool dont_use_region = false;
bool nobreak = false;
@@ -2080,9 +1875,9 @@ char *did_set_spelllang(win_T *wp)
wp->w_s->b_cjk = 0;
// Loop over comma separated language names.
- for (splp = spl_copy; *splp != NUL;) {
+ for (splp = (char *)spl_copy; *splp != NUL;) {
// Get one language name.
- copy_option_part((char **)&splp, (char *)lang, MAXWLEN, ",");
+ copy_option_part(&splp, (char *)lang, MAXWLEN, ",");
region = NULL;
len = (int)STRLEN(lang);
@@ -2204,8 +1999,8 @@ char *did_set_spelllang(win_T *wp)
// round 1: load first name in 'spellfile'.
// round 2: load second name in 'spellfile.
// etc.
- spf = curwin->w_s->b_p_spf;
- for (round = 0; round == 0 || *spf != NUL; ++round) {
+ spf = (char *)curwin->w_s->b_p_spf;
+ for (round = 0; round == 0 || *spf != NUL; round++) {
if (round == 0) {
// Internal wordlist, if there is one.
if (int_wordlist == NULL) {
@@ -2214,7 +2009,7 @@ char *did_set_spelllang(win_T *wp)
int_wordlist_spl(spf_name);
} else {
// One entry in 'spellfile'.
- copy_option_part((char **)&spf, (char *)spf_name, MAXPATHL - 5, ",");
+ copy_option_part(&spf, (char *)spf_name, MAXPATHL - 5, ",");
STRCAT(spf_name, ".spl");
// If it was already found above then skip it.
@@ -2338,7 +2133,7 @@ theend:
// Clear the midword characters for buffer "buf".
static void clear_midword(win_T *wp)
{
- memset(wp->w_s->b_spell_ismw, 0, 256);
+ CLEAR_FIELD(wp->w_s->b_spell_ismw);
XFREE_CLEAR(wp->w_s->b_spell_ismw_mb);
}
@@ -2444,51 +2239,6 @@ int captype(char_u *word, char_u *end)
return 0;
}
-// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a
-// capital. So that make_case_word() can turn WOrd into Word.
-// Add ALLCAP for "WOrD".
-static int badword_captype(char_u *word, char_u *end)
- FUNC_ATTR_NONNULL_ALL
-{
- int flags = captype(word, end);
- int c;
- int l, u;
- bool first;
- char_u *p;
-
- if (flags & WF_KEEPCAP) {
- // Count the number of UPPER and lower case letters.
- l = u = 0;
- first = false;
- for (p = word; p < end; MB_PTR_ADV(p)) {
- c = utf_ptr2char((char *)p);
- if (SPELL_ISUPPER(c)) {
- ++u;
- if (p == word) {
- first = true;
- }
- } else {
- ++l;
- }
- }
-
- // If there are more UPPER than lower case letters suggest an
- // ALLCAP word. Otherwise, if the first letter is UPPER then
- // suggest ONECAP. Exception: "ALl" most likely should be "All",
- // require three upper case letters.
- if (u > l && u > 2) {
- flags |= WF_ALLCAP;
- } else if (first) {
- flags |= WF_ONECAP;
- }
-
- if (u >= 2 && l >= 2) { // maCARONI maCAroni
- flags |= WF_MIXCAP;
- }
- }
- return flags;
-}
-
// Delete the internal wordlist and its .spl file.
void spell_delete_wordlist(void)
{
@@ -2547,36 +2297,6 @@ void spell_reload(void)
}
}
-// Opposite of offset2bytes().
-// "pp" points to the bytes and is advanced over it.
-// Returns the offset.
-static int bytes2offset(char_u **pp)
-{
- char_u *p = *pp;
- int nr;
- int c;
-
- c = *p++;
- if ((c & 0x80) == 0x00) { // 1 byte
- nr = c - 1;
- } else if ((c & 0xc0) == 0x80) { // 2 bytes
- nr = (c & 0x3f) - 1;
- nr = nr * 255 + (*p++ - 1);
- } else if ((c & 0xe0) == 0xc0) { // 3 bytes
- nr = (c & 0x1f) - 1;
- nr = nr * 255 + (*p++ - 1);
- nr = nr * 255 + (*p++ - 1);
- } else { // 4 bytes
- nr = (c & 0x0f) - 1;
- nr = nr * 255 + (*p++ - 1);
- nr = nr * 255 + (*p++ - 1);
- nr = nr * 255 + (*p++ - 1);
- }
-
- *pp = p;
- return nr;
-}
-
// Open a spell buffer. This is a nameless buffer that is not in the buffer
// list and only contains text lines. Can use a swapfile to reduce memory
// use.
@@ -2610,9 +2330,9 @@ void clear_spell_chartab(spelltab_T *sp)
{
int i;
- // Init everything to false.
- memset(sp->st_isw, false, sizeof(sp->st_isw));
- memset(sp->st_isu, false, sizeof(sp->st_isu));
+ // Init everything to false (zero).
+ CLEAR_FIELD(sp->st_isw);
+ CLEAR_FIELD(sp->st_isu);
for (i = 0; i < 256; i++) {
sp->st_fold[i] = (char_u)i;
@@ -2665,7 +2385,7 @@ void init_spell_chartab(void)
/// Thus this only works properly when past the first character of the word.
///
/// @param wp Buffer used.
-static bool spell_iswordp(const char_u *p, const win_T *wp)
+bool spell_iswordp(const char_u *p, const win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
int c;
@@ -2783,298 +2503,9 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle
return OK;
}
-// values for sps_flags
-#define SPS_BEST 1
-#define SPS_FAST 2
-#define SPS_DOUBLE 4
-
-static int sps_flags = SPS_BEST; // flags from 'spellsuggest'
-static int sps_limit = 9999; // max nr of suggestions given
-
-// Check the 'spellsuggest' option. Return FAIL if it's wrong.
-// Sets "sps_flags" and "sps_limit".
-int spell_check_sps(void)
-{
- char_u *p;
- char_u *s;
- char_u buf[MAXPATHL];
- int f;
-
- sps_flags = 0;
- sps_limit = 9999;
-
- for (p = p_sps; *p != NUL;) {
- copy_option_part((char **)&p, (char *)buf, MAXPATHL, ",");
-
- f = 0;
- if (ascii_isdigit(*buf)) {
- s = buf;
- sps_limit = getdigits_int((char **)&s, true, 0);
- if (*s != NUL && !ascii_isdigit(*s)) {
- f = -1;
- }
- } else if (STRCMP(buf, "best") == 0) {
- f = SPS_BEST;
- } else if (STRCMP(buf, "fast") == 0) {
- f = SPS_FAST;
- } else if (STRCMP(buf, "double") == 0) {
- f = SPS_DOUBLE;
- } else if (STRNCMP(buf, "expr:", 5) != 0
- && STRNCMP(buf, "file:", 5) != 0) {
- f = -1;
- }
-
- if (f == -1 || (sps_flags != 0 && f != 0)) {
- sps_flags = SPS_BEST;
- sps_limit = 9999;
- return FAIL;
- }
- if (f != 0) {
- sps_flags = f;
- }
- }
-
- if (sps_flags == 0) {
- sps_flags = SPS_BEST;
- }
-
- return OK;
-}
-
-// "z=": Find badly spelled word under or after the cursor.
-// Give suggestions for the properly spelled word.
-// In Visual mode use the highlighted word as the bad word.
-// When "count" is non-zero use that suggestion.
-void spell_suggest(int count)
-{
- char_u *line;
- pos_T prev_cursor = curwin->w_cursor;
- char_u wcopy[MAXWLEN + 2];
- char_u *p;
- int c;
- suginfo_T sug;
- suggest_T *stp;
- int mouse_used;
- int need_cap;
- int limit;
- int selected = count;
- int badlen = 0;
- int msg_scroll_save = msg_scroll;
- const int wo_spell_save = curwin->w_p_spell;
-
- if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
- curwin->w_p_spell = true;
- }
-
- if (*curwin->w_s->b_p_spl == NUL) {
- emsg(_(e_no_spell));
- return;
- }
-
- if (VIsual_active) {
- // Use the Visually selected text as the bad word. But reject
- // a multi-line selection.
- if (curwin->w_cursor.lnum != VIsual.lnum) {
- vim_beep(BO_SPELL);
- return;
- }
- badlen = (int)curwin->w_cursor.col - (int)VIsual.col;
- if (badlen < 0) {
- badlen = -badlen;
- } else {
- curwin->w_cursor.col = VIsual.col;
- }
- badlen++;
- end_visual_mode();
- } else
- // Find the start of the badly spelled word.
- if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0
- || curwin->w_cursor.col > prev_cursor.col) {
- // No bad word or it starts after the cursor: use the word under the
- // cursor.
- curwin->w_cursor = prev_cursor;
- line = get_cursor_line_ptr();
- p = line + curwin->w_cursor.col;
- // Backup to before start of word.
- while (p > line && spell_iswordp_nmw(p, curwin)) {
- MB_PTR_BACK(line, p);
- }
- // Forward to start of word.
- while (*p != NUL && !spell_iswordp_nmw(p, curwin)) {
- MB_PTR_ADV(p);
- }
-
- if (!spell_iswordp_nmw(p, curwin)) { // No word found.
- beep_flush();
- return;
- }
- curwin->w_cursor.col = (colnr_T)(p - line);
- }
-
- // Get the word and its length.
-
- // Figure out if the word should be capitalised.
- need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col);
-
- // Make a copy of current line since autocommands may free the line.
- line = vim_strsave(get_cursor_line_ptr());
-
- // Get the list of suggestions. Limit to 'lines' - 2 or the number in
- // 'spellsuggest', whatever is smaller.
- if (sps_limit > Rows - 2) {
- limit = Rows - 2;
- } else {
- limit = sps_limit;
- }
- spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
- true, need_cap, true);
-
- if (GA_EMPTY(&sug.su_ga)) {
- msg(_("Sorry, no suggestions"));
- } else if (count > 0) {
- if (count > sug.su_ga.ga_len) {
- smsg(_("Sorry, only %" PRId64 " suggestions"),
- (int64_t)sug.su_ga.ga_len);
- }
- } else {
- // When 'rightleft' is set the list is drawn right-left.
- cmdmsg_rl = curwin->w_p_rl;
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- }
-
- // List the suggestions.
- msg_start();
- msg_row = Rows - 1; // for when 'cmdheight' > 1
- lines_left = Rows; // avoid more prompt
- vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"),
- sug.su_badlen, sug.su_badptr);
- if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) {
- // And now the rabbit from the high hat: Avoid showing the
- // untranslated message rightleft.
- vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC",
- sug.su_badlen, sug.su_badptr);
- }
- msg_puts((const char *)IObuff);
- msg_clr_eos();
- msg_putchar('\n');
-
- msg_scroll = TRUE;
- for (int i = 0; i < sug.su_ga.ga_len; ++i) {
- stp = &SUG(sug.su_ga, i);
-
- // The suggested word may replace only part of the bad word, add
- // the not replaced part. But only when it's not getting too long.
- STRLCPY(wcopy, stp->st_word, MAXWLEN + 1);
- int el = sug.su_badlen - stp->st_orglen;
- if (el > 0 && stp->st_wordlen + el <= MAXWLEN) {
- STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1);
- }
- vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1);
- if (cmdmsg_rl) {
- rl_mirror(IObuff);
- }
- msg_puts((const char *)IObuff);
-
- vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy);
- msg_puts((const char *)IObuff);
-
- // The word may replace more than "su_badlen".
- if (sug.su_badlen < stp->st_orglen) {
- vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""),
- stp->st_orglen, sug.su_badptr);
- msg_puts((const char *)IObuff);
- }
-
- if (p_verbose > 0) {
- // Add the score.
- if (sps_flags & (SPS_DOUBLE | SPS_BEST)) {
- vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)",
- stp->st_salscore ? "s " : "",
- stp->st_score, stp->st_altscore);
- } else {
- vim_snprintf((char *)IObuff, IOSIZE, " (%d)",
- stp->st_score);
- }
- if (cmdmsg_rl) {
- // Mirror the numbers, but keep the leading space.
- rl_mirror(IObuff + 1);
- }
- msg_advance(30);
- msg_puts((const char *)IObuff);
- }
- msg_putchar('\n');
- }
-
- cmdmsg_rl = FALSE;
- msg_col = 0;
- // Ask for choice.
- selected = prompt_for_number(&mouse_used);
-
- if (ui_has(kUIMessages)) {
- ui_call_msg_clear();
- }
-
- if (mouse_used) {
- selected -= lines_left;
- }
- lines_left = Rows; // avoid more prompt
- // don't delay for 'smd' in normal_cmd()
- msg_scroll = msg_scroll_save;
- }
-
- if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) {
- // Save the from and to text for :spellrepall.
- XFREE_CLEAR(repl_from);
- XFREE_CLEAR(repl_to);
-
- stp = &SUG(sug.su_ga, selected - 1);
- if (sug.su_badlen > stp->st_orglen) {
- // Replacing less than "su_badlen", append the remainder to
- // repl_to.
- repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen);
- vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word,
- sug.su_badlen - stp->st_orglen,
- sug.su_badptr + stp->st_orglen);
- repl_to = vim_strsave(IObuff);
- } else {
- // Replacing su_badlen or more, use the whole word.
- repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen);
- repl_to = vim_strsave(stp->st_word);
- }
-
- // Replace the word.
- p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1);
- c = (int)(sug.su_badptr - line);
- memmove(p, line, (size_t)c);
- STRCPY(p + c, stp->st_word);
- STRCAT(p, sug.su_badptr + stp->st_orglen);
-
- // For redo we use a change-word command.
- ResetRedobuff();
- AppendToRedobuff("ciw");
- AppendToRedobuffLit((char *)p + c,
- stp->st_wordlen + sug.su_badlen - stp->st_orglen);
- AppendCharToRedobuff(ESC);
-
- // "p" may be freed here
- ml_replace(curwin->w_cursor.lnum, (char *)p, false);
- curwin->w_cursor.col = c;
-
- inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen);
- } else {
- curwin->w_cursor = prev_cursor;
- }
-
- spell_find_cleanup(&sug);
- xfree(line);
- curwin->w_p_spell = wo_spell_save;
-}
-
// Check if the word at line "lnum" column "col" is required to start with a
// capital. This uses 'spellcapcheck' of the current buffer.
-static bool check_need_cap(linenr_T lnum, colnr_T col)
+bool check_need_cap(linenr_T lnum, colnr_T col)
{
bool need_cap = false;
char_u *line;
@@ -3176,10 +2607,10 @@ void ex_spellrepall(exarg_T *eap)
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
if (curwin->w_cursor.lnum != prev_lnum) {
- ++sub_nlines;
+ sub_nlines++;
prev_lnum = curwin->w_cursor.lnum;
}
- ++sub_nsubs;
+ sub_nsubs++;
}
curwin->w_cursor.col += (colnr_T)STRLEN(repl_to);
}
@@ -3195,336 +2626,6 @@ void ex_spellrepall(exarg_T *eap)
}
}
-/// Find spell suggestions for "word". Return them in the growarray "*gap" as
-/// a list of allocated strings.
-///
-/// @param maxcount maximum nr of suggestions
-/// @param need_cap 'spellcapcheck' matched
-void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive)
-{
- suginfo_T sug;
- suggest_T *stp;
- char_u *wcopy;
-
- spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive);
-
- // Make room in "gap".
- ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1);
- ga_grow(gap, sug.su_ga.ga_len);
- for (int i = 0; i < sug.su_ga.ga_len; ++i) {
- stp = &SUG(sug.su_ga, i);
-
- // The suggested word may replace only part of "word", add the not
- // replaced part.
- wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1);
- STRCPY(wcopy, stp->st_word);
- STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen);
- ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy;
- }
-
- spell_find_cleanup(&sug);
-}
-
-/// Find spell suggestions for the word at the start of "badptr".
-/// Return the suggestions in "su->su_ga".
-/// The maximum number of suggestions is "maxcount".
-/// Note: does use info for the current window.
-/// This is based on the mechanisms of Aspell, but completely reimplemented.
-///
-/// @param badlen length of bad word or 0 if unknown
-/// @param banbadword don't include badword in suggestions
-/// @param need_cap word should start with capital
-static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount,
- bool banbadword, bool need_cap, bool interactive)
-{
- hlf_T attr = HLF_COUNT;
- char_u buf[MAXPATHL];
- char_u *p;
- bool do_combine = false;
- char_u *sps_copy;
- static bool expr_busy = false;
- int c;
- langp_T *lp;
- bool did_intern = false;
-
- // Set the info in "*su".
- memset(su, 0, sizeof(suginfo_T));
- ga_init(&su->su_ga, (int)sizeof(suggest_T), 10);
- ga_init(&su->su_sga, (int)sizeof(suggest_T), 10);
- if (*badptr == NUL) {
- return;
- }
- hash_init(&su->su_banned);
-
- su->su_badptr = badptr;
- if (badlen != 0) {
- su->su_badlen = badlen;
- } else {
- size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false);
- assert(tmplen <= INT_MAX);
- su->su_badlen = (int)tmplen;
- }
- su->su_maxcount = maxcount;
- su->su_maxscore = SCORE_MAXINIT;
-
- if (su->su_badlen >= MAXWLEN) {
- su->su_badlen = MAXWLEN - 1; // just in case
- }
- STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1);
- (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword,
- MAXWLEN);
-
- // TODO(vim): make this work if the case-folded text is longer than the
- // original text. Currently an illegal byte causes wrong pointer
- // computations.
- su->su_fbadword[su->su_badlen] = NUL;
-
- // get caps flags for bad word
- su->su_badflags = badword_captype(su->su_badptr,
- su->su_badptr + su->su_badlen);
- if (need_cap) {
- su->su_badflags |= WF_ONECAP;
- }
-
- // Find the default language for sound folding. We simply use the first
- // one in 'spelllang' that supports sound folding. That's good for when
- // using multiple files for one language, it's not that bad when mixing
- // languages (e.g., "pl,en").
- for (int i = 0; i < curbuf->b_s.b_langp.ga_len; ++i) {
- lp = LANGP_ENTRY(curbuf->b_s.b_langp, i);
- if (lp->lp_sallang != NULL) {
- su->su_sallang = lp->lp_sallang;
- break;
- }
- }
-
- // Soundfold the bad word with the default sound folding, so that we don't
- // have to do this many times.
- if (su->su_sallang != NULL) {
- spell_soundfold(su->su_sallang, su->su_fbadword, true,
- su->su_sal_badword);
- }
-
- // If the word is not capitalised and spell_check() doesn't consider the
- // word to be bad then it might need to be capitalised. Add a suggestion
- // for that.
- c = utf_ptr2char((char *)su->su_badptr);
- if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) {
- make_case_word(su->su_badword, buf, WF_ONECAP);
- add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE,
- 0, true, su->su_sallang, false);
- }
-
- // Ban the bad word itself. It may appear in another region.
- if (banbadword) {
- add_banned(su, su->su_badword);
- }
-
- // Make a copy of 'spellsuggest', because the expression may change it.
- sps_copy = vim_strsave(p_sps);
-
- // Loop over the items in 'spellsuggest'.
- for (p = sps_copy; *p != NUL;) {
- copy_option_part((char **)&p, (char *)buf, MAXPATHL, ",");
-
- if (STRNCMP(buf, "expr:", 5) == 0) {
- // Evaluate an expression. Skip this when called recursively,
- // when using spellsuggest() in the expression.
- if (!expr_busy) {
- expr_busy = true;
- spell_suggest_expr(su, buf + 5);
- expr_busy = false;
- }
- } else if (STRNCMP(buf, "file:", 5) == 0) {
- // Use list of suggestions in a file.
- spell_suggest_file(su, buf + 5);
- } else if (!did_intern) {
- // Use internal method once.
- spell_suggest_intern(su, interactive);
- if (sps_flags & SPS_DOUBLE) {
- do_combine = true;
- }
- did_intern = true;
- }
- }
-
- xfree(sps_copy);
-
- if (do_combine) {
- // Combine the two list of suggestions. This must be done last,
- // because sorting changes the order again.
- score_combine(su);
- }
-}
-
-// Find suggestions by evaluating expression "expr".
-static void spell_suggest_expr(suginfo_T *su, char_u *expr)
-{
- int score;
- const char *p;
-
- // The work is split up in a few parts to avoid having to export
- // suginfo_T.
- // First evaluate the expression and get the resulting list.
- list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr);
- if (list != NULL) {
- // Loop over the items in the list.
- TV_LIST_ITER(list, li, {
- if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
- // Get the word and the score from the items.
- score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p);
- if (score >= 0 && score <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen,
- score, 0, true, su->su_sallang, false);
- }
- }
- });
- tv_list_unref(list);
- }
-
- // Remove bogus suggestions, sort and truncate at "maxcount".
- check_suggestions(su, &su->su_ga);
- (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
-}
-
-// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'.
-static void spell_suggest_file(suginfo_T *su, char_u *fname)
-{
- FILE *fd;
- char_u line[MAXWLEN * 2];
- char_u *p;
- int len;
- char_u cword[MAXWLEN];
-
- // Open the file.
- fd = os_fopen((char *)fname, "r");
- if (fd == NULL) {
- semsg(_(e_notopen), fname);
- return;
- }
-
- // Read it line by line.
- while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) {
- line_breakcheck();
-
- p = (char_u *)vim_strchr((char *)line, '/');
- if (p == NULL) {
- continue; // No Tab found, just skip the line.
- }
- *p++ = NUL;
- if (STRICMP(su->su_badword, line) == 0) {
- // Match! Isolate the good word, until CR or NL.
- for (len = 0; p[len] >= ' '; len++) {}
- p[len] = NUL;
-
- // If the suggestion doesn't have specific case duplicate the case
- // of the bad word.
- if (captype(p, NULL) == 0) {
- make_case_word(p, cword, su->su_badflags);
- p = cword;
- }
-
- add_suggestion(su, &su->su_ga, p, su->su_badlen,
- SCORE_FILE, 0, true, su->su_sallang, false);
- }
- }
-
- fclose(fd);
-
- // Remove bogus suggestions, sort and truncate at "maxcount".
- check_suggestions(su, &su->su_ga);
- (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
-}
-
-// Find suggestions for the internal method indicated by "sps_flags".
-static void spell_suggest_intern(suginfo_T *su, bool interactive)
-{
- // Load the .sug file(s) that are available and not done yet.
- suggest_load_files();
-
- // 1. Try special cases, such as repeating a word: "the the" -> "the".
- //
- // Set a maximum score to limit the combination of operations that is
- // tried.
- suggest_try_special(su);
-
- // 2. Try inserting/deleting/swapping/changing a letter, use REP entries
- // from the .aff file and inserting a space (split the word).
- suggest_try_change(su);
-
- // For the resulting top-scorers compute the sound-a-like score.
- if (sps_flags & SPS_DOUBLE) {
- score_comp_sal(su);
- }
-
- // 3. Try finding sound-a-like words.
- if ((sps_flags & SPS_FAST) == 0) {
- if (sps_flags & SPS_BEST) {
- // Adjust the word score for the suggestions found so far for how
- // they sounds like.
- rescore_suggestions(su);
- }
-
- // While going through the soundfold tree "su_maxscore" is the score
- // for the soundfold word, limits the changes that are being tried,
- // and "su_sfmaxscore" the rescored score, which is set by
- // cleanup_suggestions().
- // First find words with a small edit distance, because this is much
- // faster and often already finds the top-N suggestions. If we didn't
- // find many suggestions try again with a higher edit distance.
- // "sl_sounddone" is used to avoid doing the same word twice.
- suggest_try_soundalike_prep();
- su->su_maxscore = SCORE_SFMAX1;
- su->su_sfmaxscore = SCORE_MAXINIT * 3;
- suggest_try_soundalike(su);
- if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) {
- // We didn't find enough matches, try again, allowing more
- // changes to the soundfold word.
- su->su_maxscore = SCORE_SFMAX2;
- suggest_try_soundalike(su);
- if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) {
- // Still didn't find enough matches, try again, allowing even
- // more changes to the soundfold word.
- su->su_maxscore = SCORE_SFMAX3;
- suggest_try_soundalike(su);
- }
- }
- su->su_maxscore = su->su_sfmaxscore;
- suggest_try_soundalike_finish();
- }
-
- // When CTRL-C was hit while searching do show the results. Only clear
- // got_int when using a command, not for spellsuggest().
- os_breakcheck();
- if (interactive && got_int) {
- (void)vgetc();
- got_int = FALSE;
- }
-
- if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) {
- if (sps_flags & SPS_BEST) {
- // Adjust the word score for how it sounds like.
- rescore_suggestions(su);
- }
-
- // Remove bogus suggestions, sort and truncate at "maxcount".
- check_suggestions(su, &su->su_ga);
- (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
- }
-}
-
-// Free the info put in "*su" by spell_find_suggest().
-static void spell_find_cleanup(suginfo_T *su)
-{
-#define FREE_SUG_WORD(sug) xfree((sug)->st_word)
- // Free the suggestions.
- GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD);
- GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD);
-
- // Free the banned words.
- hash_clear_all(&su->su_banned, 0);
-}
-
/// Make a copy of "word", with the first letter upper or lower cased, to
/// "wcopy[MAXWLEN]". "word" must not be empty.
/// The result is NUL terminated.
@@ -3547,7 +2648,7 @@ void onecap_copy(char_u *word, char_u *wcopy, bool upper)
// Make a copy of "word" with all the letters upper cased into
// "wcopy[MAXWLEN]". The result is NUL terminated.
-static void allcap_copy(char_u *word, char_u *wcopy)
+void allcap_copy(char_u *word, char_u *wcopy)
{
char_u *d = wcopy;
for (char_u *s = word; *s != NUL;) {
@@ -3571,1383 +2672,9 @@ static void allcap_copy(char_u *word, char_u *wcopy)
*d = NUL;
}
-// Try finding suggestions by recognizing specific situations.
-static void suggest_try_special(suginfo_T *su)
-{
- int c;
- char_u word[MAXWLEN];
-
- // Recognize a word that is repeated: "the the".
- char_u *p = skiptowhite(su->su_fbadword);
- size_t len = (size_t)(p - su->su_fbadword);
- p = (char_u *)skipwhite((char *)p);
- if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) {
- // Include badflags: if the badword is onecap or allcap
- // use that for the goodword too: "The the" -> "The".
- c = su->su_fbadword[len];
- su->su_fbadword[len] = NUL;
- make_case_word(su->su_fbadword, word, su->su_badflags);
- su->su_fbadword[len] = (char_u)c;
-
- // Give a soundalike score of 0, compute the score as if deleting one
- // character.
- add_suggestion(su, &su->su_ga, word, su->su_badlen,
- RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false);
- }
-}
-
-// Measure how much time is spent in each state.
-// Output is dumped in "suggestprof".
-
-#ifdef SUGGEST_PROFILE
-proftime_T current;
-proftime_T total;
-proftime_T times[STATE_FINAL + 1];
-long counts[STATE_FINAL + 1];
-
-static void prof_init(void)
-{
- for (int i = 0; i <= STATE_FINAL; i++) {
- profile_zero(&times[i]);
- counts[i] = 0;
- }
- profile_start(&current);
- profile_start(&total);
-}
-
-// call before changing state
-static void prof_store(state_T state)
-{
- profile_end(&current);
- profile_add(&times[state], &current);
- counts[state]++;
- profile_start(&current);
-}
-# 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(&times[i]), counts[i]);
- }
- fclose(fd);
-}
-#else
-# define PROF_STORE(state)
-#endif
-
-// Try finding suggestions by adding/removing/swapping letters.
-
-static void suggest_try_change(suginfo_T *su)
-{
- char_u fword[MAXWLEN]; // copy of the bad word, case-folded
- int n;
- char_u *p;
- langp_T *lp;
-
- // We make a copy of the case-folded bad word, so that we can modify it
- // to find matches (esp. REP items). Append some more text, changing
- // chars after the bad word may help.
- STRCPY(fword, su->su_fbadword);
- n = (int)STRLEN(fword);
- p = su->su_badptr + su->su_badlen;
- (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n);
-
- // Make sure the resulting text is not longer than the original text.
- n = (int)STRLEN(su->su_badptr);
- if (n < MAXWLEN) {
- fword[n] = NUL;
- }
-
- for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-
- // If reloading a spell file fails it's still in the list but
- // everything has been cleared.
- if (lp->lp_slang->sl_fbyts == NULL) {
- continue;
- }
-
- // Try it for this language. Will add possible suggestions.
- //
-#ifdef SUGGEST_PROFILE
- prof_init();
-#endif
- suggest_trie_walk(su, lp, fword, false);
-#ifdef SUGGEST_PROFILE
- prof_report("try_change");
-#endif
- }
-}
-
-// Check the maximum score, if we go over it we won't try this change.
-#define TRY_DEEPER(su, stack, depth, add) \
- ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore)
-
-// Try finding suggestions by adding/removing/swapping letters.
-//
-// This uses a state machine. At each node in the tree we try various
-// operations. When trying if an operation works "depth" is increased and the
-// stack[] is used to store info. This allows combinations, thus insert one
-// character, replace one and delete another. The number of changes is
-// limited by su->su_maxscore.
-//
-// After implementing this I noticed an article by Kemal Oflazer that
-// describes something similar: "Error-tolerant Finite State Recognition with
-// Applications to Morphological Analysis and Spelling Correction" (1996).
-// The implementation in the article is simplified and requires a stack of
-// unknown depth. The implementation here only needs a stack depth equal to
-// the length of the word.
-//
-// This is also used for the sound-folded word, "soundfold" is true then.
-// The mechanism is the same, but we find a match with a sound-folded word
-// that comes from one or more original words. Each of these words may be
-// added, this is done by add_sound_suggest().
-// Don't use:
-// the prefix tree or the keep-case tree
-// "su->su_badlen"
-// anything to do with upper and lower case
-// anything to do with word or non-word characters ("spell_iswordp()")
-// banned words
-// word flags (rare, region, compounding)
-// word splitting for now
-// "similar_chars()"
-// use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep"
-static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool soundfold)
-{
- char_u tword[MAXWLEN]; // good word collected so far
- trystate_T stack[MAXWLEN];
- char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case;
- // concatenation of prefix compound
- // words and split word. NUL terminated
- // when going deeper but not when coming
- // back.
- char_u compflags[MAXWLEN]; // compound flags, one for each word
- trystate_T *sp;
- int newscore;
- int score;
- char_u *byts, *fbyts, *pbyts;
- idx_T *idxs, *fidxs, *pidxs;
- int depth;
- int c, c2, c3;
- int n = 0;
- int flags;
- garray_T *gap;
- idx_T arridx;
- int len;
- char_u *p;
- fromto_T *ftp;
- int fl = 0, tl;
- int repextra = 0; // extra bytes in fword[] from REP item
- slang_T *slang = lp->lp_slang;
- int fword_ends;
- bool goodword_ends;
-#ifdef DEBUG_TRIEWALK
- // Stores the name of the change made at each level.
- char_u changename[MAXWLEN][80];
-#endif
- int breakcheckcount = 1000;
- bool compound_ok;
-
- // Go through the whole case-fold tree, try changes at each node.
- // "tword[]" contains the word collected from nodes in the tree.
- // "fword[]" the word we are trying to match with (initially the bad
- // word).
- depth = 0;
- sp = &stack[0];
- memset(sp, 0, sizeof(trystate_T)); // -V512
- sp->ts_curi = 1;
-
- if (soundfold) {
- // Going through the soundfold tree.
- byts = fbyts = slang->sl_sbyts;
- idxs = fidxs = slang->sl_sidxs;
- pbyts = NULL;
- pidxs = NULL;
- sp->ts_prefixdepth = PFD_NOPREFIX;
- sp->ts_state = STATE_START;
- } else {
- // When there are postponed prefixes we need to use these first. At
- // the end of the prefix we continue in the case-fold tree.
- fbyts = slang->sl_fbyts;
- fidxs = slang->sl_fidxs;
- pbyts = slang->sl_pbyts;
- pidxs = slang->sl_pidxs;
- if (pbyts != NULL) {
- byts = pbyts;
- idxs = pidxs;
- sp->ts_prefixdepth = PFD_PREFIXTREE;
- sp->ts_state = STATE_NOPREFIX; // try without prefix first
- } else {
- byts = fbyts;
- idxs = fidxs;
- sp->ts_prefixdepth = PFD_NOPREFIX;
- sp->ts_state = STATE_START;
- }
- }
-
- // The loop may take an indefinite amount of time. Break out after five
- // sectonds. TODO(vim): add an option for the time limit.
- proftime_T time_limit = profile_setlimit(5000);
-
- // Loop to find all suggestions. At each round we either:
- // - For the current state try one operation, advance "ts_curi",
- // increase "depth".
- // - When a state is done go to the next, set "ts_state".
- // - When all states are tried decrease "depth".
- while (depth >= 0 && !got_int) {
- sp = &stack[depth];
- switch (sp->ts_state) {
- case STATE_START:
- case STATE_NOPREFIX:
- // Start of node: Deal with NUL bytes, which means
- // tword[] may end here.
- arridx = sp->ts_arridx; // current node in the tree
- len = byts[arridx]; // bytes in this node
- arridx += sp->ts_curi; // index of current byte
-
- if (sp->ts_prefixdepth == PFD_PREFIXTREE) {
- // Skip over the NUL bytes, we use them later.
- for (n = 0; n < len && byts[arridx + n] == 0; n++) {}
- sp->ts_curi = (int16_t)(sp->ts_curi + n);
-
- // Always past NUL bytes now.
- n = (int)sp->ts_state;
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_ENDNUL;
- sp->ts_save_badflags = (char_u)su->su_badflags;
-
- // At end of a prefix or at start of prefixtree: check for
- // following word.
- if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) {
- // Set su->su_badflags to the caps type at this position.
- // Use the caps type until here for the prefix itself.
- n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
- flags = badword_captype(su->su_badptr, su->su_badptr + n);
- su->su_badflags = badword_captype(su->su_badptr + n,
- su->su_badptr + su->su_badlen);
-#ifdef DEBUG_TRIEWALK
- sprintf(changename[depth], "prefix");
-#endif
- go_deeper(stack, depth, 0);
- ++depth;
- sp = &stack[depth];
- sp->ts_prefixdepth = (char_u)(depth - 1);
- byts = fbyts;
- idxs = fidxs;
- sp->ts_arridx = 0;
-
- // Move the prefix to preword[] with the right case
- // and make find_keepcap_word() works.
- tword[sp->ts_twordlen] = NUL;
- make_case_word(tword + sp->ts_splitoff,
- preword + sp->ts_prewordlen, flags);
- sp->ts_prewordlen = (char_u)STRLEN(preword);
- sp->ts_splitoff = sp->ts_twordlen;
- }
- break;
- }
-
- if (sp->ts_curi > len || byts[arridx] != 0) {
- // Past bytes in node and/or past NUL bytes.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_ENDNUL;
- sp->ts_save_badflags = (char_u)su->su_badflags;
- break;
- }
-
- // End of word in tree.
- ++sp->ts_curi; // eat one NUL byte
-
- flags = (int)idxs[arridx];
-
- // Skip words with the NOSUGGEST flag.
- if (flags & WF_NOSUGGEST) {
- break;
- }
-
- fword_ends = (fword[sp->ts_fidx] == NUL
- || (soundfold
- ? ascii_iswhite(fword[sp->ts_fidx])
- : !spell_iswordp(fword + sp->ts_fidx, curwin)));
- tword[sp->ts_twordlen] = NUL;
-
- if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
- && (sp->ts_flags & TSF_PREFIXOK) == 0
- && pbyts != NULL) {
- // There was a prefix before the word. Check that the prefix
- // can be used with this word.
- // Count the length of the NULs in the prefix. If there are
- // none this must be the first try without a prefix.
- n = stack[sp->ts_prefixdepth].ts_arridx;
- len = pbyts[n++];
- for (c = 0; c < len && pbyts[n + c] == 0; c++) {}
- if (c > 0) {
- c = valid_word_prefix(c, n, flags,
- tword + sp->ts_splitoff, slang, false);
- if (c == 0) {
- break;
- }
-
- // Use the WF_RARE flag for a rare prefix.
- if (c & WF_RAREPFX) {
- flags |= WF_RARE;
- }
-
- // Tricky: when checking for both prefix and compounding
- // we run into the prefix flag first.
- // Remember that it's OK, so that we accept the prefix
- // when arriving at a compound flag.
- sp->ts_flags |= TSF_PREFIXOK;
- }
- }
-
- // Check NEEDCOMPOUND: can't use word without compounding. Do try
- // appending another compound word below.
- if (sp->ts_complen == sp->ts_compsplit && fword_ends
- && (flags & WF_NEEDCOMP)) {
- goodword_ends = false;
- } else {
- goodword_ends = true;
- }
-
- p = NULL;
- compound_ok = true;
- if (sp->ts_complen > sp->ts_compsplit) {
- if (slang->sl_nobreak) {
- // There was a word before this word. When there was no
- // change in this word (it was correct) add the first word
- // as a suggestion. If this word was corrected too, we
- // need to check if a correct word follows.
- if (sp->ts_fidx - sp->ts_splitfidx
- == sp->ts_twordlen - sp->ts_splitoff
- && STRNCMP(fword + sp->ts_splitfidx,
- tword + sp->ts_splitoff,
- sp->ts_fidx - sp->ts_splitfidx) == 0) {
- preword[sp->ts_prewordlen] = NUL;
- newscore = score_wordcount_adj(slang, sp->ts_score,
- preword + sp->ts_prewordlen,
- sp->ts_prewordlen > 0);
- // Add the suggestion if the score isn't too bad.
- if (newscore <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, preword,
- sp->ts_splitfidx - repextra,
- newscore, 0, false,
- lp->lp_sallang, false);
- }
- break;
- }
- } else {
- // There was a compound word before this word. If this
- // word does not support compounding then give up
- // (splitting is tried for the word without compound
- // flag).
- if (((unsigned)flags >> 24) == 0
- || sp->ts_twordlen - sp->ts_splitoff
- < slang->sl_compminlen) {
- break;
- }
- // For multi-byte chars check character length against
- // COMPOUNDMIN.
- if (slang->sl_compminlen > 0
- && mb_charlen(tword + sp->ts_splitoff)
- < slang->sl_compminlen) {
- break;
- }
-
- compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
- compflags[sp->ts_complen + 1] = NUL;
- STRLCPY(preword + sp->ts_prewordlen,
- tword + sp->ts_splitoff,
- sp->ts_twordlen - sp->ts_splitoff + 1);
-
- // Verify CHECKCOMPOUNDPATTERN rules.
- if (match_checkcompoundpattern(preword, sp->ts_prewordlen,
- &slang->sl_comppat)) {
- compound_ok = false;
- }
-
- if (compound_ok) {
- p = preword;
- while (*skiptowhite(p) != NUL) {
- p = (char_u *)skipwhite((char *)skiptowhite(p));
- }
- if (fword_ends && !can_compound(slang, p,
- compflags + sp->ts_compsplit)) {
- // Compound is not allowed. But it may still be
- // possible if we add another (short) word.
- compound_ok = false;
- }
- }
-
- // Get pointer to last char of previous word.
- p = preword + sp->ts_prewordlen;
- MB_PTR_BACK(preword, p);
- }
- }
-
- // Form the word with proper case in preword.
- // If there is a word from a previous split, append.
- // For the soundfold tree don't change the case, simply append.
- if (soundfold) {
- STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff);
- } else if (flags & WF_KEEPCAP) {
- // Must find the word in the keep-case tree.
- find_keepcap_word(slang, tword + sp->ts_splitoff,
- preword + sp->ts_prewordlen);
- } else {
- // Include badflags: If the badword is onecap or allcap
- // use that for the goodword too. But if the badword is
- // allcap and it's only one char long use onecap.
- c = su->su_badflags;
- if ((c & WF_ALLCAP)
- && su->su_badlen ==
- utfc_ptr2len((char *)su->su_badptr)) {
- c = WF_ONECAP;
- }
- c |= flags;
-
- // When appending a compound word after a word character don't
- // use Onecap.
- if (p != NULL && spell_iswordp_nmw(p, curwin)) {
- c &= ~WF_ONECAP;
- }
- make_case_word(tword + sp->ts_splitoff,
- preword + sp->ts_prewordlen, c);
- }
-
- if (!soundfold) {
- // Don't use a banned word. It may appear again as a good
- // word, thus remember it.
- if (flags & WF_BANNED) {
- add_banned(su, preword + sp->ts_prewordlen);
- break;
- }
- if ((sp->ts_complen == sp->ts_compsplit
- && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen))
- || WAS_BANNED(su, (char *)preword)) {
- if (slang->sl_compprog == NULL) {
- break;
- }
- // the word so far was banned but we may try compounding
- goodword_ends = false;
- }
- }
-
- newscore = 0;
- if (!soundfold) { // soundfold words don't have flags
- if ((flags & WF_REGION)
- && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) {
- newscore += SCORE_REGION;
- }
- if (flags & WF_RARE) {
- newscore += SCORE_RARE;
- }
-
- if (!spell_valid_case(su->su_badflags,
- captype(preword + sp->ts_prewordlen, NULL))) {
- newscore += SCORE_ICASE;
- }
- }
-
- // TODO: how about splitting in the soundfold tree?
- if (fword_ends
- && goodword_ends
- && sp->ts_fidx >= sp->ts_fidxtry
- && compound_ok) {
- // The badword also ends: add suggestions.
-#ifdef DEBUG_TRIEWALK
- if (soundfold && STRCMP(preword, "smwrd") == 0) {
- int j;
-
- // print the stack of changes that brought us here
- smsg("------ %s -------", fword);
- for (j = 0; j < depth; ++j) {
- smsg("%s", changename[j]);
- }
- }
-#endif
- if (soundfold) {
- // For soundfolded words we need to find the original
- // words, the edit distance and then add them.
- add_sound_suggest(su, preword, sp->ts_score, lp);
- } else if (sp->ts_fidx > 0) {
- // Give a penalty when changing non-word char to word
- // char, e.g., "thes," -> "these".
- p = fword + sp->ts_fidx;
- MB_PTR_BACK(fword, p);
- if (!spell_iswordp(p, curwin) && *preword != NUL) {
- p = preword + STRLEN(preword);
- MB_PTR_BACK(preword, p);
- if (spell_iswordp(p, curwin)) {
- newscore += SCORE_NONWORD;
- }
- }
-
- // Give a bonus to words seen before.
- score = score_wordcount_adj(slang,
- sp->ts_score + newscore,
- preword + sp->ts_prewordlen,
- sp->ts_prewordlen > 0);
-
- // Add the suggestion if the score isn't too bad.
- if (score <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, preword,
- sp->ts_fidx - repextra,
- score, 0, false, lp->lp_sallang, false);
-
- if (su->su_badflags & WF_MIXCAP) {
- // We really don't know if the word should be
- // upper or lower case, add both.
- c = captype(preword, NULL);
- if (c == 0 || c == WF_ALLCAP) {
- make_case_word(tword + sp->ts_splitoff,
- preword + sp->ts_prewordlen,
- c == 0 ? WF_ALLCAP : 0);
-
- add_suggestion(su, &su->su_ga, preword,
- sp->ts_fidx - repextra,
- score + SCORE_ICASE, 0, false,
- lp->lp_sallang, false);
- }
- }
- }
- }
- }
-
- // Try word split and/or compounding.
- if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends)
- // Don't split in the middle of a character
- && (sp->ts_tcharlen == 0)) {
- bool try_compound;
- int try_split;
-
- // If past the end of the bad word don't try a split.
- // Otherwise try changing the next word. E.g., find
- // suggestions for "the the" where the second "the" is
- // different. It's done like a split.
- // TODO: word split for soundfold words
- try_split = (sp->ts_fidx - repextra < su->su_badlen)
- && !soundfold;
-
- // Get here in several situations:
- // 1. The word in the tree ends:
- // If the word allows compounding try that. Otherwise try
- // a split by inserting a space. For both check that a
- // valid words starts at fword[sp->ts_fidx].
- // For NOBREAK do like compounding to be able to check if
- // the next word is valid.
- // 2. The badword does end, but it was due to a change (e.g.,
- // a swap). No need to split, but do check that the
- // following word is valid.
- // 3. The badword and the word in the tree end. It may still
- // be possible to compound another (short) word.
- try_compound = false;
- if (!soundfold
- && !slang->sl_nocompoundsugs
- && slang->sl_compprog != NULL
- && ((unsigned)flags >> 24) != 0
- && sp->ts_twordlen - sp->ts_splitoff
- >= slang->sl_compminlen
- && (slang->sl_compminlen == 0
- || mb_charlen(tword + sp->ts_splitoff)
- >= slang->sl_compminlen)
- && (slang->sl_compsylmax < MAXWLEN
- || sp->ts_complen + 1 - sp->ts_compsplit
- < slang->sl_compmax)
- && (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) {
- try_compound = true;
- compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
- compflags[sp->ts_complen + 1] = NUL;
- }
-
- // For NOBREAK we never try splitting, it won't make any word
- // valid.
- if (slang->sl_nobreak && !slang->sl_nocompoundsugs) {
- try_compound = true;
- } else if (!fword_ends
- && try_compound
- && (sp->ts_flags & TSF_DIDSPLIT) == 0) {
- // If we could add a compound word, and it's also possible to
- // split at this point, do the split first and set
- // TSF_DIDSPLIT to avoid doing it again.
- try_compound = false;
- sp->ts_flags |= TSF_DIDSPLIT;
- --sp->ts_curi; // do the same NUL again
- compflags[sp->ts_complen] = NUL;
- } else {
- sp->ts_flags &= (char_u) ~TSF_DIDSPLIT;
- }
-
- if (try_split || try_compound) {
- if (!try_compound && (!fword_ends || !goodword_ends)) {
- // If we're going to split need to check that the
- // words so far are valid for compounding. If there
- // is only one word it must not have the NEEDCOMPOUND
- // flag.
- if (sp->ts_complen == sp->ts_compsplit
- && (flags & WF_NEEDCOMP)) {
- break;
- }
- p = preword;
- while (*skiptowhite(p) != NUL) {
- p = (char_u *)skipwhite((char *)skiptowhite(p));
- }
- if (sp->ts_complen > sp->ts_compsplit
- && !can_compound(slang, p,
- compflags + sp->ts_compsplit)) {
- break;
- }
-
- if (slang->sl_nosplitsugs) {
- newscore += SCORE_SPLIT_NO;
- } else {
- newscore += SCORE_SPLIT;
- }
-
- // Give a bonus to words seen before.
- newscore = score_wordcount_adj(slang, newscore,
- preword + sp->ts_prewordlen, true);
- }
-
- if (TRY_DEEPER(su, stack, depth, newscore)) {
- go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
- if (!try_compound && !fword_ends) {
- sprintf(changename[depth], "%.*s-%s: split",
- sp->ts_twordlen, tword, fword + sp->ts_fidx);
- } else {
- sprintf(changename[depth], "%.*s-%s: compound",
- sp->ts_twordlen, tword, fword + sp->ts_fidx);
- }
-#endif
- // Save things to be restored at STATE_SPLITUNDO.
- sp->ts_save_badflags = (char_u)su->su_badflags;
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_SPLITUNDO;
-
- ++depth;
- sp = &stack[depth];
-
- // Append a space to preword when splitting.
- if (!try_compound && !fword_ends) {
- STRCAT(preword, " ");
- }
- sp->ts_prewordlen = (char_u)STRLEN(preword);
- sp->ts_splitoff = sp->ts_twordlen;
- sp->ts_splitfidx = sp->ts_fidx;
-
- // If the badword has a non-word character at this
- // position skip it. That means replacing the
- // non-word character with a space. Always skip a
- // character when the word ends. But only when the
- // good word can end.
- if (((!try_compound && !spell_iswordp_nmw(fword
- + sp->ts_fidx,
- curwin))
- || fword_ends)
- && fword[sp->ts_fidx] != NUL
- && goodword_ends) {
- int l;
-
- l = utfc_ptr2len((char *)fword + sp->ts_fidx);
- if (fword_ends) {
- // Copy the skipped character to preword.
- memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l);
- sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l);
- preword[sp->ts_prewordlen] = NUL;
- } else {
- sp->ts_score -= SCORE_SPLIT - SCORE_SUBST;
- }
- sp->ts_fidx = (char_u)(sp->ts_fidx + l);
- }
-
- // When compounding include compound flag in
- // compflags[] (already set above). When splitting we
- // may start compounding over again.
- if (try_compound) {
- ++sp->ts_complen;
- } else {
- sp->ts_compsplit = sp->ts_complen;
- }
- sp->ts_prefixdepth = PFD_NOPREFIX;
-
- // set su->su_badflags to the caps type at this
- // position
- n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
- su->su_badflags = badword_captype(su->su_badptr + n,
- su->su_badptr + su->su_badlen);
-
- // Restart at top of the tree.
- sp->ts_arridx = 0;
-
- // If there are postponed prefixes, try these too.
- if (pbyts != NULL) {
- byts = pbyts;
- idxs = pidxs;
- sp->ts_prefixdepth = PFD_PREFIXTREE;
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_NOPREFIX;
- }
- }
- }
- }
- break;
-
- case STATE_SPLITUNDO:
- // Undo the changes done for word split or compound word.
- su->su_badflags = sp->ts_save_badflags;
-
- // Continue looking for NUL bytes.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_START;
-
- // In case we went into the prefix tree.
- byts = fbyts;
- idxs = fidxs;
- break;
-
- case STATE_ENDNUL:
- // Past the NUL bytes in the node.
- su->su_badflags = sp->ts_save_badflags;
- if (fword[sp->ts_fidx] == NUL
- && sp->ts_tcharlen == 0) {
- // The badword ends, can't use STATE_PLAIN.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_DEL;
- break;
- }
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_PLAIN;
- FALLTHROUGH;
-
- case STATE_PLAIN:
- // Go over all possible bytes at this node, add each to tword[]
- // and use child node. "ts_curi" is the index.
- arridx = sp->ts_arridx;
- if (sp->ts_curi > byts[arridx]) {
- // Done all bytes at this node, do next state. When still at
- // already changed bytes skip the other tricks.
- PROF_STORE(sp->ts_state)
- if (sp->ts_fidx >= sp->ts_fidxtry) {
- sp->ts_state = STATE_DEL;
- } else {
- sp->ts_state = STATE_FINAL;
- }
- } else {
- arridx += sp->ts_curi++;
- c = byts[arridx];
-
- // Normal byte, go one level deeper. If it's not equal to the
- // byte in the bad word adjust the score. But don't even try
- // when the byte was already changed. And don't try when we
- // just deleted this byte, accepting it is always cheaper than
- // delete + substitute.
- if (c == fword[sp->ts_fidx]
- || (sp->ts_tcharlen > 0
- && sp->ts_isdiff != DIFF_NONE)) {
- newscore = 0;
- } else {
- newscore = SCORE_SUBST;
- }
- if ((newscore == 0
- || (sp->ts_fidx >= sp->ts_fidxtry
- && ((sp->ts_flags & TSF_DIDDEL) == 0
- || c != fword[sp->ts_delidx])))
- && TRY_DEEPER(su, stack, depth, newscore)) {
- go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
- if (newscore > 0) {
- sprintf(changename[depth], "%.*s-%s: subst %c to %c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- fword[sp->ts_fidx], c);
- } else {
- sprintf(changename[depth], "%.*s-%s: accept %c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- fword[sp->ts_fidx]);
- }
-#endif
- ++depth;
- sp = &stack[depth];
- if (fword[sp->ts_fidx] != NUL) {
- sp->ts_fidx++;
- }
- tword[sp->ts_twordlen++] = (char_u)c;
- sp->ts_arridx = idxs[arridx];
- if (newscore == SCORE_SUBST) {
- sp->ts_isdiff = DIFF_YES;
- }
- // Multi-byte characters are a bit complicated to
- // handle: They differ when any of the bytes differ
- // and then their length may also differ.
- if (sp->ts_tcharlen == 0) {
- // First byte.
- sp->ts_tcharidx = 0;
- sp->ts_tcharlen = MB_BYTE2LEN(c);
- sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1);
- sp->ts_isdiff = (newscore != 0)
- ? DIFF_YES : DIFF_NONE;
- } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) {
- // When inserting trail bytes don't advance in the
- // bad word.
- sp->ts_fidx--;
- }
- if (++sp->ts_tcharidx == sp->ts_tcharlen) {
- // Last byte of character.
- if (sp->ts_isdiff == DIFF_YES) {
- // Correct ts_fidx for the byte length of the
- // character (we didn't check that before).
- sp->ts_fidx = (char_u)(sp->ts_fcharstart
- + utfc_ptr2len((char *)fword + sp->ts_fcharstart));
-
- // For changing a composing character adjust
- // the score from SCORE_SUBST to
- // SCORE_SUBCOMP.
- if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen
- - sp->ts_tcharlen))
- && utf_iscomposing(utf_ptr2char((char *)fword
- + sp->ts_fcharstart))) {
- sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP;
- } else if (!soundfold
- && slang->sl_has_map
- && similar_chars(slang,
- utf_ptr2char((char *)tword + sp->ts_twordlen -
- sp->ts_tcharlen),
- utf_ptr2char((char *)fword + sp->ts_fcharstart))) {
- // For a similar character adjust score from
- // SCORE_SUBST to SCORE_SIMILAR.
- sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR;
- }
- } else if (sp->ts_isdiff == DIFF_INSERT
- && sp->ts_twordlen > sp->ts_tcharlen) {
- p = tword + sp->ts_twordlen - sp->ts_tcharlen;
- c = utf_ptr2char((char *)p);
- if (utf_iscomposing(c)) {
- // Inserting a composing char doesn't
- // count that much.
- sp->ts_score -= SCORE_INS - SCORE_INSCOMP;
- } else {
- // If the previous character was the same,
- // thus doubling a character, give a bonus
- // to the score. Also for the soundfold
- // tree (might seem illogical but does
- // give better scores).
- MB_PTR_BACK(tword, p);
- if (c == utf_ptr2char((char *)p)) {
- sp->ts_score -= SCORE_INS - SCORE_INSDUP;
- }
- }
- }
-
- // Starting a new char, reset the length.
- sp->ts_tcharlen = 0;
- }
- }
- }
- break;
-
- case STATE_DEL:
- // When past the first byte of a multi-byte char don't try
- // delete/insert/swap a character.
- if (sp->ts_tcharlen > 0) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_FINAL;
- break;
- }
- // Try skipping one character in the bad word (delete it).
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_INS_PREP;
- sp->ts_curi = 1;
- if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') {
- // Deleting a vowel at the start of a word counts less, see
- // soundalike_score().
- newscore = 2 * SCORE_DEL / 3;
- } else {
- newscore = SCORE_DEL;
- }
- if (fword[sp->ts_fidx] != NUL
- && TRY_DEEPER(su, stack, depth, newscore)) {
- go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
- sprintf(changename[depth], "%.*s-%s: delete %c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- fword[sp->ts_fidx]);
-#endif
- ++depth;
-
- // Remember what character we deleted, so that we can avoid
- // inserting it again.
- stack[depth].ts_flags |= TSF_DIDDEL;
- stack[depth].ts_delidx = sp->ts_fidx;
-
- // Advance over the character in fword[]. Give a bonus to the
- // score if the same character is following "nn" -> "n". It's
- // a bit illogical for soundfold tree but it does give better
- // results.
- c = utf_ptr2char((char *)fword + sp->ts_fidx);
- stack[depth].ts_fidx =
- (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)fword + sp->ts_fidx));
- if (utf_iscomposing(c)) {
- stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
- } else if (c == utf_ptr2char((char *)fword + stack[depth].ts_fidx)) {
- stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
- }
-
- break;
- }
- FALLTHROUGH;
-
- case STATE_INS_PREP:
- if (sp->ts_flags & TSF_DIDDEL) {
- // If we just deleted a byte then inserting won't make sense,
- // a substitute is always cheaper.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_SWAP;
- break;
- }
-
- // skip over NUL bytes
- n = sp->ts_arridx;
- for (;;) {
- if (sp->ts_curi > byts[n]) {
- // Only NUL bytes at this node, go to next state.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_SWAP;
- break;
- }
- if (byts[n + sp->ts_curi] != NUL) {
- // Found a byte to insert.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_INS;
- break;
- }
- ++sp->ts_curi;
- }
- break;
-
- case STATE_INS:
- // Insert one byte. Repeat this for each possible byte at this
- // node.
- n = sp->ts_arridx;
- if (sp->ts_curi > byts[n]) {
- // Done all bytes at this node, go to next state.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_SWAP;
- break;
- }
-
- // Do one more byte at this node, but:
- // - Skip NUL bytes.
- // - Skip the byte if it's equal to the byte in the word,
- // accepting that byte is always better.
- n += sp->ts_curi++;
- c = byts[n];
- if (soundfold && sp->ts_twordlen == 0 && c == '*') {
- // Inserting a vowel at the start of a word counts less,
- // see soundalike_score().
- newscore = 2 * SCORE_INS / 3;
- } else {
- newscore = SCORE_INS;
- }
- if (c != fword[sp->ts_fidx]
- && TRY_DEEPER(su, stack, depth, newscore)) {
- go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
- sprintf(changename[depth], "%.*s-%s: insert %c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- c);
-#endif
- ++depth;
- sp = &stack[depth];
- tword[sp->ts_twordlen++] = (char_u)c;
- sp->ts_arridx = idxs[n];
- fl = MB_BYTE2LEN(c);
- if (fl > 1) {
- // There are following bytes for the same character.
- // We must find all bytes before trying
- // delete/insert/swap/etc.
- sp->ts_tcharlen = (char_u)fl;
- sp->ts_tcharidx = 1;
- sp->ts_isdiff = DIFF_INSERT;
- }
- if (fl == 1) {
- // If the previous character was the same, thus doubling a
- // character, give a bonus to the score. Also for
- // soundfold words (illogical but does give a better
- // score).
- if (sp->ts_twordlen >= 2
- && tword[sp->ts_twordlen - 2] == c) {
- sp->ts_score -= SCORE_INS - SCORE_INSDUP;
- }
- }
- }
- break;
-
- case STATE_SWAP:
- // Swap two bytes in the bad word: "12" -> "21".
- // We change "fword" here, it's changed back afterwards at
- // STATE_UNSWAP.
- p = fword + sp->ts_fidx;
- c = *p;
- if (c == NUL) {
- // End of word, can't swap or replace.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_FINAL;
- break;
- }
-
- // Don't swap if the first character is not a word character.
- // SWAP3 etc. also don't make sense then.
- if (!soundfold && !spell_iswordp(p, curwin)) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- break;
- }
-
- n = utf_ptr2len((char *)p);
- c = utf_ptr2char((char *)p);
- if (p[n] == NUL) {
- c2 = NUL;
- } else if (!soundfold && !spell_iswordp(p + n, curwin)) {
- c2 = c; // don't swap non-word char
- } else {
- c2 = utf_ptr2char((char *)p + n);
- }
-
- // When the second character is NUL we can't swap.
- if (c2 == NUL) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- break;
- }
-
- // When characters are identical, swap won't do anything.
- // Also get here if the second char is not a word character.
- if (c == c2) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_SWAP3;
- break;
- }
- if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) {
- go_deeper(stack, depth, SCORE_SWAP);
-#ifdef DEBUG_TRIEWALK
- snprintf(changename[depth], sizeof(changename[0]),
- "%.*s-%s: swap %c and %c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- c, c2);
-#endif
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_UNSWAP;
- depth++;
- fl = utf_char2len(c2);
- memmove(p, p + n, (size_t)fl);
- utf_char2bytes(c, (char *)p + fl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
- } else {
- // If this swap doesn't work then SWAP3 won't either.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- }
- break;
-
- case STATE_UNSWAP:
- // Undo the STATE_SWAP swap: "21" -> "12".
- p = fword + sp->ts_fidx;
- n = utfc_ptr2len((char *)p);
- c = utf_ptr2char((char *)p + n);
- memmove(p + utfc_ptr2len((char *)p + n), p, (size_t)n);
- utf_char2bytes(c, (char *)p);
-
- FALLTHROUGH;
-
- case STATE_SWAP3:
- // Swap two bytes, skipping one: "123" -> "321". We change
- // "fword" here, it's changed back afterwards at STATE_UNSWAP3.
- p = fword + sp->ts_fidx;
- n = utf_ptr2len((char *)p);
- c = utf_ptr2char((char *)p);
- fl = utf_ptr2len((char *)p + n);
- c2 = utf_ptr2char((char *)p + n);
- if (!soundfold && !spell_iswordp(p + n + fl, curwin)) {
- c3 = c; // don't swap non-word char
- } else {
- c3 = utf_ptr2char((char *)p + n + fl);
- }
-
- // When characters are identical: "121" then SWAP3 result is
- // identical, ROT3L result is same as SWAP: "211", ROT3L result is
- // same as SWAP on next char: "112". Thus skip all swapping.
- // Also skip when c3 is NUL.
- // Also get here when the third character is not a word character.
- // Second character may any char: "a.b" -> "b.a"
- if (c == c3 || c3 == NUL) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- break;
- }
- if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) {
- go_deeper(stack, depth, SCORE_SWAP3);
-#ifdef DEBUG_TRIEWALK
- sprintf(changename[depth], "%.*s-%s: swap3 %c and %c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- c, c3);
-#endif
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_UNSWAP3;
- depth++;
- tl = utf_char2len(c3);
- memmove(p, p + n + fl, (size_t)tl);
- utf_char2bytes(c2, (char *)p + tl);
- utf_char2bytes(c, (char *)p + fl + tl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl);
- } else {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- }
- break;
-
- case STATE_UNSWAP3:
- // Undo STATE_SWAP3: "321" -> "123"
- p = fword + sp->ts_fidx;
- n = utfc_ptr2len((char *)p);
- c2 = utf_ptr2char((char *)p + n);
- fl = utfc_ptr2len((char *)p + n);
- c = utf_ptr2char((char *)p + n + fl);
- tl = utfc_ptr2len((char *)p + n + fl);
- memmove(p + fl + tl, p, (size_t)n);
- utf_char2bytes(c, (char *)p);
- utf_char2bytes(c2, (char *)p + tl);
- p = p + tl;
-
- if (!soundfold && !spell_iswordp(p, curwin)) {
- // Middle char is not a word char, skip the rotate. First and
- // third char were already checked at swap and swap3.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- break;
- }
-
- // Rotate three characters left: "123" -> "231". We change
- // "fword" here, it's changed back afterwards at STATE_UNROT3L.
- if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) {
- go_deeper(stack, depth, SCORE_SWAP3);
-#ifdef DEBUG_TRIEWALK
- p = fword + sp->ts_fidx;
- sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- p[0], p[1], p[2]);
-#endif
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_UNROT3L;
- ++depth;
- p = fword + sp->ts_fidx;
- n = utf_ptr2len((char *)p);
- c = utf_ptr2char((char *)p);
- fl = utf_ptr2len((char *)p + n);
- fl += utf_ptr2len((char *)p + n + fl);
- memmove(p, p + n, (size_t)fl);
- utf_char2bytes(c, (char *)p + fl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
- } else {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- }
- break;
-
- case STATE_UNROT3L:
- // Undo ROT3L: "231" -> "123"
- p = fword + sp->ts_fidx;
- n = utfc_ptr2len((char *)p);
- n += utfc_ptr2len((char *)p + n);
- c = utf_ptr2char((char *)p + n);
- tl = utfc_ptr2len((char *)p + n);
- memmove(p + tl, p, (size_t)n);
- utf_char2bytes(c, (char *)p);
-
- // Rotate three bytes right: "123" -> "312". We change "fword"
- // here, it's changed back afterwards at STATE_UNROT3R.
- if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) {
- go_deeper(stack, depth, SCORE_SWAP3);
-#ifdef DEBUG_TRIEWALK
- p = fword + sp->ts_fidx;
- sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- p[0], p[1], p[2]);
-#endif
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_UNROT3R;
- ++depth;
- p = fword + sp->ts_fidx;
- n = utf_ptr2len((char *)p);
- n += utf_ptr2len((char *)p + n);
- c = utf_ptr2char((char *)p + n);
- tl = utf_ptr2len((char *)p + n);
- memmove(p + tl, p, (size_t)n);
- utf_char2bytes(c, (char *)p);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl);
- } else {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_INI;
- }
- break;
-
- case STATE_UNROT3R:
- // Undo ROT3R: "312" -> "123"
- p = fword + sp->ts_fidx;
- c = utf_ptr2char((char *)p);
- tl = utfc_ptr2len((char *)p);
- n = utfc_ptr2len((char *)p + tl);
- n += utfc_ptr2len((char *)p + tl + n);
- memmove(p, p + tl, (size_t)n);
- utf_char2bytes(c, (char *)p + n);
-
- FALLTHROUGH;
-
- case STATE_REP_INI:
- // Check if matching with REP items from the .aff file would work.
- // Quickly skip if:
- // - there are no REP items and we are not in the soundfold trie
- // - the score is going to be too high anyway
- // - already applied a REP item or swapped here
- if ((lp->lp_replang == NULL && !soundfold)
- || sp->ts_score + SCORE_REP >= su->su_maxscore
- || sp->ts_fidx < sp->ts_fidxtry) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_FINAL;
- break;
- }
-
- // Use the first byte to quickly find the first entry that may
- // match. If the index is -1 there is none.
- if (soundfold) {
- sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]];
- } else {
- sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]];
- }
-
- if (sp->ts_curi < 0) {
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_FINAL;
- break;
- }
-
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP;
- FALLTHROUGH;
-
- case STATE_REP:
- // Try matching with REP items from the .aff file. For each match
- // replace the characters and check if the resulting word is
- // valid.
- p = fword + sp->ts_fidx;
-
- if (soundfold) {
- gap = &slang->sl_repsal;
- } else {
- gap = &lp->lp_replang->sl_rep;
- }
- while (sp->ts_curi < gap->ga_len) {
- ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
- if (*ftp->ft_from != *p) {
- // past possible matching entries
- sp->ts_curi = (char_u)gap->ga_len;
- break;
- }
- if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0
- && TRY_DEEPER(su, stack, depth, SCORE_REP)) {
- go_deeper(stack, depth, SCORE_REP);
-#ifdef DEBUG_TRIEWALK
- sprintf(changename[depth], "%.*s-%s: replace %s with %s",
- sp->ts_twordlen, tword, fword + sp->ts_fidx,
- ftp->ft_from, ftp->ft_to);
-#endif
- // Need to undo this afterwards.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP_UNDO;
-
- // Change the "from" to the "to" string.
- ++depth;
- fl = (int)STRLEN(ftp->ft_from);
- tl = (int)STRLEN(ftp->ft_to);
- if (fl != tl) {
- STRMOVE(p + tl, p + fl);
- repextra += tl - fl;
- }
- memmove(p, ftp->ft_to, (size_t)tl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl);
- stack[depth].ts_tcharlen = 0;
- break;
- }
- }
-
- if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) {
- // No (more) matches.
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_FINAL;
- }
-
- break;
-
- case STATE_REP_UNDO:
- // Undo a REP replacement and continue with the next one.
- if (soundfold) {
- gap = &slang->sl_repsal;
- } else {
- gap = &lp->lp_replang->sl_rep;
- }
- ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
- fl = (int)STRLEN(ftp->ft_from);
- tl = (int)STRLEN(ftp->ft_to);
- p = fword + sp->ts_fidx;
- if (fl != tl) {
- STRMOVE(p + fl, p + tl);
- repextra -= tl - fl;
- }
- memmove(p, ftp->ft_from, (size_t)fl);
- PROF_STORE(sp->ts_state)
- sp->ts_state = STATE_REP;
- break;
-
- default:
- // Did all possible states at this level, go up one level.
- --depth;
-
- if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) {
- // Continue in or go back to the prefix tree.
- byts = pbyts;
- idxs = pidxs;
- }
-
- // Don't check for CTRL-C too often, it takes time.
- if (--breakcheckcount == 0) {
- os_breakcheck();
- breakcheckcount = 1000;
- if (profile_passed_limit(time_limit)) {
- got_int = true;
- }
- }
- }
- }
-}
-
-// Go one level deeper in the tree.
-static void go_deeper(trystate_T *stack, int depth, int score_add)
-{
- stack[depth + 1] = stack[depth];
- stack[depth + 1].ts_state = STATE_START;
- stack[depth + 1].ts_score = stack[depth].ts_score + score_add;
- stack[depth + 1].ts_curi = 1; // start just after length byte
- stack[depth + 1].ts_flags = 0;
-}
-
// Case-folding may change the number of bytes: Count nr of chars in
// fword[flen] and return the byte length of that many chars in "word".
-static int nofold_len(char_u *fword, int flen, char_u *word)
+int nofold_len(char_u *fword, int flen, char_u *word)
{
char_u *p;
int i = 0;
@@ -4961,677 +2688,8 @@ static int nofold_len(char_u *fword, int flen, char_u *word)
return (int)(p - word);
}
-// "fword" is a good word with case folded. Find the matching keep-case
-// words and put it in "kword".
-// Theoretically there could be several keep-case words that result in the
-// same case-folded word, but we only find one...
-static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword)
-{
- char_u uword[MAXWLEN]; // "fword" in upper-case
- int depth;
- idx_T tryidx;
-
- // The following arrays are used at each depth in the tree.
- idx_T arridx[MAXWLEN];
- int round[MAXWLEN];
- int fwordidx[MAXWLEN];
- int uwordidx[MAXWLEN];
- int kwordlen[MAXWLEN];
-
- int flen, ulen;
- int l;
- int len;
- int c;
- idx_T lo, hi, m;
- char_u *p;
- char_u *byts = slang->sl_kbyts; // array with bytes of the words
- idx_T *idxs = slang->sl_kidxs; // array with indexes
-
- if (byts == NULL) {
- // array is empty: "cannot happen"
- *kword = NUL;
- return;
- }
-
- // Make an all-cap version of "fword".
- allcap_copy(fword, uword);
-
- // Each character needs to be tried both case-folded and upper-case.
- // All this gets very complicated if we keep in mind that changing case
- // may change the byte length of a multi-byte character...
- depth = 0;
- arridx[0] = 0;
- round[0] = 0;
- fwordidx[0] = 0;
- uwordidx[0] = 0;
- kwordlen[0] = 0;
- while (depth >= 0) {
- if (fword[fwordidx[depth]] == NUL) {
- // We are at the end of "fword". If the tree allows a word to end
- // here we have found a match.
- if (byts[arridx[depth] + 1] == 0) {
- kword[kwordlen[depth]] = NUL;
- return;
- }
-
- // kword is getting too long, continue one level up
- --depth;
- } else if (++round[depth] > 2) {
- // tried both fold-case and upper-case character, continue one
- // level up
- --depth;
- } else {
- // round[depth] == 1: Try using the folded-case character.
- // round[depth] == 2: Try using the upper-case character.
- flen = utf_ptr2len((char *)fword + fwordidx[depth]);
- ulen = utf_ptr2len((char *)uword + uwordidx[depth]);
- if (round[depth] == 1) {
- p = fword + fwordidx[depth];
- l = flen;
- } else {
- p = uword + uwordidx[depth];
- l = ulen;
- }
-
- for (tryidx = arridx[depth]; l > 0; --l) {
- // Perform a binary search in the list of accepted bytes.
- len = byts[tryidx++];
- c = *p++;
- lo = tryidx;
- hi = tryidx + len - 1;
- while (lo < hi) {
- m = (lo + hi) / 2;
- if (byts[m] > c) {
- hi = m - 1;
- } else if (byts[m] < c) {
- lo = m + 1;
- } else {
- lo = hi = m;
- break;
- }
- }
-
- // Stop if there is no matching byte.
- if (hi < lo || byts[lo] != c) {
- break;
- }
-
- // Continue at the child (if there is one).
- tryidx = idxs[lo];
- }
-
- if (l == 0) {
- // Found the matching char. Copy it to "kword" and go a
- // level deeper.
- if (round[depth] == 1) {
- STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth],
- flen);
- kwordlen[depth + 1] = kwordlen[depth] + flen;
- } else {
- STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth],
- ulen);
- kwordlen[depth + 1] = kwordlen[depth] + ulen;
- }
- fwordidx[depth + 1] = fwordidx[depth] + flen;
- uwordidx[depth + 1] = uwordidx[depth] + ulen;
-
- ++depth;
- arridx[depth] = tryidx;
- round[depth] = 0;
- }
- }
- }
-
- // Didn't find it: "cannot happen".
- *kword = NUL;
-}
-
-// Compute the sound-a-like score for suggestions in su->su_ga and add them to
-// su->su_sga.
-static void score_comp_sal(suginfo_T *su)
-{
- langp_T *lp;
- char_u badsound[MAXWLEN];
- int i;
- suggest_T *stp;
- suggest_T *sstp;
- int score;
-
- ga_grow(&su->su_sga, su->su_ga.ga_len);
-
- // Use the sound-folding of the first language that supports it.
- for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
- // soundfold the bad word
- spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound);
-
- for (i = 0; i < su->su_ga.ga_len; ++i) {
- stp = &SUG(su->su_ga, i);
-
- // Case-fold the suggested word, sound-fold it and compute the
- // sound-a-like score.
- score = stp_sal_score(stp, su, lp->lp_slang, badsound);
- if (score < SCORE_MAXMAX) {
- // Add the suggestion.
- sstp = &SUG(su->su_sga, su->su_sga.ga_len);
- sstp->st_word = vim_strsave(stp->st_word);
- sstp->st_wordlen = stp->st_wordlen;
- sstp->st_score = score;
- sstp->st_altscore = 0;
- sstp->st_orglen = stp->st_orglen;
- ++su->su_sga.ga_len;
- }
- }
- break;
- }
- }
-}
-
-// Combine the list of suggestions in su->su_ga and su->su_sga.
-// They are entwined.
-static void score_combine(suginfo_T *su)
-{
- garray_T ga;
- garray_T *gap;
- langp_T *lp;
- suggest_T *stp;
- char_u *p;
- char_u badsound[MAXWLEN];
- int round;
- slang_T *slang = NULL;
-
- // Add the alternate score to su_ga.
- for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
- // soundfold the bad word
- slang = lp->lp_slang;
- spell_soundfold(slang, su->su_fbadword, true, badsound);
-
- for (int i = 0; i < su->su_ga.ga_len; ++i) {
- stp = &SUG(su->su_ga, i);
- stp->st_altscore = stp_sal_score(stp, su, slang, badsound);
- if (stp->st_altscore == SCORE_MAXMAX) {
- stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4;
- } else {
- stp->st_score = (stp->st_score * 3
- + stp->st_altscore) / 4;
- }
- stp->st_salscore = false;
- }
- break;
- }
- }
-
- if (slang == NULL) { // Using "double" without sound folding.
- (void)cleanup_suggestions(&su->su_ga, su->su_maxscore,
- su->su_maxcount);
- return;
- }
-
- // Add the alternate score to su_sga.
- for (int i = 0; i < su->su_sga.ga_len; ++i) {
- stp = &SUG(su->su_sga, i);
- stp->st_altscore = spell_edit_score(slang,
- su->su_badword, stp->st_word);
- if (stp->st_score == SCORE_MAXMAX) {
- stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8;
- } else {
- stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8;
- }
- stp->st_salscore = true;
- }
-
- // Remove bad suggestions, sort the suggestions and truncate at "maxcount"
- // for both lists.
- check_suggestions(su, &su->su_ga);
- (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
- check_suggestions(su, &su->su_sga);
- (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount);
-
- ga_init(&ga, (int)sizeof(suginfo_T), 1);
- ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len);
-
- stp = &SUG(ga, 0);
- for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; ++i) {
- // round 1: get a suggestion from su_ga
- // round 2: get a suggestion from su_sga
- for (round = 1; round <= 2; ++round) {
- gap = round == 1 ? &su->su_ga : &su->su_sga;
- if (i < gap->ga_len) {
- // Don't add a word if it's already there.
- p = SUG(*gap, i).st_word;
- int j;
- for (j = 0; j < ga.ga_len; ++j) {
- if (STRCMP(stp[j].st_word, p) == 0) {
- break;
- }
- }
- if (j == ga.ga_len) {
- stp[ga.ga_len++] = SUG(*gap, i);
- } else {
- xfree(p);
- }
- }
- }
- }
-
- ga_clear(&su->su_ga);
- ga_clear(&su->su_sga);
-
- // Truncate the list to the number of suggestions that will be displayed.
- if (ga.ga_len > su->su_maxcount) {
- for (int i = su->su_maxcount; i < ga.ga_len; ++i) {
- xfree(stp[i].st_word);
- }
- ga.ga_len = su->su_maxcount;
- }
-
- su->su_ga = ga;
-}
-
-/// For the goodword in "stp" compute the soundalike score compared to the
-/// badword.
-///
-/// @param badsound sound-folded badword
-static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound)
-{
- char_u *p;
- char_u *pbad;
- char_u *pgood;
- char_u badsound2[MAXWLEN];
- char_u fword[MAXWLEN];
- char_u goodsound[MAXWLEN];
- char_u goodword[MAXWLEN];
- int lendiff;
-
- lendiff = su->su_badlen - stp->st_orglen;
- if (lendiff >= 0) {
- pbad = badsound;
- } else {
- // soundfold the bad word with more characters following
- (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN);
-
- // When joining two words the sound often changes a lot. E.g., "t he"
- // sounds like "t h" while "the" sounds like "@". Avoid that by
- // removing the space. Don't do it when the good word also contains a
- // space.
- if (ascii_iswhite(su->su_badptr[su->su_badlen])
- && *skiptowhite(stp->st_word) == NUL) {
- for (p = fword; *(p = skiptowhite(p)) != NUL;) {
- STRMOVE(p, p + 1);
- }
- }
-
- spell_soundfold(slang, fword, true, badsound2);
- pbad = badsound2;
- }
-
- if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) {
- // Add part of the bad word to the good word, so that we soundfold
- // what replaces the bad word.
- STRCPY(goodword, stp->st_word);
- STRLCPY(goodword + stp->st_wordlen,
- su->su_badptr + su->su_badlen - lendiff, lendiff + 1);
- pgood = goodword;
- } else {
- pgood = stp->st_word;
- }
-
- // Sound-fold the word and compute the score for the difference.
- spell_soundfold(slang, pgood, false, goodsound);
-
- return soundalike_score(goodsound, pbad);
-}
-
-static sftword_T dumsft;
-#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft)))
-#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key)
-
-// Prepare for calling suggest_try_soundalike().
-static void suggest_try_soundalike_prep(void)
-{
- langp_T *lp;
- slang_T *slang;
-
- // Do this for all languages that support sound folding and for which a
- // .sug file has been loaded.
- for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
- if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
- // prepare the hashtable used by add_sound_suggest()
- hash_init(&slang->sl_sounddone);
- }
- }
-}
-
-// Find suggestions by comparing the word in a sound-a-like form.
-// Note: This doesn't support postponed prefixes.
-static void suggest_try_soundalike(suginfo_T *su)
-{
- char_u salword[MAXWLEN];
- langp_T *lp;
- slang_T *slang;
-
- // Do this for all languages that support sound folding and for which a
- // .sug file has been loaded.
- for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
- if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
- // soundfold the bad word
- spell_soundfold(slang, su->su_fbadword, true, salword);
-
- // try all kinds of inserts/deletes/swaps/etc.
- // TODO: also soundfold the next words, so that we can try joining
- // and splitting
-#ifdef SUGGEST_PROFILE
- prof_init();
-#endif
- suggest_trie_walk(su, lp, salword, true);
-#ifdef SUGGEST_PROFILE
- prof_report("soundalike");
-#endif
- }
- }
-}
-
-// Finish up after calling suggest_try_soundalike().
-static void suggest_try_soundalike_finish(void)
-{
- langp_T *lp;
- slang_T *slang;
- int todo;
- hashitem_T *hi;
-
- // Do this for all languages that support sound folding and for which a
- // .sug file has been loaded.
- for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
- if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
- // Free the info about handled words.
- todo = (int)slang->sl_sounddone.ht_used;
- for (hi = slang->sl_sounddone.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- xfree(HI2SFT(hi));
- --todo;
- }
- }
-
- // Clear the hashtable, it may also be used by another region.
- hash_clear(&slang->sl_sounddone);
- hash_init(&slang->sl_sounddone);
- }
- }
-}
-
-/// A match with a soundfolded word is found. Add the good word(s) that
-/// produce this soundfolded word.
-///
-/// @param score soundfold score
-static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp)
-{
- slang_T *slang = lp->lp_slang; // language for sound folding
- int sfwordnr;
- char_u *nrline;
- int orgnr;
- char_u theword[MAXWLEN];
- int i;
- int wlen;
- char_u *byts;
- idx_T *idxs;
- int n;
- int wordcount;
- int wc;
- int goodscore;
- hash_T hash;
- hashitem_T *hi;
- sftword_T *sft;
- int bc, gc;
- int limit;
-
- // It's very well possible that the same soundfold word is found several
- // times with different scores. Since the following is quite slow only do
- // the words that have a better score than before. Use a hashtable to
- // remember the words that have been done.
- hash = hash_hash(goodword);
- const size_t goodword_len = STRLEN(goodword);
- hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len,
- hash);
- if (HASHITEM_EMPTY(hi)) {
- sft = xmalloc(sizeof(sftword_T) + goodword_len);
- sft->sft_score = (int16_t)score;
- memcpy(sft->sft_word, goodword, goodword_len + 1);
- hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash);
- } else {
- sft = HI2SFT(hi);
- if (score >= sft->sft_score) {
- return;
- }
- sft->sft_score = (int16_t)score;
- }
-
- // Find the word nr in the soundfold tree.
- sfwordnr = soundfold_find(slang, goodword);
- if (sfwordnr < 0) {
- internal_error("add_sound_suggest()");
- return;
- }
-
- // Go over the list of good words that produce this soundfold word
- nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false);
- orgnr = 0;
- while (*nrline != NUL) {
- // The wordnr was stored in a minimal nr of bytes as an offset to the
- // previous wordnr.
- orgnr += bytes2offset(&nrline);
-
- byts = slang->sl_fbyts;
- idxs = slang->sl_fidxs;
-
- // Lookup the word "orgnr" one of the two tries.
- n = 0;
- wordcount = 0;
- for (wlen = 0; wlen < MAXWLEN - 3; ++wlen) {
- i = 1;
- if (wordcount == orgnr && byts[n + 1] == NUL) {
- break; // found end of word
- }
- if (byts[n + 1] == NUL) {
- ++wordcount;
- }
-
- // skip over the NUL bytes
- for (; byts[n + i] == NUL; ++i) {
- if (i > byts[n]) { // safety check
- STRCPY(theword + wlen, "BAD");
- wlen += 3;
- goto badword;
- }
- }
-
- // One of the siblings must have the word.
- for (; i < byts[n]; ++i) {
- wc = idxs[idxs[n + i]]; // nr of words under this byte
- if (wordcount + wc > orgnr) {
- break;
- }
- wordcount += wc;
- }
-
- theword[wlen] = byts[n + i];
- n = idxs[n + i];
- }
-badword:
- theword[wlen] = NUL;
-
- // Go over the possible flags and regions.
- for (; i <= byts[n] && byts[n + i] == NUL; ++i) {
- char_u cword[MAXWLEN];
- char_u *p;
- int flags = (int)idxs[n + i];
-
- // Skip words with the NOSUGGEST flag
- if (flags & WF_NOSUGGEST) {
- continue;
- }
-
- if (flags & WF_KEEPCAP) {
- // Must find the word in the keep-case tree.
- find_keepcap_word(slang, theword, cword);
- p = cword;
- } else {
- flags |= su->su_badflags;
- if ((flags & WF_CAPMASK) != 0) {
- // Need to fix case according to "flags".
- make_case_word(theword, cword, flags);
- p = cword;
- } else {
- p = theword;
- }
- }
-
- // Add the suggestion.
- if (sps_flags & SPS_DOUBLE) {
- // Add the suggestion if the score isn't too bad.
- if (score <= su->su_maxscore) {
- add_suggestion(su, &su->su_sga, p, su->su_badlen,
- score, 0, false, slang, false);
- }
- } else {
- // Add a penalty for words in another region.
- if ((flags & WF_REGION)
- && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) {
- goodscore = SCORE_REGION;
- } else {
- goodscore = 0;
- }
-
- // Add a small penalty for changing the first letter from
- // lower to upper case. Helps for "tath" -> "Kath", which is
- // less common than "tath" -> "path". Don't do it when the
- // letter is the same, that has already been counted.
- gc = utf_ptr2char((char *)p);
- if (SPELL_ISUPPER(gc)) {
- bc = utf_ptr2char((char *)su->su_badword);
- if (!SPELL_ISUPPER(bc)
- && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) {
- goodscore += SCORE_ICASE / 2;
- }
- }
-
- // Compute the score for the good word. This only does letter
- // insert/delete/swap/replace. REP items are not considered,
- // which may make the score a bit higher.
- // Use a limit for the score to make it work faster. Use
- // MAXSCORE(), because RESCORE() will change the score.
- // If the limit is very high then the iterative method is
- // inefficient, using an array is quicker.
- limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
- if (limit > SCORE_LIMITMAX) {
- goodscore += spell_edit_score(slang, su->su_badword, p);
- } else {
- goodscore += spell_edit_score_limit(slang, su->su_badword,
- p, limit);
- }
-
- // When going over the limit don't bother to do the rest.
- if (goodscore < SCORE_MAXMAX) {
- // Give a bonus to words seen before.
- goodscore = score_wordcount_adj(slang, goodscore, p, false);
-
- // Add the suggestion if the score isn't too bad.
- goodscore = RESCORE(goodscore, score);
- if (goodscore <= su->su_sfmaxscore) {
- add_suggestion(su, &su->su_ga, p, su->su_badlen,
- goodscore, score, true, slang, true);
- }
- }
- }
- }
- }
-}
-
-// Find word "word" in fold-case tree for "slang" and return the word number.
-static int soundfold_find(slang_T *slang, char_u *word)
-{
- idx_T arridx = 0;
- int len;
- int wlen = 0;
- int c;
- char_u *ptr = word;
- char_u *byts;
- idx_T *idxs;
- int wordnr = 0;
-
- byts = slang->sl_sbyts;
- idxs = slang->sl_sidxs;
-
- for (;;) {
- // First byte is the number of possible bytes.
- len = byts[arridx++];
-
- // If the first possible byte is a zero the word could end here.
- // If the word ends we found the word. If not skip the NUL bytes.
- c = ptr[wlen];
- if (byts[arridx] == NUL) {
- if (c == NUL) {
- break;
- }
-
- // Skip over the zeros, there can be several.
- while (len > 0 && byts[arridx] == NUL) {
- ++arridx;
- --len;
- }
- if (len == 0) {
- return -1; // no children, word should have ended here
- }
- ++wordnr;
- }
-
- // If the word ends we didn't find it.
- if (c == NUL) {
- return -1;
- }
-
- // Perform a binary search in the list of accepted bytes.
- if (c == TAB) { // <Tab> is handled like <Space>
- c = ' ';
- }
- while (byts[arridx] < c) {
- // The word count is in the first idxs[] entry of the child.
- wordnr += idxs[idxs[arridx]];
- ++arridx;
- if (--len == 0) { // end of the bytes, didn't find it
- return -1;
- }
- }
- if (byts[arridx] != c) { // didn't find the byte
- return -1;
- }
-
- // Continue at the child (if there is one).
- arridx = idxs[arridx];
- ++wlen;
-
- // One space in the good word may stand for several spaces in the
- // checked word.
- if (c == ' ') {
- while (ptr[wlen] == ' ' || ptr[wlen] == TAB) {
- ++wlen;
- }
- }
- }
-
- return wordnr;
-}
-
// Copy "fword" to "cword", fixing case according to "flags".
-static void make_case_word(char_u *fword, char_u *cword, int flags)
+void make_case_word(char_u *fword, char_u *cword, int flags)
{
if (flags & WF_ALLCAP) {
// Make it all upper-case
@@ -5645,291 +2703,6 @@ static void make_case_word(char_u *fword, char_u *cword, int flags)
}
}
-// Returns true if "c1" and "c2" are similar characters according to the MAP
-// lines in the .aff file.
-static bool similar_chars(slang_T *slang, int c1, int c2)
-{
- int m1, m2;
- char buf[MB_MAXBYTES + 1];
- hashitem_T *hi;
-
- if (c1 >= 256) {
- buf[utf_char2bytes(c1, (char *)buf)] = 0;
- hi = hash_find(&slang->sl_map_hash, buf);
- if (HASHITEM_EMPTY(hi)) {
- m1 = 0;
- } else {
- m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1);
- }
- } else {
- m1 = slang->sl_map_array[c1];
- }
- if (m1 == 0) {
- return false;
- }
-
- if (c2 >= 256) {
- buf[utf_char2bytes(c2, (char *)buf)] = 0;
- hi = hash_find(&slang->sl_map_hash, buf);
- if (HASHITEM_EMPTY(hi)) {
- m2 = 0;
- } else {
- m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1);
- }
- } else {
- m2 = slang->sl_map_array[c2];
- }
-
- return m1 == m2;
-}
-
-/// Adds a suggestion to the list of suggestions.
-/// For a suggestion that is already in the list the lowest score is remembered.
-///
-/// @param gap either su_ga or su_sga
-/// @param badlenarg len of bad word replaced with "goodword"
-/// @param had_bonus value for st_had_bonus
-/// @param slang language for sound folding
-/// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score.
-static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg,
- int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf)
-{
- int goodlen; // len of goodword changed
- int badlen; // len of bad word changed
- suggest_T *stp;
- suggest_T new_sug;
-
- // Minimize "badlen" for consistency. Avoids that changing "the the" to
- // "thee the" is added next to changing the first "the" the "thee".
- const char_u *pgood = goodword + STRLEN(goodword);
- char_u *pbad = su->su_badptr + badlenarg;
- for (;;) {
- goodlen = (int)(pgood - goodword);
- badlen = (int)(pbad - su->su_badptr);
- if (goodlen <= 0 || badlen <= 0) {
- break;
- }
- MB_PTR_BACK(goodword, pgood);
- MB_PTR_BACK(su->su_badptr, pbad);
- if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) {
- break;
- }
- }
-
- if (badlen == 0 && goodlen == 0) {
- // goodword doesn't change anything; may happen for "the the" changing
- // the first "the" to itself.
- return;
- }
-
- int i;
- if (GA_EMPTY(gap)) {
- i = -1;
- } else {
- // Check if the word is already there. Also check the length that is
- // being replaced "thes," -> "these" is a different suggestion from
- // "thes" -> "these".
- stp = &SUG(*gap, 0);
- for (i = gap->ga_len; --i >= 0; ++stp) {
- if (stp->st_wordlen == goodlen
- && stp->st_orglen == badlen
- && STRNCMP(stp->st_word, goodword, goodlen) == 0) {
- // Found it. Remember the word with the lowest score.
- if (stp->st_slang == NULL) {
- stp->st_slang = slang;
- }
-
- new_sug.st_score = score;
- new_sug.st_altscore = altscore;
- new_sug.st_had_bonus = had_bonus;
-
- if (stp->st_had_bonus != had_bonus) {
- // Only one of the two had the soundalike score computed.
- // Need to do that for the other one now, otherwise the
- // scores can't be compared. This happens because
- // suggest_try_change() doesn't compute the soundalike
- // word to keep it fast, while some special methods set
- // the soundalike score to zero.
- if (had_bonus) {
- rescore_one(su, stp);
- } else {
- new_sug.st_word = stp->st_word;
- new_sug.st_wordlen = stp->st_wordlen;
- new_sug.st_slang = stp->st_slang;
- new_sug.st_orglen = badlen;
- rescore_one(su, &new_sug);
- }
- }
-
- if (stp->st_score > new_sug.st_score) {
- stp->st_score = new_sug.st_score;
- stp->st_altscore = new_sug.st_altscore;
- stp->st_had_bonus = new_sug.st_had_bonus;
- }
- break;
- }
- }
- }
-
- if (i < 0) {
- // Add a suggestion.
- stp = GA_APPEND_VIA_PTR(suggest_T, gap);
- stp->st_word = vim_strnsave(goodword, (size_t)goodlen);
- stp->st_wordlen = goodlen;
- stp->st_score = score;
- stp->st_altscore = altscore;
- stp->st_had_bonus = had_bonus;
- stp->st_orglen = badlen;
- stp->st_slang = slang;
-
- // If we have too many suggestions now, sort the list and keep
- // the best suggestions.
- if (gap->ga_len > SUG_MAX_COUNT(su)) {
- if (maxsf) {
- su->su_sfmaxscore = cleanup_suggestions(gap,
- su->su_sfmaxscore, SUG_CLEAN_COUNT(su));
- } else {
- su->su_maxscore = cleanup_suggestions(gap,
- su->su_maxscore, SUG_CLEAN_COUNT(su));
- }
- }
- }
-}
-
-/// Suggestions may in fact be flagged as errors. Esp. for banned words and
-/// for split words, such as "the the". Remove these from the list here.
-///
-/// @param gap either su_ga or su_sga
-static void check_suggestions(suginfo_T *su, garray_T *gap)
-{
- suggest_T *stp;
- char_u longword[MAXWLEN + 1];
- int len;
- hlf_T attr;
-
- if (gap->ga_len == 0) {
- return;
- }
- stp = &SUG(*gap, 0);
- for (int i = gap->ga_len - 1; i >= 0; --i) {
- // Need to append what follows to check for "the the".
- STRLCPY(longword, stp[i].st_word, MAXWLEN + 1);
- len = stp[i].st_wordlen;
- STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen,
- MAXWLEN - len + 1);
- attr = HLF_COUNT;
- (void)spell_check(curwin, longword, &attr, NULL, false);
- if (attr != HLF_COUNT) {
- // Remove this entry.
- xfree(stp[i].st_word);
- --gap->ga_len;
- if (i < gap->ga_len) {
- memmove(stp + i, stp + i + 1, sizeof(suggest_T) * (size_t)(gap->ga_len - i));
- }
- }
- }
-}
-
-// Add a word to be banned.
-static void add_banned(suginfo_T *su, char_u *word)
-{
- char_u *s;
- hash_T hash;
- hashitem_T *hi;
-
- hash = hash_hash(word);
- const size_t word_len = STRLEN(word);
- hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash);
- if (HASHITEM_EMPTY(hi)) {
- s = xmemdupz(word, word_len);
- hash_add_item(&su->su_banned, hi, s, hash);
- }
-}
-
-// Recompute the score for all suggestions if sound-folding is possible. This
-// is slow, thus only done for the final results.
-static void rescore_suggestions(suginfo_T *su)
-{
- if (su->su_sallang != NULL) {
- for (int i = 0; i < su->su_ga.ga_len; ++i) {
- rescore_one(su, &SUG(su->su_ga, i));
- }
- }
-}
-
-// Recompute the score for one suggestion if sound-folding is possible.
-static void rescore_one(suginfo_T *su, suggest_T *stp)
-{
- slang_T *slang = stp->st_slang;
- char_u sal_badword[MAXWLEN];
- char_u *p;
-
- // Only rescore suggestions that have no sal score yet and do have a
- // language.
- if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) {
- if (slang == su->su_sallang) {
- p = su->su_sal_badword;
- } else {
- spell_soundfold(slang, su->su_fbadword, true, sal_badword);
- p = sal_badword;
- }
-
- stp->st_altscore = stp_sal_score(stp, su, slang, p);
- if (stp->st_altscore == SCORE_MAXMAX) {
- stp->st_altscore = SCORE_BIG;
- }
- stp->st_score = RESCORE(stp->st_score, stp->st_altscore);
- stp->st_had_bonus = true;
- }
-}
-
-// Function given to qsort() to sort the suggestions on st_score.
-// First on "st_score", then "st_altscore" then alphabetically.
-static int sug_compare(const void *s1, const void *s2)
-{
- suggest_T *p1 = (suggest_T *)s1;
- suggest_T *p2 = (suggest_T *)s2;
- int n = p1->st_score - p2->st_score;
-
- if (n == 0) {
- n = p1->st_altscore - p2->st_altscore;
- if (n == 0) {
- n = STRICMP(p1->st_word, p2->st_word);
- }
- }
- return n;
-}
-
-/// Cleanup the suggestions:
-/// - Sort on score.
-/// - Remove words that won't be displayed.
-///
-/// @param keep nr of suggestions to keep
-///
-/// @return the maximum score in the list or "maxscore" unmodified.
-static int cleanup_suggestions(garray_T *gap, int maxscore, int keep)
- FUNC_ATTR_NONNULL_ALL
-{
- if (gap->ga_len > 0) {
- // Sort the list.
- qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
-
- // Truncate the list to the number of suggestions that will be displayed.
- if (gap->ga_len > keep) {
- suggest_T *const stp = &SUG(*gap, 0);
-
- for (int i = keep; i < gap->ga_len; i++) {
- xfree(stp[i].st_word);
- }
- gap->ga_len = keep;
- if (keep >= 1) {
- return stp[keep - 1].st_score;
- }
- }
- }
- return maxscore;
-}
-
/// Soundfold a string, for soundfold()
///
/// @param[in] word Word to soundfold.
@@ -6128,12 +2901,12 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
if ((pf = smp[n].sm_oneof_w) != NULL) {
// Check for match with one of the chars in "sm_oneof".
while (*pf != NUL && *pf != word[i + k]) {
- ++pf;
+ pf++;
}
if (*pf == NUL) {
continue;
}
- ++k;
+ k++;
}
char_u *s = smp[n].sm_rules;
pri = 5; // default priority
@@ -6203,12 +2976,12 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
// Check for match with one of the chars in
// "sm_oneof".
while (*pf != NUL && *pf != word[i + k0]) {
- ++pf;
+ pf++;
}
if (*pf == NUL) {
continue;
}
- ++k0;
+ k0++;
}
p0 = 5;
@@ -6339,499 +3112,6 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
res[l] = NUL;
}
-/// Compute a score for two sound-a-like words.
-/// This permits up to two inserts/deletes/swaps/etc. to keep things fast.
-/// Instead of a generic loop we write out the code. That keeps it fast by
-/// avoiding checks that will not be possible.
-///
-/// @param goodstart sound-folded good word
-/// @param badstart sound-folded bad word
-static int soundalike_score(char_u *goodstart, char_u *badstart)
-{
- char_u *goodsound = goodstart;
- char_u *badsound = badstart;
- int goodlen;
- int badlen;
- int n;
- char_u *pl, *ps;
- char_u *pl2, *ps2;
- int score = 0;
-
- // Adding/inserting "*" at the start (word starts with vowel) shouldn't be
- // counted so much, vowels in the middle of the word aren't counted at all.
- if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) {
- if ((badsound[0] == NUL && goodsound[1] == NUL)
- || (goodsound[0] == NUL && badsound[1] == NUL)) {
- // changing word with vowel to word without a sound
- return SCORE_DEL;
- }
- if (badsound[0] == NUL || goodsound[0] == NUL) {
- // more than two changes
- return SCORE_MAXMAX;
- }
-
- if (badsound[1] == goodsound[1]
- || (badsound[1] != NUL
- && goodsound[1] != NUL
- && badsound[2] == goodsound[2])) {
- // handle like a substitute
- } else {
- score = 2 * SCORE_DEL / 3;
- if (*badsound == '*') {
- ++badsound;
- } else {
- ++goodsound;
- }
- }
- }
-
- goodlen = (int)STRLEN(goodsound);
- badlen = (int)STRLEN(badsound);
-
- // Return quickly if the lengths are too different to be fixed by two
- // changes.
- n = goodlen - badlen;
- if (n < -2 || n > 2) {
- return SCORE_MAXMAX;
- }
-
- if (n > 0) {
- pl = goodsound; // goodsound is longest
- ps = badsound;
- } else {
- pl = badsound; // badsound is longest
- ps = goodsound;
- }
-
- // Skip over the identical part.
- while (*pl == *ps && *pl != NUL) {
- ++pl;
- ++ps;
- }
-
- switch (n) {
- case -2:
- case 2:
- // Must delete two characters from "pl".
- ++pl; // first delete
- while (*pl == *ps) {
- ++pl;
- ++ps;
- }
- // strings must be equal after second delete
- if (STRCMP(pl + 1, ps) == 0) {
- return score + SCORE_DEL * 2;
- }
-
- // Failed to compare.
- break;
-
- case -1:
- case 1:
- // Minimal one delete from "pl" required.
-
- // 1: delete
- pl2 = pl + 1;
- ps2 = ps;
- while (*pl2 == *ps2) {
- if (*pl2 == NUL) { // reached the end
- return score + SCORE_DEL;
- }
- ++pl2;
- ++ps2;
- }
-
- // 2: delete then swap, then rest must be equal
- if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
- && STRCMP(pl2 + 2, ps2 + 2) == 0) {
- return score + SCORE_DEL + SCORE_SWAP;
- }
-
- // 3: delete then substitute, then the rest must be equal
- if (STRCMP(pl2 + 1, ps2 + 1) == 0) {
- return score + SCORE_DEL + SCORE_SUBST;
- }
-
- // 4: first swap then delete
- if (pl[0] == ps[1] && pl[1] == ps[0]) {
- pl2 = pl + 2; // swap, skip two chars
- ps2 = ps + 2;
- while (*pl2 == *ps2) {
- ++pl2;
- ++ps2;
- }
- // delete a char and then strings must be equal
- if (STRCMP(pl2 + 1, ps2) == 0) {
- return score + SCORE_SWAP + SCORE_DEL;
- }
- }
-
- // 5: first substitute then delete
- pl2 = pl + 1; // substitute, skip one char
- ps2 = ps + 1;
- while (*pl2 == *ps2) {
- ++pl2;
- ++ps2;
- }
- // delete a char and then strings must be equal
- if (STRCMP(pl2 + 1, ps2) == 0) {
- return score + SCORE_SUBST + SCORE_DEL;
- }
-
- // Failed to compare.
- break;
-
- case 0:
- // Lengths are equal, thus changes must result in same length: An
- // insert is only possible in combination with a delete.
- // 1: check if for identical strings
- if (*pl == NUL) {
- return score;
- }
-
- // 2: swap
- if (pl[0] == ps[1] && pl[1] == ps[0]) {
- pl2 = pl + 2; // swap, skip two chars
- ps2 = ps + 2;
- while (*pl2 == *ps2) {
- if (*pl2 == NUL) { // reached the end
- return score + SCORE_SWAP;
- }
- ++pl2;
- ++ps2;
- }
- // 3: swap and swap again
- if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
- && STRCMP(pl2 + 2, ps2 + 2) == 0) {
- return score + SCORE_SWAP + SCORE_SWAP;
- }
-
- // 4: swap and substitute
- if (STRCMP(pl2 + 1, ps2 + 1) == 0) {
- return score + SCORE_SWAP + SCORE_SUBST;
- }
- }
-
- // 5: substitute
- pl2 = pl + 1;
- ps2 = ps + 1;
- while (*pl2 == *ps2) {
- if (*pl2 == NUL) { // reached the end
- return score + SCORE_SUBST;
- }
- ++pl2;
- ++ps2;
- }
-
- // 6: substitute and swap
- if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
- && STRCMP(pl2 + 2, ps2 + 2) == 0) {
- return score + SCORE_SUBST + SCORE_SWAP;
- }
-
- // 7: substitute and substitute
- if (STRCMP(pl2 + 1, ps2 + 1) == 0) {
- return score + SCORE_SUBST + SCORE_SUBST;
- }
-
- // 8: insert then delete
- pl2 = pl;
- ps2 = ps + 1;
- while (*pl2 == *ps2) {
- ++pl2;
- ++ps2;
- }
- if (STRCMP(pl2 + 1, ps2) == 0) {
- return score + SCORE_INS + SCORE_DEL;
- }
-
- // 9: delete then insert
- pl2 = pl + 1;
- ps2 = ps;
- while (*pl2 == *ps2) {
- ++pl2;
- ++ps2;
- }
- if (STRCMP(pl2, ps2 + 1) == 0) {
- return score + SCORE_INS + SCORE_DEL;
- }
-
- // Failed to compare.
- break;
- }
-
- return SCORE_MAXMAX;
-}
-
-// Compute the "edit distance" to turn "badword" into "goodword". The less
-// deletes/inserts/substitutes/swaps are required the lower the score.
-//
-// The algorithm is described by Du and Chang, 1992.
-// The implementation of the algorithm comes from Aspell editdist.cpp,
-// edit_distance(). It has been converted from C++ to C and modified to
-// support multi-byte characters.
-static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword)
-{
- int *cnt;
- int j, i;
- int t;
- int bc, gc;
- int pbc, pgc;
- int wbadword[MAXWLEN];
- int wgoodword[MAXWLEN];
-
- // Lengths with NUL.
- int badlen;
- int goodlen;
- {
- // Get the characters from the multi-byte strings and put them in an
- // int array for easy access.
- badlen = 0;
- for (const char_u *p = badword; *p != NUL;) {
- wbadword[badlen++] = mb_cptr2char_adv(&p);
- }
- wbadword[badlen++] = 0;
- goodlen = 0;
- for (const char_u *p = goodword; *p != NUL;) {
- wgoodword[goodlen++] = mb_cptr2char_adv(&p);
- }
- wgoodword[goodlen++] = 0;
- }
-
- // We use "cnt" as an array: CNT(badword_idx, goodword_idx).
-#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)]
- cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1));
-
- CNT(0, 0) = 0;
- for (j = 1; j <= goodlen; ++j) {
- CNT(0, j) = CNT(0, j - 1) + SCORE_INS;
- }
-
- for (i = 1; i <= badlen; ++i) {
- CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL;
- for (j = 1; j <= goodlen; j++) {
- bc = wbadword[i - 1];
- gc = wgoodword[j - 1];
- if (bc == gc) {
- CNT(i, j) = CNT(i - 1, j - 1);
- } else {
- // Use a better score when there is only a case difference.
- if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) {
- CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1);
- } else {
- // For a similar character use SCORE_SIMILAR.
- if (slang != NULL
- && slang->sl_has_map
- && similar_chars(slang, gc, bc)) {
- CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1);
- } else {
- CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1);
- }
- }
-
- if (i > 1 && j > 1) {
- pbc = wbadword[i - 2];
- pgc = wgoodword[j - 2];
- if (bc == pgc && pbc == gc) {
- t = SCORE_SWAP + CNT(i - 2, j - 2);
- if (t < CNT(i, j)) {
- CNT(i, j) = t;
- }
- }
- }
- t = SCORE_DEL + CNT(i - 1, j);
- if (t < CNT(i, j)) {
- CNT(i, j) = t;
- }
- t = SCORE_INS + CNT(i, j - 1);
- if (t < CNT(i, j)) {
- CNT(i, j) = t;
- }
- }
- }
- }
-
- i = CNT(badlen - 1, goodlen - 1);
- xfree(cnt);
- return i;
-}
-
-// Like spell_edit_score(), but with a limit on the score to make it faster.
-// May return SCORE_MAXMAX when the score is higher than "limit".
-//
-// This uses a stack for the edits still to be tried.
-// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support
-// for multi-byte characters.
-static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit)
-{
- return spell_edit_score_limit_w(slang, badword, goodword, limit);
-}
-
-// Multi-byte version of spell_edit_score_limit().
-// Keep it in sync with the above!
-static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit)
-{
- limitscore_T stack[10]; // allow for over 3 * 2 edits
- int stackidx;
- int bi, gi;
- int bi2, gi2;
- int bc, gc;
- int score;
- int score_off;
- int minscore;
- int round;
- int wbadword[MAXWLEN];
- int wgoodword[MAXWLEN];
-
- // Get the characters from the multi-byte strings and put them in an
- // int array for easy access.
- bi = 0;
- for (const char_u *p = badword; *p != NUL;) {
- wbadword[bi++] = mb_cptr2char_adv(&p);
- }
- wbadword[bi++] = 0;
- gi = 0;
- for (const char_u *p = goodword; *p != NUL;) {
- wgoodword[gi++] = mb_cptr2char_adv(&p);
- }
- wgoodword[gi++] = 0;
-
- // The idea is to go from start to end over the words. So long as
- // characters are equal just continue, this always gives the lowest score.
- // When there is a difference try several alternatives. Each alternative
- // increases "score" for the edit distance. Some of the alternatives are
- // pushed unto a stack and tried later, some are tried right away. At the
- // end of the word the score for one alternative is known. The lowest
- // possible score is stored in "minscore".
- stackidx = 0;
- bi = 0;
- gi = 0;
- score = 0;
- minscore = limit + 1;
-
- for (;;) {
- // Skip over an equal part, score remains the same.
- for (;;) {
- bc = wbadword[bi];
- gc = wgoodword[gi];
-
- if (bc != gc) { // stop at a char that's different
- break;
- }
- if (bc == NUL) { // both words end
- if (score < minscore) {
- minscore = score;
- }
- goto pop; // do next alternative
- }
- ++bi;
- ++gi;
- }
-
- if (gc == NUL) { // goodword ends, delete badword chars
- do {
- if ((score += SCORE_DEL) >= minscore) {
- goto pop; // do next alternative
- }
- } while (wbadword[++bi] != NUL);
- minscore = score;
- } else if (bc == NUL) { // badword ends, insert badword chars
- do {
- if ((score += SCORE_INS) >= minscore) {
- goto pop; // do next alternative
- }
- } while (wgoodword[++gi] != NUL);
- minscore = score;
- } else { // both words continue
- // If not close to the limit, perform a change. Only try changes
- // that may lead to a lower score than "minscore".
- // round 0: try deleting a char from badword
- // round 1: try inserting a char in badword
- for (round = 0; round <= 1; ++round) {
- score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
- if (score_off < minscore) {
- if (score_off + SCORE_EDIT_MIN >= minscore) {
- // Near the limit, rest of the words must match. We
- // can check that right now, no need to push an item
- // onto the stack.
- bi2 = bi + 1 - round;
- gi2 = gi + round;
- while (wgoodword[gi2] == wbadword[bi2]) {
- if (wgoodword[gi2] == NUL) {
- minscore = score_off;
- break;
- }
- ++bi2;
- ++gi2;
- }
- } else {
- // try deleting a character from badword later
- stack[stackidx].badi = bi + 1 - round;
- stack[stackidx].goodi = gi + round;
- stack[stackidx].score = score_off;
- ++stackidx;
- }
- }
- }
-
- if (score + SCORE_SWAP < minscore) {
- // If swapping two characters makes a match then the
- // substitution is more expensive, thus there is no need to
- // try both.
- if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) {
- // Swap two characters, that is: skip them.
- gi += 2;
- bi += 2;
- score += SCORE_SWAP;
- continue;
- }
- }
-
- // Substitute one character for another which is the same
- // thing as deleting a character from both goodword and badword.
- // Use a better score when there is only a case difference.
- if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) {
- score += SCORE_ICASE;
- } else {
- // For a similar character use SCORE_SIMILAR.
- if (slang != NULL
- && slang->sl_has_map
- && similar_chars(slang, gc, bc)) {
- score += SCORE_SIMILAR;
- } else {
- score += SCORE_SUBST;
- }
- }
-
- if (score < minscore) {
- // Do the substitution.
- ++gi;
- ++bi;
- continue;
- }
- }
-pop:
- // Get here to try the next alternative, pop it from the stack.
- if (stackidx == 0) { // stack is empty, finished
- break;
- }
-
- // pop an item from the stack
- --stackidx;
- gi = stack[stackidx].goodi;
- bi = stack[stackidx].badi;
- score = stack[stackidx].score;
- }
-
- // When the score goes over "limit" it may actually be much higher.
- // Return a very large number to avoid going below the limit when giving a
- // bonus.
- if (minscore > limit) {
- return SCORE_MAXMAX;
- }
- return minscore;
-}
-
// ":spellinfo"
void ex_spellinfo(exarg_T *eap)
{
@@ -7005,7 +3285,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg)
&& (pat == NULL || !ins_compl_interrupted())) {
if (curi[depth] > byts[arridx[depth]]) {
// Done all bytes at this node, go up one level.
- --depth;
+ depth--;
line_breakcheck();
ins_compl_check_keys(50, false);
} else {
@@ -7038,7 +3318,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg)
dump_word(slang, word, pat, dir,
dumpflags, flags, lnum);
if (pat == NULL) {
- ++lnum;
+ lnum++;
}
}
@@ -7063,7 +3343,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg)
assert(depth >= 0);
if (depth <= patlen
&& mb_strnicmp(word, pat, (size_t)depth) != 0) {
- --depth;
+ depth--;
}
}
}
@@ -7201,7 +3481,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi
len = byts[n];
if (curi[depth] > len) {
// Done all bytes at this node, go up one level.
- --depth;
+ depth--;
line_breakcheck();
} else {
// Do one more byte at this node.
@@ -7224,7 +3504,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi
(c & WF_RAREPFX) ? (flags | WF_RARE)
: flags, lnum);
if (lnum != 0) {
- ++lnum;
+ lnum++;
}
}
@@ -7240,7 +3520,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi
(c & WF_RAREPFX) ? (flags | WF_RARE)
: flags, lnum);
if (lnum != 0) {
- ++lnum;
+ lnum++;
}
}
}
@@ -7327,3 +3607,71 @@ int expand_spelling(linenr_T lnum, char_u *pat, char ***matchp)
*matchp = ga.ga_data;
return ga.ga_len;
}
+
+/// Return true if "val" is a valid 'spelllang' value.
+bool valid_spelllang(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return valid_name(val, ".-_,@");
+}
+
+/// Return true if "val" is a valid 'spellfile' value.
+bool valid_spellfile(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ for (const char_u *s = val; *s != NUL; s++) {
+ if (!vim_isfilec(*s) && *s != ',' && *s != ' ') {
+ return false;
+ }
+ }
+ return true;
+}
+
+char *did_set_spell_option(bool is_spellfile)
+{
+ char *errmsg = NULL;
+
+ if (is_spellfile) {
+ int l = (int)STRLEN(curwin->w_s->b_p_spf);
+ if (l > 0
+ && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) {
+ errmsg = e_invarg;
+ }
+ }
+
+ if (errmsg == NULL) {
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == curbuf && wp->w_p_spell) {
+ errmsg = did_set_spelllang(wp);
+ break;
+ }
+ }
+ }
+
+ return errmsg;
+}
+
+/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
+/// Return error message when failed, NULL when OK.
+char *compile_cap_prog(synblock_T *synblock)
+ FUNC_ATTR_NONNULL_ALL
+{
+ regprog_T *rp = synblock->b_cap_prog;
+ char_u *re;
+
+ if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) {
+ synblock->b_cap_prog = NULL;
+ } else {
+ // Prepend a ^ so that we only match at one column
+ re = concat_str((char_u *)"^", synblock->b_p_spc);
+ synblock->b_cap_prog = vim_regcomp((char *)re, RE_MAGIC);
+ xfree(re);
+ if (synblock->b_cap_prog == NULL) {
+ synblock->b_cap_prog = rp; // restore the previous program
+ return e_invarg;
+ }
+ }
+
+ vim_regfree(rp);
+ return NULL;
+}
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index 222d103f5d..61f722b9ee 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -35,6 +35,8 @@ typedef int idx_T;
#define WF_FIXCAP 0x40 // keep-case word, allcap not allowed
#define WF_KEEPCAP 0x80 // keep-case word
+#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP)
+
// for <flags2>, shifted up one byte to be used in wn_flags
#define WF_HAS_AFF 0x0100 // word includes affix
#define WF_NEEDCOMP 0x0200 // word only valid in compound
@@ -208,56 +210,6 @@ typedef struct {
char_u st_upper[256]; // chars: upper case
} spelltab_T;
-// For finding suggestions: At each node in the tree these states are tried:
-typedef enum {
- STATE_START = 0, // At start of node check for NUL bytes (goodword
- // ends); if badword ends there is a match, otherwise
- // try splitting word.
- STATE_NOPREFIX, // try without prefix
- STATE_SPLITUNDO, // Undo splitting.
- STATE_ENDNUL, // Past NUL bytes at start of the node.
- STATE_PLAIN, // Use each byte of the node.
- STATE_DEL, // Delete a byte from the bad word.
- STATE_INS_PREP, // Prepare for inserting bytes.
- STATE_INS, // Insert a byte in the bad word.
- STATE_SWAP, // Swap two bytes.
- STATE_UNSWAP, // Undo swap two characters.
- STATE_SWAP3, // Swap two characters over three.
- STATE_UNSWAP3, // Undo Swap two characters over three.
- STATE_UNROT3L, // Undo rotate three characters left
- STATE_UNROT3R, // Undo rotate three characters right
- STATE_REP_INI, // Prepare for using REP items.
- STATE_REP, // Use matching REP items from the .aff file.
- STATE_REP_UNDO, // Undo a REP item replacement.
- STATE_FINAL, // End of this node.
-} state_T;
-
-// Struct to keep the state at each level in suggest_try_change().
-typedef struct trystate_S {
- state_T ts_state; // state at this level, STATE_
- int ts_score; // score
- idx_T ts_arridx; // index in tree array, start of node
- int16_t ts_curi; // index in list of child nodes
- char_u ts_fidx; // index in fword[], case-folded bad word
- char_u ts_fidxtry; // ts_fidx at which bytes may be changed
- char_u ts_twordlen; // valid length of tword[]
- char_u ts_prefixdepth; // stack depth for end of prefix or
- // PFD_PREFIXTREE or PFD_NOPREFIX
- char_u ts_flags; // TSF_ flags
- char_u ts_tcharlen; // number of bytes in tword character
- char_u ts_tcharidx; // current byte index in tword character
- char_u ts_isdiff; // DIFF_ values
- char_u ts_fcharstart; // index in fword where badword char started
- char_u ts_prewordlen; // length of word in "preword[]"
- char_u ts_splitoff; // index in "tword" after last split
- char_u ts_splitfidx; // "ts_fidx" at word split
- char_u ts_complen; // nr of compound words used
- char_u ts_compsplit; // index for "compflags" where word was spit
- char_u ts_save_badflags; // su_badflags saved here
- char_u ts_delidx; // index in fword for char that was deleted,
- // valid when "ts_flags" has TSF_DIDDEL
-} trystate_T;
-
// Use our own character-case definitions, because the current locale may
// differ from what the .spl file uses.
// These must not be called with negative number!
@@ -290,4 +242,17 @@ typedef enum {
SPELL_ADD_RARE = 2,
} SpellAddType;
+typedef struct wordcount_S {
+ uint16_t wc_count; ///< nr of times word was seen
+ char_u wc_word[1]; ///< word, actually longer
+} wordcount_T;
+
+#define WC_KEY_OFF offsetof(wordcount_T, wc_word)
+#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF))
+#define MAXWORDCOUNT 0xffff
+
+// Remember what "z?" replaced.
+extern char_u *repl_from;
+extern char_u *repl_to;
+
#endif // NVIM_SPELL_DEFS_H
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 9f21e24d4c..be1373f617 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -230,9 +230,11 @@
#include <stdio.h>
#include <wctype.h>
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
#include "nvim/ex_cmds2.h"
#include "nvim/fileio.h"
#include "nvim/memline.h"
@@ -242,7 +244,7 @@
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/spell.h"
#include "nvim/spell_defs.h"
#include "nvim/spellfile.h"
@@ -576,11 +578,10 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile
char_u *p;
int n;
int len;
- char_u *save_sourcing_name = (char_u *)sourcing_name;
- linenr_T save_sourcing_lnum = sourcing_lnum;
slang_T *lp = NULL;
int c = 0;
int res;
+ bool did_estack_push = false;
fd = os_fopen((char *)fname, "r");
if (fd == NULL) {
@@ -612,8 +613,8 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile
}
// Set sourcing_name, so that error messages mention the file name.
- sourcing_name = (char *)fname;
- sourcing_lnum = 0;
+ estack_push(ETYPE_SPELL, (char *)fname, 0);
+ did_estack_push = true;
// <HEADER>: <fileID>
const int scms_ret = spell_check_magic_string(fd);
@@ -809,8 +810,9 @@ endOK:
if (fd != NULL) {
fclose(fd);
}
- sourcing_name = (char *)save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
+ if (did_estack_push) {
+ estack_pop();
+ }
return lp;
}
@@ -838,7 +840,7 @@ static void tree_count_words(char_u *byts, idx_T *idxs)
wordcount[depth - 1] += wordcount[depth];
}
- --depth;
+ depth--;
fast_breakcheck();
} else {
// Do one more byte at this node.
@@ -853,12 +855,12 @@ static void tree_count_words(char_u *byts, idx_T *idxs)
// Skip over any other NUL bytes (same word with different
// flags).
while (byts[n + 1] == 0) {
- ++n;
- ++curi[depth];
+ n++;
+ curi[depth]++;
}
} else {
// Normal char, go one level deeper to count the words.
- ++depth;
+ depth++;
arridx[depth] = idxs[n];
curi[depth] = 1;
wordcount[depth] = 0;
@@ -1369,21 +1371,21 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
if (todo < 2) {
return SP_FORMERROR; // need at least two bytes
}
- --todo;
+ todo--;
c = getc(fd); // <compmax>
if (c < 2) {
c = MAXWLEN;
}
slang->sl_compmax = c;
- --todo;
+ todo--;
c = getc(fd); // <compminlen>
if (c < 1) {
c = 0;
}
slang->sl_compminlen = c;
- --todo;
+ todo--;
c = getc(fd); // <compsylmax>
if (c < 1) {
c = MAXWLEN;
@@ -1394,9 +1396,9 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
if (c != 0) {
ungetc(c, fd); // be backwards compatible with Vim 7.0b
} else {
- --todo;
+ todo--;
c = getc(fd); // only use the lower byte for now
- --todo;
+ todo--;
slang->sl_compoptions = c;
gap = &slang->sl_comppat;
@@ -1408,8 +1410,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
ga_init(gap, sizeof(char_u *), c);
ga_grow(gap, c);
while (--c >= 0) {
- ((char_u **)(gap->ga_data))[gap->ga_len++] =
- read_cnt_string(fd, 1, &cnt);
+ ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt);
// <comppatlen> <comppattext>
if (cnt < 0) {
return cnt;
@@ -2064,7 +2065,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
// Read all the lines in the file one by one.
while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) {
line_breakcheck();
- ++lnum;
+ lnum++;
// Skip comment lines.
if (*rline == '#') {
@@ -2091,7 +2092,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
itemcnt = 0;
for (p = line;;) {
while (*p != NUL && *p <= ' ') { // skip white space and CR/NL
- ++p;
+ p++;
}
if (*p == NUL) {
break;
@@ -2103,11 +2104,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
// A few items have arbitrary text argument, don't split them.
if (itemcnt == 2 && spell_info_item(items[0])) {
while (*p >= ' ' || *p == TAB) { // skip until CR/NL
- ++p;
+ p++;
}
} else {
while (*p > ' ') { // skip until white space or CR/NL
- ++p;
+ p++;
}
}
if (*p == NUL) {
@@ -2300,18 +2301,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
// Only add the couple if it isn't already there.
for (i = 0; i < gap->ga_len - 1; i += 2) {
- if (STRCMP(((char_u **)(gap->ga_data))[i], items[1]) == 0
- && STRCMP(((char_u **)(gap->ga_data))[i + 1],
- items[2]) == 0) {
+ if (STRCMP(((char **)(gap->ga_data))[i], items[1]) == 0
+ && STRCMP(((char **)(gap->ga_data))[i + 1], items[2]) == 0) {
break;
}
}
if (i >= gap->ga_len) {
ga_grow(gap, 2);
- ((char_u **)(gap->ga_data))[gap->ga_len++]
- = getroom_save(spin, items[1]);
- ((char_u **)(gap->ga_data))[gap->ga_len++]
- = getroom_save(spin, items[2]);
+ ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[1]);
+ ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[2]);
}
} else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2)
&& syllable == NULL) {
@@ -2387,7 +2385,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
// Check for the "S" flag, which apparently means that another
// block with the same affix name is following.
if (itemcnt > lasti && STRCMP(items[lasti], "S") == 0) {
- ++lasti;
+ lasti++;
cur_aff->ah_follows = true;
} else {
cur_aff->ah_follows = false;
@@ -2809,7 +2807,7 @@ static void aff_process_flags(afffile_T *affile, affentry_T *entry)
}
}
if (affile->af_flagtype == AFT_NUM && *p == ',') {
- ++p;
+ p++;
}
}
if (*entry->ae_flags == NUL) {
@@ -2945,7 +2943,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla
*tp++ = (char_u)id;
}
if (aff->af_flagtype == AFT_NUM && *p == ',') {
- ++p;
+ p++;
}
}
}
@@ -2968,7 +2966,7 @@ static void check_renumber(spellinfo_T *spin)
// Returns true if flag "flag" appears in affix list "afflist".
static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag)
{
- char_u *p;
+ char *p;
unsigned n;
switch (flagtype) {
@@ -2977,7 +2975,7 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag)
case AFT_CAPLONG:
case AFT_LONG:
- for (p = afflist; *p != NUL;) {
+ for (p = (char *)afflist; *p != NUL;) {
n = (unsigned)mb_ptr2char_adv((const char_u **)&p);
if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z'))
&& *p != NUL) {
@@ -2990,8 +2988,8 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag)
break;
case AFT_NUM:
- for (p = afflist; *p != NUL;) {
- int digits = getdigits_int((char **)&p, true, 0);
+ for (p = (char *)afflist; *p != NUL;) {
+ int digits = getdigits_int(&p, true, 0);
assert(digits >= 0);
n = (unsigned int)digits;
if (n == 0) {
@@ -3072,7 +3070,7 @@ static void spell_free_aff(afffile_T *aff)
todo = (int)ht->ht_used;
for (hi = ht->ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
- --todo;
+ todo--;
ah = HI2AH(hi);
for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next) {
vim_regfree(ae->ae_prog);
@@ -3142,7 +3140,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
// the hashtable.
while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) {
line_breakcheck();
- ++lnum;
+ lnum++;
if (line[0] == '#' || line[0] == '/') {
continue; // comment line
}
@@ -3150,7 +3148,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
// the word is kept to allow multi-word terms like "et al.".
l = (int)STRLEN(line);
while (l > 0 && line[l - 1] <= ' ') {
- --l;
+ l--;
}
if (l == 0) {
continue; // empty line
@@ -3186,7 +3184,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
// Skip non-ASCII words when "spin->si_ascii" is true.
if (spin->si_ascii && has_non_ascii(w)) {
- ++non_ascii;
+ non_ascii++;
xfree(pc);
continue;
}
@@ -3227,7 +3225,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
smsg(_("First duplicate word in %s line %d: %s"),
fname, lnum, dw);
}
- ++duplicate;
+ duplicate++;
} else {
hash_add_item(&ht, hi, dw, hash);
}
@@ -3362,7 +3360,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist
}
}
if (affile->af_flagtype == AFT_NUM && *p == ',') {
- ++p;
+ p++;
}
}
@@ -3392,7 +3390,7 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl
}
}
if (affile->af_flagtype == AFT_NUM && *p == ',') {
- ++p;
+ p++;
}
}
@@ -3438,7 +3436,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff
todo = (int)ht->ht_used;
for (hi = ht->ht_array; todo > 0 && retval == OK; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
- --todo;
+ todo--;
ah = HI2AH(hi);
// Check that the affix combines, if required, and that the word
@@ -3686,7 +3684,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname)
// Read all the lines in the file one by one.
while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) {
line_breakcheck();
- ++lnum;
+ lnum++;
// Skip comment lines.
if (*rline == '#') {
@@ -3696,7 +3694,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname)
// Remove CR, LF and white space from the end.
l = (int)STRLEN(rline);
while (l > 0 && rline[l - 1] <= ' ') {
- --l;
+ l--;
}
if (l == 0) {
continue; // empty or blank line
@@ -3719,7 +3717,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname)
}
if (*line == '/') {
- ++line;
+ line++;
if (STRNCMP(line, "encoding=", 9) == 0) {
if (spin->si_conv.vc_type != CONV_NONE) {
smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"),
@@ -3802,13 +3800,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname)
fname, lnum, p);
break;
}
- ++p;
+ p++;
}
}
// Skip non-ASCII words when "spin->si_ascii" is true.
if (spin->si_ascii && has_non_ascii(line)) {
- ++non_ascii;
+ non_ascii++;
continue;
}
@@ -4146,8 +4144,8 @@ static wordnode_T *get_wordnode(spellinfo_T *spin)
} else {
n = spin->si_first_free;
spin->si_first_free = n->wn_child;
- memset(n, 0, sizeof(wordnode_T));
- --spin->si_free_count;
+ CLEAR_POINTER(n);
+ spin->si_free_count--;
}
#ifdef SPELL_PRINTTREE
if (n != NULL) {
@@ -4173,7 +4171,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
cnt += deref_wordnode(spin, np->wn_child);
}
free_wordnode(spin, np);
- ++cnt;
+ cnt++;
}
++cnt; // length field
}
@@ -4248,7 +4246,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo
// Note that with "child" we mean not just the node that is pointed to,
// but the whole list of siblings of which the child node is the first.
for (np = node; np != NULL && !got_int; np = np->wn_sibling) {
- ++len;
+ len++;
if ((child = np->wn_child) != NULL) {
// Compress the child first. This fills hashkey.
compressed += node_compress(spin, child, ht, tot);
@@ -4581,7 +4579,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname)
if (round == 2) { // <word>
fwv &= fwrite(hi->hi_key, l, 1, fd);
}
- --todo;
+ todo--;
}
}
if (round == 1) {
@@ -4644,8 +4642,8 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname)
size_t l = STRLEN(spin->si_compflags);
assert(spin->si_comppat.ga_len >= 0);
- for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; ++i) {
- l += STRLEN(((char_u **)(spin->si_comppat.ga_data))[i]) + 1;
+ for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) {
+ l += STRLEN(((char **)(spin->si_comppat.ga_data))[i]) + 1;
}
put_bytes(fd, l + 7, 4); // <sectionlen>
@@ -4655,8 +4653,8 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname)
putc(0, fd); // for Vim 7.0b compatibility
putc(spin->si_compoptions, fd); // <compoptions>
put_bytes(fd, (uintmax_t)spin->si_comppat.ga_len, 2); // <comppatcount>
- for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; ++i) {
- char_u *p = ((char_u **)(spin->si_comppat.ga_data))[i];
+ for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) {
+ char *p = ((char **)(spin->si_comppat.ga_data))[i];
assert(STRLEN(p) < INT_MAX);
putc((int)STRLEN(p), fd); // <comppatlen>
fwv &= fwrite(p, STRLEN(p), 1, fd); // <comppattext>
@@ -4782,7 +4780,7 @@ static int put_node(FILE *fd, wordnode_T *node, int idx, int regionmask, bool pr
// Count the number of siblings.
int siblingcount = 0;
for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
- ++siblingcount;
+ siblingcount++;
}
// Write the sibling count.
@@ -5012,7 +5010,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
wordcount[depth - 1] += wordcount[depth];
}
- --depth;
+ depth--;
line_breakcheck();
} else {
// Do one more byte at this node.
@@ -5033,8 +5031,8 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
return FAIL;
}
- ++words_done;
- ++wordcount[depth];
+ words_done++;
+ wordcount[depth]++;
// Reset the block count each time to avoid compression
// kicking in.
@@ -5282,7 +5280,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
bool error = false;
spellinfo_T spin;
- memset(&spin, 0, sizeof(spin));
+ CLEAR_FIELD(spin);
spin.si_verbose = !added_word;
spin.si_ascii = ascii;
spin.si_followup = true;
@@ -5815,7 +5813,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv)
size_t totlen = 2 + (size_t)gap->ga_len; // <prefcondcnt> and <condlen> bytes
for (int i = 0; i < gap->ga_len; i++) {
// <prefcond> : <condlen> <condstr>
- char_u *p = ((char_u **)gap->ga_data)[i];
+ char *p = ((char **)gap->ga_data)[i];
if (p != NULL) {
size_t len = STRLEN(p);
if (fd != NULL) {
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
new file mode 100644
index 0000000000..b4a9bed437
--- /dev/null
+++ b/src/nvim/spellsuggest.c
@@ -0,0 +1,3800 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// spellsuggest.c: functions for spelling suggestions
+
+#include "nvim/ascii.h"
+#include "nvim/change.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/eval.h"
+#include "nvim/fileio.h"
+#include "nvim/garray.h"
+#include "nvim/getchar.h"
+#include "nvim/hashtab.h"
+#include "nvim/input.h"
+#include "nvim/mbyte.h"
+#include "nvim/memline.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/input.h"
+#include "nvim/profile.h"
+#include "nvim/screen.h"
+#include "nvim/spell.h"
+#include "nvim/spell_defs.h"
+#include "nvim/spellfile.h"
+#include "nvim/spellsuggest.h"
+#include "nvim/strings.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+#include "nvim/vim.h"
+
+// Use this to adjust the score after finding suggestions, based on the
+// suggested word sounding like the bad word. This is much faster than doing
+// it for every possible suggestion.
+// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@"
+// vs "ht") and goes down in the list.
+// Used when 'spellsuggest' is set to "best".
+#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4)
+
+// Do the opposite: based on a maximum end score and a known sound score,
+// compute the maximum word score that can be used.
+#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3)
+
+// only used for su_badflags
+#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI
+
+/// Information used when looking for suggestions.
+typedef struct suginfo_S {
+ garray_T su_ga; ///< suggestions, contains "suggest_T"
+ int su_maxcount; ///< max. number of suggestions displayed
+ int su_maxscore; ///< maximum score for adding to su_ga
+ int su_sfmaxscore; ///< idem, for when doing soundfold words
+ garray_T su_sga; ///< like su_ga, sound-folded scoring
+ char_u *su_badptr; ///< start of bad word in line
+ int su_badlen; ///< length of detected bad word in line
+ int su_badflags; ///< caps flags for bad word
+ char_u su_badword[MAXWLEN]; ///< bad word truncated at su_badlen
+ char_u su_fbadword[MAXWLEN]; ///< su_badword case-folded
+ char_u su_sal_badword[MAXWLEN]; ///< su_badword soundfolded
+ hashtab_T su_banned; ///< table with banned words
+ slang_T *su_sallang; ///< default language for sound folding
+} suginfo_T;
+
+/// One word suggestion. Used in "si_ga".
+typedef struct {
+ char_u *st_word; ///< suggested word, allocated string
+ int st_wordlen; ///< STRLEN(st_word)
+ int st_orglen; ///< length of replaced text
+ int st_score; ///< lower is better
+ int st_altscore; ///< used when st_score compares equal
+ bool st_salscore; ///< st_score is for soundalike
+ bool st_had_bonus; ///< bonus already included in score
+ slang_T *st_slang; ///< language used for sound folding
+} suggest_T;
+
+#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i])
+
+// True if a word appears in the list of banned words.
+#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word)))
+
+// Number of suggestions kept when cleaning up. We need to keep more than
+// what is displayed, because when rescore_suggestions() is called the score
+// may change and wrong suggestions may be removed later.
+#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \
+ 130 ? 150 : (su)->su_maxcount + 20)
+
+// Threshold for sorting and cleaning up suggestions. Don't want to keep lots
+// of suggestions that are not going to be displayed.
+#define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50)
+
+// score for various changes
+#define SCORE_SPLIT 149 // split bad word
+#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS
+#define SCORE_ICASE 52 // slightly different case
+#define SCORE_REGION 200 // word is for different region
+#define SCORE_RARE 180 // rare word
+#define SCORE_SWAP 75 // swap two characters
+#define SCORE_SWAP3 110 // swap two characters in three
+#define SCORE_REP 65 // REP replacement
+#define SCORE_SUBST 93 // substitute a character
+#define SCORE_SIMILAR 33 // substitute a similar character
+#define SCORE_SUBCOMP 33 // substitute a composing character
+#define SCORE_DEL 94 // delete a character
+#define SCORE_DELDUP 66 // delete a duplicated character
+#define SCORE_DELCOMP 28 // delete a composing character
+#define SCORE_INS 96 // insert a character
+#define SCORE_INSDUP 67 // insert a duplicate character
+#define SCORE_INSCOMP 30 // insert a composing character
+#define SCORE_NONWORD 103 // change non-word to word char
+
+#define SCORE_FILE 30 // suggestion from a file
+#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower.
+ // 350 allows for about three changes.
+
+#define SCORE_COMMON1 30 // subtracted for words seen before
+#define SCORE_COMMON2 40 // subtracted for words often seen
+#define SCORE_COMMON3 50 // subtracted for words very often seen
+#define SCORE_THRES2 10 // word count threshold for COMMON2
+#define SCORE_THRES3 100 // word count threshold for COMMON3
+
+// When trying changed soundfold words it becomes slow when trying more than
+// two changes. With less than two changes it's slightly faster but we miss a
+// few good suggestions. In rare cases we need to try three of four changes.
+#define SCORE_SFMAX1 200 // maximum score for first try
+#define SCORE_SFMAX2 300 // maximum score for second try
+#define SCORE_SFMAX3 400 // maximum score for third try
+
+#define SCORE_BIG (SCORE_INS * 3) // big difference
+#define SCORE_MAXMAX 999999 // accept any score
+#define SCORE_LIMITMAX 350 // for spell_edit_score_limit()
+
+// for spell_edit_score_limit() we need to know the minimum value of
+// SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS
+#define SCORE_EDIT_MIN SCORE_SIMILAR
+
+/// For finding suggestions: At each node in the tree these states are tried:
+typedef enum {
+ STATE_START = 0, ///< At start of node check for NUL bytes (goodword
+ ///< ends); if badword ends there is a match, otherwise
+ ///< try splitting word.
+ STATE_NOPREFIX, ///< try without prefix
+ STATE_SPLITUNDO, ///< Undo splitting.
+ STATE_ENDNUL, ///< Past NUL bytes at start of the node.
+ STATE_PLAIN, ///< Use each byte of the node.
+ STATE_DEL, ///< Delete a byte from the bad word.
+ STATE_INS_PREP, ///< Prepare for inserting bytes.
+ STATE_INS, ///< Insert a byte in the bad word.
+ STATE_SWAP, ///< Swap two bytes.
+ STATE_UNSWAP, ///< Undo swap two characters.
+ STATE_SWAP3, ///< Swap two characters over three.
+ STATE_UNSWAP3, ///< Undo Swap two characters over three.
+ STATE_UNROT3L, ///< Undo rotate three characters left
+ STATE_UNROT3R, ///< Undo rotate three characters right
+ STATE_REP_INI, ///< Prepare for using REP items.
+ STATE_REP, ///< Use matching REP items from the .aff file.
+ STATE_REP_UNDO, ///< Undo a REP item replacement.
+ STATE_FINAL, ///< End of this node.
+} state_T;
+
+/// Struct to keep the state at each level in suggest_try_change().
+typedef struct trystate_S {
+ state_T ts_state; ///< state at this level, STATE_
+ int ts_score; ///< score
+ idx_T ts_arridx; ///< index in tree array, start of node
+ int16_t ts_curi; ///< index in list of child nodes
+ char_u ts_fidx; ///< index in fword[], case-folded bad word
+ char_u ts_fidxtry; ///< ts_fidx at which bytes may be changed
+ char_u ts_twordlen; ///< valid length of tword[]
+ char_u ts_prefixdepth; ///< stack depth for end of prefix or
+ ///< PFD_PREFIXTREE or PFD_NOPREFIX
+ char_u ts_flags; ///< TSF_ flags
+ char_u ts_tcharlen; ///< number of bytes in tword character
+ char_u ts_tcharidx; ///< current byte index in tword character
+ char_u ts_isdiff; ///< DIFF_ values
+ char_u ts_fcharstart; ///< index in fword where badword char started
+ char_u ts_prewordlen; ///< length of word in "preword[]"
+ char_u ts_splitoff; ///< index in "tword" after last split
+ char_u ts_splitfidx; ///< "ts_fidx" at word split
+ char_u ts_complen; ///< nr of compound words used
+ char_u ts_compsplit; ///< index for "compflags" where word was spit
+ char_u ts_save_badflags; ///< su_badflags saved here
+ char_u ts_delidx; ///< index in fword for char that was deleted,
+ ///< valid when "ts_flags" has TSF_DIDDEL
+} trystate_T;
+
+// values for ts_isdiff
+#define DIFF_NONE 0 // no different byte (yet)
+#define DIFF_YES 1 // different byte found
+#define DIFF_INSERT 2 // inserting character
+
+// values for ts_flags
+#define TSF_PREFIXOK 1 // already checked that prefix is OK
+#define TSF_DIDSPLIT 2 // tried split at this point
+#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index
+
+// special values ts_prefixdepth
+#define PFD_NOPREFIX 0xff // not using prefixes
+#define PFD_PREFIXTREE 0xfe // walking through the prefix tree
+#define PFD_NOTSPECIAL 0xfd // highest value that's not special
+
+static long spell_suggest_timeout = 5000;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "spellsuggest.c.generated.h"
+#endif
+
+/// Returns true when the sequence of flags in "compflags" plus "flag" can
+/// possibly form a valid compounded word. This also checks the COMPOUNDRULE
+/// lines if they don't contain wildcards.
+static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag)
+{
+ // If the flag doesn't appear in sl_compstartflags or sl_compallflags
+ // then it can't possibly compound.
+ if (!byte_in_str(sp->ts_complen == sp->ts_compsplit
+ ? slang->sl_compstartflags : slang->sl_compallflags, flag)) {
+ return false;
+ }
+
+ // If there are no wildcards, we can check if the flags collected so far
+ // possibly can form a match with COMPOUNDRULE patterns. This only
+ // makes sense when we have two or more words.
+ if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) {
+ compflags[sp->ts_complen] = (char_u)flag;
+ compflags[sp->ts_complen + 1] = NUL;
+ bool v = match_compoundrule(slang, compflags + sp->ts_compsplit);
+ compflags[sp->ts_complen] = NUL;
+ return v;
+ }
+
+ return true;
+}
+
+/// Adjust the score of common words.
+///
+/// @param split word was split, less bonus
+static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split)
+{
+ wordcount_T *wc;
+ int bonus;
+ int newscore;
+
+ hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word);
+ if (!HASHITEM_EMPTY(hi)) {
+ wc = HI2WC(hi);
+ if (wc->wc_count < SCORE_THRES2) {
+ bonus = SCORE_COMMON1;
+ } else if (wc->wc_count < SCORE_THRES3) {
+ bonus = SCORE_COMMON2;
+ } else {
+ bonus = SCORE_COMMON3;
+ }
+ if (split) {
+ newscore = score - bonus / 2;
+ } else {
+ newscore = score - bonus;
+ }
+ if (newscore < 0) {
+ return 0;
+ }
+ return newscore;
+ }
+ return score;
+}
+
+/// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a
+/// capital. So that make_case_word() can turn WOrd into Word.
+/// Add ALLCAP for "WOrD".
+static int badword_captype(char_u *word, char_u *end)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int flags = captype(word, end);
+ int c;
+ int l, u;
+ bool first;
+ char_u *p;
+
+ if (flags & WF_KEEPCAP) {
+ // Count the number of UPPER and lower case letters.
+ l = u = 0;
+ first = false;
+ for (p = word; p < end; MB_PTR_ADV(p)) {
+ c = utf_ptr2char((char *)p);
+ if (SPELL_ISUPPER(c)) {
+ u++;
+ if (p == word) {
+ first = true;
+ }
+ } else {
+ l++;
+ }
+ }
+
+ // If there are more UPPER than lower case letters suggest an
+ // ALLCAP word. Otherwise, if the first letter is UPPER then
+ // suggest ONECAP. Exception: "ALl" most likely should be "All",
+ // require three upper case letters.
+ if (u > l && u > 2) {
+ flags |= WF_ALLCAP;
+ } else if (first) {
+ flags |= WF_ONECAP;
+ }
+
+ if (u >= 2 && l >= 2) { // maCARONI maCAroni
+ flags |= WF_MIXCAP;
+ }
+ }
+ return flags;
+}
+
+/// Opposite of offset2bytes().
+/// "pp" points to the bytes and is advanced over it.
+///
+/// @return the offset.
+static int bytes2offset(char_u **pp)
+{
+ char_u *p = *pp;
+ int nr;
+ int c;
+
+ c = *p++;
+ if ((c & 0x80) == 0x00) { // 1 byte
+ nr = c - 1;
+ } else if ((c & 0xc0) == 0x80) { // 2 bytes
+ nr = (c & 0x3f) - 1;
+ nr = nr * 255 + (*p++ - 1);
+ } else if ((c & 0xe0) == 0xc0) { // 3 bytes
+ nr = (c & 0x1f) - 1;
+ nr = nr * 255 + (*p++ - 1);
+ nr = nr * 255 + (*p++ - 1);
+ } else { // 4 bytes
+ nr = (c & 0x0f) - 1;
+ nr = nr * 255 + (*p++ - 1);
+ nr = nr * 255 + (*p++ - 1);
+ nr = nr * 255 + (*p++ - 1);
+ }
+
+ *pp = p;
+ return nr;
+}
+
+// values for sps_flags
+#define SPS_BEST 1
+#define SPS_FAST 2
+#define SPS_DOUBLE 4
+
+static int sps_flags = SPS_BEST; ///< flags from 'spellsuggest'
+static int sps_limit = 9999; ///< max nr of suggestions given
+
+/// Check the 'spellsuggest' option. Return FAIL if it's wrong.
+/// Sets "sps_flags" and "sps_limit".
+int spell_check_sps(void)
+{
+ char *p;
+ char *s;
+ char_u buf[MAXPATHL];
+ int f;
+
+ sps_flags = 0;
+ sps_limit = 9999;
+
+ for (p = (char *)p_sps; *p != NUL;) {
+ copy_option_part(&p, (char *)buf, MAXPATHL, ",");
+
+ f = 0;
+ if (ascii_isdigit(*buf)) {
+ s = (char *)buf;
+ sps_limit = getdigits_int(&s, true, 0);
+ if (*s != NUL && !ascii_isdigit(*s)) {
+ f = -1;
+ }
+ } else if (STRCMP(buf, "best") == 0) {
+ f = SPS_BEST;
+ } else if (STRCMP(buf, "fast") == 0) {
+ f = SPS_FAST;
+ } else if (STRCMP(buf, "double") == 0) {
+ f = SPS_DOUBLE;
+ } else if (STRNCMP(buf, "expr:", 5) != 0
+ && STRNCMP(buf, "file:", 5) != 0
+ && (STRNCMP(buf, "timeout:", 8) != 0
+ || (!ascii_isdigit(buf[8])
+ && !(buf[8] == '-' && ascii_isdigit(buf[9]))))) {
+ f = -1;
+ }
+
+ if (f == -1 || (sps_flags != 0 && f != 0)) {
+ sps_flags = SPS_BEST;
+ sps_limit = 9999;
+ return FAIL;
+ }
+ if (f != 0) {
+ sps_flags = f;
+ }
+ }
+
+ if (sps_flags == 0) {
+ sps_flags = SPS_BEST;
+ }
+
+ return OK;
+}
+
+/// "z=": Find badly spelled word under or after the cursor.
+/// Give suggestions for the properly spelled word.
+/// In Visual mode use the highlighted word as the bad word.
+/// When "count" is non-zero use that suggestion.
+void spell_suggest(int count)
+{
+ char_u *line;
+ pos_T prev_cursor = curwin->w_cursor;
+ char_u wcopy[MAXWLEN + 2];
+ char_u *p;
+ int c;
+ suginfo_T sug;
+ suggest_T *stp;
+ int mouse_used;
+ int need_cap;
+ int limit;
+ int selected = count;
+ int badlen = 0;
+ int msg_scroll_save = msg_scroll;
+ const int wo_spell_save = curwin->w_p_spell;
+
+ if (!curwin->w_p_spell) {
+ did_set_spelllang(curwin);
+ curwin->w_p_spell = true;
+ }
+
+ if (*curwin->w_s->b_p_spl == NUL) {
+ emsg(_(e_no_spell));
+ return;
+ }
+
+ if (VIsual_active) {
+ // Use the Visually selected text as the bad word. But reject
+ // a multi-line selection.
+ if (curwin->w_cursor.lnum != VIsual.lnum) {
+ vim_beep(BO_SPELL);
+ return;
+ }
+ badlen = (int)curwin->w_cursor.col - (int)VIsual.col;
+ if (badlen < 0) {
+ badlen = -badlen;
+ } else {
+ curwin->w_cursor.col = VIsual.col;
+ }
+ badlen++;
+ end_visual_mode();
+ // Find the start of the badly spelled word.
+ } else if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0
+ || curwin->w_cursor.col > prev_cursor.col) {
+ // No bad word or it starts after the cursor: use the word under the
+ // cursor.
+ curwin->w_cursor = prev_cursor;
+ line = get_cursor_line_ptr();
+ p = line + curwin->w_cursor.col;
+ // Backup to before start of word.
+ while (p > line && spell_iswordp_nmw(p, curwin)) {
+ MB_PTR_BACK(line, p);
+ }
+ // Forward to start of word.
+ while (*p != NUL && !spell_iswordp_nmw(p, curwin)) {
+ MB_PTR_ADV(p);
+ }
+
+ if (!spell_iswordp_nmw(p, curwin)) { // No word found.
+ beep_flush();
+ return;
+ }
+ curwin->w_cursor.col = (colnr_T)(p - line);
+ }
+
+ // Get the word and its length.
+
+ // Figure out if the word should be capitalised.
+ need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col);
+
+ // Make a copy of current line since autocommands may free the line.
+ line = vim_strsave(get_cursor_line_ptr());
+ spell_suggest_timeout = 5000;
+
+ // Get the list of suggestions. Limit to 'lines' - 2 or the number in
+ // 'spellsuggest', whatever is smaller.
+ if (sps_limit > Rows - 2) {
+ limit = Rows - 2;
+ } else {
+ limit = sps_limit;
+ }
+ spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
+ true, need_cap, true);
+
+ if (GA_EMPTY(&sug.su_ga)) {
+ msg(_("Sorry, no suggestions"));
+ } else if (count > 0) {
+ if (count > sug.su_ga.ga_len) {
+ smsg(_("Sorry, only %" PRId64 " suggestions"),
+ (int64_t)sug.su_ga.ga_len);
+ }
+ } else {
+ // When 'rightleft' is set the list is drawn right-left.
+ cmdmsg_rl = curwin->w_p_rl;
+ if (cmdmsg_rl) {
+ msg_col = Columns - 1;
+ }
+
+ // List the suggestions.
+ msg_start();
+ msg_row = Rows - 1; // for when 'cmdheight' > 1
+ lines_left = Rows; // avoid more prompt
+ vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"),
+ sug.su_badlen, sug.su_badptr);
+ if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) {
+ // And now the rabbit from the high hat: Avoid showing the
+ // untranslated message rightleft.
+ vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC",
+ sug.su_badlen, sug.su_badptr);
+ }
+ msg_puts((const char *)IObuff);
+ msg_clr_eos();
+ msg_putchar('\n');
+
+ msg_scroll = true;
+ for (int i = 0; i < sug.su_ga.ga_len; i++) {
+ stp = &SUG(sug.su_ga, i);
+
+ // The suggested word may replace only part of the bad word, add
+ // the not replaced part. But only when it's not getting too long.
+ STRLCPY(wcopy, stp->st_word, MAXWLEN + 1);
+ int el = sug.su_badlen - stp->st_orglen;
+ if (el > 0 && stp->st_wordlen + el <= MAXWLEN) {
+ STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1);
+ }
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1);
+ if (cmdmsg_rl) {
+ rl_mirror(IObuff);
+ }
+ msg_puts((const char *)IObuff);
+
+ vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy);
+ msg_puts((const char *)IObuff);
+
+ // The word may replace more than "su_badlen".
+ if (sug.su_badlen < stp->st_orglen) {
+ vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""),
+ stp->st_orglen, sug.su_badptr);
+ msg_puts((const char *)IObuff);
+ }
+
+ if (p_verbose > 0) {
+ // Add the score.
+ if (sps_flags & (SPS_DOUBLE | SPS_BEST)) {
+ vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)",
+ stp->st_salscore ? "s " : "",
+ stp->st_score, stp->st_altscore);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, " (%d)",
+ stp->st_score);
+ }
+ if (cmdmsg_rl) {
+ // Mirror the numbers, but keep the leading space.
+ rl_mirror(IObuff + 1);
+ }
+ msg_advance(30);
+ msg_puts((const char *)IObuff);
+ }
+ msg_putchar('\n');
+ }
+
+ cmdmsg_rl = false;
+ msg_col = 0;
+ // Ask for choice.
+ selected = prompt_for_number(&mouse_used);
+
+ if (ui_has(kUIMessages)) {
+ ui_call_msg_clear();
+ }
+
+ if (mouse_used) {
+ selected -= lines_left;
+ }
+ lines_left = Rows; // avoid more prompt
+ // don't delay for 'smd' in normal_cmd()
+ msg_scroll = msg_scroll_save;
+ }
+
+ if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) {
+ // Save the from and to text for :spellrepall.
+ XFREE_CLEAR(repl_from);
+ XFREE_CLEAR(repl_to);
+
+ stp = &SUG(sug.su_ga, selected - 1);
+ if (sug.su_badlen > stp->st_orglen) {
+ // Replacing less than "su_badlen", append the remainder to
+ // repl_to.
+ repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen);
+ vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word,
+ sug.su_badlen - stp->st_orglen,
+ sug.su_badptr + stp->st_orglen);
+ repl_to = vim_strsave(IObuff);
+ } else {
+ // Replacing su_badlen or more, use the whole word.
+ repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen);
+ repl_to = vim_strsave(stp->st_word);
+ }
+
+ // Replace the word.
+ p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1);
+ c = (int)(sug.su_badptr - line);
+ memmove(p, line, (size_t)c);
+ STRCPY(p + c, stp->st_word);
+ STRCAT(p, sug.su_badptr + stp->st_orglen);
+
+ // For redo we use a change-word command.
+ ResetRedobuff();
+ AppendToRedobuff("ciw");
+ AppendToRedobuffLit((char *)p + c,
+ stp->st_wordlen + sug.su_badlen - stp->st_orglen);
+ AppendCharToRedobuff(ESC);
+
+ // "p" may be freed here
+ ml_replace(curwin->w_cursor.lnum, (char *)p, false);
+ curwin->w_cursor.col = c;
+
+ inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen);
+ } else {
+ curwin->w_cursor = prev_cursor;
+ }
+
+ spell_find_cleanup(&sug);
+ xfree(line);
+ curwin->w_p_spell = wo_spell_save;
+}
+
+/// Find spell suggestions for "word". Return them in the growarray "*gap" as
+/// a list of allocated strings.
+///
+/// @param maxcount maximum nr of suggestions
+/// @param need_cap 'spellcapcheck' matched
+void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive)
+{
+ suginfo_T sug;
+ suggest_T *stp;
+ char_u *wcopy;
+
+ spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive);
+
+ // Make room in "gap".
+ ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1);
+ ga_grow(gap, sug.su_ga.ga_len);
+ for (int i = 0; i < sug.su_ga.ga_len; i++) {
+ stp = &SUG(sug.su_ga, i);
+
+ // The suggested word may replace only part of "word", add the not
+ // replaced part.
+ wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1);
+ STRCPY(wcopy, stp->st_word);
+ STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen);
+ ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy;
+ }
+
+ spell_find_cleanup(&sug);
+}
+
+/// Find spell suggestions for the word at the start of "badptr".
+/// Return the suggestions in "su->su_ga".
+/// The maximum number of suggestions is "maxcount".
+/// Note: does use info for the current window.
+/// This is based on the mechanisms of Aspell, but completely reimplemented.
+///
+/// @param badlen length of bad word or 0 if unknown
+/// @param banbadword don't include badword in suggestions
+/// @param need_cap word should start with capital
+static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount,
+ bool banbadword, bool need_cap, bool interactive)
+{
+ hlf_T attr = HLF_COUNT;
+ char_u buf[MAXPATHL];
+ char *p;
+ bool do_combine = false;
+ char_u *sps_copy;
+ static bool expr_busy = false;
+ int c;
+ langp_T *lp;
+ bool did_intern = false;
+
+ // Set the info in "*su".
+ CLEAR_POINTER(su);
+ ga_init(&su->su_ga, (int)sizeof(suggest_T), 10);
+ ga_init(&su->su_sga, (int)sizeof(suggest_T), 10);
+ if (*badptr == NUL) {
+ return;
+ }
+ hash_init(&su->su_banned);
+
+ su->su_badptr = badptr;
+ if (badlen != 0) {
+ su->su_badlen = badlen;
+ } else {
+ size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false);
+ assert(tmplen <= INT_MAX);
+ su->su_badlen = (int)tmplen;
+ }
+ su->su_maxcount = maxcount;
+ su->su_maxscore = SCORE_MAXINIT;
+
+ if (su->su_badlen >= MAXWLEN) {
+ su->su_badlen = MAXWLEN - 1; // just in case
+ }
+ STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1);
+ (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword,
+ MAXWLEN);
+
+ // TODO(vim): make this work if the case-folded text is longer than the
+ // original text. Currently an illegal byte causes wrong pointer
+ // computations.
+ su->su_fbadword[su->su_badlen] = NUL;
+
+ // get caps flags for bad word
+ su->su_badflags = badword_captype(su->su_badptr,
+ su->su_badptr + su->su_badlen);
+ if (need_cap) {
+ su->su_badflags |= WF_ONECAP;
+ }
+
+ // Find the default language for sound folding. We simply use the first
+ // one in 'spelllang' that supports sound folding. That's good for when
+ // using multiple files for one language, it's not that bad when mixing
+ // languages (e.g., "pl,en").
+ for (int i = 0; i < curbuf->b_s.b_langp.ga_len; i++) {
+ lp = LANGP_ENTRY(curbuf->b_s.b_langp, i);
+ if (lp->lp_sallang != NULL) {
+ su->su_sallang = lp->lp_sallang;
+ break;
+ }
+ }
+
+ // Soundfold the bad word with the default sound folding, so that we don't
+ // have to do this many times.
+ if (su->su_sallang != NULL) {
+ spell_soundfold(su->su_sallang, su->su_fbadword, true,
+ su->su_sal_badword);
+ }
+
+ // If the word is not capitalised and spell_check() doesn't consider the
+ // word to be bad then it might need to be capitalised. Add a suggestion
+ // for that.
+ c = utf_ptr2char((char *)su->su_badptr);
+ if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) {
+ make_case_word(su->su_badword, buf, WF_ONECAP);
+ add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE,
+ 0, true, su->su_sallang, false);
+ }
+
+ // Ban the bad word itself. It may appear in another region.
+ if (banbadword) {
+ add_banned(su, su->su_badword);
+ }
+
+ // Make a copy of 'spellsuggest', because the expression may change it.
+ sps_copy = vim_strsave(p_sps);
+
+ // Loop over the items in 'spellsuggest'.
+ for (p = (char *)sps_copy; *p != NUL;) {
+ copy_option_part(&p, (char *)buf, MAXPATHL, ",");
+
+ if (STRNCMP(buf, "expr:", 5) == 0) {
+ // Evaluate an expression. Skip this when called recursively,
+ // when using spellsuggest() in the expression.
+ if (!expr_busy) {
+ expr_busy = true;
+ spell_suggest_expr(su, buf + 5);
+ expr_busy = false;
+ }
+ } else if (STRNCMP(buf, "file:", 5) == 0) {
+ // Use list of suggestions in a file.
+ spell_suggest_file(su, buf + 5);
+ } else if (STRNCMP(buf, "timeout:", 8) == 0) {
+ // Limit the time searching for suggestions.
+ spell_suggest_timeout = atol((char *)buf + 8);
+ } else if (!did_intern) {
+ // Use internal method once.
+ spell_suggest_intern(su, interactive);
+ if (sps_flags & SPS_DOUBLE) {
+ do_combine = true;
+ }
+ did_intern = true;
+ }
+ }
+
+ xfree(sps_copy);
+
+ if (do_combine) {
+ // Combine the two list of suggestions. This must be done last,
+ // because sorting changes the order again.
+ score_combine(su);
+ }
+}
+
+/// Find suggestions by evaluating expression "expr".
+static void spell_suggest_expr(suginfo_T *su, char_u *expr)
+{
+ int score;
+ const char *p;
+
+ // The work is split up in a few parts to avoid having to export
+ // suginfo_T.
+ // First evaluate the expression and get the resulting list.
+ list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr);
+ if (list != NULL) {
+ // Loop over the items in the list.
+ TV_LIST_ITER(list, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
+ // Get the word and the score from the items.
+ score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p);
+ if (score >= 0 && score <= su->su_maxscore) {
+ add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen,
+ score, 0, true, su->su_sallang, false);
+ }
+ }
+ });
+ tv_list_unref(list);
+ }
+
+ // Remove bogus suggestions, sort and truncate at "maxcount".
+ check_suggestions(su, &su->su_ga);
+ (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+}
+
+/// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'.
+static void spell_suggest_file(suginfo_T *su, char_u *fname)
+{
+ FILE *fd;
+ char_u line[MAXWLEN * 2];
+ char_u *p;
+ int len;
+ char_u cword[MAXWLEN];
+
+ // Open the file.
+ fd = os_fopen((char *)fname, "r");
+ if (fd == NULL) {
+ semsg(_(e_notopen), fname);
+ return;
+ }
+
+ // Read it line by line.
+ while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) {
+ line_breakcheck();
+
+ p = (char_u *)vim_strchr((char *)line, '/');
+ if (p == NULL) {
+ continue; // No Tab found, just skip the line.
+ }
+ *p++ = NUL;
+ if (STRICMP(su->su_badword, line) == 0) {
+ // Match! Isolate the good word, until CR or NL.
+ for (len = 0; p[len] >= ' '; len++) {}
+ p[len] = NUL;
+
+ // If the suggestion doesn't have specific case duplicate the case
+ // of the bad word.
+ if (captype(p, NULL) == 0) {
+ make_case_word(p, cword, su->su_badflags);
+ p = cword;
+ }
+
+ add_suggestion(su, &su->su_ga, p, su->su_badlen,
+ SCORE_FILE, 0, true, su->su_sallang, false);
+ }
+ }
+
+ fclose(fd);
+
+ // Remove bogus suggestions, sort and truncate at "maxcount".
+ check_suggestions(su, &su->su_ga);
+ (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+}
+
+/// Find suggestions for the internal method indicated by "sps_flags".
+static void spell_suggest_intern(suginfo_T *su, bool interactive)
+{
+ // Load the .sug file(s) that are available and not done yet.
+ suggest_load_files();
+
+ // 1. Try special cases, such as repeating a word: "the the" -> "the".
+ //
+ // Set a maximum score to limit the combination of operations that is
+ // tried.
+ suggest_try_special(su);
+
+ // 2. Try inserting/deleting/swapping/changing a letter, use REP entries
+ // from the .aff file and inserting a space (split the word).
+ suggest_try_change(su);
+
+ // For the resulting top-scorers compute the sound-a-like score.
+ if (sps_flags & SPS_DOUBLE) {
+ score_comp_sal(su);
+ }
+
+ // 3. Try finding sound-a-like words.
+ if ((sps_flags & SPS_FAST) == 0) {
+ if (sps_flags & SPS_BEST) {
+ // Adjust the word score for the suggestions found so far for how
+ // they sounds like.
+ rescore_suggestions(su);
+ }
+
+ // While going through the soundfold tree "su_maxscore" is the score
+ // for the soundfold word, limits the changes that are being tried,
+ // and "su_sfmaxscore" the rescored score, which is set by
+ // cleanup_suggestions().
+ // First find words with a small edit distance, because this is much
+ // faster and often already finds the top-N suggestions. If we didn't
+ // find many suggestions try again with a higher edit distance.
+ // "sl_sounddone" is used to avoid doing the same word twice.
+ suggest_try_soundalike_prep();
+ su->su_maxscore = SCORE_SFMAX1;
+ su->su_sfmaxscore = SCORE_MAXINIT * 3;
+ suggest_try_soundalike(su);
+ if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) {
+ // We didn't find enough matches, try again, allowing more
+ // changes to the soundfold word.
+ su->su_maxscore = SCORE_SFMAX2;
+ suggest_try_soundalike(su);
+ if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) {
+ // Still didn't find enough matches, try again, allowing even
+ // more changes to the soundfold word.
+ su->su_maxscore = SCORE_SFMAX3;
+ suggest_try_soundalike(su);
+ }
+ }
+ su->su_maxscore = su->su_sfmaxscore;
+ suggest_try_soundalike_finish();
+ }
+
+ // When CTRL-C was hit while searching do show the results. Only clear
+ // got_int when using a command, not for spellsuggest().
+ os_breakcheck();
+ if (interactive && got_int) {
+ (void)vgetc();
+ got_int = false;
+ }
+
+ if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) {
+ if (sps_flags & SPS_BEST) {
+ // Adjust the word score for how it sounds like.
+ rescore_suggestions(su);
+ }
+
+ // Remove bogus suggestions, sort and truncate at "maxcount".
+ check_suggestions(su, &su->su_ga);
+ (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+ }
+}
+
+/// Free the info put in "*su" by spell_find_suggest().
+static void spell_find_cleanup(suginfo_T *su)
+{
+#define FREE_SUG_WORD(sug) xfree((sug)->st_word)
+ // Free the suggestions.
+ GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD);
+ GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD);
+
+ // Free the banned words.
+ hash_clear_all(&su->su_banned, 0);
+}
+
+/// Try finding suggestions by recognizing specific situations.
+static void suggest_try_special(suginfo_T *su)
+{
+ int c;
+ char_u word[MAXWLEN];
+
+ // Recognize a word that is repeated: "the the".
+ char_u *p = skiptowhite(su->su_fbadword);
+ size_t len = (size_t)(p - su->su_fbadword);
+ p = (char_u *)skipwhite((char *)p);
+ if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) {
+ // Include badflags: if the badword is onecap or allcap
+ // use that for the goodword too: "The the" -> "The".
+ c = su->su_fbadword[len];
+ su->su_fbadword[len] = NUL;
+ make_case_word(su->su_fbadword, word, su->su_badflags);
+ su->su_fbadword[len] = (char_u)c;
+
+ // Give a soundalike score of 0, compute the score as if deleting one
+ // character.
+ add_suggestion(su, &su->su_ga, word, su->su_badlen,
+ RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false);
+ }
+}
+
+// Measure how much time is spent in each state.
+// Output is dumped in "suggestprof".
+
+#ifdef SUGGEST_PROFILE
+proftime_T current;
+proftime_T total;
+proftime_T times[STATE_FINAL + 1];
+long counts[STATE_FINAL + 1];
+
+static void prof_init(void)
+{
+ for (int i = 0; i <= STATE_FINAL; i++) {
+ profile_zero(&times[i]);
+ counts[i] = 0;
+ }
+ profile_start(&current);
+ profile_start(&total);
+}
+
+/// call before changing state
+static void prof_store(state_T state)
+{
+ profile_end(&current);
+ profile_add(&times[state], &current);
+ counts[state]++;
+ profile_start(&current);
+}
+# 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(&times[i]), counts[i]);
+ }
+ fclose(fd);
+}
+#else
+# define PROF_STORE(state)
+#endif
+
+/// Try finding suggestions by adding/removing/swapping letters.
+static void suggest_try_change(suginfo_T *su)
+{
+ char_u fword[MAXWLEN]; // copy of the bad word, case-folded
+ int n;
+ char_u *p;
+ langp_T *lp;
+
+ // We make a copy of the case-folded bad word, so that we can modify it
+ // to find matches (esp. REP items). Append some more text, changing
+ // chars after the bad word may help.
+ STRCPY(fword, su->su_fbadword);
+ n = (int)STRLEN(fword);
+ p = su->su_badptr + su->su_badlen;
+ (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n);
+
+ // Make sure the resulting text is not longer than the original text.
+ n = (int)STRLEN(su->su_badptr);
+ if (n < MAXWLEN) {
+ fword[n] = NUL;
+ }
+
+ for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
+ lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+
+ // If reloading a spell file fails it's still in the list but
+ // everything has been cleared.
+ if (lp->lp_slang->sl_fbyts == NULL) {
+ continue;
+ }
+
+ // Try it for this language. Will add possible suggestions.
+#ifdef SUGGEST_PROFILE
+ prof_init();
+#endif
+ suggest_trie_walk(su, lp, fword, false);
+#ifdef SUGGEST_PROFILE
+ prof_report("try_change");
+#endif
+ }
+}
+
+// Check the maximum score, if we go over it we won't try this change.
+#define TRY_DEEPER(su, stack, depth, add) \
+ ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore)
+
+/// Try finding suggestions by adding/removing/swapping letters.
+///
+/// This uses a state machine. At each node in the tree we try various
+/// operations. When trying if an operation works "depth" is increased and the
+/// stack[] is used to store info. This allows combinations, thus insert one
+/// character, replace one and delete another. The number of changes is
+/// limited by su->su_maxscore.
+///
+/// After implementing this I noticed an article by Kemal Oflazer that
+/// describes something similar: "Error-tolerant Finite State Recognition with
+/// Applications to Morphological Analysis and Spelling Correction" (1996).
+/// The implementation in the article is simplified and requires a stack of
+/// unknown depth. The implementation here only needs a stack depth equal to
+/// the length of the word.
+///
+/// This is also used for the sound-folded word, "soundfold" is true then.
+/// The mechanism is the same, but we find a match with a sound-folded word
+/// that comes from one or more original words. Each of these words may be
+/// added, this is done by add_sound_suggest().
+/// Don't use:
+/// the prefix tree or the keep-case tree
+/// "su->su_badlen"
+/// anything to do with upper and lower case
+/// anything to do with word or non-word characters ("spell_iswordp()")
+/// banned words
+/// word flags (rare, region, compounding)
+/// word splitting for now
+/// "similar_chars()"
+/// use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep"
+static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool soundfold)
+{
+ char_u tword[MAXWLEN]; // good word collected so far
+ trystate_T stack[MAXWLEN];
+ char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case;
+ // concatenation of prefix compound
+ // words and split word. NUL terminated
+ // when going deeper but not when coming
+ // back.
+ char_u compflags[MAXWLEN]; // compound flags, one for each word
+ trystate_T *sp;
+ int newscore;
+ int score;
+ char_u *byts, *fbyts, *pbyts;
+ idx_T *idxs, *fidxs, *pidxs;
+ int depth;
+ int c, c2, c3;
+ int n = 0;
+ int flags;
+ garray_T *gap;
+ idx_T arridx;
+ int len;
+ char_u *p;
+ fromto_T *ftp;
+ int fl = 0, tl;
+ int repextra = 0; // extra bytes in fword[] from REP item
+ slang_T *slang = lp->lp_slang;
+ int fword_ends;
+ bool goodword_ends;
+#ifdef DEBUG_TRIEWALK
+ // Stores the name of the change made at each level.
+ char_u changename[MAXWLEN][80];
+#endif
+ int breakcheckcount = 1000;
+ bool compound_ok;
+
+ // Go through the whole case-fold tree, try changes at each node.
+ // "tword[]" contains the word collected from nodes in the tree.
+ // "fword[]" the word we are trying to match with (initially the bad
+ // word).
+ depth = 0;
+ sp = &stack[0];
+ CLEAR_POINTER(sp); // -V1068
+ sp->ts_curi = 1;
+
+ if (soundfold) {
+ // Going through the soundfold tree.
+ byts = fbyts = slang->sl_sbyts;
+ idxs = fidxs = slang->sl_sidxs;
+ pbyts = NULL;
+ pidxs = NULL;
+ sp->ts_prefixdepth = PFD_NOPREFIX;
+ sp->ts_state = STATE_START;
+ } else {
+ // When there are postponed prefixes we need to use these first. At
+ // the end of the prefix we continue in the case-fold tree.
+ fbyts = slang->sl_fbyts;
+ fidxs = slang->sl_fidxs;
+ pbyts = slang->sl_pbyts;
+ pidxs = slang->sl_pidxs;
+ if (pbyts != NULL) {
+ byts = pbyts;
+ idxs = pidxs;
+ sp->ts_prefixdepth = PFD_PREFIXTREE;
+ sp->ts_state = STATE_NOPREFIX; // try without prefix first
+ } else {
+ byts = fbyts;
+ idxs = fidxs;
+ sp->ts_prefixdepth = PFD_NOPREFIX;
+ sp->ts_state = STATE_START;
+ }
+ }
+
+ // The loop may take an indefinite amount of time. Break out after some
+ // time.
+ proftime_T time_limit;
+ if (spell_suggest_timeout > 0) {
+ time_limit = profile_setlimit(spell_suggest_timeout);
+ }
+
+ // Loop to find all suggestions. At each round we either:
+ // - For the current state try one operation, advance "ts_curi",
+ // increase "depth".
+ // - When a state is done go to the next, set "ts_state".
+ // - When all states are tried decrease "depth".
+ while (depth >= 0 && !got_int) {
+ sp = &stack[depth];
+ switch (sp->ts_state) {
+ case STATE_START:
+ case STATE_NOPREFIX:
+ // Start of node: Deal with NUL bytes, which means
+ // tword[] may end here.
+ arridx = sp->ts_arridx; // current node in the tree
+ len = byts[arridx]; // bytes in this node
+ arridx += sp->ts_curi; // index of current byte
+
+ if (sp->ts_prefixdepth == PFD_PREFIXTREE) {
+ // Skip over the NUL bytes, we use them later.
+ for (n = 0; n < len && byts[arridx + n] == 0; n++) {}
+ sp->ts_curi = (int16_t)(sp->ts_curi + n);
+
+ // Always past NUL bytes now.
+ n = (int)sp->ts_state;
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_ENDNUL;
+ sp->ts_save_badflags = (char_u)su->su_badflags;
+
+ // At end of a prefix or at start of prefixtree: check for
+ // following word.
+ if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) {
+ // Set su->su_badflags to the caps type at this position.
+ // Use the caps type until here for the prefix itself.
+ n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
+ flags = badword_captype(su->su_badptr, su->su_badptr + n);
+ su->su_badflags = badword_captype(su->su_badptr + n,
+ su->su_badptr + su->su_badlen);
+#ifdef DEBUG_TRIEWALK
+ sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf)
+#endif
+ go_deeper(stack, depth, 0);
+ depth++;
+ sp = &stack[depth];
+ sp->ts_prefixdepth = (char_u)(depth - 1);
+ byts = fbyts;
+ idxs = fidxs;
+ sp->ts_arridx = 0;
+
+ // Move the prefix to preword[] with the right case
+ // and make find_keepcap_word() works.
+ tword[sp->ts_twordlen] = NUL;
+ make_case_word(tword + sp->ts_splitoff,
+ preword + sp->ts_prewordlen, flags);
+ sp->ts_prewordlen = (char_u)STRLEN(preword);
+ sp->ts_splitoff = sp->ts_twordlen;
+ }
+ break;
+ }
+
+ if (sp->ts_curi > len || byts[arridx] != 0) {
+ // Past bytes in node and/or past NUL bytes.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_ENDNUL;
+ sp->ts_save_badflags = (char_u)su->su_badflags;
+ break;
+ }
+
+ // End of word in tree.
+ sp->ts_curi++; // eat one NUL byte
+
+ flags = (int)idxs[arridx];
+
+ // Skip words with the NOSUGGEST flag.
+ if (flags & WF_NOSUGGEST) {
+ break;
+ }
+
+ fword_ends = (fword[sp->ts_fidx] == NUL
+ || (soundfold
+ ? ascii_iswhite(fword[sp->ts_fidx])
+ : !spell_iswordp(fword + sp->ts_fidx, curwin)));
+ tword[sp->ts_twordlen] = NUL;
+
+ if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
+ && (sp->ts_flags & TSF_PREFIXOK) == 0
+ && pbyts != NULL) {
+ // There was a prefix before the word. Check that the prefix
+ // can be used with this word.
+ // Count the length of the NULs in the prefix. If there are
+ // none this must be the first try without a prefix.
+ n = stack[sp->ts_prefixdepth].ts_arridx;
+ len = pbyts[n++];
+ for (c = 0; c < len && pbyts[n + c] == 0; c++) {}
+ if (c > 0) {
+ c = valid_word_prefix(c, n, flags,
+ tword + sp->ts_splitoff, slang, false);
+ if (c == 0) {
+ break;
+ }
+
+ // Use the WF_RARE flag for a rare prefix.
+ if (c & WF_RAREPFX) {
+ flags |= WF_RARE;
+ }
+
+ // Tricky: when checking for both prefix and compounding
+ // we run into the prefix flag first.
+ // Remember that it's OK, so that we accept the prefix
+ // when arriving at a compound flag.
+ sp->ts_flags |= TSF_PREFIXOK;
+ }
+ }
+
+ // Check NEEDCOMPOUND: can't use word without compounding. Do try
+ // appending another compound word below.
+ if (sp->ts_complen == sp->ts_compsplit && fword_ends
+ && (flags & WF_NEEDCOMP)) {
+ goodword_ends = false;
+ } else {
+ goodword_ends = true;
+ }
+
+ p = NULL;
+ compound_ok = true;
+ if (sp->ts_complen > sp->ts_compsplit) {
+ if (slang->sl_nobreak) {
+ // There was a word before this word. When there was no
+ // change in this word (it was correct) add the first word
+ // as a suggestion. If this word was corrected too, we
+ // need to check if a correct word follows.
+ if (sp->ts_fidx - sp->ts_splitfidx
+ == sp->ts_twordlen - sp->ts_splitoff
+ && STRNCMP(fword + sp->ts_splitfidx,
+ tword + sp->ts_splitoff,
+ sp->ts_fidx - sp->ts_splitfidx) == 0) {
+ preword[sp->ts_prewordlen] = NUL;
+ newscore = score_wordcount_adj(slang, sp->ts_score,
+ preword + sp->ts_prewordlen,
+ sp->ts_prewordlen > 0);
+ // Add the suggestion if the score isn't too bad.
+ if (newscore <= su->su_maxscore) {
+ add_suggestion(su, &su->su_ga, preword,
+ sp->ts_splitfidx - repextra,
+ newscore, 0, false,
+ lp->lp_sallang, false);
+ }
+ break;
+ }
+ } else {
+ // There was a compound word before this word. If this
+ // word does not support compounding then give up
+ // (splitting is tried for the word without compound
+ // flag).
+ if (((unsigned)flags >> 24) == 0
+ || sp->ts_twordlen - sp->ts_splitoff
+ < slang->sl_compminlen) {
+ break;
+ }
+ // For multi-byte chars check character length against
+ // COMPOUNDMIN.
+ if (slang->sl_compminlen > 0
+ && mb_charlen(tword + sp->ts_splitoff)
+ < slang->sl_compminlen) {
+ break;
+ }
+
+ compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
+ compflags[sp->ts_complen + 1] = NUL;
+ STRLCPY(preword + sp->ts_prewordlen,
+ tword + sp->ts_splitoff,
+ sp->ts_twordlen - sp->ts_splitoff + 1);
+
+ // Verify CHECKCOMPOUNDPATTERN rules.
+ if (match_checkcompoundpattern(preword, sp->ts_prewordlen,
+ &slang->sl_comppat)) {
+ compound_ok = false;
+ }
+
+ if (compound_ok) {
+ p = preword;
+ while (*skiptowhite(p) != NUL) {
+ p = (char_u *)skipwhite((char *)skiptowhite(p));
+ }
+ if (fword_ends && !can_compound(slang, p,
+ compflags + sp->ts_compsplit)) {
+ // Compound is not allowed. But it may still be
+ // possible if we add another (short) word.
+ compound_ok = false;
+ }
+ }
+
+ // Get pointer to last char of previous word.
+ p = preword + sp->ts_prewordlen;
+ MB_PTR_BACK(preword, p);
+ }
+ }
+
+ // Form the word with proper case in preword.
+ // If there is a word from a previous split, append.
+ // For the soundfold tree don't change the case, simply append.
+ if (soundfold) {
+ STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff);
+ } else if (flags & WF_KEEPCAP) {
+ // Must find the word in the keep-case tree.
+ find_keepcap_word(slang, tword + sp->ts_splitoff,
+ preword + sp->ts_prewordlen);
+ } else {
+ // Include badflags: If the badword is onecap or allcap
+ // use that for the goodword too. But if the badword is
+ // allcap and it's only one char long use onecap.
+ c = su->su_badflags;
+ if ((c & WF_ALLCAP)
+ && su->su_badlen ==
+ utfc_ptr2len((char *)su->su_badptr)) {
+ c = WF_ONECAP;
+ }
+ c |= flags;
+
+ // When appending a compound word after a word character don't
+ // use Onecap.
+ if (p != NULL && spell_iswordp_nmw(p, curwin)) {
+ c &= ~WF_ONECAP;
+ }
+ make_case_word(tword + sp->ts_splitoff,
+ preword + sp->ts_prewordlen, c);
+ }
+
+ if (!soundfold) {
+ // Don't use a banned word. It may appear again as a good
+ // word, thus remember it.
+ if (flags & WF_BANNED) {
+ add_banned(su, preword + sp->ts_prewordlen);
+ break;
+ }
+ if ((sp->ts_complen == sp->ts_compsplit
+ && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen))
+ || WAS_BANNED(su, (char *)preword)) {
+ if (slang->sl_compprog == NULL) {
+ break;
+ }
+ // the word so far was banned but we may try compounding
+ goodword_ends = false;
+ }
+ }
+
+ newscore = 0;
+ if (!soundfold) { // soundfold words don't have flags
+ if ((flags & WF_REGION)
+ && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) {
+ newscore += SCORE_REGION;
+ }
+ if (flags & WF_RARE) {
+ newscore += SCORE_RARE;
+ }
+
+ if (!spell_valid_case(su->su_badflags,
+ captype(preword + sp->ts_prewordlen, NULL))) {
+ newscore += SCORE_ICASE;
+ }
+ }
+
+ // TODO(vim): how about splitting in the soundfold tree?
+ if (fword_ends
+ && goodword_ends
+ && sp->ts_fidx >= sp->ts_fidxtry
+ && compound_ok) {
+ // The badword also ends: add suggestions.
+#ifdef DEBUG_TRIEWALK
+ if (soundfold && STRCMP(preword, "smwrd") == 0) {
+ int j;
+
+ // print the stack of changes that brought us here
+ smsg("------ %s -------", fword);
+ for (j = 0; j < depth; j++) {
+ smsg("%s", changename[j]);
+ }
+ }
+#endif
+ if (soundfold) {
+ // For soundfolded words we need to find the original
+ // words, the edit distance and then add them.
+ add_sound_suggest(su, preword, sp->ts_score, lp);
+ } else if (sp->ts_fidx > 0) {
+ // Give a penalty when changing non-word char to word
+ // char, e.g., "thes," -> "these".
+ p = fword + sp->ts_fidx;
+ MB_PTR_BACK(fword, p);
+ if (!spell_iswordp(p, curwin) && *preword != NUL) {
+ p = preword + STRLEN(preword);
+ MB_PTR_BACK(preword, p);
+ if (spell_iswordp(p, curwin)) {
+ newscore += SCORE_NONWORD;
+ }
+ }
+
+ // Give a bonus to words seen before.
+ score = score_wordcount_adj(slang,
+ sp->ts_score + newscore,
+ preword + sp->ts_prewordlen,
+ sp->ts_prewordlen > 0);
+
+ // Add the suggestion if the score isn't too bad.
+ if (score <= su->su_maxscore) {
+ add_suggestion(su, &su->su_ga, preword,
+ sp->ts_fidx - repextra,
+ score, 0, false, lp->lp_sallang, false);
+
+ if (su->su_badflags & WF_MIXCAP) {
+ // We really don't know if the word should be
+ // upper or lower case, add both.
+ c = captype(preword, NULL);
+ if (c == 0 || c == WF_ALLCAP) {
+ make_case_word(tword + sp->ts_splitoff,
+ preword + sp->ts_prewordlen,
+ c == 0 ? WF_ALLCAP : 0);
+
+ add_suggestion(su, &su->su_ga, preword,
+ sp->ts_fidx - repextra,
+ score + SCORE_ICASE, 0, false,
+ lp->lp_sallang, false);
+ }
+ }
+ }
+ }
+ }
+
+ // Try word split and/or compounding.
+ if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends)
+ // Don't split in the middle of a character
+ && (sp->ts_tcharlen == 0)) {
+ bool try_compound;
+ int try_split;
+
+ // If past the end of the bad word don't try a split.
+ // Otherwise try changing the next word. E.g., find
+ // suggestions for "the the" where the second "the" is
+ // different. It's done like a split.
+ // TODO(vim): word split for soundfold words
+ try_split = (sp->ts_fidx - repextra < su->su_badlen)
+ && !soundfold;
+
+ // Get here in several situations:
+ // 1. The word in the tree ends:
+ // If the word allows compounding try that. Otherwise try
+ // a split by inserting a space. For both check that a
+ // valid words starts at fword[sp->ts_fidx].
+ // For NOBREAK do like compounding to be able to check if
+ // the next word is valid.
+ // 2. The badword does end, but it was due to a change (e.g.,
+ // a swap). No need to split, but do check that the
+ // following word is valid.
+ // 3. The badword and the word in the tree end. It may still
+ // be possible to compound another (short) word.
+ try_compound = false;
+ if (!soundfold
+ && !slang->sl_nocompoundsugs
+ && slang->sl_compprog != NULL
+ && ((unsigned)flags >> 24) != 0
+ && sp->ts_twordlen - sp->ts_splitoff
+ >= slang->sl_compminlen
+ && (slang->sl_compminlen == 0
+ || mb_charlen(tword + sp->ts_splitoff)
+ >= slang->sl_compminlen)
+ && (slang->sl_compsylmax < MAXWLEN
+ || sp->ts_complen + 1 - sp->ts_compsplit
+ < slang->sl_compmax)
+ && (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) {
+ try_compound = true;
+ compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
+ compflags[sp->ts_complen + 1] = NUL;
+ }
+
+ // For NOBREAK we never try splitting, it won't make any word
+ // valid.
+ if (slang->sl_nobreak && !slang->sl_nocompoundsugs) {
+ try_compound = true;
+ } else if (!fword_ends
+ && try_compound
+ && (sp->ts_flags & TSF_DIDSPLIT) == 0) {
+ // If we could add a compound word, and it's also possible to
+ // split at this point, do the split first and set
+ // TSF_DIDSPLIT to avoid doing it again.
+ try_compound = false;
+ sp->ts_flags |= TSF_DIDSPLIT;
+ sp->ts_curi--; // do the same NUL again
+ compflags[sp->ts_complen] = NUL;
+ } else {
+ sp->ts_flags &= (char_u) ~TSF_DIDSPLIT;
+ }
+
+ if (try_split || try_compound) {
+ if (!try_compound && (!fword_ends || !goodword_ends)) {
+ // If we're going to split need to check that the
+ // words so far are valid for compounding. If there
+ // is only one word it must not have the NEEDCOMPOUND
+ // flag.
+ if (sp->ts_complen == sp->ts_compsplit
+ && (flags & WF_NEEDCOMP)) {
+ break;
+ }
+ p = preword;
+ while (*skiptowhite(p) != NUL) {
+ p = (char_u *)skipwhite((char *)skiptowhite(p));
+ }
+ if (sp->ts_complen > sp->ts_compsplit
+ && !can_compound(slang, p,
+ compflags + sp->ts_compsplit)) {
+ break;
+ }
+
+ if (slang->sl_nosplitsugs) {
+ newscore += SCORE_SPLIT_NO;
+ } else {
+ newscore += SCORE_SPLIT;
+ }
+
+ // Give a bonus to words seen before.
+ newscore = score_wordcount_adj(slang, newscore,
+ preword + sp->ts_prewordlen, true);
+ }
+
+ if (TRY_DEEPER(su, stack, depth, newscore)) {
+ go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+ if (!try_compound && !fword_ends) {
+ sprintf(changename[depth], "%.*s-%s: split", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx);
+ } else {
+ sprintf(changename[depth], "%.*s-%s: compound", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx);
+ }
+#endif
+ // Save things to be restored at STATE_SPLITUNDO.
+ sp->ts_save_badflags = (char_u)su->su_badflags;
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_SPLITUNDO;
+
+ depth++;
+ sp = &stack[depth];
+
+ // Append a space to preword when splitting.
+ if (!try_compound && !fword_ends) {
+ STRCAT(preword, " ");
+ }
+ sp->ts_prewordlen = (char_u)STRLEN(preword);
+ sp->ts_splitoff = sp->ts_twordlen;
+ sp->ts_splitfidx = sp->ts_fidx;
+
+ // If the badword has a non-word character at this
+ // position skip it. That means replacing the
+ // non-word character with a space. Always skip a
+ // character when the word ends. But only when the
+ // good word can end.
+ if (((!try_compound && !spell_iswordp_nmw(fword
+ + sp->ts_fidx,
+ curwin))
+ || fword_ends)
+ && fword[sp->ts_fidx] != NUL
+ && goodword_ends) {
+ int l;
+
+ l = utfc_ptr2len((char *)fword + sp->ts_fidx);
+ if (fword_ends) {
+ // Copy the skipped character to preword.
+ memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l);
+ sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l);
+ preword[sp->ts_prewordlen] = NUL;
+ } else {
+ sp->ts_score -= SCORE_SPLIT - SCORE_SUBST;
+ }
+ sp->ts_fidx = (char_u)(sp->ts_fidx + l);
+ }
+
+ // When compounding include compound flag in
+ // compflags[] (already set above). When splitting we
+ // may start compounding over again.
+ if (try_compound) {
+ sp->ts_complen++;
+ } else {
+ sp->ts_compsplit = sp->ts_complen;
+ }
+ sp->ts_prefixdepth = PFD_NOPREFIX;
+
+ // set su->su_badflags to the caps type at this
+ // position
+ n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
+ su->su_badflags = badword_captype(su->su_badptr + n,
+ su->su_badptr + su->su_badlen);
+
+ // Restart at top of the tree.
+ sp->ts_arridx = 0;
+
+ // If there are postponed prefixes, try these too.
+ if (pbyts != NULL) {
+ byts = pbyts;
+ idxs = pidxs;
+ sp->ts_prefixdepth = PFD_PREFIXTREE;
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_NOPREFIX;
+ }
+ }
+ }
+ }
+ break;
+
+ case STATE_SPLITUNDO:
+ // Undo the changes done for word split or compound word.
+ su->su_badflags = sp->ts_save_badflags;
+
+ // Continue looking for NUL bytes.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_START;
+
+ // In case we went into the prefix tree.
+ byts = fbyts;
+ idxs = fidxs;
+ break;
+
+ case STATE_ENDNUL:
+ // Past the NUL bytes in the node.
+ su->su_badflags = sp->ts_save_badflags;
+ if (fword[sp->ts_fidx] == NUL
+ && sp->ts_tcharlen == 0) {
+ // The badword ends, can't use STATE_PLAIN.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_DEL;
+ break;
+ }
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_PLAIN;
+ FALLTHROUGH;
+
+ case STATE_PLAIN:
+ // Go over all possible bytes at this node, add each to tword[]
+ // and use child node. "ts_curi" is the index.
+ arridx = sp->ts_arridx;
+ if (sp->ts_curi > byts[arridx]) {
+ // Done all bytes at this node, do next state. When still at
+ // already changed bytes skip the other tricks.
+ PROF_STORE(sp->ts_state)
+ if (sp->ts_fidx >= sp->ts_fidxtry) {
+ sp->ts_state = STATE_DEL;
+ } else {
+ sp->ts_state = STATE_FINAL;
+ }
+ } else {
+ arridx += sp->ts_curi++;
+ c = byts[arridx];
+
+ // Normal byte, go one level deeper. If it's not equal to the
+ // byte in the bad word adjust the score. But don't even try
+ // when the byte was already changed. And don't try when we
+ // just deleted this byte, accepting it is always cheaper than
+ // delete + substitute.
+ if (c == fword[sp->ts_fidx]
+ || (sp->ts_tcharlen > 0
+ && sp->ts_isdiff != DIFF_NONE)) {
+ newscore = 0;
+ } else {
+ newscore = SCORE_SUBST;
+ }
+ if ((newscore == 0
+ || (sp->ts_fidx >= sp->ts_fidxtry
+ && ((sp->ts_flags & TSF_DIDDEL) == 0
+ || c != fword[sp->ts_delidx])))
+ && TRY_DEEPER(su, stack, depth, newscore)) {
+ go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+ if (newscore > 0) {
+ sprintf(changename[depth], "%.*s-%s: subst %c to %c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ fword[sp->ts_fidx], c);
+ } else {
+ sprintf(changename[depth], "%.*s-%s: accept %c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ fword[sp->ts_fidx]);
+ }
+#endif
+ depth++;
+ sp = &stack[depth];
+ if (fword[sp->ts_fidx] != NUL) {
+ sp->ts_fidx++;
+ }
+ tword[sp->ts_twordlen++] = (char_u)c;
+ sp->ts_arridx = idxs[arridx];
+ if (newscore == SCORE_SUBST) {
+ sp->ts_isdiff = DIFF_YES;
+ }
+ // Multi-byte characters are a bit complicated to
+ // handle: They differ when any of the bytes differ
+ // and then their length may also differ.
+ if (sp->ts_tcharlen == 0) {
+ // First byte.
+ sp->ts_tcharidx = 0;
+ sp->ts_tcharlen = MB_BYTE2LEN(c);
+ sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1);
+ sp->ts_isdiff = (newscore != 0)
+ ? DIFF_YES : DIFF_NONE;
+ } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) {
+ // When inserting trail bytes don't advance in the
+ // bad word.
+ sp->ts_fidx--;
+ }
+ if (++sp->ts_tcharidx == sp->ts_tcharlen) {
+ // Last byte of character.
+ if (sp->ts_isdiff == DIFF_YES) {
+ // Correct ts_fidx for the byte length of the
+ // character (we didn't check that before).
+ sp->ts_fidx = (char_u)(sp->ts_fcharstart
+ + utfc_ptr2len((char *)fword + sp->ts_fcharstart));
+
+ // For changing a composing character adjust
+ // the score from SCORE_SUBST to
+ // SCORE_SUBCOMP.
+ if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen
+ - sp->ts_tcharlen))
+ && utf_iscomposing(utf_ptr2char((char *)fword
+ + sp->ts_fcharstart))) {
+ sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP;
+ } else if (!soundfold
+ && slang->sl_has_map
+ && similar_chars(slang,
+ utf_ptr2char((char *)tword + sp->ts_twordlen -
+ sp->ts_tcharlen),
+ utf_ptr2char((char *)fword + sp->ts_fcharstart))) {
+ // For a similar character adjust score from
+ // SCORE_SUBST to SCORE_SIMILAR.
+ sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR;
+ }
+ } else if (sp->ts_isdiff == DIFF_INSERT
+ && sp->ts_twordlen > sp->ts_tcharlen) {
+ p = tword + sp->ts_twordlen - sp->ts_tcharlen;
+ c = utf_ptr2char((char *)p);
+ if (utf_iscomposing(c)) {
+ // Inserting a composing char doesn't
+ // count that much.
+ sp->ts_score -= SCORE_INS - SCORE_INSCOMP;
+ } else {
+ // If the previous character was the same,
+ // thus doubling a character, give a bonus
+ // to the score. Also for the soundfold
+ // tree (might seem illogical but does
+ // give better scores).
+ MB_PTR_BACK(tword, p);
+ if (c == utf_ptr2char((char *)p)) {
+ sp->ts_score -= SCORE_INS - SCORE_INSDUP;
+ }
+ }
+ }
+
+ // Starting a new char, reset the length.
+ sp->ts_tcharlen = 0;
+ }
+ }
+ }
+ break;
+
+ case STATE_DEL:
+ // When past the first byte of a multi-byte char don't try
+ // delete/insert/swap a character.
+ if (sp->ts_tcharlen > 0) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_FINAL;
+ break;
+ }
+ // Try skipping one character in the bad word (delete it).
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_INS_PREP;
+ sp->ts_curi = 1;
+ if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') {
+ // Deleting a vowel at the start of a word counts less, see
+ // soundalike_score().
+ newscore = 2 * SCORE_DEL / 3;
+ } else {
+ newscore = SCORE_DEL;
+ }
+ if (fword[sp->ts_fidx] != NUL
+ && TRY_DEEPER(su, stack, depth, newscore)) {
+ go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+ sprintf(changename[depth], "%.*s-%s: delete %c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ fword[sp->ts_fidx]);
+#endif
+ depth++;
+
+ // Remember what character we deleted, so that we can avoid
+ // inserting it again.
+ stack[depth].ts_flags |= TSF_DIDDEL;
+ stack[depth].ts_delidx = sp->ts_fidx;
+
+ // Advance over the character in fword[]. Give a bonus to the
+ // score if the same character is following "nn" -> "n". It's
+ // a bit illogical for soundfold tree but it does give better
+ // results.
+ c = utf_ptr2char((char *)fword + sp->ts_fidx);
+ stack[depth].ts_fidx =
+ (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)fword + sp->ts_fidx));
+ if (utf_iscomposing(c)) {
+ stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
+ } else if (c == utf_ptr2char((char *)fword + stack[depth].ts_fidx)) {
+ stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
+ }
+
+ break;
+ }
+ FALLTHROUGH;
+
+ case STATE_INS_PREP:
+ if (sp->ts_flags & TSF_DIDDEL) {
+ // If we just deleted a byte then inserting won't make sense,
+ // a substitute is always cheaper.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_SWAP;
+ break;
+ }
+
+ // skip over NUL bytes
+ n = sp->ts_arridx;
+ for (;;) {
+ if (sp->ts_curi > byts[n]) {
+ // Only NUL bytes at this node, go to next state.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_SWAP;
+ break;
+ }
+ if (byts[n + sp->ts_curi] != NUL) {
+ // Found a byte to insert.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_INS;
+ break;
+ }
+ sp->ts_curi++;
+ }
+ break;
+
+ case STATE_INS:
+ // Insert one byte. Repeat this for each possible byte at this
+ // node.
+ n = sp->ts_arridx;
+ if (sp->ts_curi > byts[n]) {
+ // Done all bytes at this node, go to next state.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_SWAP;
+ break;
+ }
+
+ // Do one more byte at this node, but:
+ // - Skip NUL bytes.
+ // - Skip the byte if it's equal to the byte in the word,
+ // accepting that byte is always better.
+ n += sp->ts_curi++;
+ c = byts[n];
+ if (soundfold && sp->ts_twordlen == 0 && c == '*') {
+ // Inserting a vowel at the start of a word counts less,
+ // see soundalike_score().
+ newscore = 2 * SCORE_INS / 3;
+ } else {
+ newscore = SCORE_INS;
+ }
+ if (c != fword[sp->ts_fidx]
+ && TRY_DEEPER(su, stack, depth, newscore)) {
+ go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+ sprintf(changename[depth], "%.*s-%s: insert %c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ c);
+#endif
+ depth++;
+ sp = &stack[depth];
+ tword[sp->ts_twordlen++] = (char_u)c;
+ sp->ts_arridx = idxs[n];
+ fl = MB_BYTE2LEN(c);
+ if (fl > 1) {
+ // There are following bytes for the same character.
+ // We must find all bytes before trying
+ // delete/insert/swap/etc.
+ sp->ts_tcharlen = (char_u)fl;
+ sp->ts_tcharidx = 1;
+ sp->ts_isdiff = DIFF_INSERT;
+ }
+ if (fl == 1) {
+ // If the previous character was the same, thus doubling a
+ // character, give a bonus to the score. Also for
+ // soundfold words (illogical but does give a better
+ // score).
+ if (sp->ts_twordlen >= 2
+ && tword[sp->ts_twordlen - 2] == c) {
+ sp->ts_score -= SCORE_INS - SCORE_INSDUP;
+ }
+ }
+ }
+ break;
+
+ case STATE_SWAP:
+ // Swap two bytes in the bad word: "12" -> "21".
+ // We change "fword" here, it's changed back afterwards at
+ // STATE_UNSWAP.
+ p = fword + sp->ts_fidx;
+ c = *p;
+ if (c == NUL) {
+ // End of word, can't swap or replace.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_FINAL;
+ break;
+ }
+
+ // Don't swap if the first character is not a word character.
+ // SWAP3 etc. also don't make sense then.
+ if (!soundfold && !spell_iswordp(p, curwin)) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ break;
+ }
+
+ n = utf_ptr2len((char *)p);
+ c = utf_ptr2char((char *)p);
+ if (p[n] == NUL) {
+ c2 = NUL;
+ } else if (!soundfold && !spell_iswordp(p + n, curwin)) {
+ c2 = c; // don't swap non-word char
+ } else {
+ c2 = utf_ptr2char((char *)p + n);
+ }
+
+ // When the second character is NUL we can't swap.
+ if (c2 == NUL) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ break;
+ }
+
+ // When characters are identical, swap won't do anything.
+ // Also get here if the second char is not a word character.
+ if (c == c2) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_SWAP3;
+ break;
+ }
+ if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) {
+ go_deeper(stack, depth, SCORE_SWAP);
+#ifdef DEBUG_TRIEWALK
+ snprintf(changename[depth], sizeof(changename[0]),
+ "%.*s-%s: swap %c and %c",
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ c, c2);
+#endif
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_UNSWAP;
+ depth++;
+ fl = utf_char2len(c2);
+ memmove(p, p + n, (size_t)fl);
+ utf_char2bytes(c, (char *)p + fl);
+ stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
+ } else {
+ // If this swap doesn't work then SWAP3 won't either.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ }
+ break;
+
+ case STATE_UNSWAP:
+ // Undo the STATE_SWAP swap: "21" -> "12".
+ p = fword + sp->ts_fidx;
+ n = utfc_ptr2len((char *)p);
+ c = utf_ptr2char((char *)p + n);
+ memmove(p + utfc_ptr2len((char *)p + n), p, (size_t)n);
+ utf_char2bytes(c, (char *)p);
+
+ FALLTHROUGH;
+
+ case STATE_SWAP3:
+ // Swap two bytes, skipping one: "123" -> "321". We change
+ // "fword" here, it's changed back afterwards at STATE_UNSWAP3.
+ p = fword + sp->ts_fidx;
+ n = utf_ptr2len((char *)p);
+ c = utf_ptr2char((char *)p);
+ fl = utf_ptr2len((char *)p + n);
+ c2 = utf_ptr2char((char *)p + n);
+ if (!soundfold && !spell_iswordp(p + n + fl, curwin)) {
+ c3 = c; // don't swap non-word char
+ } else {
+ c3 = utf_ptr2char((char *)p + n + fl);
+ }
+
+ // When characters are identical: "121" then SWAP3 result is
+ // identical, ROT3L result is same as SWAP: "211", ROT3L result is
+ // same as SWAP on next char: "112". Thus skip all swapping.
+ // Also skip when c3 is NUL.
+ // Also get here when the third character is not a word character.
+ // Second character may any char: "a.b" -> "b.a"
+ if (c == c3 || c3 == NUL) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ break;
+ }
+ if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) {
+ go_deeper(stack, depth, SCORE_SWAP3);
+#ifdef DEBUG_TRIEWALK
+ sprintf(changename[depth], "%.*s-%s: swap3 %c and %c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ c, c3);
+#endif
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_UNSWAP3;
+ depth++;
+ tl = utf_char2len(c3);
+ memmove(p, p + n + fl, (size_t)tl);
+ utf_char2bytes(c2, (char *)p + tl);
+ utf_char2bytes(c, (char *)p + fl + tl);
+ stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl);
+ } else {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ }
+ break;
+
+ case STATE_UNSWAP3:
+ // Undo STATE_SWAP3: "321" -> "123"
+ p = fword + sp->ts_fidx;
+ n = utfc_ptr2len((char *)p);
+ c2 = utf_ptr2char((char *)p + n);
+ fl = utfc_ptr2len((char *)p + n);
+ c = utf_ptr2char((char *)p + n + fl);
+ tl = utfc_ptr2len((char *)p + n + fl);
+ memmove(p + fl + tl, p, (size_t)n);
+ utf_char2bytes(c, (char *)p);
+ utf_char2bytes(c2, (char *)p + tl);
+ p = p + tl;
+
+ if (!soundfold && !spell_iswordp(p, curwin)) {
+ // Middle char is not a word char, skip the rotate. First and
+ // third char were already checked at swap and swap3.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ break;
+ }
+
+ // Rotate three characters left: "123" -> "231". We change
+ // "fword" here, it's changed back afterwards at STATE_UNROT3L.
+ if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) {
+ go_deeper(stack, depth, SCORE_SWAP3);
+#ifdef DEBUG_TRIEWALK
+ p = fword + sp->ts_fidx;
+ sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ p[0], p[1], p[2]);
+#endif
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_UNROT3L;
+ depth++;
+ p = fword + sp->ts_fidx;
+ n = utf_ptr2len((char *)p);
+ c = utf_ptr2char((char *)p);
+ fl = utf_ptr2len((char *)p + n);
+ fl += utf_ptr2len((char *)p + n + fl);
+ memmove(p, p + n, (size_t)fl);
+ utf_char2bytes(c, (char *)p + fl);
+ stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
+ } else {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ }
+ break;
+
+ case STATE_UNROT3L:
+ // Undo ROT3L: "231" -> "123"
+ p = fword + sp->ts_fidx;
+ n = utfc_ptr2len((char *)p);
+ n += utfc_ptr2len((char *)p + n);
+ c = utf_ptr2char((char *)p + n);
+ tl = utfc_ptr2len((char *)p + n);
+ memmove(p + tl, p, (size_t)n);
+ utf_char2bytes(c, (char *)p);
+
+ // Rotate three bytes right: "123" -> "312". We change "fword"
+ // here, it's changed back afterwards at STATE_UNROT3R.
+ if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) {
+ go_deeper(stack, depth, SCORE_SWAP3);
+#ifdef DEBUG_TRIEWALK
+ p = fword + sp->ts_fidx;
+ sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ p[0], p[1], p[2]);
+#endif
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_UNROT3R;
+ depth++;
+ p = fword + sp->ts_fidx;
+ n = utf_ptr2len((char *)p);
+ n += utf_ptr2len((char *)p + n);
+ c = utf_ptr2char((char *)p + n);
+ tl = utf_ptr2len((char *)p + n);
+ memmove(p + tl, p, (size_t)n);
+ utf_char2bytes(c, (char *)p);
+ stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl);
+ } else {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_INI;
+ }
+ break;
+
+ case STATE_UNROT3R:
+ // Undo ROT3R: "312" -> "123"
+ p = fword + sp->ts_fidx;
+ c = utf_ptr2char((char *)p);
+ tl = utfc_ptr2len((char *)p);
+ n = utfc_ptr2len((char *)p + tl);
+ n += utfc_ptr2len((char *)p + tl + n);
+ memmove(p, p + tl, (size_t)n);
+ utf_char2bytes(c, (char *)p + n);
+
+ FALLTHROUGH;
+
+ case STATE_REP_INI:
+ // Check if matching with REP items from the .aff file would work.
+ // Quickly skip if:
+ // - there are no REP items and we are not in the soundfold trie
+ // - the score is going to be too high anyway
+ // - already applied a REP item or swapped here
+ if ((lp->lp_replang == NULL && !soundfold)
+ || sp->ts_score + SCORE_REP >= su->su_maxscore
+ || sp->ts_fidx < sp->ts_fidxtry) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_FINAL;
+ break;
+ }
+
+ // Use the first byte to quickly find the first entry that may
+ // match. If the index is -1 there is none.
+ if (soundfold) {
+ sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]];
+ } else {
+ sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]];
+ }
+
+ if (sp->ts_curi < 0) {
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_FINAL;
+ break;
+ }
+
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP;
+ FALLTHROUGH;
+
+ case STATE_REP:
+ // Try matching with REP items from the .aff file. For each match
+ // replace the characters and check if the resulting word is
+ // valid.
+ p = fword + sp->ts_fidx;
+
+ if (soundfold) {
+ gap = &slang->sl_repsal;
+ } else {
+ gap = &lp->lp_replang->sl_rep;
+ }
+ while (sp->ts_curi < gap->ga_len) {
+ ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
+ if (*ftp->ft_from != *p) {
+ // past possible matching entries
+ sp->ts_curi = (char_u)gap->ga_len;
+ break;
+ }
+ if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0
+ && TRY_DEEPER(su, stack, depth, SCORE_REP)) {
+ go_deeper(stack, depth, SCORE_REP);
+#ifdef DEBUG_TRIEWALK
+ sprintf(changename[depth], "%.*s-%s: replace %s with %s", // NOLINT(runtime/printf)
+ sp->ts_twordlen, tword, fword + sp->ts_fidx,
+ ftp->ft_from, ftp->ft_to);
+#endif
+ // Need to undo this afterwards.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP_UNDO;
+
+ // Change the "from" to the "to" string.
+ depth++;
+ fl = (int)STRLEN(ftp->ft_from);
+ tl = (int)STRLEN(ftp->ft_to);
+ if (fl != tl) {
+ STRMOVE(p + tl, p + fl);
+ repextra += tl - fl;
+ }
+ memmove(p, ftp->ft_to, (size_t)tl);
+ stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl);
+ stack[depth].ts_tcharlen = 0;
+ break;
+ }
+ }
+
+ if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) {
+ // No (more) matches.
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_FINAL;
+ }
+
+ break;
+
+ case STATE_REP_UNDO:
+ // Undo a REP replacement and continue with the next one.
+ if (soundfold) {
+ gap = &slang->sl_repsal;
+ } else {
+ gap = &lp->lp_replang->sl_rep;
+ }
+ ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
+ fl = (int)STRLEN(ftp->ft_from);
+ tl = (int)STRLEN(ftp->ft_to);
+ p = fword + sp->ts_fidx;
+ if (fl != tl) {
+ STRMOVE(p + fl, p + tl);
+ repextra -= tl - fl;
+ }
+ memmove(p, ftp->ft_from, (size_t)fl);
+ PROF_STORE(sp->ts_state)
+ sp->ts_state = STATE_REP;
+ break;
+
+ default:
+ // Did all possible states at this level, go up one level.
+ depth--;
+
+ if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) {
+ // Continue in or go back to the prefix tree.
+ byts = pbyts;
+ idxs = pidxs;
+ }
+
+ // Don't check for CTRL-C too often, it takes time.
+ if (--breakcheckcount == 0) {
+ os_breakcheck();
+ breakcheckcount = 1000;
+ if (spell_suggest_timeout > 0 && profile_passed_limit(time_limit)) {
+ got_int = true;
+ }
+ }
+ }
+ }
+}
+
+/// Go one level deeper in the tree.
+static void go_deeper(trystate_T *stack, int depth, int score_add)
+{
+ stack[depth + 1] = stack[depth];
+ stack[depth + 1].ts_state = STATE_START;
+ stack[depth + 1].ts_score = stack[depth].ts_score + score_add;
+ stack[depth + 1].ts_curi = 1; // start just after length byte
+ stack[depth + 1].ts_flags = 0;
+}
+
+/// "fword" is a good word with case folded. Find the matching keep-case
+/// words and put it in "kword".
+/// Theoretically there could be several keep-case words that result in the
+/// same case-folded word, but we only find one...
+static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword)
+{
+ char_u uword[MAXWLEN]; // "fword" in upper-case
+ int depth;
+ idx_T tryidx;
+
+ // The following arrays are used at each depth in the tree.
+ idx_T arridx[MAXWLEN];
+ int round[MAXWLEN];
+ int fwordidx[MAXWLEN];
+ int uwordidx[MAXWLEN];
+ int kwordlen[MAXWLEN];
+
+ int flen, ulen;
+ int l;
+ int len;
+ int c;
+ idx_T lo, hi, m;
+ char_u *p;
+ char_u *byts = slang->sl_kbyts; // array with bytes of the words
+ idx_T *idxs = slang->sl_kidxs; // array with indexes
+
+ if (byts == NULL) {
+ // array is empty: "cannot happen"
+ *kword = NUL;
+ return;
+ }
+
+ // Make an all-cap version of "fword".
+ allcap_copy(fword, uword);
+
+ // Each character needs to be tried both case-folded and upper-case.
+ // All this gets very complicated if we keep in mind that changing case
+ // may change the byte length of a multi-byte character...
+ depth = 0;
+ arridx[0] = 0;
+ round[0] = 0;
+ fwordidx[0] = 0;
+ uwordidx[0] = 0;
+ kwordlen[0] = 0;
+ while (depth >= 0) {
+ if (fword[fwordidx[depth]] == NUL) {
+ // We are at the end of "fword". If the tree allows a word to end
+ // here we have found a match.
+ if (byts[arridx[depth] + 1] == 0) {
+ kword[kwordlen[depth]] = NUL;
+ return;
+ }
+
+ // kword is getting too long, continue one level up
+ depth--;
+ } else if (++round[depth] > 2) {
+ // tried both fold-case and upper-case character, continue one
+ // level up
+ depth--;
+ } else {
+ // round[depth] == 1: Try using the folded-case character.
+ // round[depth] == 2: Try using the upper-case character.
+ flen = utf_ptr2len((char *)fword + fwordidx[depth]);
+ ulen = utf_ptr2len((char *)uword + uwordidx[depth]);
+ if (round[depth] == 1) {
+ p = fword + fwordidx[depth];
+ l = flen;
+ } else {
+ p = uword + uwordidx[depth];
+ l = ulen;
+ }
+
+ for (tryidx = arridx[depth]; l > 0; l--) {
+ // Perform a binary search in the list of accepted bytes.
+ len = byts[tryidx++];
+ c = *p++;
+ lo = tryidx;
+ hi = tryidx + len - 1;
+ while (lo < hi) {
+ m = (lo + hi) / 2;
+ if (byts[m] > c) {
+ hi = m - 1;
+ } else if (byts[m] < c) {
+ lo = m + 1;
+ } else {
+ lo = hi = m;
+ break;
+ }
+ }
+
+ // Stop if there is no matching byte.
+ if (hi < lo || byts[lo] != c) {
+ break;
+ }
+
+ // Continue at the child (if there is one).
+ tryidx = idxs[lo];
+ }
+
+ if (l == 0) {
+ // Found the matching char. Copy it to "kword" and go a
+ // level deeper.
+ if (round[depth] == 1) {
+ STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], // NOLINT(runtime/printf)
+ flen);
+ kwordlen[depth + 1] = kwordlen[depth] + flen;
+ } else {
+ STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], // NOLINT(runtime/printf)
+ ulen);
+ kwordlen[depth + 1] = kwordlen[depth] + ulen;
+ }
+ fwordidx[depth + 1] = fwordidx[depth] + flen;
+ uwordidx[depth + 1] = uwordidx[depth] + ulen;
+
+ depth++;
+ arridx[depth] = tryidx;
+ round[depth] = 0;
+ }
+ }
+ }
+
+ // Didn't find it: "cannot happen".
+ *kword = NUL;
+}
+
+/// Compute the sound-a-like score for suggestions in su->su_ga and add them to
+/// su->su_sga.
+static void score_comp_sal(suginfo_T *su)
+{
+ langp_T *lp;
+ char_u badsound[MAXWLEN];
+ int i;
+ suggest_T *stp;
+ suggest_T *sstp;
+ int score;
+
+ ga_grow(&su->su_sga, su->su_ga.ga_len);
+
+ // Use the sound-folding of the first language that supports it.
+ for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
+ lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
+ // soundfold the bad word
+ spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound);
+
+ for (i = 0; i < su->su_ga.ga_len; i++) {
+ stp = &SUG(su->su_ga, i);
+
+ // Case-fold the suggested word, sound-fold it and compute the
+ // sound-a-like score.
+ score = stp_sal_score(stp, su, lp->lp_slang, badsound);
+ if (score < SCORE_MAXMAX) {
+ // Add the suggestion.
+ sstp = &SUG(su->su_sga, su->su_sga.ga_len);
+ sstp->st_word = vim_strsave(stp->st_word);
+ sstp->st_wordlen = stp->st_wordlen;
+ sstp->st_score = score;
+ sstp->st_altscore = 0;
+ sstp->st_orglen = stp->st_orglen;
+ su->su_sga.ga_len++;
+ }
+ }
+ break;
+ }
+ }
+}
+
+/// Combine the list of suggestions in su->su_ga and su->su_sga.
+/// They are entwined.
+static void score_combine(suginfo_T *su)
+{
+ garray_T ga;
+ garray_T *gap;
+ langp_T *lp;
+ suggest_T *stp;
+ char_u *p;
+ char_u badsound[MAXWLEN];
+ int round;
+ slang_T *slang = NULL;
+
+ // Add the alternate score to su_ga.
+ for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
+ lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
+ // soundfold the bad word
+ slang = lp->lp_slang;
+ spell_soundfold(slang, su->su_fbadword, true, badsound);
+
+ for (int i = 0; i < su->su_ga.ga_len; i++) {
+ stp = &SUG(su->su_ga, i);
+ stp->st_altscore = stp_sal_score(stp, su, slang, badsound);
+ if (stp->st_altscore == SCORE_MAXMAX) {
+ stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4;
+ } else {
+ stp->st_score = (stp->st_score * 3 + stp->st_altscore) / 4;
+ }
+ stp->st_salscore = false;
+ }
+ break;
+ }
+ }
+
+ if (slang == NULL) { // Using "double" without sound folding.
+ (void)cleanup_suggestions(&su->su_ga, su->su_maxscore,
+ su->su_maxcount);
+ return;
+ }
+
+ // Add the alternate score to su_sga.
+ for (int i = 0; i < su->su_sga.ga_len; i++) {
+ stp = &SUG(su->su_sga, i);
+ stp->st_altscore = spell_edit_score(slang,
+ su->su_badword, stp->st_word);
+ if (stp->st_score == SCORE_MAXMAX) {
+ stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8;
+ } else {
+ stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8;
+ }
+ stp->st_salscore = true;
+ }
+
+ // Remove bad suggestions, sort the suggestions and truncate at "maxcount"
+ // for both lists.
+ check_suggestions(su, &su->su_ga);
+ (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+ check_suggestions(su, &su->su_sga);
+ (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount);
+
+ ga_init(&ga, (int)sizeof(suginfo_T), 1);
+ ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len);
+
+ stp = &SUG(ga, 0);
+ for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; i++) {
+ // round 1: get a suggestion from su_ga
+ // round 2: get a suggestion from su_sga
+ for (round = 1; round <= 2; round++) {
+ gap = round == 1 ? &su->su_ga : &su->su_sga;
+ if (i < gap->ga_len) {
+ // Don't add a word if it's already there.
+ p = SUG(*gap, i).st_word;
+ int j;
+ for (j = 0; j < ga.ga_len; j++) {
+ if (STRCMP(stp[j].st_word, p) == 0) {
+ break;
+ }
+ }
+ if (j == ga.ga_len) {
+ stp[ga.ga_len++] = SUG(*gap, i);
+ } else {
+ xfree(p);
+ }
+ }
+ }
+ }
+
+ ga_clear(&su->su_ga);
+ ga_clear(&su->su_sga);
+
+ // Truncate the list to the number of suggestions that will be displayed.
+ if (ga.ga_len > su->su_maxcount) {
+ for (int i = su->su_maxcount; i < ga.ga_len; i++) {
+ xfree(stp[i].st_word);
+ }
+ ga.ga_len = su->su_maxcount;
+ }
+
+ su->su_ga = ga;
+}
+
+/// For the goodword in "stp" compute the soundalike score compared to the
+/// badword.
+///
+/// @param badsound sound-folded badword
+static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound)
+{
+ char_u *p;
+ char_u *pbad;
+ char_u *pgood;
+ char_u badsound2[MAXWLEN];
+ char_u fword[MAXWLEN];
+ char_u goodsound[MAXWLEN];
+ char_u goodword[MAXWLEN];
+ int lendiff;
+
+ lendiff = su->su_badlen - stp->st_orglen;
+ if (lendiff >= 0) {
+ pbad = badsound;
+ } else {
+ // soundfold the bad word with more characters following
+ (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN);
+
+ // When joining two words the sound often changes a lot. E.g., "t he"
+ // sounds like "t h" while "the" sounds like "@". Avoid that by
+ // removing the space. Don't do it when the good word also contains a
+ // space.
+ if (ascii_iswhite(su->su_badptr[su->su_badlen])
+ && *skiptowhite(stp->st_word) == NUL) {
+ for (p = fword; *(p = skiptowhite(p)) != NUL;) {
+ STRMOVE(p, p + 1);
+ }
+ }
+
+ spell_soundfold(slang, fword, true, badsound2);
+ pbad = badsound2;
+ }
+
+ if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) {
+ // Add part of the bad word to the good word, so that we soundfold
+ // what replaces the bad word.
+ STRCPY(goodword, stp->st_word);
+ STRLCPY(goodword + stp->st_wordlen,
+ su->su_badptr + su->su_badlen - lendiff, lendiff + 1);
+ pgood = goodword;
+ } else {
+ pgood = stp->st_word;
+ }
+
+ // Sound-fold the word and compute the score for the difference.
+ spell_soundfold(slang, pgood, false, goodsound);
+
+ return soundalike_score(goodsound, pbad);
+}
+
+/// structure used to store soundfolded words that add_sound_suggest() has
+/// handled already.
+typedef struct {
+ int16_t sft_score; ///< lowest score used
+ char_u sft_word[1]; ///< soundfolded word, actually longer
+} sftword_T;
+
+static sftword_T dumsft;
+#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft)))
+#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key)
+
+/// Prepare for calling suggest_try_soundalike().
+static void suggest_try_soundalike_prep(void)
+{
+ langp_T *lp;
+ slang_T *slang;
+
+ // Do this for all languages that support sound folding and for which a
+ // .sug file has been loaded.
+ for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
+ lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang = lp->lp_slang;
+ if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
+ // prepare the hashtable used by add_sound_suggest()
+ hash_init(&slang->sl_sounddone);
+ }
+ }
+}
+
+/// Find suggestions by comparing the word in a sound-a-like form.
+/// Note: This doesn't support postponed prefixes.
+static void suggest_try_soundalike(suginfo_T *su)
+{
+ char_u salword[MAXWLEN];
+ langp_T *lp;
+ slang_T *slang;
+
+ // Do this for all languages that support sound folding and for which a
+ // .sug file has been loaded.
+ for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
+ lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang = lp->lp_slang;
+ if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
+ // soundfold the bad word
+ spell_soundfold(slang, su->su_fbadword, true, salword);
+
+ // try all kinds of inserts/deletes/swaps/etc.
+ // TODO(vim): also soundfold the next words, so that we can try joining
+ // and splitting
+#ifdef SUGGEST_PROFILE
+ prof_init();
+#endif
+ suggest_trie_walk(su, lp, salword, true);
+#ifdef SUGGEST_PROFILE
+ prof_report("soundalike");
+#endif
+ }
+ }
+}
+
+/// Finish up after calling suggest_try_soundalike().
+static void suggest_try_soundalike_finish(void)
+{
+ langp_T *lp;
+ slang_T *slang;
+ int todo;
+ hashitem_T *hi;
+
+ // Do this for all languages that support sound folding and for which a
+ // .sug file has been loaded.
+ for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
+ lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang = lp->lp_slang;
+ if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
+ // Free the info about handled words.
+ todo = (int)slang->sl_sounddone.ht_used;
+ for (hi = slang->sl_sounddone.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ xfree(HI2SFT(hi));
+ todo--;
+ }
+ }
+
+ // Clear the hashtable, it may also be used by another region.
+ hash_clear(&slang->sl_sounddone);
+ hash_init(&slang->sl_sounddone);
+ }
+ }
+}
+
+/// A match with a soundfolded word is found. Add the good word(s) that
+/// produce this soundfolded word.
+///
+/// @param score soundfold score
+static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp)
+{
+ slang_T *slang = lp->lp_slang; // language for sound folding
+ int sfwordnr;
+ char_u *nrline;
+ int orgnr;
+ char_u theword[MAXWLEN];
+ int i;
+ int wlen;
+ char_u *byts;
+ idx_T *idxs;
+ int n;
+ int wordcount;
+ int wc;
+ int goodscore;
+ hash_T hash;
+ hashitem_T *hi;
+ sftword_T *sft;
+ int bc, gc;
+ int limit;
+
+ // It's very well possible that the same soundfold word is found several
+ // times with different scores. Since the following is quite slow only do
+ // the words that have a better score than before. Use a hashtable to
+ // remember the words that have been done.
+ hash = hash_hash(goodword);
+ const size_t goodword_len = STRLEN(goodword);
+ hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len,
+ hash);
+ if (HASHITEM_EMPTY(hi)) {
+ sft = xmalloc(sizeof(sftword_T) + goodword_len);
+ sft->sft_score = (int16_t)score;
+ memcpy(sft->sft_word, goodword, goodword_len + 1);
+ hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash);
+ } else {
+ sft = HI2SFT(hi);
+ if (score >= sft->sft_score) {
+ return;
+ }
+ sft->sft_score = (int16_t)score;
+ }
+
+ // Find the word nr in the soundfold tree.
+ sfwordnr = soundfold_find(slang, goodword);
+ if (sfwordnr < 0) {
+ internal_error("add_sound_suggest()");
+ return;
+ }
+
+ // Go over the list of good words that produce this soundfold word
+ nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false);
+ orgnr = 0;
+ while (*nrline != NUL) {
+ // The wordnr was stored in a minimal nr of bytes as an offset to the
+ // previous wordnr.
+ orgnr += bytes2offset(&nrline);
+
+ byts = slang->sl_fbyts;
+ idxs = slang->sl_fidxs;
+
+ // Lookup the word "orgnr" one of the two tries.
+ n = 0;
+ wordcount = 0;
+ for (wlen = 0; wlen < MAXWLEN - 3; wlen++) {
+ i = 1;
+ if (wordcount == orgnr && byts[n + 1] == NUL) {
+ break; // found end of word
+ }
+ if (byts[n + 1] == NUL) {
+ wordcount++;
+ }
+
+ // skip over the NUL bytes
+ for (; byts[n + i] == NUL; i++) {
+ if (i > byts[n]) { // safety check
+ STRCPY(theword + wlen, "BAD");
+ wlen += 3;
+ goto badword;
+ }
+ }
+
+ // One of the siblings must have the word.
+ for (; i < byts[n]; i++) {
+ wc = idxs[idxs[n + i]]; // nr of words under this byte
+ if (wordcount + wc > orgnr) {
+ break;
+ }
+ wordcount += wc;
+ }
+
+ theword[wlen] = byts[n + i];
+ n = idxs[n + i];
+ }
+badword:
+ theword[wlen] = NUL;
+
+ // Go over the possible flags and regions.
+ for (; i <= byts[n] && byts[n + i] == NUL; i++) {
+ char_u cword[MAXWLEN];
+ char_u *p;
+ int flags = (int)idxs[n + i];
+
+ // Skip words with the NOSUGGEST flag
+ if (flags & WF_NOSUGGEST) {
+ continue;
+ }
+
+ if (flags & WF_KEEPCAP) {
+ // Must find the word in the keep-case tree.
+ find_keepcap_word(slang, theword, cword);
+ p = cword;
+ } else {
+ flags |= su->su_badflags;
+ if ((flags & WF_CAPMASK) != 0) {
+ // Need to fix case according to "flags".
+ make_case_word(theword, cword, flags);
+ p = cword;
+ } else {
+ p = theword;
+ }
+ }
+
+ // Add the suggestion.
+ if (sps_flags & SPS_DOUBLE) {
+ // Add the suggestion if the score isn't too bad.
+ if (score <= su->su_maxscore) {
+ add_suggestion(su, &su->su_sga, p, su->su_badlen,
+ score, 0, false, slang, false);
+ }
+ } else {
+ // Add a penalty for words in another region.
+ if ((flags & WF_REGION)
+ && (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) {
+ goodscore = SCORE_REGION;
+ } else {
+ goodscore = 0;
+ }
+
+ // Add a small penalty for changing the first letter from
+ // lower to upper case. Helps for "tath" -> "Kath", which is
+ // less common than "tath" -> "path". Don't do it when the
+ // letter is the same, that has already been counted.
+ gc = utf_ptr2char((char *)p);
+ if (SPELL_ISUPPER(gc)) {
+ bc = utf_ptr2char((char *)su->su_badword);
+ if (!SPELL_ISUPPER(bc)
+ && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) {
+ goodscore += SCORE_ICASE / 2;
+ }
+ }
+
+ // Compute the score for the good word. This only does letter
+ // insert/delete/swap/replace. REP items are not considered,
+ // which may make the score a bit higher.
+ // Use a limit for the score to make it work faster. Use
+ // MAXSCORE(), because RESCORE() will change the score.
+ // If the limit is very high then the iterative method is
+ // inefficient, using an array is quicker.
+ limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
+ if (limit > SCORE_LIMITMAX) {
+ goodscore += spell_edit_score(slang, su->su_badword, p);
+ } else {
+ goodscore += spell_edit_score_limit(slang, su->su_badword,
+ p, limit);
+ }
+
+ // When going over the limit don't bother to do the rest.
+ if (goodscore < SCORE_MAXMAX) {
+ // Give a bonus to words seen before.
+ goodscore = score_wordcount_adj(slang, goodscore, p, false);
+
+ // Add the suggestion if the score isn't too bad.
+ goodscore = RESCORE(goodscore, score);
+ if (goodscore <= su->su_sfmaxscore) {
+ add_suggestion(su, &su->su_ga, p, su->su_badlen,
+ goodscore, score, true, slang, true);
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Find word "word" in fold-case tree for "slang" and return the word number.
+static int soundfold_find(slang_T *slang, char_u *word)
+{
+ idx_T arridx = 0;
+ int len;
+ int wlen = 0;
+ int c;
+ char_u *ptr = word;
+ char_u *byts;
+ idx_T *idxs;
+ int wordnr = 0;
+
+ byts = slang->sl_sbyts;
+ idxs = slang->sl_sidxs;
+
+ for (;;) {
+ // First byte is the number of possible bytes.
+ len = byts[arridx++];
+
+ // If the first possible byte is a zero the word could end here.
+ // If the word ends we found the word. If not skip the NUL bytes.
+ c = ptr[wlen];
+ if (byts[arridx] == NUL) {
+ if (c == NUL) {
+ break;
+ }
+
+ // Skip over the zeros, there can be several.
+ while (len > 0 && byts[arridx] == NUL) {
+ arridx++;
+ len--;
+ }
+ if (len == 0) {
+ return -1; // no children, word should have ended here
+ }
+ wordnr++;
+ }
+
+ // If the word ends we didn't find it.
+ if (c == NUL) {
+ return -1;
+ }
+
+ // Perform a binary search in the list of accepted bytes.
+ if (c == TAB) { // <Tab> is handled like <Space>
+ c = ' ';
+ }
+ while (byts[arridx] < c) {
+ // The word count is in the first idxs[] entry of the child.
+ wordnr += idxs[idxs[arridx]];
+ arridx++;
+ if (--len == 0) { // end of the bytes, didn't find it
+ return -1;
+ }
+ }
+ if (byts[arridx] != c) { // didn't find the byte
+ return -1;
+ }
+
+ // Continue at the child (if there is one).
+ arridx = idxs[arridx];
+ wlen++;
+
+ // One space in the good word may stand for several spaces in the
+ // checked word.
+ if (c == ' ') {
+ while (ptr[wlen] == ' ' || ptr[wlen] == TAB) {
+ wlen++;
+ }
+ }
+ }
+
+ return wordnr;
+}
+
+/// Returns true if "c1" and "c2" are similar characters according to the MAP
+/// lines in the .aff file.
+static bool similar_chars(slang_T *slang, int c1, int c2)
+{
+ int m1, m2;
+ char buf[MB_MAXBYTES + 1];
+ hashitem_T *hi;
+
+ if (c1 >= 256) {
+ buf[utf_char2bytes(c1, (char *)buf)] = 0;
+ hi = hash_find(&slang->sl_map_hash, buf);
+ if (HASHITEM_EMPTY(hi)) {
+ m1 = 0;
+ } else {
+ m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1);
+ }
+ } else {
+ m1 = slang->sl_map_array[c1];
+ }
+ if (m1 == 0) {
+ return false;
+ }
+
+ if (c2 >= 256) {
+ buf[utf_char2bytes(c2, (char *)buf)] = 0;
+ hi = hash_find(&slang->sl_map_hash, buf);
+ if (HASHITEM_EMPTY(hi)) {
+ m2 = 0;
+ } else {
+ m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1);
+ }
+ } else {
+ m2 = slang->sl_map_array[c2];
+ }
+
+ return m1 == m2;
+}
+
+/// Adds a suggestion to the list of suggestions.
+/// For a suggestion that is already in the list the lowest score is remembered.
+///
+/// @param gap either su_ga or su_sga
+/// @param badlenarg len of bad word replaced with "goodword"
+/// @param had_bonus value for st_had_bonus
+/// @param slang language for sound folding
+/// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score.
+static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg,
+ int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf)
+{
+ int goodlen; // len of goodword changed
+ int badlen; // len of bad word changed
+ suggest_T *stp;
+ suggest_T new_sug;
+
+ // Minimize "badlen" for consistency. Avoids that changing "the the" to
+ // "thee the" is added next to changing the first "the" the "thee".
+ const char_u *pgood = goodword + STRLEN(goodword);
+ char_u *pbad = su->su_badptr + badlenarg;
+ for (;;) {
+ goodlen = (int)(pgood - goodword);
+ badlen = (int)(pbad - su->su_badptr);
+ if (goodlen <= 0 || badlen <= 0) {
+ break;
+ }
+ MB_PTR_BACK(goodword, pgood);
+ MB_PTR_BACK(su->su_badptr, pbad);
+ if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) {
+ break;
+ }
+ }
+
+ if (badlen == 0 && goodlen == 0) {
+ // goodword doesn't change anything; may happen for "the the" changing
+ // the first "the" to itself.
+ return;
+ }
+
+ int i;
+ if (GA_EMPTY(gap)) {
+ i = -1;
+ } else {
+ // Check if the word is already there. Also check the length that is
+ // being replaced "thes," -> "these" is a different suggestion from
+ // "thes" -> "these".
+ stp = &SUG(*gap, 0);
+ for (i = gap->ga_len; --i >= 0; ++stp) {
+ if (stp->st_wordlen == goodlen
+ && stp->st_orglen == badlen
+ && STRNCMP(stp->st_word, goodword, goodlen) == 0) {
+ // Found it. Remember the word with the lowest score.
+ if (stp->st_slang == NULL) {
+ stp->st_slang = slang;
+ }
+
+ new_sug.st_score = score;
+ new_sug.st_altscore = altscore;
+ new_sug.st_had_bonus = had_bonus;
+
+ if (stp->st_had_bonus != had_bonus) {
+ // Only one of the two had the soundalike score computed.
+ // Need to do that for the other one now, otherwise the
+ // scores can't be compared. This happens because
+ // suggest_try_change() doesn't compute the soundalike
+ // word to keep it fast, while some special methods set
+ // the soundalike score to zero.
+ if (had_bonus) {
+ rescore_one(su, stp);
+ } else {
+ new_sug.st_word = stp->st_word;
+ new_sug.st_wordlen = stp->st_wordlen;
+ new_sug.st_slang = stp->st_slang;
+ new_sug.st_orglen = badlen;
+ rescore_one(su, &new_sug);
+ }
+ }
+
+ if (stp->st_score > new_sug.st_score) {
+ stp->st_score = new_sug.st_score;
+ stp->st_altscore = new_sug.st_altscore;
+ stp->st_had_bonus = new_sug.st_had_bonus;
+ }
+ break;
+ }
+ }
+ }
+
+ if (i < 0) {
+ // Add a suggestion.
+ stp = GA_APPEND_VIA_PTR(suggest_T, gap);
+ stp->st_word = vim_strnsave(goodword, (size_t)goodlen);
+ stp->st_wordlen = goodlen;
+ stp->st_score = score;
+ stp->st_altscore = altscore;
+ stp->st_had_bonus = had_bonus;
+ stp->st_orglen = badlen;
+ stp->st_slang = slang;
+
+ // If we have too many suggestions now, sort the list and keep
+ // the best suggestions.
+ if (gap->ga_len > SUG_MAX_COUNT(su)) {
+ if (maxsf) {
+ su->su_sfmaxscore = cleanup_suggestions(gap,
+ su->su_sfmaxscore, SUG_CLEAN_COUNT(su));
+ } else {
+ su->su_maxscore = cleanup_suggestions(gap,
+ su->su_maxscore, SUG_CLEAN_COUNT(su));
+ }
+ }
+ }
+}
+
+/// Suggestions may in fact be flagged as errors. Esp. for banned words and
+/// for split words, such as "the the". Remove these from the list here.
+///
+/// @param gap either su_ga or su_sga
+static void check_suggestions(suginfo_T *su, garray_T *gap)
+{
+ suggest_T *stp;
+ char_u longword[MAXWLEN + 1];
+ int len;
+ hlf_T attr;
+
+ if (gap->ga_len == 0) {
+ return;
+ }
+ stp = &SUG(*gap, 0);
+ for (int i = gap->ga_len - 1; i >= 0; i--) {
+ // Need to append what follows to check for "the the".
+ STRLCPY(longword, stp[i].st_word, MAXWLEN + 1);
+ len = stp[i].st_wordlen;
+ STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen,
+ MAXWLEN - len + 1);
+ attr = HLF_COUNT;
+ (void)spell_check(curwin, longword, &attr, NULL, false);
+ if (attr != HLF_COUNT) {
+ // Remove this entry.
+ xfree(stp[i].st_word);
+ gap->ga_len--;
+ if (i < gap->ga_len) {
+ memmove(stp + i, stp + i + 1, sizeof(suggest_T) * (size_t)(gap->ga_len - i));
+ }
+ }
+ }
+}
+
+/// Add a word to be banned.
+static void add_banned(suginfo_T *su, char_u *word)
+{
+ char_u *s;
+ hash_T hash;
+ hashitem_T *hi;
+
+ hash = hash_hash(word);
+ const size_t word_len = STRLEN(word);
+ hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash);
+ if (HASHITEM_EMPTY(hi)) {
+ s = xmemdupz(word, word_len);
+ hash_add_item(&su->su_banned, hi, s, hash);
+ }
+}
+
+/// Recompute the score for all suggestions if sound-folding is possible. This
+/// is slow, thus only done for the final results.
+static void rescore_suggestions(suginfo_T *su)
+{
+ if (su->su_sallang != NULL) {
+ for (int i = 0; i < su->su_ga.ga_len; i++) {
+ rescore_one(su, &SUG(su->su_ga, i));
+ }
+ }
+}
+
+/// Recompute the score for one suggestion if sound-folding is possible.
+static void rescore_one(suginfo_T *su, suggest_T *stp)
+{
+ slang_T *slang = stp->st_slang;
+ char_u sal_badword[MAXWLEN];
+ char_u *p;
+
+ // Only rescore suggestions that have no sal score yet and do have a
+ // language.
+ if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) {
+ if (slang == su->su_sallang) {
+ p = su->su_sal_badword;
+ } else {
+ spell_soundfold(slang, su->su_fbadword, true, sal_badword);
+ p = sal_badword;
+ }
+
+ stp->st_altscore = stp_sal_score(stp, su, slang, p);
+ if (stp->st_altscore == SCORE_MAXMAX) {
+ stp->st_altscore = SCORE_BIG;
+ }
+ stp->st_score = RESCORE(stp->st_score, stp->st_altscore);
+ stp->st_had_bonus = true;
+ }
+}
+
+/// Function given to qsort() to sort the suggestions on st_score.
+/// First on "st_score", then "st_altscore" then alphabetically.
+static int sug_compare(const void *s1, const void *s2)
+{
+ suggest_T *p1 = (suggest_T *)s1;
+ suggest_T *p2 = (suggest_T *)s2;
+ int n = p1->st_score - p2->st_score;
+
+ if (n == 0) {
+ n = p1->st_altscore - p2->st_altscore;
+ if (n == 0) {
+ n = STRICMP(p1->st_word, p2->st_word);
+ }
+ }
+ return n;
+}
+
+/// Cleanup the suggestions:
+/// - Sort on score.
+/// - Remove words that won't be displayed.
+///
+/// @param keep nr of suggestions to keep
+///
+/// @return the maximum score in the list or "maxscore" unmodified.
+static int cleanup_suggestions(garray_T *gap, int maxscore, int keep)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (gap->ga_len > 0) {
+ // Sort the list.
+ qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
+
+ // Truncate the list to the number of suggestions that will be displayed.
+ if (gap->ga_len > keep) {
+ suggest_T *const stp = &SUG(*gap, 0);
+
+ for (int i = keep; i < gap->ga_len; i++) {
+ xfree(stp[i].st_word);
+ }
+ gap->ga_len = keep;
+ if (keep >= 1) {
+ return stp[keep - 1].st_score;
+ }
+ }
+ }
+ return maxscore;
+}
+
+/// Compute a score for two sound-a-like words.
+/// This permits up to two inserts/deletes/swaps/etc. to keep things fast.
+/// Instead of a generic loop we write out the code. That keeps it fast by
+/// avoiding checks that will not be possible.
+///
+/// @param goodstart sound-folded good word
+/// @param badstart sound-folded bad word
+static int soundalike_score(char_u *goodstart, char_u *badstart)
+{
+ char_u *goodsound = goodstart;
+ char_u *badsound = badstart;
+ int goodlen;
+ int badlen;
+ int n;
+ char_u *pl, *ps;
+ char_u *pl2, *ps2;
+ int score = 0;
+
+ // Adding/inserting "*" at the start (word starts with vowel) shouldn't be
+ // counted so much, vowels in the middle of the word aren't counted at all.
+ if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) {
+ if ((badsound[0] == NUL && goodsound[1] == NUL)
+ || (goodsound[0] == NUL && badsound[1] == NUL)) {
+ // changing word with vowel to word without a sound
+ return SCORE_DEL;
+ }
+ if (badsound[0] == NUL || goodsound[0] == NUL) {
+ // more than two changes
+ return SCORE_MAXMAX;
+ }
+
+ if (badsound[1] == goodsound[1]
+ || (badsound[1] != NUL
+ && goodsound[1] != NUL
+ && badsound[2] == goodsound[2])) {
+ // handle like a substitute
+ } else {
+ score = 2 * SCORE_DEL / 3;
+ if (*badsound == '*') {
+ badsound++;
+ } else {
+ goodsound++;
+ }
+ }
+ }
+
+ goodlen = (int)STRLEN(goodsound);
+ badlen = (int)STRLEN(badsound);
+
+ // Return quickly if the lengths are too different to be fixed by two
+ // changes.
+ n = goodlen - badlen;
+ if (n < -2 || n > 2) {
+ return SCORE_MAXMAX;
+ }
+
+ if (n > 0) {
+ pl = goodsound; // goodsound is longest
+ ps = badsound;
+ } else {
+ pl = badsound; // badsound is longest
+ ps = goodsound;
+ }
+
+ // Skip over the identical part.
+ while (*pl == *ps && *pl != NUL) {
+ pl++;
+ ps++;
+ }
+
+ switch (n) {
+ case -2:
+ case 2:
+ // Must delete two characters from "pl".
+ pl++; // first delete
+ while (*pl == *ps) {
+ pl++;
+ ps++;
+ }
+ // strings must be equal after second delete
+ if (STRCMP(pl + 1, ps) == 0) {
+ return score + SCORE_DEL * 2;
+ }
+
+ // Failed to compare.
+ break;
+
+ case -1:
+ case 1:
+ // Minimal one delete from "pl" required.
+
+ // 1: delete
+ pl2 = pl + 1;
+ ps2 = ps;
+ while (*pl2 == *ps2) {
+ if (*pl2 == NUL) { // reached the end
+ return score + SCORE_DEL;
+ }
+ pl2++;
+ ps2++;
+ }
+
+ // 2: delete then swap, then rest must be equal
+ if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
+ && STRCMP(pl2 + 2, ps2 + 2) == 0) {
+ return score + SCORE_DEL + SCORE_SWAP;
+ }
+
+ // 3: delete then substitute, then the rest must be equal
+ if (STRCMP(pl2 + 1, ps2 + 1) == 0) {
+ return score + SCORE_DEL + SCORE_SUBST;
+ }
+
+ // 4: first swap then delete
+ if (pl[0] == ps[1] && pl[1] == ps[0]) {
+ pl2 = pl + 2; // swap, skip two chars
+ ps2 = ps + 2;
+ while (*pl2 == *ps2) {
+ pl2++;
+ ps2++;
+ }
+ // delete a char and then strings must be equal
+ if (STRCMP(pl2 + 1, ps2) == 0) {
+ return score + SCORE_SWAP + SCORE_DEL;
+ }
+ }
+
+ // 5: first substitute then delete
+ pl2 = pl + 1; // substitute, skip one char
+ ps2 = ps + 1;
+ while (*pl2 == *ps2) {
+ pl2++;
+ ps2++;
+ }
+ // delete a char and then strings must be equal
+ if (STRCMP(pl2 + 1, ps2) == 0) {
+ return score + SCORE_SUBST + SCORE_DEL;
+ }
+
+ // Failed to compare.
+ break;
+
+ case 0:
+ // Lengths are equal, thus changes must result in same length: An
+ // insert is only possible in combination with a delete.
+ // 1: check if for identical strings
+ if (*pl == NUL) {
+ return score;
+ }
+
+ // 2: swap
+ if (pl[0] == ps[1] && pl[1] == ps[0]) {
+ pl2 = pl + 2; // swap, skip two chars
+ ps2 = ps + 2;
+ while (*pl2 == *ps2) {
+ if (*pl2 == NUL) { // reached the end
+ return score + SCORE_SWAP;
+ }
+ pl2++;
+ ps2++;
+ }
+ // 3: swap and swap again
+ if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
+ && STRCMP(pl2 + 2, ps2 + 2) == 0) {
+ return score + SCORE_SWAP + SCORE_SWAP;
+ }
+
+ // 4: swap and substitute
+ if (STRCMP(pl2 + 1, ps2 + 1) == 0) {
+ return score + SCORE_SWAP + SCORE_SUBST;
+ }
+ }
+
+ // 5: substitute
+ pl2 = pl + 1;
+ ps2 = ps + 1;
+ while (*pl2 == *ps2) {
+ if (*pl2 == NUL) { // reached the end
+ return score + SCORE_SUBST;
+ }
+ pl2++;
+ ps2++;
+ }
+
+ // 6: substitute and swap
+ if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
+ && STRCMP(pl2 + 2, ps2 + 2) == 0) {
+ return score + SCORE_SUBST + SCORE_SWAP;
+ }
+
+ // 7: substitute and substitute
+ if (STRCMP(pl2 + 1, ps2 + 1) == 0) {
+ return score + SCORE_SUBST + SCORE_SUBST;
+ }
+
+ // 8: insert then delete
+ pl2 = pl;
+ ps2 = ps + 1;
+ while (*pl2 == *ps2) {
+ pl2++;
+ ps2++;
+ }
+ if (STRCMP(pl2 + 1, ps2) == 0) {
+ return score + SCORE_INS + SCORE_DEL;
+ }
+
+ // 9: delete then insert
+ pl2 = pl + 1;
+ ps2 = ps;
+ while (*pl2 == *ps2) {
+ pl2++;
+ ps2++;
+ }
+ if (STRCMP(pl2, ps2 + 1) == 0) {
+ return score + SCORE_INS + SCORE_DEL;
+ }
+
+ // Failed to compare.
+ break;
+ }
+
+ return SCORE_MAXMAX;
+}
+
+/// Compute the "edit distance" to turn "badword" into "goodword". The less
+/// deletes/inserts/substitutes/swaps are required the lower the score.
+///
+/// The algorithm is described by Du and Chang, 1992.
+/// The implementation of the algorithm comes from Aspell editdist.cpp,
+/// edit_distance(). It has been converted from C++ to C and modified to
+/// support multi-byte characters.
+static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword)
+{
+ int *cnt;
+ int j, i;
+ int t;
+ int bc, gc;
+ int pbc, pgc;
+ int wbadword[MAXWLEN];
+ int wgoodword[MAXWLEN];
+
+ // Lengths with NUL.
+ int badlen;
+ int goodlen;
+ {
+ // Get the characters from the multi-byte strings and put them in an
+ // int array for easy access.
+ badlen = 0;
+ for (const char_u *p = badword; *p != NUL;) {
+ wbadword[badlen++] = mb_cptr2char_adv(&p);
+ }
+ wbadword[badlen++] = 0;
+ goodlen = 0;
+ for (const char_u *p = goodword; *p != NUL;) {
+ wgoodword[goodlen++] = mb_cptr2char_adv(&p);
+ }
+ wgoodword[goodlen++] = 0;
+ }
+
+ // We use "cnt" as an array: CNT(badword_idx, goodword_idx).
+#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)]
+ cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1));
+
+ CNT(0, 0) = 0;
+ for (j = 1; j <= goodlen; j++) {
+ CNT(0, j) = CNT(0, j - 1) + SCORE_INS;
+ }
+
+ for (i = 1; i <= badlen; i++) {
+ CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL;
+ for (j = 1; j <= goodlen; j++) {
+ bc = wbadword[i - 1];
+ gc = wgoodword[j - 1];
+ if (bc == gc) {
+ CNT(i, j) = CNT(i - 1, j - 1);
+ } else {
+ // Use a better score when there is only a case difference.
+ if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) {
+ CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1);
+ } else {
+ // For a similar character use SCORE_SIMILAR.
+ if (slang != NULL
+ && slang->sl_has_map
+ && similar_chars(slang, gc, bc)) {
+ CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1);
+ } else {
+ CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1);
+ }
+ }
+
+ if (i > 1 && j > 1) {
+ pbc = wbadword[i - 2];
+ pgc = wgoodword[j - 2];
+ if (bc == pgc && pbc == gc) {
+ t = SCORE_SWAP + CNT(i - 2, j - 2);
+ if (t < CNT(i, j)) {
+ CNT(i, j) = t;
+ }
+ }
+ }
+ t = SCORE_DEL + CNT(i - 1, j);
+ if (t < CNT(i, j)) {
+ CNT(i, j) = t;
+ }
+ t = SCORE_INS + CNT(i, j - 1);
+ if (t < CNT(i, j)) {
+ CNT(i, j) = t;
+ }
+ }
+ }
+ }
+
+ i = CNT(badlen - 1, goodlen - 1);
+ xfree(cnt);
+ return i;
+}
+
+typedef struct {
+ int badi;
+ int goodi;
+ int score;
+} limitscore_T;
+
+/// Like spell_edit_score(), but with a limit on the score to make it faster.
+/// May return SCORE_MAXMAX when the score is higher than "limit".
+///
+/// This uses a stack for the edits still to be tried.
+/// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support
+/// for multi-byte characters.
+static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit)
+{
+ return spell_edit_score_limit_w(slang, badword, goodword, limit);
+}
+
+/// Multi-byte version of spell_edit_score_limit().
+/// Keep it in sync with the above!
+static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit)
+{
+ limitscore_T stack[10]; // allow for over 3 * 2 edits
+ int stackidx;
+ int bi, gi;
+ int bi2, gi2;
+ int bc, gc;
+ int score;
+ int score_off;
+ int minscore;
+ int round;
+ int wbadword[MAXWLEN];
+ int wgoodword[MAXWLEN];
+
+ // Get the characters from the multi-byte strings and put them in an
+ // int array for easy access.
+ bi = 0;
+ for (const char_u *p = badword; *p != NUL;) {
+ wbadword[bi++] = mb_cptr2char_adv(&p);
+ }
+ wbadword[bi++] = 0;
+ gi = 0;
+ for (const char_u *p = goodword; *p != NUL;) {
+ wgoodword[gi++] = mb_cptr2char_adv(&p);
+ }
+ wgoodword[gi++] = 0;
+
+ // The idea is to go from start to end over the words. So long as
+ // characters are equal just continue, this always gives the lowest score.
+ // When there is a difference try several alternatives. Each alternative
+ // increases "score" for the edit distance. Some of the alternatives are
+ // pushed unto a stack and tried later, some are tried right away. At the
+ // end of the word the score for one alternative is known. The lowest
+ // possible score is stored in "minscore".
+ stackidx = 0;
+ bi = 0;
+ gi = 0;
+ score = 0;
+ minscore = limit + 1;
+
+ for (;;) {
+ // Skip over an equal part, score remains the same.
+ for (;;) {
+ bc = wbadword[bi];
+ gc = wgoodword[gi];
+
+ if (bc != gc) { // stop at a char that's different
+ break;
+ }
+ if (bc == NUL) { // both words end
+ if (score < minscore) {
+ minscore = score;
+ }
+ goto pop; // do next alternative
+ }
+ bi++;
+ gi++;
+ }
+
+ if (gc == NUL) { // goodword ends, delete badword chars
+ do {
+ if ((score += SCORE_DEL) >= minscore) {
+ goto pop; // do next alternative
+ }
+ } while (wbadword[++bi] != NUL);
+ minscore = score;
+ } else if (bc == NUL) { // badword ends, insert badword chars
+ do {
+ if ((score += SCORE_INS) >= minscore) {
+ goto pop; // do next alternative
+ }
+ } while (wgoodword[++gi] != NUL);
+ minscore = score;
+ } else { // both words continue
+ // If not close to the limit, perform a change. Only try changes
+ // that may lead to a lower score than "minscore".
+ // round 0: try deleting a char from badword
+ // round 1: try inserting a char in badword
+ for (round = 0; round <= 1; round++) {
+ score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
+ if (score_off < minscore) {
+ if (score_off + SCORE_EDIT_MIN >= minscore) {
+ // Near the limit, rest of the words must match. We
+ // can check that right now, no need to push an item
+ // onto the stack.
+ bi2 = bi + 1 - round;
+ gi2 = gi + round;
+ while (wgoodword[gi2] == wbadword[bi2]) {
+ if (wgoodword[gi2] == NUL) {
+ minscore = score_off;
+ break;
+ }
+ bi2++;
+ gi2++;
+ }
+ } else {
+ // try deleting a character from badword later
+ stack[stackidx].badi = bi + 1 - round;
+ stack[stackidx].goodi = gi + round;
+ stack[stackidx].score = score_off;
+ stackidx++;
+ }
+ }
+ }
+
+ if (score + SCORE_SWAP < minscore) {
+ // If swapping two characters makes a match then the
+ // substitution is more expensive, thus there is no need to
+ // try both.
+ if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) {
+ // Swap two characters, that is: skip them.
+ gi += 2;
+ bi += 2;
+ score += SCORE_SWAP;
+ continue;
+ }
+ }
+
+ // Substitute one character for another which is the same
+ // thing as deleting a character from both goodword and badword.
+ // Use a better score when there is only a case difference.
+ if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) {
+ score += SCORE_ICASE;
+ } else {
+ // For a similar character use SCORE_SIMILAR.
+ if (slang != NULL
+ && slang->sl_has_map
+ && similar_chars(slang, gc, bc)) {
+ score += SCORE_SIMILAR;
+ } else {
+ score += SCORE_SUBST;
+ }
+ }
+
+ if (score < minscore) {
+ // Do the substitution.
+ gi++;
+ bi++;
+ continue;
+ }
+ }
+pop:
+ // Get here to try the next alternative, pop it from the stack.
+ if (stackidx == 0) { // stack is empty, finished
+ break;
+ }
+
+ // pop an item from the stack
+ stackidx--;
+ gi = stack[stackidx].goodi;
+ bi = stack[stackidx].badi;
+ score = stack[stackidx].score;
+ }
+
+ // When the score goes over "limit" it may actually be much higher.
+ // Return a very large number to avoid going below the limit when giving a
+ // bonus.
+ if (minscore > limit) {
+ return SCORE_MAXMAX;
+ }
+ return minscore;
+}
diff --git a/src/nvim/spellsuggest.h b/src/nvim/spellsuggest.h
new file mode 100644
index 0000000000..8813a5b3f1
--- /dev/null
+++ b/src/nvim/spellsuggest.h
@@ -0,0 +1,9 @@
+#ifndef NVIM_SPELLSUGGEST_H
+#define NVIM_SPELLSUGGEST_H
+
+#include "nvim/garray.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "spellsuggest.h.generated.h"
+#endif
+#endif // NVIM_SPELLSUGGEST_H
diff --git a/src/nvim/state.c b/src/nvim/state.c
index d6cca71ad8..61740800a1 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -5,6 +5,7 @@
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
#include "nvim/getchar.h"
@@ -15,7 +16,6 @@
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/os/input.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 22effaade0..78312c738c 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -191,7 +191,7 @@ char *vim_strnsave_unquoted(const char *const string, const size_t length)
char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_newline)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
{
- char_u *d;
+ char *d;
char_u *escaped_string;
size_t l;
int csh_like;
@@ -238,7 +238,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n
// Allocate memory for the result and fill it.
escaped_string = xmalloc(length);
- d = escaped_string;
+ d = (char *)escaped_string;
// add opening quote
#ifdef WIN32
@@ -248,7 +248,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n
#endif
*d++ = '\'';
- for (const char_u *p = string; *p != NUL;) {
+ for (const char *p = (char *)string; *p != NUL;) {
#ifdef WIN32
if (!p_ssl) {
if (*p == '"') {
@@ -264,7 +264,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n
*d++ = '\\';
*d++ = '\'';
*d++ = '\'';
- ++p;
+ p++;
continue;
}
if ((*p == '\n' && (csh_like || do_newline))
@@ -276,7 +276,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n
*d++ = *p++;
continue;
}
- if (do_special && find_cmdline_var(p, &l) >= 0) {
+ if (do_special && find_cmdline_var((char_u *)p, &l) >= 0) {
*d++ = '\\'; // insert backslash
while (--l != SIZE_MAX) { // copy the var
*d++ = *p++;
@@ -431,8 +431,8 @@ int vim_stricmp(const char *s1, const char *s2)
if (*s1 == NUL) {
break; // strings match until NUL
}
- ++s1;
- ++s2;
+ s1++;
+ s2++;
}
return 0; // strings match
}
@@ -457,9 +457,9 @@ int vim_strnicmp(const char *s1, const char *s2, size_t len)
if (*s1 == NUL) {
break; // strings match until NUL
}
- ++s1;
- ++s2;
- --len;
+ s1++;
+ s2++;
+ len--;
}
return 0; // strings match
}
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 4ec4a57d68..47b5647a08 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -14,12 +14,13 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/vars.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
@@ -41,8 +42,8 @@
#include "nvim/os/time.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/sign.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
@@ -378,7 +379,7 @@ void syntax_start(win_T *wp, linenr_T lnum)
&& current_lnum < syn_buf->b_ml.ml_line_count) {
(void)syn_finish_line(false);
if (!current_state_stored) {
- ++current_lnum;
+ current_lnum++;
(void)store_current_state();
}
@@ -723,7 +724,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
} else if (found_m_endpos.col > current_col) {
current_col = found_m_endpos.col;
} else {
- ++current_col;
+ current_col++;
}
// syn_current_attr() will have skipped the check for
@@ -731,7 +732,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
// careful not to go past the NUL.
prev_current_col = current_col;
if (syn_getcurline()[current_col] != NUL) {
- ++current_col;
+ current_col++;
}
check_state_ends();
current_col = prev_current_col;
@@ -1029,7 +1030,7 @@ static void syn_stack_alloc(void)
// Move the states from the old array to the new one.
for (from = syn_block->b_sst_first; from != NULL;
from = from->sst_next) {
- ++to;
+ to++;
*to = *from;
to->sst_next = to + 1;
}
@@ -1500,7 +1501,7 @@ bool syntax_check_changed(linenr_T lnum)
/*
* Store the current state in b_sst_array[] for later use.
*/
- ++current_lnum;
+ current_lnum++;
(void)store_current_state();
}
}
@@ -2095,9 +2096,9 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
check_state_ends();
if (!GA_EMPTY(&current_state)
&& syn_getcurline()[current_col] != NUL) {
- ++current_col;
+ current_col++;
check_state_ends();
- --current_col;
+ current_col--;
}
}
} else if (can_spell != NULL) {
@@ -2506,7 +2507,7 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force)
static void push_current_state(int idx)
{
stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, &current_state);
- memset(p, 0, sizeof(*p));
+ CLEAR_POINTER(p);
p->si_idx = idx;
}
@@ -2582,7 +2583,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
if (spp->sp_type != SPTYPE_START) {
break;
}
- ++idx;
+ idx++;
}
/*
@@ -2590,7 +2591,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
*/
if (spp->sp_type == SPTYPE_SKIP) {
spp_skip = spp;
- ++idx;
+ idx++;
} else {
spp_skip = NULL;
}
@@ -3653,7 +3654,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
&& SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END) {
put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
}
- --idx;
+ idx--;
msg_putchar(' ');
}
syn_list_flags(namelist1, spp->sp_flags, attr);
@@ -3927,7 +3928,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht)
if (HASHITEM_EMPTY(hi)) {
continue;
}
- --todo;
+ todo--;
kp_prev = NULL;
for (kp = HI2KE(hi); kp != NULL;) {
if (kp->k_syn.id == id) {
@@ -3967,7 +3968,7 @@ static void clear_keywtab(hashtab_T *ht)
todo = (int)ht->ht_used;
for (hi = ht->ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
- --todo;
+ todo--;
for (kp = HI2KE(hi); kp != NULL; kp = kp_next) {
kp_next = kp->ke_next;
xfree(kp->next_list);
@@ -4257,7 +4258,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
}
if (arg[0] == '@') {
- ++arg;
+ arg++;
rest = get_group_name(arg, &group_name_end);
if (rest == NULL) {
emsg(_("E397: Filename required"));
@@ -4453,7 +4454,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing)
// get the pattern.
init_syn_patterns();
- memset(&item, 0, sizeof(item));
+ CLEAR_FIELD(item);
rest = get_syn_pattern(rest, &item);
if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) {
syn_opt_arg.flags |= HL_HAS_EOL;
@@ -4583,7 +4584,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing)
// must be a pattern or matchgroup then
key_end = rest;
while (*key_end && !ascii_iswhite(*key_end) && *key_end != '=') {
- ++key_end;
+ key_end++;
}
xfree(key);
key = vim_strnsave_up(rest, (size_t)(key_end - rest));
@@ -4708,8 +4709,8 @@ static void syn_cmd_region(exarg_T *eap, int syncing)
SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
syn_opt_arg.next_list;
}
- ++curwin->w_s->b_syn_patterns.ga_len;
- ++idx;
+ curwin->w_s->b_syn_patterns.ga_len++;
+ idx++;
if (syn_opt_arg.flags & HL_FOLD) {
++curwin->w_s->b_syn_folditems;
}
@@ -4945,7 +4946,7 @@ static int syn_add_cluster(char_u *name)
syn_cluster_T *scp = GA_APPEND_VIA_PTR(syn_cluster_T,
&curwin->w_s->b_syn_clusters);
- memset(scp, 0, sizeof(*scp));
+ CLEAR_POINTER(scp);
scp->scl_name = name;
scp->scl_name_u = vim_strsave_up(name);
scp->scl_list = NULL;
@@ -5048,7 +5049,7 @@ static void init_syn_patterns(void)
*/
static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
{
- char_u *end;
+ char *end;
int *p;
int idx;
char *cpo_save;
@@ -5058,13 +5059,13 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
return NULL;
}
- end = skip_regexp(arg + 1, *arg, TRUE, NULL);
- if (*end != *arg) { // end delimiter not found
+ end = (char *)skip_regexp(arg + 1, *arg, true, NULL);
+ if (*end != (char)(*arg)) { // end delimiter not found
semsg(_("E401: Pattern delimiter not found: %s"), arg);
return NULL;
}
// store the pattern and compiled regexp program
- ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - arg) - 1);
+ ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - (char *)arg) - 1);
// Make 'cpoptions' empty, to avoid the 'l' flag
cpo_save = p_cpo;
@@ -5081,7 +5082,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
/*
* Check for a match, highlight or region offset.
*/
- ++end;
+ end++;
do {
for (idx = SPO_COUNT; --idx >= 0;) {
if (STRNCMP(end, spo_name_tab[idx], 3) == 0) {
@@ -5106,7 +5107,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
ci->sp_off_flags |= (int16_t)(1 << idx);
if (idx == SPO_LC_OFF) { // lc=99
end += 3;
- *p = getdigits_int((char **)&end, true, 0);
+ *p = getdigits_int(&end, true, 0);
// "lc=" offset automatically sets "ms=" offset
if (!(ci->sp_off_flags & (1 << SPO_MS_OFF))) {
@@ -5117,16 +5118,16 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
end += 4;
if (*end == '+') {
end++;
- *p = getdigits_int((char **)&end, true, 0); // positive offset
+ *p = getdigits_int(&end, true, 0); // positive offset
} else if (*end == '-') {
end++;
- *p = -getdigits_int((char **)&end, true, 0); // negative offset
+ *p = -getdigits_int(&end, true, 0); // negative offset
}
}
if (*end != ',') {
break;
}
- ++end;
+ end++;
}
}
} while (idx >= 0);
@@ -5135,7 +5136,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
semsg(_("E402: Garbage after pattern: %s"), arg);
return NULL;
}
- return (char_u *)skipwhite((char *)end);
+ return (char_u *)skipwhite(end);
}
/*
@@ -5144,7 +5145,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
static void syn_cmd_sync(exarg_T *eap, int syncing)
{
char_u *arg_start = (char_u *)eap->arg;
- char_u *arg_end;
+ char *arg_end;
char_u *key = NULL;
char_u *next_arg;
int illegal = false;
@@ -5157,21 +5158,21 @@ static void syn_cmd_sync(exarg_T *eap, int syncing)
}
while (!ends_excmd(*arg_start)) {
- arg_end = skiptowhite(arg_start);
- next_arg = (char_u *)skipwhite((char *)arg_end);
+ arg_end = (char *)skiptowhite(arg_start);
+ next_arg = (char_u *)skipwhite(arg_end);
xfree(key);
- key = vim_strnsave_up(arg_start, (size_t)(arg_end - arg_start));
+ key = vim_strnsave_up(arg_start, (size_t)(arg_end - (char *)arg_start));
if (STRCMP(key, "CCOMMENT") == 0) {
if (!eap->skip) {
curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
}
if (!ends_excmd(*next_arg)) {
- arg_end = skiptowhite(next_arg);
+ arg_end = (char *)skiptowhite(next_arg);
if (!eap->skip) {
curwin->w_s->b_syn_sync_id =
- (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - next_arg));
+ (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - (char *)next_arg));
}
- next_arg = (char_u *)skipwhite((char *)arg_end);
+ next_arg = (char_u *)skipwhite(arg_end);
} else if (!eap->skip) {
curwin->w_s->b_syn_sync_id = (int16_t)syn_name2id("Comment");
}
@@ -5180,17 +5181,17 @@ static void syn_cmd_sync(exarg_T *eap, int syncing)
|| STRNCMP(key, "MAXLINES", 8) == 0
|| STRNCMP(key, "LINEBREAKS", 10) == 0) {
if (key[4] == 'S') {
- arg_end = key + 6;
+ arg_end = (char *)key + 6;
} else if (key[0] == 'L') {
- arg_end = key + 11;
+ arg_end = (char *)key + 11;
} else {
- arg_end = key + 9;
+ arg_end = (char *)key + 9;
}
if (arg_end[-1] != '=' || !ascii_isdigit(*arg_end)) {
illegal = TRUE;
break;
}
- linenr_T n = getdigits_int32((char **)&arg_end, false, 0);
+ linenr_T n = getdigits_int32(&arg_end, false, 0);
if (!eap->skip) {
if (key[4] == 'B') {
curwin->w_s->b_syn_sync_linebreaks = n;
@@ -5215,16 +5216,16 @@ static void syn_cmd_sync(exarg_T *eap, int syncing)
finished = TRUE;
break;
}
- arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL);
- if (*arg_end != *next_arg) { // end delimiter not found
- illegal = TRUE;
+ arg_end = (char *)skip_regexp(next_arg + 1, *next_arg, true, NULL);
+ if (*arg_end != (char)(*next_arg)) { // end delimiter not found
+ illegal = true;
break;
}
if (!eap->skip) {
// store the pattern and compiled regexp program
curwin->w_s->b_syn_linecont_pat =
- vim_strnsave(next_arg + 1, (size_t)(arg_end - next_arg) - 1);
+ vim_strnsave(next_arg + 1, (size_t)(arg_end - (char *)next_arg) - 1);
curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
// Make 'cpoptions' empty, to avoid the 'l' flag
@@ -5241,7 +5242,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing)
break;
}
}
- next_arg = (char_u *)skipwhite((char *)arg_end + 1);
+ next_arg = (char_u *)skipwhite(arg_end + 1);
} else {
eap->arg = (char *)next_arg;
if (STRCMP(key, "MATCH") == 0) {
@@ -5401,7 +5402,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis
retval[count] = (int16_t)id;
}
}
- ++count;
+ count++;
}
p = skipwhite(end);
if (*p != ',') {
@@ -5477,7 +5478,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// that we don't go back past the first one.
while ((cur_si->si_flags & HL_TRANS_CONT)
&& cur_si > (stateitem_T *)(current_state.ga_data)) {
- --cur_si;
+ cur_si--;
}
// cur_si->si_idx is -1 for keywords, these never contain anything.
if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
@@ -5541,9 +5542,9 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// restrict recursiveness to 30 to avoid an endless loop for a
// cluster that includes itself (indirectly)
if (scl_list != NULL && depth < 30) {
- ++depth;
+ depth++;
r = in_id_list(NULL, scl_list, ssp, contained);
- --depth;
+ depth--;
if (r) {
return retval;
}
@@ -5614,7 +5615,7 @@ void ex_syntax(exarg_T *eap)
}
xfree(subcmd_name);
if (eap->skip) {
- --emsg_skip;
+ emsg_skip--;
}
}
@@ -5624,8 +5625,7 @@ void ex_ownsyntax(exarg_T *eap)
char_u *new_value;
if (curwin->w_s == &curwin->w_buffer->b_s) {
- curwin->w_s = xmalloc(sizeof(synblock_T));
- memset(curwin->w_s, 0, sizeof(synblock_T));
+ curwin->w_s = xcalloc(1, sizeof(synblock_T));
hash_init(&curwin->w_s->b_keywtab);
hash_init(&curwin->w_s->b_keywtab_ic);
// TODO: Keep the spell checking as it was. NOLINT(readability/todo)
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 5b799be381..f212aefbfc 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -14,16 +14,17 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
+#include "nvim/help.h"
#include "nvim/if_cscope.h"
#include "nvim/input.h"
#include "nvim/insexpand.h"
@@ -40,7 +41,7 @@
#include "nvim/path.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
@@ -465,7 +466,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose)
// when the argument starts with '/', use it as a regexp
if (!no_regexp && *name == '/') {
flags = TAG_REGEXP;
- ++name;
+ name++;
} else {
flags = TAG_NOIC;
}
@@ -653,13 +654,13 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose)
|| cur_match < num_matches - 1))) {
error_cur_match = cur_match;
if (use_tagstack) {
- --tagstackidx;
+ tagstackidx--;
}
if (type == DT_PREV) {
- --cur_match;
+ cur_match--;
} else {
type = DT_NEXT;
- ++cur_match;
+ cur_match++;
}
continue;
}
@@ -1076,9 +1077,9 @@ static int tag_strnicmp(char_u *s1, char_u *s2, size_t len)
if (*s1 == NUL) {
break; // strings match until NUL
}
- ++s1;
- ++s2;
- --len;
+ s1++;
+ s2++;
+ len--;
}
return 0; // strings match
}
@@ -1320,7 +1321,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl
// Add all matches because tagfunc should do filtering.
ga_grow(ga, 1);
- ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp;
+ ((char **)(ga->ga_data))[ga->ga_len++] = (char *)mfp;
ntags++;
result = OK;
});
@@ -1411,7 +1412,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi
int matchoff = 0;
int save_emsg_off;
- char_u *mfp;
+ char *mfp;
garray_T ga_match[MT_COUNT]; // stores matches in sequence
hashtab_T ht_match[MT_COUNT]; // stores matches by key
hash_T hash = 0;
@@ -1476,7 +1477,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi
lbuf = xmalloc((size_t)lbuf_size);
tag_fname = xmalloc(MAXPATHL + 1);
for (mtt = 0; mtt < MT_COUNT; mtt++) {
- ga_init(&ga_match[mtt], sizeof(char_u *), 100);
+ ga_init(&ga_match[mtt], sizeof(char *), 100);
hash_init(&ht_match[mtt]);
}
@@ -1520,7 +1521,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi
// This is only to avoid a compiler warning for using search_info
// uninitialised.
- memset(&search_info, 0, 1); // -V512
+ CLEAR_FIELD(search_info);
if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) {
tfu_in_use = true;
@@ -1612,7 +1613,7 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi
// unless found already.
help_pri++;
if (STRICMP(help_lang, "en") != 0) {
- ++help_pri;
+ help_pri++;
}
}
}
@@ -1860,7 +1861,7 @@ parse_line:
// For "normal" tags: Do a quick check if the tag matches.
// This speeds up tag searching a lot!
if (orgpat.headlen) {
- memset(&tagp, 0, sizeof(tagp));
+ CLEAR_FIELD(tagp);
tagp.tagname = lbuf;
tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB);
if (tagp.tagname_end == NULL) {
@@ -2088,9 +2089,9 @@ parse_line:
// The format is {tagname}@{lang}NUL{heuristic}NUL
*tagp.tagname_end = NUL;
len = (size_t)(tagp.tagname_end - tagp.tagname);
- mfp = xmalloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1);
+ mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1);
- p = mfp;
+ p = (char_u *)mfp;
STRCPY(p, tagp.tagname);
p[len] = '@';
STRCPY(p + len + 1, help_lang);
@@ -2122,7 +2123,7 @@ parse_line:
get_it_again = false;
} else {
len = (size_t)(tagp.tagname_end - tagp.tagname);
- mfp = xmalloc(sizeof(char_u) + len + 1);
+ mfp = xmalloc(sizeof(char) + len + 1);
STRLCPY(mfp, tagp.tagname, len + 1);
// if wanted, re-read line to get long form too
@@ -2140,8 +2141,8 @@ parse_line:
// without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL>
// Here <mtt> is the "mtt" value plus 1 to avoid NUL.
len = tag_fname_len + STRLEN(lbuf) + 3;
- mfp = xmalloc(sizeof(char_u) + len + 1);
- p = mfp;
+ mfp = xmalloc(sizeof(char) + len + 1);
+ p = (char_u *)mfp;
p[0] = (char_u)(mtt + 1);
STRCPY(p + 1, tag_fname);
#ifdef BACKSLASH_IN_FILENAME
@@ -2166,15 +2167,14 @@ parse_line:
if (use_cscope) {
hash++;
} else {
- hash = hash_hash(mfp);
+ hash = hash_hash((char_u *)mfp);
}
hi = hash_lookup(&ht_match[mtt], (const char *)mfp,
STRLEN(mfp), hash);
if (HASHITEM_EMPTY(hi)) {
- hash_add_item(&ht_match[mtt], hi, mfp, hash);
+ hash_add_item(&ht_match[mtt], hi, (char_u *)mfp, hash);
ga_grow(&ga_match[mtt], 1);
- ((char_u **)(ga_match[mtt].ga_data))
- [ga_match[mtt].ga_len++] = mfp;
+ ((char **)(ga_match[mtt].ga_data))[ga_match[mtt].ga_len++] = mfp;
match_count++;
} else {
// duplicate tag, drop it
@@ -2258,29 +2258,29 @@ findtag_end:
}
if (match_count > 0) {
- matches = xmalloc((size_t)match_count * sizeof(char_u *));
+ matches = xmalloc((size_t)match_count * sizeof(char *));
} else {
matches = NULL;
}
match_count = 0;
for (mtt = 0; mtt < MT_COUNT; mtt++) {
for (i = 0; i < ga_match[mtt].ga_len; i++) {
- mfp = ((char_u **)(ga_match[mtt].ga_data))[i];
+ mfp = ((char **)(ga_match[mtt].ga_data))[i];
if (matches == NULL) {
xfree(mfp);
} else {
if (!name_only) {
// Change mtt back to zero-based.
- *mfp = (char_u)(*mfp - 1);
+ *mfp = (char)(*mfp - 1);
// change the TAG_SEP back to NUL
- for (p = mfp + 1; *p != NUL; p++) {
+ for (p = (char_u *)mfp + 1; *p != NUL; p++) {
if (*p == TAG_SEP) {
*p = NUL;
}
}
}
- matches[match_count++] = (char *)mfp;
+ matches[match_count++] = mfp;
}
}
@@ -2342,7 +2342,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf)
char_u *r_ptr;
if (first) {
- memset(tnp, 0, sizeof(tagname_T));
+ CLEAR_POINTER(tnp);
}
if (curbuf->b_help) {
@@ -2353,7 +2353,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf)
*/
if (first) {
ga_clear_strings(&tag_fnames);
- ga_init(&tag_fnames, (int)sizeof(char_u *), 10);
+ ga_init(&tag_fnames, (int)sizeof(char *), 10);
do_in_runtimepath("doc/tags doc/tags-??", DIP_ALL,
found_tagfile_cb, NULL);
}
@@ -2373,13 +2373,12 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf)
simplify_filename(buf);
for (int i = 0; i < tag_fnames.ga_len; i++) {
- if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0) {
+ if (STRCMP(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) {
return FAIL; // avoid duplicate file names
}
}
} else {
- STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[tnp->tn_hf_idx++],
- MAXPATHL);
+ STRLCPY(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL);
}
return OK;
}
@@ -2387,9 +2386,8 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf)
if (first) {
// Init. We make a copy of 'tags', because autocommands may change
// the value without notifying us.
- tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL)
- ? curbuf->b_p_tags : p_tags);
- tnp->tn_np = tnp->tn_tags;
+ tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : p_tags);
+ tnp->tn_np = (char *)tnp->tn_tags;
}
/*
@@ -2420,7 +2418,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf)
* Copy next file name into buf.
*/
buf[0] = NUL;
- (void)copy_option_part((char **)&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,");
+ (void)copy_option_part(&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,");
r_ptr = vim_findfile_stopdir(buf);
// move the filename one char forward and truncate the
@@ -2478,7 +2476,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp)
// Isolate file name, from first to second white space
if (*p != NUL) {
- ++p;
+ p++;
}
tagp->fname = p;
p = (char_u *)vim_strchr((char *)p, TAB);
@@ -2489,7 +2487,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp)
// find start of search command, after second white space
if (*p != NUL) {
- ++p;
+ p++;
}
if (*p == NUL) {
return FAIL;
@@ -2720,7 +2718,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help)
goto erret;
}
- ++RedrawingDisabled;
+ RedrawingDisabled++;
if (l_g_do_tagpreview != 0) {
postponed_split = 0; // don't split again below
@@ -3171,7 +3169,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char_u *sta
if (end == NULL) {
end = start + STRLEN(start);
while (end > start && (end[-1] == '\r' || end[-1] == '\n')) {
- --end;
+ end--;
}
}
len = (int)(end - start);
@@ -3250,13 +3248,13 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
// separated by Tabs.
n = p;
while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':') {
- ++p;
+ p++;
}
len = (int)(p - n);
if (*p == ':' && len > 0) {
s = ++p;
while (*p != NUL && *p >= ' ') {
- ++p;
+ p++;
}
n[len] = NUL;
if (add_tag_field(dict, (char *)n, s, p) == FAIL) {
@@ -3266,7 +3264,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
} else {
// Skip field without colon.
while (*p != NUL && *p >= ' ') {
- ++p;
+ p++;
}
}
if (*p == NUL) {
diff --git a/src/nvim/tag.h b/src/nvim/tag.h
index c8051e1dcc..0b4039afb6 100644
--- a/src/nvim/tag.h
+++ b/src/nvim/tag.h
@@ -35,7 +35,7 @@
// Structure used for get_tagfname().
typedef struct {
char_u *tn_tags; // value of 'tags' when starting
- char_u *tn_np; // current position in tn_tags
+ char *tn_np; // current position in tn_tags
int tn_did_filefind_init;
int tn_hf_idx;
void *tn_search_ctx;
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index eb7c83d317..844a79b33d 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -44,15 +44,16 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
-#include "nvim/edit.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
-#include "nvim/fileio.h"
#include "nvim/getchar.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
@@ -69,7 +70,6 @@
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/terminal.h"
#include "nvim/ui.h"
@@ -681,7 +681,7 @@ static bool is_filter_char(int c)
return !!(tpf_flags & flag);
}
-void terminal_paste(long count, char_u **y_array, size_t y_size)
+void terminal_paste(long count, char **y_array, size_t y_size)
{
if (y_size == 0) {
return;
@@ -702,7 +702,7 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
buff_len = len;
}
char_u *dst = buff;
- char_u *src = y_array[j];
+ char_u *src = (char_u *)y_array[j];
while (*src != '\0') {
len = (size_t)utf_ptr2len((char *)src);
int c = utf_ptr2char((char *)src);
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 6b16e888a9..fcd3d5724c 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -257,6 +257,7 @@ endfunc
func EarlyExit(test)
" It's OK for the test we use to test the quit detection.
if a:test != 'Test_zz_quit_detected()'
+ call add(v:errors, v:errmsg)
call add(v:errors, 'Test caused Vim to exit: ' . a:test)
endif
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index ca7c8574cb..521c3fcd57 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -87,6 +87,10 @@ func Test_argadd()
new
arga
call assert_equal(0, len(argv()))
+
+ if has('unix')
+ call assert_fails('argadd `Xdoes_not_exist`', 'E479:')
+ endif
endfunc
func Test_argadd_empty_curbuf()
@@ -408,6 +412,35 @@ func Test_argedit()
bw! x
endfunc
+" Test for the :argdedupe command
+func Test_argdedupe()
+ call Reset_arglist()
+ argdedupe
+ call assert_equal([], argv())
+ args a a a aa b b a b aa
+ argdedupe
+ call assert_equal(['a', 'aa', 'b'], argv())
+ args a b c
+ argdedupe
+ call assert_equal(['a', 'b', 'c'], argv())
+ args a
+ argdedupe
+ call assert_equal(['a'], argv())
+ args a A b B
+ argdedupe
+ if has('fname_case')
+ call assert_equal(['a', 'A', 'b', 'B'], argv())
+ else
+ call assert_equal(['a', 'b'], argv())
+ endif
+ args a b a c a b
+ last
+ argdedupe
+ next
+ call assert_equal('c', expand('%:t'))
+ %argd
+endfunc
+
" Test for the :argdelete command
func Test_argdelete()
call Reset_arglist()
@@ -420,6 +453,8 @@ func Test_argdelete()
call assert_equal(['b'], argv())
call assert_fails('argdelete', 'E610:')
call assert_fails('1,100argdelete', 'E16:')
+ call assert_fails('argdel /\)/', 'E55:')
+ call assert_fails('1argdel 1', 'E474:')
call Reset_arglist()
args a b c d
@@ -427,6 +462,8 @@ func Test_argdelete()
argdel
call Assert_argc(['a', 'c', 'd'])
%argdel
+
+ call assert_fails('argdel does_not_exist', 'E480:')
endfunc
func Test_argdelete_completion()
@@ -472,13 +509,16 @@ func Test_arglist_autocmd()
new
" redefine arglist; go to Xxx1
next! Xxx1 Xxx2 Xxx3
- " open window for all args
+ " open window for all args; Reading Xxx2 will change the arglist and the
+ " third window will get Xxx1:
+ " win 1: Xxx1
+ " win 2: Xxx2
+ " win 3: Xxx1
all
call assert_equal('test file Xxx1', getline(1))
wincmd w
wincmd w
call assert_equal('test file Xxx1', getline(1))
- " should now be in Xxx2
rewind
call assert_equal('test file Xxx2', getline(1))
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 1c2f86a584..716511210d 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -2614,6 +2614,28 @@ func Test_BufWrite_lockmarks()
call delete('Xtest2')
endfunc
+func Test_FileType_spell()
+ if !isdirectory('/tmp')
+ throw "Skipped: requires /tmp directory"
+ endif
+
+ " this was crashing with an invalid free()
+ setglobal spellfile=/tmp/en.utf-8.add
+ augroup crash
+ autocmd!
+ autocmd BufNewFile,BufReadPost crashfile setf somefiletype
+ autocmd BufNewFile,BufReadPost crashfile set ft=anotherfiletype
+ autocmd FileType anotherfiletype setlocal spell
+ augroup END
+ func! NoCrash() abort
+ edit /tmp/crashfile
+ endfunc
+ call NoCrash()
+
+ au! crash
+ setglobal spellfile=
+endfunc
+
" Test closing a window or editing another buffer from a FileChangedRO handler
" in a readonly buffer
func Test_FileChangedRO_winclose()
@@ -2715,6 +2737,59 @@ func Test_autocmd_sigusr1()
unlet g:sigusr1_passed
endfunc
+" Test for BufReadPre autocmd deleting the file
+func Test_BufReadPre_delfile()
+ augroup TestAuCmd
+ au!
+ autocmd BufReadPre Xfile call delete('Xfile')
+ augroup END
+ call writefile([], 'Xfile')
+ call assert_fails('new Xfile', 'E200:')
+ call assert_equal('Xfile', @%)
+ call assert_equal(1, &readonly)
+ call delete('Xfile')
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for BufReadPre autocmd changing the current buffer
+func Test_BufReadPre_changebuf()
+ augroup TestAuCmd
+ au!
+ autocmd BufReadPre Xfile edit Xsomeotherfile
+ augroup END
+ call writefile([], 'Xfile')
+ call assert_fails('new Xfile', 'E201:')
+ call assert_equal('Xsomeotherfile', @%)
+ call assert_equal(1, &readonly)
+ call delete('Xfile')
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for BufWipeouti autocmd changing the current buffer when reading a file
+" in an empty buffer with 'f' flag in 'cpo'
+func Test_BufDelete_changebuf()
+ new
+ augroup TestAuCmd
+ au!
+ autocmd BufWipeout * let bufnr = bufadd('somefile') | exe "b " .. bufnr
+ augroup END
+ let save_cpo = &cpo
+ set cpo+=f
+ call assert_fails('r Xfile', 'E484:')
+ call assert_equal('somefile', @%)
+ let &cpo = save_cpo
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
" Test for the temporary internal window used to execute autocmds
func Test_autocmd_window()
%bw!
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
index ffb8e3facd..579d3a5eb5 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/src/nvim/testdir/test_bufline.vim
@@ -19,7 +19,7 @@ func Test_setbufline_getbufline()
let b = bufnr('%')
wincmd w
call assert_equal(1, setbufline(b, 5, ['x']))
- call assert_equal(1, setbufline(1234, 1, ['x']))
+ call assert_equal(1, setbufline(bufnr('$') + 1, 1, ['x']))
call assert_equal(0, setbufline(b, 4, ['d', 'e']))
call assert_equal(['c'], b->getbufline(3))
call assert_equal(['d'], getbufline(b, 4))
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 7aac731709..b9f027afb2 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -126,6 +126,40 @@ func Test_wildmenu_screendump()
call delete('XTest_wildmenu')
endfunc
+func Test_changing_cmdheight()
+ CheckScreendump
+
+ let lines =<< trim END
+ set cmdheight=1 laststatus=2
+ END
+ call writefile(lines, 'XTest_cmdheight')
+
+ let buf = RunVimInTerminal('-S XTest_cmdheight', {'rows': 8})
+ call term_sendkeys(buf, ":resize -3\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {})
+
+ " using the space available doesn't change the status line
+ call term_sendkeys(buf, ":set cmdheight+=3\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {})
+
+ " using more space moves the status line up
+ call term_sendkeys(buf, ":set cmdheight+=1\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {})
+
+ " reducing cmdheight moves status line down
+ call term_sendkeys(buf, ":set cmdheight-=2\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {})
+
+ " reducing window size and then setting cmdheight
+ call term_sendkeys(buf, ":resize -1\<CR>")
+ call term_sendkeys(buf, ":set cmdheight=1\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_cmdheight')
+endfunc
+
func Test_map_completion()
if !has('cmdline_compl')
return
@@ -912,12 +946,26 @@ func Test_cmdline_complete_various()
call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:)
+ " completion of autocmd group after comma
+ call feedkeys(":doautocmd BufNew,BufEn\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"doautocmd BufNew,BufEnter", @:)
+
+ " completion of file name in :doautocmd
+ call writefile([], 'Xfile1')
+ call writefile([], 'Xfile2')
+ call feedkeys(":doautocmd BufEnter Xfi\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"doautocmd BufEnter Xfile1 Xfile2", @:)
+ call delete('Xfile1')
+ call delete('Xfile2')
+
" completion for the :augroup command
- augroup XTest
+ augroup XTest.test
augroup END
call feedkeys(":augroup X\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"augroup XTest", @:)
- augroup! XTest
+ call assert_equal("\"augroup XTest.test", @:)
+ call feedkeys(":au X\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"au XTest.test", @:)
+ augroup! XTest.test
" completion for the :unlet command
call feedkeys(":unlet one two\<C-A>\<C-B>\"\<CR>", 'xt')
@@ -1392,14 +1440,6 @@ func Test_cmdwin_jump_to_win()
call assert_equal(1, winnr('$'))
endfunc
-" Test for backtick expression in the command line
-func Test_cmd_backtick()
- %argd
- argadd `=['a', 'b', 'c']`
- call assert_equal(['a', 'b', 'c'], argv())
- %argd
-endfunc
-
func Test_cmdwin_tabpage()
tabedit
" v8.2.1919 isn't ported yet, so E492 is thrown after E11 here.
@@ -1412,11 +1452,22 @@ func Test_cmdwin_tabpage()
tabclose!
endfunc
+" Test for backtick expression in the command line
+func Test_cmd_backtick()
+ CheckNotMSWindows " FIXME: see #19297
+ %argd
+ argadd `=['a', 'b', 'c']`
+ call assert_equal(['a', 'b', 'c'], argv())
+ %argd
+
+ argadd `echo abc def`
+ call assert_equal(['abc def'], argv())
+ %argd
+endfunc
+
" Test for the :! command
func Test_cmd_bang()
- if !has('unix')
- return
- endif
+ CheckUnix
let lines =<< trim [SCRIPT]
" Test for no previous command
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 1dbbe578c5..ea453b7174 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -137,7 +137,7 @@ func Common_vert_split()
" Test diffoff
diffoff!
- 1wincmd 2
+ 1wincmd w
let &diff = 1
let &fdm = diff_fdm
let &fdc = diff_fdc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index a09346a595..e26bbdc5be 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -262,22 +262,6 @@ func Test_edit_09()
bw!
endfunc
-func Test_edit_10()
- " Test for starting selectmode
- new
- set selectmode=key keymodel=startsel
- call setline(1, ['abc', 'def', 'ghi'])
- call cursor(1, 4)
- call feedkeys("A\<s-home>start\<esc>", 'txin')
- call assert_equal(['startdef', 'ghi'], getline(1, '$'))
- " start select mode again with gv
- set selectmode=cmd
- call feedkeys('gvabc', 'xt')
- call assert_equal('abctdef', getline(1))
- set selectmode= keymodel=
- bw!
-endfunc
-
func Test_edit_11()
" Test that indenting kicks in
new
@@ -1620,6 +1604,7 @@ func Test_edit_InsertLeave_undo()
bwipe!
au! InsertLeave
call delete('XtestUndo')
+ call delete(undofile('XtestUndo'))
set undofile&
endfunc
@@ -1687,11 +1672,11 @@ func Test_edit_noesckeys()
endfunc
" Test for running an invalid ex command in insert mode using CTRL-O
-" Note that vim has a hard-coded sleep of 3 seconds. So this test will take
-" more than 3 seconds to complete.
func Test_edit_ctrl_o_invalid_cmd()
new
set showmode showcmd
+ " Avoid a sleep of 3 seconds. Zero might have side effects.
+ " call test_override('ui_delay', 50)
let caught_e492 = 0
try
call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt")
@@ -1701,6 +1686,18 @@ func Test_edit_ctrl_o_invalid_cmd()
call assert_equal(1, caught_e492)
call assert_equal('abc', getline(1))
set showmode& showcmd&
+ " call test_override('ui_delay', 0)
+ close!
+endfunc
+
+" Test for editing a file with a very long name
+func Test_edit_illegal_filename()
+ CheckEnglish
+ new
+ redir => msg
+ exe 'edit ' . repeat('f', 5000)
+ redir END
+ call assert_match("Illegal file name$", split(msg, "\n")[0])
close!
endfunc
@@ -1763,6 +1760,102 @@ func Test_edit_is_a_directory()
call delete(dirname, 'rf')
endfunc
+" Test for editing a file using invalid file encoding
+func Test_edit_invalid_encoding()
+ CheckEnglish
+ call writefile([], 'Xfile')
+ redir => msg
+ new ++enc=axbyc Xfile
+ redir END
+ call assert_match('\[NOT converted\]', msg)
+ call delete('Xfile')
+ close!
+endfunc
+
+" Test for the "charconvert" option
+func Test_edit_charconvert()
+ CheckEnglish
+ call writefile(['one', 'two'], 'Xfile')
+
+ " set 'charconvert' to a non-existing function
+ set charconvert=NonExitingFunc()
+ new
+ let caught_e117 = v:false
+ try
+ redir => msg
+ edit ++enc=axbyc Xfile
+ catch /E117:/
+ let caught_e117 = v:true
+ finally
+ redir END
+ endtry
+ call assert_true(caught_e117)
+ call assert_equal(['one', 'two'], getline(1, '$'))
+ call assert_match("Conversion with 'charconvert' failed", msg)
+ close!
+ set charconvert&
+
+ " 'charconvert' function doesn't create a output file
+ func Cconv1()
+ endfunc
+ set charconvert=Cconv1()
+ new
+ redir => msg
+ edit ++enc=axbyc Xfile
+ redir END
+ call assert_equal(['one', 'two'], getline(1, '$'))
+ call assert_match("can't read output of 'charconvert'", msg)
+ close!
+ delfunc Cconv1
+ set charconvert&
+
+ " 'charconvert' function to convert to upper case
+ func Cconv2()
+ let data = readfile(v:fname_in)
+ call map(data, 'toupper(v:val)')
+ call writefile(data, v:fname_out)
+ endfunc
+ set charconvert=Cconv2()
+ new Xfile
+ write ++enc=ucase Xfile1
+ call assert_equal(['ONE', 'TWO'], readfile('Xfile1'))
+ call delete('Xfile1')
+ close!
+ delfunc Cconv2
+ set charconvert&
+
+ " 'charconvert' function removes the input file
+ func Cconv3()
+ call delete(v:fname_in)
+ endfunc
+ set charconvert=Cconv3()
+ new
+ call assert_fails('edit ++enc=lcase Xfile', 'E202:')
+ call assert_equal([''], getline(1, '$'))
+ close!
+ delfunc Cconv3
+ set charconvert&
+
+ call delete('Xfile')
+endfunc
+
+" Test for editing a file without read permission
+func Test_edit_file_no_read_perm()
+ CheckUnix
+ CheckNotBSD
+ call writefile(['one', 'two'], 'Xfile')
+ call setfperm('Xfile', '-w-------')
+ new
+ redir => msg
+ edit Xfile
+ redir END
+ call assert_equal(1, &readonly)
+ call assert_equal([''], getline(1, '$'))
+ call assert_match('\[Permission Denied\]', msg)
+ close!
+ call delete('Xfile')
+endfunc
+
" Using :edit without leaving 'insertmode' should not cause Insert mode to be
" re-entered immediately after <C-L>
func Test_edit_insertmode_ex_edit()
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 811c6c946d..eff1376d3c 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -75,6 +75,18 @@ func Test_for_invalid()
redraw
endfunc
+func Test_for_over_null_string()
+ let save_enc = &enc
+ " set enc=iso8859
+ let cnt = 0
+ for c in v:_null_string
+ let cnt += 1
+ endfor
+ call assert_equal(0, cnt)
+
+ let &enc = save_enc
+endfunc
+
func Test_readfile_binary()
new
call setline(1, ['one', 'two', 'three'])
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
index befcaec2b2..37be293950 100644
--- a/src/nvim/testdir/test_exit.vim
+++ b/src/nvim/testdir/test_exit.vim
@@ -95,7 +95,7 @@ func Test_exit_code()
[CODE]
if RunVim(before, ['quit'], '')
- call assert_equal(['qp = null', 'ep = null', 'lp = 0', 'l = 0'], readfile('Xtestout'))
+ call assert_equal(['qp = v:null', 'ep = v:null', 'lp = 0', 'l = 0'], readfile('Xtestout'))
endif
call delete('Xtestout')
diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim
index b48c2e8a19..df01d84f19 100644
--- a/src/nvim/testdir/test_expand_func.vim
+++ b/src/nvim/testdir/test_expand_func.vim
@@ -37,17 +37,54 @@ func Test_expand_sflnum()
delcommand Flnum
endfunc
-func Test_expand_sfile()
+func Test_expand_sfile_and_stack()
call assert_match('test_expand_func\.vim$', s:sfile)
- call assert_match('^function .*\.\.Test_expand_sfile$', expand('<sfile>'))
+ let expected = 'script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack'
+ call assert_match(expected .. '$', expand('<sfile>'))
+ call assert_match(expected .. '\[4\]$' , expand('<stack>'))
" Call in script-local function
- call assert_match('^function .*\.\.Test_expand_sfile\[5\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile())
+ call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack\[7\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile())
" Call in command
command Sfile echo expand('<sfile>')
- call assert_match('^function .*\.\.Test_expand_sfile$', trim(execute('Sfile')))
+ call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack$', trim(execute('Sfile')))
delcommand Sfile
+
+ " Use <stack> from sourced script.
+ let lines =<< trim END
+ " comment here
+ let g:stack_value = expand('<stack>')
+ END
+ call writefile(lines, 'Xstack')
+ source Xstack
+ call assert_match('\<Xstack\[2\]$', g:stack_value)
+ unlet g:stack_value
+ call delete('Xstack')
+
+ if exists('+shellslash')
+ call mkdir('Xshellslash')
+ let lines =<< trim END
+ let g:stack1 = expand('<stack>')
+ set noshellslash
+ let g:stack2 = expand('<stack>')
+ set shellslash
+ let g:stack3 = expand('<stack>')
+ END
+ call writefile(lines, 'Xshellslash/Xstack')
+ " Test that changing 'shellslash' always affects the result of expand()
+ " when sourcing a script multiple times.
+ for i in range(2)
+ source Xshellslash/Xstack
+ call assert_match('\<Xshellslash/Xstack\[1\]$', g:stack1)
+ call assert_match('\<Xshellslash\\Xstack\[3\]$', g:stack2)
+ call assert_match('\<Xshellslash/Xstack\[5\]$', g:stack3)
+ unlet g:stack1
+ unlet g:stack2
+ unlet g:stack3
+ endfor
+ call delete('Xshellslash', 'rf')
+ endif
endfunc
func Test_expand_slnum()
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index c6e781a1ef..b77f02afd1 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -242,6 +242,15 @@ func Test_file_changed_dialog()
call assert_equal(1, line('$'))
call assert_equal('new line', getline(1))
+ " File created after starting to edit it
+ call delete('Xchanged_d')
+ new Xchanged_d
+ call writefile(['one'], 'Xchanged_d')
+ call feedkeys('L', 'L')
+ checktime Xchanged_d
+ call assert_equal(['one'], getline(1, '$'))
+ close!
+
bwipe!
call delete('Xchanged_d')
endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index eedad15e9e..e3a8370661 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -71,6 +71,7 @@ let s:filename_checks = {
\ 'asciidoc': ['file.asciidoc', 'file.adoc'],
\ 'asn': ['file.asn', 'file.asn1'],
\ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'],
+ \ 'astro': ['file.astro'],
\ 'atlas': ['file.atl', 'file.as'],
\ 'autohotkey': ['file.ahk'],
\ 'autoit': ['file.au3'],
@@ -360,7 +361,7 @@ let s:filename_checks = {
\ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'],
\ 'moo': ['file.moo'],
\ 'moonscript': ['file.moon'],
- \ 'mp': ['file.mp'],
+ \ 'mp': ['file.mp', 'file.mpxl', 'file.mpiv', 'file.mpvi'],
\ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'],
\ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'],
\ 'msidl': ['file.odl', 'file.mof'],
@@ -441,6 +442,7 @@ let s:filename_checks = {
\ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
\ 'ql': ['file.ql', 'file.qll'],
\ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'],
+ \ 'quarto': ['file.qmd'],
\ 'r': ['file.r'],
\ 'radiance': ['file.rad', 'file.mat'],
\ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'],
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index c11e7b4fea..44b6f0373e 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1769,6 +1769,15 @@ func Test_char2nr()
call assert_equal(12354, char2nr('あ', 1))
endfunc
+func Test_charclass()
+ call assert_equal(0, charclass(' '))
+ call assert_equal(1, charclass('.'))
+ call assert_equal(2, charclass('x'))
+ call assert_equal(3, charclass("\u203c"))
+ " this used to crash vim
+ call assert_equal(0, "xxx"[-1]->charclass())
+endfunc
+
func Test_eventhandler()
call assert_equal(0, eventhandler())
endfunc
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index feae44e5ee..b2bb189688 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -191,6 +191,22 @@ func Test_gf_error()
au! InsertCharPre
bwipe!
+
+ " gf is not allowed when buffer is locked
+ new
+ augroup Test_gf
+ au!
+ au OptionSet diff norm! gf
+ augroup END
+ call setline(1, ['Xfile1', 'line2', 'line3', 'line4'])
+ " Nvim does not support test_override()
+ " call test_override('starting', 1)
+ " call assert_fails('diffthis', 'E788:')
+ " call test_override('starting', 0)
+ augroup Test_gf
+ au!
+ augroup END
+ bw!
endfunc
" If a file is not found by 'gf', then 'includeexpr' should be used to locate
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 947f7efc7c..cb6851250c 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -9,7 +9,10 @@ func Test_yank_put_clipboard()
set clipboard=unnamed
g/^/normal yyp
call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
-
+ set clipboard=unnamed,unnamedplus
+ call setline(1, ['a', 'b', 'c'])
+ g/^/normal yyp
+ call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
set clipboard&
bwipe!
endfunc
diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim
index 49095400ef..6d029ffda2 100644
--- a/src/nvim/testdir/test_goto.vim
+++ b/src/nvim/testdir/test_goto.vim
@@ -122,6 +122,24 @@ func Test_gd()
call XTest_goto_decl('gd', lines, 3, 14)
endfunc
+" Using gd to jump to a declaration in a fold
+func Test_gd_with_fold()
+ new
+ let lines =<< trim END
+ #define ONE 1
+ #define TWO 2
+ #define THREE 3
+
+ TWO
+ END
+ call setline(1, lines)
+ 1,3fold
+ call feedkeys('Ggd', 'xt')
+ call assert_equal(2, line('.'))
+ call assert_equal(-1, foldclosedend(2))
+ bw!
+endfunc
+
func Test_gd_not_local()
let lines =<< trim [CODE]
int func1(void)
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index aa66d86af1..2f4e1db4a1 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -649,6 +649,8 @@ func Test_reduce()
call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:')
+ call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:')
let g:lut = [1, 2, 3, 4]
func EvilRemove()
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index a02d23b409..3a607ff533 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -95,6 +95,65 @@ func Test_echoerr()
call test_ignore_error('RESET')
endfunc
+func Test_mode_message_at_leaving_insert_by_ctrl_c()
+ if !has('terminal') || has('gui_running')
+ return
+ endif
+
+ " Set custom statusline built by user-defined function.
+ let testfile = 'Xtest.vim'
+ call writefile([
+ \ 'func StatusLine() abort',
+ \ ' return ""',
+ \ 'endfunc',
+ \ 'set statusline=%!StatusLine()',
+ \ 'set laststatus=2',
+ \ ], testfile)
+
+ let rows = 10
+ let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
+ call term_wait(buf, 200)
+ call assert_equal('run', job_status(term_getjob(buf)))
+
+ call term_sendkeys(buf, "i")
+ call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))})
+ call term_sendkeys(buf, "\<C-C>")
+ call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))})
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
+ exe buf . 'bwipe!'
+ call delete(testfile)
+endfunc
+
+func Test_mode_message_at_leaving_insert_with_esc_mapped()
+ if !has('terminal') || has('gui_running')
+ return
+ endif
+
+ " Set custom statusline built by user-defined function.
+ let testfile = 'Xtest.vim'
+ call writefile([
+ \ 'set laststatus=2',
+ \ 'inoremap <Esc> <Esc>00',
+ \ ], testfile)
+
+ let rows = 10
+ let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
+ call term_wait(buf, 200)
+ call assert_equal('run', job_status(term_getjob(buf)))
+
+ call term_sendkeys(buf, "i")
+ call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))})
+ call term_sendkeys(buf, "\<Esc>")
+ call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))})
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
+ exe buf . 'bwipe!'
+ call delete(testfile)
+endfunc
+
func Test_echospace()
set noruler noshowcmd laststatus=1
call assert_equal(&columns - 1, v:echospace)
@@ -317,6 +376,7 @@ func Test_fileinfo_after_echo()
endfunc
func Test_cmdheight_zero()
+ enew
set cmdheight=0
set showcmd
redraw!
@@ -366,10 +426,13 @@ func Test_cmdheight_zero()
7
call feedkeys(":\"\<C-R>=line('w0')\<CR>\<CR>", "xt")
call assert_equal('"1', @:)
- bwipe!
+ bwipe!
+ bwipe!
set cmdheight&
set showcmd&
+ tabnew
+ tabonly
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 7cb70aa2af..347404a579 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -3,6 +3,7 @@
source shared.vim
source check.vim
source view_util.vim
+source screendump.vim
func Setup_NewWindow()
10new
@@ -123,31 +124,6 @@ func Test_normal01_keymodel()
bw!
endfunc
-" Test for select mode
-func Test_normal02_selectmode()
- call Setup_NewWindow()
- 50
- norm! gHy
- call assert_equal('y51', getline('.'))
- call setline(1, range(1,100))
- 50
- exe ":norm! V9jo\<c-g>y"
- call assert_equal('y60', getline('.'))
- " clean up
- bw!
-endfunc
-
-func Test_normal02_selectmode2()
- " some basic select mode tests
- call Setup_NewWindow()
- 50
- " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx')
- call feedkeys("i\<c-o>gHc\<esc>", 'tx')
- call assert_equal('c51', getline('.'))
- " clean up
- bw!
-endfunc
-
func Test_normal03_join()
" basic join test
call Setup_NewWindow()
@@ -491,6 +467,18 @@ func Test_normal11_showcmd()
call assert_equal(3, line('$'))
exe "norm! 0d3\<del>2l"
call assert_equal('obar2foobar3', getline('.'))
+ " test for the visual block size displayed in the status line
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc'])
+ call feedkeys("ggl\<C-V>lljj", 'xt')
+ redraw!
+ call assert_match('3x3$', Screenline(&lines))
+ call feedkeys("\<C-V>", 'xt')
+ " test for visually selecting a multi-byte character
+ call setline(1, ["\U2206"])
+ call feedkeys("ggv", 'xt')
+ redraw!
+ call assert_match('1-3$', Screenline(&lines))
+ call feedkeys("v", 'xt')
bw!
endfunc
@@ -654,6 +642,19 @@ func Test_normal15_z_scroll_vert()
call assert_equal(21, winsaveview()['topline'])
call assert_equal([0, 21, 2, 0, 9], getcurpos())
+ " Test for z+ with [count] greater than buffer size
+ 1
+ norm! 1000z+
+ call assert_equal(' 100', getline('.'))
+ call assert_equal(100, winsaveview()['topline'])
+ call assert_equal([0, 100, 2, 0, 9], getcurpos())
+
+ " Test for z+ from the last buffer line
+ norm! Gz.z+
+ call assert_equal(' 100', getline('.'))
+ call assert_equal(100, winsaveview()['topline'])
+ call assert_equal([0, 100, 2, 0, 9], getcurpos())
+
" Test for z^
norm! 22z+0
norm! z^
@@ -661,6 +662,12 @@ func Test_normal15_z_scroll_vert()
call assert_equal(12, winsaveview()['topline'])
call assert_equal([0, 21, 2, 0, 9], getcurpos())
+ " Test for z^ from first buffer line
+ norm! ggz^
+ call assert_equal('1', getline('.'))
+ call assert_equal(1, winsaveview()['topline'])
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+
" Test for [count]z^
1
norm! 30z^
@@ -740,6 +747,19 @@ func Test_normal16_z_scroll_hor()
norm! yl
call assert_equal('z', @0)
+ " Test for zs and ze with folds
+ %fold
+ norm! $zs
+ call assert_equal(26, col('.'))
+ call assert_equal(0, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+ norm! ze
+ call assert_equal(26, col('.'))
+ call assert_equal(0, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+
" cleanup
set wrap listchars=eol:$
bw!
@@ -833,6 +853,19 @@ func Test_vert_scroll_cmds()
normal! 4H
call assert_equal(33, line('.'))
+ " Test for using a large count value
+ %d
+ call setline(1, range(1, 4))
+ norm! 6H
+ call assert_equal(4, line('.'))
+
+ " Test for 'M' with folded lines
+ %d
+ call setline(1, range(1, 20))
+ 1,5fold
+ norm! LM
+ call assert_equal(12, line('.'))
+
" Test for the CTRL-E and CTRL-Y commands with folds
%d
call setline(1, range(1, 10))
@@ -851,6 +884,18 @@ func Test_vert_scroll_cmds()
exe "normal \<C-Y>\<C-Y>"
call assert_equal(h + 1, line('w$'))
+ " Test for CTRL-Y from the first line and CTRL-E from the last line
+ %d
+ set scrolloff=2
+ call setline(1, range(1, 4))
+ exe "normal gg\<C-Y>"
+ call assert_equal(1, line('w0'))
+ call assert_equal(1, line('.'))
+ exe "normal G4\<C-E>\<C-E>"
+ call assert_equal(4, line('w$'))
+ call assert_equal(4, line('.'))
+ set scrolloff&
+
" Using <PageUp> and <PageDown> in an empty buffer should beep
%d
call assert_beeps('exe "normal \<PageUp>"')
@@ -899,6 +944,18 @@ func Test_vert_scroll_cmds()
exe "normal \<C-D>"
call assert_equal(50, line('w0'))
+ " Test for <S-CR>. Page down.
+ %d
+ call setline(1, range(1, 100))
+ call feedkeys("\<S-CR>", 'xt')
+ call assert_equal(14, line('w0'))
+ call assert_equal(28, line('w$'))
+
+ " Test for <S-->. Page up.
+ call feedkeys("\<S-->", 'xt')
+ call assert_equal(1, line('w0'))
+ call assert_equal(15, line('w$'))
+
set foldenable&
close!
endfunc
@@ -1213,6 +1270,13 @@ func Test_normal18_z_fold()
norm! j
call assert_equal('55', getline('.'))
+ " Test for zm with a count
+ 50
+ set foldlevel=2
+ norm! 3zm
+ call assert_equal(0, &foldlevel)
+ call assert_equal(49, foldclosed(line('.')))
+
" Test for zM
48
set nofoldenable foldlevel=99
@@ -1420,6 +1484,15 @@ func Test_normal23_K()
set iskeyword-=%
set iskeyword-=\|
+ " Currently doesn't work in Nvim, see #19436
+ " Test for specifying a count to K
+ " 1
+ " com! -nargs=* Kprog let g:Kprog_Args = <q-args>
+ " set keywordprg=:Kprog
+ " norm! 3K
+ " call assert_equal('3 version8', g:Kprog_Args)
+ " delcom Kprog
+
" Only expect "man" to work on Unix
if !has("unix") || has('nvim') " Nvim K uses :terminal. #15398
let &keywordprg = k
@@ -1867,7 +1940,31 @@ func Test_normal29_brace()
bw!
endfunc
-" Test for ~ command
+" Test for section movements
+func Test_normal_section()
+ new
+ let lines =<< trim [END]
+ int foo()
+ {
+ if (1)
+ {
+ a = 1;
+ }
+ }
+ [END]
+ call setline(1, lines)
+
+ " jumping to a folded line using [[ should open the fold
+ 2,3fold
+ call cursor(5, 1)
+ call feedkeys("[[", 'xt')
+ call assert_equal(2, line('.'))
+ call assert_equal(-1, foldclosedend(line('.')))
+
+ close!
+endfunc
+
+" Test for changing case using u, U, gu, gU and ~ (tilde) commands
func Test_normal30_changecase()
new
call append(0, 'This is a simple test: äüöß')
@@ -1887,6 +1984,9 @@ func Test_normal30_changecase()
call assert_equal('this is a SIMPLE TEST: ÄÜÖSS', getline('.'))
norm! V~
call assert_equal('THIS IS A simple test: äüöss', getline('.'))
+ call assert_beeps('norm! c~')
+ %d
+ call assert_beeps('norm! ~')
" Test for changing case across lines using 'whichwrap'
call setline(1, ['aaaaaa', 'aaaaaa'])
@@ -2038,9 +2138,9 @@ func Test_normal33_g_cmd2()
call assert_equal(2, line('.'))
call assert_fails(':norm! g;', 'E662')
call assert_fails(':norm! g,', 'E663')
- let &ul=&ul
+ let &ul = &ul
call append('$', ['a', 'b', 'c', 'd'])
- let &ul=&ul
+ let &ul = &ul
call append('$', ['Z', 'Y', 'X', 'W'])
let a = execute(':changes')
call assert_match('2\s\+0\s\+2', a)
@@ -2889,6 +2989,20 @@ func Test_message_when_using_ctrl_c()
bwipe!
endfunc
+func Test_mode_updated_after_ctrl_c()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', {'rows': 5})
+ call term_sendkeys(buf, "i")
+ call term_sendkeys(buf, "\<C-O>")
+ " wait a moment so that the "-- (insert) --" message is displayed
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<C-C>")
+ call VerifyScreenDump(buf, 'Test_mode_updated_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
" Test for '[m', ']m', '[M' and ']M'
" Jumping to beginning and end of methods in Java-like languages
func Test_java_motion()
@@ -2897,25 +3011,26 @@ func Test_java_motion()
call assert_beeps('normal! ]m')
call assert_beeps('normal! [M')
call assert_beeps('normal! ]M')
- a
-Piece of Java
-{
- tt m1 {
- t1;
- } e1
-
- tt m2 {
- t2;
- } e2
-
- tt m3 {
- if (x)
- {
- t3;
- }
- } e3
-}
-.
+ let lines =<< trim [CODE]
+ Piece of Java
+ {
+ tt m1 {
+ t1;
+ } e1
+
+ tt m2 {
+ t2;
+ } e2
+
+ tt m3 {
+ if (x)
+ {
+ t3;
+ }
+ } e3
+ }
+ [CODE]
+ call setline(1, lines)
normal gg
@@ -2968,14 +3083,21 @@ Piece of Java
call assert_equal("{LF", getline('.'))
call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+ call cursor(2, 1)
+ call assert_beeps('norm! 5]m')
+
+ " jumping to a method in a fold should open the fold
+ 6,10fold
+ call feedkeys("gg3]m", 'xt')
+ call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')])
+ call assert_equal(-1, foldclosedend(7))
+
close!
endfunc
+" Tests for g cmds
func Test_normal_gdollar_cmd()
- if !has("jumplist")
- return
- endif
- " Tests for g cmds
+ CheckFeature jumplist
call Setup_NewWindow()
" Make long lines that will wrap
%s/$/\=repeat(' foobar', 10)/
@@ -3183,6 +3305,27 @@ func Test_normal_colon_op()
close!
endfunc
+" Test for deleting or changing characters across lines with 'whichwrap'
+" containing 's'. Should count <EOL> as one character.
+func Test_normal_op_across_lines()
+ new
+ set whichwrap&
+ call setline(1, ['one two', 'three four'])
+ exe "norm! $3d\<Space>"
+ call assert_equal(['one twhree four'], getline(1, '$'))
+
+ call setline(1, ['one two', 'three four'])
+ exe "norm! $3c\<Space>x"
+ call assert_equal(['one twxhree four'], getline(1, '$'))
+
+ set whichwrap+=l
+ call setline(1, ['one two', 'three four'])
+ exe "norm! $3x"
+ call assert_equal(['one twhree four'], getline(1, '$'))
+ close!
+ set whichwrap&
+endfunc
+
" Test for 'w' and 'b' commands
func Test_normal_word_move()
new
@@ -3256,6 +3399,19 @@ func Test_normal_vert_scroll_longline()
close!
endfunc
+" Test for jumping in a file using %
+func Test_normal_percent_jump()
+ new
+ call setline(1, range(1, 100))
+
+ " jumping to a folded line should open the fold
+ 25,75fold
+ call feedkeys('50%', 'xt')
+ call assert_equal(50, line('.'))
+ call assert_equal(-1, foldclosedend(50))
+ close!
+endfunc
+
" Some commands like yy, cc, dd, >>, << and !! accept a count after
" typing the first letter of the command.
func Test_normal_count_after_operator()
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index b10f0f5030..fdfc1c0f89 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -368,9 +368,17 @@ func Test_set_errors()
call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
call assert_fails('set foldmarker={{{,', 'E474:')
call assert_fails('set sessionoptions=sesdir,curdir', 'E474:')
- call assert_fails('set listchars=trail:· ambiwidth=double', 'E834:')
+ setlocal listchars=trail:·
+ call assert_fails('set ambiwidth=double', 'E834:')
+ setlocal listchars=trail:-
+ setglobal listchars=trail:·
+ call assert_fails('set ambiwidth=double', 'E834:')
set listchars&
- call assert_fails('set fillchars=stl:· ambiwidth=double', 'E835:')
+ setlocal fillchars=stl:·
+ call assert_fails('set ambiwidth=double', 'E835:')
+ setlocal fillchars=stl:-
+ setglobal fillchars=stl:·
+ call assert_fails('set ambiwidth=double', 'E835:')
set fillchars&
call assert_fails('set fileencoding=latin1,utf-8', 'E474:')
set nomodifiable
@@ -812,11 +820,16 @@ func Test_rightleftcmd()
set rightleft&
endfunc
-" Test for the "debug" option
+" Test for the 'debug' option
func Test_debug_option()
+ " redraw to avoid matching previous messages
+ redraw
set debug=beep
exe "normal \<C-c>"
call assert_equal('Beep!', Screenline(&lines))
+ call assert_equal('line 4:', Screenline(&lines - 1))
+ " only match the final colon in the line that shows the source
+ call assert_match(':$', Screenline(&lines - 2))
set debug&
endfunc
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index 191cd948ac..14b9724d67 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -522,8 +522,8 @@ endfunc
func Test_search_with_end_offset()
new
call setline(1, ['', 'dog(a', 'cat('])
- exe "normal /(/e+" .. "\<CR>"
- normal "ayn
+ exe "normal /(/e+\<CR>"
+ normal n"ayn
call assert_equal("a\ncat(", @a)
close!
endfunc
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 52e745438d..11dd3badb6 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -279,7 +279,12 @@ func Test_get_register()
call feedkeys(":\<C-R>r\<Esc>", 'xt')
call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137
+ call assert_fails('let r = getreg("=", [])', 'E745:')
+ call assert_fails('let r = getreg("=", 1, [])', 'E745:')
enew!
+
+ " Using a register in operator-pending mode should fail
+ call assert_beeps('norm! c"')
endfunc
func Test_set_register()
@@ -426,6 +431,12 @@ func Test_execute_register()
@
call assert_equal(3, i)
+ " try to execute expression register and use a backspace to cancel it
+ new
+ call feedkeys("@=\<BS>ax\<CR>y", 'xt')
+ call assert_equal(['x', 'y'], getline(1, '$'))
+ close!
+
" cannot execute a register in operator pending mode
call assert_beeps('normal! c@r')
endfunc
diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim
index b483841060..f2cab45450 100644
--- a/src/nvim/testdir/test_selectmode.vim
+++ b/src/nvim/testdir/test_selectmode.vim
@@ -2,6 +2,156 @@
source shared.vim
+" Test for select mode
+func Test_selectmode_basic()
+ new
+ call setline(1, range(1,100))
+ 50
+ norm! gHy
+ call assert_equal('y51', getline('.'))
+ call setline(1, range(1,100))
+ 50
+ exe ":norm! V9jo\<c-g>y"
+ call assert_equal('y60', getline('.'))
+ call setline(1, range(1,100))
+ 50
+ " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx')
+ call feedkeys("i\<c-o>gHc\<esc>", 'tx')
+ call assert_equal('c51', getline('.'))
+ " clean up
+ bw!
+endfunc
+
+" Test for starting selectmode
+func Test_selectmode_start()
+ new
+ set selectmode=key keymodel=startsel
+ call setline(1, ['abc', 'def', 'ghi'])
+ call cursor(1, 4)
+ call feedkeys("A\<s-home>start\<esc>", 'txin')
+ call assert_equal(['startdef', 'ghi'], getline(1, '$'))
+ " start select mode again with gv
+ set selectmode=cmd
+ call feedkeys('gvabc', 'xt')
+ call assert_equal('abctdef', getline(1))
+ set selectmode= keymodel=
+ bw!
+endfunc
+
+" Test for characterwise select mode
+func Test_characterwise_select_mode()
+ new
+
+ " Select mode maps
+ snoremap <lt>End> <End>
+ snoremap <lt>Down> <Down>
+ snoremap <lt>Del> <Del>
+
+ " characterwise select mode: delete middle line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<End>\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Ggh\<End>\<Del>"
+ call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
+
+ " characterwise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'a', ''], getline(1, '$'))
+
+ " CTRL-H in select mode behaves like 'x'
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
+ call assert_equal('ef', getline(1))
+
+ " CTRL-O in select mode switches to visual mode for one command
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<C-O>3lm"
+ call assert_equal('mef', getline(1))
+
+ sunmap <lt>End>
+ sunmap <lt>Down>
+ sunmap <lt>Del>
+ bwipe!
+endfunc
+
+" Test for linewise select mode
+func Test_linewise_select_mode()
+ new
+
+ " linewise select mode: delete middle line
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Down>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GgH\<Del>"
+ call assert_equal(['', 'a', 'b'], getline(1, '$'))
+
+ " linewise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkgH\<Down>\<Del>"
+ call assert_equal(['', 'a'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for blockwise select mode (g CTRL-H)
+func Test_blockwise_select_mode()
+ new
+ call setline(1, ['foo', 'bar'])
+ call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt')
+ call assert_equal(['mmo', 'mmr'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for using visual mode maps in select mode
+func Test_select_mode_map()
+ new
+ vmap <buffer> <F2> 3l
+ call setline(1, 'Test line')
+ call feedkeys("gh\<F2>map", 'xt')
+ call assert_equal('map line', getline(1))
+
+ vmap <buffer> <F2> ygV
+ call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt')
+ call assert_equal('abc line', getline(1))
+
+ vmap <buffer> <F2> :<C-U>let v=100<CR>
+ call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt')
+ call assert_equal('foo line', getline(1))
+
+ " reselect the select mode using gv from a visual mode map
+ vmap <buffer> <F2> gv
+ set selectmode=cmd
+ call feedkeys("0gh\<F2>map", 'xt')
+ call assert_equal('map line', getline(1))
+ set selectmode&
+
+ close!
+endfunc
+
" Test for selecting a register with CTRL-R
func Test_selectmode_register()
new
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index 7744c5bcca..8ab8204b10 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -474,6 +474,16 @@ func Test_spellsuggest_option_expr()
bwipe!
endfunc
+func Test_spellsuggest_timeout()
+ set spellsuggest=timeout:30
+ set spellsuggest=timeout:-123
+ set spellsuggest=timeout:999999
+ call assert_fails('set spellsuggest=timeout', 'E474:')
+ call assert_fails('set spellsuggest=timeout:x', 'E474:')
+ call assert_fails('set spellsuggest=timeout:-x', 'E474:')
+ call assert_fails('set spellsuggest=timeout:--9', 'E474:')
+endfunc
+
func Test_spellinfo()
throw 'skipped: Nvim does not support enc=latin1'
new
diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim
index b028e9d969..dbffbafed9 100644
--- a/src/nvim/testdir/test_spellfile.vim
+++ b/src/nvim/testdir/test_spellfile.vim
@@ -25,6 +25,18 @@ func Test_spell_normal()
let cnt=readfile('./Xspellfile.add')
call assert_equal('goood', cnt[0])
+ " zg should fail in operator-pending mode
+ call assert_beeps('norm! czg')
+
+ " zg fails in visual mode when not able to get the visual text
+ call assert_beeps('norm! ggVjzg')
+ norm! V
+
+ " zg fails for a non-identifier word
+ call append(line('$'), '###')
+ call assert_fails('norm! Gzg', 'E349:')
+ $d
+
" Test for zw
2
norm! $zw
@@ -907,6 +919,33 @@ func Test_spellfile_COMMON()
call delete('XtestCOMMON-utf8.spl')
endfunc
+" Test NOSUGGEST (see :help spell-COMMON)
+func Test_spellfile_NOSUGGEST()
+ call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic')
+ call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff')
+
+ mkspell! XtestNOSUGGEST-utf8.spl XtestNOSUGGEST
+ set spell spelllang=XtestNOSUGGEST-utf8.spl
+
+ for goodword in ['foo', 'Foo', 'FOO', 'fog', 'Fog', 'FOG']
+ call assert_equal(['', ''], spellbadword(goodword), goodword)
+ endfor
+ for badword in ['foO', 'fOO', 'fooo', 'foog', 'foofog', 'fogfoo']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ call assert_equal(['fog'], spellsuggest('fooo', 1))
+ call assert_equal(['fog'], spellsuggest('fOo', 1))
+ call assert_equal(['fog'], spellsuggest('foG', 1))
+ call assert_equal(['fog'], spellsuggest('fogg', 1))
+
+ set spell& spelllang&
+ call delete('XtestNOSUGGEST.dic')
+ call delete('XtestNOSUGGEST.aff')
+ call delete('XtestNOSUGGEST-utf8.spl')
+endfunc
+
+
" Test CIRCUMFIX (see: :help spell-CIRCUMFIX)
func Test_spellfile_CIRCUMFIX()
" Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index f795d1c0cf..b3a80072d9 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -858,6 +858,44 @@ func Test_substitute_skipped_range()
bwipe!
endfunc
+" Test using the 'gdefault' option (when on, flag 'g' is default on).
+func Test_substitute_gdefault()
+ new
+
+ " First check without 'gdefault'
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/
+ call assert_equal('FOO bar foo', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/g
+ call assert_equal('FOO bar FOO', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/gg
+ call assert_equal('FOO bar foo', getline(1))
+
+ " Then check with 'gdefault'
+ set gdefault
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/
+ call assert_equal('FOO bar FOO', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/g
+ call assert_equal('FOO bar foo', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/gg
+ call assert_equal('FOO bar FOO', getline(1))
+
+ " Setting 'compatible' should reset 'gdefault'
+ call assert_equal(1, &gdefault)
+ " set compatible
+ set nogdefault
+ call assert_equal(0, &gdefault)
+ set nocompatible
+ call assert_equal(0, &gdefault)
+
+ bw!
+endfunc
+
" This was using "old_sub" after it was freed.
func Test_using_old_sub()
" set compatible maxfuncdepth=10
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index 07f35ddb4f..6d468ec9de 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -670,15 +670,19 @@ func Test_tabline_tabmenu()
call assert_equal(3, tabpagenr('$'))
" go to tab page 2 in operator-pending mode (should beep)
- call assert_beeps('call feedkeys("f" .. TabLineSelectPageCode(2), "Lx!")')
+ call assert_beeps('call feedkeys("c" .. TabLineSelectPageCode(2), "Lx!")')
+ call assert_equal(2, tabpagenr())
+ call assert_equal(3, tabpagenr('$'))
" open new tab page before tab page 1 in operator-pending mode (should beep)
- call assert_beeps('call feedkeys("f" .. TabMenuNewItemCode(1), "Lx!")')
+ call assert_beeps('call feedkeys("c" .. TabMenuNewItemCode(1), "Lx!")')
+ call assert_equal(1, tabpagenr())
+ call assert_equal(4, tabpagenr('$'))
" open new tab page after tab page 3 in normal mode
call feedkeys(TabMenuNewItemCode(4), "Lx!")
call assert_equal(4, tabpagenr())
- call assert_equal(4, tabpagenr('$'))
+ call assert_equal(5, tabpagenr('$'))
" go to tab page 2 in insert mode
call feedkeys("i" .. TabLineSelectPageCode(2) .. "\<C-C>", "Lx!")
@@ -686,17 +690,17 @@ func Test_tabline_tabmenu()
" close tab page 2 in insert mode
call feedkeys("i" .. TabMenuCloseItemCode(2) .. "\<C-C>", "Lx!")
- call assert_equal(3, tabpagenr('$'))
+ call assert_equal(4, tabpagenr('$'))
" open new tab page before tab page 3 in insert mode
call feedkeys("i" .. TabMenuNewItemCode(3) .. "\<C-C>", "Lx!")
call assert_equal(3, tabpagenr())
- call assert_equal(4, tabpagenr('$'))
+ call assert_equal(5, tabpagenr('$'))
" open new tab page after tab page 4 in insert mode
call feedkeys("i" .. TabMenuNewItemCode(5) .. "\<C-C>", "Lx!")
call assert_equal(5, tabpagenr())
- call assert_equal(5, tabpagenr('$'))
+ call assert_equal(6, tabpagenr('$'))
%bw!
endfunc
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 1dd656ece5..04c0218f74 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -1133,7 +1133,7 @@ endfunc
" Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands
func Test_inc_search()
new
- call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo'])
+ call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '==='])
call cursor(3, 1)
" Test for [i and ]i
@@ -1143,6 +1143,9 @@ func Test_inc_search()
call assert_equal('3:foo', execute('normal ]i'))
call assert_equal('4:foo', execute('normal 2]i'))
call assert_fails('normal 3]i', 'E389:')
+ call assert_fails('normal G]i', 'E349:')
+ call assert_fails('normal [i', 'E349:')
+ call cursor(3, 1)
" Test for :isearch
call assert_equal('1:foo', execute('isearch foo'))
@@ -1163,6 +1166,9 @@ func Test_inc_search()
call assert_equal([
\ ' 1: 4 3:foo',
\ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n"))
+ call assert_fails('normal G]I', 'E349:')
+ call assert_fails('normal [I', 'E349:')
+ call cursor(3, 1)
" Test for :ilist
call assert_equal([
@@ -1188,6 +1194,9 @@ func Test_inc_search()
exe "normal k2]\t"
call assert_equal([5, 3], [line('.'), col('.')])
call assert_fails("normal 2k3]\t", 'E389:')
+ call assert_fails("normal G[\t", 'E349:')
+ call assert_fails("normal ]\t", 'E349:')
+ call cursor(3, 1)
" Test for :ijump
call cursor(3, 1)
@@ -1212,6 +1221,8 @@ func Test_inc_search()
close
call assert_fails('3wincmd i', 'E387:')
call assert_fails('6wincmd i', 'E389:')
+ call assert_fails("normal G\<C-W>i", 'E349:')
+ call cursor(3, 1)
" Test for :isplit
isplit foo
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index eeb2946a8b..f21d6fcb99 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -1,7 +1,6 @@
" Test for textobjects
source check.vim
-CheckFeature textobjects
func CpoM(line, useM, expected)
new
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index a9ec405aa4..eb47af08d7 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -336,7 +336,7 @@ func Test_undofile_earlier()
" create undofile with timestamps older than Vim startup time.
let t0 = localtime() - 43200
call test_settime(t0)
- new Xfile
+ new XfileEarlier
call feedkeys("ione\<Esc>", 'xt')
set ul=100
call test_settime(t0 + 1)
@@ -350,12 +350,12 @@ func Test_undofile_earlier()
bwipe!
" restore normal timestamps.
call test_settime(0)
- new Xfile
+ new XfileEarlier
rundo Xundofile
earlier 1d
call assert_equal('', getline(1))
bwipe!
- call delete('Xfile')
+ call delete('XfileEarlier')
call delete('Xundofile')
endfunc
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index 9b010a5dbc..ab3503c282 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -140,6 +140,51 @@ func Test_list2str_str2list_latin1()
call assert_equal(s, sres)
endfunc
+func Test_setcellwidths()
+ call setcellwidths([
+ \ [0x1330, 0x1330, 2],
+ \ [9999, 10000, 1],
+ \ [0x1337, 0x1339, 2],
+ \])
+
+ call assert_equal(2, strwidth("\u1330"))
+ call assert_equal(1, strwidth("\u1336"))
+ call assert_equal(2, strwidth("\u1337"))
+ call assert_equal(2, strwidth("\u1339"))
+ call assert_equal(1, strwidth("\u133a"))
+
+ call setcellwidths([])
+
+ call assert_fails('call setcellwidths(1)', 'E714:')
+
+ call assert_fails('call setcellwidths([1, 2, 0])', 'E1109:')
+
+ call assert_fails('call setcellwidths([[0x101]])', 'E1110:')
+ call assert_fails('call setcellwidths([[0x101, 0x102]])', 'E1110:')
+ call assert_fails('call setcellwidths([[0x101, 0x102, 1, 4]])', 'E1110:')
+ call assert_fails('call setcellwidths([["a"]])', 'E1110:')
+
+ call assert_fails('call setcellwidths([[0x102, 0x101, 1]])', 'E1111:')
+
+ call assert_fails('call setcellwidths([[0x101, 0x102, 0]])', 'E1112:')
+ call assert_fails('call setcellwidths([[0x101, 0x102, 3]])', 'E1112:')
+
+ call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x115, 0x116, 2]])', 'E1113:')
+ call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x122, 0x123, 2]])', 'E1113:')
+
+ call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:')
+
+ set listchars=tab:--\\u2192
+ call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:')
+
+ set fillchars=stl:\\u2501
+ call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:')
+
+ set listchars&
+ set fillchars&
+ call setcellwidths([])
+endfunc
+
func Test_print_overlong()
" Text with more composing characters than MB_MAXBYTES.
new
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index de4629451b..0f204cdd0c 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1165,10 +1165,10 @@ func Test_type()
" call assert_equal(0, 0 + v:none)
call assert_equal(0, 0 + v:null)
- call assert_equal('false', '' . v:false)
- call assert_equal('true', '' . v:true)
- " call assert_equal('none', '' . v:none)
- call assert_equal('null', '' . v:null)
+ call assert_equal('v:false', '' . v:false)
+ call assert_equal('v:true', '' . v:true)
+ " call assert_equal('v:none', '' . v:none)
+ call assert_equal('v:null', '' . v:null)
call assert_true(v:false == 0)
call assert_false(v:false != 0)
@@ -1573,6 +1573,23 @@ func Test_script_local_func()
enew! | close
endfunc
+func Test_script_expand_sfile()
+ let lines =<< trim END
+ func s:snr()
+ return expand('<sfile>')
+ endfunc
+ let g:result = s:snr()
+ END
+ call writefile(lines, 'Xexpand')
+ source Xexpand
+ call assert_match('<SNR>\d\+_snr', g:result)
+ source Xexpand
+ call assert_match('<SNR>\d\+_snr', g:result)
+
+ call delete('Xexpand')
+ unlet g:result
+endfunc
+
func Test_compound_assignment_operators()
" Test for number
let x = 1
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index f9ac0e0884..9c1ad0c099 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -378,14 +378,17 @@ endfunc
func Test_Visual_paragraph_textobject()
new
- call setline(1, ['First line.',
- \ '',
- \ 'Second line.',
- \ 'Third line.',
- \ 'Fourth line.',
- \ 'Fifth line.',
- \ '',
- \ 'Sixth line.'])
+ let lines =<< trim [END]
+ First line.
+
+ Second line.
+ Third line.
+ Fourth line.
+ Fifth line.
+
+ Sixth line.
+ [END]
+ call setline(1, lines)
" When start and end of visual area are identical, 'ap' or 'ip' select
" the whole paragraph.
@@ -639,6 +642,14 @@ func Test_characterwise_visual_mode()
normal Gkvj$d
call assert_equal(['', 'a', ''], getline(1, '$'))
+ " characterwise visual mode: use a count with the visual mode from the last
+ " line in the buffer
+ %d _
+ call setline(1, ['one', 'two', 'three', 'four'])
+ norm! vj$y
+ norm! G1vy
+ call assert_equal('four', @")
+
" characterwise visual mode: replace a single character line and the eol
%d _
call setline(1, "a")
@@ -653,92 +664,6 @@ func Test_characterwise_visual_mode()
bwipe!
endfunc
-func Test_characterwise_select_mode()
- new
-
- " Select mode maps
- snoremap <lt>End> <End>
- snoremap <lt>Down> <Down>
- snoremap <lt>Del> <Del>
-
- " characterwise select mode: delete middle line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkkgh\<End>\<Del>"
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " characterwise select mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkkgh\<Down>\<End>\<Del>"
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " characterwise select mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Ggh\<End>\<Del>"
- call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
-
- " characterwise select mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkgh\<Down>\<End>\<Del>"
- call assert_equal(['', 'a', ''], getline(1, '$'))
-
- " CTRL-H in select mode behaves like 'x'
- call setline(1, 'abcdef')
- exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
- call assert_equal('ef', getline(1))
-
- " CTRL-O in select mode switches to visual mode for one command
- call setline(1, 'abcdef')
- exe "normal! gggh\<C-O>3lm"
- call assert_equal('mef', getline(1))
-
- sunmap <lt>End>
- sunmap <lt>Down>
- sunmap <lt>Del>
- bwipe!
-endfunc
-
-func Test_linewise_select_mode()
- new
-
- " linewise select mode: delete middle line
- call append('$', ['a', 'b', 'c'])
- exe "normal GkkgH\<Del>"
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " linewise select mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GkkgH\<Down>\<Del>"
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " linewise select mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GgH\<Del>"
- call assert_equal(['', 'a', 'b'], getline(1, '$'))
-
- " linewise select mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GkgH\<Down>\<Del>"
- call assert_equal(['', 'a'], getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Test for blockwise select mode (g CTRL-H)
-func Test_blockwise_select_mode()
- new
- call setline(1, ['foo', 'bar'])
- call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt')
- call assert_equal(['mmo', 'mmr'], getline(1, '$'))
- close!
-endfunc
-
func Test_visual_mode_put()
new
@@ -778,16 +703,16 @@ func Test_visual_mode_put()
bwipe!
endfunc
-func Test_select_mode_gv()
+func Test_gv_with_exclusive_selection()
new
- " gv in exclusive select mode after operation
+ " gv with exclusive selection after an operation
call append('$', ['zzz ', 'äà '])
set selection=exclusive
normal Gkv3lyjv3lpgvcxxx
call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$'))
- " gv in exclusive select mode without operation
+ " gv with exclusive selection without an operation
call deletebufline('', 1, '$')
call append('$', 'zzz ')
set selection=exclusive
@@ -1136,32 +1061,6 @@ func Test_star_register()
close!
endfunc
-" Test for using visual mode maps in select mode
-func Test_select_mode_map()
- new
- vmap <buffer> <F2> 3l
- call setline(1, 'Test line')
- call feedkeys("gh\<F2>map", 'xt')
- call assert_equal('map line', getline(1))
-
- vmap <buffer> <F2> ygV
- call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt')
- call assert_equal('abc line', getline(1))
-
- vmap <buffer> <F2> :<C-U>let v=100<CR>
- call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt')
- call assert_equal('foo line', getline(1))
-
- " reselect the select mode using gv from a visual mode map
- vmap <buffer> <F2> gv
- set selectmode=cmd
- call feedkeys("0gh\<F2>map", 'xt')
- call assert_equal('map line', getline(1))
- set selectmode&
-
- close!
-endfunc
-
" Test for changing text in visual mode with 'exclusive' selection
func Test_exclusive_selection()
new
@@ -1178,15 +1077,38 @@ func Test_exclusive_selection()
close!
endfunc
-" Test for starting visual mode with a count.
-" This test should be run without any previous visual modes. So this should be
-" run as a first test.
-func Test_AAA_start_visual_mode_with_count()
- new
- call setline(1, ['aaaaaaa', 'aaaaaaa', 'aaaaaaa', 'aaaaaaa'])
- normal! gg2Vy
- call assert_equal("aaaaaaa\naaaaaaa\n", @")
- close!
+" Test for starting linewise visual with a count.
+" This test needs to be run without any previous visual mode. Otherwise the
+" count will use the count from the previous visual mode.
+func Test_linewise_visual_with_count()
+ let after =<< trim [CODE]
+ call setline(1, ['one', 'two', 'three', 'four'])
+ norm! 3Vy
+ call assert_equal("one\ntwo\nthree\n", @")
+ call writefile(v:errors, 'Xtestout')
+ qall!
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for starting characterwise visual with a count.
+" This test needs to be run without any previous visual mode. Otherwise the
+" count will use the count from the previous visual mode.
+func Test_characterwise_visual_with_count()
+ let after =<< trim [CODE]
+ call setline(1, ['one two', 'three'])
+ norm! l5vy
+ call assert_equal("ne tw", @")
+ call writefile(v:errors, 'Xtestout')
+ qall!
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
endfunc
" Test for visually selecting an inner block (iB)
@@ -1550,5 +1472,25 @@ func Test_visual_area_adjusted_when_hiding()
bwipe!
endfunc
+func Test_switch_buffer_ends_visual_mode()
+ enew
+ call setline(1, 'foo')
+ set hidden
+ set virtualedit=all
+ let buf1 = bufnr()
+ enew
+ let buf2 = bufnr()
+ call setline(1, ['', '', '', ''])
+ call cursor(4, 5)
+ call feedkeys("\<C-V>3k4h", 'xt')
+ exe 'buffer' buf1
+ call assert_equal('n', mode())
+
+ set nohidden
+ set virtualedit=
+ bwipe!
+ exe 'bwipe!' buf2
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index bfbba1f793..a8735bcaf1 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -128,6 +128,25 @@ func Test_nowrite_quit_split()
bwipe Xfile
endfunc
+func Test_writefile_sync_arg()
+ " This doesn't check if fsync() works, only that the argument is accepted.
+ call writefile(['one'], 'Xtest', 's')
+ call writefile(['two'], 'Xtest', 'S')
+ call delete('Xtest')
+endfunc
+
+func Test_writefile_sync_dev_stdout()
+ if !has('unix')
+ return
+ endif
+ if filewritable('/dev/stdout')
+ " Just check that this doesn't cause an error.
+ call writefile(['one'], '/dev/stdout', 's')
+ else
+ throw 'Skipped: /dev/stdout is not writable'
+ endif
+endfunc
+
func Test_writefile_autowrite()
set autowrite
new
@@ -237,29 +256,18 @@ func Test_write_errors()
call delete('Xfile')
endfunc
-func Test_writefile_sync_dev_stdout()
- if !has('unix')
- return
- endif
- if filewritable('/dev/stdout')
- " Just check that this doesn't cause an error.
- call writefile(['one'], '/dev/stdout', 's')
- else
- throw 'Skipped: /dev/stdout is not writable'
- endif
-endfunc
-
-func Test_writefile_sync_arg()
- " This doesn't check if fsync() works, only that the argument is accepted.
- call writefile(['one'], 'Xtest', 's')
- call writefile(['two'], 'Xtest', 'S')
- call delete('Xtest')
+" Test for writing a file using invalid file encoding
+func Test_write_invalid_encoding()
+ new
+ call setline(1, 'abc')
+ call assert_fails('write ++enc=axbyc Xfile', 'E213:')
+ close!
endfunc
" Tests for reading and writing files with conversion for Win32.
func Test_write_file_encoding()
- CheckMSWindows
throw 'skipped: Nvim does not support :w ++enc=cp1251'
+ CheckMSWindows
let save_encoding = &encoding
let save_fileencodings = &fileencodings
set encoding& fileencodings&
diff --git a/src/nvim/testing.c b/src/nvim/testing.c
index 69b687e44f..e70e9f2cbd 100644
--- a/src/nvim/testing.c
+++ b/src/nvim/testing.c
@@ -7,6 +7,7 @@
#include "nvim/eval/encode.h"
#include "nvim/ex_docmd.h"
#include "nvim/os/os.h"
+#include "nvim/runtime.h"
#include "nvim/testing.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -17,21 +18,23 @@
static void prepare_assert_error(garray_T *gap)
{
char buf[NUMBUFLEN];
+ char *sname = estack_sfile(ESTACK_NONE);
ga_init(gap, 1, 100);
- if (sourcing_name != NULL) {
- ga_concat(gap, (char *)sourcing_name);
- if (sourcing_lnum > 0) {
+ if (sname != NULL) {
+ ga_concat(gap, sname);
+ if (SOURCING_LNUM > 0) {
ga_concat(gap, " ");
}
}
- if (sourcing_lnum > 0) {
- vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum);
+ if (SOURCING_LNUM > 0) {
+ vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)SOURCING_LNUM);
ga_concat(gap, buf);
}
- if (sourcing_name != NULL || sourcing_lnum > 0) {
+ if (sname != NULL || SOURCING_LNUM > 0) {
ga_concat(gap, ": ");
}
+ xfree(sname);
}
/// Append "p[clen]" to "gap", escaping unprintable characters.
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index e2289eb9ce..38e8c15762 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -171,7 +171,7 @@ UI *tui_start(void)
ui->option_set = tui_option_set;
ui->raw_line = tui_raw_line;
- memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
+ CLEAR_FIELD(ui->ui_ext);
ui->ui_ext[kUILinegrid] = true;
ui->ui_ext[kUITermColors] = true;
@@ -875,6 +875,53 @@ safe_move:
ugrid_goto(grid, row, col);
}
+static void print_spaces(UI *ui, int width)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+
+ out(ui, data->space_buf, (size_t)width);
+ grid->col += width;
+ if (data->immediate_wrap_after_last_column) {
+ // Printing at the right margin immediately advances the cursor.
+ final_column_wrap(ui);
+ }
+}
+
+/// Move cursor to the position given by `row` and `col` and print the character in `cell`.
+/// This allows the grid and the host terminal to assume different widths of ambiguous-width chars.
+///
+/// @param is_doublewidth whether the character is double-width on the grid.
+/// If true and the character is ambiguous-width, clear two cells.
+static void print_cell_at_pos(UI *ui, int row, int col, UCell *cell, bool is_doublewidth)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+
+ if (grid->row == -1 && cell->data[0] == NUL) {
+ // If cursor needs to repositioned and there is nothing to print, don't move cursor.
+ return;
+ }
+
+ cursor_goto(ui, row, col);
+
+ bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data));
+ if (is_ambiwidth && is_doublewidth) {
+ // Clear the two screen cells.
+ // If the character is single-width in the host terminal it won't change the second cell.
+ update_attrs(ui, cell->attr);
+ print_spaces(ui, 2);
+ cursor_goto(ui, row, col);
+ }
+
+ print_cell(ui, cell);
+
+ if (is_ambiwidth) {
+ // Force repositioning cursor after printing an ambiguous-width character.
+ grid->row = -1;
+ }
+}
+
static void clear_region(UI *ui, int top, int bot, int left, int right, int attr_id)
{
TUIData *data = ui->data;
@@ -888,7 +935,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right, int attr
&& left == 0 && right == ui->width && bot == ui->height) {
if (top == 0) {
unibi_out(ui, unibi_clear_screen);
- ugrid_goto(&data->grid, top, left);
+ ugrid_goto(grid, top, left);
} else {
cursor_goto(ui, top, 0);
unibi_out(ui, unibi_clr_eos);
@@ -905,12 +952,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right, int attr
UNIBI_SET_NUM_VAR(data->params[0], width);
unibi_out(ui, unibi_erase_chars);
} else {
- out(ui, data->space_buf, (size_t)width);
- grid->col += width;
- if (data->immediate_wrap_after_last_column) {
- // Printing at the right margin immediately advances the cursor.
- final_column_wrap(ui);
- }
+ print_spaces(ui, width);
}
}
}
@@ -1302,8 +1344,8 @@ static void tui_flush(UI *ui)
}
UGRID_FOREACH_CELL(grid, row, r.left, clear_col, {
- cursor_goto(ui, row, curcol);
- print_cell(ui, cell);
+ print_cell_at_pos(ui, row, curcol, cell,
+ curcol < clear_col - 1 && (cell + 1)->data[0] == NUL);
});
if (clear_col < r.right) {
clear_region(ui, row, row + 1, clear_col, r.right, clear_attr);
@@ -1439,8 +1481,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I
grid->cells[linerow][c].attr = attrs[c - startcol];
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, {
- cursor_goto(ui, (int)linerow, curcol);
- print_cell(ui, cell);
+ print_cell_at_pos(ui, (int)linerow, curcol, cell,
+ curcol < endcol - 1 && (cell + 1)->data[0] == NUL);
});
if (clearcol > endcol) {
@@ -1458,8 +1500,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I
if (endcol != grid->width) {
// Print the last char of the row, if we haven't already done so.
int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1;
- cursor_goto(ui, (int)linerow, grid->width - size);
- print_cell(ui, &grid->cells[linerow][grid->width - size]);
+ print_cell_at_pos(ui, (int)linerow, grid->width - size,
+ &grid->cells[linerow][grid->width - size], size == 2);
}
// Wrap the cursor over to the next line. The next line will be
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 00b9e6fc09..477102276c 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -28,6 +28,8 @@ typedef handle_T NS;
typedef struct expand expand_T;
+typedef uint64_t proftime_T;
+
typedef enum {
kNone = -1,
kFalse = 0,
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 4fcfee1192..da671a3ad1 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -13,11 +13,12 @@
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/event/loop.h"
-#include "nvim/ex_cmds2.h"
#include "nvim/ex_getln.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
#include "nvim/main.h"
@@ -31,8 +32,7 @@
#include "nvim/os/signal.h"
#include "nvim/os/time.h"
#include "nvim/os_unix.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/vim.h"
@@ -663,6 +663,6 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
// non-positive indicates no request
wp->w_height_request = MAX(height, 0);
wp->w_width_request = MAX(width, 0);
- win_set_inner_size(wp);
+ win_set_inner_size(wp, true);
}
}
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 5df70d0d8e..2216e25db9 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -13,6 +13,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lib/kvec.h"
@@ -22,8 +23,7 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/os.h"
-#include "nvim/popupmnu.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
#include "nvim/ugrid.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 45c083b034..75a09b244c 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1,73 +1,71 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * undo.c: multi level undo facility
- *
- * The saved lines are stored in a list of lists (one for each buffer):
- *
- * b_u_oldhead------------------------------------------------+
- * |
- * V
- * +--------------+ +--------------+ +--------------+
- * b_u_newhead--->| u_header | | u_header | | u_header |
- * | uh_next------>| uh_next------>| uh_next---->NULL
- * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev |
- * | uh_entry | | uh_entry | | uh_entry |
- * +--------|-----+ +--------|-----+ +--------|-----+
- * | | |
- * V V V
- * +--------------+ +--------------+ +--------------+
- * | u_entry | | u_entry | | u_entry |
- * | ue_next | | ue_next | | ue_next |
- * +--------|-----+ +--------|-----+ +--------|-----+
- * | | |
- * V V V
- * +--------------+ NULL NULL
- * | u_entry |
- * | ue_next |
- * +--------|-----+
- * |
- * V
- * etc.
- *
- * Each u_entry list contains the information for one undo or redo.
- * curbuf->b_u_curhead points to the header of the last undo (the next redo),
- * or is NULL if nothing has been undone (end of the branch).
- *
- * For keeping alternate undo/redo branches the uh_alt field is used. Thus at
- * each point in the list a branch may appear for an alternate to redo. The
- * uh_seq field is numbered sequentially to be able to find a newer or older
- * branch.
- *
- * +---------------+ +---------------+
- * b_u_oldhead --->| u_header | | u_header |
- * | uh_alt_next ---->| uh_alt_next ----> NULL
- * NULL <----- uh_alt_prev |<------ uh_alt_prev |
- * | uh_prev | | uh_prev |
- * +-----|---------+ +-----|---------+
- * | |
- * V V
- * +---------------+ +---------------+
- * | u_header | | u_header |
- * | uh_alt_next | | uh_alt_next |
- * b_u_newhead --->| uh_alt_prev | | uh_alt_prev |
- * | uh_prev | | uh_prev |
- * +-----|---------+ +-----|---------+
- * | |
- * V V
- * NULL +---------------+ +---------------+
- * | u_header | | u_header |
- * | uh_alt_next ---->| uh_alt_next |
- * | uh_alt_prev |<------ uh_alt_prev |
- * | uh_prev | | uh_prev |
- * +-----|---------+ +-----|---------+
- * | |
- * etc. etc.
- *
- *
- * All data is allocated and will all be freed when the buffer is unloaded.
- */
+// undo.c: multi level undo facility
+
+// The saved lines are stored in a list of lists (one for each buffer):
+//
+// b_u_oldhead------------------------------------------------+
+// |
+// V
+// +--------------+ +--------------+ +--------------+
+// b_u_newhead--->| u_header | | u_header | | u_header |
+// | uh_next------>| uh_next------>| uh_next---->NULL
+// NULL<--------uh_prev |<---------uh_prev |<---------uh_prev |
+// | uh_entry | | uh_entry | | uh_entry |
+// +--------|-----+ +--------|-----+ +--------|-----+
+// | | |
+// V V V
+// +--------------+ +--------------+ +--------------+
+// | u_entry | | u_entry | | u_entry |
+// | ue_next | | ue_next | | ue_next |
+// +--------|-----+ +--------|-----+ +--------|-----+
+// | | |
+// V V V
+// +--------------+ NULL NULL
+// | u_entry |
+// | ue_next |
+// +--------|-----+
+// |
+// V
+// etc.
+//
+// Each u_entry list contains the information for one undo or redo.
+// curbuf->b_u_curhead points to the header of the last undo (the next redo),
+// or is NULL if nothing has been undone (end of the branch).
+//
+// For keeping alternate undo/redo branches the uh_alt field is used. Thus at
+// each point in the list a branch may appear for an alternate to redo. The
+// uh_seq field is numbered sequentially to be able to find a newer or older
+// branch.
+//
+// +---------------+ +---------------+
+// b_u_oldhead --->| u_header | | u_header |
+// | uh_alt_next ---->| uh_alt_next ----> NULL
+// NULL <----- uh_alt_prev |<------ uh_alt_prev |
+// | uh_prev | | uh_prev |
+// +-----|---------+ +-----|---------+
+// | |
+// V V
+// +---------------+ +---------------+
+// | u_header | | u_header |
+// | uh_alt_next | | uh_alt_next |
+// b_u_newhead --->| uh_alt_prev | | uh_alt_prev |
+// | uh_prev | | uh_prev |
+// +-----|---------+ +-----|---------+
+// | |
+// V V
+// NULL +---------------+ +---------------+
+// | u_header | | u_header |
+// | uh_alt_next ---->| uh_alt_next |
+// | uh_alt_prev |<------ uh_alt_prev |
+// | uh_prev | | uh_prev |
+// +-----|---------+ +-----|---------+
+// | |
+// etc. etc.
+//
+//
+// All data is allocated and will all be freed when the buffer is unloaded.
// Uncomment the next line for including the u_check() function. This warns
// for errors in the debug information.
@@ -88,6 +86,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/cursor.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
@@ -113,6 +112,12 @@
#include "nvim/types.h"
#include "nvim/undo.h"
+/// Structure passed around between undofile functions.
+typedef struct {
+ buf_T *bi_buf;
+ FILE *bi_fp;
+} bufinfo_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
#endif
@@ -120,31 +125,25 @@
// used in undo_end() to report number of added and deleted lines
static long u_newcount, u_oldcount;
-/*
- * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember
- * the action that "u" should do.
- */
+// When 'u' flag included in 'cpoptions', we behave like vi. Need to remember
+// the action that "u" should do.
static bool undo_undoes = false;
static int lastmark = 0;
#if defined(U_DEBUG)
-/*
- * Check the undo structures for being valid. Print a warning when something
- * looks wrong.
- */
+// Check the undo structures for being valid. Print a warning when something
+// looks wrong.
static int seen_b_u_curhead;
static int seen_b_u_newhead;
static int header_count;
static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *exp_uh_alt_prev)
{
- u_entry_T *uep;
-
if (uhp == NULL) {
return;
}
- ++header_count;
+ header_count++;
if (uhp == curbuf->b_u_curhead && ++seen_b_u_curhead > 1) {
emsg("b_u_curhead found twice (looping?)");
return;
@@ -170,7 +169,7 @@ static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *e
}
// Check the undo tree at this header.
- for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) {
+ for (u_entry_T *uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) {
if (uep->ue_magic != UE_MAGIC) {
emsg("ue_magic wrong (may be using freed memory)");
break;
@@ -209,11 +208,9 @@ static void u_check(int newhead_may_be_NULL)
#endif
-/*
- * Save the current line for both the "u" and "U" command.
- * Careful: may trigger autocommands that reload the buffer.
- * Returns OK or FAIL.
- */
+/// Save the current line for both the "u" and "U" command.
+/// Careful: may trigger autocommands that reload the buffer.
+/// Returns OK or FAIL.
int u_save_cursor(void)
{
linenr_T cur = curwin->w_cursor.lnum;
@@ -223,12 +220,10 @@ int u_save_cursor(void)
return u_save(top, bot);
}
-/*
- * Save the lines between "top" and "bot" for both the "u" and "U" command.
- * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1.
- * Careful: may trigger autocommands that reload the buffer.
- * Returns FAIL when lines could not be saved, OK otherwise.
- */
+/// Save the lines between "top" and "bot" for both the "u" and "U" command.
+/// "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1.
+/// Careful: may trigger autocommands that reload the buffer.
+/// Returns FAIL when lines could not be saved, OK otherwise.
int u_save(linenr_T top, linenr_T bot)
{
if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
@@ -242,35 +237,29 @@ int u_save(linenr_T top, linenr_T bot)
return u_savecommon(curbuf, top, bot, (linenr_T)0, false);
}
-/*
- * Save the line "lnum" (used by ":s" and "~" command).
- * The line is replaced, so the new bottom line is lnum + 1.
- * Careful: may trigger autocommands that reload the buffer.
- * Returns FAIL when lines could not be saved, OK otherwise.
- */
+/// Save the line "lnum" (used by ":s" and "~" command).
+/// The line is replaced, so the new bottom line is lnum + 1.
+/// Careful: may trigger autocommands that reload the buffer.
+/// Returns FAIL when lines could not be saved, OK otherwise.
int u_savesub(linenr_T lnum)
{
return u_savecommon(curbuf, lnum - 1, lnum + 1, lnum + 1, false);
}
-/*
- * A new line is inserted before line "lnum" (used by :s command).
- * The line is inserted, so the new bottom line is lnum + 1.
- * Careful: may trigger autocommands that reload the buffer.
- * Returns FAIL when lines could not be saved, OK otherwise.
- */
+/// A new line is inserted before line "lnum" (used by :s command).
+/// The line is inserted, so the new bottom line is lnum + 1.
+/// Careful: may trigger autocommands that reload the buffer.
+/// Returns FAIL when lines could not be saved, OK otherwise.
int u_inssub(linenr_T lnum)
{
return u_savecommon(curbuf, lnum - 1, lnum, lnum + 1, false);
}
-/*
- * Save the lines "lnum" - "lnum" + nlines (used by delete command).
- * The lines are deleted, so the new bottom line is lnum, unless the buffer
- * becomes empty.
- * Careful: may trigger autocommands that reload the buffer.
- * Returns FAIL when lines could not be saved, OK otherwise.
- */
+/// Save the lines "lnum" - "lnum" + nlines (used by delete command).
+/// The lines are deleted, so the new bottom line is lnum, unless the buffer
+/// becomes empty.
+/// Careful: may trigger autocommands that reload the buffer.
+/// Returns FAIL when lines could not be saved, OK otherwise.
int u_savedel(linenr_T lnum, long nlines)
{
return u_savecommon(curbuf, lnum - 1, lnum + (linenr_T)nlines,
@@ -322,25 +311,15 @@ static inline void zero_fmark_additional_data(fmark_T *fmarks)
}
}
-/*
- * Common code for various ways to save text before a change.
- * "top" is the line above the first changed line.
- * "bot" is the line below the last changed line.
- * "newbot" is the new bottom line. Use zero when not known.
- * "reload" is TRUE when saving for a buffer reload.
- * Careful: may trigger autocommands that reload the buffer.
- * Returns FAIL when lines could not be saved, OK otherwise.
- */
+/// Common code for various ways to save text before a change.
+/// "top" is the line above the first changed line.
+/// "bot" is the line below the last changed line.
+/// "newbot" is the new bottom line. Use zero when not known.
+/// "reload" is true when saving for a buffer reload.
+/// Careful: may trigger autocommands that reload the buffer.
+/// Returns FAIL when lines could not be saved, OK otherwise.
int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int reload)
{
- linenr_T lnum;
- long i;
- u_header_T *uhp;
- u_header_T *old_curhead;
- u_entry_T *uep;
- u_entry_T *prev_uep;
- long size;
-
if (!reload) {
// When making changes is not allowed return FAIL. It's a crude way
// to make all change commands fail.
@@ -365,16 +344,19 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
}
#ifdef U_DEBUG
- u_check(FALSE);
+ u_check(false);
#endif
- size = bot - top - 1;
+ u_entry_T *uep;
+ u_entry_T *prev_uep;
+ long size = bot - top - 1;
// If curbuf->b_u_synced == true make a new header.
if (buf->b_u_synced) {
// Need to create new entry in b_changelist.
buf->b_new_change = true;
+ u_header_T *uhp;
if (get_undolevel(buf) >= 0) {
// Make a new header entry. Do this first so that we don't mess
// up the undo info when out of memory.
@@ -387,19 +369,15 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
uhp = NULL;
}
- /*
- * If we undid more than we redid, move the entry lists before and
- * including curbuf->b_u_curhead to an alternate branch.
- */
- old_curhead = buf->b_u_curhead;
+ // If we undid more than we redid, move the entry lists before and
+ // including curbuf->b_u_curhead to an alternate branch.
+ u_header_T *old_curhead = buf->b_u_curhead;
if (old_curhead != NULL) {
buf->b_u_newhead = old_curhead->uh_next.ptr;
buf->b_u_curhead = NULL;
}
- /*
- * free headers to keep the size right
- */
+ // free headers to keep the size right
while (buf->b_u_numhead > get_undolevel(buf)
&& buf->b_u_oldhead != NULL) {
u_header_T *uhfree = buf->b_u_oldhead;
@@ -418,7 +396,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
u_freebranch(buf, uhfree, &old_curhead);
}
#ifdef U_DEBUG
- u_check(TRUE);
+ u_check(true);
#endif
}
@@ -490,19 +468,17 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
return OK;
}
- /*
- * When saving a single line, and it has been saved just before, it
- * doesn't make sense saving it again. Saves a lot of memory when
- * making lots of changes inside the same line.
- * This is only possible if the previous change didn't increase or
- * decrease the number of lines.
- * Check the ten last changes. More doesn't make sense and takes too
- * long.
- */
+ // When saving a single line, and it has been saved just before, it
+ // doesn't make sense saving it again. Saves a lot of memory when
+ // making lots of changes inside the same line.
+ // This is only possible if the previous change didn't increase or
+ // decrease the number of lines.
+ // Check the ten last changes. More doesn't make sense and takes too
+ // long.
if (size == 1) {
uep = u_get_headentry(buf);
prev_uep = NULL;
- for (i = 0; i < 10; ++i) {
+ for (long i = 0; i < 10; i++) {
if (uep == NULL) {
break;
}
@@ -514,7 +490,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
!= (uep->ue_bot == 0
? buf->b_ml.ml_line_count + 1
: uep->ue_bot))
- : uep->ue_lcount != buf->b_ml.ml_line_count)
+ : uep->ue_lcount != buf->b_ml.ml_line_count)
|| (uep->ue_size > 1
&& top >= uep->ue_top
&& top + 2 <= uep->ue_top + uep->ue_size + 1)) {
@@ -561,11 +537,9 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
u_getbot(buf);
}
- /*
- * add lines in front of entry list
- */
+ // add lines in front of entry list
uep = xmalloc(sizeof(u_entry_T));
- memset(uep, 0, sizeof(u_entry_T));
+ CLEAR_POINTER(uep);
#ifdef U_DEBUG
uep->ue_magic = UE_MAGIC;
#endif
@@ -585,7 +559,9 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
if (size > 0) {
uep->ue_array = xmalloc(sizeof(char_u *) * (size_t)size);
- for (i = 0, lnum = top + 1; i < size; ++i) {
+ linenr_T lnum;
+ long i;
+ for (i = 0, lnum = top + 1; i < size; i++) {
fast_breakcheck();
if (got_int) {
u_freeentry(uep, i);
@@ -607,7 +583,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
undo_undoes = false;
#ifdef U_DEBUG
- u_check(FALSE);
+ u_check(false);
#endif
return OK;
}
@@ -643,12 +619,9 @@ static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
void u_compute_hash(buf_T *buf, char_u *hash)
{
context_sha256_T ctx;
- linenr_T lnum;
- char_u *p;
-
sha256_start(&ctx);
- for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- p = ml_get_buf(buf, lnum, false);
+ for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
+ char_u *p = ml_get_buf(buf, lnum, false);
sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1));
}
sha256_finish(&ctx, hash);
@@ -668,21 +641,14 @@ void u_compute_hash(buf_T *buf, char_u *hash)
char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- char *dirp;
- char dir_name[MAXPATHL + 1];
- char *munged_name = NULL;
- char *undo_file_name = NULL;
const char *ffname = buf_ffname;
-#ifdef HAVE_READLINK
- char fname_buf[MAXPATHL];
-#endif
- char *p;
if (ffname == NULL) {
return NULL;
}
#ifdef HAVE_READLINK
+ char fname_buf[MAXPATHL];
// Expand symlink in the file name, so that we put the undo file with the
// actual file instead of with the symlink.
if (resolve_symlink((const char_u *)ffname, (char_u *)fname_buf) == OK) {
@@ -690,9 +656,13 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
}
#endif
+ char dir_name[MAXPATHL + 1];
+ char *munged_name = NULL;
+ char *undo_file_name = NULL;
+
// Loop over 'undodir'. When reading find the first file that exists.
// When not reading use the first directory that exists or ".".
- dirp = (char *)p_udir;
+ char *dirp = (char *)p_udir;
while (*dirp != NUL) {
size_t dir_len = copy_option_part(&dirp, dir_name, MAXPATHL, ",");
if (dir_len == 1 && dir_name[0] == '.') {
@@ -710,7 +680,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
dir_name[dir_len] = NUL;
// Remove trailing pathseps from directory name
- p = &dir_name[dir_len - 1];
+ char *p = &dir_name[dir_len - 1];
while (vim_ispathsep(*p)) {
*p-- = NUL;
}
@@ -731,9 +701,9 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
if (has_directory) {
if (munged_name == NULL) {
munged_name = xstrdup(ffname);
- for (p = munged_name; *p != NUL; MB_PTR_ADV(p)) {
- if (vim_ispathsep(*p)) {
- *p = '%';
+ for (char *c = munged_name; *c != NUL; MB_PTR_ADV(c)) {
+ if (vim_ispathsep(*c)) {
+ *c = '%';
}
}
}
@@ -765,12 +735,9 @@ static void corruption_error(const char *const mesg, const char *const file_name
static void u_free_uhp(u_header_T *uhp)
{
- u_entry_T *nuep;
- u_entry_T *uep;
-
- uep = uhp->uh_entry;
+ u_entry_T *uep = uhp->uh_entry;
while (uep != NULL) {
- nuep = uep->ue_next;
+ u_entry_T *nuep = uep->ue_next;
u_freeentry(uep, uep->ue_size);
uep = nuep;
}
@@ -895,7 +862,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
static u_header_T *unserialize_uhp(bufinfo_T *bi, const char *file_name)
{
u_header_T *uhp = xmalloc(sizeof(u_header_T));
- memset(uhp, 0, sizeof(u_header_T));
+ CLEAR_POINTER(uhp);
#ifdef U_DEBUG
uhp->uh_magic = UH_MAGIC;
#endif
@@ -1016,23 +983,21 @@ static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup)
static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error, const char *filename)
{
- UndoObjectType type;
uint8_t *buf = NULL;
- size_t n_elems;
ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject));
- type = (UndoObjectType)undo_read_4c(bi);
+ UndoObjectType type = (UndoObjectType)undo_read_4c(bi);
extup->type = type;
if (type == kExtmarkSplice) {
- n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t);
+ size_t n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t);
buf = xcalloc(n_elems, sizeof(uint8_t));
if (!undo_read(bi, buf, n_elems)) {
goto error;
}
extup->data.splice = *(ExtmarkSplice *)buf;
} else if (type == kExtmarkMove) {
- n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t);
+ size_t n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t);
buf = xcalloc(n_elems, sizeof(uint8_t));
if (!undo_read(bi, buf, n_elems)) {
goto error;
@@ -1083,7 +1048,7 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep)
static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_name)
{
u_entry_T *uep = xmalloc(sizeof(u_entry_T));
- memset(uep, 0, sizeof(u_entry_T));
+ CLEAR_POINTER(uep);
#ifdef U_DEBUG
uep->ue_magic = UE_MAGIC;
#endif
@@ -1173,17 +1138,12 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info)
void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, char_u *const hash)
FUNC_ATTR_NONNULL_ARG(3, 4)
{
- u_header_T *uhp;
char *file_name;
- int mark;
#ifdef U_DEBUG
int headers_written = 0;
#endif
- int fd;
FILE *fp = NULL;
- int perm;
bool write_ok = false;
- bufinfo_T bi;
if (name == NULL) {
file_name = u_get_undo_file_name(buf->b_ffname, false);
@@ -1199,12 +1159,10 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
file_name = (char *)name;
}
- /*
- * Decide about the permission to use for the undo file. If the buffer
- * has a name use the permission of the original file. Otherwise only
- * allow the user to access the undo file.
- */
- perm = 0600;
+ // Decide about the permission to use for the undo file. If the buffer
+ // has a name use the permission of the original file. Otherwise only
+ // allow the user to access the undo file.
+ int perm = 0600;
if (buf->b_ffname != NULL) {
perm = os_getperm((const char *)buf->b_ffname);
if (perm < 0) {
@@ -1215,6 +1173,8 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
// Strip any sticky and executable bits.
perm = perm & 0666;
+ int fd;
+
// If the undo file already exists, verify that it actually is an undo
// file, and delete it.
if (os_path_exists((char_u *)file_name)) {
@@ -1279,15 +1239,13 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
#ifdef U_DEBUG
// Check there is no problem in undo info before writing.
- u_check(FALSE);
+ u_check(false);
#endif
#ifdef UNIX
- /*
- * Try to set the group of the undo file same as the original file. If
- * this fails, set the protection bits for the group same as the
- * protection bits for others.
- */
+ // Try to set the group of the undo file same as the original file. If
+ // this fails, set the protection bits for the group same as the
+ // protection bits for others.
FileInfo file_info_old;
FileInfo file_info_new;
if (buf->b_ffname != NULL
@@ -1310,26 +1268,24 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
// Undo must be synced.
u_sync(true);
- /*
- * Write the header.
- */
- bi.bi_buf = buf;
- bi.bi_fp = fp;
+ // Write the header.
+ bufinfo_T bi = {
+ .bi_buf = buf,
+ .bi_fp = fp,
+ };
if (!serialize_header(&bi, hash)) {
goto write_error;
}
- /*
- * Iteratively serialize UHPs and their UEPs from the top down.
- */
- mark = ++lastmark;
- uhp = buf->b_u_oldhead;
+ // Iteratively serialize UHPs and their UEPs from the top down.
+ int mark = ++lastmark;
+ u_header_T *uhp = buf->b_u_oldhead;
while (uhp != NULL) {
// Serialize current UHP if we haven't seen it
if (uhp->uh_walk != mark) {
uhp->uh_walk = mark;
#ifdef U_DEBUG
- ++headers_written;
+ headers_written++;
#endif
if (!serialize_uhp(&bi, uhp)) {
goto write_error;
@@ -1437,9 +1393,10 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT
goto error;
}
- bufinfo_T bi;
- bi.bi_buf = curbuf;
- bi.bi_fp = fp;
+ bufinfo_T bi = {
+ .bi_buf = curbuf,
+ .bi_fp = fp,
+ };
// Read the undo file header.
char_u magic_buf[UF_START_MAGIC_LEN];
@@ -1568,7 +1525,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT
// We have put all of the headers into a table. Now we iterate through the
// table and swizzle each sequence number we have stored in uh_*_seq into
// a pointer corresponding to the header with that sequence number.
- short old_idx = -1, new_idx = -1, cur_idx = -1;
+ int16_t old_idx = -1, new_idx = -1, cur_idx = -1;
for (int i = 0; i < num_head; i++) {
u_header_T *uhp = uhp_table[i];
if (uhp == NULL) {
@@ -1614,18 +1571,18 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT
}
}
if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) {
- assert(i <= SHRT_MAX);
- old_idx = (short)i;
+ assert(i <= INT16_MAX);
+ old_idx = (int16_t)i;
SET_FLAG(i);
}
if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq) {
- assert(i <= SHRT_MAX);
- new_idx = (short)i;
+ assert(i <= INT16_MAX);
+ new_idx = (int16_t)i;
SET_FLAG(i);
}
if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq) {
- assert(i <= SHRT_MAX);
- cur_idx = (short)i;
+ assert(i <= INT16_MAX);
+ cur_idx = (int16_t)i;
SET_FLAG(i);
}
}
@@ -1656,7 +1613,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT
}
}
xfree(uhp_table_used);
- u_check(TRUE);
+ u_check(true);
#endif
if (name != NULL) {
@@ -1781,17 +1738,13 @@ static uint8_t *undo_read_string(bufinfo_T *bi, size_t len)
return ptr;
}
-/*
- * If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible).
- * If 'cpoptions' does not contain 'u': Always undo.
- */
+/// If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible).
+/// If 'cpoptions' does not contain 'u': Always undo.
void u_undo(int count)
{
- /*
- * If we get an undo command while executing a macro, we behave like the
- * original vi. If this happens twice in one macro the result will not
- * be compatible.
- */
+ // If we get an undo command while executing a macro, we behave like the
+ // original vi. If this happens twice in one macro the result will not
+ // be compatible.
if (curbuf->b_u_synced == false) {
u_sync(true);
count = 1;
@@ -1805,10 +1758,8 @@ void u_undo(int count)
u_doit(count, false, true);
}
-/*
- * If 'cpoptions' contains 'u': Repeat the previous undo or redo.
- * If 'cpoptions' does not contain 'u': Always redo.
- */
+/// If 'cpoptions' contains 'u': Repeat the previous undo or redo.
+/// If 'cpoptions' does not contain 'u': Always redo.
void u_redo(int count)
{
if (vim_strchr(p_cpo, CPO_UNDO) == NULL) {
@@ -1870,8 +1821,6 @@ bool u_undo_and_forget(int count)
/// @param do_buf_event If `true`, send the changedtick with the buffer updates
static void u_doit(int startcount, bool quiet, bool do_buf_event)
{
- int count = startcount;
-
if (!undo_allowed(curbuf)) {
return;
}
@@ -1881,6 +1830,8 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
u_oldcount = -1;
}
+
+ int count = startcount;
while (count--) {
// Do the change warning now, so that it triggers FileChangedRO when
// needed. This may cause the file to be reloaded, that must happen
@@ -1940,21 +1891,6 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
// "sec" must be false then.
void undo_time(long step, bool sec, bool file, bool absolute)
{
- long target;
- long closest;
- long closest_start;
- long closest_seq = 0;
- long val;
- u_header_T *uhp = NULL;
- u_header_T *last;
- int mark;
- int nomark = 0; // shut up compiler
- int round;
- bool dosec = sec;
- bool dofile = file;
- bool above = false;
- bool did_undo = true;
-
if (text_locked()) {
text_locked_msg();
return;
@@ -1971,6 +1907,14 @@ void undo_time(long step, bool sec, bool file, bool absolute)
u_oldcount = -1;
}
+ long target;
+ long closest;
+ u_header_T *uhp = NULL;
+ bool dosec = sec;
+ bool dofile = file;
+ bool above = false;
+ bool did_undo = true;
+
// "target" is the node below which we want to be.
// Init "closest" to a value we can't reach.
if (absolute) {
@@ -2034,8 +1978,10 @@ void undo_time(long step, bool sec, bool file, bool absolute)
}
}
}
- closest_start = closest;
- closest_seq = curbuf->b_u_seq_cur;
+ long closest_start = closest;
+ long closest_seq = curbuf->b_u_seq_cur;
+ int mark;
+ int nomark = 0; // shut up compiler
// When "target" is 0; Back to origin.
if (target == 0) {
@@ -2043,15 +1989,13 @@ void undo_time(long step, bool sec, bool file, bool absolute)
goto target_zero;
}
- /*
- * May do this twice:
- * 1. Search for "target", update "closest" to the best match found.
- * 2. If "target" not found search for "closest".
- *
- * When using the closest time we use the sequence number in the second
- * round, because there may be several entries with the same time.
- */
- for (round = 1; round <= 2; round++) {
+ // May do this twice:
+ // 1. Search for "target", update "closest" to the best match found.
+ // 2. If "target" not found search for "closest".
+ //
+ // When using the closest time we use the sequence number in the second
+ // round, because there may be several entries with the same time.
+ for (int round = 1; round <= 2; round++) {
// Find the path from the current state to where we want to go. The
// desired state can be anywhere in the undo tree, need to go all over
// it. We put "nomark" in uh_walk where we have been without success,
@@ -2067,13 +2011,9 @@ void undo_time(long step, bool sec, bool file, bool absolute)
while (uhp != NULL) {
uhp->uh_walk = mark;
- if (dosec) {
- val = (long)(uhp->uh_time);
- } else if (dofile) {
- val = uhp->uh_save_nr;
- } else {
- val = uhp->uh_seq;
- }
+ long val = dosec ? (long)(uhp->uh_time) :
+ dofile ? uhp->uh_save_nr
+ : uhp->uh_seq;
if (round == 1 && !(dofile && val == 0)) {
// Remember the header that is closest to the target.
@@ -2081,17 +2021,17 @@ void undo_time(long step, bool sec, bool file, bool absolute)
// "b_u_seq_cur"). When the timestamp is equal find the
// highest/lowest sequence number.
if ((step < 0 ? uhp->uh_seq <= curbuf->b_u_seq_cur
- : uhp->uh_seq > curbuf->b_u_seq_cur)
+ : uhp->uh_seq > curbuf->b_u_seq_cur)
&& ((dosec && val == closest)
? (step < 0
? uhp->uh_seq < closest_seq
: uhp->uh_seq > closest_seq)
- : closest == closest_start
+ : closest == closest_start
|| (val > target
? (closest > target
? val - target <= closest - target
: val - target <= target - closest)
- : (closest > target
+ : (closest > target
? target - val <= closest - target
: target - val <= target - closest)))) {
closest = val;
@@ -2110,11 +2050,10 @@ void undo_time(long step, bool sec, bool file, bool absolute)
if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark
&& uhp->uh_prev.ptr->uh_walk != mark) {
uhp = uhp->uh_prev.ptr;
- }
- // go to alternate branch if we haven't been there
- else if (uhp->uh_alt_next.ptr != NULL
- && uhp->uh_alt_next.ptr->uh_walk != nomark
- && uhp->uh_alt_next.ptr->uh_walk != mark) {
+ } else if (uhp->uh_alt_next.ptr != NULL
+ && uhp->uh_alt_next.ptr->uh_walk != nomark
+ && uhp->uh_alt_next.ptr->uh_walk != mark) {
+ // go to alternate branch if we haven't been there
uhp = uhp->uh_alt_next.ptr;
} else if (uhp->uh_next.ptr != NULL && uhp->uh_alt_prev.ptr == NULL
// go up in the tree if we haven't been there and we are at the
@@ -2208,7 +2147,7 @@ target_zero:
}
// Find the last branch with a mark, that's the one.
- last = uhp;
+ u_header_T *last = uhp;
while (last->uh_alt_next.ptr != NULL
&& last->uh_alt_next.ptr->uh_walk == mark) {
last = last->uh_alt_next.ptr;
@@ -2285,19 +2224,10 @@ target_zero:
static void u_undoredo(int undo, bool do_buf_event)
{
char_u **newarray = NULL;
- linenr_T oldsize;
- linenr_T newsize;
- linenr_T top, bot;
- linenr_T lnum;
linenr_T newlnum = MAXLNUM;
- long i;
- u_entry_T *uep, *nuep;
+ u_entry_T *nuep;
u_entry_T *newlist = NULL;
- int old_flags;
- int new_flags;
fmark_T namedm[NMARKS];
- visualinfo_T visualinfo;
- bool empty_buffer; // buffer became empty
u_header_T *curhead = curbuf->b_u_curhead;
// Don't want autocommands using the undo structures here, they are
@@ -2305,28 +2235,26 @@ static void u_undoredo(int undo, bool do_buf_event)
block_autocmds();
#ifdef U_DEBUG
- u_check(FALSE);
+ u_check(false);
#endif
- old_flags = curhead->uh_flags;
- new_flags = (curbuf->b_changed ? UH_CHANGED : 0)
- | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0)
- | (old_flags & UH_RELOAD);
+ int old_flags = curhead->uh_flags;
+ int new_flags = (curbuf->b_changed ? UH_CHANGED : 0)
+ | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0)
+ | (old_flags & UH_RELOAD);
setpcmark();
- /*
- * save marks before undo/redo
- */
+ // save marks before undo/redo
zero_fmark_additional_data(curbuf->b_namedm);
memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS);
- visualinfo = curbuf->b_visual;
+ visualinfo_T visualinfo = curbuf->b_visual;
curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count;
curbuf->b_op_start.col = 0;
curbuf->b_op_end.lnum = 0;
curbuf->b_op_end.col = 0;
- for (uep = curhead->uh_entry; uep != NULL; uep = nuep) {
- top = uep->ue_top;
- bot = uep->ue_bot;
+ for (u_entry_T *uep = curhead->uh_entry; uep != NULL; uep = nuep) {
+ linenr_T top = uep->ue_top;
+ linenr_T bot = uep->ue_bot;
if (bot == 0) {
bot = curbuf->b_ml.ml_line_count + 1;
}
@@ -2338,21 +2266,22 @@ static void u_undoredo(int undo, bool do_buf_event)
return;
}
- oldsize = bot - top - 1; // number of lines before undo
- newsize = (linenr_T)uep->ue_size; // number of lines after undo
+ linenr_T oldsize = bot - top - 1; // number of lines before undo
+ linenr_T newsize = (linenr_T)uep->ue_size; // number of lines after undo
if (top < newlnum) {
- /* If the saved cursor is somewhere in this undo block, move it to
- * the remembered position. Makes "gwap" put the cursor back
- * where it was. */
- lnum = curhead->uh_cursor.lnum;
+ // If the saved cursor is somewhere in this undo block, move it to
+ // the remembered position. Makes "gwap" put the cursor back
+ // where it was.
+ linenr_T lnum = curhead->uh_cursor.lnum;
if (lnum >= top && lnum <= top + newsize + 1) {
curwin->w_cursor = curhead->uh_cursor;
newlnum = curwin->w_cursor.lnum - 1;
} else {
- /* Use the first line that actually changed. Avoids that
- * undoing auto-formatting puts the cursor in the previous
- * line. */
+ // Use the first line that actually changed. Avoids that
+ // undoing auto-formatting puts the cursor in the previous
+ // line.
+ long i;
for (i = 0; i < newsize && i < oldsize; i++) {
if (STRCMP(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) {
break;
@@ -2368,17 +2297,19 @@ static void u_undoredo(int undo, bool do_buf_event)
}
}
- empty_buffer = false;
+ bool empty_buffer = false;
// delete the lines between top and bot and save them in newarray
if (oldsize > 0) {
newarray = xmalloc(sizeof(char_u *) * (size_t)oldsize);
// delete backwards, it goes faster in most cases
+ long i;
+ linenr_T lnum;
for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum) {
// what can we do when we run out of memory?
newarray[i] = u_save_line(lnum);
- /* remember we deleted the last line in the buffer, and a
- * dummy empty line will be inserted */
+ // remember we deleted the last line in the buffer, and a
+ // dummy empty line will be inserted
if (curbuf->b_ml.ml_line_count == 1) {
empty_buffer = true;
}
@@ -2390,11 +2321,11 @@ static void u_undoredo(int undo, bool do_buf_event)
// insert the lines in u_array between top and bot
if (newsize) {
- for (lnum = top, i = 0; i < newsize; ++i, ++lnum) {
- /*
- * If the file is empty, there is an empty line 1 that we
- * should get rid of, by replacing it with the new line
- */
+ long i;
+ linenr_T lnum;
+ for (lnum = top, i = 0; i < newsize; i++, lnum++) {
+ // If the file is empty, there is an empty line 1 that we
+ // should get rid of, by replacing it with the new line
if (empty_buffer && lnum == 0) {
ml_replace((linenr_T)1, (char *)uep->ue_array[i], true);
} else {
@@ -2435,9 +2366,7 @@ static void u_undoredo(int undo, bool do_buf_event)
uep->ue_array = newarray;
uep->ue_bot = top + newsize + 1;
- /*
- * insert this entry in front of the new entry list
- */
+ // insert this entry in front of the new entry list
nuep = uep->ue_next;
uep->ue_next = newlist;
newlist = uep;
@@ -2454,13 +2383,13 @@ static void u_undoredo(int undo, bool do_buf_event)
// Adjust Extmarks
ExtmarkUndoObject undo_info;
if (undo) {
- for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
+ for (long i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
// redo
} else {
- for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ for (long i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
@@ -2490,10 +2419,8 @@ static void u_undoredo(int undo, bool do_buf_event)
buf_updates_changedtick(curbuf);
}
- /*
- * restore marks from before undo/redo
- */
- for (i = 0; i < NMARKS; ++i) {
+ // restore marks from before undo/redo
+ for (long i = 0; i < NMARKS; i++) {
if (curhead->uh_namedm[i].mark.lnum != 0) {
free_fmark(curbuf->b_namedm[i]);
curbuf->b_namedm[i] = curhead->uh_namedm[i];
@@ -2509,14 +2436,12 @@ static void u_undoredo(int undo, bool do_buf_event)
curhead->uh_visual = visualinfo;
}
- /*
- * If the cursor is only off by one line, put it at the same position as
- * before starting the change (for the "o" command).
- * Otherwise the cursor should go to the first undone line.
- */
+ // If the cursor is only off by one line, put it at the same position as
+ // before starting the change (for the "o" command).
+ // Otherwise the cursor should go to the first undone line.
if (curhead->uh_cursor.lnum + 1 == curwin->w_cursor.lnum
&& curwin->w_cursor.lnum > 1) {
- --curwin->w_cursor.lnum;
+ curwin->w_cursor.lnum--;
}
if (curwin->w_cursor.lnum <= curbuf->b_ml.ml_line_count) {
if (curhead->uh_cursor.lnum == curwin->w_cursor.lnum) {
@@ -2565,7 +2490,7 @@ static void u_undoredo(int undo, bool do_buf_event)
unblock_autocmds();
#ifdef U_DEBUG
- u_check(FALSE);
+ u_check(false);
#endif
}
@@ -2577,10 +2502,6 @@ static void u_undoredo(int undo, bool do_buf_event)
/// @param absolute used ":undo N"
static void u_undo_end(bool did_undo, bool absolute, bool quiet)
{
- char *msgstr;
- u_header_T *uhp;
- char_u msgbuf[80];
-
if ((fdo_flags & FDO_UNDO) && KeyTyped) {
foldOpenCursor();
}
@@ -2592,10 +2513,11 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
}
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- --u_newcount;
+ u_newcount--;
}
u_oldcount -= u_newcount;
+ char *msgstr;
if (u_oldcount == -1) {
msgstr = N_("more line");
} else if (u_oldcount < 0) {
@@ -2613,6 +2535,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
}
}
+ u_header_T *uhp;
if (curbuf->b_u_curhead != NULL) {
// For ":undo N" we prefer a "after #N" message.
if (absolute && curbuf->b_u_curhead->uh_next.ptr != NULL) {
@@ -2627,6 +2550,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
uhp = curbuf->b_u_newhead;
}
+ char_u msgbuf[80];
if (uhp == NULL) {
*msgbuf = NUL;
} else {
@@ -2657,9 +2581,8 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
/// Put the timestamp of an undo header in "buf[buflen]" in a nice format.
void undo_fmt_time(char_u *buf, size_t buflen, time_t tt)
{
- struct tm curtime;
-
if (time(NULL) - tt >= 100) {
+ struct tm curtime;
os_localtime_r(&tt, &curtime);
if (time(NULL) - tt < (60L * 60L * 12L)) {
// within 12 hours
@@ -2695,27 +2618,20 @@ void u_sync(bool force)
}
}
-/*
- * ":undolist": List the leafs of the undo tree
- */
+/// ":undolist": List the leafs of the undo tree
void ex_undolist(exarg_T *eap)
{
- garray_T ga;
- u_header_T *uhp;
- int mark;
- int nomark;
int changes = 1;
- /*
- * 1: walk the tree to find all leafs, put the info in "ga".
- * 2: sort the lines
- * 3: display the list
- */
- mark = ++lastmark;
- nomark = ++lastmark;
+ // 1: walk the tree to find all leafs, put the info in "ga".
+ // 2: sort the lines
+ // 3: display the list
+ int mark = ++lastmark;
+ int nomark = ++lastmark;
+ garray_T ga;
ga_init(&ga, (int)sizeof(char *), 20);
- uhp = curbuf->b_u_oldhead;
+ u_header_T *uhp = curbuf->b_u_oldhead;
while (uhp != NULL) {
if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark
&& uhp->uh_walk != mark) {
@@ -2736,12 +2652,11 @@ void ex_undolist(exarg_T *eap)
if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark
&& uhp->uh_prev.ptr->uh_walk != mark) {
uhp = uhp->uh_prev.ptr;
- ++changes;
- }
- // go to alternate branch if we haven't been there
- else if (uhp->uh_alt_next.ptr != NULL
- && uhp->uh_alt_next.ptr->uh_walk != nomark
- && uhp->uh_alt_next.ptr->uh_walk != mark) {
+ changes++;
+ } else if (uhp->uh_alt_next.ptr != NULL
+ && uhp->uh_alt_next.ptr->uh_walk != nomark
+ && uhp->uh_alt_next.ptr->uh_walk != mark) {
+ // go to alternate branch if we haven't been there
uhp = uhp->uh_alt_next.ptr;
} else if (uhp->uh_next.ptr != NULL && uhp->uh_alt_prev.ptr == NULL
// go up in the tree if we haven't been there and we are at the
@@ -2749,7 +2664,7 @@ void ex_undolist(exarg_T *eap)
&& uhp->uh_next.ptr->uh_walk != nomark
&& uhp->uh_next.ptr->uh_walk != mark) {
uhp = uhp->uh_next.ptr;
- --changes;
+ changes--;
} else {
// need to backtrack; mark this node as done
uhp->uh_walk = nomark;
@@ -2757,7 +2672,7 @@ void ex_undolist(exarg_T *eap)
uhp = uhp->uh_alt_prev.ptr;
} else {
uhp = uhp->uh_next.ptr;
- --changes;
+ changes--;
}
}
}
@@ -2783,9 +2698,7 @@ void ex_undolist(exarg_T *eap)
}
}
-/*
- * ":undojoin": continue adding to the last entry list
- */
+/// ":undojoin": continue adding to the last entry list
void ex_undojoin(exarg_T *eap)
{
if (curbuf->b_u_newhead == NULL) {
@@ -2805,35 +2718,30 @@ void ex_undojoin(exarg_T *eap)
}
}
-/*
- * Called after writing or reloading the file and setting b_changed to FALSE.
- * Now an undo means that the buffer is modified.
- */
+/// Called after writing or reloading the file and setting b_changed to false.
+/// Now an undo means that the buffer is modified.
void u_unchanged(buf_T *buf)
{
u_unch_branch(buf->b_u_oldhead);
buf->b_did_warn = false;
}
-/*
- * After reloading a buffer which was saved for 'undoreload': Find the first
- * line that was changed and set the cursor there.
- */
+/// After reloading a buffer which was saved for 'undoreload': Find the first
+/// line that was changed and set the cursor there.
void u_find_first_changed(void)
{
u_header_T *uhp = curbuf->b_u_newhead;
- u_entry_T *uep;
- linenr_T lnum;
if (curbuf->b_u_curhead != NULL || uhp == NULL) {
return; // undid something in an autocmd?
}
// Check that the last undo block was for the whole file.
- uep = uhp->uh_entry;
+ u_entry_T *uep = uhp->uh_entry;
if (uep->ue_top != 0 || uep->ue_bot != 0) {
return;
}
+ linenr_T lnum;
for (lnum = 1; lnum < curbuf->b_ml.ml_line_count
&& lnum <= uep->ue_size; lnum++) {
if (STRCMP(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) {
@@ -2849,17 +2757,13 @@ void u_find_first_changed(void)
}
}
-/*
- * Increase the write count, store it in the last undo header, what would be
- * used for "u".
- */
+/// Increase the write count, store it in the last undo header, what would be
+/// used for "u".
void u_update_save_nr(buf_T *buf)
{
- u_header_T *uhp;
-
- ++buf->b_u_save_nr_last;
+ buf->b_u_save_nr_last++;
buf->b_u_save_nr_cur = buf->b_u_save_nr_last;
- uhp = buf->b_u_curhead;
+ u_header_T *uhp = buf->b_u_curhead;
if (uhp != NULL) {
uhp = uhp->uh_next.ptr;
} else {
@@ -2872,9 +2776,7 @@ void u_update_save_nr(buf_T *buf)
static void u_unch_branch(u_header_T *uhp)
{
- u_header_T *uh;
-
- for (uh = uhp; uh != NULL; uh = uh->uh_prev.ptr) {
+ for (u_header_T *uh = uhp; uh != NULL; uh = uh->uh_prev.ptr) {
uh->uh_flags |= UH_CHANGED;
if (uh->uh_alt_next.ptr != NULL) {
u_unch_branch(uh->uh_alt_next.ptr); // recursive
@@ -2882,10 +2784,8 @@ static void u_unch_branch(u_header_T *uhp)
}
}
-/*
- * Get pointer to last added entry.
- * If it's not valid, give an error message and return NULL.
- */
+/// Get pointer to last added entry.
+/// If it's not valid, give an error message and return NULL.
static u_entry_T *u_get_headentry(buf_T *buf)
{
if (buf->b_u_newhead == NULL || buf->b_u_newhead->uh_entry == NULL) {
@@ -2895,28 +2795,21 @@ static u_entry_T *u_get_headentry(buf_T *buf)
return buf->b_u_newhead->uh_entry;
}
-/*
- * u_getbot(): compute the line number of the previous u_save
- * It is called only when b_u_synced is false.
- */
+/// u_getbot(): compute the line number of the previous u_save
+/// It is called only when b_u_synced is false.
static void u_getbot(buf_T *buf)
{
- u_entry_T *uep;
- linenr_T extra;
-
- uep = u_get_headentry(buf); // check for corrupt undo list
+ u_entry_T *uep = u_get_headentry(buf); // check for corrupt undo list
if (uep == NULL) {
return;
}
uep = buf->b_u_newhead->uh_getbot_entry;
if (uep != NULL) {
- /*
- * the new ue_bot is computed from the number of lines that has been
- * inserted (0 - deleted) since calling u_save. This is equal to the
- * old line count subtracted from the current line count.
- */
- extra = buf->b_ml.ml_line_count - uep->ue_lcount;
+ // the new ue_bot is computed from the number of lines that has been
+ // inserted (0 - deleted) since calling u_save. This is equal to the
+ // old line count subtracted from the current line count.
+ linenr_T extra = buf->b_ml.ml_line_count - uep->ue_lcount;
uep->ue_bot = uep->ue_top + (linenr_T)uep->ue_size + 1 + extra;
if (uep->ue_bot < 1 || uep->ue_bot > buf->b_ml.ml_line_count) {
iemsg(_("E440: undo line missing"));
@@ -2937,8 +2830,6 @@ static void u_getbot(buf_T *buf)
/// @param uhpp if not NULL reset when freeing this header
static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
{
- u_header_T *uhap;
-
// When there is an alternate redo list free that branch completely,
// because we can never go there.
if (uhp->uh_alt_next.ptr != NULL) {
@@ -2959,7 +2850,7 @@ static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
if (uhp->uh_prev.ptr == NULL) {
buf->b_u_newhead = uhp->uh_next.ptr;
} else {
- for (uhap = uhp->uh_prev.ptr; uhap != NULL;
+ for (u_header_T *uhap = uhp->uh_prev.ptr; uhap != NULL;
uhap = uhap->uh_alt_next.ptr) {
uhap->uh_next.ptr = uhp->uh_next.ptr;
}
@@ -2973,8 +2864,6 @@ static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
/// @param uhpp if not NULL reset when freeing this header
static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
{
- u_header_T *tofree, *next;
-
// If this is the top branch we may need to use u_freeheader() to update
// all the pointers.
if (uhp == buf->b_u_oldhead) {
@@ -2988,9 +2877,9 @@ static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
uhp->uh_alt_prev.ptr->uh_alt_next.ptr = NULL;
}
- next = uhp;
+ u_header_T *next = uhp;
while (next != NULL) {
- tofree = next;
+ u_header_T *tofree = next;
if (tofree->uh_alt_next.ptr != NULL) {
u_freebranch(buf, tofree->uh_alt_next.ptr, uhpp); // recursive
}
@@ -3029,12 +2918,10 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
uhp->uh_magic = 0;
#endif
xfree((char_u *)uhp);
- --buf->b_u_numhead;
+ buf->b_u_numhead--;
}
-/*
- * free entry 'uep' and 'n' lines in uep->ue_array[]
- */
+/// free entry 'uep' and 'n' lines in uep->ue_array[]
static void u_freeentry(u_entry_T *uep, long n)
{
while (n > 0) {
@@ -3047,9 +2934,7 @@ static void u_freeentry(u_entry_T *uep, long n)
xfree((char_u *)uep);
}
-/*
- * invalidate the undo buffer; called when storage has already been released
- */
+/// invalidate the undo buffer; called when storage has already been released
void u_clearall(buf_T *buf)
{
buf->b_u_newhead = buf->b_u_oldhead = buf->b_u_curhead = NULL;
@@ -3059,9 +2944,7 @@ void u_clearall(buf_T *buf)
buf->b_u_line_lnum = 0;
}
-/*
- * save the line "lnum" for the "U" command
- */
+/// save the line "lnum" for the "U" command
void u_saveline(linenr_T lnum)
{
if (lnum == curbuf->b_u_line_lnum) { // line is already saved
@@ -3080,10 +2963,8 @@ void u_saveline(linenr_T lnum)
curbuf->b_u_line_ptr = u_save_line(lnum);
}
-/*
- * clear the line saved for the "U" command
- * (this is used externally for crossing a line while in insert mode)
- */
+/// clear the line saved for the "U" command
+/// (this is used externally for crossing a line while in insert mode)
void u_clearline(void)
{
if (curbuf->b_u_line_ptr != NULL) {
@@ -3092,17 +2973,12 @@ void u_clearline(void)
}
}
-/*
- * Implementation of the "U" command.
- * Differentiation from vi: "U" can be undone with the next "U".
- * We also allow the cursor to be in another line.
- * Careful: may trigger autocommands that reload the buffer.
- */
+/// Implementation of the "U" command.
+/// Differentiation from vi: "U" can be undone with the next "U".
+/// We also allow the cursor to be in another line.
+/// Careful: may trigger autocommands that reload the buffer.
void u_undoline(void)
{
- colnr_T t;
- char_u *oldp;
-
if (curbuf->b_u_line_ptr == NULL
|| curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) {
beep_flush();
@@ -3115,7 +2991,7 @@ void u_undoline(void)
return;
}
- oldp = u_save_line(curbuf->b_u_line_lnum);
+ char_u *oldp = u_save_line(curbuf->b_u_line_lnum);
ml_replace(curbuf->b_u_line_lnum, (char *)curbuf->b_u_line_ptr, true);
changed_bytes(curbuf->b_u_line_lnum, 0);
extmark_splice_cols(curbuf, (int)curbuf->b_u_line_lnum - 1, 0, (colnr_T)STRLEN(oldp),
@@ -3123,7 +2999,7 @@ void u_undoline(void)
xfree(curbuf->b_u_line_ptr);
curbuf->b_u_line_ptr = oldp;
- t = curbuf->b_u_line_colnr;
+ colnr_T t = curbuf->b_u_line_colnr;
if (curwin->w_cursor.lnum == curbuf->b_u_line_lnum) {
curbuf->b_u_line_colnr = curwin->w_cursor.col;
}
@@ -3132,9 +3008,7 @@ void u_undoline(void)
check_cursor_col();
}
-/*
- * Free all allocated memory blocks for the buffer 'buf'.
- */
+/// Free all allocated memory blocks for the buffer 'buf'.
void u_blockfree(buf_T *buf)
{
while (buf->b_u_oldhead != NULL) {
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index d8470b07b1..4b64f97919 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -74,10 +74,4 @@ struct u_header {
#define UH_EMPTYBUF 0x02 // buffer was empty
#define UH_RELOAD 0x04 // buffer was reloaded
-/// Structure passed around between undofile functions.
-typedef struct {
- buf_T *bi_buf;
- FILE *bi_fp;
-} bufinfo_T;
-
#endif // NVIM_UNDO_DEFS_H
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
index 15197dc504..59b8d10200 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -12,10 +12,12 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/charset.h"
+#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
#include "nvim/garray.h"
#include "nvim/lua/executor.h"
#include "nvim/os/input.h"
+#include "nvim/runtime.h"
#include "nvim/usercmd.h"
#include "nvim/window.h"
@@ -167,7 +169,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp)
xp->xp_luaref = uc->uc_compl_luaref;
xp->xp_arg = (char *)uc->uc_compl_arg;
xp->xp_script_ctx = uc->uc_script_ctx;
- xp->xp_script_ctx.sc_lnum += sourcing_lnum;
+ xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
}
// Do not search for further abbreviations
// if this is an exact match.
@@ -889,7 +891,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt,
cmd->uc_def = def;
cmd->uc_compl = compl;
cmd->uc_script_ctx = current_sctx;
- cmd->uc_script_ctx.sc_lnum += sourcing_lnum;
+ cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&cmd->uc_script_ctx);
cmd->uc_compl_arg = (char_u *)compl_arg;
cmd->uc_compl_luaref = compl_luaref;
@@ -1162,7 +1164,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc,
*q++ = ' ';
*q++ = '"';
} else {
- mb_copy_char((const char_u **)&p, (char_u **)&q);
+ mb_copy_char((const char **)&p, &q);
}
}
} else {
@@ -1175,7 +1177,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc,
*q++ = '\\';
*q++ = *p++;
} else {
- mb_copy_char((const char_u **)&p, (char_u **)&q);
+ mb_copy_char((const char **)&p, &q);
}
}
if (i != argc - 1) {
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 3ffae6592c..0667243bc3 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -14,12 +14,13 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/drawscreen.h"
+#include "nvim/grid.h"
#include "nvim/iconv.h"
#include "nvim/lua/executor.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/version.h"
#include "nvim/vim.h"
@@ -2063,7 +2064,7 @@ static void list_features(void)
/// List string items nicely aligned in columns.
/// When "size" is < 0 then the last entry is marked with NULL.
/// The entry with index "current" is inclosed in [].
-void list_in_columns(char_u **items, int size, int current)
+void list_in_columns(char **items, int size, int current)
{
int item_count = 0;
int width = 0;
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 31ac5a67ff..09b949bb20 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -199,6 +199,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext()
// Size in bytes of the hash used in the undo file.
#define UNDO_HASH_SIZE 32
+#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field))
#define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr)))
// defines to avoid typecasts from (char_u *) to (char *) and back
@@ -274,8 +275,8 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext()
(const char *)(y), \
(size_t)(n))
-// Enums need a typecast to be used as array index (for Ultrix).
-#define HL_ATTR(n) highlight_attr[(int)(n)]
+// Enums need a typecast to be used as array index.
+#define HL_ATTR(n) hl_attr_active[(int)(n)]
/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
/// character of up to 6 bytes, or one 16-bit character of up to three bytes
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index fd7dc17ee3..387b9d61f2 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1828,13 +1828,13 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no
v_p += special_len;
} else {
is_unknown = true;
- mb_copy_char((const char_u **)&p, (char_u **)&v_p);
+ mb_copy_char(&p, &v_p);
}
break;
}
default:
is_unknown = true;
- mb_copy_char((const char_u **)&p, (char_u **)&v_p);
+ mb_copy_char(&p, &v_p);
break;
}
if (pstate->colors) {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 39346faa14..2d995af00d 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -6,11 +6,14 @@
#include <stdbool.h>
#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/vars.h"
@@ -25,7 +28,9 @@
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
+#include "nvim/grid.h"
#include "nvim/hashtab.h"
+#include "nvim/highlight.h"
#include "nvim/main.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
@@ -43,7 +48,6 @@
#include "nvim/plines.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
@@ -122,7 +126,7 @@ void do_window(int nchar, long Prenum, int xchar)
{
long Prenum1;
win_T *wp;
- char_u *ptr;
+ char *ptr;
linenr_T lnum = -1;
int type = FIND_DEFINE;
size_t len;
@@ -483,14 +487,14 @@ newwindow:
wingotofile:
CHECK_CMDWIN;
- ptr = grab_file_name(Prenum1, &lnum);
+ ptr = (char *)grab_file_name(Prenum1, &lnum);
if (ptr != NULL) {
tabpage_T *oldtab = curtab;
win_T *oldwin = curwin;
setpcmark();
if (win_split(0, 0) == OK) {
RESET_BINDING(curwin);
- if (do_ecmd(0, (char *)ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
+ if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
@@ -518,9 +522,9 @@ wingotofile:
}
// Make a copy, if the line was changed it will be freed.
- ptr = vim_strnsave(ptr, len);
+ ptr = xstrnsave(ptr, len);
- find_pattern_in_path(ptr, 0, len, true, Prenum == 0,
+ find_pattern_in_path((char_u *)ptr, 0, len, true, Prenum == 0,
type, Prenum1, ACTION_SPLIT, 1, MAXLNUM);
xfree(ptr);
curwin->w_set_curswant = true;
@@ -698,14 +702,14 @@ win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
win_remove(wp, NULL);
win_append(lastwin_nofloating(), wp);
}
- wp->w_floating = 1;
+ wp->w_floating = true;
wp->w_status_height = 0;
wp->w_winbar_height = 0;
wp->w_hsep_height = 0;
wp->w_vsep_width = 0;
win_config_float(wp, fconfig);
- win_set_inner_size(wp);
+ win_set_inner_size(wp, true);
wp->w_pos_changed = true;
redraw_later(wp, VALID);
return wp;
@@ -728,13 +732,15 @@ void win_set_minimal_style(win_T *wp)
: concat_str(old, (char_u *)",eob: "));
free_string_option(old);
}
- if (wp->w_hl_ids[HLF_EOB] != -1) {
- char_u *old = wp->w_p_winhl;
- wp->w_p_winhl = ((*old == NUL)
- ? (char_u *)xstrdup("EndOfBuffer:")
- : concat_str(old, (char_u *)",EndOfBuffer:"));
- free_string_option(old);
- }
+
+ // TODO(bfredl): this could use a highlight namespace directly,
+ // and avoid pecularities around window options
+ char_u *old = wp->w_p_winhl;
+ wp->w_p_winhl = ((*old == NUL)
+ ? (char_u *)xstrdup("EndOfBuffer:")
+ : concat_str(old, (char_u *)",EndOfBuffer:"));
+ free_string_option(old);
+ parse_winhl_opt(wp);
// signcolumn: use 'auto'
if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) {
@@ -789,7 +795,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
}
- win_set_inner_size(wp);
+ win_set_inner_size(wp, true);
must_redraw = MAX(must_redraw, VALID);
wp->w_pos_changed = true;
@@ -1270,7 +1276,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_floating = false;
// non-floating window doesn't store float config or have a border.
wp->w_float_config = FLOAT_CONFIG_INIT;
- memset(wp->w_border_adj, 0, sizeof(wp->w_border_adj));
+ CLEAR_FIELD(wp->w_border_adj);
}
/*
@@ -1670,7 +1676,7 @@ int win_count(void)
int count = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- ++count;
+ count++;
}
return count;
}
@@ -2459,7 +2465,7 @@ void close_windows(buf_T *buf, bool keep_curwin)
tabpage_T *tp, *nexttp;
int h = tabline_height();
- ++RedrawingDisabled;
+ RedrawingDisabled++;
// Start from lastwin to close floating windows with the same buffer first.
// When the autocommand window is involved win_close() may need to print an error message.
@@ -2496,7 +2502,7 @@ void close_windows(buf_T *buf, bool keep_curwin)
}
}
- --RedrawingDisabled;
+ RedrawingDisabled--;
redraw_tabline = true;
if (h != tabline_height()) {
@@ -3825,7 +3831,7 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin)
m = (int)p_wmw + topfrp->fr_win->w_vsep_width;
// Current window is minimal one column wide
if (p_wmw == 0 && topfrp->fr_win == curwin && next_curwin == NULL) {
- ++m;
+ m++;
}
}
} else if (topfrp->fr_layout == FR_COL) {
@@ -4098,7 +4104,7 @@ int win_new_tabpage(int after, char_u *filename)
n = 2;
for (tp = first_tabpage; tp->tp_next != NULL
&& n < after; tp = tp->tp_next) {
- ++n;
+ n++;
}
}
newtp->tp_next = tp->tp_next;
@@ -4244,7 +4250,7 @@ tabpage_T *find_tabpage(int n)
int i = 1;
for (tp = first_tabpage; tp != NULL && i != n; tp = tp->tp_next) {
- ++i;
+ i++;
}
return tp;
}
@@ -4259,7 +4265,7 @@ int tabpage_index(tabpage_T *ftp)
tabpage_T *tp;
for (tp = first_tabpage; tp != NULL && tp != ftp; tp = tp->tp_next) {
- ++i;
+ i++;
}
return i;
}
@@ -4521,7 +4527,7 @@ void tabpage_move(int nr)
}
for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) {
- ++n;
+ n++;
}
if (tp == curtab || (nr > 0 && tp->tp_next != NULL
@@ -4868,10 +4874,17 @@ static void win_enter_ext(win_T *const wp, const int flags)
redraw_later(curwin, VALID); // causes status line redraw
}
- if (HL_ATTR(HLF_INACTIVE)
- || (prevwin && prevwin->w_hl_ids[HLF_INACTIVE])
- || curwin->w_hl_ids[HLF_INACTIVE]) {
- redraw_all_later(NOT_VALID);
+ // change background color according to NormalNC,
+ // but only if actually defined (otherwise no extra redraw)
+ if (curwin->w_hl_attr_normal != curwin->w_hl_attr_normalnc) {
+ // TODO(bfredl): eventually we should be smart enough
+ // to only recompose the window, not redraw it.
+ redraw_later(curwin, NOT_VALID);
+ }
+ if (prevwin) {
+ if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) {
+ redraw_later(prevwin, NOT_VALID);
+ }
}
// set window height to desired minimal value
@@ -5040,6 +5053,8 @@ static win_T *win_alloc(win_T *after, bool hidden)
new_wp->w_float_config = FLOAT_CONFIG_INIT;
new_wp->w_viewport_invalid = true;
+ new_wp->w_ns_hl = -1;
+
// use global option for global-local options
new_wp->w_p_so = -1;
new_wp->w_p_siso = -1;
@@ -5180,7 +5195,7 @@ void win_free_grid(win_T *wp, bool reinit)
grid_free(&wp->w_grid_alloc);
if (reinit) {
// if a float is turned into a split, the grid data structure will be reused
- memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc));
+ CLEAR_FIELD(wp->w_grid_alloc);
}
}
@@ -5556,7 +5571,7 @@ static void frame_setheight(frame_T *curfrp, int height)
}
if (curfrp->fr_parent == NULL) {
- // topframe: can only change the command line
+ // topframe: can only change the command line height
if (height > ROWS_AVAIL) {
// If height is greater than the available space, try to create space for
// the frame by reducing 'cmdheight' if possible, while making sure
@@ -5919,6 +5934,13 @@ void win_drag_status_line(win_T *dragwin, int offset)
int row;
bool up; // if true, drag status line up, otherwise down
int n;
+ static bool p_ch_was_zero = false;
+
+ // If the user explicitly set 'cmdheight' to zero, then allow for dragging
+ // the status line making it zero again.
+ if (p_ch == 0) {
+ p_ch_was_zero = true;
+ }
fr = dragwin->w_frame;
curfr = fr;
@@ -5969,6 +5991,8 @@ void win_drag_status_line(win_T *dragwin, int offset)
room = Rows - cmdline_row;
if (curfr->fr_next != NULL) {
room -= (int)p_ch + global_stl_height();
+ } else if (!p_ch_was_zero) {
+ room--;
}
if (room < 0) {
room = 0;
@@ -6024,7 +6048,7 @@ void win_drag_status_line(win_T *dragwin, int offset)
clear_cmdline = true;
}
cmdline_row = row;
- p_ch = MAX(Rows - cmdline_row, 0);
+ p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1);
curtab->tp_ch_used = p_ch;
redraw_all_later(SOME_VALID);
showmode();
@@ -6167,7 +6191,7 @@ void win_new_height(win_T *wp, int height)
wp->w_height = height;
wp->w_pos_changed = true;
- win_set_inner_size(wp);
+ win_set_inner_size(wp, true);
}
void scroll_to_fraction(win_T *wp, int prev_height)
@@ -6230,7 +6254,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
if (lnum == 1) {
// first line in buffer is folded
line_size = 1;
- --sline;
+ sline--;
break;
}
lnum--;
@@ -6276,7 +6300,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
invalidate_botline_win(wp);
}
-void win_set_inner_size(win_T *wp)
+void win_set_inner_size(win_T *wp, bool valid_cursor)
{
int width = wp->w_width_request;
if (width == 0) {
@@ -6290,7 +6314,7 @@ void win_set_inner_size(win_T *wp)
}
if (height != prev_height) {
- if (height > 0) {
+ if (height > 0 && valid_cursor) {
if (wp == curwin) {
// w_wrow needs to be valid. When setting 'laststatus' this may
// call win_new_height() recursively.
@@ -6309,7 +6333,7 @@ void win_set_inner_size(win_T *wp)
// There is no point in adjusting the scroll position when exiting. Some
// values might be invalid.
// Skip scroll_to_fraction() when 'cmdheight' was set to one from zero.
- if (!exiting && !made_cmdheight_nonzero) {
+ if (!exiting && !made_cmdheight_nonzero && valid_cursor) {
scroll_to_fraction(wp, prev_height);
}
redraw_later(wp, NOT_VALID); // SOME_VALID??
@@ -6318,11 +6342,13 @@ void win_set_inner_size(win_T *wp)
if (width != wp->w_width_inner) {
wp->w_width_inner = width;
wp->w_lines_valid = 0;
- changed_line_abv_curs_win(wp);
- invalidate_botline_win(wp);
- if (wp == curwin) {
- update_topline(wp);
- curs_columns(wp, true); // validate w_wrow
+ if (valid_cursor) {
+ changed_line_abv_curs_win(wp);
+ invalidate_botline_win(wp);
+ if (wp == curwin) {
+ update_topline(wp);
+ curs_columns(wp, true); // validate w_wrow
+ }
}
redraw_later(wp, NOT_VALID);
}
@@ -6351,7 +6377,7 @@ static int win_border_width(win_T *wp)
void win_new_width(win_T *wp, int width)
{
wp->w_width = width;
- win_set_inner_size(wp);
+ win_set_inner_size(wp, true);
wp->w_redr_status = true;
wp->w_pos_changed = true;
@@ -6386,6 +6412,19 @@ void command_height(void)
// p_ch was changed in another tab page.
curtab->tp_ch_used = p_ch;
+ // If the space for the command line is already more than 'cmdheight' there
+ // is nothing to do (window size must have decreased).
+ if (p_ch > old_p_ch && cmdline_row <= Rows - p_ch) {
+ return;
+ }
+
+ // If cmdline_row is smaller than what it is supposed to be for 'cmdheight'
+ // then set old_p_ch to what it would be, so that the windows get resized
+ // properly for the new value.
+ if (cmdline_row < Rows - p_ch) {
+ old_p_ch = Rows - cmdline_row;
+ }
+
// Find bottom frame with width of screen.
frp = lastwin_nofloating()->w_frame;
while (frp->fr_width != Columns && frp->fr_parent != NULL) {
@@ -6470,17 +6509,17 @@ char_u *grab_file_name(long count, linenr_T *file_lnum)
int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
if (VIsual_active) {
size_t len;
- char_u *ptr;
+ char *ptr;
if (get_visual_text(NULL, &ptr, &len) == FAIL) {
return NULL;
}
// Only recognize ":123" here
if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) {
- char *p = (char *)ptr + len + 1;
+ char *p = ptr + len + 1;
*file_lnum = (linenr_T)getdigits_long(&p, false, 0);
}
- return find_file_name_in_path(ptr, len, options, count, (char_u *)curbuf->b_ffname);
+ return find_file_name_in_path((char_u *)ptr, len, options, count, (char_u *)curbuf->b_ffname);
}
return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
}
@@ -6566,7 +6605,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u
if (ptr[len] == '\\' && ptr[len + 1] == ' ') {
// Skip over the "\" in "\ ".
- ++len;
+ len++;
}
len += (size_t)(utfc_ptr2len(ptr + len));
}
@@ -6577,7 +6616,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u
*/
if (len > 2 && vim_strchr(".,:;!", ptr[len - 1]) != NULL
&& ptr[len - 2] != '.') {
- --len;
+ len--;
}
if (file_lnum != NULL) {
@@ -6743,9 +6782,11 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
/// Add or remove window bar from window "wp".
///
/// @param make_room Whether to resize frames to make room for winbar.
+/// @param valid_cursor Whether the cursor is valid and should be used while
+/// resizing.
///
/// @return Success status.
-int set_winbar_win(win_T *wp, bool make_room)
+int set_winbar_win(win_T *wp, bool make_room, bool valid_cursor)
{
// Require the local value to be set in order to show winbar on a floating window.
int winbar_height = wp->w_floating ? ((*wp->w_p_wbr != NUL) ? 1 : 0)
@@ -6761,7 +6802,7 @@ int set_winbar_win(win_T *wp, bool make_room)
}
}
wp->w_winbar_height = winbar_height;
- win_set_inner_size(wp);
+ win_set_inner_size(wp, valid_cursor);
wp->w_redr_status = wp->w_redr_status || winbar_height;
if (winbar_height == 0) {
@@ -6782,7 +6823,7 @@ int set_winbar_win(win_T *wp, bool make_room)
void set_winbar(bool make_room)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (set_winbar_win(wp, make_room) == FAIL) {
+ if (set_winbar_win(wp, make_room, true) == FAIL) {
break;
}
}
@@ -7100,7 +7141,7 @@ int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_displa
// As switch_win() but without blocking autocommands.
int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display)
{
- memset(switchwin, 0, sizeof(switchwin_T));
+ CLEAR_POINTER(switchwin);
switchwin->sw_curwin = curwin;
if (win == curwin) {
switchwin->sw_same_win = true;
@@ -7233,6 +7274,88 @@ static bool frame_check_width(const frame_T *topfrp, int width)
return true;
}
+/// Simple int comparison function for use with qsort()
+static int int_cmp(const void *a, const void *b)
+{
+ return *(const int *)a - *(const int *)b;
+}
+
+/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
+///
+/// @return error message, NULL if it's OK.
+char *check_colorcolumn(win_T *wp)
+{
+ char *s;
+ int col;
+ unsigned int count = 0;
+ int color_cols[256];
+ int j = 0;
+
+ if (wp->w_buffer == NULL) {
+ return NULL; // buffer was closed
+ }
+
+ for (s = (char *)wp->w_p_cc; *s != NUL && count < 255;) {
+ if (*s == '-' || *s == '+') {
+ // -N and +N: add to 'textwidth'
+ col = (*s == '-') ? -1 : 1;
+ s++;
+ if (!ascii_isdigit(*s)) {
+ return e_invarg;
+ }
+ col = col * getdigits_int(&s, true, 0);
+ if (wp->w_buffer->b_p_tw == 0) {
+ goto skip; // 'textwidth' not set, skip this item
+ }
+ assert((col >= 0
+ && wp->w_buffer->b_p_tw <= INT_MAX - col
+ && wp->w_buffer->b_p_tw + col >= INT_MIN)
+ || (col < 0
+ && wp->w_buffer->b_p_tw >= INT_MIN - col
+ && wp->w_buffer->b_p_tw + col <= INT_MAX));
+ col += (int)wp->w_buffer->b_p_tw;
+ if (col < 0) {
+ goto skip;
+ }
+ } else if (ascii_isdigit(*s)) {
+ col = getdigits_int(&s, true, 0);
+ } else {
+ return e_invarg;
+ }
+ color_cols[count++] = col - 1; // 1-based to 0-based
+skip:
+ if (*s == NUL) {
+ break;
+ }
+ if (*s != ',') {
+ return e_invarg;
+ }
+ if (*++s == NUL) {
+ return e_invarg; // illegal trailing comma as in "set cc=80,"
+ }
+ }
+
+ xfree(wp->w_p_cc_cols);
+ if (count == 0) {
+ wp->w_p_cc_cols = NULL;
+ } else {
+ wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1));
+ // sort the columns for faster usage on screen redraw inside
+ // win_line()
+ qsort(color_cols, count, sizeof(int), int_cmp);
+
+ for (unsigned int i = 0; i < count; i++) {
+ // skip duplicates
+ if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) {
+ wp->w_p_cc_cols[j++] = color_cols[i];
+ }
+ }
+ wp->w_p_cc_cols[j] = -1; // end marker
+ }
+
+ return NULL; // no error
+}
+
int win_getid(typval_T *argvars)
{
if (argvars[0].v_type == VAR_UNKNOWN) {