aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml104
-rw-r--r--ci/build.ps12
-rw-r--r--runtime/doc/builtin.txt1
-rw-r--r--runtime/doc/cmdline.txt16
-rw-r--r--runtime/doc/lua.txt24
-rw-r--r--runtime/doc/options.txt5
-rw-r--r--runtime/doc/vim_diff.txt3
-rw-r--r--runtime/lua/vim/keymap.lua19
-rw-r--r--src/nvim/autocmd.c24
-rw-r--r--src/nvim/autocmd.h21
-rw-r--r--src/nvim/buffer.c15
-rw-r--r--src/nvim/buffer_defs.h37
-rw-r--r--src/nvim/debugger.c36
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/eval.c25
-rw-r--r--src/nvim/eval/funcs.c584
-rw-r--r--src/nvim/eval/typval.h2
-rw-r--r--src/nvim/eval/userfunc.c162
-rw-r--r--src/nvim/ex_docmd.c953
-rw-r--r--src/nvim/ex_eval.c20
-rw-r--r--src/nvim/ex_eval_defs.h6
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/fold.c121
-rw-r--r--src/nvim/globals.h12
-rw-r--r--src/nvim/highlight.c46
-rw-r--r--src/nvim/highlight_group.c14
-rw-r--r--src/nvim/lua/executor.c8
-rw-r--r--src/nvim/main.c19
-rw-r--r--src/nvim/mapping.c5
-rw-r--r--src/nvim/menu.c21
-rw-r--r--src/nvim/menu.h21
-rw-r--r--src/nvim/menu_defs.h64
-rw-r--r--src/nvim/message.c80
-rw-r--r--src/nvim/normal.c8
-rw-r--r--src/nvim/option.c3
-rw-r--r--src/nvim/profile.c11
-rw-r--r--src/nvim/runtime.c171
-rw-r--r--src/nvim/runtime.h44
-rw-r--r--src/nvim/screen.c42
-rw-r--r--src/nvim/spell.c3751
-rw-r--r--src/nvim/spell_defs.h65
-rw-r--r--src/nvim/spellfile.c13
-rw-r--r--src/nvim/spellsuggest.c3800
-rw-r--r--src/nvim/spellsuggest.h9
-rw-r--r--src/nvim/testdir/test_arglist.vim15
-rw-r--r--src/nvim/testdir/test_autocmd.vim22
-rw-r--r--src/nvim/testdir/test_bufline.vim2
-rw-r--r--src/nvim/testdir/test_cmdline.vim45
-rw-r--r--src/nvim/testdir/test_expand_func.vim20
-rw-r--r--src/nvim/testdir/test_gf.vim16
-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.vim59
-rw-r--r--src/nvim/testdir/test_normal.vim233
-rw-r--r--src/nvim/testdir/test_options.vim7
-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_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_vimscript.vim17
-rw-r--r--src/nvim/testdir/test_visual.vim68
-rw-r--r--src/nvim/testing.c15
-rw-r--r--src/nvim/usercmd.c5
-rw-r--r--test/functional/ex_cmds/source_spec.lua28
-rw-r--r--test/functional/legacy/gf_spec.lua15
-rw-r--r--test/functional/legacy/messages_spec.lua89
-rw-r--r--test/functional/ui/messages_spec.lua13
-rw-r--r--test/functional/vimscript/null_spec.lua2
71 files changed, 5971 insertions, 5244 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 74940c762f..e22d99067a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,11 +18,6 @@ concurrency:
jobs:
lint:
- # This job tests two things: it lints the code but also builds neovim using
- # system dependencies instead of bundled dependencies. This is to make sure
- # we are able to build neovim without pigeonholing ourselves into specifics
- # of the bundled dependencies.
-
if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master')
runs-on: ubuntu-20.04
timeout-minutes: 10
@@ -65,7 +60,6 @@ jobs:
ninja-build \
pkg-config
-
- name: Cache uncrustify
id: cache-uncrustify
uses: actions/cache@v3
@@ -101,13 +95,6 @@ jobs:
- name: Build third-party deps
run: ./ci/before_script.sh
- - name: Build nvim
- run: ./ci/run_tests.sh build_nvim
-
- - if: "!cancelled()"
- name: lintcfull
- run: make lintcfull
-
- if: "!cancelled()"
name: lintstylua
uses: JohnnyMorganz/stylua-action@1.0.0
@@ -116,12 +103,6 @@ jobs:
args: --check runtime/
- if: "!cancelled()"
- name: uncrustify
- run: |
- ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]")
- git diff --color --exit-code
-
- - if: "!cancelled()"
name: lintlua
run: make lintlua
@@ -134,6 +115,91 @@ jobs:
run: make lintsh
- if: "!cancelled()"
+ name: uncrustify
+ run: |
+ ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]")
+
+ - if: "!cancelled()"
+ name: suggester / uncrustify
+ uses: reviewdog/action-suggester@v1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ tool_name: uncrustify
+ cleanup: false
+
+ - if: "!cancelled()"
+ name: check uncrustify
+ run: |
+ git diff --color --exit-code
+
+ - name: Cache dependencies
+ run: ./ci/before_cache.sh
+
+ lintc:
+ # This job tests two things: it lints the code but also builds neovim using
+ # system dependencies instead of bundled dependencies. This is to make sure
+ # we are able to build neovim without pigeonholing ourselves into specifics
+ # of the bundled dependencies.
+
+ if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master')
+ runs-on: ubuntu-20.04
+ timeout-minutes: 10
+ env:
+ CC: gcc
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup common environment variables
+ run: ./.github/workflows/env.sh lint
+
+ - name: Install apt packages
+ run: |
+ sudo add-apt-repository ppa:neovim-ppa/stable
+ sudo apt-get update
+ sudo apt-get install -y \
+ autoconf \
+ automake \
+ build-essential \
+ cmake \
+ gettext \
+ libluajit-5.1-dev \
+ libmsgpack-dev \
+ libtermkey-dev \
+ libtool-bin \
+ libtree-sitter-dev \
+ libunibilium-dev \
+ libuv1-dev \
+ libvterm-dev \
+ locales \
+ lua-busted \
+ lua-check \
+ lua-filesystem \
+ lua-inspect \
+ lua-lpeg \
+ lua-luv-dev \
+ lua-nvim \
+ luajit \
+ ninja-build \
+ pkg-config
+
+ - name: Cache artifacts
+ uses: actions/cache@v3
+ with:
+ path: |
+ ${{ env.CACHE_NVIM_DEPS_DIR }}
+ key: lint-${{ hashFiles('cmake/*', '**/CMakeLists.txt', '!cmake.deps/**CMakeLists.txt') }}-${{ github.base_ref }}
+
+ - name: Build third-party deps
+ run: ./ci/before_script.sh
+
+ - name: Build nvim
+ run: ./ci/run_tests.sh build_nvim
+
+ - if: "!cancelled()"
+ name: lintc
+ run: make lintc
+
+ - if: "!cancelled()"
name: check-single-includes
run: make check-single-includes
diff --git a/ci/build.ps1 b/ci/build.ps1
index 6c042f9116..6709a9507a 100644
--- a/ci/build.ps1
+++ b/ci/build.ps1
@@ -101,6 +101,8 @@ function Test {
# The $LastExitCode from MSBuild can't be trusted
$failed = $false
+ # Run only this test file:
+ # $env:TEST_FILE = "test\functional\foo.lua"
cmake --build $buildDir --target functionaltest 2>&1 |
ForEach-Object { $failed = $failed -or
$_ -match 'functional tests failed with error'; $_ }
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 291eea7263..0fc8a30c20 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -2010,6 +2010,7 @@ expand({string} [, {nosuf} [, {list}]]) *expand()*
a function
<SID> "<SNR>123_" where "123" is the
current script ID |<SID>|
+ <stack> call stack
<cword> word under the cursor
<cWORD> WORD under the cursor
<client> the {clientid} of the last received
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index 87f1589ea1..29eff75bfa 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -879,12 +879,18 @@ Note: these are typed literally, they are not special keys!
*:<sfile>* *<sfile>*
<sfile> When executing a `:source` command, is replaced with the
file name of the sourced file. *E498*
- When executing a function, is replaced with:
- "function {function-name}[{lnum}]"
- function call nesting is indicated like this:
- "function {function-name1}[{lnum}]..{function-name2}[{lnum}]"
+ When executing a function, is replaced with the call stack,
+ as with <stack> (this is for backwards compatibility, using
+ <stack> is preferred).
Note that filename-modifiers are useless when <sfile> is
- used inside a function.
+ not used inside a script.
+ *:<stack>* *<stack>*
+ <stack> is replaced with the call stack, using
+ "function {function-name}[{lnum}]" for a function line
+ and "script {file-name}[{lnum}]" for a script line, and
+ ".." in between items. E.g.:
+ "function {function-name1}[{lnum}]..{function-name2}[{lnum}]"
+ If there is no call stack you get error *E489* .
*:<slnum>* *<slnum>*
<slnum> When executing a `:source` command, is replaced with the
line number. *E842*
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 26d154ae65..9cc17ffa34 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2161,16 +2161,20 @@ set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()*
{lhs} (string) Left-hand side |{lhs}| of the mapping.
{rhs} string|function Right-hand side |{rhs}| of the mapping. Can
also be a Lua function.
- {opts} (table) A table of |:map-arguments| such as "silent". In
- addition to the options listed in |nvim_set_keymap()|, this
- table also accepts the following keys:
- • buffer: (number or boolean) Add a mapping to the given
- buffer. When "true" or 0, use the current buffer.
- • remap: (boolean) Make the mapping recursive. This is the
- inverse of the "noremap" option from |nvim_set_keymap()|.
- Default `false`.
- • replace_keycodes: (boolean) defaults to true if "expr" is
- true.
+ {opts} (table) A table of |:map-arguments|.
+ • Accepts options accepted by the {opts} parameter in
+ |nvim_set_keymap()|, with the following notable differences:
+ • replace_keycodes: Defaults to `true` if "expr" is `true`.
+ • noremap: Always overridden with the inverse of "remap"
+ (see below).
+
+ • In addition to those options, the table accepts the
+ following keys:
+ • buffer: (number or boolean) Add a mapping to the given
+ buffer. When `0` or `true`, use the current buffer.
+ • remap: (boolean) Make the mapping recursive. This is the
+ inverse of the "noremap" option from |nvim_set_keymap()|.
+ Defaults to `false`.
See also: ~
|nvim_set_keymap()|
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 614c6aec60..579c76d57e 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -5876,6 +5876,11 @@ A jump table for the options with a short description can be found at |Q_op|.
suggestions is never more than the value of 'lines'
minus two.
+ timeout:{millisec} Limit the time searching for suggestions to
+ {millisec} milli seconds. Applies to the following
+ methods. When omitted the limit is 5000. When
+ negative there is no limit.
+
file:{filename} Read file {filename}, which must have two columns,
separated by a slash. The first column contains the
bad word, the second column the suggested good word.
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 27c953a460..822a03feb6 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -209,7 +209,7 @@ Commands:
|:Man| is available by default, with many improvements such as completion
|:sign-define| accepts a `numhl` argument, to highlight the line number
|:match| can be invoked before highlight group is defined
- |:source| works with Lua and anonymous (no file) scripts
+ |:source| works with Lua
User commands can support |:command-preview| to show results as you type
Events:
@@ -271,7 +271,6 @@ Options:
'signcolumn' supports up to 9 dynamic/fixed columns
'statusline' supports unlimited alignment sections
'tabline' %@Func@foo%X can call any function on mouse-click
- 'wildoptions' "pum" flag to use popupmenu for wildmode completion
'winblend' pseudo-transparency in floating windows |api-floatwin|
'winhighlight' window-local highlights
diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua
index 7265beb56b..219de16b5c 100644
--- a/runtime/lua/vim/keymap.lua
+++ b/runtime/lua/vim/keymap.lua
@@ -36,14 +36,17 @@ local keymap = {}
---@param lhs string Left-hand side |{lhs}| of the mapping.
---@param rhs string|function Right-hand side |{rhs}| of the mapping. Can also be a Lua function.
--
----@param opts table A table of |:map-arguments| such as "silent". In addition to the options
---- listed in |nvim_set_keymap()|, this table also accepts the following keys:
---- - buffer: (number or boolean) Add a mapping to the given buffer. When "true"
---- or 0, use the current buffer.
---- - remap: (boolean) Make the mapping recursive. This is the
---- inverse of the "noremap" option from |nvim_set_keymap()|.
---- Default `false`.
---- - replace_keycodes: (boolean) defaults to true if "expr" is true.
+---@param opts table A table of |:map-arguments|.
+--- + Accepts options accepted by the {opts} parameter in |nvim_set_keymap()|,
+--- with the following notable differences:
+--- - replace_keycodes: Defaults to `true` if "expr" is `true`.
+--- - noremap: Always overridden with the inverse of "remap" (see below).
+--- + In addition to those options, the table accepts the following keys:
+--- - buffer: (number or boolean) Add a mapping to the given buffer.
+--- When `0` or `true`, use the current buffer.
+--- - remap: (boolean) Make the mapping recursive.
+--- This is the inverse of the "noremap" option from |nvim_set_keymap()|.
+--- Defaults to `false`.
---@see |nvim_set_keymap()|
function keymap.set(mode, lhs, rhs, opts)
vim.validate({
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 4ffd7a4850..0d8efbb830 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -27,6 +27,7 @@
#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"
@@ -1143,7 +1144,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;
@@ -1771,10 +1772,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;
@@ -1878,9 +1878,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;
@@ -1983,8 +1982,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;
@@ -2009,11 +2009,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 be376c2109..6262f2d61c 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -70,6 +70,7 @@
#include "nvim/plines.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
@@ -5149,8 +5150,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++) {
@@ -5195,10 +5194,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) {
@@ -5238,7 +5235,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;
@@ -5253,9 +5250,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_defs.h b/src/nvim/buffer_defs.h
index 89a8a99296..f5dd86cb98 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1132,43 +1132,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.
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index 0eaff06833..72d776d1e4 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -17,6 +17,7 @@
#include "nvim/os/os.h"
#include "nvim/pos.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/screen.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/edit.c b/src/nvim/edit.c
index 7ddff92632..e1473f30c8 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -4251,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
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0cd2c7314d..ff94cf5944 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -49,6 +49,7 @@
#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"
@@ -4187,6 +4188,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();
@@ -8613,8 +8631,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,
@@ -8713,8 +8730,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, "?");
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index e13046ef82..dafc27c4df 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -47,6 +47,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"
@@ -62,11 +63,13 @@
#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"
@@ -120,13 +123,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) {
@@ -157,13 +159,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;
}
@@ -297,10 +298,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) {
@@ -482,8 +482,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))
@@ -635,17 +635,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;
}
@@ -654,13 +652,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;
@@ -689,10 +687,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));
@@ -738,13 +734,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
@@ -761,7 +757,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) {
@@ -779,6 +774,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));
@@ -901,10 +897,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
@@ -987,9 +982,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;
@@ -1000,7 +992,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);
@@ -1009,6 +1001,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) {
@@ -1024,11 +1017,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();
@@ -1063,14 +1053,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;
}
@@ -1082,7 +1070,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 {
@@ -1155,15 +1143,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) {
@@ -1183,19 +1169,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)) {
@@ -1417,10 +1401,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 {
@@ -1567,10 +1549,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
@@ -1579,6 +1557,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);
@@ -1592,6 +1571,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;
@@ -1663,8 +1644,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;
@@ -1673,7 +1652,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;
@@ -1695,7 +1674,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 {
@@ -2045,12 +2024,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;
@@ -2069,7 +2045,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));
@@ -2088,6 +2065,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) {
@@ -2154,8 +2132,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) {
@@ -2163,6 +2139,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 {
@@ -2176,7 +2153,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"),
@@ -2193,7 +2170,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;
@@ -2201,7 +2177,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.
}
@@ -2363,7 +2339,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
@@ -2443,130 +2419,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 + 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)
{}
@@ -2596,10 +2448,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;
@@ -2620,17 +2468,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;
}
@@ -2872,6 +2722,8 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
// 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();
@@ -2952,7 +2804,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
}
(void)mouse_comp_pos(win, &row, &col, &lnum);
for (wp = firstwin; wp != win; wp = wp->w_next) {
- ++winnr;
+ winnr++;
}
set_vim_var_nr(VV_MOUSE_WIN, winnr);
set_vim_var_nr(VV_MOUSE_WINID, wp->handle);
@@ -3206,15 +3058,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;
@@ -3486,8 +3338,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;
@@ -3498,12 +3348,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
@@ -3791,7 +3641,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;
@@ -3805,6 +3654,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
@@ -3829,12 +3679,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)
@@ -3844,6 +3690,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;
@@ -3893,8 +3741,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)) {
@@ -4445,21 +4293,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;
@@ -4477,7 +4322,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;
}
@@ -4614,7 +4460,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]),
@@ -4627,7 +4472,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
@@ -5329,7 +5174,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,
@@ -5339,18 +5184,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;
@@ -5377,6 +5220,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;
@@ -5485,10 +5329,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;
}
@@ -5629,13 +5471,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
@@ -5966,14 +5808,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;
@@ -5986,13 +5827,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;
}
@@ -6010,13 +5850,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;
}
@@ -6261,13 +6100,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;
@@ -6287,7 +6124,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);
}
}
@@ -6298,8 +6135,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;
@@ -6308,11 +6143,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;
}
@@ -6354,12 +6191,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) {
@@ -6401,7 +6237,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:
@@ -6433,9 +6269,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;
@@ -6509,10 +6345,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 {
@@ -7061,7 +6897,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];
@@ -7069,6 +6904,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':
@@ -7118,25 +6954,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;
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;
}
@@ -7163,27 +6993,28 @@ 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 };
searchit_arg_T sia = {
.sa_stop_lnum = (linenr_T)lnum_stop,
.sa_tm = &tm,
};
+ int subpatnum;
+
// Repeat until {skip} returns false.
for (;;) {
subpatnum
@@ -7317,8 +7148,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;
@@ -7326,16 +7156,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;
@@ -7352,8 +7180,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;
@@ -7487,14 +7314,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 {
@@ -7506,14 +7332,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 {
@@ -7525,10 +7350,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) {
@@ -7559,10 +7384,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;
@@ -7571,9 +7392,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);
@@ -7641,7 +7466,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;
@@ -7659,7 +7483,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;
}
@@ -7753,33 +7577,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);
@@ -7795,19 +7610,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 = {
.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;
@@ -7833,7 +7650,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);
@@ -8022,13 +7839,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;
@@ -8062,15 +7879,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];
@@ -8078,7 +7893,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);
}
@@ -8243,11 +8058,9 @@ skip_args:
/// "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]);
+ win_T *win = find_win_by_nr_or_id(&argvars[0]);
if (win != NULL) {
set_qf_ll_list(win, &argvars[1], rettv);
}
@@ -8299,11 +8112,9 @@ static int get_yank_type(char **const pp, MotionType *const yank_type, long *con
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.
@@ -8442,14 +8253,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;
}
@@ -8459,7 +8268,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;
}
@@ -8519,9 +8328,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
}
@@ -8600,10 +8407,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;
@@ -8651,9 +8457,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) {
@@ -8667,6 +8470,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);
@@ -8698,22 +8504,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) {
@@ -8727,8 +8527,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;
@@ -8757,14 +8560,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]);
@@ -8797,6 +8598,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 {
@@ -8897,7 +8699,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) {
@@ -8927,6 +8728,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) {
@@ -9613,11 +9415,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);
@@ -9662,14 +9462,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;
@@ -9691,13 +9491,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;
@@ -9897,7 +9695,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()) {
@@ -9905,8 +9702,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;
}
@@ -10047,10 +9844,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;
@@ -10075,6 +9870,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) {
@@ -10097,7 +9893,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) {
@@ -10196,10 +9992,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.
@@ -10212,7 +10007,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;
@@ -10303,17 +10098,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;
}
@@ -10394,18 +10186,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.
@@ -10430,10 +10219,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;
@@ -10481,10 +10269,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.h b/src/nvim/eval/typval.h
index e411419a07..c4bc9f603b 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -355,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 3a69e40acb..c527c70be0 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -23,6 +23,7 @@
#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"
@@ -219,6 +220,17 @@ 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}.
@@ -297,7 +309,7 @@ int get_lambda_tv(char **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);
@@ -319,7 +331,7 @@ int get_lambda_tv(char **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;
@@ -744,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);
@@ -807,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;
@@ -998,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;
@@ -1148,10 +1135,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
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];
@@ -1167,7 +1154,7 @@ 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);
}
}
@@ -1177,9 +1164,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
--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);
@@ -1188,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;
@@ -1637,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);
}
@@ -2196,7 +2180,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;
@@ -2238,10 +2222,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;
}
@@ -2458,9 +2442,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;
}
@@ -2499,13 +2486,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);
@@ -2540,7 +2526,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);
@@ -3148,8 +3134,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) {
@@ -3170,7 +3155,7 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat)
retval = NULL;
} else {
retval = (char_u *)xstrdup(((char **)(gap->ga_data))[fcp->linenr++]);
- sourcing_lnum = fcp->linenr;
+ SOURCING_LNUM = fcp->linenr;
if (do_profiling == PROF_YES) {
func_line_start(cookie);
}
@@ -3178,11 +3163,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/ex_docmd.c b/src/nvim/ex_docmd.c
index a681b3c693..b9bd7bc663 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -102,16 +102,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;
};
@@ -138,9 +136,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
@@ -150,7 +146,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);
@@ -181,11 +177,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();
@@ -196,7 +187,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
@@ -211,9 +202,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;
@@ -234,7 +225,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
@@ -303,22 +294,22 @@ 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;
msglist_T **saved_msg_list = NULL;
@@ -363,7 +354,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
@@ -373,14 +364,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;
@@ -398,7 +387,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
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;
@@ -410,12 +399,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);
@@ -429,11 +416,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) {
@@ -466,20 +451,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) {
@@ -511,10 +495,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;
}
@@ -534,9 +516,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) {
@@ -551,13 +531,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);
@@ -565,32 +543,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--;
@@ -623,18 +597,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);
@@ -668,34 +640,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]
@@ -721,38 +686,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))
@@ -769,18 +730,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);
@@ -800,17 +759,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;
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.
- */
+ // 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,
@@ -826,10 +781,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'
@@ -849,9 +801,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
@@ -881,25 +832,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);
}
@@ -915,32 +862,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);
}
}
@@ -955,13 +896,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);
@@ -970,7 +910,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;
@@ -978,8 +918,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);
}
@@ -988,23 +928,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;
@@ -1018,14 +955,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;
@@ -1039,11 +973,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;
}
@@ -1086,7 +1019,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;
}
@@ -1099,7 +1032,7 @@ static int current_tab_nr(tabpage_T *tab)
int nr = 0;
FOR_ALL_TABS(tp) {
- ++nr;
+ nr++;
if (tp == tab) {
break;
}
@@ -1386,12 +1319,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) {
@@ -1424,8 +1356,6 @@ 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
@@ -1451,13 +1381,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;
@@ -1721,7 +1651,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
@@ -1857,7 +1787,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
@@ -1892,7 +1822,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
@@ -1943,7 +1873,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);
}
@@ -2017,7 +1947,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);
@@ -2196,16 +2126,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;
@@ -2296,10 +2226,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;
@@ -2334,7 +2264,7 @@ doend:
ea.nextcmd = NULL;
}
- --ex_nesting_level;
+ ex_nesting_level--;
return ea.nextcmd;
}
@@ -2370,8 +2300,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.
@@ -2403,7 +2331,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':
@@ -2842,12 +2770,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;
@@ -2860,7 +2788,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.
@@ -2897,29 +2825,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
@@ -2931,15 +2853,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++;
}
}
@@ -2947,17 +2869,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 {
@@ -2994,7 +2917,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;
}
@@ -3005,7 +2928,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);
}
@@ -3077,9 +3000,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;
@@ -3095,11 +3015,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;
}
@@ -3115,7 +3036,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;
@@ -3129,6 +3049,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;
@@ -3177,14 +3098,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;
@@ -3197,13 +3114,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;
@@ -3283,9 +3198,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;
}
@@ -3343,10 +3256,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
@@ -3394,18 +3305,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 == '`') {
@@ -3414,13 +3322,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);
@@ -3440,10 +3344,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;
}
@@ -3452,7 +3354,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)) {
@@ -3485,14 +3387,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:
@@ -3516,8 +3416,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:
@@ -3575,9 +3475,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);
@@ -3994,8 +3892,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] == '&') {
@@ -4008,10 +3904,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) {
@@ -4019,7 +3915,7 @@ char *skip_range(const char *cmd, int *ctx)
}
}
if (*cmd != NUL) {
- ++cmd;
+ cmd++;
}
}
@@ -4057,17 +3953,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:
@@ -4103,7 +3997,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:
@@ -4164,7 +4058,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".
@@ -4196,7 +4090,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;
@@ -4236,7 +4130,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;
@@ -4267,7 +4161,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
goto error;
}
}
- ++cmd;
+ cmd++;
break;
default:
@@ -4562,45 +4456,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;
}
@@ -4670,20 +4554,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);
@@ -4697,15 +4577,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;
@@ -4735,11 +4616,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) {
@@ -4748,12 +4627,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);
@@ -4849,12 +4726,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
}
@@ -4868,7 +4745,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)) {
@@ -4876,7 +4753,7 @@ static char *skip_cmd_arg(char *p, int rembs)
if (rembs) {
STRMOVE(p, p + 1);
} else {
- ++p;
+ p++;
}
}
MB_PTR_ADV(p);
@@ -4907,7 +4784,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) {
@@ -4966,7 +4842,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 {
@@ -4991,7 +4867,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.
@@ -5003,7 +4878,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) {
@@ -5225,9 +5100,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;
@@ -5268,10 +5143,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);
@@ -5475,16 +5349,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;
@@ -5512,8 +5385,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) {
@@ -5521,7 +5392,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;
@@ -5593,7 +5464,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];
@@ -5601,7 +5471,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
@@ -5621,10 +5491,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;
@@ -5638,7 +5507,7 @@ static void ex_only(exarg_T *eap)
if (wp != curwin) {
win_goto(wp);
}
- close_others(TRUE, eap->forceit);
+ close_others(true, eap->forceit);
}
/// ":all" and ":sall".
@@ -5803,28 +5672,25 @@ void alist_new(void)
/// 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. */
+ // 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) {
+ 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);
}
- old_arg_count = GARGCOUNT;
+ 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);
+ true, fnum_list, fnum_len);
FreeWild(old_arg_count, old_arg_files);
}
p_su = save_p_su;
@@ -5835,7 +5701,6 @@ void alist_expand(int *fnum_list, int fnum_len)
/// 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) {
@@ -5847,18 +5712,18 @@ void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_l
alist_clear(al);
ga_grow(&al->al_ga, count);
{
- for (i = 0; i < count; ++i) {
+ for (int i = 0; i < count; i++) {
if (got_int) {
- /* When adding many buffers this can take a long time. Allow
- * interrupting here. */
+ // 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. */
+ // 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]);
}
@@ -5892,7 +5757,7 @@ void alist_add(alist_T *al, char *fname, int set_fnum)
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;
+ al->al_ga.ga_len++;
}
#if defined(BACKSLASH_IN_FILENAME)
@@ -5900,7 +5765,7 @@ void alist_add(alist_T *al, char *fname, int set_fnum)
/// Adjust slashes in file names. Called after 'shellslash' was set.
void alist_slash_adjust(void)
{
- for (int i = 0; i < GARGCOUNT; ++i) {
+ for (int i = 0; i < GARGCOUNT; i++) {
if (GARGLIST[i].ae_fname != NULL) {
slash_adjust(GARGLIST[i].ae_fname);
}
@@ -5908,7 +5773,7 @@ void alist_slash_adjust(void)
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_alist != &global_alist) {
- for (int i = 0; i < WARGCOUNT(wp); ++i) {
+ for (int i = 0; i < WARGCOUNT(wp); i++) {
if (WARGLIST(wp)[i].ae_fname != NULL) {
slash_adjust(WARGLIST(wp)[i].ae_fname);
}
@@ -5987,9 +5852,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) {
@@ -6093,7 +5956,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
@@ -6148,15 +6011,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;
@@ -6177,15 +6039,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);
@@ -6211,11 +6070,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;
@@ -6287,12 +6143,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);
@@ -6321,10 +6177,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
@@ -6370,9 +6224,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) {
@@ -6390,23 +6242,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;
@@ -6426,9 +6276,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);
@@ -6436,6 +6284,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
@@ -6458,6 +6307,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 {
@@ -6686,17 +6536,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;
@@ -6816,7 +6663,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;
@@ -6829,7 +6676,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();
@@ -6847,9 +6694,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;
@@ -6911,7 +6756,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);
@@ -6940,11 +6785,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);
}
@@ -7033,15 +6876,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;
}
}
@@ -7056,17 +6899,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";
@@ -7076,7 +6918,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;
}
@@ -7086,7 +6928,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
@@ -7115,10 +6957,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) {
@@ -7147,14 +6989,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();
}
@@ -7181,7 +7024,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 {
@@ -7246,8 +7089,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)) {
@@ -7260,6 +7101,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);
}
@@ -7270,14 +7112,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
@@ -7358,10 +7198,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));
@@ -7379,6 +7216,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
@@ -7404,6 +7243,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
@@ -7522,8 +7362,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]) {
@@ -7545,7 +7383,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);
@@ -7553,7 +7391,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);
@@ -7687,7 +7525,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] = "#",
@@ -7706,8 +7543,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);
@@ -7726,6 +7563,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
@@ -7747,12 +7585,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;
@@ -7760,40 +7595,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(&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;
@@ -7823,16 +7652,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--;
@@ -7854,7 +7683,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;
@@ -7919,29 +7748,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;
@@ -7967,6 +7800,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);
@@ -8001,20 +7835,15 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum
/// 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
- */
+ // 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]);
+ int len = 0;
+ for (int idx = 0; idx < ARGCOUNT; idx++) {
+ char *p = alist_name(&ARGLIST[idx]);
if (p == NULL) {
continue;
}
@@ -8023,7 +7852,7 @@ static char *arg_all(void)
if (retval != NULL) {
retval[len] = ' ';
}
- ++len;
+ len++;
}
for (; *p != NUL; p++) {
if (*p == ' '
@@ -8062,22 +7891,16 @@ static char *arg_all(void)
/// @return an allocated string, or NULL for any error.
char *expand_sfile(char *arg)
{
- char *errormsg;
- size_t len;
- char *result;
- char *newres;
- char *repl;
- size_t srclen;
- char *p;
+ char *result = xstrdup(arg);
- result = xstrdup(arg);
-
- for (p = result; *p;) {
+ for (char *p = result; *p;) {
if (STRNCMP(p, "<sfile>", 7) != 0) {
- ++p;
+ p++;
} else {
// replace "<sfile>" with the sourced file name, and do ":" stuff
- repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL);
+ size_t srclen;
+ char *errormsg;
+ char *repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL);
if (errormsg != NULL) {
if (*errormsg) {
emsg(errormsg);
@@ -8089,8 +7912,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);
@@ -8108,9 +7931,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";
}
@@ -8194,10 +8015,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",
@@ -8207,6 +8024,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) {
@@ -8343,7 +8164,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 9e029aa68a..c39bb16498 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -275,6 +275,11 @@ 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;
}
@@ -289,6 +294,7 @@ static void free_msglist(msglist_T *l)
while (messages != NULL) {
next = messages->next;
xfree(messages->msg);
+ xfree(messages->sfile);
xfree(messages);
messages = next;
}
@@ -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;
diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h
index 75150d6da4..9da0c9ad12 100644
--- a/src/nvim/ex_eval_defs.h
+++ b/src/nvim/ex_eval_defs.h
@@ -38,11 +38,13 @@ enum {
/// 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.
+/// message in the list. See cause_errthrow().
typedef struct msglist msglist_T;
struct msglist {
- char *msg; ///< original message
+ 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
};
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 80c774c1c5..aaa710245b 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -5285,8 +5285,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 },
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 8f26e03a94..b02c12c7cb 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -32,6 +32,7 @@
#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.
@@ -1786,7 +1787,7 @@ 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);
@@ -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/globals.h b/src/nvim/globals.h
index 199dd7e8e2..a8e15dfdd9 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_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"
@@ -143,6 +145,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 +251,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
@@ -348,8 +348,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/highlight.c b/src/nvim/highlight.c
index 71c7194479..dfd077840e 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -437,52 +437,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,
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 42db85b3eb..d79283d3e3 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -691,12 +691,12 @@ 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;
}
@@ -735,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);
@@ -863,7 +863,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);
}
}
@@ -873,7 +873,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
@@ -884,7 +884,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);
@@ -1274,7 +1274,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
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 2ce564c011..a86f23db8e 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -37,6 +37,7 @@
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/os.h"
#include "nvim/profile.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
@@ -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, NULL, 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/main.c b/src/nvim/main.c
index 378ca6ba71..4dacf6948f 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -60,6 +60,7 @@
#include "nvim/popupmnu.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/shada.h"
#include "nvim/sign.h"
@@ -159,6 +160,7 @@ bool event_teardown(void)
void early_init(mparm_T *paramp)
{
env_init();
+ estack_init();
cmdline_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
@@ -1814,12 +1816,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 +1843,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 +1852,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 +2061,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/mapping.c b/src/nvim/mapping.c
index a3d8587a97..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"
@@ -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);
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 16802a4e50..1aa1fb5f5a 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -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;
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 adab7c1c7b..6910fc16ae 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -38,6 +38,7 @@
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
@@ -524,7 +525,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 +535,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 +554,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 +581,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 +598,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 +617,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 +704,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 +715,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 +740,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 +750,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.
@@ -2457,7 +2473,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 +2484,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);
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index b293841bb0..a623c14a8e 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -43,6 +43,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"
@@ -58,6 +59,7 @@
#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"
@@ -1282,7 +1284,7 @@ static void normal_redraw(NormalState *s)
update_screen(INVERTED);
} else if (must_redraw) {
update_screen(0);
- } else if (redraw_cmdline || clear_cmdline) {
+ } else if (redraw_cmdline || clear_cmdline || redraw_mode) {
showmode();
}
@@ -6928,6 +6930,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/option.c b/src/nvim/option.c
index 916c460ed6..da29d40b05 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -72,6 +72,7 @@
#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"
@@ -3996,7 +3997,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
};
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index b05ba0d8f8..d4f3756f4d 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -532,9 +532,8 @@ 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;
+ 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--;
@@ -798,11 +797,11 @@ void script_line_start(void)
return;
}
si = &SCRIPT_ITEM(current_sctx.sc_sid);
- if (si->sn_prof_on && sourcing_lnum >= 1) {
+ 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;
+ (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.
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 89848b43bd..4eb38c2c9e 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -50,8 +50,112 @@ struct source_cookie {
# 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;
@@ -1645,18 +1749,19 @@ scriptitem_T *new_script_item(char *const name, scid_T *const sid_out)
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 *save_sourcing_name = SOURCING_NAME;
+ linenr_T save_sourcing_lnum = SOURCING_LNUM;
char sourcing_name_buf[256];
+ char *sname;
if (save_sourcing_name == NULL) {
- sourcing_name = (char *)traceback_name;
+ 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);
- sourcing_name = sourcing_name_buf; // -V507 reassigned below, before return.
+ sname = sourcing_name_buf;
}
- sourcing_lnum = 0;
+ estack_push(ETYPE_SCRIPT, sname, 0);
const sctx_T save_current_sctx = current_sctx;
if (current_sctx.sc_sid != SID_LUA) {
@@ -1668,8 +1773,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
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;
+ estack_pop();
current_sctx = save_current_sctx;
restore_funccal();
return retval;
@@ -1736,8 +1840,6 @@ int do_source_str(const char *cmd, const char *traceback_name)
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;
@@ -1791,11 +1893,11 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (cookie.fp == NULL) {
if (p_verbose > 1) {
verbose_enter();
- if (sourcing_name == NULL) {
+ if (SOURCING_NAME == NULL) {
smsg(_("could not source \"%s\""), fname);
} else {
smsg(_("line %" PRId64 ": could not source \"%s\""),
- (int64_t)sourcing_lnum, fname);
+ (int64_t)SOURCING_LNUM, fname);
}
verbose_leave();
}
@@ -1807,11 +1909,10 @@ int do_source(char *fname, int check_other, int is_vimrc)
// - For a vimrc file, may want to call vimrc_found().
if (p_verbose > 1) {
verbose_enter();
- if (sourcing_name == NULL) {
+ if (SOURCING_NAME == NULL) {
smsg(_("sourcing \"%s\""), fname);
} else {
- smsg(_("line %" PRId64 ": sourcing \"%s\""),
- (int64_t)sourcing_lnum, fname);
+ smsg(_("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname);
}
verbose_leave();
}
@@ -1841,10 +1942,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
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;
+ estack_push(ETYPE_SCRIPT, fname_exp, 0);
// start measuring script load time if --startuptime was passed and
// time_fd was successfully opened afterwards.
@@ -1866,7 +1964,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
save_funccal(&funccalp_entry);
const sctx_T save_current_sctx = current_sctx;
- si = get_current_script_id((char_u *)fname_exp, &current_sctx);
+ si = get_current_script_id(&fname_exp, &current_sctx);
if (l_do_profiling == PROF_YES) {
bool forceit = false;
@@ -1901,14 +1999,13 @@ int do_source(char *fname, int check_other, int is_vimrc)
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;
+ estack_push(ETYPE_SCRIPT, NULL, 0);
// Source the file as lua
nlua_exec_file((const char *)fname_exp);
current_sctx = current_sctx_backup;
- sourcing_lnum = sourcing_lnum_backup;
+ estack_pop();
} else {
// Call do_cmdline, which will call getsourceline() to get the lines.
do_cmdline((char *)firstline, getsourceline, (void *)&cookie,
@@ -1931,13 +2028,12 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (got_int) {
emsg(_(e_interr));
}
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
+ estack_pop();
if (p_verbose > 1) {
verbose_enter();
smsg(_("finished sourcing %s"), fname);
- if (sourcing_name != NULL) {
- smsg(_("continuing in %s"), sourcing_name);
+ if (SOURCING_NAME != NULL) {
+ smsg(_("continuing in %s"), SOURCING_NAME);
}
verbose_leave();
}
@@ -1981,9 +2077,9 @@ theend:
/// 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)
+/// @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;
@@ -2000,13 +2096,14 @@ scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx)
// - 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) {
+ if (si->sn_name != NULL && FNAMECMP(si->sn_name, *fnamep) == 0) {
// Found it!
break;
}
}
if (script_sctx.sc_sid == 0) {
- si = new_script_item((char *)vim_strsave(fname), &script_sctx.sc_sid);
+ si = new_script_item(*fnamep, &script_sctx.sc_sid);
+ *fnamep = xstrdup((char *)si->sn_name);
}
if (ret_sctx != NULL) {
*ret_sctx = script_sctx;
@@ -2110,7 +2207,7 @@ linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
{
return fgetline == getsourceline
? ((struct source_cookie *)cookie)->sourcing_lnum
- : sourcing_lnum;
+ : SOURCING_LNUM;
}
/// Get one full line from a sourced file.
@@ -2126,14 +2223,14 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
// 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->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;
+ 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) {
@@ -2191,10 +2288,10 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
}
// Did we encounter a breakpoint?
- if (sp->breakpoint != 0 && sp->breakpoint <= sourcing_lnum) {
- dbg_breakpoint((char_u *)sp->fname, sourcing_lnum);
+ 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->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM);
sp->dbg_tick = debug_tick;
}
diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h
index fb12029693..a255c6c096 100644
--- a/src/nvim/runtime.h
+++ b/src/nvim/runtime.h
@@ -3,7 +3,51 @@
#include <stdbool.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;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 609c2e3017..8cc597fa99 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -591,7 +591,7 @@ int update_screen(int type)
// Clear or redraw the command line. Done last, because scrolling may
// mess up the command line.
- if (clear_cmdline || redraw_cmdline) {
+ if (clear_cmdline || redraw_cmdline || redraw_mode) {
showmode();
}
@@ -5821,12 +5821,26 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
}
}
-// 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).
+/// @return true when postponing displaying the mode message: when not redrawing
+/// or inside a mapping.
+bool skip_showmode(void)
+{
+ // 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.
+/// If redraw_mode is true show or clear the mode.
+/// @return the length of the message (0 if no message).
int showmode(void)
{
bool need_clear;
@@ -5850,12 +5864,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;
@@ -5989,7 +5999,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
@@ -6001,6 +6011,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
@@ -6018,6 +6031,7 @@ int showmode(void)
win_redr_ruler(last, true);
}
redraw_cmdline = false;
+ redraw_mode = false;
clear_cmdline = false;
return length;
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 17e677c6b7..ed2b8bbdb5 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>
@@ -85,13 +73,10 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
#include "nvim/ex_cmds.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"
@@ -106,22 +91,15 @@
#include "nvim/os/os.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
-#include "nvim/profile.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 +114,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 +169,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 +183,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.
@@ -1050,7 +902,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 +924,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 +953,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;
@@ -1188,8 +1014,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;
@@ -1368,7 +1194,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 +1203,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)) {
@@ -1903,38 +1729,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)
@@ -2444,51 +2238,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 +2296,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.
@@ -2665,7 +2384,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 +2502,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 *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) {
- 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;
@@ -3195,336 +2625,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 *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 (!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 +2647,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 +2671,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];
- 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 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 +2687,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 +2702,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.
@@ -6339,499 +3111,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)
{
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 36df7695ff..58a66f0635 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -242,6 +242,7 @@
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/regexp.h"
+#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/spell.h"
#include "nvim/spell_defs.h"
@@ -576,11 +577,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 +612,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 +809,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;
}
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/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index ca7c8574cb..0a72e38755 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()
@@ -420,6 +424,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 +433,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 +480,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..89e3c9927b 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()
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..b9eefa937a 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -912,12 +912,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 +1406,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 +1418,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_expand_func.vim b/src/nvim/testdir/test_expand_func.vim
index b48c2e8a19..fc0f7619c4 100644
--- a/src/nvim/testdir/test_expand_func.vim
+++ b/src/nvim/testdir/test_expand_func.vim
@@ -37,17 +37,29 @@ 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)
+ call delete('Xstack')
endfunc
func Test_expand_slnum()
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_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..e181641a3b 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)
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 7cb70aa2af..f125c860d5 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
@@ -491,6 +492,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 +667,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 +687,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 +772,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 +878,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 +909,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 +969,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 +1295,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 +1509,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 +1965,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 +2009,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 +2163,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 +3014,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 +3036,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 +3108,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 +3330,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 +3424,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 6e9f2d2377..fdfc1c0f89 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -820,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_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_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index d16783cf31..0f204cdd0c 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -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 b6a8fb24a4..456f0e0edd 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")
@@ -1178,15 +1189,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)
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/usercmd.c b/src/nvim/usercmd.c
index bf5faf7bc9..59b8d10200 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -17,6 +17,7 @@
#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"
@@ -168,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.
@@ -890,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;
diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua
index 13a40fcc53..4bc3355e9e 100644
--- a/test/functional/ex_cmds/source_spec.lua
+++ b/test/functional/ex_cmds/source_spec.lua
@@ -13,6 +13,10 @@ local exec_lua = helpers.exec_lua
local eval = helpers.eval
local exec_capture = helpers.exec_capture
local neq = helpers.neq
+local matches = helpers.matches
+local iswin = helpers.iswin
+local mkdir = helpers.mkdir
+local rmdir = helpers.rmdir
describe(':source', function()
before_each(function()
@@ -39,6 +43,30 @@ describe(':source', function()
os.remove(test_file)
end)
+ it("changing 'shellslash' changes the result of expand()", function()
+ if not iswin() then
+ pending("'shellslash' only works on Windows")
+ return
+ end
+ mkdir('Xshellslash')
+ local script = [[
+ let g:result1 = expand('<stack>')
+ set shellslash
+ let g:result2 = expand('<stack>')
+ set noshellslash
+ let g:result3 = expand('<stack>')
+ ]]
+ write_file([[Xshellslash/Xexpand.vim]], script)
+
+ meths.set_option('shellslash', false)
+ command([[source Xshellslash/Xexpand.vim]])
+ matches([[Xshellslash\Xexpand%.vim]], meths.get_var('result1'))
+ matches([[Xshellslash/Xexpand%.vim]], meths.get_var('result2'))
+ matches([[Xshellslash\Xexpand%.vim]], meths.get_var('result3'))
+
+ rmdir('Xshellslash')
+ end)
+
it('current buffer', function()
insert([[
let a = 2
diff --git a/test/functional/legacy/gf_spec.lua b/test/functional/legacy/gf_spec.lua
new file mode 100644
index 0000000000..f1b1790ba1
--- /dev/null
+++ b/test/functional/legacy/gf_spec.lua
@@ -0,0 +1,15 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local pcall_err = helpers.pcall_err
+
+describe('gf', function()
+ before_each(clear)
+
+ it('is not allowed when buffer is locked', function()
+ command('au OptionSet diff norm! gf')
+ command([[call setline(1, ['Xfile1', 'line2', 'line3', 'line4'])]])
+ eq('Vim(normal):E788: Not allowed to edit another buffer now', pcall_err(command, 'diffthis'))
+ end)
+end)
diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua
index 51c2406933..159cf7a551 100644
--- a/test/functional/legacy/messages_spec.lua
+++ b/test/functional/legacy/messages_spec.lua
@@ -337,6 +337,95 @@ describe('messages', function()
end)
end)
+ describe('mode is cleared when', function()
+ before_each(function()
+ screen = Screen.new(40, 6)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [2] = {bold = true}, -- ModeMsg
+ [3] = {bold = true, reverse=true}, -- StatusLine
+ })
+ screen:attach()
+ end)
+
+ -- oldtest: Test_mode_message_at_leaving_insert_by_ctrl_c()
+ it('leaving Insert mode with Ctrl-C vim-patch:8.1.1189', function()
+ exec([[
+ func StatusLine() abort
+ return ""
+ endfunc
+ set statusline=%!StatusLine()
+ set laststatus=2
+ ]])
+ feed('i')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3: }|
+ {2:-- INSERT --} |
+ ]])
+ feed('<C-C>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3: }|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_mode_message_at_leaving_insert_with_esc_mapped()
+ it('leaving Insert mode with ESC in the middle of a mapping vim-patch:8.1.1192', function()
+ exec([[
+ set laststatus=2
+ inoremap <Esc> <Esc>00
+ ]])
+ feed('i')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ {2:-- INSERT --} |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_mode_updated_after_ctrl_c()
+ it('pressing Ctrl-C in i_CTRL-O', function()
+ feed('i<C-O>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- (insert) --} |
+ ]])
+ feed('<C-C>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end)
+ end)
+
-- oldtest: Test_ask_yesno()
it('y/n prompt works', function()
screen = Screen.new(75, 6)
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index e7eaedba2d..2cff7c1cf4 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -1243,6 +1243,19 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
|
]])
end)
+
+ it('echo messages are shown correctly when getchar() immediately follows', function()
+ feed([[:echo 'foo' | echo 'bar' | call getchar()<CR>]])
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3: }|
+ foo |
+ bar^ |
+ ]])
+ end)
end)
describe('ui/ext_messages', function()
diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua
index f23f00bcc5..2451da983e 100644
--- a/test/functional/vimscript/null_spec.lua
+++ b/test/functional/vimscript/null_spec.lua
@@ -135,7 +135,7 @@ describe('NULL', function()
null_test('does not make Neovim crash when v:oldfiles gets assigned to that', ':let v:oldfiles = L|oldfiles', 0)
null_expr_test('does not make complete() crash or error out',
'execute(":normal i\\<C-r>=complete(1, L)[-1]\\n")',
- '', '\n', function()
+ 0, '', function()
eq({''}, curbufmeths.get_lines(0, -1, false))
end)
null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, 0)