aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/release.yml2
-rw-r--r--INSTALL.md25
-rw-r--r--cmake.deps/cmake/TreesitterCMakeLists.txt1
-rw-r--r--cmake.deps/deps.txt8
-rw-r--r--runtime/doc/api.txt3
-rw-r--r--runtime/doc/diagnostic.txt4
-rw-r--r--runtime/doc/eval.txt46
-rw-r--r--runtime/doc/lsp.txt28
-rw-r--r--runtime/doc/lua.txt6
-rw-r--r--runtime/doc/news.txt21
-rw-r--r--runtime/doc/options.txt2
-rw-r--r--runtime/doc/syntax.txt9
-rw-r--r--runtime/doc/treesitter.txt31
-rw-r--r--runtime/doc/vim_diff.txt3
-rw-r--r--runtime/ftplugin/cpp.vim3
-rw-r--r--runtime/lua/vim/_editor.lua5
-rw-r--r--runtime/lua/vim/_meta/api.lua6
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua48
-rw-r--r--runtime/lua/vim/_meta/options.lua2
-rw-r--r--runtime/lua/vim/diagnostic.lua4
-rw-r--r--runtime/lua/vim/lsp.lua1006
-rw-r--r--runtime/lua/vim/lsp/_changetracking.lua2
-rw-r--r--runtime/lua/vim/lsp/_dynamic.lua7
-rw-r--r--runtime/lua/vim/lsp/buf.lua2
-rw-r--r--runtime/lua/vim/lsp/client.lua877
-rw-r--r--runtime/lua/vim/lsp/codelens.lua36
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua189
-rw-r--r--runtime/lua/vim/lsp/handlers.lua14
-rw-r--r--runtime/lua/vim/lsp/health.lua2
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua4
-rw-r--r--runtime/lua/vim/lsp/log.lua206
-rw-r--r--runtime/lua/vim/lsp/rpc.lua57
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua6
-rw-r--r--runtime/lua/vim/lsp/sync.lua31
-rw-r--r--runtime/lua/vim/lsp/util.lua3
-rw-r--r--runtime/lua/vim/shared.lua1
-rw-r--r--runtime/lua/vim/snippet.lua37
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua23
-rw-r--r--runtime/lua/vim/treesitter/dev.lua54
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua2
-rw-r--r--runtime/lua/vim/treesitter/query.lua91
-rw-r--r--runtime/lua/vim/uri.lua12
-rw-r--r--runtime/queries/bash/highlights.scm8
-rw-r--r--runtime/syntax/dosbatch.vim31
-rw-r--r--runtime/syntax/gpg.vim21
-rw-r--r--runtime/syntax/vim.vim14
-rw-r--r--runtime/tutor/ja/vim-01-beginner.tutor995
-rw-r--r--runtime/tutor/ja/vim-01-beginner.tutor.json44
-rwxr-xr-xscripts/gen_vimdoc.py2
-rw-r--r--scripts/lua2dox.lua12
-rwxr-xr-xsrc/clint.py7
-rw-r--r--src/nvim/api/buffer.c96
-rw-r--r--src/nvim/api/command.c127
-rw-r--r--src/nvim/api/deprecated.c13
-rw-r--r--src/nvim/api/extmark.c87
-rw-r--r--src/nvim/api/keysets_defs.h4
-rw-r--r--src/nvim/api/options.c8
-rw-r--r--src/nvim/api/private/helpers.c52
-rw-r--r--src/nvim/api/private/helpers.h5
-rw-r--r--src/nvim/api/ui.c15
-rw-r--r--src/nvim/api/vim.c100
-rw-r--r--src/nvim/api/win_config.c124
-rw-r--r--src/nvim/arglist.c11
-rw-r--r--src/nvim/autocmd.c6
-rw-r--r--src/nvim/buffer.c6
-rw-r--r--src/nvim/buffer_defs.h29
-rw-r--r--src/nvim/buffer_updates.c97
-rw-r--r--src/nvim/change.c151
-rw-r--r--src/nvim/channel.c7
-rw-r--r--src/nvim/decoration.c87
-rw-r--r--src/nvim/drawline.c52
-rw-r--r--src/nvim/drawscreen.c60
-rw-r--r--src/nvim/edit.c8
-rw-r--r--src/nvim/eval/funcs.c6
-rw-r--r--src/nvim/ex_cmds.c10
-rw-r--r--src/nvim/ex_cmds2.c2
-rw-r--r--src/nvim/extmark.c17
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua276
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/grid.c2
-rw-r--r--src/nvim/help.c2
-rw-r--r--src/nvim/highlight.c78
-rw-r--r--src/nvim/highlight_group.c7
-rw-r--r--src/nvim/highlight_group.h2
-rw-r--r--src/nvim/indent.c174
-rw-r--r--src/nvim/input.c2
-rw-r--r--src/nvim/insexpand.c3
-rw-r--r--src/nvim/lua/converter.c43
-rw-r--r--src/nvim/lua/treesitter.c8
-rw-r--r--src/nvim/main.c13
-rw-r--r--src/nvim/mbyte.c105
-rw-r--r--src/nvim/mbyte.h8
-rw-r--r--src/nvim/mbyte_defs.h8
-rw-r--r--src/nvim/message.c8
-rw-r--r--src/nvim/mouse.c5
-rw-r--r--src/nvim/move.c25
-rw-r--r--src/nvim/msgpack_rpc/channel.c6
-rw-r--r--src/nvim/normal.c6
-rw-r--r--src/nvim/option.c45
-rw-r--r--src/nvim/option_vars.h2
-rw-r--r--src/nvim/options.lua2
-rw-r--r--src/nvim/os/fs.c2
-rw-r--r--src/nvim/os/input.c6
-rw-r--r--src/nvim/plines.h18
-rw-r--r--src/nvim/popupmenu.c18
-rw-r--r--src/nvim/profile.c47
-rw-r--r--src/nvim/search.c18
-rw-r--r--src/nvim/shada.c4
-rw-r--r--src/nvim/sign.c26
-rw-r--r--src/nvim/sign_defs.h6
-rw-r--r--src/nvim/spellsuggest.c4
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/statusline.c13
-rw-r--r--src/nvim/tui/tui.c16
-rw-r--r--src/nvim/ui.c42
-rw-r--r--src/nvim/undo.c4
-rw-r--r--src/nvim/usercmd.c14
-rw-r--r--src/nvim/window.c54
-rw-r--r--src/nvim/winfloat.c40
-rw-r--r--test/benchmark/screenpos_spec.lua319
-rw-r--r--test/functional/api/vim_spec.lua12
-rw-r--r--test/functional/autocmd/textchanged_spec.lua11
-rw-r--r--test/functional/core/startup_spec.lua1
-rw-r--r--test/functional/editor/fold_spec.lua43
-rw-r--r--test/functional/legacy/listlbr_utf8_spec.lua29
-rw-r--r--test/functional/legacy/messages_spec.lua2
-rw-r--r--test/functional/legacy/number_spec.lua306
-rw-r--r--test/functional/lua/snippet_spec.lua12
-rw-r--r--test/functional/plugin/lsp/diagnostic_spec.lua8
-rw-r--r--test/functional/plugin/lsp_spec.lua24
-rw-r--r--test/functional/terminal/helpers.lua13
-rw-r--r--test/functional/terminal/tui_spec.lua277
-rw-r--r--test/functional/treesitter/inspect_tree_spec.lua115
-rw-r--r--test/functional/ui/decorations_spec.lua98
-rw-r--r--test/functional/ui/float_spec.lua9
-rw-r--r--test/functional/ui/fold_spec.lua135
-rw-r--r--test/functional/ui/highlight_spec.lua113
-rw-r--r--test/functional/ui/messages_spec.lua17
-rw-r--r--test/functional/ui/mouse_spec.lua6
-rw-r--r--test/functional/ui/popupmenu_spec.lua70
-rw-r--r--test/functional/ui/statuscolumn_spec.lua16
-rw-r--r--test/old/testdir/test_autocmd.vim20
-rw-r--r--test/old/testdir/test_breakindent.vim50
-rw-r--r--test/old/testdir/test_highlight.vim20
-rw-r--r--test/old/testdir/test_listlbr_utf8.vim21
-rw-r--r--test/old/testdir/test_number.vim71
-rw-r--r--test/old/testdir/test_popup.vim6
-rw-r--r--test/old/testdir/test_registers.vim2
-rw-r--r--test/old/testdir/test_tabpage.vim7
-rw-r--r--test/unit/indent_spec.lua30
-rw-r--r--test/unit/statusline_spec.lua1
151 files changed, 5302 insertions, 2961 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 815e7554cf..059372cf2a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -60,7 +60,7 @@ jobs:
printf 'END\n' >> $GITHUB_OUTPUT
macOS:
- runs-on: macos-11
+ runs-on: macos-12
steps:
- uses: actions/checkout@v4
with:
diff --git a/INSTALL.md b/INSTALL.md
index 8ca3ab7a13..9bba1f0a8c 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -93,6 +93,20 @@ The [Releases](https://github.com/neovim/neovim/releases) page provides pre-buil
## Linux
+### Pre-built archives
+
+The [Releases](https://github.com/neovim/neovim/releases) page provides pre-built binaries for Linux systems.
+
+```sh
+curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
+sudo rm -rf /opt/nvim
+sudo tar -C /opt -xzf nvim-linux64.tar.gz
+```
+
+After this step add this to `~/.bashrc`:
+
+ export PATH="$PATH:/opt/nvim-linux64/bin"
+
### AppImage ("universal" Linux package)
The [Releases](https://github.com/neovim/neovim/releases) page provides an [AppImage](https://appimage.org) that runs on most Linux systems. No installation is needed, just download `nvim.appimage` and run it. (It might not work if your Linux distribution is more than 4 years old.)
@@ -101,6 +115,15 @@ The [Releases](https://github.com/neovim/neovim/releases) page provides an [AppI
chmod u+x nvim.appimage
./nvim.appimage
+To expose nvim globally:
+
+ mkdir -p /opt/nvim
+ mv nvim.appimage /opt/nvim/nvim
+
+And the following line to `~/.bashrc`:
+
+ export PATH="$PATH:/opt/nvim/"
+
If the `./nvim.appimage` command fails, try:
```sh
./nvim.appimage --appimage-extract
@@ -111,7 +134,7 @@ sudo mv squashfs-root /
sudo ln -s /squashfs-root/AppRun /usr/bin/nvim
nvim
```
-
+
### Arch Linux
Neovim can be installed from the community repository:
diff --git a/cmake.deps/cmake/TreesitterCMakeLists.txt b/cmake.deps/cmake/TreesitterCMakeLists.txt
index 9f96430ac2..f1e0d4e575 100644
--- a/cmake.deps/cmake/TreesitterCMakeLists.txt
+++ b/cmake.deps/cmake/TreesitterCMakeLists.txt
@@ -13,7 +13,6 @@ target_include_directories(tree-sitter
install(FILES
lib/include/tree_sitter/api.h
- lib/include/tree_sitter/parser.h
DESTINATION include/tree_sitter)
include(GNUInstallDirs)
diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index d69839c9bc..1230fd9030 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -1,5 +1,5 @@
-LIBUV_URL https://github.com/libuv/libuv/archive/v1.47.0.tar.gz
-LIBUV_SHA256 d50af7e6d72526db137e66fad812421c8a1cae09d146b0ec2bb9a22c5f23ba93
+LIBUV_URL https://github.com/libuv/libuv/archive/v1.48.0.tar.gz
+LIBUV_SHA256 8c253adb0f800926a6cbd1c6576abae0bc8eb86a4f891049b72f9e5b7dc58f33
MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/c-6.0.0.tar.gz
MSGPACK_SHA256 af6f3cf25edb220aa2140b09bb5bdd73ddf00938194bd94ebe5c92090cccb466
@@ -53,8 +53,8 @@ TREESITTER_QUERY_URL https://github.com/nvim-treesitter/tree-sitter-query/archiv
TREESITTER_QUERY_SHA256 e2b806f80e8bf1c4f4e5a96248393fe6622fc1fc6189d6896d269658f67f914c
TREESITTER_PYTHON_URL https://github.com/tree-sitter/tree-sitter-python/archive/v0.20.4.tar.gz
TREESITTER_PYTHON_SHA256 1e38c991832f461c0da8ca222fbe5be3b82b868fe34025f0295206b5e5789d7a
-TREESITTER_BASH_URL https://github.com/tree-sitter/tree-sitter-bash/archive/v0.20.4.tar.gz
-TREESITTER_BASH_SHA256 8a86182b9dd66acdce27c1e272247882b5cf910dd8725fbb68a8bf9d808fecba
+TREESITTER_BASH_URL https://github.com/tree-sitter/tree-sitter-bash/archive/v0.20.5.tar.gz
+TREESITTER_BASH_SHA256 7bba80ac64a18ec1b3f47e738e6a168f065c3cb4244234eff1b773816008f5a7
TREESITTER_MARKDOWN_URL https://github.com/MDeiml/tree-sitter-markdown/archive/v0.1.7.tar.gz
TREESITTER_MARKDOWN_SHA256 7d0e7f7ed4516ed0816f9c304e2e7fa93b2c16f9280416c2fb64dc4efd9c5f83
TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.9.tar.gz
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 89d2860ad2..95bcb31db9 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -888,6 +888,9 @@ nvim_get_api_info() *nvim_get_api_info()*
nvim_get_chan_info({chan}) *nvim_get_chan_info()*
Gets information about a channel.
+ Parameters: ~
+ • {chan} channel_id, or 0 for current channel
+
Return: ~
Dictionary describing a channel, with these keys:
• "id" Channel id.
diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt
index bf0408c773..bee0445e4e 100644
--- a/runtime/doc/diagnostic.txt
+++ b/runtime/doc/diagnostic.txt
@@ -742,14 +742,14 @@ open_float({opts}, {...}) *vim.diagnostic.open_float()*
(`integer?, integer?`) ({float_bufnr}, {win_id})
reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
+ Remove all diagnostics from the given namespace.
+
Unlike |vim.diagnostic.hide()|, this function removes all saved
diagnostics. They cannot be redisplayed using |vim.diagnostic.show()|. To
simply remove diagnostic decorations in a way that they can be
re-displayed, use |vim.diagnostic.hide()|.
Parameters: ~
- • {d} (`vim.Diagnostic`) Remove all diagnostics from the given
- namespace.
• {namespace} (`integer?`) Diagnostic namespace. When omitted, remove
diagnostics from all namespaces.
• {bufnr} (`integer?`) Remove diagnostics for the given buffer.
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 076d324b60..ef416fe56f 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -252,10 +252,15 @@ List concatenation ~
*list-concatenation*
Two lists can be concatenated with the "+" operator: >
:let longlist = mylist + [5, 6]
- :let mylist += [7, 8]
+ :let longlist = [5, 6] + mylist
+To prepend or append an item, turn it into a list by putting [] around it.
-To prepend or append an item, turn the item into a list by putting [] around
-it. To change a list in-place, refer to |list-modification| below.
+A list can be concatenated with another one in-place using |:let+=| or
+|extend()|: >
+ :let mylist += [7, 8]
+ :call extend(mylist, [7, 8])
+<
+See |list-modification| below for more about changing a list in-place.
Sublist ~
@@ -374,6 +379,18 @@ To change part of a list you can specify the first and last item to be
modified. The value must at least have the number of items in the range: >
:let list[3:5] = [3, 4, 5]
+To add items to a List in-place, you can use |:let+=| (|list-concatenation|): >
+ :let listA = [1, 2]
+ :let listA += [3, 4]
+<
+When two variables refer to the same List, changing one List in-place will
+cause the referenced List to be changed in-place: >
+ :let listA = [1, 2]
+ :let listB = listA
+ :let listB += [3, 4]
+ :echo listA
+ [1, 2, 3, 4]
+<
Adding and removing items from a list is done with functions. Here are a few
examples: >
:call insert(list, 'a') " prepend item 'a'
@@ -682,12 +699,15 @@ This calls Doit() with 0x11, 0x22 and 0x33.
Blob concatenation ~
-
+ *blob-concatenation*
Two blobs can be concatenated with the "+" operator: >
:let longblob = myblob + 0z4455
+ :let longblob = 0z4455 + myblob
+<
+A blob can be concatenated with another one in-place using |:let+=|: >
:let myblob += 0z6677
-
-To change a blob in-place see |blob-modification| below.
+<
+See |blob-modification| below for more about changing a blob in-place.
Part of a blob ~
@@ -730,6 +750,18 @@ To change part of a blob you can specify the first and last byte to be
modified. The value must have the same number of bytes in the range: >
:let blob[3:5] = 0z334455
+To add items to a Blob in-place, you can use |:let+=| (|blob-concatenation|): >
+ :let blobA = 0z1122
+ :let blobA += 0z3344
+<
+When two variables refer to the same Blob, changing one Blob in-place will
+cause the referenced Blob to be changed in-place: >
+ :let blobA = 0z1122
+ :let blobB = blobA
+ :let blobB += 0z3344
+ :echo blobA
+ 0z11223344
+<
You can also use the functions |add()|, |remove()| and |insert()|.
@@ -1878,6 +1910,8 @@ This does NOT work: >
:let {var} ..= {expr1} Like ":let {var} = {var} .. {expr1}".
These fail if {var} was not set yet and when the type
of {var} and {expr1} don't fit the operator.
+ `+=` modifies a |List| or a |Blob| in-place instead of
+ creating a new one.
:let ${env-name} = {expr1} *:let-environment* *:let-$*
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index ad1b838578..fc4c164ea0 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -932,7 +932,7 @@ start({config}, {opts}) *vim.lsp.start()*
`ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
Parameters: ~
- • {config} (`table`) Same configuration as documented in
+ • {config} (`lsp.ClientConfig`) Same configuration as documented in
|vim.lsp.start_client()|
• {opts} (`lsp.StartOpts?`) Optional keyword arguments:
• reuse_client (fun(client: client, config: table): boolean)
@@ -1384,6 +1384,7 @@ on_diagnostic({_}, {result}, {ctx}, {config})
<
Parameters: ~
+ • {result} (`lsp.DocumentDiagnosticReport`)
• {ctx} (`lsp.HandlerContext`)
• {config} (`table`) Configuration table (see
|vim.diagnostic.config()|).
@@ -1414,8 +1415,9 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config})
<
Parameters: ~
+ • {result} (`lsp.PublishDiagnosticsParams`)
• {ctx} (`lsp.HandlerContext`)
- • {config} (`table`) Configuration table (see
+ • {config} (`vim.diagnostic.Opts?`) Configuration table (see
|vim.diagnostic.config()|).
@@ -1427,7 +1429,8 @@ clear({client_id}, {bufnr}) *vim.lsp.codelens.clear()*
Parameters: ~
• {client_id} (`integer?`) filter by client_id. All clients if nil
- • {bufnr} (`integer?`) filter by buffer. All buffers if nil
+ • {bufnr} (`integer?`) filter by buffer. All buffers if nil, 0 for
+ current buffer
display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()*
Display the lenses using virtual text
@@ -1454,15 +1457,21 @@ on_codelens({err}, {result}, {ctx}, {_})
Parameters: ~
• {ctx} (`lsp.HandlerContext`)
-refresh() *vim.lsp.codelens.refresh()*
- Refresh the codelens for the current buffer
+refresh({opts}) *vim.lsp.codelens.refresh()*
+ Refresh the lenses.
It is recommended to trigger this using an autocmd or via keymap.
Example: >vim
- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+ autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh({ bufnr = 0 })
<
+ Parameters: ~
+ • {opts} (`vim.lsp.codelens.RefreshOptions?`) Table with the following
+ fields:
+ • `bufnr` (integer|nil): filter by buffer. All buffers if nil,
+ 0 for current buffer
+
run() *vim.lsp.codelens.run()*
Run the code lens in the current line
@@ -2164,17 +2173,14 @@ rpc_response_error({code}, {message}, {data})
See also: ~
• lsp.ErrorCodes See `vim.lsp.protocol.ErrorCodes`
- *vim.lsp.rpc.start()*
-start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params})
+start({cmd}, {dispatchers}, {extra_spawn_params}) *vim.lsp.rpc.start()*
Starts an LSP server process and create an LSP RPC client object to
interact with it. Communication with the spawned process happens via
stdio. For communication via TCP, spawn a process manually and use
|vim.lsp.rpc.connect()|
Parameters: ~
- • {cmd} (`string`) Command to start the LSP server.
- • {cmd_args} (`string[]`) List of additional string arguments
- to pass to {cmd}.
+ • {cmd} (`string[]`) Command to start the LSP server.
• {dispatchers} (`vim.lsp.rpc.Dispatchers?`) Dispatchers for LSP
message types. Valid dispatcher names are:
• `"notification"`
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index c2f5941a5c..6a1d94d34b 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1622,9 +1622,9 @@ vim.on_key({fn}, {ns_id}) *vim.on_key()*
• {fn} will receive the keys after mappings have been evaluated
Parameters: ~
- • {fn} (`fun(key: string)`) Function invoked on every key press.
- |i_CTRL-V| Returning nil removes the callback associated with
- namespace {ns_id}.
+ • {fn} (`fun(key: string)?`) Function invoked on every key press.
+ |i_CTRL-V| Passing in nil when {ns_id} is specified removes
+ the callback associated with namespace {ns_id}.
• {ns_id} (`integer?`) Namespace ID. If nil or 0, generates and returns
a new |nvim_create_namespace()| id.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index e83fc25f88..573a9f43b8 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -104,10 +104,10 @@ The following changes may require adaptations in user config or plugins.
• |hl-WinSeparator| is linked to |hl-Normal| instead of |hl-VertSplit|.
This also might result into some color schemes looking differently due to
- them relying on an implicit assumptions about how highlight groups are
- defined by default. To account for this, define all attributes of highlight
- groups explicitly. Alternatively, use `:colorscheme vim` or
- `:source $VIMRUNTIME/colors/vim.lua` to restore previous definitions.
+ them relying on implicit assumptions about how highlight groups are defined
+ by default. To account for this, define all attributes of highlight groups
+ explicitly. Alternatively, use `:colorscheme vim` or `:source
+ $VIMRUNTIME/colors/vim.lua` to restore previous definitions.
• 'termguicolors' is enabled by default when Nvim is able to determine that
the host terminal emulator supports 24-bit color.
@@ -116,6 +116,11 @@ The following changes may require adaptations in user config or plugins.
upstream tree-sitter and Helix to make it easier to share queries. The full
list is documented in |treesitter-highlight-groups|.
+• |vim.lsp.codelens.refresh()| now takes an `opts` argument. With this change,
+ the default behavior of just refreshing the current buffer has been replaced by
+ refreshing all buffers.
+
+• |shm-q| now fully hides macro recording message instead of only shortening it.
==============================================================================
BREAKING CHANGES IN HEAD *news-breaking-dev*
@@ -125,7 +130,6 @@ unreleased features on Nvim HEAD.
• Removed `vim.treesitter.foldtext` as transparent foldtext is now supported
https://github.com/neovim/neovim/pull/20750
-• ...
==============================================================================
NEW FEATURES *news-features*
@@ -224,6 +228,9 @@ The following new APIs and features were added.
• |vim.treesitter.query.edit()| allows live editing of treesitter
queries.
• Improved error messages for query parsing.
+ • `:InspectTree` (|vim.treesitter.inspect_tree()|) shows node ranges in
+ 0-based indexing instead of 1-based indexing.
+ • `:InspectTree` (|vim.treesitter.inspect_tree()|) shows root nodes
• |vim.ui.open()| opens URIs using the system default handler (macOS `open`,
Windows `explorer`, Linux `xdg-open`, etc.)
@@ -403,6 +410,8 @@ The following changes to existing APIs or features add new behavior.
• Attempting to set an invalid keycode option (e.g. `set t_foo=123`) no longer
gives an error.
+• Passing 0 to |nvim_get_chan_info()| gets info about the current channel.
+
• |:checkhealth| buffer can now be opened in a split window using modifiers like
|:vertical|, |:horizontal| and |:botright|.
@@ -411,6 +420,8 @@ The following changes to existing APIs or features add new behavior.
• 'errorfile' (|-q|) accepts `-` as an alias for stdin.
+• |--startuptime| reports the startup times for both processes (TUI + server) as separate sections.
+
==============================================================================
REMOVED FEATURES *news-removed*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 6bdb5127c3..25607630f6 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -5459,7 +5459,7 @@ A jump table for the options with a short description can be found at |Q_op|.
match", "Pattern not found", "Back at original", etc.
C don't give messages while scanning for ins-completion *shm-C*
items, for instance "scanning tags"
- q use "recording" instead of "recording @a" *shm-q*
+ q do not show "recording @a" when recording a macro *shm-q*
F don't give the file info when editing a file, like *shm-F*
`:silent` was used for the command
S do not show search count message when searching, e.g. *shm-S*
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index bc5c4a5b3b..0e7c38b38d 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -1294,12 +1294,15 @@ Stack Overflow -
https://stackoverflow.com/questions/12407800/which-comment-style-should-i-use-in-batch-files
-To allow the use of the :: idiom for comments in the Windows Command
-Interpreter or working with MS-DOS bat files, set the
-dosbatch_colons_comment variable to anything: >
+To allow the use of the :: idiom for comments in command blocks with the
+Windows Command Interpreter set the dosbatch_colons_comment variable to
+anything: >
:let dosbatch_colons_comment = 1
+If this variable is set then a :: comment that is the last line in a command
+block will be highlighted as an error.
+
There is an option that covers whether `*.btm` files should be detected as type
"dosbatch" (MS-DOS batch files) or type "btm" (4DOS batch files). The latter
is used by default. You may select the former with the following line: >
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 2755cd421b..f6ee2ef425 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -976,7 +976,8 @@ get({lang}, {query_name}) *vim.treesitter.query.get()*
• {query_name} (`string`) Name of the query (e.g. "highlights")
Return: ~
- (`Query?`) Parsed query
+ (`vim.treesitter.Query?`) Parsed query. `nil` if no query files are
+ found.
*vim.treesitter.query.get_files()*
get_files({lang}, {query_name}, {is_included})
@@ -1040,12 +1041,12 @@ parse({lang}, {query}) *vim.treesitter.query.parse()*
read the contents into a string before calling).
Returns a `Query` (see |lua-treesitter-query|) object which can be used to
- search nodes in the syntax tree for the patterns defined in {query} using `iter_*` methods
- below.
+ search nodes in the syntax tree for the patterns defined in {query} using
+ the `iter_captures` and `iter_matches` methods.
Exposes `info` and `captures` with additional context about {query}.
• `captures` contains the list of unique capture names defined in {query}.
- -`info.captures` also points to `captures`.
+ • `info.captures` also points to `captures`.
• `info.patterns` contains information about predicates.
Parameters: ~
@@ -1053,7 +1054,10 @@ parse({lang}, {query}) *vim.treesitter.query.parse()*
• {query} (`string`) Query in s-expr syntax
Return: ~
- (`Query`) Parsed query
+ (`vim.treesitter.Query`) Parsed query
+
+ See also: ~
+ • |vim.treesitter.query.get()|
*Query:iter_captures()*
Query:iter_captures({node}, {source}, {start}, {stop})
@@ -1082,8 +1086,10 @@ Query:iter_captures({node}, {source}, {start}, {stop})
• {node} (`TSNode`) under which the search will occur
• {source} (`integer|string`) Source buffer or string to extract text
from
- • {start} (`integer`) Starting line for the search
- • {stop} (`integer`) Stopping line for the search (end-exclusive)
+ • {start} (`integer?`) Starting line for the search. Defaults to
+ `node:start()`.
+ • {stop} (`integer?`) Stopping line for the search (end-exclusive).
+ Defaults to `node:end_()`.
Return: ~
(`fun(end_line: integer?): integer, TSNode, TSMetadata`) capture id,
@@ -1115,13 +1121,14 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts})
Parameters: ~
• {node} (`TSNode`) under which the search will occur
• {source} (`integer|string`) Source buffer or string to search
- • {start} (`integer`) Starting line for the search
- • {stop} (`integer`) Stopping line for the search (end-exclusive)
- • {opts} (`table?`) Options:
+ • {start} (`integer?`) Starting line for the search. Defaults to
+ `node:start()`.
+ • {stop} (`integer?`) Stopping line for the search (end-exclusive).
+ Defaults to `node:end_()`.
+ • {opts} (`table?`) Optional keyword arguments:
• max_start_depth (integer) if non-zero, sets the maximum
start depth for each match. This is used to prevent
- traversing too deep into a tree. Requires treesitter >=
- 0.20.9.
+ traversing too deep into a tree.
Return: ~
(`fun(): integer, table<integer,TSNode>, table`) pattern id, match,
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 46550f42b7..023944a8ad 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -581,6 +581,9 @@ Autocommands:
- |TermResponse| is fired for any OSC sequence received from the terminal,
instead of the Primary Device Attributes response. |v:termresponse|
+Options:
+- |shm-q| fully hides macro recording message instead of only shortening it.
+
==============================================================================
Missing features *nvim-missing*
diff --git a/runtime/ftplugin/cpp.vim b/runtime/ftplugin/cpp.vim
index cb425aa8e7..d4931a2533 100644
--- a/runtime/ftplugin/cpp.vim
+++ b/runtime/ftplugin/cpp.vim
@@ -10,8 +10,7 @@ if exists("b:did_ftplugin")
endif
" Behaves mostly just like C
-runtime! ftplugin/c.vim ftplugin/c_*.vim ftplugin/c/*.vim
-runtime! ftplugin/c.lua ftplugin/c_*.lua ftplugin/c/*.lua
+runtime! ftplugin/c.{vim,lua} ftplugin/c_*.{vim,lua} ftplugin/c/*.{vim,lua}
" C++ uses templates with <things>
" Disabled, because it gives an error for typing an unmatched ">".
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index c130eb1958..c5a6e65e86 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -652,8 +652,9 @@ local on_key_cbs = {}
---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
---@note {fn} will receive the keys after mappings have been evaluated
---
----@param fn fun(key: string) Function invoked on every key press. |i_CTRL-V|
---- Returning nil removes the callback associated with namespace {ns_id}.
+---@param fn fun(key: string)? Function invoked on every key press. |i_CTRL-V|
+--- Passing in nil when {ns_id} is specified removes the
+--- callback associated with namespace {ns_id}.
---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a
--- new |nvim_create_namespace()| id.
---
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 77a29cb4c1..f46ab8023f 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -1158,7 +1158,7 @@ function vim.api.nvim_get_autocmds(opts) end
--- Gets information about a channel.
---
---- @param chan integer
+--- @param chan integer channel_id, or 0 for current channel
--- @return table<string,any>
function vim.api.nvim_get_chan_info(chan) end
@@ -1535,7 +1535,7 @@ function vim.api.nvim_open_term(buffer, opts) end
---
--- @param buffer integer Buffer to display, or 0 for current buffer
--- @param enter boolean Enter the window (make it the current window)
---- @param config vim.api.keyset.float_config Map defining the window configuration. Keys:
+--- @param config vim.api.keyset.win_config Map defining the window configuration. Keys:
--- • relative: Sets the window layout to "floating", placed at
--- (row,col) coordinates relative to:
--- • "editor" The global editor grid
@@ -2093,7 +2093,7 @@ function vim.api.nvim_win_set_buf(window, buffer) end
--- changed. `row`/`col` and `relative` must be reconfigured together.
---
--- @param window integer Window handle, or 0 for current window
---- @param config vim.api.keyset.float_config Map defining the window configuration, see `nvim_open_win()`
+--- @param config vim.api.keyset.win_config Map defining the window configuration, see `nvim_open_win()`
function vim.api.nvim_win_set_config(window, config) end
--- Sets the (1,0)-indexed cursor position in the window. `api-indexing` This
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
index 1b6c6811a2..0442a89e3f 100644
--- a/runtime/lua/vim/_meta/api_keysets.lua
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -111,30 +111,6 @@ error('Cannot require a meta file')
--- @class vim.api.keyset.exec_opts
--- @field output? boolean
---- @class vim.api.keyset.float_config
---- @field row? number
---- @field col? number
---- @field width? integer
---- @field height? integer
---- @field anchor? string
---- @field relative? string
---- @field split? string
---- @field win? integer
---- @field bufpos? any[]
---- @field external? boolean
---- @field focusable? boolean
---- @field vertical? boolean
---- @field zindex? integer
---- @field border? any
---- @field title? any
---- @field title_pos? string
---- @field footer? any
---- @field footer_pos? string
---- @field style? string
---- @field noautocmd? boolean
---- @field fixed? boolean
---- @field hide? boolean
-
--- @class vim.api.keyset.get_autocmds
--- @field event? any
--- @field group? any
@@ -292,6 +268,30 @@ error('Cannot require a meta file')
--- @field range? any
--- @field register? boolean
+--- @class vim.api.keyset.win_config
+--- @field row? number
+--- @field col? number
+--- @field width? integer
+--- @field height? integer
+--- @field anchor? string
+--- @field relative? string
+--- @field split? string
+--- @field win? integer
+--- @field bufpos? any[]
+--- @field external? boolean
+--- @field focusable? boolean
+--- @field vertical? boolean
+--- @field zindex? integer
+--- @field border? any
+--- @field title? any
+--- @field title_pos? string
+--- @field footer? any
+--- @field footer_pos? string
+--- @field style? string
+--- @field noautocmd? boolean
+--- @field fixed? boolean
+--- @field hide? boolean
+
--- @class vim.api.keyset.win_text_height
--- @field start_row? integer
--- @field end_row? integer
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 83da61cc2b..835bd95a3a 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -5797,7 +5797,7 @@ vim.bo.sw = vim.bo.shiftwidth
--- match", "Pattern not found", "Back at original", etc.
--- C don't give messages while scanning for ins-completion *shm-C*
--- items, for instance "scanning tags"
---- q use "recording" instead of "recording @a" *shm-q*
+--- q do not show "recording @a" when recording a macro *shm-q*
--- F don't give the file info when editing a file, like *shm-F*
--- `:silent` was used for the command
--- S do not show search count message when searching, e.g. *shm-S*
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index a3a2422ab5..91f91b5879 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -11,7 +11,7 @@ local M = {}
--- @field severity? vim.diagnostic.Severity
--- @field message string
--- @field source? string
---- @field code? string
+--- @field code? string|integer
--- @field _tags? { deprecated: boolean, unnecessary: boolean}
--- @field user_data? any arbitrary data plugins can add
--- @field namespace? integer
@@ -1023,7 +1023,7 @@ end
--- @class vim.diagnostic.GotoOpts : vim.diagnostic.GetOpts
--- @field cursor_position? {[1]:integer,[2]:integer}
---- @field wrap? integer
+--- @field wrap? boolean
--- @field float? boolean|vim.diagnostic.Opts.Float
--- @field win_id? integer
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index d8d47a8464..13f2c92cc2 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,8 +1,4 @@
----@diagnostic disable: invisible
local api = vim.api
-local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds =
- api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds
-local uv = vim.uv
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate
local if_nil = vim.F.if_nil
@@ -15,6 +11,7 @@ local lsp = vim._defer_require('vim.lsp', {
_tagfunc = ..., --- @module 'vim.lsp._tagfunc'
_watchfiles = ..., --- @module 'vim.lsp._watchfiles'
buf = ..., --- @module 'vim.lsp.buf'
+ client = ..., --- @module 'vim.lsp.client'
codelens = ..., --- @module 'vim.lsp.codelens'
diagnostic = ..., --- @module 'vim.lsp.diagnostic'
handlers = ..., --- @module 'vim.lsp.handlers'
@@ -70,14 +67,6 @@ lsp._request_name_to_capability = {
-- TODO improve handling of scratch buffers with LSP attached.
---- Concatenates and writes a list of strings to the Vim error buffer.
----
----@param ... string List to write to the buffer
-local function err_message(...)
- nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
- nvim_command('redraw')
-end
-
--- Returns the buffer number for the given {bufnr}.
---
---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
@@ -103,30 +92,8 @@ function lsp._unsupported_method(method)
return msg
end
---- Checks whether a given path is a directory.
----
----@param filename (string) path to check
----@return boolean # true if {filename} exists and is a directory, false otherwise
-local function is_dir(filename)
- validate({ filename = { filename, 's' } })
- local stat = uv.fs_stat(filename)
- return stat and stat.type == 'directory' or false
-end
-
local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
-local valid_encodings = {
- ['utf-8'] = 'utf-8',
- ['utf-16'] = 'utf-16',
- ['utf-32'] = 'utf-32',
- ['utf8'] = 'utf-8',
- ['utf16'] = 'utf-16',
- ['utf32'] = 'utf-32',
- UTF8 = 'utf-8',
- UTF16 = 'utf-16',
- UTF32 = 'utf-32',
-}
-
local format_line_ending = {
['unix'] = '\n',
['dos'] = '\r\n',
@@ -140,14 +107,6 @@ function lsp._buf_get_line_ending(bufnr)
return format_line_ending[vim.bo[bufnr].fileformat] or '\n'
end
-local client_index = 0
---- Returns a new, unused client id.
----
----@return integer client_id
-local function next_client_id()
- client_index = client_index + 1
- return client_index
-end
-- Tracks all clients created via lsp.start_client
local active_clients = {} --- @type table<integer,lsp.Client>
local all_buffer_active_clients = {} --- @type table<integer,table<integer,true>>
@@ -198,115 +157,6 @@ lsp.client_errors = tbl_extend(
})
)
---- Normalizes {encoding} to valid LSP encoding names.
----
----@param encoding (string) Encoding to normalize
----@return string # normalized encoding name
-local function validate_encoding(encoding)
- validate({
- encoding = { encoding, 's' },
- })
- return valid_encodings[encoding:lower()]
- or error(
- string.format(
- "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'",
- encoding
- )
- )
-end
-
----@internal
---- Parses a command invocation into the command itself and its args. If there
---- are no arguments, an empty table is returned as the second argument.
----
----@param input string[]
----@return string command, string[] args #the command and arguments
-function lsp._cmd_parts(input)
- validate({
- cmd = {
- input,
- function()
- return vim.tbl_islist(input)
- end,
- 'list',
- },
- })
-
- local cmd = input[1]
- local cmd_args = {}
- -- Don't mutate our input.
- for i, v in ipairs(input) do
- validate({ ['cmd argument'] = { v, 's' } })
- if i > 1 then
- table.insert(cmd_args, v)
- end
- end
- return cmd, cmd_args
-end
-
---- Augments a validator function with support for optional (nil) values.
----
----@param fn (fun(v): boolean) The original validator function; should return a
----bool.
----@return fun(v): boolean # The augmented function. Also returns true if {v} is
----`nil`.
-local function optional_validator(fn)
- return function(v)
- return v == nil or fn(v)
- end
-end
-
---- Validates a client configuration as given to |vim.lsp.start_client()|.
----
----@param config (lsp.ClientConfig)
----@return (string|fun(dispatchers:vim.rpc.Dispatchers):vim.lsp.rpc.PublicClient?) Command
----@return string[] Arguments
----@return string Encoding.
-local function validate_client_config(config)
- validate({
- config = { config, 't' },
- })
- validate({
- handlers = { config.handlers, 't', true },
- capabilities = { config.capabilities, 't', true },
- cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
- cmd_env = { config.cmd_env, 't', true },
- detached = { config.detached, 'b', true },
- name = { config.name, 's', true },
- on_error = { config.on_error, 'f', true },
- on_exit = { config.on_exit, 'f', true },
- on_init = { config.on_init, 'f', true },
- settings = { config.settings, 't', true },
- commands = { config.commands, 't', true },
- before_init = { config.before_init, 'f', true },
- offset_encoding = { config.offset_encoding, 's', true },
- flags = { config.flags, 't', true },
- get_language_id = { config.get_language_id, 'f', true },
- })
- assert(
- (
- not config.flags
- or not config.flags.debounce_text_changes
- or type(config.flags.debounce_text_changes) == 'number'
- ),
- 'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
- )
-
- local cmd, cmd_args --- @type (string|fun(dispatchers:vim.rpc.Dispatchers):vim.lsp.rpc.PublicClient), string[]
- local config_cmd = config.cmd
- if type(config_cmd) == 'function' then
- cmd = config_cmd
- else
- cmd, cmd_args = lsp._cmd_parts(config_cmd)
- end
- local offset_encoding = valid_encodings.UTF16
- if config.offset_encoding then
- offset_encoding = validate_encoding(config.offset_encoding)
- end
-
- return cmd, cmd_args, offset_encoding
-end
-
---@private
--- Returns full text of buffer {bufnr} as a string.
---
@@ -314,7 +164,7 @@ end
---@return string # Buffer text as string.
function lsp._buf_get_full_text(bufnr)
local line_ending = lsp._buf_get_line_ending(bufnr)
- local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
+ local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
if vim.bo[bufnr].eol then
text = text .. line_ending
end
@@ -341,42 +191,6 @@ local function once(fn)
end
end
---- Default handler for the 'textDocument/didOpen' LSP notification.
----
----@param bufnr integer Number of the buffer, or 0 for current
----@param client lsp.Client Client object
-local function text_document_did_open_handler(bufnr, client)
- changetracking.init(client, bufnr)
- if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
- return
- end
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
- local filetype = vim.bo[bufnr].filetype
-
- local params = {
- textDocument = {
- version = 0,
- uri = vim.uri_from_bufnr(bufnr),
- languageId = client.config.get_language_id(bufnr, filetype),
- text = lsp._buf_get_full_text(bufnr),
- },
- }
- client.notify(ms.textDocument_didOpen, params)
- util.buf_versions[bufnr] = params.textDocument.version
-
- -- Next chance we get, we should re-do the diagnostics
- vim.schedule(function()
- -- Protect against a race where the buffer disappears
- -- between `did_open_handler` and the scheduled function firing.
- if api.nvim_buf_is_valid(bufnr) then
- local namespace = vim.lsp.diagnostic.get_namespace(client.id)
- vim.diagnostic.show(namespace, bufnr)
- end
- end)
-end
-
-- FIXME: DOC: Shouldn't need to use a dummy function
--
--- LSP client object. You can get an active client object via
@@ -508,8 +322,8 @@ end
--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
----@param config table Same configuration as documented in |vim.lsp.start_client()|
----@param opts (nil|lsp.StartOpts) Optional keyword arguments:
+---@param config lsp.ClientConfig Same configuration as documented in |vim.lsp.start_client()|
+---@param opts lsp.StartOpts? Optional keyword arguments:
--- - reuse_client (fun(client: client, config: table): boolean)
--- Predicate used to decide if a client should be re-used.
--- Used on all running clients.
@@ -518,20 +332,16 @@ end
--- - bufnr (number)
--- Buffer handle to attach to if starting or re-using a
--- client (0 for current).
----@return integer|nil client_id
+---@return integer? client_id
function lsp.start(config, opts)
opts = opts or {}
local reuse_client = opts.reuse_client
or function(client, conf)
return client.config.root_dir == conf.root_dir and client.name == conf.name
end
- if not config.name and type(config.cmd) == 'table' then
- config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil
- end
- local bufnr = opts.bufnr
- if bufnr == nil or bufnr == 0 then
- bufnr = api.nvim_get_current_buf()
- end
+
+ local bufnr = resolve_bufnr(opts.bufnr)
+
for _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do
for _, client in pairs(clients) do
if reuse_client(client, config) then
@@ -540,10 +350,13 @@ function lsp.start(config, opts)
end
end
end
+
local client_id = lsp.start_client(config)
- if client_id == nil then
- return nil -- lsp.start_client will have printed an error
+
+ if not client_id then
+ return -- lsp.start_client will have printed an error
end
+
lsp.buf_attach_client(bufnr, client_id)
return client_id
end
@@ -556,7 +369,9 @@ function lsp.status()
local percentage = nil
local messages = {} --- @type string[]
for _, client in ipairs(vim.lsp.get_clients()) do
+ --- @diagnostic disable-next-line:no-unknown
for progress in client.progress do
+ --- @cast progress {token: lsp.ProgressToken, value: lsp.LSPAny}
local value = progress.value
if type(value) == 'table' and value.kind then
local message = value.message and (value.title .. ': ' .. value.message) or value.title
@@ -632,28 +447,109 @@ function lsp._set_defaults(client, bufnr)
end
end
---- @class lsp.ClientConfig
---- @field cmd (string[]|fun(dispatchers: table):table)
---- @field cmd_cwd string
---- @field cmd_env (table)
---- @field detached boolean
---- @field workspace_folders (table)
---- @field capabilities lsp.ClientCapabilities
---- @field handlers table<string,function>
---- @field settings table
---- @field commands table
---- @field init_options table
---- @field name string
---- @field get_language_id fun(bufnr: integer, filetype: string): string
---- @field offset_encoding string
---- @field on_error fun(code: integer)
---- @field before_init function
---- @field on_init function
---- @field on_exit fun(code: integer, signal: integer, client_id: integer)
---- @field on_attach fun(client: lsp.Client, bufnr: integer)
---- @field trace 'off'|'messages'|'verbose'|nil
---- @field flags table
---- @field root_dir string
+--- Reset defaults set by `set_defaults`.
+--- Must only be called if the last client attached to a buffer exits.
+local function reset_defaults(bufnr)
+ if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
+ vim.bo[bufnr].tagfunc = nil
+ end
+ if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
+ vim.bo[bufnr].omnifunc = nil
+ end
+ if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
+ vim.bo[bufnr].formatexpr = nil
+ end
+ api.nvim_buf_call(bufnr, function()
+ local keymap = vim.fn.maparg('K', 'n', false, true)
+ if keymap and keymap.callback == vim.lsp.buf.hover then
+ vim.keymap.del('n', 'K', { buffer = bufnr })
+ end
+ end)
+end
+
+--- @param client lsp.Client
+local function on_client_init(client)
+ local id = client.id
+ uninitialized_clients[id] = nil
+ -- Only assign after initialized.
+ active_clients[id] = client
+ -- If we had been registered before we start, then send didOpen This can
+ -- happen if we attach to buffers before initialize finishes or if
+ -- someone restarts a client.
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[id] then
+ client.on_attach(bufnr)
+ end
+ end
+end
+
+--- @param code integer
+--- @param signal integer
+--- @param client_id integer
+local function on_client_exit(code, signal, client_id)
+ local client = active_clients[client_id] or uninitialized_clients[client_id]
+
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ vim.schedule(function()
+ if client and client.attached_buffers[bufnr] then
+ api.nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+ end
+
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ vim.diagnostic.reset(namespace, bufnr)
+
+ client_ids[client_id] = nil
+ if vim.tbl_isempty(client_ids) then
+ reset_defaults(bufnr)
+ end
+ end)
+ end
+ end
+
+ local name = client.name or 'unknown'
+
+ -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
+ -- autocommands
+ vim.schedule(function()
+ active_clients[client_id] = nil
+ uninitialized_clients[client_id] = nil
+
+ -- Client can be absent if executable starts, but initialize fails
+ -- init/attach won't have happened
+ if client then
+ changetracking.reset(client)
+ end
+ if code ~= 0 or (signal ~= 0 and signal ~= 15) then
+ local msg = string.format(
+ 'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
+ name,
+ code,
+ signal,
+ lsp.get_log_path()
+ )
+ vim.notify(msg, vim.log.levels.WARN)
+ end
+ end)
+end
+
+--- @generic F: function
+--- @param ... F
+--- @return F
+local function join_cbs(...)
+ local funcs = vim.F.pack_len(...)
+ return function(...)
+ for i = 1, funcs.n do
+ if funcs[i] ~= nil then
+ funcs[i](...)
+ end
+ end
+ end
+end
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g.
@@ -775,654 +671,22 @@ end
--- fully initialized. Use `on_init` to do any actions once
--- the client has been initialized.
function lsp.start_client(config)
- local cmd, cmd_args, offset_encoding = validate_client_config(config)
-
- config.flags = config.flags or {}
- config.settings = config.settings or {}
+ config = vim.deepcopy(config, false)
+ config.on_init = join_cbs(config.on_init, on_client_init)
+ config.on_exit = join_cbs(config.on_exit, on_client_exit)
- -- By default, get_language_id just returns the exact filetype it is passed.
- -- It is possible to pass in something that will calculate a different filetype,
- -- to be sent by the client.
- config.get_language_id = config.get_language_id or function(_, filetype)
- return filetype
- end
-
- local client_id = next_client_id()
-
- local handlers = config.handlers or {}
- local name = config.name or tostring(client_id)
- local log_prefix = string.format('LSP[%s]', name)
-
- local dispatch = {}
+ local client = require('vim.lsp.client').start(config)
- --- Returns the handler associated with an LSP method.
- --- Returns the default handler if the user hasn't set a custom one.
- ---
- ---@param method (string) LSP method name
- ---@return lsp.Handler|nil handler for the given method, if defined, or the default from |vim.lsp.handlers|
- local function resolve_handler(method)
- return handlers[method] or lsp.handlers[method]
- end
-
- ---@private
- --- Handles a notification sent by an LSP server by invoking the
- --- corresponding handler.
- ---
- ---@param method (string) LSP method name
- ---@param params (table) The parameters for that method.
- function dispatch.notification(method, params)
- if log.trace() then
- log.trace('notification', method, params)
- end
- local handler = resolve_handler(method)
- if handler then
- -- Method name is provided here for convenience.
- handler(nil, params, { method = method, client_id = client_id })
- end
- end
-
- ---@private
- --- Handles a request from an LSP server by invoking the corresponding handler.
- ---
- ---@param method (string) LSP method name
- ---@param params (table) The parameters for that method
- ---@return any result
- ---@return lsp.ResponseError error code and message set in case an exception happens during the request.
- function dispatch.server_request(method, params)
- if log.trace() then
- log.trace('server_request', method, params)
- end
- local handler = resolve_handler(method)
- if handler then
- if log.trace() then
- log.trace('server_request: found handler for', method)
- end
- return handler(nil, params, { method = method, client_id = client_id })
- end
- if log.warn() then
- log.warn('server_request: no handler found for', method)
- end
- return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
- end
-
- --- Logs the given error to the LSP log and to the error buffer.
- --- @param code integer Error code
- --- @param err any Error arguments
- local function write_error(code, err)
- if log.error() then
- log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
- end
- err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
- end
-
- ---@private
- --- Invoked when the client operation throws an error.
- ---
- ---@param code (integer) Error code
- ---@param err any Other arguments may be passed depending on the error kind
- ---@see vim.lsp.rpc.client_errors for possible errors. Use
- ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
- function dispatch.on_error(code, err)
- write_error(code, err)
- if config.on_error then
- local status, usererr = pcall(config.on_error, code, err)
- if not status then
- if log.error() then
- log.error(log_prefix, 'user on_error failed', { err = usererr })
- end
- err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
- end
- end
- end
-
- --- Reset defaults set by `set_defaults`.
- --- Must only be called if the last client attached to a buffer exits.
- local function reset_defaults(bufnr)
- if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
- vim.bo[bufnr].tagfunc = nil
- end
- if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
- vim.bo[bufnr].omnifunc = nil
- end
- if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
- vim.bo[bufnr].formatexpr = nil
- end
- api.nvim_buf_call(bufnr, function()
- local keymap = vim.fn.maparg('K', 'n', false, true)
- if keymap and keymap.callback == vim.lsp.buf.hover then
- vim.keymap.del('n', 'K', { buffer = bufnr })
- end
- end)
- end
-
- ---@private
- --- Invoked on client exit.
- ---
- ---@param code (integer) exit code of the process
- ---@param signal (integer) the signal used to terminate (if any)
- function dispatch.on_exit(code, signal)
- if config.on_exit then
- pcall(config.on_exit, code, signal, client_id)
- end
-
- local client = active_clients[client_id] and active_clients[client_id]
- or uninitialized_clients[client_id]
-
- for bufnr, client_ids in pairs(all_buffer_active_clients) do
- if client_ids[client_id] then
- vim.schedule(function()
- if client and client.attached_buffers[bufnr] then
- nvim_exec_autocmds('LspDetach', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client_id },
- })
- end
-
- local namespace = vim.lsp.diagnostic.get_namespace(client_id)
- vim.diagnostic.reset(namespace, bufnr)
-
- client_ids[client_id] = nil
- if vim.tbl_isempty(client_ids) then
- reset_defaults(bufnr)
- end
- end)
- end
- end
-
- -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
- -- autocommands
- vim.schedule(function()
- active_clients[client_id] = nil
- uninitialized_clients[client_id] = nil
-
- -- Client can be absent if executable starts, but initialize fails
- -- init/attach won't have happened
- if client then
- changetracking.reset(client)
- end
- if code ~= 0 or (signal ~= 0 and signal ~= 15) then
- local msg = string.format(
- 'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
- name,
- code,
- signal,
- lsp.get_log_path()
- )
- vim.notify(msg, vim.log.levels.WARN)
- end
- end)
- end
-
- -- Start the RPC client.
- local rpc --- @type vim.lsp.rpc.PublicClient?
- if type(cmd) == 'function' then
- rpc = cmd(dispatch)
- else
- rpc = lsp.rpc.start(cmd, cmd_args, dispatch, {
- cwd = config.cmd_cwd,
- env = config.cmd_env,
- detached = config.detached,
- })
- end
-
- -- Return nil if client fails to start
- if not rpc then
+ if not client then
return
end
- ---@class lsp.Client
- local client = {
- id = client_id,
- name = name,
- rpc = rpc,
- offset_encoding = offset_encoding,
- config = config,
- attached_buffers = {}, --- @type table<integer,true>
-
- handlers = handlers,
- --- @type table<string,function>
- commands = config.commands or {},
-
- --- @type table<integer,{ type: string, bufnr: integer, method: string}>
- requests = {},
-
- --- Contains $/progress report messages.
- --- They have the format {token: integer|string, value: any}
- --- For "work done progress", value will be one of:
- --- - lsp.WorkDoneProgressBegin,
- --- - lsp.WorkDoneProgressReport (extended with title from Begin)
- --- - lsp.WorkDoneProgressEnd (extended with title from Begin)
- progress = vim.ringbuf(50),
-
- --- @type lsp.ServerCapabilities
- server_capabilities = {},
-
- ---@deprecated use client.progress instead
- messages = { name = name, messages = {}, progress = {}, status = {} },
- dynamic_capabilities = vim.lsp._dynamic.new(client_id),
- }
-
- ---@type table<string|integer, string> title of unfinished progress sequences by token
- client.progress.pending = {}
-
- --- @type lsp.ClientCapabilities
- client.config.capabilities = config.capabilities or protocol.make_client_capabilities()
-
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
- uninitialized_clients[client_id] = client
-
- local function initialize()
- local valid_traces = {
- off = 'off',
- messages = 'messages',
- verbose = 'verbose',
- }
-
- local workspace_folders --- @type lsp.WorkspaceFolder[]?
- local root_uri --- @type string?
- local root_path --- @type string?
- if config.workspace_folders or config.root_dir then
- if config.root_dir and not config.workspace_folders then
- workspace_folders = {
- {
- uri = vim.uri_from_fname(config.root_dir),
- name = string.format('%s', config.root_dir),
- },
- }
- else
- workspace_folders = config.workspace_folders
- end
- root_uri = workspace_folders[1].uri
- root_path = vim.uri_to_fname(root_uri)
- else
- workspace_folders = nil
- root_uri = nil
- root_path = nil
- end
-
- local initialize_params = {
- -- The process Id of the parent process that started the server. Is null if
- -- the process has not been started by another process. If the parent
- -- process is not alive then the server should exit (see exit notification)
- -- its process.
- processId = uv.os_getpid(),
- -- Information about the client
- -- since 3.15.0
- clientInfo = {
- name = 'Neovim',
- version = tostring(vim.version()),
- },
- -- The rootPath of the workspace. Is null if no folder is open.
- --
- -- @deprecated in favour of rootUri.
- rootPath = root_path or vim.NIL,
- -- The rootUri of the workspace. Is null if no folder is open. If both
- -- `rootPath` and `rootUri` are set `rootUri` wins.
- rootUri = root_uri or vim.NIL,
- -- The workspace folders configured in the client when the server starts.
- -- This property is only available if the client supports workspace folders.
- -- It can be `null` if the client supports workspace folders but none are
- -- configured.
- workspaceFolders = workspace_folders or vim.NIL,
- -- User provided initialization options.
- initializationOptions = config.init_options,
- -- The capabilities provided by the client (editor or tool)
- capabilities = config.capabilities,
- -- The initial trace setting. If omitted trace is disabled ("off").
- -- trace = "off" | "messages" | "verbose";
- trace = valid_traces[config.trace] or 'off',
- }
- if config.before_init then
- local status, err = pcall(config.before_init, initialize_params, config)
- if not status then
- write_error(lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR, err)
- end
- end
-
- --- @param method string
- --- @param opts? {bufnr: integer?}
- client.supports_method = function(method, opts)
- opts = opts or {}
- local required_capability = lsp._request_name_to_capability[method]
- -- if we don't know about the method, assume that the client supports it.
- if not required_capability then
- return true
- end
- if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then
- return true
- else
- if client.dynamic_capabilities:supports_registration(method) then
- return client.dynamic_capabilities:supports(method, opts)
- end
- return false
- end
- end
-
- if log.trace() then
- log.trace(log_prefix, 'initialize_params', initialize_params)
- end
- rpc.request('initialize', initialize_params, function(init_err, result)
- assert(not init_err, tostring(init_err))
- assert(result, 'server sent empty result')
- rpc.notify('initialized', vim.empty_dict())
- client.initialized = true
- uninitialized_clients[client_id] = nil
- client.workspace_folders = workspace_folders
-
- -- These are the cleaned up capabilities we use for dynamically deciding
- -- when to send certain events to clients.
- client.server_capabilities =
- assert(result.capabilities, "initialize result doesn't contain capabilities")
- client.server_capabilities = assert(protocol.resolve_capabilities(client.server_capabilities))
-
- if client.server_capabilities.positionEncoding then
- client.offset_encoding = client.server_capabilities.positionEncoding
- end
+ -- TODO(lewis6991): do this on before_init(). Requires API change to before_init() so it
+ -- can access the client_id.
+ uninitialized_clients[client.id] = client
- if next(config.settings) then
- client.notify(ms.workspace_didChangeConfiguration, { settings = config.settings })
- end
-
- if config.on_init then
- local status, err = pcall(config.on_init, client, result)
- if not status then
- write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
- end
- end
- if log.info() then
- log.info(
- log_prefix,
- 'server_capabilities',
- { server_capabilities = client.server_capabilities }
- )
- end
-
- -- Only assign after initialized.
- active_clients[client_id] = client
- -- If we had been registered before we start, then send didOpen This can
- -- happen if we attach to buffers before initialize finishes or if
- -- someone restarts a client.
- for bufnr, client_ids in pairs(all_buffer_active_clients) do
- if client_ids[client_id] then
- client._on_attach(bufnr)
- end
- end
- end)
- end
-
- ---@nodoc
- --- Sends a request to the server.
- ---
- --- This is a thin wrapper around {client.rpc.request} with some additional
- --- checks for capabilities and handler availability.
- ---
- ---@param method string LSP method name.
- ---@param params table|nil LSP request params.
- ---@param handler lsp.Handler|nil Response |lsp-handler| for this method.
- ---@param bufnr integer Buffer handle (0 for current).
- ---@return boolean status, integer|nil request_id {status} is a bool indicating
- ---whether the request was successful. If it is `false`, then it will
- ---always be `false` (the client has shutdown). If it was
- ---successful, then it will return {request_id} as the
- ---second result. You can use this with `client.cancel_request(request_id)`
- ---to cancel the-request.
- ---@see |vim.lsp.buf_request_all()|
- function client.request(method, params, handler, bufnr)
- if not handler then
- handler = assert(
- resolve_handler(method),
- string.format('not found: %q request handler for client %q.', method, client.name)
- )
- end
- -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
- changetracking.flush(client, bufnr)
- local version = util.buf_versions[bufnr]
- bufnr = resolve_bufnr(bufnr)
- if log.debug() then
- log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
- end
- local success, request_id = rpc.request(method, params, function(err, result)
- local context = {
- method = method,
- client_id = client_id,
- bufnr = bufnr,
- params = params,
- version = version,
- }
- handler(err, result, context)
- end, function(request_id)
- local request = client.requests[request_id]
- request.type = 'complete'
- nvim_exec_autocmds('LspRequest', {
- buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
- modeline = false,
- data = { client_id = client_id, request_id = request_id, request = request },
- })
- client.requests[request_id] = nil
- end)
-
- if success and request_id then
- local request = { type = 'pending', bufnr = bufnr, method = method }
- client.requests[request_id] = request
- nvim_exec_autocmds('LspRequest', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client_id, request_id = request_id, request = request },
- })
- end
-
- return success, request_id
- end
-
- ---@private
- --- Sends a request to the server and synchronously waits for the response.
- ---
- --- This is a wrapper around {client.request}
- ---
- ---@param method (string) LSP method name.
- ---@param params (table) LSP request params.
- ---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for
- --- a result. Defaults to 1000
- ---@param bufnr (integer) Buffer handle (0 for current).
- ---@return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
- --- `err` and `result` come from the |lsp-handler|.
- --- On timeout, cancel or error, returns `(nil, err)` where `err` is a
- --- string describing the failure reason. If the request was unsuccessful
- --- returns `nil`.
- ---@see |vim.lsp.buf_request_sync()|
- function client.request_sync(method, params, timeout_ms, bufnr)
- local request_result = nil
- local function _sync_handler(err, result)
- request_result = { err = err, result = result }
- end
-
- local success, request_id = client.request(method, params, _sync_handler, bufnr)
- if not success then
- return nil
- end
-
- local wait_result, reason = vim.wait(timeout_ms or 1000, function()
- return request_result ~= nil
- end, 10)
-
- if not wait_result then
- if request_id then
- client.cancel_request(request_id)
- end
- return nil, wait_result_reason[reason]
- end
- return request_result
- end
-
- ---@nodoc
- --- Sends a notification to an LSP server.
- ---
- ---@param method string LSP method name.
- ---@param params table|nil LSP request params.
- ---@return boolean status true if the notification was successful.
- ---If it is false, then it will always be false
- ---(the client has shutdown).
- function client.notify(method, params)
- if method ~= ms.textDocument_didChange then
- changetracking.flush(client)
- end
-
- local client_active = rpc.notify(method, params)
-
- if client_active then
- vim.schedule(function()
- nvim_exec_autocmds('LspNotify', {
- modeline = false,
- data = {
- client_id = client.id,
- method = method,
- params = params,
- },
- })
- end)
- end
-
- return client_active
- end
-
- ---@nodoc
- --- Cancels a request with a given request id.
- ---
- ---@param id (integer) id of request to cancel
- ---@return boolean status true if notification was successful. false otherwise
- ---@see |vim.lsp.client.notify()|
- function client.cancel_request(id)
- validate({ id = { id, 'n' } })
- local request = client.requests[id]
- if request and request.type == 'pending' then
- request.type = 'cancel'
- nvim_exec_autocmds('LspRequest', {
- buffer = request.bufnr,
- modeline = false,
- data = { client_id = client_id, request_id = id, request = request },
- })
- end
- return rpc.notify(ms.dollar_cancelRequest, { id = id })
- end
-
- -- Track this so that we can escalate automatically if we've already tried a
- -- graceful shutdown
- local graceful_shutdown_failed = false
-
- ---@nodoc
- --- Stops a client, optionally with force.
- ---
- ---By default, it will just ask the - server to shutdown without force. If
- --- you request to stop a client which has previously been requested to
- --- shutdown, it will automatically escalate and force shutdown.
- ---
- ---@param force boolean|nil
- function client.stop(force)
- if rpc.is_closing() then
- return
- end
- if force or not client.initialized or graceful_shutdown_failed then
- rpc.terminate()
- return
- end
- -- Sending a signal after a process has exited is acceptable.
- rpc.request(ms.shutdown, nil, function(err, _)
- if err == nil then
- rpc.notify(ms.exit)
- else
- -- If there was an error in the shutdown request, then term to be safe.
- rpc.terminate()
- graceful_shutdown_failed = true
- end
- end)
- end
-
- ---@private
- --- Checks whether a client is stopped.
- ---
- ---@return boolean # true if client is stopped or in the process of being
- ---stopped; false otherwise
- function client.is_stopped()
- return rpc.is_closing()
- end
-
- ---@private
- --- Execute a lsp command, either via client command function (if available)
- --- or via workspace/executeCommand (if supported by the server)
- ---
- ---@param command lsp.Command
- ---@param context? {bufnr: integer}
- ---@param handler? lsp.Handler only called if a server command
- function client._exec_cmd(command, context, handler)
- context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]]
- context.bufnr = context.bufnr or api.nvim_get_current_buf()
- context.client_id = client.id
- local cmdname = command.command
- local fn = client.commands[cmdname] or lsp.commands[cmdname]
- if fn then
- fn(command, context)
- return
- end
-
- local command_provider = client.server_capabilities.executeCommandProvider
- local commands = type(command_provider) == 'table' and command_provider.commands or {}
- if not vim.list_contains(commands, cmdname) then
- vim.notify_once(
- string.format(
- 'Language server `%s` does not support command `%s`. This command may require a client extension.',
- client.name,
- cmdname
- ),
- vim.log.levels.WARN
- )
- return
- end
- -- Not using command directly to exclude extra properties,
- -- see https://github.com/python-lsp/python-lsp-server/issues/146
- local params = {
- command = command.command,
- arguments = command.arguments,
- }
- client.request(ms.workspace_executeCommand, params, handler, context.bufnr)
- end
-
- ---@private
- --- Runs the on_attach function from the client's config if it was defined.
- ---@param bufnr integer Buffer number
- function client._on_attach(bufnr)
- text_document_did_open_handler(bufnr, client)
-
- lsp._set_defaults(client, bufnr)
-
- nvim_exec_autocmds('LspAttach', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client.id },
- })
-
- if config.on_attach then
- local status, err = pcall(config.on_attach, client, bufnr)
- if not status then
- write_error(lsp.client_errors.ON_ATTACH_ERROR, err)
- end
- end
-
- -- schedule the initialization of semantic tokens to give the above
- -- on_attach and LspAttach callbacks the ability to schedule wrap the
- -- opt-out (deleting the semanticTokensProvider from capabilities)
- vim.schedule(function()
- if vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then
- lsp.semantic_tokens.start(bufnr, client.id)
- end
- end)
-
- client.attached_buffers[bufnr] = true
- end
-
- initialize()
-
- return client_id
+ return client.id
end
--- Notify all attached clients that a buffer has changed.
@@ -1505,9 +769,7 @@ function lsp.buf_attach_client(bufnr, client_id)
})
bufnr = resolve_bufnr(bufnr)
if not api.nvim_buf_is_loaded(bufnr) then
- if log.warn() then
- log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
- end
+ log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
return false
end
local buffer_client_ids = all_buffer_active_clients[bufnr]
@@ -1564,7 +826,7 @@ function lsp.buf_attach_client(bufnr, client_id)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
client.notify(ms.textDocument_didClose, params)
end
- text_document_did_open_handler(bufnr, client)
+ client:_text_document_did_open_handler(bufnr)
end
end,
on_detach = function()
@@ -1596,7 +858,7 @@ function lsp.buf_attach_client(bufnr, client_id)
-- Send didOpen for the client if it is initialized. If it isn't initialized
-- then it will send didOpen on initialize.
if client then
- client._on_attach(bufnr)
+ client:_on_attach(bufnr)
end
return true
end
@@ -1626,7 +888,7 @@ function lsp.buf_detach_client(bufnr, client_id)
return
end
- nvim_exec_autocmds('LspDetach', {
+ api.nvim_exec_autocmds('LspDetach', {
buffer = bufnr,
modeline = false,
data = { client_id = client_id },
@@ -1849,7 +1111,7 @@ function lsp.buf_request(bufnr, method, params, handler)
-- if has client but no clients support the given method, notify the user
if next(clients) and not method_supported then
vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR)
- nvim_command('redraw')
+ vim.cmd.redraw()
return {}, function() end
end
@@ -1967,9 +1229,7 @@ end
--- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base)
- if log.debug() then
- log.debug('omnifunc.findstart', { findstart = findstart, base = base })
- end
+ log.debug('omnifunc.findstart', { findstart = findstart, base = base })
return vim.lsp._completion.omnifunc(findstart, base)
end
diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua
index 67c74f069d..8b624cd5ea 100644
--- a/runtime/lua/vim/lsp/_changetracking.lua
+++ b/runtime/lua/vim/lsp/_changetracking.lua
@@ -64,7 +64,7 @@ local state_by_group = setmetatable({}, {
---@param client lsp.Client
---@return vim.lsp.CTGroup
local function get_group(client)
- local allow_inc_sync = vim.F.if_nil(client.config.flags.allow_incremental_sync, true)
+ local allow_inc_sync = vim.F.if_nil(client.config.flags.allow_incremental_sync, true) --- @type boolean
local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua
index 3c9dee2c69..b6c335bb13 100644
--- a/runtime/lua/vim/lsp/_dynamic.lua
+++ b/runtime/lua/vim/lsp/_dynamic.lua
@@ -6,6 +6,7 @@ local glob = vim.glob
local M = {}
--- @param client_id number
+--- @return lsp.DynamicCapabilities
function M.new(client_id)
return setmetatable({
capabilities = {},
@@ -23,7 +24,7 @@ function M:supports_registration(method)
end
--- @param registrations lsp.Registration[]
---- @private
+--- @package
function M:register(registrations)
-- remove duplicates
self:unregister(registrations)
@@ -37,7 +38,7 @@ function M:register(registrations)
end
--- @param unregisterations lsp.Unregistration[]
---- @private
+--- @package
function M:unregister(unregisterations)
for _, unreg in ipairs(unregisterations) do
local method = unreg.method
@@ -77,7 +78,7 @@ end
--- @param method string
--- @param opts? {bufnr: integer?}
---- @private
+--- @package
function M:supports(method, opts)
return self:get(method, opts) ~= nil
end
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index d67b2ac8ea..7fc5286a78 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -652,7 +652,7 @@ local function on_code_action_results(results, opts)
end
if action.command then
local command = type(action.command) == 'table' and action.command or action
- client._exec_cmd(command, ctx)
+ client:_exec_cmd(command, ctx)
end
end
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
new file mode 100644
index 0000000000..a279be55e9
--- /dev/null
+++ b/runtime/lua/vim/lsp/client.lua
@@ -0,0 +1,877 @@
+local uv = vim.uv
+local api = vim.api
+local lsp = vim.lsp
+local log = lsp.log
+local ms = lsp.protocol.Methods
+local changetracking = lsp._changetracking
+local validate = vim.validate
+
+--- @class lsp.ClientConfig
+--- @field cmd (string[]|fun(dispatchers: table):table)
+--- @field cmd_cwd string
+--- @field cmd_env (table)
+--- @field detached boolean
+--- @field workspace_folders (table)
+--- @field capabilities lsp.ClientCapabilities
+--- @field handlers table<string,function>
+--- @field settings table
+--- @field commands table
+--- @field init_options table
+--- @field name? string
+--- @field get_language_id fun(bufnr: integer, filetype: string): string
+--- @field offset_encoding string
+--- @field on_error fun(code: integer)
+--- @field before_init fun(params: lsp.InitializeParams, config: lsp.ClientConfig)
+--- @field on_init fun(client: lsp.Client, initialize_result: lsp.InitializeResult)
+--- @field on_exit fun(code: integer, signal: integer, client_id: integer)
+--- @field on_attach fun(client: lsp.Client, bufnr: integer)
+--- @field trace 'off'|'messages'|'verbose'|nil
+--- @field flags table
+--- @field root_dir string
+
+--- @class lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
+--- @field pending table<lsp.ProgressToken,lsp.LSPAny>
+
+--- @class lsp.Client
+---
+--- The id allocated to the client.
+--- @field id integer
+---
+--- If a name is specified on creation, that will be used. Otherwise it is just
+--- the client id. This is used for logs and messages.
+--- @field name string
+---
+--- RPC client object, for low level interaction with the client.
+--- See |vim.lsp.rpc.start()|.
+--- @field rpc vim.lsp.rpc.PublicClient
+---
+--- The encoding used for communicating with the server. You can modify this in
+--- the `config`'s `on_init` method before text is sent to the server.
+--- @field offset_encoding string
+---
+--- The handlers used by the client as described in |lsp-handler|.
+--- @field handlers table<string,lsp.Handler>
+---
+--- The current pending requests in flight to the server. Entries are key-value
+--- pairs with the key being the request ID while the value is a table with
+--- `type`, `bufnr`, and `method` key-value pairs. `type` is either "pending"
+--- for an active request, or "cancel" for a cancel request. It will be
+--- "complete" ephemerally while executing |LspRequest| autocmds when replies
+--- are received from the server.
+--- @field requests table<integer,{ type: string, bufnr: integer, method: string}>
+---
+--- copy of the table that was passed by the user
+--- to |vim.lsp.start_client()|.
+--- @field config lsp.ClientConfig
+---
+--- Response from the server sent on
+--- initialize` describing the server's capabilities.
+--- @field server_capabilities lsp.ServerCapabilities
+---
+--- A ring buffer (|vim.ringbuf()|) containing progress messages
+--- sent by the server.
+--- @field progress lsp.Client.Progress
+---
+--- @field initialized true?
+--- @field workspace_folders lsp.WorkspaceFolder[]?
+--- @field attached_buffers table<integer,true>
+--- @field private _log_prefix string
+--- Track this so that we can escalate automatically if we've already tried a
+--- graceful shutdown
+--- @field private _graceful_shutdown_failed true?
+--- @field private commands table
+---
+--- @field dynamic_capabilities lsp.DynamicCapabilities
+---
+--- Sends a request to the server.
+--- This is a thin wrapper around {client.rpc.request} with some additional
+--- checking.
+--- If {handler} is not specified, If one is not found there, then an error
+--- will occur. Returns: {status}, {[client_id]}. {status} is a boolean
+--- indicating if the notification was successful. If it is `false`, then it
+--- will always be `false` (the client has shutdown).
+--- If {status} is `true`, the function returns {request_id} as the second
+--- result. You can use this with `client.cancel_request(request_id)` to cancel
+--- the request.
+--- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer): boolean, integer?
+---
+--- Sends a request to the server and synchronously waits for the response.
+--- This is a wrapper around {client.request}
+--- Returns: { err=err, result=result }, a dictionary, where `err` and `result`
+--- come from the |lsp-handler|. On timeout, cancel or error, returns `(nil,
+--- err)` where `err` is a string describing the failure reason. If the request
+--- was unsuccessful returns `nil`.
+--- @field request_sync fun(method: string, params: table?, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
+---
+--- Sends a notification to an LSP server.
+--- Returns: a boolean to indicate if the notification was successful. If
+--- it is false, then it will always be false (the client has shutdown).
+--- @field notify fun(method: string, params: table?): boolean
+---
+--- Cancels a request with a given request id.
+--- Returns: same as `notify()`.
+--- @field cancel_request fun(id: integer): boolean
+---
+--- Stops a client, optionally with force.
+--- By default, it will just ask the server to shutdown without force.
+--- If you request to stop a client which has previously been requested to
+--- shutdown, it will automatically escalate and force shutdown.
+--- @field stop fun(force?: boolean)
+---
+--- Runs the on_attach function from the client's config if it was defined.
+--- Useful for buffer-local setup.
+--- @field on_attach fun(bufnr: integer)
+---
+--- Checks if a client supports a given method.
+--- Always returns true for unknown off-spec methods.
+--- [opts] is a optional `{bufnr?: integer}` table.
+--- Some language server capabilities can be file specific.
+--- @field supports_method fun(method: string, opts?: {bufnr: integer?}): boolean
+---
+--- Checks whether a client is stopped.
+--- Returns: true if the client is fully stopped.
+--- @field is_stopped fun(): boolean
+local Client = {}
+Client.__index = Client
+
+--- @param cls table
+--- @param meth any
+--- @return function
+local function method_wrapper(cls, meth)
+ return function(...)
+ return meth(cls, ...)
+ end
+end
+
+local client_index = 0
+
+--- Checks whether a given path is a directory.
+--- @param filename (string) path to check
+--- @return boolean # true if {filename} exists and is a directory, false otherwise
+local function is_dir(filename)
+ validate({ filename = { filename, 's' } })
+ local stat = uv.fs_stat(filename)
+ return stat and stat.type == 'directory' or false
+end
+
+local valid_encodings = {
+ ['utf-8'] = 'utf-8',
+ ['utf-16'] = 'utf-16',
+ ['utf-32'] = 'utf-32',
+ ['utf8'] = 'utf-8',
+ ['utf16'] = 'utf-16',
+ ['utf32'] = 'utf-32',
+ UTF8 = 'utf-8',
+ UTF16 = 'utf-16',
+ UTF32 = 'utf-32',
+}
+
+--- Normalizes {encoding} to valid LSP encoding names.
+--- @param encoding string? Encoding to normalize
+--- @return string # normalized encoding name
+local function validate_encoding(encoding)
+ validate({
+ encoding = { encoding, 's', true },
+ })
+ if not encoding then
+ return valid_encodings.UTF16
+ end
+ return valid_encodings[encoding:lower()]
+ or error(
+ string.format(
+ "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'",
+ encoding
+ )
+ )
+end
+
+--- Augments a validator function with support for optional (nil) values.
+--- @param fn (fun(v): boolean) The original validator function; should return a
+--- bool.
+--- @return fun(v): boolean # The augmented function. Also returns true if {v} is
+--- `nil`.
+local function optional_validator(fn)
+ return function(v)
+ return v == nil or fn(v)
+ end
+end
+
+--- Validates a client configuration as given to |vim.lsp.start_client()|.
+--- @param config lsp.ClientConfig
+local function process_client_config(config)
+ validate({
+ config = { config, 't' },
+ })
+ validate({
+ handlers = { config.handlers, 't', true },
+ capabilities = { config.capabilities, 't', true },
+ cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
+ cmd_env = { config.cmd_env, 't', true },
+ detached = { config.detached, 'b', true },
+ name = { config.name, 's', true },
+ on_error = { config.on_error, 'f', true },
+ on_exit = { config.on_exit, 'f', true },
+ on_init = { config.on_init, 'f', true },
+ settings = { config.settings, 't', true },
+ commands = { config.commands, 't', true },
+ before_init = { config.before_init, 'f', true },
+ offset_encoding = { config.offset_encoding, 's', true },
+ flags = { config.flags, 't', true },
+ get_language_id = { config.get_language_id, 'f', true },
+ })
+ assert(
+ (
+ not config.flags
+ or not config.flags.debounce_text_changes
+ or type(config.flags.debounce_text_changes) == 'number'
+ ),
+ 'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
+ )
+
+ if not config.name and type(config.cmd) == 'table' then
+ config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil
+ end
+
+ config.offset_encoding = validate_encoding(config.offset_encoding)
+ config.flags = config.flags or {}
+ config.settings = config.settings or {}
+ config.handlers = config.handlers or {}
+
+ -- By default, get_language_id just returns the exact filetype it is passed.
+ -- It is possible to pass in something that will calculate a different filetype,
+ -- to be sent by the client.
+ config.get_language_id = config.get_language_id or function(_, filetype)
+ return filetype
+ end
+
+ config.capabilities = config.capabilities or lsp.protocol.make_client_capabilities()
+ config.commands = config.commands or {}
+end
+
+--- @package
+--- @param config lsp.ClientConfig
+--- @return lsp.Client?
+function Client.start(config)
+ process_client_config(config)
+
+ client_index = client_index + 1
+ local id = client_index
+
+ local name = config.name or tostring(id)
+
+ --- @class lsp.Client
+ local self = {
+ id = id,
+ config = config,
+ handlers = config.handlers,
+ offset_encoding = config.offset_encoding,
+ name = name,
+ _log_prefix = string.format('LSP[%s]', name),
+ requests = {},
+ attached_buffers = {},
+ server_capabilities = {},
+ dynamic_capabilities = vim.lsp._dynamic.new(id),
+ commands = config.commands, -- Remove in Nvim 0.11
+
+ --- Contains $/progress report messages.
+ --- They have the format {token: integer|string, value: any}
+ --- For "work done progress", value will be one of:
+ --- - lsp.WorkDoneProgressBegin,
+ --- - lsp.WorkDoneProgressReport (extended with title from Begin)
+ --- - lsp.WorkDoneProgressEnd (extended with title from Begin)
+ progress = vim.ringbuf(50) --[[@as lsp.Client.Progress]],
+
+ --- @deprecated use client.progress instead
+ messages = { name = name, messages = {}, progress = {}, status = {} },
+ }
+
+ self.request = method_wrapper(self, Client._request)
+ self.request_sync = method_wrapper(self, Client._request_sync)
+ self.notify = method_wrapper(self, Client._notify)
+ self.cancel_request = method_wrapper(self, Client._cancel_request)
+ self.stop = method_wrapper(self, Client._stop)
+ self.is_stopped = method_wrapper(self, Client._is_stopped)
+ self.on_attach = method_wrapper(self, Client._on_attach)
+ self.supports_method = method_wrapper(self, Client._supports_method)
+
+ --- @type table<string|integer, string> title of unfinished progress sequences by token
+ self.progress.pending = {}
+
+ --- @type vim.lsp.rpc.Dispatchers
+ local dispatchers = {
+ notification = method_wrapper(self, Client._notification),
+ server_request = method_wrapper(self, Client._server_request),
+ on_error = method_wrapper(self, Client._on_error),
+ on_exit = method_wrapper(self, Client._on_exit),
+ }
+
+ -- Start the RPC client.
+ local rpc --- @type vim.lsp.rpc.PublicClient?
+ local config_cmd = config.cmd
+ if type(config_cmd) == 'function' then
+ rpc = config_cmd(dispatchers)
+ else
+ rpc = lsp.rpc.start(config_cmd, dispatchers, {
+ cwd = config.cmd_cwd,
+ env = config.cmd_env,
+ detached = config.detached,
+ })
+ end
+
+ -- Return nil if the rpc client fails to start
+ if not rpc then
+ return
+ end
+
+ self.rpc = rpc
+
+ setmetatable(self, Client)
+
+ self:initialize()
+
+ return self
+end
+
+--- @private
+function Client:initialize()
+ local valid_traces = {
+ off = 'off',
+ messages = 'messages',
+ verbose = 'verbose',
+ }
+
+ local config = self.config
+
+ local workspace_folders --- @type lsp.WorkspaceFolder[]?
+ local root_uri --- @type string?
+ local root_path --- @type string?
+ if config.workspace_folders or config.root_dir then
+ if config.root_dir and not config.workspace_folders then
+ workspace_folders = {
+ {
+ uri = vim.uri_from_fname(config.root_dir),
+ name = string.format('%s', config.root_dir),
+ },
+ }
+ else
+ workspace_folders = config.workspace_folders
+ end
+ root_uri = workspace_folders[1].uri
+ root_path = vim.uri_to_fname(root_uri)
+ else
+ workspace_folders = nil
+ root_uri = nil
+ root_path = nil
+ end
+
+ local initialize_params = {
+ -- The process Id of the parent process that started the server. Is null if
+ -- the process has not been started by another process. If the parent
+ -- process is not alive then the server should exit (see exit notification)
+ -- its process.
+ processId = uv.os_getpid(),
+ -- Information about the client
+ -- since 3.15.0
+ clientInfo = {
+ name = 'Neovim',
+ version = tostring(vim.version()),
+ },
+ -- The rootPath of the workspace. Is null if no folder is open.
+ --
+ -- @deprecated in favour of rootUri.
+ rootPath = root_path or vim.NIL,
+ -- The rootUri of the workspace. Is null if no folder is open. If both
+ -- `rootPath` and `rootUri` are set `rootUri` wins.
+ rootUri = root_uri or vim.NIL,
+ -- The workspace folders configured in the client when the server starts.
+ -- This property is only available if the client supports workspace folders.
+ -- It can be `null` if the client supports workspace folders but none are
+ -- configured.
+ workspaceFolders = workspace_folders or vim.NIL,
+ -- User provided initialization options.
+ initializationOptions = config.init_options,
+ -- The capabilities provided by the client (editor or tool)
+ capabilities = config.capabilities,
+ -- The initial trace setting. If omitted trace is disabled ("off").
+ -- trace = "off" | "messages" | "verbose";
+ trace = valid_traces[config.trace] or 'off',
+ }
+ if config.before_init then
+ --- @type boolean, string?
+ local status, err = pcall(config.before_init, initialize_params, config)
+ if not status then
+ self:write_error(lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR, err)
+ end
+ end
+
+ log.trace(self._log_prefix, 'initialize_params', initialize_params)
+
+ local rpc = self.rpc
+
+ rpc.request('initialize', initialize_params, function(init_err, result)
+ assert(not init_err, tostring(init_err))
+ assert(result, 'server sent empty result')
+ rpc.notify('initialized', vim.empty_dict())
+ self.initialized = true
+ self.workspace_folders = workspace_folders
+
+ -- These are the cleaned up capabilities we use for dynamically deciding
+ -- when to send certain events to clients.
+ self.server_capabilities =
+ assert(result.capabilities, "initialize result doesn't contain capabilities")
+ self.server_capabilities = assert(lsp.protocol.resolve_capabilities(self.server_capabilities))
+
+ if self.server_capabilities.positionEncoding then
+ self.offset_encoding = self.server_capabilities.positionEncoding
+ end
+
+ if next(config.settings) then
+ self:_notify(ms.workspace_didChangeConfiguration, { settings = config.settings })
+ end
+
+ if config.on_init then
+ --- @type boolean, string?
+ local status, err = pcall(config.on_init, self, result)
+ if not status then
+ self:write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
+ end
+ end
+
+ log.info(
+ self._log_prefix,
+ 'server_capabilities',
+ { server_capabilities = self.server_capabilities }
+ )
+ end)
+end
+
+--- @private
+--- Returns the handler associated with an LSP method.
+--- Returns the default handler if the user hasn't set a custom one.
+---
+--- @param method (string) LSP method name
+--- @return lsp.Handler|nil handler for the given method, if defined, or the default from |vim.lsp.handlers|
+function Client:_resolve_handler(method)
+ return self.handlers[method] or lsp.handlers[method]
+end
+
+--- Returns the buffer number for the given {bufnr}.
+---
+--- @param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
+--- @return integer bufnr
+local function resolve_bufnr(bufnr)
+ validate({ bufnr = { bufnr, 'n', true } })
+ if bufnr == nil or bufnr == 0 then
+ return api.nvim_get_current_buf()
+ end
+ return bufnr
+end
+
+--- @private
+--- Sends a request to the server.
+---
+--- This is a thin wrapper around {client.rpc.request} with some additional
+--- checks for capabilities and handler availability.
+---
+--- @param method string LSP method name.
+--- @param params table|nil LSP request params.
+--- @param handler lsp.Handler|nil Response |lsp-handler| for this method.
+--- @param bufnr integer Buffer handle (0 for current).
+--- @return boolean status, integer|nil request_id {status} is a bool indicating
+--- whether the request was successful. If it is `false`, then it will
+--- always be `false` (the client has shutdown). If it was
+--- successful, then it will return {request_id} as the
+--- second result. You can use this with `client.cancel_request(request_id)`
+--- to cancel the-request.
+--- @see |vim.lsp.buf_request_all()|
+function Client:_request(method, params, handler, bufnr)
+ if not handler then
+ handler = assert(
+ self:_resolve_handler(method),
+ string.format('not found: %q request handler for client %q.', method, self.name)
+ )
+ end
+ -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
+ changetracking.flush(self, bufnr)
+ local version = lsp.util.buf_versions[bufnr]
+ bufnr = resolve_bufnr(bufnr)
+ log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr)
+ local success, request_id = self.rpc.request(method, params, function(err, result)
+ local context = {
+ method = method,
+ client_id = self.id,
+ bufnr = bufnr,
+ params = params,
+ version = version,
+ }
+ handler(err, result, context)
+ end, function(request_id)
+ local request = self.requests[request_id]
+ request.type = 'complete'
+ api.nvim_exec_autocmds('LspRequest', {
+ buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
+ modeline = false,
+ data = { client_id = self.id, request_id = request_id, request = request },
+ })
+ self.requests[request_id] = nil
+ end)
+
+ if success and request_id then
+ local request = { type = 'pending', bufnr = bufnr, method = method }
+ self.requests[request_id] = request
+ api.nvim_exec_autocmds('LspRequest', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = self.id, request_id = request_id, request = request },
+ })
+ end
+
+ return success, request_id
+end
+
+-- TODO(lewis6991): duplicated from lsp.lua
+local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
+
+--- Concatenates and writes a list of strings to the Vim error buffer.
+---
+--- @param ... string List to write to the buffer
+local function err_message(...)
+ api.nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
+ api.nvim_command('redraw')
+end
+
+--- @private
+--- Sends a request to the server and synchronously waits for the response.
+---
+--- This is a wrapper around {client.request}
+---
+--- @param method (string) LSP method name.
+--- @param params (table) LSP request params.
+--- @param timeout_ms (integer|nil) Maximum time in milliseconds to wait for
+--- a result. Defaults to 1000
+--- @param bufnr (integer) Buffer handle (0 for current).
+--- @return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
+--- `err` and `result` come from the |lsp-handler|.
+--- On timeout, cancel or error, returns `(nil, err)` where `err` is a
+--- string describing the failure reason. If the request was unsuccessful
+--- returns `nil`.
+--- @see |vim.lsp.buf_request_sync()|
+function Client:_request_sync(method, params, timeout_ms, bufnr)
+ local request_result = nil
+ local function _sync_handler(err, result)
+ request_result = { err = err, result = result }
+ end
+
+ local success, request_id = self:_request(method, params, _sync_handler, bufnr)
+ if not success then
+ return nil
+ end
+
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_result ~= nil
+ end, 10)
+
+ if not wait_result then
+ if request_id then
+ self:_cancel_request(request_id)
+ end
+ return nil, wait_result_reason[reason]
+ end
+ return request_result
+end
+
+--- @private
+--- Sends a notification to an LSP server.
+---
+--- @param method string LSP method name.
+--- @param params table|nil LSP request params.
+--- @return boolean status true if the notification was successful.
+--- If it is false, then it will always be false
+--- (the client has shutdown).
+function Client:_notify(method, params)
+ if method ~= ms.textDocument_didChange then
+ changetracking.flush(self)
+ end
+
+ local client_active = self.rpc.notify(method, params)
+
+ if client_active then
+ vim.schedule(function()
+ api.nvim_exec_autocmds('LspNotify', {
+ modeline = false,
+ data = {
+ client_id = self.id,
+ method = method,
+ params = params,
+ },
+ })
+ end)
+ end
+
+ return client_active
+end
+
+--- @private
+--- Cancels a request with a given request id.
+---
+--- @param id (integer) id of request to cancel
+--- @return boolean status true if notification was successful. false otherwise
+--- @see |vim.lsp.client.notify()|
+function Client:_cancel_request(id)
+ validate({ id = { id, 'n' } })
+ local request = self.requests[id]
+ if request and request.type == 'pending' then
+ request.type = 'cancel'
+ api.nvim_exec_autocmds('LspRequest', {
+ buffer = request.bufnr,
+ modeline = false,
+ data = { client_id = self.id, request_id = id, request = request },
+ })
+ end
+ return self.rpc.notify(ms.dollar_cancelRequest, { id = id })
+end
+
+--- @nodoc
+--- Stops a client, optionally with force.
+---
+--- By default, it will just ask the - server to shutdown without force. If
+--- you request to stop a client which has previously been requested to
+--- shutdown, it will automatically escalate and force shutdown.
+---
+--- @param force boolean|nil
+function Client:_stop(force)
+ local rpc = self.rpc
+
+ if rpc.is_closing() then
+ return
+ end
+
+ if force or not self.initialized or self._graceful_shutdown_failed then
+ rpc.terminate()
+ return
+ end
+
+ -- Sending a signal after a process has exited is acceptable.
+ rpc.request(ms.shutdown, nil, function(err, _)
+ if err == nil then
+ rpc.notify(ms.exit)
+ else
+ -- If there was an error in the shutdown request, then term to be safe.
+ rpc.terminate()
+ self._graceful_shutdown_failed = true
+ end
+ end)
+end
+
+--- @private
+--- Checks whether a client is stopped.
+---
+--- @return boolean # true if client is stopped or in the process of being
+--- stopped; false otherwise
+function Client:_is_stopped()
+ return self.rpc.is_closing()
+end
+
+--- @private
+--- Execute a lsp command, either via client command function (if available)
+--- or via workspace/executeCommand (if supported by the server)
+---
+--- @param command lsp.Command
+--- @param context? {bufnr: integer}
+--- @param handler? lsp.Handler only called if a server command
+function Client:_exec_cmd(command, context, handler)
+ context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]]
+ context.bufnr = context.bufnr or api.nvim_get_current_buf()
+ context.client_id = self.id
+ local cmdname = command.command
+ local fn = self.config.commands[cmdname] or lsp.commands[cmdname]
+ if fn then
+ fn(command, context)
+ return
+ end
+
+ local command_provider = self.server_capabilities.executeCommandProvider
+ local commands = type(command_provider) == 'table' and command_provider.commands or {}
+ if not vim.list_contains(commands, cmdname) then
+ vim.notify_once(
+ string.format(
+ 'Language server `%s` does not support command `%s`. This command may require a client extension.',
+ self.name,
+ cmdname
+ ),
+ vim.log.levels.WARN
+ )
+ return
+ end
+ -- Not using command directly to exclude extra properties,
+ -- see https://github.com/python-lsp/python-lsp-server/issues/146
+ local params = {
+ command = command.command,
+ arguments = command.arguments,
+ }
+ self.request(ms.workspace_executeCommand, params, handler, context.bufnr)
+end
+
+--- @package
+--- Default handler for the 'textDocument/didOpen' LSP notification.
+---
+--- @param bufnr integer Number of the buffer, or 0 for current
+function Client:_text_document_did_open_handler(bufnr)
+ changetracking.init(self, bufnr)
+ if not vim.tbl_get(self.server_capabilities, 'textDocumentSync', 'openClose') then
+ return
+ end
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ local filetype = vim.bo[bufnr].filetype
+
+ local params = {
+ textDocument = {
+ version = 0,
+ uri = vim.uri_from_bufnr(bufnr),
+ languageId = self.config.get_language_id(bufnr, filetype),
+ text = lsp._buf_get_full_text(bufnr),
+ },
+ }
+ self.notify(ms.textDocument_didOpen, params)
+ lsp.util.buf_versions[bufnr] = params.textDocument.version
+
+ -- Next chance we get, we should re-do the diagnostics
+ vim.schedule(function()
+ -- Protect against a race where the buffer disappears
+ -- between `did_open_handler` and the scheduled function firing.
+ if api.nvim_buf_is_valid(bufnr) then
+ local namespace = vim.lsp.diagnostic.get_namespace(self.id)
+ vim.diagnostic.show(namespace, bufnr)
+ end
+ end)
+end
+
+--- @private
+--- Runs the on_attach function from the client's config if it was defined.
+--- @param bufnr integer Buffer number
+function Client:_on_attach(bufnr)
+ self:_text_document_did_open_handler(bufnr)
+
+ lsp._set_defaults(self, bufnr)
+
+ api.nvim_exec_autocmds('LspAttach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = self.id },
+ })
+
+ if self.config.on_attach then
+ --- @type boolean, string?
+ local status, err = pcall(self.config.on_attach, self, bufnr)
+ if not status then
+ self:write_error(lsp.client_errors.ON_ATTACH_ERROR, err)
+ end
+ end
+
+ -- schedule the initialization of semantic tokens to give the above
+ -- on_attach and LspAttach callbacks the ability to schedule wrap the
+ -- opt-out (deleting the semanticTokensProvider from capabilities)
+ vim.schedule(function()
+ if vim.tbl_get(self.server_capabilities, 'semanticTokensProvider', 'full') then
+ lsp.semantic_tokens.start(bufnr, self.id)
+ end
+ end)
+
+ self.attached_buffers[bufnr] = true
+end
+
+--- @private
+--- Logs the given error to the LSP log and to the error buffer.
+--- @param code integer Error code
+--- @param err any Error arguments
+function Client:write_error(code, err)
+ local client_error = lsp.client_errors[code] --- @type string|integer
+ log.error(self._log_prefix, 'on_error', { code = client_error, err = err })
+ err_message(self._log_prefix, ': Error ', client_error, ': ', vim.inspect(err))
+end
+
+--- @param method string
+--- @param opts? {bufnr: integer?}
+function Client:_supports_method(method, opts)
+ opts = opts or {}
+ local required_capability = lsp._request_name_to_capability[method]
+ -- if we don't know about the method, assume that the client supports it.
+ if not required_capability then
+ return true
+ end
+ if vim.tbl_get(self.server_capabilities, unpack(required_capability)) then
+ return true
+ else
+ if self.dynamic_capabilities:supports_registration(method) then
+ return self.dynamic_capabilities:supports(method, opts)
+ end
+ return false
+ end
+end
+
+--- @private
+--- Handles a notification sent by an LSP server by invoking the
+--- corresponding handler.
+---
+--- @param method string LSP method name
+--- @param params table The parameters for that method.
+function Client:_notification(method, params)
+ log.trace('notification', method, params)
+ local handler = self:_resolve_handler(method)
+ if handler then
+ -- Method name is provided here for convenience.
+ handler(nil, params, { method = method, client_id = self.id })
+ end
+end
+
+--- @private
+--- Handles a request from an LSP server by invoking the corresponding handler.
+---
+--- @param method (string) LSP method name
+--- @param params (table) The parameters for that method
+--- @return any result
+--- @return lsp.ResponseError error code and message set in case an exception happens during the request.
+function Client:_server_request(method, params)
+ log.trace('server_request', method, params)
+ local handler = self:_resolve_handler(method)
+ if handler then
+ log.trace('server_request: found handler for', method)
+ return handler(nil, params, { method = method, client_id = self.id })
+ end
+ log.warn('server_request: no handler found for', method)
+ return nil, lsp.rpc_response_error(lsp.protocol.ErrorCodes.MethodNotFound)
+end
+
+--- @private
+--- Invoked when the client operation throws an error.
+---
+--- @param code integer Error code
+--- @param err any Other arguments may be passed depending on the error kind
+--- @see vim.lsp.rpc.client_errors for possible errors. Use
+--- `vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
+function Client:_on_error(code, err)
+ self:write_error(code, err)
+ if self.config.on_error then
+ --- @type boolean, string
+ local status, usererr = pcall(self.config.on_error, code, err)
+ if not status then
+ log.error(self._log_prefix, 'user on_error failed', { err = usererr })
+ err_message(self._log_prefix, ' user on_error failed: ', tostring(usererr))
+ end
+ end
+end
+
+--- @private
+--- Invoked on client exit.
+---
+--- @param code integer) exit code of the process
+--- @param signal integer the signal used to terminate (if any)
+function Client:_on_exit(code, signal)
+ if self.config.on_exit then
+ pcall(self.config.on_exit, code, signal, self.id)
+ end
+end
+
+return Client
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 199da288f4..ab49e03c52 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -48,7 +48,8 @@ local function execute_lens(lens, bufnr, client_id)
local client = vim.lsp.get_client_by_id(client_id)
assert(client, 'Client is required to execute lens, client_id=' .. client_id)
- client._exec_cmd(lens.command, { bufnr = bufnr }, function(...)
+ ---@diagnostic disable-next-line: invisible
+ client:_exec_cmd(lens.command, { bufnr = bufnr }, function(...)
vim.lsp.handlers[ms.workspace_executeCommand](...)
M.refresh()
end)
@@ -111,7 +112,7 @@ end
--- Clear the lenses
---
---@param client_id integer|nil filter by client_id. All clients if nil
----@param bufnr integer|nil filter by buffer. All buffers if nil
+---@param bufnr integer|nil filter by buffer. All buffers if nil, 0 for current buffer
function M.clear(client_id, bufnr)
bufnr = bufnr and resolve_bufnr(bufnr)
local buffers = bufnr and { bufnr }
@@ -262,9 +263,7 @@ end
function M.on_codelens(err, result, ctx, _)
if err then
active_refreshes[assert(ctx.bufnr)] = nil
- if log.error() then
- log.error('codelens', err)
- end
+ log.error('codelens', err)
return
end
@@ -279,25 +278,36 @@ function M.on_codelens(err, result, ctx, _)
end)
end
---- Refresh the codelens for the current buffer
+--- @class vim.lsp.codelens.RefreshOptions
+--- @field bufnr integer? filter by buffer. All buffers if nil, 0 for current buffer
+
+--- Refresh the lenses.
---
--- It is recommended to trigger this using an autocmd or via keymap.
---
--- Example:
---
--- ```vim
---- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh({ bufnr = 0 })
--- ```
-function M.refresh()
+---
+--- @param opts? vim.lsp.codelens.RefreshOptions Table with the following fields:
+--- - `bufnr` (integer|nil): filter by buffer. All buffers if nil, 0 for current buffer
+function M.refresh(opts)
+ opts = opts or {}
+ local bufnr = opts.bufnr and resolve_bufnr(opts.bufnr)
+ local buffers = bufnr and { bufnr }
+ or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
local params = {
textDocument = util.make_text_document_params(),
}
- local bufnr = api.nvim_get_current_buf()
- if active_refreshes[bufnr] then
- return
+
+ for _, buf in ipairs(buffers) do
+ if not active_refreshes[buf] then
+ active_refreshes[buf] = true
+ vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens)
+ end
end
- active_refreshes[bufnr] = true
- vim.lsp.buf_request(0, ms.textDocument_codeLens, params, M.on_codelens)
end
return M
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 46dda01e3f..33051ab61c 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -22,7 +22,7 @@ end
---@param severity lsp.DiagnosticSeverity
local function severity_lsp_to_vim(severity)
if type(severity) == 'string' then
- severity = protocol.DiagnosticSeverity[severity]
+ severity = protocol.DiagnosticSeverity[severity] --- @type integer
end
return severity
end
@@ -35,7 +35,7 @@ local function severity_vim_to_lsp(severity)
return severity
end
----@param lines string[]
+---@param lines string[]?
---@param lnum integer
---@param col integer
---@param offset_encoding string
@@ -48,14 +48,14 @@ local function line_byte_from_position(lines, lnum, col, offset_encoding)
local line = lines[lnum + 1]
local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16')
if ok then
- return result
+ return result --- @type integer
end
return col
end
---@param bufnr integer
----@return string[]
+---@return string[]?
local function get_buf_lines(bufnr)
if vim.api.nvim_buf_is_loaded(bufnr) then
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
@@ -102,16 +102,17 @@ end
---@param diagnostics lsp.Diagnostic[]
---@param bufnr integer
---@param client_id integer
----@return Diagnostic[]
+---@return vim.Diagnostic[]
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id)
local offset_encoding = client and client.offset_encoding or 'utf-16'
- ---@diagnostic disable-next-line:no-unknown
+ --- @param diagnostic lsp.Diagnostic
+ --- @return vim.Diagnostic
return vim.tbl_map(function(diagnostic)
- ---@cast diagnostic lsp.Diagnostic
local start = diagnostic.range.start
local _end = diagnostic.range['end']
+ --- @type vim.Diagnostic
return {
lnum = start.line,
col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
@@ -135,12 +136,29 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
end, diagnostics)
end
---- @param diagnostics Diagnostic[]
+--- @param diagnostic vim.Diagnostic
+--- @return lsp.DiagnosticTag[]?
+local function tags_vim_to_vim(diagnostic)
+ if not diagnostic._tags then
+ return
+ end
+
+ local tags = {} --- @type lsp.DiagnosticTag[]
+ if diagnostic._tags.unnecessary then
+ tags[#tags + 1] = protocol.DiagnosticTag.Unnecessary
+ end
+ if diagnostic._tags.deprecated then
+ tags[#tags + 1] = protocol.DiagnosticTag.Deprecated
+ end
+ return tags
+end
+
+--- @param diagnostics vim.Diagnostic[]
--- @return lsp.Diagnostic[]
local function diagnostic_vim_to_lsp(diagnostics)
- ---@diagnostic disable-next-line:no-unknown
+ ---@param diagnostic vim.Diagnostic
+ ---@return lsp.Diagnostic
return vim.tbl_map(function(diagnostic)
- ---@cast diagnostic Diagnostic
return vim.tbl_extend('keep', {
-- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
range = {
@@ -157,6 +175,7 @@ local function diagnostic_vim_to_lsp(diagnostics)
message = diagnostic.message,
source = diagnostic.source,
code = diagnostic.code,
+ tags = tags_vim_to_vim(diagnostics),
}, diagnostic.user_data and (diagnostic.user_data.lsp or {}) or {})
end, diagnostics)
end
@@ -202,6 +221,47 @@ function M.get_namespace(client_id, is_pull)
end
end
+local function convert_severity(opt)
+ if type(opt) == 'table' and not opt.severity and opt.severity_limit then
+ vim.deprecate('severity_limit', '{min = severity} See vim.diagnostic.severity', '0.11')
+ opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
+ end
+end
+
+--- @param uri string
+--- @param client_id? integer
+--- @param diagnostics vim.Diagnostic[]
+--- @param is_pull boolean
+--- @param config? vim.diagnostic.Opts
+local function handle_diagnostics(uri, client_id, diagnostics, is_pull, config)
+ local fname = vim.uri_to_fname(uri)
+
+ if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
+ return
+ end
+
+ local bufnr = vim.fn.bufadd(fname)
+ if not bufnr then
+ return
+ end
+
+ client_id = get_client_id(client_id)
+ local namespace = M.get_namespace(client_id, is_pull)
+
+ if config then
+ --- @cast config table<string, table>
+ for _, opt in pairs(config) do
+ convert_severity(opt)
+ end
+ -- Persist configuration to ensure buffer reloads use the same
+ -- configuration. To make lsp.with configuration work (See :help
+ -- lsp-handler-configuration)
+ vim.diagnostic.config(config, namespace)
+ end
+
+ vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+end
+
--- |lsp-handler| for the method "textDocument/publishDiagnostics"
---
--- See |vim.diagnostic.config()| for configuration options. Handler-specific
@@ -227,41 +287,11 @@ end
--- )
--- ```
---
+---@param result lsp.PublishDiagnosticsParams
---@param ctx lsp.HandlerContext
----@param config table Configuration table (see |vim.diagnostic.config()|).
+---@param config? vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|).
function M.on_publish_diagnostics(_, result, ctx, config)
- local client_id = ctx.client_id
- local uri = result.uri
- local fname = vim.uri_to_fname(uri)
- local diagnostics = result.diagnostics
- if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
- return
- end
- local bufnr = vim.fn.bufadd(fname)
-
- if not bufnr then
- return
- end
-
- client_id = get_client_id(client_id)
- local namespace = M.get_namespace(client_id, false)
-
- if config then
- for _, opt in pairs(config) do
- if type(opt) == 'table' then
- if not opt.severity and opt.severity_limit then
- opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
- end
- end
- end
-
- -- Persist configuration to ensure buffer reloads use the same
- -- configuration. To make lsp.with configuration work (See :help
- -- lsp-handler-configuration)
- vim.diagnostic.config(config, namespace)
- end
-
- vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+ handle_diagnostics(result.uri, ctx.client_id, result.diagnostics, false, config)
end
--- |lsp-handler| for the method "textDocument/diagnostic"
@@ -289,49 +319,15 @@ end
--- )
--- ```
---
+---@param result lsp.DocumentDiagnosticReport
---@param ctx lsp.HandlerContext
---@param config table Configuration table (see |vim.diagnostic.config()|).
function M.on_diagnostic(_, result, ctx, config)
- local client_id = ctx.client_id
- local uri = ctx.params.textDocument.uri
- local fname = vim.uri_to_fname(uri)
-
- if result == nil then
- return
- end
-
- if result.kind == 'unchanged' then
- return
- end
-
- local diagnostics = result.items
- if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
+ if result == nil or result.kind == 'unchanged' then
return
end
- local bufnr = vim.fn.bufadd(fname)
- if not bufnr then
- return
- end
-
- client_id = get_client_id(client_id)
-
- local namespace = M.get_namespace(client_id, true)
-
- if config then
- for _, opt in pairs(config) do
- if type(opt) == 'table' and not opt.severity and opt.severity_limit then
- opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
- end
- end
-
- -- Persist configuration to ensure buffer reloads use the same
- -- configuration. To make lsp.with configuration work (See :help
- -- lsp-handler-configuration)
- vim.diagnostic.config(config, namespace)
- end
-
- vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+ handle_diagnostics(ctx.params.textDocument.uri, ctx.client_id, result.items, true, config)
end
--- Clear push diagnostics and diagnostic cache.
@@ -341,7 +337,7 @@ end
--- implementation so it's simply marked @private rather than @deprecated.
---
---@param client_id integer
----@param buffer_client_map table map of buffers to active clients
+---@param buffer_client_map table<integer, table<integer, table>> map of buffers to active clients
---@private
function M.reset(client_id, buffer_client_map)
buffer_client_map = vim.deepcopy(buffer_client_map)
@@ -362,34 +358,28 @@ end
---
---@param bufnr integer|nil The buffer number
---@param line_nr integer|nil The line number
----@param opts table|nil Configuration keys
---- - severity: (DiagnosticSeverity, default nil)
---- - Only return diagnostics with this severity. Overrides severity_limit
---- - severity_limit: (DiagnosticSeverity, default nil)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
+---@param opts {severity?:lsp.DiagnosticSeverity}?
+--- - severity: (lsp.DiagnosticSeverity)
+--- - Only return diagnostics with this severity.
---@param client_id integer|nil the client id
---@return table Table with map of line number to list of diagnostics.
--- Structured: { [1] = {...}, [5] = {.... } }
---@private
function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
- opts = opts or {}
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
- end
+ convert_severity(opts)
+ local diag_opts = {} --- @type vim.diagnostic.GetOpts
- if client_id then
- opts.namespace = M.get_namespace(client_id, false)
+ if opts and opts.severity then
+ diag_opts.severity = severity_lsp_to_vim(opts.severity)
end
- if not line_nr then
- line_nr = vim.api.nvim_win_get_cursor(0)[1] - 1
+ if client_id then
+ diag_opts.namespace = M.get_namespace(client_id, false)
end
- opts.lnum = line_nr
+ diag_opts.lnum = line_nr or (api.nvim_win_get_cursor(0)[1] - 1)
- return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts))
+ return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, diag_opts))
end
--- Clear diagnostics from pull based clients
@@ -447,7 +437,8 @@ function M._enable(bufnr)
return
end
if bufstates[bufnr] and bufstates[bufnr].enabled then
- _refresh(bufnr, { only_visible = true, client_id = opts.data.client_id })
+ local client_id = opts.data.client_id --- @type integer?
+ _refresh(bufnr, { only_visible = true, client_id = client_id })
end
end,
group = augroup,
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 26a71487e2..4bb14e5a09 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -413,9 +413,7 @@ M[ms.textDocument_hover] = M.hover
---(`textDocument/definition` can return `Location` or `Location[]`
local function location_handler(_, result, ctx, config)
if result == nil or vim.tbl_isempty(result) then
- if log.info() then
- log.info(ctx.method, 'No location found')
- end
+ log.info(ctx.method, 'No location found')
return nil
end
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
@@ -646,16 +644,22 @@ M[ms.workspace_inlayHint_refresh] = function(err, result, ctx, config)
return vim.lsp.inlay_hint.on_refresh(err, result, ctx, config)
end
+---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
+M[ms.workspace_semanticTokens_refresh] = function(err, result, ctx, _config)
+ return vim.lsp.semantic_tokens._refresh(err, result, ctx)
+end
+
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config)
- local _ = log.trace()
- and log.trace('default_handler', ctx.method, {
+ if log.trace() then
+ log.trace('default_handler', ctx.method, {
err = err,
result = result,
ctx = vim.inspect(ctx),
config = config,
})
+ end
if err then
-- LSP spec:
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 9c989e5246..d770735895 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -7,7 +7,7 @@ function M.check()
local log = vim.lsp.log
local current_log_level = log.get_level()
- local log_level_string = log.levels[current_log_level]
+ local log_level_string = log.levels[current_log_level] ---@type string
report_info(string.format('LSP log level : %s', log_level_string))
if current_log_level < log.levels.WARN then
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
index 62138c0edf..49dc35fdf6 100644
--- a/runtime/lua/vim/lsp/inlay_hint.lua
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -22,9 +22,7 @@ local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {})
---@private
function M.on_inlayhint(err, result, ctx, _)
if err then
- if log.error() then
- log.error('inlayhint', err)
- end
+ log.error('inlayhint', err)
return
end
local bufnr = assert(ctx.bufnr)
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 00433474fe..a9d49bc8f4 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -12,130 +12,130 @@ log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn.
local current_log_level = log.levels.WARN
+
local log_date_format = '%F %H:%M:%S'
-local format_func = function(arg)
+
+local function format_func(arg)
return vim.inspect(arg, { newline = '' })
end
-do
- local function notify(msg, level)
- if vim.in_fast_event() then
- vim.schedule(function()
- vim.notify(msg, level)
- end)
- else
+local function notify(msg, level)
+ if vim.in_fast_event() then
+ vim.schedule(function()
vim.notify(msg, level)
- end
+ end)
+ else
+ vim.notify(msg, level)
end
+end
+
+local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'lsp.log')
+
+-- TODO: Ideally the directory should be created in open_logfile(), right
+-- before opening the log file, but open_logfile() can be called from libuv
+-- callbacks, where using fn.mkdir() is not allowed.
+vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
+
+--- Returns the log filename.
+---@return string log filename
+function log.get_filename()
+ return logfilename
+end
+
+--- @type file*?, string?
+local logfile, openerr
- local path_sep = vim.uv.os_uname().version:match('Windows') and '\\' or '/'
- local function path_join(...)
- return table.concat(vim.tbl_flatten({ ... }), path_sep)
+--- Opens log file. Returns true if file is open, false on error
+local function open_logfile()
+ -- Try to open file only once
+ if logfile then
+ return true
+ end
+ if openerr then
+ return false
end
- local logfilename = path_join(vim.fn.stdpath('log'), 'lsp.log')
- -- TODO: Ideally the directory should be created in open_logfile(), right
- -- before opening the log file, but open_logfile() can be called from libuv
- -- callbacks, where using fn.mkdir() is not allowed.
- vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
+ logfile, openerr = io.open(logfilename, 'a+')
+ if not logfile then
+ local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
+ notify(err_msg, vim.log.levels.ERROR)
+ return false
+ end
- --- Returns the log filename.
- ---@return string log filename
- function log.get_filename()
- return logfilename
+ local log_info = vim.uv.fs_stat(logfilename)
+ if log_info and log_info.size > 1e9 then
+ local warn_msg = string.format(
+ 'LSP client log is large (%d MB): %s',
+ log_info.size / (1000 * 1000),
+ logfilename
+ )
+ notify(warn_msg)
end
- local logfile, openerr
- --- Opens log file. Returns true if file is open, false on error
- local function open_logfile()
- -- Try to open file only once
- if logfile then
+ -- Start message for logging
+ logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
+ return true
+end
+
+for level, levelnr in pairs(log.levels) do
+ -- Also export the log level on the root object.
+ log[level] = levelnr
+end
+
+vim.tbl_add_reverse_lookup(log.levels)
+
+--- @param level string
+--- @param levelnr integer
+--- @return fun(...:any): boolean?
+local function create_logger(level, levelnr)
+ return function(...)
+ if levelnr < current_log_level then
+ return false
+ end
+ local argc = select('#', ...)
+ if argc == 0 then
return true
end
- if openerr then
+ if not open_logfile() then
return false
end
-
- logfile, openerr = io.open(logfilename, 'a+')
- if not logfile then
- local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
- notify(err_msg, vim.log.levels.ERROR)
- return false
+ local info = debug.getinfo(2, 'Sl')
+ local header = string.format(
+ '[%s][%s] ...%s:%s',
+ level,
+ os.date(log_date_format),
+ info.short_src:sub(-16),
+ info.currentline
+ )
+ local parts = { header }
+ for i = 1, argc do
+ local arg = select(i, ...)
+ table.insert(parts, arg == nil and 'nil' or format_func(arg))
end
+ assert(logfile)
+ logfile:write(table.concat(parts, '\t'), '\n')
+ logfile:flush()
+ end
+end
- local log_info = vim.uv.fs_stat(logfilename)
- if log_info and log_info.size > 1e9 then
- local warn_msg = string.format(
- 'LSP client log is large (%d MB): %s',
- log_info.size / (1000 * 1000),
- logfilename
- )
- notify(warn_msg)
- end
+-- If called without arguments, it will check whether the log level is
+-- greater than or equal to this one. When called with arguments, it will
+-- log at that level (if applicable, it is checked either way).
- -- Start message for logging
- logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
- return true
- end
+--- @nodoc
+log.debug = create_logger('DEBUG', vim.log.levels.DEBUG)
- for level, levelnr in pairs(log.levels) do
- -- Also export the log level on the root object.
- log[level] = levelnr
- -- FIXME: DOC
- -- Should be exposed in the vim docs.
- --
- -- Set the lowercase name as the main use function.
- -- If called without arguments, it will check whether the log level is
- -- greater than or equal to this one. When called with arguments, it will
- -- log at that level (if applicable, it is checked either way).
- --
- -- Recommended usage:
- -- ```
- -- if log.warn() then
- -- log.warn("123")
- -- end
- -- ```
- --
- -- This way you can avoid string allocations if the log level isn't high enough.
- if level ~= 'OFF' then
- log[level:lower()] = function(...)
- local argc = select('#', ...)
- if levelnr < current_log_level then
- return false
- end
- if argc == 0 then
- return true
- end
- if not open_logfile() then
- return false
- end
- local info = debug.getinfo(2, 'Sl')
- local header = string.format(
- '[%s][%s] ...%s:%s',
- level,
- os.date(log_date_format),
- string.sub(info.short_src, #info.short_src - 15),
- info.currentline
- )
- local parts = { header }
- for i = 1, argc do
- local arg = select(i, ...)
- if arg == nil then
- table.insert(parts, 'nil')
- else
- table.insert(parts, format_func(arg))
- end
- end
- logfile:write(table.concat(parts, '\t'), '\n')
- logfile:flush()
- end
- end
- end
-end
+--- @nodoc
+log.error = create_logger('ERROR', vim.log.levels.ERROR)
--- This is put here on purpose after the loop above so that it doesn't
--- interfere with iterating the levels
-vim.tbl_add_reverse_lookup(log.levels)
+--- @nodoc
+log.info = create_logger('INFO', vim.log.levels.INFO)
+
+--- @nodoc
+log.trace = create_logger('TRACE', vim.log.levels.TRACE)
+
+--- @nodoc
+log.warn = create_logger('WARN', vim.log.levels.WARN)
--- Sets the current log level.
---@param level (string|integer) One of `vim.lsp.log.levels`
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 660b126ce4..e849bb4f2a 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -26,24 +26,6 @@ local function format_message_with_content_length(message)
})
end
-local function log_error(...)
- if log.error() then
- log.error(...)
- end
-end
-
-local function log_info(...)
- if log.info() then
- log.info(...)
- end
-end
-
-local function log_debug(...)
- if log.debug() then
- log.debug(...)
- end
-end
-
---@class vim.lsp.rpc.Headers: {string: any}
---@field content_length integer
@@ -65,7 +47,7 @@ local function parse_headers(header)
key = key:lower():gsub('%-', '_') --- @type string
headers[key] = value
else
- log_error('invalid header line %q', line)
+ log.error('invalid header line %q', line)
error(string.format('invalid header line %q', line))
end
end
@@ -224,7 +206,7 @@ local default_dispatchers = {
---@param method string The invoked LSP method
---@param params table Parameters for the invoked LSP method
notification = function(method, params)
- log_debug('notification', method, params)
+ log.debug('notification', method, params)
end,
--- Default dispatcher for requests sent to an LSP server.
@@ -234,7 +216,7 @@ local default_dispatchers = {
---@return any result (always nil for the default dispatchers)
---@return lsp.ResponseError error `vim.lsp.protocol.ErrorCodes.MethodNotFound`
server_request = function(method, params)
- log_debug('server_request', method, params)
+ log.debug('server_request', method, params)
return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end,
@@ -243,7 +225,7 @@ local default_dispatchers = {
---@param code integer Exit code
---@param signal integer Number describing the signal used to terminate (if any)
on_exit = function(code, signal)
- log_info('client_exit', { code = code, signal = signal })
+ log.info('client_exit', { code = code, signal = signal })
end,
--- Default dispatcher for client errors.
@@ -251,7 +233,7 @@ local default_dispatchers = {
---@param code integer Error code
---@param err any Details about the error
on_error = function(code, err)
- log_error('client_error:', M.client_errors[code], err)
+ log.error('client_error:', M.client_errors[code], err)
end,
}
@@ -297,7 +279,7 @@ local Client = {}
---@private
function Client:encode_and_send(payload)
- log_debug('rpc.send', payload)
+ log.debug('rpc.send', payload)
if self.transport.is_closing() then
return false
end
@@ -419,7 +401,7 @@ function Client:handle_body(body)
self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded)
return
end
- log_debug('rpc.receive', decoded)
+ log.debug('rpc.receive', decoded)
if type(decoded.method) == 'string' and decoded.id then
local err --- @type lsp.ResponseError|nil
@@ -434,7 +416,7 @@ function Client:handle_body(body)
decoded.method,
decoded.params
)
- log_debug(
+ log.debug(
'server_request: callback result',
{ status = status, result = result, err = err }
)
@@ -490,7 +472,7 @@ function Client:handle_body(body)
if decoded.error then
local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
- log_debug('Received cancellation ack', decoded)
+ log.debug('Received cancellation ack', decoded)
mute_error = true
end
@@ -516,7 +498,7 @@ function Client:handle_body(body)
if decoded.error then
decoded.error = setmetatable(decoded.error, {
__tostring = M.format_rpc_error,
- })
+ }) --- @type table
end
self:try_call(
M.client_errors.SERVER_RESULT_CALLBACK_ERROR,
@@ -526,7 +508,7 @@ function Client:handle_body(body)
)
else
self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
- log_error('No callback found for server response id ' .. result_id)
+ log.error('No callback found for server response id ' .. result_id)
end
elseif type(decoded.method) == 'string' then
-- Notification
@@ -750,8 +732,7 @@ end
--- interact with it. Communication with the spawned process happens via stdio. For
--- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()|
---
----@param cmd string Command to start the LSP server.
----@param cmd_args string[] List of additional string arguments to pass to {cmd}.
+---@param cmd string[] Command to start the LSP server.
---
---@param dispatchers? vim.lsp.rpc.Dispatchers Dispatchers for LSP message types.
--- Valid dispatcher names are:
@@ -772,12 +753,11 @@ end
--- - `request()` |vim.lsp.rpc.request()|
--- - `is_closing()` returns a boolean indicating if the RPC is closing.
--- - `terminate()` terminates the RPC client.
-function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
- log_info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
+function M.start(cmd, dispatchers, extra_spawn_params)
+ log.info('Starting RPC client', { cmd = cmd, extra = extra_spawn_params })
validate({
- cmd = { cmd, 's' },
- cmd_args = { cmd_args, 't' },
+ cmd = { cmd, 't' },
dispatchers = { dispatchers, 't', true },
})
@@ -813,7 +793,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
local stderr_handler = function(_, chunk)
if chunk then
- log_error('rpc', cmd, 'stderr', chunk)
+ log.error('rpc', cmd[1], 'stderr', chunk)
end
end
@@ -822,10 +802,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
detached = extra_spawn_params.detached
end
- local cmd1 = { cmd }
- vim.list_extend(cmd1, cmd_args)
-
- local ok, sysobj_or_err = pcall(vim.system, cmd1, {
+ local ok, sysobj_or_err = pcall(vim.system, cmd, {
stdin = true,
stdout = stdout_handler,
stderr = stderr_handler,
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index b0cec0dd0e..fe8638bdfa 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -1,6 +1,5 @@
local api = vim.api
local bit = require('bit')
-local handlers = require('vim.lsp.handlers')
local ms = require('vim.lsp.protocol').Methods
local util = require('vim.lsp.util')
local uv = vim.uv
@@ -763,6 +762,7 @@ function M.highlight_token(token, bufnr, client_id, hl_group, opts)
})
end
+--- @package
--- |lsp-handler| for the method `workspace/semanticTokens/refresh`
---
--- Refresh requests are sent by the server to indicate a project-wide change
@@ -770,9 +770,7 @@ end
--- invalidate the current results of all buffers and automatically kick off a
--- new request for buffers that are displayed in a window. For those that aren't, a
--- the BufWinEnter event should take care of it next time it's displayed.
----
----@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
-handlers[ms.workspace_semanticTokens_refresh] = function(err, _, ctx)
+function M._refresh(err, _, ctx)
if err then
return vim.NIL
end
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index 7ebe2dbb88..62fa0b33f4 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -58,8 +58,7 @@ local function byte_to_utf(line, byte, offset_encoding)
-- convert to 0 based indexing for str_utfindex
byte = byte - 1
- local utf_idx --- @type integer
- local _
+ local utf_idx, _ --- @type integer, integer
-- Convert the byte range to utf-{8,16,32} and convert 1-based (lua) indexing to 0-based
if offset_encoding == 'utf-16' then
_, utf_idx = str_utfindex(line, byte)
@@ -77,8 +76,7 @@ end
---@param offset_encoding string
---@return integer
local function compute_line_length(line, offset_encoding)
- local length --- @type integer
- local _
+ local length, _ --- @type integer, integer
if offset_encoding == 'utf-16' then
_, length = str_utfindex(line)
elseif offset_encoding == 'utf-32' then
@@ -122,6 +120,11 @@ local function align_end_position(line, byte, offset_encoding)
return byte, char
end
+---@class vim.lsp.sync.Range
+---@field line_idx integer
+---@field byte_idx integer
+---@field char_idx integer
+
--- Finds the first line, byte, and char index of the difference between the previous and current lines buffer normalized to the previous codepoint.
---@param prev_lines string[] list of lines from previous buffer
---@param curr_lines string[] list of lines from current buffer
@@ -129,7 +132,7 @@ end
---@param lastline integer lastline from on_lines, adjusted to 1-index
---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index
---@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8)
----@return table result table include line_idx, byte_idx, and char_idx of first change position
+---@return vim.lsp.sync.Range result table include line_idx, byte_idx, and char_idx of first change position
local function compute_start_range(
prev_lines,
curr_lines,
@@ -202,14 +205,14 @@ end
--- prev_end_range is the text range sent to the server representing the changed region.
--- curr_end_range is the text that should be collected and sent to the server.
--
----@param prev_lines table list of lines
----@param curr_lines table list of lines
----@param start_range table
+---@param prev_lines string[] list of lines
+---@param curr_lines string[] list of lines
+---@param start_range vim.lsp.sync.Range
+---@param firstline integer
---@param lastline integer
---@param new_lastline integer
---@param offset_encoding string
----@return integer|table end_line_idx and end_col_idx of range
----@return table|nil end_col_idx of range
+---@return vim.lsp.sync.Range, vim.lsp.sync.Range
local function compute_end_range(
prev_lines,
curr_lines,
@@ -253,7 +256,7 @@ local function compute_end_range(
-- Editing the same line
-- If the byte offset is zero, that means there is a difference on the last byte (not newline)
if prev_line_idx == curr_line_idx then
- local max_length
+ local max_length --- @type integer
if start_line_idx == prev_line_idx then
-- Search until beginning of difference
max_length = min(
@@ -286,7 +289,7 @@ local function compute_end_range(
local prev_end_range =
{ line_idx = prev_line_idx, byte_idx = prev_byte_idx, char_idx = prev_char_idx }
- local curr_end_range
+ local curr_end_range ---@type vim.lsp.sync.Range
-- Deletion event, new_range cannot be before start
if curr_line_idx < start_line_idx then
curr_end_range = { line_idx = start_line_idx, byte_idx = 1, char_idx = 1 }
@@ -347,8 +350,8 @@ end
-- Line endings count here as 2 chars for \r\n (dos), 1 char for \n (unix), and 1 char for \r (mac)
-- These correspond to Windows, Linux/macOS (OSX and newer), and macOS (version 9 and prior)
---@param lines string[]
----@param start_range table
----@param end_range table
+---@param start_range vim.lsp.sync.Range
+---@param end_range vim.lsp.sync.Range
---@param offset_encoding string
---@param line_ending string
---@return integer
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index b5e15e135c..86bef1ac8a 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -693,6 +693,9 @@ function M.rename(old_fname, new_fname, opts)
end)
end
+ local newdir = assert(vim.fs.dirname(new_fname))
+ vim.fn.mkdir(newdir, 'p')
+
local ok, err = os.rename(old_fname, new_fname)
assert(ok, err)
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index fa7690e41e..dd0f7c2e1e 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -895,6 +895,7 @@ do
---@field private _idx_read integer
---@field private _idx_write integer
---@field private _size integer
+ ---@overload fun(self): table?
local Ringbuf = {}
--- Clear all items
diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua
index 4c62b5c076..7e37d84393 100644
--- a/runtime/lua/vim/snippet.lua
+++ b/runtime/lua/vim/snippet.lua
@@ -1,6 +1,7 @@
local G = vim.lsp._snippet_grammar
local snippet_group = vim.api.nvim_create_augroup('vim/snippet', {})
local snippet_ns = vim.api.nvim_create_namespace('vim/snippet')
+local hl_group = 'SnippetTabstop'
--- Returns the 0-based cursor position.
---
@@ -117,11 +118,11 @@ local Tabstop = {}
--- @return vim.snippet.Tabstop
function Tabstop.new(index, bufnr, range, choices)
local extmark_id = vim.api.nvim_buf_set_extmark(bufnr, snippet_ns, range[1], range[2], {
- right_gravity = false,
+ right_gravity = true,
end_right_gravity = true,
end_line = range[3],
end_col = range[4],
- hl_group = 'SnippetTabstop',
+ hl_group = hl_group,
})
local self = setmetatable(
@@ -161,6 +162,21 @@ function Tabstop:set_text(text)
vim.api.nvim_buf_set_text(self.bufnr, range[1], range[2], range[3], range[4], text_to_lines(text))
end
+--- Sets the right gravity of the tabstop's extmark.
+---
+--- @package
+--- @param right_gravity boolean
+function Tabstop:set_right_gravity(right_gravity)
+ local range = self:get_range()
+ self.extmark_id = vim.api.nvim_buf_set_extmark(self.bufnr, snippet_ns, range[1], range[2], {
+ right_gravity = right_gravity,
+ end_right_gravity = true,
+ end_line = range[3],
+ end_col = range[4],
+ hl_group = hl_group,
+ })
+end
+
--- @class vim.snippet.Session
--- @field bufnr integer
--- @field extmark_id integer
@@ -218,6 +234,17 @@ function Session:get_dest_index(direction)
end
end
+--- Sets the right gravity of the tabstop group with the given index.
+---
+--- @package
+--- @param index integer
+--- @param right_gravity boolean
+function Session:set_group_gravity(index, right_gravity)
+ for _, tabstop in ipairs(self.tabstops[index]) do
+ tabstop:set_right_gravity(right_gravity)
+ end
+end
+
--- @class vim.snippet.Snippet
--- @field private _session? vim.snippet.Session
local M = { session = nil }
@@ -560,9 +587,15 @@ function M.jump(direction)
-- Clear the autocommands so that we can move the cursor freely while selecting the tabstop.
vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr })
+ -- Deactivate expansion of the current tabstop.
+ M._session:set_group_gravity(M._session.current_tabstop.index, true)
+
M._session.current_tabstop = dest
select_tabstop(dest)
+ -- Activate expansion of the destination tabstop.
+ M._session:set_group_gravity(dest.index, false)
+
-- Restore the autocommands.
setup_autocmds(M._session.bufnr)
end
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index 80c998b555..6a714de052 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -1,4 +1,5 @@
---@meta
+error('Cannot require a meta file')
---@class TSNode: userdata
---@field id fun(self: TSNode): string
@@ -33,7 +34,7 @@
---@field byte_length fun(self: TSNode): integer
local TSNode = {}
----@param query userdata
+---@param query TSQuery
---@param captures true
---@param start? integer
---@param end_? integer
@@ -41,17 +42,17 @@ local TSNode = {}
---@return fun(): integer, TSNode, any
function TSNode:_rawquery(query, captures, start, end_, opts) end
----@param query userdata
+---@param query TSQuery
---@param captures false
---@param start? integer
---@param end_? integer
---@param opts? table
----@return fun(): string, any
+---@return fun(): integer, any
function TSNode:_rawquery(query, captures, start, end_, opts) end
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
----@class TSParser
+---@class TSParser: userdata
---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: true): TSTree, Range6[]
---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: false|nil): TSTree, Range4[]
---@field reset fun(self: TSParser)
@@ -62,19 +63,31 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback)
---@field _logger fun(self: TSParser): TSLoggerCallback
----@class TSTree
+---@class TSTree: userdata
---@field root fun(self: TSTree): TSNode
---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer)
---@field copy fun(self: TSTree): TSTree
---@field included_ranges fun(self: TSTree, include_bytes: true): Range6[]
---@field included_ranges fun(self: TSTree, include_bytes: false): Range4[]
+---@class TSQuery: userdata
+---@field inspect fun(self: TSQuery): TSQueryInfo
+
+---@class (exact) TSQueryInfo
+---@field captures string[]
+---@field patterns table<integer, (integer|string)[][]>
+
---@return integer
vim._ts_get_language_version = function() end
---@return integer
vim._ts_get_minimum_language_version = function() end
+---@param lang string Language to use for the query
+---@param query string Query string in s-expr syntax
+---@return TSQuery
+vim._ts_parse_query = function(lang, query) end
+
---@param lang string
---@return TSParser
vim._create_ts_parser = function(lang) end
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
index 1385642acd..551067533a 100644
--- a/runtime/lua/vim/treesitter/dev.lua
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -3,16 +3,20 @@ local api = vim.api
---@class TSDevModule
local M = {}
+---@private
---@class TSTreeView
---@field ns integer API namespace
----@field opts table Options table with the following keys:
---- - anon (boolean): If true, display anonymous nodes
---- - lang (boolean): If true, display the language alongside each node
---- - indent (number): Number of spaces to indent nested lines. Default is 2.
+---@field opts TSTreeViewOpts
---@field nodes TSP.Node[]
---@field named TSP.Node[]
local TSTreeView = {}
+---@private
+---@class TSTreeViewOpts
+---@field anon boolean If true, display anonymous nodes.
+---@field lang boolean If true, display the language alongside each node.
+---@field indent number Number of spaces to indent nested lines.
+
---@class TSP.Node
---@field node TSNode Treesitter node
---@field field string? Node field
@@ -39,25 +43,26 @@ local TSTreeView = {}
---
---@param node TSNode Starting node to begin traversal |tsnode|
---@param depth integer Current recursion depth
+---@param field string|nil The field of the current node
---@param lang string Language of the tree currently being traversed
---@param injections table<string, TSP.Injection> Mapping of node ids to root nodes
--- of injected language trees (see explanation above)
---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree
-local function traverse(node, depth, lang, injections, tree)
+local function traverse(node, depth, field, lang, injections, tree)
+ table.insert(tree, {
+ node = node,
+ depth = depth,
+ lang = lang,
+ field = field,
+ })
+
local injection = injections[node:id()]
if injection then
- traverse(injection.root, depth, injection.lang, injections, tree)
+ traverse(injection.root, depth + 1, nil, injection.lang, injections, tree)
end
- for child, field in node:iter_children() do
- table.insert(tree, {
- node = child,
- field = field,
- depth = depth,
- lang = lang,
- })
-
- traverse(child, depth + 1, lang, injections, tree)
+ for child, child_field in node:iter_children() do
+ traverse(child, depth + 1, child_field, lang, injections, tree)
end
return tree
@@ -102,7 +107,7 @@ function TSTreeView:new(bufnr, lang)
end
end)
- local nodes = traverse(root, 0, parser:lang(), injections, {})
+ local nodes = traverse(root, 0, nil, parser:lang(), injections, {})
local named = {} ---@type TSP.Node[]
for _, v in ipairs(nodes) do
@@ -115,6 +120,7 @@ function TSTreeView:new(bufnr, lang)
ns = api.nvim_create_namespace('treesitter/dev-inspect'),
nodes = nodes,
named = named,
+ ---@type TSTreeViewOpts
opts = {
anon = false,
lang = false,
@@ -129,16 +135,12 @@ end
local decor_ns = api.nvim_create_namespace('ts.dev')
----@param lnum integer
----@param col integer
----@param end_lnum integer
----@param end_col integer
+---@param range Range4
---@return string
-local function get_range_str(lnum, col, end_lnum, end_col)
- if lnum == end_lnum then
- return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col)
- end
- return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col)
+local function range_to_string(range)
+ ---@type integer, integer, integer, integer
+ local row, col, end_row, end_col = unpack(range)
+ return string.format('[%d, %d] - [%d, %d]', row, col, end_row, end_col)
end
---@param w integer
@@ -212,7 +214,7 @@ function TSTreeView:draw(bufnr)
local lang_hl_marks = {} ---@type table[]
for i, item in self:iter() do
- local range_str = get_range_str(item.node:range())
+ local range_str = range_to_string({ item.node:range() })
local lang_str = self.opts.lang and string.format(' %s', item.lang) or ''
local text ---@type string
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 84d40322fe..08c4c2a832 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -7,7 +7,7 @@ local ns = api.nvim_create_namespace('treesitter/highlighter')
---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, TSMetadata
---@class vim.treesitter.highlighter.Query
----@field private _query Query?
+---@field private _query vim.treesitter.query.Query?
---@field private lang string
---@field private hl_cache table<integer,integer>
local TSHighlighterQuery = {}
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 63d4a9382a..cd65c0d7f6 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -1,18 +1,48 @@
local api = vim.api
local language = require('vim.treesitter.language')
----@class Query
----@field captures string[] List of captures used in query
----@field info TSQueryInfo Contains used queries, predicates, directives
----@field query userdata Parsed query
+local M = {}
+
+---Parsed query, see |vim.treesitter.query.parse()|
+---
+---@class vim.treesitter.Query
+---@field lang string name of the language for this parser
+---@field captures string[] list of (unique) capture names defined in query
+---@field info vim.treesitter.QueryInfo contains information used in the query (e.g. captures, predicates, directives)
+---@field query TSQuery userdata query object
local Query = {}
Query.__index = Query
----@class TSQueryInfo
----@field captures table
----@field patterns table<string,any[][]>
+---@package
+---@see vim.treesitter.query.parse
+---@param lang string
+---@param ts_query TSQuery
+---@return vim.treesitter.Query
+function Query.new(lang, ts_query)
+ local self = setmetatable({}, Query)
+ local query_info = ts_query:inspect() ---@type TSQueryInfo
+ self.query = ts_query
+ self.lang = lang
+ self.info = {
+ captures = query_info.captures,
+ patterns = query_info.patterns,
+ }
+ self.captures = self.info.captures
+ return self
+end
-local M = {}
+---Information for Query, see |vim.treesitter.query.parse()|
+---@class vim.treesitter.QueryInfo
+---
+---List of (unique) capture names defined in query.
+---@field captures string[]
+---
+---Contains information about predicates and directives.
+---Key is pattern id, and value is list of predicates or directives defined in the pattern.
+---A predicate or directive is a list of (integer|string); integer represents `capture_id`, and
+---string represents (literal) arguments to predicate/directive. See |treesitter-predicates|
+---and |treesitter-directives| for more details.
+---@field patterns table<integer, (integer|string)[][]>
---@param files string[]
---@return string[]
@@ -162,7 +192,7 @@ local function read_query_files(filenames)
end
-- The explicitly set queries from |vim.treesitter.query.set()|
----@type table<string,table<string,Query>>
+---@type table<string,table<string,vim.treesitter.Query>>
local explicit_queries = setmetatable({}, {
__index = function(t, k)
local lang_queries = {}
@@ -201,7 +231,7 @@ end
---@param lang string Language to use for the query
---@param query_name string Name of the query (e.g. "highlights")
---
----@return Query|nil Parsed query
+---@return vim.treesitter.Query|nil -- Parsed query. `nil` if no query files are found.
M.get = vim.func._memoize('concat-2', function(lang, query_name)
if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name]
@@ -228,26 +258,24 @@ end
---
--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
--- search nodes in the syntax tree for the patterns defined in {query}
---- using `iter_*` methods below.
+--- using the `iter_captures` and `iter_matches` methods.
---
--- Exposes `info` and `captures` with additional context about {query}.
---- - `captures` contains the list of unique capture names defined in
---- {query}.
---- -` info.captures` also points to `captures`.
+--- - `captures` contains the list of unique capture names defined in {query}.
+--- - `info.captures` also points to `captures`.
--- - `info.patterns` contains information about predicates.
---
---@param lang string Language to use for the query
---@param query string Query in s-expr syntax
---
----@return Query Parsed query
+---@return vim.treesitter.Query Parsed query
+---
+---@see |vim.treesitter.query.get()|
M.parse = vim.func._memoize('concat-2', function(lang, query)
language.add(lang)
- local self = setmetatable({}, Query)
- self.query = vim._ts_parse_query(lang, query)
- self.info = self.query:inspect()
- self.captures = self.info.captures
- return self
+ local ts_query = vim._ts_parse_query(lang, query)
+ return Query.new(lang, ts_query)
end)
---@deprecated
@@ -644,14 +672,16 @@ end
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
----@param start integer
----@param stop integer
+---@param start integer|nil
+---@param stop integer|nil
---@param node TSNode
---@return integer, integer
local function value_or_node_range(start, stop, node)
- if start == nil and stop == nil then
- local node_start, _, node_stop, _ = node:range()
- return node_start, node_stop + 1 -- Make stop inclusive
+ if start == nil then
+ start = node:start()
+ end
+ if stop == nil then
+ stop = node:end_() + 1 -- Make stop inclusive
end
return start, stop
@@ -682,8 +712,8 @@ end
---
---@param node TSNode under which the search will occur
---@param source (integer|string) Source buffer or string to extract text from
----@param start integer Starting line for the search
----@param stop integer Stopping line for the search (end-exclusive)
+---@param start? integer Starting line for the search. Defaults to `node:start()`.
+---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`.
---
---@return (fun(end_line: integer|nil): integer, TSNode, TSMetadata):
--- capture id, capture node, metadata
@@ -741,12 +771,11 @@ end
---
---@param node TSNode under which the search will occur
---@param source (integer|string) Source buffer or string to search
----@param start integer Starting line for the search
----@param stop integer Stopping line for the search (end-exclusive)
----@param opts table|nil Options:
+---@param start? integer Starting line for the search. Defaults to `node:start()`.
+---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`.
+---@param opts? table Optional keyword arguments:
--- - max_start_depth (integer) if non-zero, sets the maximum start depth
--- for each match. This is used to prevent traversing too deep into a tree.
---- Requires treesitter >= 0.20.9.
---
---@return (fun(): integer, table<integer,TSNode>, table): pattern id, match, metadata
function Query:iter_matches(node, source, start, stop, opts)
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index 2dc817c5c1..038aa8acfb 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -10,14 +10,14 @@ local tohex = require('bit').tohex
local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
local PATTERNS = {
- ---RFC 2396
- ---https://tools.ietf.org/html/rfc2396#section-2.2
+ -- RFC 2396
+ -- https://tools.ietf.org/html/rfc2396#section-2.2
rfc2396 = "^A-Za-z0-9%-_.!~*'()",
- ---RFC 2732
- ---https://tools.ietf.org/html/rfc2732
+ -- RFC 2732
+ -- https://tools.ietf.org/html/rfc2732
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
- ---RFC 3986
- ---https://tools.ietf.org/html/rfc3986#section-2.2
+ -- RFC 3986
+ -- https://tools.ietf.org/html/rfc3986#section-2.2
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
}
diff --git a/runtime/queries/bash/highlights.scm b/runtime/queries/bash/highlights.scm
index 21346ded8f..b4360ce7e1 100644
--- a/runtime/queries/bash/highlights.scm
+++ b/runtime/queries/bash/highlights.scm
@@ -43,6 +43,7 @@
"<<-"
"<<<"
".."
+ "!"
] @operator
; Do *not* spell check strings since they typically have some sort of
@@ -66,6 +67,13 @@
(command
argument: "$" @string) ; bare dollar
+(concatenation
+ [
+ (simple_expansion)
+ (expansion)
+ ]
+ (word) @string)
+
[
"if"
"then"
diff --git a/runtime/syntax/dosbatch.vim b/runtime/syntax/dosbatch.vim
index a75771bd2d..55601996ad 100644
--- a/runtime/syntax/dosbatch.vim
+++ b/runtime/syntax/dosbatch.vim
@@ -2,11 +2,11 @@
" Language: MS-DOS/Windows batch file (with NT command extensions)
" Maintainer: Mike Williams <mrmrdubya@gmail.com>
" Filenames: *.bat
-" Last Change: 12th February 2023
+" Last Change: 3rd February 2024
"
" Options Flags:
" dosbatch_cmdextversion - 1 = Windows NT, 2 = Windows 2000 [default]
-" dosbatch_colons_comment - any value to treat :: as comment line
+" dosbatch_colons_comment - any value to allow :: comments in code blocks
"
" quit when a syntax file was already loaded
@@ -88,18 +88,22 @@ syn match dosbatchLabel "\<goto\s\+\h\w*\>"lc=4
syn match dosbatchLabel ":\h\w*\>"
" Comments - usual rem but also two colons as first non-space is an idiom
-syn match dosbatchComment "^rem\($\|\s.*$\)"lc=3 contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
-syn match dosbatchComment "^@rem\($\|\s.*$\)"lc=4 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
-syn match dosbatchComment "\srem\($\|\s.*$\)"lc=4 contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
-syn match dosbatchComment "\s@rem\($\|\s.*$\)"lc=5 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+syn match dosbatchRemComment "^rem\($\|\s.*$\)"lc=3 contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+syn match dosbatchRemComment "^@rem\($\|\s.*$\)"lc=4 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+syn match dosbatchRemComment "\srem\($\|\s.*$\)"lc=4 contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+syn match dosbatchRemComment "\s@rem\($\|\s.*$\)"lc=5 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+syn match dosbatchColonComment "\s*:\s*:.*$" contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+
+" Commands code blocks
+syn cluster dosbatchCodeBlockComment contains=dosbatchRemComment
if exists("dosbatch_colons_comment")
- syn match dosbatchComment "\s*:\s*:.*$" contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+ syn cluster dosbatchCodeBlockComment add=dosbatchColonComment
else
- syn match dosbatchError "\s*:\s*:.*$"
+ syn match dosbatchColonCommentErr contained "\s*:\s*:.*$"
endif
-
-" Comments in ()'s - still to handle spaces before rem
-syn match dosbatchComment "(rem\([^)]\|\^\@<=)\)*"lc=4 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+syn match dosbatchColonCommentErr contained "\s*:\s*:[^)]*\(\(\n\s*\)\?)\)\@="
+syn region dosbatchCodeBlock transparent start=+(+ end=+)+ contains=dosbatchString,dosbatchVariable,dosBatchArgument,@dosbatchNumber,dosbatchImplicit,dosbatchStatement,dosbatchConditional,dosbatchRepeat,dosbatchOperator,@dosbatchCodeBlockComment,dosbatchColonCommentErr,dosbatchCodeBlock
+syn match dosbatchCodeBlockErr ")"
syn keyword dosbatchImplicit append assoc at attrib break cacls cd chcp chdir
syn keyword dosbatchImplicit chkdsk chkntfs cls cmd color comp compact convert copy
@@ -116,6 +120,8 @@ syn keyword dosbatchImplicit vol xcopy
hi def link dosbatchTodo Todo
hi def link dosbatchError Error
+hi def link dosbatchCodeBlockErr dosbatchError
+hi def link dosbatchColonCommentErr dosbatchError
hi def link dosbatchStatement Statement
hi def link dosbatchCommands dosbatchStatement
@@ -140,6 +146,9 @@ hi def link dosbatchBinary dosbatchNumber
hi def link dosbatchOctal dosbatchNumber
hi def link dosbatchComment Comment
+hi def link dosbatchRemComment dosbatchComment
+hi def link dosbatchColonComment dosbatchComment
+
hi def link dosbatchImplicit Function
hi def link dosbatchSwitch Special
diff --git a/runtime/syntax/gpg.vim b/runtime/syntax/gpg.vim
index c7f3584ff0..2728ecfccd 100644
--- a/runtime/syntax/gpg.vim
+++ b/runtime/syntax/gpg.vim
@@ -1,9 +1,13 @@
" Vim syntax file
" Language: gpg(1) configuration file
+" Maintainer: This file is looking for a maintainer!
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2010-10-14
-" Updated: 2023-01-23 @ObserverOfTime: added a couple of keywords
+" Latest Revision: 2024-02-11
+" Updated:
+" 2023-01-23 @ObserverOfTime: added a couple of keywords
" 2023-03-21 Todd Zullinger <tmz@pobox.com>: sync with gnupg-2.4.0
+" 2024-02-10 Daniel Kahn Gillmor <dkg@fifthhorseman.net>:
+" mark use-embedded-filename as warning for security reasons
if exists("b:current_syntax")
finish
@@ -21,7 +25,7 @@ syn region gpgComment contained display oneline start='#' end='$'
syn match gpgID contained display '\<\(0x\)\=\x\{8,}\>'
-syn match gpgBegin display '^' skipwhite nextgroup=gpgComment,gpgOption,gpgCommand
+syn match gpgBegin display '^' skipwhite nextgroup=gpgComment,gpgOption,gpgOptionDeprecated,gpgCommand
syn keyword gpgCommand contained skipwhite nextgroup=gpgArg
\ change-passphrase check-sig check-signatures
@@ -41,6 +45,7 @@ syn keyword gpgCommand contained skipwhite nextgroup=gpgArg
\ quick-set-expire quick-set-primary-uid quick-sign-key
\ quick-update-pref receive-keys recv-keys refresh-keys
\ search-keys show-key show-keys sign-key tofu-policy
+
syn keyword gpgCommand contained skipwhite nextgroup=gpgArgError
\ card-edit card-status change-pin check-trustdb
\ clear-sign clearsign dearmor dearmour decrypt
@@ -97,6 +102,7 @@ syn keyword gpgOption contained skipwhite nextgroup=gpgArg
\ trusted-key trust-model try-secret-key ttyname
\ ttytype ungroup user verify-options weak-digest
\ xauthority
+
syn keyword gpgOption contained skipwhite nextgroup=gpgArgError
\ allow-freeform-uid allow-multiple-messages
\ allow-multisig-verification allow-non-selfsigned-uid
@@ -145,7 +151,7 @@ syn keyword gpgOption contained skipwhite nextgroup=gpgArgError
\ no-sk-comments no-skip-hidden-recipients
\ no-symkey-cache not-dash-escaped no-textmode
\ no-throw-keyids no-tty no-use-agent
- \ no-use-embedded-filename no-utf8-strings no-verbose
+ \ no-utf8-strings no-verbose
\ no-version only-sign-text-ids openpgp
\ override-compliance-check pgp6 pgp7 pgp8
\ preserve-permissions print-dane-records quiet
@@ -155,7 +161,7 @@ syn keyword gpgOption contained skipwhite nextgroup=gpgArgError
\ show-notation show-photos show-policy-url
\ show-session-key sk-comments skip-hidden-recipients
\ skip-verify textmode throw-keyids try-all-secrets
- \ unwrap use-agent use-embedded-filename use-keyboxd
+ \ unwrap use-agent use-keyboxd
\ use-only-openpgp-card utf8-strings verbose version
\ warranty with-colons with-fingerprint
\ with-icao-spelling with-key-data with-keygrip
@@ -164,6 +170,10 @@ syn keyword gpgOption contained skipwhite nextgroup=gpgArgError
\ with-subkey-fingerprints with-tofu-info with-wkd-hash
\ yes
+" depcrated for security reasons
+syn keyword gpgOptionDeprecated contained skipwhite nextgroup=gpgArgError
+ \ use-embedded-filename no-use-embedded-filename
+
syn match gpgArg contained display '\S\+\(\s\+\S\+\)*' contains=gpgID
syn match gpgArgError contained display '\S\+\(\s\+\S\+\)*'
@@ -171,6 +181,7 @@ hi def link gpgComment Comment
hi def link gpgTodo Todo
hi def link gpgID Number
hi def link gpgOption Keyword
+hi def link gpgOptionDeprecated WarningMsg
hi def link gpgCommand Error
hi def link gpgArgError Error
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index be02ce661b..ac36720369 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -433,7 +433,8 @@ syn case match
" Maps: {{{2
" ====
-syn match vimMap "\<map\>!\=\ze\s*[^(]" skipwhite nextgroup=vimMapMod,vimMapLhs
+syn match vimMap "\<map\>\ze\s*(\@!" skipwhite nextgroup=vimMapMod,vimMapLhs
+syn match vimMap "\<map!" contains=vimMapBang skipwhite nextgroup=vimMapMod,vimMapLhs
syn keyword vimMap cm[ap] cno[remap] im[ap] ino[remap] lm[ap] ln[oremap] nm[ap] nn[oremap] no[remap] om[ap] ono[remap] smap snor[emap] tno[remap] tm[ap] vm[ap] vmapc[lear] vn[oremap] xm[ap] xn[oremap] skipwhite nextgroup=vimMapBang,vimMapMod,vimMapLhs
syn keyword vimMap mapc[lear] smapc[lear]
syn keyword vimUnmap cu[nmap] iu[nmap] lu[nmap] nun[map] ou[nmap] sunm[ap] tunma[p] unm[ap] unm[ap] vu[nmap] xu[nmap] skipwhite nextgroup=vimMapBang,vimMapMod,vimMapLhs
@@ -630,7 +631,7 @@ syn match vimHiBang contained "!" skipwhite nextgroup=@vimHighlightCluster
syn match vimHiGroup contained "\i\+"
syn case ignore
-syn keyword vimHiAttrib contained none bold inverse italic nocombine reverse standout strikethrough underline undercurl underdouble underdotted underdashed
+syn keyword vimHiAttrib contained none bold inverse italic nocombine reverse standout strikethrough underline undercurl underdashed underdotted underdouble
syn keyword vimFgBgAttrib contained none bg background fg foreground
syn case match
syn match vimHiAttribList contained "\i\+" contains=vimHiAttrib
@@ -645,7 +646,7 @@ syn match vimHiGuiFontname contained "'[a-zA-Z\-* ]\+'"
syn match vimHiGuiRgb contained "#\x\{6}"
" Highlighting: hi group key=arg ... {{{2
-syn cluster vimHiCluster contains=vimGroup,vimHiBlend,vimHiGroup,vimHiTerm,vimHiCTerm,vimHiStartStop,vimHiCtermFgBg,vimHiCtermul,vimHiGui,vimHiGuiFont,vimHiGuiFgBg,vimHiKeyError,vimNotation
+syn cluster vimHiCluster contains=vimGroup,vimHiBlend,vimHiGroup,vimHiTerm,vimHiCTerm,vimHiStartStop,vimHiCtermFgBg,vimHiCtermul,vimHiGui,vimHiGuiFont,vimHiGuiFgBg,vimHiKeyError,vimNotation,vimComment,vim9comment
syn region vimHiKeyList contained oneline start="\i\+" skip="\\\\\|\\|" end="$\||" contains=@vimHiCluster
if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_vimhikeyerror")
syn match vimHiKeyError contained "\i\+="he=e-1
@@ -916,6 +917,7 @@ if !exists("skip_vim_syntax_inits")
hi def link vimAutoEvent Type
hi def link vimAutoCmdMod Special
hi def link vimAutoSet vimCommand
+ hi def link vimBang vimOper
hi def link vimBehaveModel vimBehave
hi def link vimBehave vimCommand
hi def link vimBracket Delimiter
@@ -955,6 +957,7 @@ if !exists("skip_vim_syntax_inits")
hi def link vimGroupSpecial Special
hi def link vimGroup Type
hi def link vimHiAttrib PreProc
+ hi def link vimHiBang vimBang
hi def link vimHiBlend vimHiTerm
hi def link vimHiClear vimHighlight
hi def link vimHiCtermFgBg vimHiTerm
@@ -983,12 +986,13 @@ if !exists("skip_vim_syntax_inits")
hi def link vimLetRegister Special
hi def link vimLineComment vimComment
hi def link vim9LineComment vimComment
- hi def link vimMapBang vimCommand
+ hi def link vimMapBang vimBang
hi def link vimMapModKey vimFuncSID
hi def link vimMapMod vimBracket
hi def link vimMap vimCommand
hi def link vimMark Number
hi def link vimMarkNumber vimNumber
+ hi def link vimMenuBang vimBang
hi def link vimMenuMod vimMapMod
hi def link vimMenuNameMore vimMenuName
hi def link vimMenuName PreProc
@@ -1063,7 +1067,7 @@ if !exists("skip_vim_syntax_inits")
hi def link vimTodo Todo
hi def link vimType Type
hi def link vimUnlet vimCommand
- hi def link vimUnletBang vimCommand
+ hi def link vimUnletBang vimBang
hi def link vimUnmap vimMap
hi def link vimUserAttrbCmpltFunc Special
hi def link vimUserAttrbCmplt vimSpecial
diff --git a/runtime/tutor/ja/vim-01-beginner.tutor b/runtime/tutor/ja/vim-01-beginner.tutor
new file mode 100644
index 0000000000..b6c73c0cda
--- /dev/null
+++ b/runtime/tutor/ja/vim-01-beginner.tutor
@@ -0,0 +1,995 @@
+# Neovimのチュートリアルへようこそ
+
+Neovim は、このチュートリアルで説明するには多すぎる程のコマンドを備えた非常に
+強力なエディターです。このチュートリアルは、あなたが Neovim を万能エディターとして
+使いこなせるようになるのに十分なコマンドについて説明をするようになっています。
+
+このチュートリアルが、体を使うことで覚えられる仕組みになっていることを、
+心しておかなければなりません。正しく学習するには実際にやってなければならない
+のです。テキストをただ読むだけでは、何がが重要だったか忘れてしまうでしょう!
+
+それでは、CapsLockキーが押されていないことを確認した後、画面にレッスン 0が
+全部表示されるところまで、`j`{normal} キーを押してカーソルを移動しましょう。
+
+# レッスン 0
+
+NOTE: 以下の練習用コマンドにはこの文章を変更するものもありますが、それらの
+変更は保存されません。失敗を恐れる必要はありません、 [<Esc>](<Esc>) キーを押した後、
+ [u](u) キーを押すことで最後の操作を元に戻せる事を覚えておいてください。
+
+このチュートリアルはインタラクティブな設計になっており、
+あなたが知っておくべきことがいくつかあります。
+-[このようなリンク](holy-grail )の上で [<Enter>](<Enter>) キーを押すことで、リンクされたヘルプを開くことができます。
+-もしくは、ドキュメントで検索したい単語の上で [K](K) キー(大文字)を押してみましょう。
+(注: 現在、日本語の単語には対応していません。)
+-ヘルプウィンドウは `:q`{vim} `<Enter>`{normal} で閉じることができます。
+
+画面の左端に ✗ が表示されている場合、その行のテキストを編集しなければなりません。
+正しいテキストに書き換えることで、左端の ✗ は ✓ に変わります。
+Neovim がいかに優れているか、おわかりいただけるでしょうか?
+
+また、次のようにコマンドを実行するよう求められることや、(後で詳しく説明します。)
+
+ `:help`{vim} `<Enter>`{normal}
+
+キーシークエンスを押すこともあります。
+~~~ normal
+ <Esc>0f<Space>d3wP$P
+~~~
+< と > の間に囲まれたテキスト(例えば `<Enter>`{normal})は、
+そのテキストをタイプするのではなく、そのキーを押すことを表しています。
+
+では、次のレッスンに向かいましょう。( `j`{normal} キーでスクロールダウンします。)
+
+# レッスン 1.1: カーソルの移動
+
+** カーソルを移動するには、示される様に `h`, `j`, `k`, `l` を押します。 **
+
+ ↑
+ k ヒント: h キーは左方向に移動します。
+ ← h l → l キーは右方向に移動します。
+ j j キーは下矢印キーのようなキーです。
+ ↓
+
+ 1. 移動に慣れるまで、スクリーンでカーソル移動させましょう。
+
+ 2. 下へのキー(j)を押しつづけると、連続して移動できます。
+ これで次のレッスンに移動する方法がわかりましたね。
+
+ 3. 下へのキーを使って、レッスン1.2 に移動しましょう。
+
+NOTE: 何をタイプしているか判らなくなったら、`<Esc>`{normal} を押してノーマルモードにします。
+ それから入力しようとしていたコマンドを再入力しましょう。
+
+NOTE: カーソルキーでも移動できます。しかし hjkl に一度慣れてしまえば、
+ はるかに速く移動することができるでしょう。
+
+# レッスン 1.2: NEOVIM の起動と終了
+
+!! NOTE: 以下のあらゆるステップを行う前に、このレッスンを全部読みましょう!!
+
+ 1. `<Esc>`{normal} キーを押しましょう。(確実にノーマルモードにするため)
+
+ 2. 次のようにタイプ:
+
+ `:q!`{vim} `<Enter>`{normal}
+
+ これにより編集した内容を保存せずにエディタが終了します。
+
+ 3. Neovim を開いて、このチュートリアルを始める為のコマンドを実行し、
+ ここに戻ってきます。そのコマンドは:
+
+ `:Tutor`{vim} `<Enter>`{normal}
+
+ 4. これまでのステップを覚え自信がついたならば、ステップ 1 から 3 までを実際に試して、
+ エディタを1度終了してから再び起動しましょう。
+
+NOTE: [:q!](:q) `<Enter>`{normal} は全ての変更を破棄します。
+ 後に変更をファイルに保存する方法についても勉強していきましょう。
+
+ 5. 1.3までカーソルを移動させましょう。
+
+# レッスン 1.3: テキスト編集 - 削除
+
+** カーソルの下の文字を削除するには `x`{normal} を押します。 **
+
+ 1. 以下の ✗ と示された行にカーソルを移動しましょう。
+
+ 2. 間違いを修正するために、削除する最初の文字までカーソルを移動します。
+
+ 3. 不必要な文字を [x キー](x) を押して削除しましょう。
+
+ 4. 文が正しくなるまで ステップ 2 から 4 を繰り返しましょう。
+
+その ううさぎ は つつきき を こええてて とびはねたた
+
+ 5. 行が正しくなったら、レッスン 1.4 へ進みましょう。
+
+NOTE: 全てのレッスンを通じて、頭で覚えようとしないでください。
+ 実際に手を動かすことで、あなたの Neovim に対する理解度は広がっていきます。
+ 復習のためにも、たまにこのチュートリアルをやり直してみてください。
+
+# レッスン 1.4: テキスト編集 - 挿入
+
+** テキストを挿入(Insert)するには `i`{normal} を押します。 **
+
+ 1. 以下の ✗ と示された最初の行にカーソルを移動しましょう。
+
+ 2. 1行目を2行目と同じ様にするために、テキストを挿入しなければ
+ ならない位置の次の文字にカーソルを移動します。
+
+ 3. `i`{normal} キーを押してから、追加が必要な文字をタイプしましょう。
+
+ 4. 間違いを修正したら `<Esc>`{normal} を押してノーマルモードに戻り、
+ 正しい文になる様にステップ 2 から 4 を繰り返しましょう。
+
+この には 足り テキスト が 。
+この 行 には 幾つか 足りない テキスト が ある。
+
+ 5. 挿入の方法がわかったらレッスン 1.5 へ進みましょう。
+
+# レッスン 1.5: テキスト編集 - 追加
+
+** テキストを追加(Append)するには `A`{normal} を押しましょう。 **
+
+ 1. 以下の ✗ と示された最初の行にカーソルを移動しましょう。
+ カーソルがその行のどの文字上にあってもかまいません。
+
+ 2. [A](A) キー(大文字)を押してから、追加が必要な文字をタイプしましょう。
+
+ 3. テキストを追加し終えたら、 `<Esc>`{normal} を押してノーマルモードに戻りましょう。
+
+ 4. 2行目の ✗ と示された場所へ移動し、ステップ 2 から 3 を繰り返して
+ 文法を修正しましょう。
+
+この 行 には 間違った テキスト が あり
+この 行 には 間違った テキスト が あります。
+ここ にも 間違った テキス
+ここ にも 間違った テキスト が あります。
+
+ 5. テキストの追加が軽快になってきたらレッスン 1.6 へ進みましょう。
+
+# レッスン 1.6: ファイルの編集
+
+** ファイルを保存して終了するには `:wq`{vim} とタイプします。 **
+
+
+!! NOTE: 以下のあらゆるステップを行う前に、このレッスンを全部読みましょう!!
+
+ 1. レッスン 1.2 でやったように `:q!`{vim} をタイプして、このチュートリアルを終了します。
+ もしくは、別のターミナルにアクセスできる場合、そこで以下の内容を行ってください。
+
+ 2. シェルプロンプトでこのコマンドをタイプします:
+~~~ sh
+ $ nvim tutor
+~~~
+ 'nvim' が Nvim エディタを起動するコマンド、'tutor' は編集したい
+ ファイルの名前です。変更できるファイルの名前を使いましょう。
+
+ 3. 前のレッスンで学んだように、テキストを挿入、削除します。
+
+ 4. 次のコマンドで変更をファイルに保存し、Neovim を終了します:
+~~~ cmd
+ :wq
+~~~
+ コマンドを実行するには `<Enter>`{normal} を押さなければなりません。
+
+ 5. ステップ 1 でこのチュートリアルを終了した場合は
+ 再度起動した後、以下の要約へ進みましょう。
+
+ 6. 以上のステップを読んで理解した上でこれを実行しましょう。
+
+# レッスン 1 要約
+
+ 1. カーソルは矢印キーもしくは hjkl キーで移動します。
+ h (左) j (下) k (上) l (右)
+
+ 2. シェルプロンプトからNeovimを起動するにはこのようにタイプします:
+~~~ sh
+ $ nvim FILENAME
+~~~
+ 3. Vim を終了するには: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} とタイプします(変更を破棄)。
+ もしくは: `<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} とタイプします(変更を保存)。
+
+ 4. カーソルの下の文字を削除するには、ノーマルモードで `x`{normal} とタイプします。
+
+ 5. カーソルの位置に文字を挿入するには、ノーマルモードで i とタイプします。
+ `i`{normal} テキストのタイプ `<Esc>`{normal} カーソル位置に追加
+ `A`{normal} テキストの追加 `<Esc>`{normal} 行末に追加
+
+NOTE: `<Esc>`{normal} キーを押すとノーマルモードに移行します。
+ その際間違ったり、入力途中のコマンドを取り消すことができます。
+
+さて、続けてレッスン 2 を始めましょう。
+
+# レッスン 2.1: 削除コマンド
+
+** 単語の末尾までを削除するには `dw`{normal} とタイプしましょう。 **
+
+ 1. 確実にノーマルモードにするため `<Esc>`{normal} を押しましょう。
+
+ 2. 以下の ✗ と示された行にカーソルを移動しましょう。
+
+ 3. 消したい単語の先頭にカーソルを移動しましょう。
+
+ 4. 単語を削除するために [d](d)[w](w) とタイプしましょう。
+
+この 文 紙 には いくつかの たのしい 必要のない 単語 が 含まれて います。
+
+ 5. 3 から 4 までを文が正しくなるまで繰り返し、レッスン 2.2 へ進みましょう。
+
+# レッスン 2.2: その他の削除コマンド
+
+** 行の末尾までを削除するには `d$`{normal} とタイプしましょう。 **
+
+ 1. 確実にノーマルモードにするため `<Esc>`{normal} を押しましょう。
+
+ 2. 以下の ✗ と示された行にカーソルを移動しましょう。
+
+ 3. 正しい文の末尾へカーソルを移動しましょう(最初の 。 の後です)。
+
+ 4. 行末まで削除するために `d$`{normal} とタイプしましょう。
+
+誰かがこの行の最後を2度タイプしました。 2度タイプしました。
+
+ 5. どういうことか理解するために、レッスン 2.3 へ進みましょう。
+
+# レッスン 2.3: オペレータとモーション
+
+テキストに変更を加える多くのコマンドは[オペレータ](operator)と[モーション](navigation)からなります。
+削除(Delete)コマンド [d](d) のオペレータは次の様になっています:
+
+ d モーション
+
+ それぞれ:
+ d - 削除コマンド。
+ モーション - 何に対して働きかけるか(以下に挙げます)。
+
+ モーション一覧の一部:
+ [w](w) - 次の単語の先頭まで。(カーソル位置の単語の先頭を除く)
+ [e](e) - 次の単語の末尾まで。(カーソル位置の単語の末尾を含む)
+ [$]($) - 行末まで。(カーソル位置の単語の末尾を含む)
+
+ つまり `de`{normal} とタイプすると、カーソル位置から単語の終わりまでを削除します。
+
+NOTE: 冒険したい人は、ノーマルモードにてオペレータなしにモーションを押してみましょう。
+ カーソルが目的語一覧で示される位置に移動するはずです。
+
+# レッスン 2.4: モーションにカウントを使用する
+
+** 何回も繰り返し行いたいのモーションの前にその回数をタイプします。 **
+
+ 1. 以下の ✓ と示された行の先頭にカーソルを移動します。
+
+ 2. `2w`{normal} をタイプして単語2つ分先に移動します。
+
+ 3. `3e`{normal} をタイプして3つ目の単語の終端に移動します。
+
+ 4. `0`{normal} ([ゼロ](0))をタイプして行頭に移動します。
+
+ 5. ステップ 2 と 3 を違う数値を使って繰り返します。
+
+This is just a line with words you can move around in.
+
+ 6. レッスン 2.5 に進みましょう。
+
+# レッスン 2.5: より多くを削除するためにカウントを使用する
+
+** オペレータとカウントをタイプすると、その操作が複数回繰り返されます。 **
+
+既述の削除のオペレータとモーションの組み合わせにカウントを追加することで、
+より多くの削除が行えます:
+ d 数値 モーション
+
+ 1. ✗ と示された行の最初の大文字の単語にカーソルを移動しましょう。
+
+ 2. 大文字の単語2つを `d2w`{normal} とタイプして削除します。
+
+ 3. 連続した大文字の単語を、異なるカウントを指定した1つのコマンドで削除し、
+ ステップ 1 と 2 を繰り返します。
+
+このABC DE行のFGHI JK LMN OP単語はQ RS TUV綺麗になった。
+
+# レッスン 2.6: 行の操作
+
+** 行全体を削除するには `dd`{normal} とタイプします。 **
+
+行全体を削除する頻度が多いので、Viのデザイナーは行の
+削除を d の2回タイプという簡単なものに決めました。
+
+ 1. 以下の詩の2行目にカーソルを移動します。
+
+ 2. [dd](dd) とタイプして行を削除します。
+
+ 3. さらに4行目に移動します。
+
+ 4. `2dd`{normal} とタイプして2行を削除します。
+
+1) 薔薇は赤く
+2) 泥は楽しい
+3) 菫は青く
+4) 私は車をもっている
+5) 時計が時刻を告げる
+6) 砂糖は甘く
+7) そして貴方も
+
+# Lesson 2.7: THE UNDO COMMAND
+
+** 最後のコマンドを取り消す(Undo)には `u`{normal} を押します。`U`{normal} は行全体の取り消しです。 **
+
+ 1. 以下の ✗ と示された行にカーソルを移動し、最初の間違いにカーソルを移動しましょう。
+
+ 2. `x`{normal} をタイプして最初のいらない文字を削除しましょう。
+
+ 3. では、`u`{normal} をタイプして最後に実行したコマンドを取り消してしまいしょう。
+
+ 4. 今度は、`x`{normal} を使用して行内の誤りを全て修正しましょう。
+
+ 5. 大文字の `U`{normal} をタイプして、行を元の状態に戻しましょう。
+
+ 6. `u`{normal} をタイプして直前の `U`{normal} コマンドを取り消しましょう。
+
+ 7. ではコマンドを再実行するのに `<C-r>`{normal} (Ctrl + R)を数回
+ タイプしてみましょう(取り消しの取り消し)。
+
+このの行のの間違いを修正々し、後でそれらの修正をを取り消しまますす。
+
+ 8. これはとても便利なコマンドです。さぁレッスン 2 要約へ進みましょう。
+
+# レッスン 2 要約
+
+ 1. カーソル位置から次の単語までを削除: `dw`{normal}
+
+ 2. カーソル位置から行の末尾までを削除: `d$`{normal}
+
+ 3. 行全体を削除: `dd`{normal}
+
+ 4. モーションを繰り返すには数値を付与: `2w`{normal}
+
+ 5. 変更に用いるコマンドの形式は:
+
+ オペレータ [数値] モーション
+
+ それぞれ:
+
+ オペレータ - オペレータ - 削除 d の類で何をするか
+ [数値] - そのコマンドを何回繰り返すか
+ モーション - テキストの何に対して働きかけるか、例えば:
+ [w](w) (単語),
+ [$]($) (行末), など。
+
+ 6. 行の先頭に移動するにはゼロを使用します: [0](0)
+
+ 7. 前回の動作を取り消し: `u`{normal} (小文字 u)
+ 行全体の変更を取り消し: `U`{normal} (大文字 U)
+ 取り消しの取り消し: `<C-r>`{normal} (Ctrl + R)
+
+# レッスン 3.1: 貼り付けコマンド
+
+** 最後に削除された行をカーソルの後に貼り付ける(Put)には `p`{normal} をタイプします。 **
+
+ 1. ✓ と示された以下の最初の行にカーソルを移動しましょう。
+
+ 2. `dd`{normal} とタイプして行を削除し、Neovim のレジスタに格納しましょう。
+
+ 3. 削除した行が本来あるべき位置の上の行である c) 行まで、カーソルを移動させましょう。
+
+ 4. `p`{normal} をタイプして格納した行をカーソルの下に戻します。
+
+ 5. 順番が正しくなる様にステップ 2 から 4 を繰り返しましょう。
+
+d) 貴方も学ぶことができるか?
+b) 菫は青く
+c) 知恵とは学ぶもの
+a) 薔薇は赤く
+
+NOTE: `P`{normal} (大文字 P)とタイプすることで、カーソルの前に貼り付ける事もできます。
+
+# レッスン 3.2: 置き換えコマンド
+
+** カーソルの下の文字を x に置き換える(Replace)には `rx`{normal} をタイプします。 **
+
+ 1. 以下の ✗ と示された最初の行にカーソルを移動しましょう。
+
+ 2. 最初の間違いの先頭にカーソルを移動しましょう。
+
+ 3. `r`{normal} とタイプし、間違っている文字を置き換える、正しい文字をタイプしましょう。
+
+ 4. 最初の行が正しくなるまでステップ 2 から 3 を繰り返しましょう。
+
+この合を人力した時ね、その人は幾つか問違ったキーを押しもした!
+この行を入力した時に、その人は幾つか間違ったキーを押しました!
+
+ 5. さぁ、レッスン 3.3 へ進みましょう。
+
+NOTE: 実際に試しましょう。決して覚えるだけにはしないこと。
+
+# レッスン 3.3: 変更オペレータ
+
+** 単語の末尾までを変更(Change)するには `ce`{normal} とタイプします。 **
+
+ 1. 以下の ✗ と示された最初の行にカーソルを移動しましょう。
+
+ 2. "lubw" の "u" の位置にカーソルを移動しましょう。
+
+ 3. `ce`{normal} とタイプし、正しい単語をタイプしましょう(この場合 "ine" とタイプ)。
+
+ 4. `<Esc>`{normal} をタイプしてから次の間違い(変更すべき文字の先頭)に移動します。
+
+ 5. 最初の行が次の行の様になるまでステップ 3 と 4 を繰り返します。
+
+This lubw has a few wptfd that mrrf changing usf the change operator.
+This line has a few words that need changing using the change operator.
+
+ce は単語を削除した後、挿入モードに入ることに注意しましょう。
+
+# レッスン 3.4: `c`{normal} を使用したその他の変更
+
+** 変更オペレータは、削除と同じ様にモーションを使用します。 **
+
+ 1. 変更オペレータは、削除と同じような動作をします。その形式は
+
+ c [数値] モーション
+
+ 2. モーションも同じで、`w`{normal} (単語) や `$`{normal} (行末まで)といったものです。
+
+ 3. 以下の ✗ と示された最初の行にカーソルを移動しましょう。
+
+ 4. 最初の間違いへカーソルを移動しましょう。
+
+ 5. `c$`{normal} とタイプして行の残りを2行目の様にし、`<Esc>`{normal} を押しましょう。
+
+The end of this line needs some help to make it like the second.
+The end of this line needs to be corrected using the c$ command.
+
+NOTE: タイプ中の間違いはバックスペースキーを使って直すこともできます。
+
+# レッスン 3 要約
+
+ 1. 既に削除されたテキストを再配置するには、[p](p) をタイプします。
+ これは削除されたテキストをカーソルの後に挿入します(行単位で削除されたのならば、
+ カーソルのある次の行に挿入されます)。
+
+ 2. カーソルの下の文字を置き換えるには、[r](r) をタイプした後、
+ それを置き換える文字をタイプします。
+
+ 3. [変更オペレータ](c)ではカーソル位置から特定のモーションで指定される終端までを
+ 変更することが可能です。例えば `ce`{normal} ならばカーソル位置から単語の
+ 終わりまで、`c$`{normal} ならば行の終わりまでを変更します。
+
+ 4. 変更コマンドの形式は
+
+ c [数値] モーション
+
+さぁ、次のレッスンへ進みましょう。
+
+# レッスン 4.1: 位置とファイルの情報
+
+** ファイル内での位置とファイルの状態を表示するには `<C-g>`{normal} をタイプします。
+ ファイル内のある行に移動するには `G`{normal} をタイプします。 **
+
+NOTE: ステップを実行する前に、このレッスン全てに目を通しましょう!!
+
+ 1. `<Ctrl>`{normal} を押したまま `g`{normal} を押しましょう。この操作を `<C-g>`{normal} と呼んでいます。
+ ページの一番下にファイル名と行番号が表示されるはずです。
+ ステップ 3のために行番号を覚えておきましょう。
+
+NOTE: 画面の右下隅にカーソルの位置が表示されているかもしれません。
+ これは ['ruler']('ruler') オプションを設定することで表示されます。
+
+ 2. ファイルの最下行に移動するために [G](G) をタイプしましょう。
+ ファイルの先頭に移動するには [gg](gg) とタイプしましょう。
+
+ 3. 先ほどの行の番号をタイプし `G`{normal} をタイプしましょう。
+ 最初に `<C-g>`{normal} を押した行に戻って来るはずです。
+
+ 4. 自信が持てたらステップ 1 から 3 を実行しましょう。
+
+# レッスン 4.2: 検索コマンド
+
+** 語句を検索するには `/`{normal} と、前方検索する語句をタイプします。 **
+
+ 1. ノーマルモードで `/`{normal} という文字をタイプします。画面一番下に `:`{normal} コマンドと
+ 同じ様に カーソルが現れることに気づくでしょう。
+
+ 2. では、'errroor' `<Enter>`{normal} とタイプしましょう。これが検索したい単語です。
+
+ 3. 同じ語句をもう一度検索するときは 単に [n](n) をタイプします。
+ 逆方向に語句を検索するときは [N](N) をタイプします。
+
+ 4. 逆方向に語句を検索する場合は、`/`{normal} の代わりに [?](?) コマンドを使用します。
+
+ 5. 元の場所に戻るには `<C-o>`{normal} (`<Ctrl>`{normal} を押し続けながら `o`{normal} をタイプ)を
+ タイプします。さらに戻るにはこれを繰り返します。`<C-i>`{normal} は前方向です。
+
+"errroor" は error とスペルが違います; errroor はいわゆる error です。
+
+NOTE: 検索がファイルの終わりに達すると、オプション ['wrapscan']('wrapscan') が設定されている場合は、
+ ファイルの先頭から検索を続行します。
+
+# レッスン 4.3: 対応する括弧を検索
+
+** 対応する ),] や } を検索するには `%`{normal} をタイプします。 **
+
+ 1. 下の ✓ で示された行で (,[ か { のどれかにカーソルを移動しましょう。
+
+ 2. そこで [%](%) とタイプしましょう。
+
+ 3. カーソルは対応する括弧に移動するはずです。
+
+ 4. 最初の括弧に移動するには `%`{normal} とタイプしましょう。
+
+ 5. 他の (,),[,],{ や } でカーソルを移動し、`%`{normal} が何をしているか確認しましょう。
+
+This ( is a test line with ('s, ['s ] and {'s } in it. ))
+
+NOTE: この機能は括弧が一致していないプログラムをデバッグするのにとても役立ちます!
+
+# レッスン 4.4: 代替コマンド
+
+** "old" を "new" に置換(Substitute)するには `:s/old/new/g` とタイプします。 **
+
+ 1. 以下の ✗ と示された行にカーソルを移動しましょう。
+
+ 2. 次のようにタイプします
+~~~ cmd
+ :s/thee/the/
+~~~
+ NOTE: [:s](:s) コマンドはその行で最初に見つかったものにだけ
+ 行われることに気をつけましょう。
+
+ 3. さらに、次のようにタイプします
+~~~ cmd
+ :s/thee/the/g
+~~~
+ 追加した g [フラグ](:s_flags)は行全体を置換することを意味します。
+ この変更はその行で見つかった全ての箇所に対して行われます。
+
+Usually thee best time to see thee flowers is in thee spring.
+
+ 4. 複数行から見つかる文字の全ての箇所を変更するには
+~~~ cmd
+ :#,#s/old/new/g
+~~~
+ #,# には置き換える範囲の開始と終了の行番号を指定する。
+ (例えば、`1,3`は1行目から3行目を意味します。)
+
+~~~ cmd
+ :%s/old/new/g
+~~~
+ ファイル全体で見つかるものに対して変更する。
+~~~ cmd
+ :%s/old/new/gc
+~~~
+ ファイル全体で見つかるものに対して、1つ1つ確認をとりながら変更する。
+
+NOTE: 置き換えたいテキストをビジュアルモードで選択することもできます。
+ これについては少し後のレッスンで説明します。
+
+# レッスン 4 要約
+
+ 1. `<C-g>`{normal} はファイルでの位置とファイルの詳細を表示します。
+ `G`{normal} はファイルの最下行に移動します。
+ 数値 `G`{normal} はその行に移動します。
+ `gg`{normal} は先頭行に移動します。
+
+ 2. `/`{normal} の後に語句をタイプすると前方に語句を検索します。
+ `?`{normal} の後に語句をタイプすると後方に語句を検索します。
+ 検索の後の `n`{normal} は同じ方向の次の検索を、`N`{normal} は逆方向の検索をします。
+ `<C-o>`{normal} は場所を前に移し、`<C-i>`{normal} は場所を次に移動します。
+
+ 3. (,),[,],{, もしくは } 上にカーソルがある状態で `%`{normal} をタイプすると
+ 対になる文字へ移動します。
+
+ 4. 現在行の最初の old を new に置換する。
+~~~ cmd
+ :s/old/new
+~~~
+ 現在行の全ての old を new に置換する。
+~~~ cmd
+ :s/old/new/g
+~~~
+ 2つの # 行の間で語句を置換する。
+~~~ cmd
+ :#,#s/old/new/g
+~~~
+ ファイルの中の全ての検索語句を置換する。
+~~~ cmd
+ :%s/old/new/g
+~~~
+ 'c' を加えると置換の度に確認を求める。
+~~~ cmd
+ :%s/old/new/gc
+~~~
+
+# レッスン 5.1: 外部コマンドを実行する方法
+
+** `:!`{vim} の後に実行する外部コマンドをタイプします。 **
+
+ 1. 画面の最下部にカーソルが移動するよう、慣れ親しんだ `:`{normal} をタイプしましょう。
+ これでコマンドライン命令がタイプできる様になります。
+
+ 2. ここで [!](!cmd) という文字(感嘆符)をタイプしましょう。
+ これで外部シェルコマンドが実行できる様になります。
+
+ 3. 例として "!" に続けて "ls" とタイプし `<Enter>`{normal} を押しましょう。
+ シェルプロンプトのようにディレクトリの一覧が表示されるはずです。
+
+NOTE: この方法によってあらゆるコマンドが実行することができます。
+ もちろん引数も与えられます。
+
+NOTE: 全ての `:`{vim} コマンドは `<Enter>`{normal} を押して終了しなければなりません。
+
+# レッスン 5.2: その他のファイルへ書き込み
+
+** ファイルへ変更を保存するには `:w`{vim} ファイル名 とタイプします。 **
+
+ 1. ディレクトリの一覧を得るために `:!{unix:(ls),win:(dir)}`{vim} とタイプしましょう。
+ このあと `<Enter>`{normal} を押すのは既にご存知ですね。
+
+ 2. TEST のように、そのディレクトリに無いファイル名を一つ選びます。
+
+ 3. 次のようにタイプしましょう:
+~~~ cmd
+ :w TEST
+~~~
+ (TEST は、選んだファイル名です。)
+
+ 4. これによりファイル全体が TEST という名前で保存されます。
+ もう一度 `:!{unix:(ls),win:(dir)}`{vim} とタイプしてディレクトリを確認してみましょう。
+
+NOTE: ここで Neovim を終了し、`nvim TEST` で起動すると、保存した時の
+ チュートリアルの複製ができ上がるはずです。
+
+ 5. さらに、次のようにタイプしてファイルを消しましょう:
+~~~ cmd
+ :!{unix:(rm),win:(del)} TEST
+~~~
+# レッスン 5.3: 選択した書き込み
+
+** ファイルの一部を保存するには、`v`{normal} モーションと `:w ファイル名`{vim} をタイプします。 **
+
+ 1. この行にカーソルを移動します。
+
+ 2. [v](v) を押し、以下の第5項目にカーソルを移動します。
+ テキストが強調表示されるのに注目して下さい。
+
+ 3. 文字 : を押すと、画面の最下部に
+
+ `:'<,'>`{vim}
+
+ が現れます。
+
+ 4. 次のようにタイプします。
+
+ `:w TEST`{vim}
+
+ TEST は存在しないファイル名です。`<Enter>`{normal} を押す前に
+
+ `:'<,'>w TEST`{vim}
+
+ となっていることを確認して下さい。
+
+ 5. Vim は TEST というファイルに選択された行を書き込むでしょう。
+ :`:!{unix:(ls),win:(dir)}`{vim} でそれを確認します。
+ それは削除しないでおいて下さい。次のレッスンで使用します。
+
+NOTE: [v](v) を押すと、ビジュアル(Visual)選択が始まります。カーソルを動かすことで、
+ 選択範囲を大きくも小さくもできます。さらに、その選択範囲に対して
+ オペレータを適用できます。例えば `d`{normal} はテキストを削除します。
+
+# レッスン 5.4: ファイルの取込と合併
+
+** ファイルの中身を挿入するには `:r ファイル名`{vim} とタイプします。 **
+
+ 1. カーソルをこの行のすぐ上に合わせます。
+
+NOTE: ステップ 2 の実行後、レッスン 5.3 のテキストが現れます。
+ 下に下がってこのレッスンに移動しましょう。
+
+ 2. では TEST というファイルを 次のコマンドで読み込みます。
+
+ `:r TEST`{vim}
+
+ ここでいう TEST は使うファイルの名前のことです。
+ 読み込まれたファイルは、カーソル行の下にあります。
+
+ 3. 取り込んだファイルを確認してみましょう。カーソルを戻すと、レッスン5.3 の
+ オリジナルとファイルによるものの2つがあることがわかります。
+
+ 4. 残りのチュートリアルの為に、`u`{normal} を押して最後のコマンドを取り消します。
+
+NOTE: 外部コマンドの出力を読み込むこともできます。例えば、
+
+ `:r !{unix:(ls),win:(dir)}`{vim}
+
+ は `ls` コマンドの出力をカーソル以下に読み込みます。
+
+# レッスン 5 要約
+
+ 1. [:!command](:!cmd) によって、外部コマンドを実行する。
+
+ よく使う例:
+ `:!{unix:(ls ),win:(dir)}`{vim} - ディレクトリ内の一覧を見る。
+ `:!{unix:(rm ),win:(del)} ファイル名`{vim} - ファイルを削除する。
+
+ 2. [:w](:w) ファイル名 ファイル名 によってファイル名というファイルがディスクに書き込まれる。
+
+ 3. [v](v) モーション で :w ファイル名 とすると、ビジュアル選択行がファイルに保存される。
+
+ 4. [:r](:r) ファイル名 によりファイル名というファイルがディスクより取り込まれ、
+ カーソル位置の下に挿入される。
+
+ 5. {unix:([:r !ls](:r!) ),win:([:r !dir](:r!))} は {unix:(ls),win:(dir)} コマンドの
+ 出力をカーソル位置以下に読み込む。
+
+# レッスン 6.1: オープンコマンド
+
+** `o`{normal} をタイプすると、カーソルの下の行が開き(Open)、挿入モードに入ります。 **
+
+ 1. 以下の ✓ と示された最初の行にカーソルを移動しましょう。
+
+ 2. `o`{normal} (小文字) をタイプして、カーソルの下の行を[開き](o)、挿入モードに入ります。
+
+ 3. いくつか文字をタイプしてから、挿入モードを終了する為に `<Esc>`{normal} を
+ タイプします。
+
+ `o`{normal} をタイプするとカーソルは開いた行へ移動し挿入モードに入ります。
+
+ 4. カーソルの上の行に挿入するには、小文字の `o`{normal} ではなく、
+ 単純に[大文字の O](O)をタイプします。次の行で試してみましょう。
+
+この行の上へ挿入するには、この行へカーソルを置いて `O`{normal} をタイプします。
+
+# レッスン 6.2: 追加コマンド
+
+** カーソルの次の位置からテキストを追加(Append)するには `a`{normal} とタイプします。 **
+
+ 1. カーソルを ✗ で示された最初の行へ移動しましょう。
+
+ 2. `e`{normal} を押して "li" の終端部までカーソルを移動します。
+
+ 3. カーソルの後ろにテキストを[追加](a)するために `a`{normal} (小文字) をタイプします。
+
+ 4. その下の行のような単語に完成させます。挿入モードを抜ける為に `<Esc>`{normal} を押します。
+
+ 5. `e`{normal} を使って次の不完全な単語へ移動し、ステップ 3 と 4 を繰り返します。
+
+This li will allow you to pract appendi text to a line.
+This line will allow you to practice appending text to a line.
+
+NOTE: [a](a), [i](i) と [A](A) は同じ挿入モードへ移りますが、文字が挿入される位置だけが異なります。
+
+# レッスン 6.3: その他の置換方法
+
+** 1文字以上を置き換える(Replace)には大文字の `R`{normal} とタイプしましょう。 **
+
+ 1. 以下の ✗ と示された行にカーソルを移動します。最初の "xxx" の先頭に移動します。
+
+ 2. `R`{normal} ([大文字 R](R)) を押して、2行目の数値をタイプすることで、"xxx" が置換されます。
+
+ 3. 置換モードを抜けるには `<Esc>`{normal} を押します。行の残りが変更されていないままに
+ なることに注意してください。
+
+ 4. 残った "xxx" をステップを繰り返して置換しましょう。
+
+Adding 123 to xxx gives you xxx.
+Adding 123 to 456 gives you 579.
+
+NOTE: 置換モードは挿入モードに似ていますが、全てのタイプされた文字は
+ 既存の文字を削除します。
+
+# レッスン 6.4: テキストのコピーとペースト
+
+** テキストのコピー(Yank)にはオペレータ `y`{normal} を、ペーストには `p`{normal} を使います。 **
+
+ 1. ✓ と示された行へ移動し、カーソルを "a)" の後に置いておきます。
+
+ 2. `v`{normal} でビジュアルモードを開始し、"first" の手前までカーソルを移動します。
+
+ 3. `y`{normal} をタイプして強調表示されたテキストを [yank](yank) (コピー)します。
+
+ 4. 次の行の行末までカーソルを移動します: `j$`{normal}
+
+ 5. `p`{normal} を押して貼り付け([put](put))てから、次をタイプします: a second <ESC>
+
+ 6. `a`{normal} を押してから、 "second" とタイプします。その後、`<Esc>`{normal}を
+ 押して挿入モードを終了します。
+
+ 7. ビジュアルモードで " item." を選択し、`y`{normal} で yank、次の行の行末まで `j$`{normal} で
+ 移動し、 `p`{normal} でテキストをそこに put します。
+
+a) This is the first item.
+b)
+
+NOTE: `y`{normal} をオペレータとして使うこともできます。`yw`{normal} は単語を1つ yank します。
+
+NOTE: `P`{normal} を使用するとカーソルの後ではなく前に put できます。
+
+# レッスン 6.5: オプションの設定
+
+** 検索や置換の際に大文字/小文字を無視するには、オプションを設定します。 **
+
+Neovim にはあなたの必要に応じて設定できる、様々なオプションが用意されています。
+
+ 1. 次の様に入力して 'ignore' を検索しましょう: `/ignore`
+ `n`{normal} を押して何度か検索を繰り返します。
+
+ 2. 次の様に入力して 'ic' (Ignore Case の略) オプションを設定します:
+~~~ cmd
+ :set ic
+~~~
+ 3. では `n`{normal} によってもう1度 'ignore' を検索します。
+ "Ignore"や"IGNORE"も見つけられることに気づくでしょう。
+
+ 4. 'hlsearch' と 'incsearch' オプションを設定しましょう:
+~~~ cmd
+ :set hls is
+~~~
+ 5. 検索コマンドを再入力して、何が起こるか見てみましょう: /ignore <Enter>
+
+ 6. 大文字小文字の区別を無効にするには次の様に入力します:
+~~~ cmd
+ :set noic
+~~~
+ 7. オプションの値を反転するには、先頭に "inv"を追加します:
+~~~ cmd
+ :set invic
+~~~
+NOTE: マッチの強調表示をやめるには次の様に入力します:
+~~~ cmd
+ :nohlsearch
+~~~
+NOTE: 1つの検索コマンドだけ大文字小文字の区別をやめたいならば、
+ 語句内で [\c](/\c) を使用します: /ignore\c <Enter>
+
+# レッスン 6 要約
+
+ 1. `o`{normal} をタイプするとカーソルの下の行を開けて、そこで挿入モードになる。
+ `O`{normal} をタイプするとカーソルの上の行で挿入モードになる。
+
+ 2. カーソル上の文字の次からテキストを追加するには `a`{normal} とタイプする。
+ 行末にテキストを挿入するには `A`{normal} をタイプする。
+
+ 3. `e`{normal} コマンドは単語の終端にカーソルを移動する。
+
+ 4. `y`{normal} オペレータはテキストを yank (コピー)し、`p`{normal} はそれを put (ペースト)する。
+
+ 5. 大文字の `R`{normal} をタイプすると置換モードに入り、`<Esc>`{normal} を押すと抜ける。
+
+ 6. "[:set](:set) xxx" とタイプするとオプション "xxx" が設定される。
+
+ 'ic' 'ignorecase' 検索時に大文字小文字の区別しない
+ 'is' 'incsearch' 検索フレーズに部分マッチしている部分を表示する
+ 'hls' 'hlsearch' マッチするすべてを強調表示する
+
+ 正称、省略形、どちらのオプション名でも使用できる。
+
+ 7. オプションを無効にするには "no" を付与する:
+~~~ cmd
+ :set noic
+~~~
+ 8. 先頭に "inv" オプションを反転する:
+~~~ cmd
+ :set invic
+~~~
+# レッスン 7.1: ヘルプコマンド
+
+** Use the online help system. **
+
+Neovim には広範にわたるオンラインヘルプシステムがあります。
+
+ヘルプを開始するには、次の中でどれか1つを試してみましょう:
+ - `<F1>`{normal} キーを押す(もしあるならば)。
+ - `:help`{vim} とタイプする。
+
+ヘルプウィンドウのテキストを読むと、ヘルプの動作が理解できます。
+ `<C-w><C-w>`{normal} とタイプすると ヘルプウィンドウへジャンプします。
+ `:q`{vim} とタイプすると ヘルプウィンドウを閉じられます。
+
+":help" コマンドに引数を与えることにより、あらゆる題名のヘルプを見つけること
+ができます。これらを試してみましょう(`<Enter>`{normal} をタイプし忘れないように):
+~~~ cmd
+ :help w
+ :help c_CTRL-D
+ :help insert-index
+ :help user-manual
+~~~
+
+# レッスン 7.2: 起動スクリプトの作成
+
+** Neovim の特徴を発揮する **
+
+Neovim はとても自由度の高いエディタです。あなたの好きなように
+カスタマイズすることができます。より多くの機能を使いはじめるには
+"init.vim" ファイルを作成します。
+
+ 1. "init.vim" ファイルの編集を開始します。
+
+ `:call mkdir(stdpath('config'),'p')`{vim}
+ `:exe 'edit' stdpath('config').'/init.vim'`{vim}
+
+ 3. 以下のようにファイルへ書き込みます。
+
+ `:w`{vim}
+
+ この "init.vim" ファイルへ、お好みの設定を追加することができます。
+ より多くの情報を得るには `:help init.vim`{vim} とタイプします。
+
+# レッスン 7.3: 補完
+
+** `<C-d>`{normal} と `<Tab>`{normal} でコマンドラインを補完する **
+
+ 1. 現在のディレクトリに在るものを表示します: `:!{unix:(ls),win:(dir)}`{vim}
+
+ 2. コマンドの先頭をタイプします: :e
+
+ 3. `<C-d>`{normal} を押すと Neovim は "e" から始まるコマンドの一覧を表示します。
+
+ 4. `<Tab>`{normal} とタイプすると Neoim は ":edit" というコマンド名を補完します。
+
+ 5. さらに空白と、既存のファイル名の始まりを加えます: `:edit FIL`{vim}
+
+ 6. `<Tab>`{normal} を押すと Neovim は名前を補完します。("FIL" -> "FILE"、重複しない場合)
+
+NOTE: 補完は多くのコマンドで動作します。特に `:help`{vim} の際に役立ちます。
+
+# レッスン 7 要約
+
+ 1. ヘルプウィンドウを開くには `:help`{vim} とするか `<F1>`{normal} を押す。
+
+ 2. '知りたい事' のヘルプを検索するには `:help '知りたい事'`{vim} とタイプする。
+
+ 3. 別のウィンドウへジャンプするには `<C-w><C-w>`{normal} とタイプする。
+
+ 4. ヘルプウィンドウを閉じるには `:q`{vim} とタイプする。
+
+ 5. お好みの設定を保つには init.vim 起動スクリプトを作成する。
+
+ 6. : command で可能な補完を見るには `<C-d>`{normal} をタイプする。
+ 補完を使用するには `<Tab>`{normal} を押す。
+
+# おわりに
+
+これにて Neovim のチュートリアルを終わります。エディタを簡単に、しかも充分に
+使うことができるようにと、Neovim の持つ概念の要点のみを伝えようとしました。
+Neovim にはさらに多くのコマンドがあり、ここで全てを説明することはできません。
+ヘルプを沢山活用してください。オンライン上にも数多の教材や動画を
+見つけることができます。ここにいくつか紹介します:
+
+- *Learn Vim Progressively*:
+ http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/
+- *Learning Vim in 2014*:
+ http://benmccormick.org/learning-vim-in-2014/
+- *Vimcasts*:
+ http://vimcasts.org/
+- *Vim Video-Tutorials by Derek Wyatt*:
+ http://derekwyatt.org/vim/tutorials/
+- *Learn Vimscript the Hard Way*:
+ http://learnvimscriptthehardway.stevelosh.com/
+- *7 Habits of Effective Text Editing*:
+ http://www.moolenaar.net/habits.html
+- *vim-galore*:
+ https://github.com/mhinz/vim-galore
+- *vim-jp Vim日本語ドキュメント*
+ https://vim-jp.org/vimdoc-ja/
+
+もしあなたが本が好きならば、*Practical Vim* by Drew Neil をお勧めします。
+(続編である *Modern Vim* には Neovimについての内容も含まれています。)
+
+このチュートリアルは Colorado State University の Charles Smith のアイデア
+を基に、Colorado School of Mines の Michael C. Pierce と Robert K. Ware の
+両名によって書かれました。 E-mail: bware@mines.colorado.edu.
+
+Modified for Vim by Bram Moolenaar.
+Modified for vim-tutor-mode by Felipe Morales.
+Modified for Neovim by Rory Nesbitt.
+
+Vim Tutor 翻訳
+ 日本語訳 松本 泰弘 <mattn.jp@gmail.com>
+ vim-jpチーム <https://github.com/vim-jp/lang-ja>
+ 監修 村岡 太郎 <koron.kaoriya@gmail.com>
+Neovim Tutor 翻訳
+ 監修 ite-usagi <https://github.com/ite-usagi>
+
+// vim: nowrap
diff --git a/runtime/tutor/ja/vim-01-beginner.tutor.json b/runtime/tutor/ja/vim-01-beginner.tutor.json
new file mode 100644
index 0000000000..5af4d5da94
--- /dev/null
+++ b/runtime/tutor/ja/vim-01-beginner.tutor.json
@@ -0,0 +1,44 @@
+{
+ "expect": {
+ "104": "その うさぎ は つき を こえて とびはねた",
+ "126": "この 行 には 幾つか 足りない テキスト が ある。",
+ "127": "この 行 には 幾つか 足りない テキスト が ある。",
+ "145": "この 行 には 間違った テキスト が あります。",
+ "146": "この 行 には 間違った テキスト が あります。",
+ "147": "ここ にも 間違った テキスト が あります。",
+ "148": "ここ にも 間違った テキスト が あります。",
+ "217": "この 文 には いくつかの 必要のない 単語 が 含まれて います。",
+ "233": "誰かがこの行の最後を2度タイプしました。",
+ "272": -1,
+ "291": "この行の単語は綺麗になった。",
+ "308": -1,
+ "309": -1,
+ "310": -1,
+ "311": -1,
+ "312": -1,
+ "313": -1,
+ "314": -1,
+ "335": "この行の間違いを修正し、後でそれらの修正をを取り消します。",
+ "381": -1,
+ "382": -1,
+ "383": -1,
+ "384": -1,
+ "400": "この行を入力した時に、その人は幾つか間違ったキーを押しました!",
+ "401": "この行を入力した時に、その人は幾つか間違ったキーを押しました!",
+ "421": "This line has a few words that need changing using the change operator.",
+ "422": "This line has a few words that need changing using the change operator.",
+ "442": "The end of this line needs to be corrected using the c$ command.",
+ "443": "The end of this line needs to be corrected using the c$ command.",
+ "505": -1,
+ "524": -1,
+ "548": "Usually the best time to see the flowers is in the spring.",
+ "737": -1,
+ "742": -1,
+ "758": "This line will allow you to practice appending text to a line.",
+ "759": "This line will allow you to practice appending text to a line.",
+ "776": "Adding 123 to 456 gives you 579.",
+ "777": "Adding 123 to 456 gives you 579.",
+ "802": "a) This is the first item.",
+ "803": "b) This is the second item."
+ }
+}
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 4cb90a4588..c1a2183f24 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -1688,7 +1688,7 @@ def filter_source(filename, keep_tmpfiles):
else:
"""Filters the source to fix macros that confuse Doxygen."""
with open(filename, 'rt') as fp:
- print(re.sub(r'^(ArrayOf|DictionaryOf)(\(.*?\))',
+ print(re.sub(r'^(ArrayOf|DictionaryOf|Dict)(\(.*?\))',
lambda m: m.group(1)+'_'.join(
re.split(r'[^\w]+', m.group(2))),
fp.read(), flags=re.M))
diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua
index 4f9973449e..0b3daa59b2 100644
--- a/scripts/lua2dox.lua
+++ b/scripts/lua2dox.lua
@@ -447,6 +447,8 @@ end
function Lua2DoxFilter:filter(filename)
local in_stream = StreamRead.new(filename)
+ local last_was_magic = false
+
while not in_stream:eof() do
local line = in_stream:getLine()
@@ -457,6 +459,16 @@ function Lua2DoxFilter:filter(filename)
end
if out_line then
+ -- Ensure all magic blocks associate with some object to prevent doxygen
+ -- from getting confused.
+ if vim.startswith(out_line, '///') then
+ last_was_magic = true
+ else
+ if last_was_magic and out_line:match('^// zz: [^-]+') then
+ writeln('local_function _ignore() {}')
+ end
+ last_was_magic = false
+ end
writeln(out_line)
end
end
diff --git a/src/clint.py b/src/clint.py
index 78eabb698f..062901b43a 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -152,8 +152,10 @@ _ERROR_CATEGORIES = [
'build/endif_comment',
'build/header_guard',
'build/include_defs',
+ 'build/defs_header',
'build/printf_format',
'build/storage_class',
+ 'build/init_macro',
'readability/bool',
'readability/multiline_comment',
'readability/multiline_string',
@@ -2086,6 +2088,11 @@ def CheckLanguage(filename, clean_lines, linenum, error):
" named ('k' followed by CamelCase) compile-time constant for"
" the size.")
+ # INIT() macro should only be used in header files.
+ if not filename.endswith('.h') and Search(r' INIT\(', line):
+ error(filename, linenum, 'build/init_macro', 4,
+ 'INIT() macro should only be used in header files.')
+
# Detect TRUE and FALSE.
match = Search(r'\b(TRUE|FALSE)\b', line)
if match:
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 7af2b7241c..183483b329 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -264,6 +264,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
Integer start,
Integer end,
Boolean strict_indexing,
+ Arena *arena,
lua_State *lstate,
Error *err)
FUNC_API_SINCE(1)
@@ -295,18 +296,10 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
size_t size = (size_t)(end - start);
- init_line_array(lstate, &rv, size);
+ init_line_array(lstate, &rv, size, arena);
- if (!buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv,
- lstate, err)) {
- goto end;
- }
-
-end:
- if (ERROR_SET(err)) {
- api_free_array(rv);
- rv.items = NULL;
- }
+ buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv,
+ lstate, arena);
return rv;
}
@@ -763,8 +756,8 @@ early_end:
ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
Integer start_row, Integer start_col,
Integer end_row, Integer end_col,
- Dict(empty) *opts, lua_State *lstate,
- Error *err)
+ Dict(empty) *opts,
+ Arena *arena, lua_State *lstate, Error *err)
FUNC_API_SINCE(9)
{
Array rv = ARRAY_DICT_INIT;
@@ -799,44 +792,38 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
size_t size = (size_t)(end_row - start_row) + 1;
- init_line_array(lstate, &rv, size);
+ init_line_array(lstate, &rv, size, arena);
if (start_row == end_row) {
String line = buf_get_text(buf, start_row, start_col, end_col, err);
if (ERROR_SET(err)) {
goto end;
}
- push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl);
+ push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl, arena);
return rv;
}
String str = buf_get_text(buf, start_row, start_col, MAXCOL - 1, err);
-
- push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl);
-
if (ERROR_SET(err)) {
goto end;
}
+ push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl, arena);
+
if (size > 2) {
- if (!buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate,
- err)) {
- goto end;
- }
+ buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate, arena);
}
str = buf_get_text(buf, end_row, 0, end_col, err);
- push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl);
-
if (ERROR_SET(err)) {
goto end;
}
+ push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl, arena);
+
end:
if (ERROR_SET(err)) {
- api_free_array(rv);
- rv.size = 0;
- rv.items = NULL;
+ return (Array)ARRAY_DICT_INIT;
}
return rv;
@@ -1262,28 +1249,27 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
return res;
}
-Dictionary nvim__buf_stats(Buffer buffer, Error *err)
+Dictionary nvim__buf_stats(Buffer buffer, Arena *arena, Error *err)
{
- Dictionary rv = ARRAY_DICT_INIT;
-
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
- return rv;
+ return (Dictionary)ARRAY_DICT_INIT;
}
+ Dictionary rv = arena_dict(arena, 7);
// Number of times the cached line was flushed.
// This should generally not increase while editing the same
// line in the same mode.
- PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count));
+ PUT_C(rv, "flush_count", INTEGER_OBJ(buf->flush_count));
// lnum of current line
- PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum));
+ PUT_C(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum));
// whether the line has unflushed changes.
- PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY));
+ PUT_C(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY));
// NB: this should be zero at any time API functions are called,
// this exists to debug issues
- PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
- PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
- PUT(rv, "virt_blocks", INTEGER_OBJ((Integer)buf_meta_total(buf, kMTMetaLines)));
+ PUT_C(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ PUT_C(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
+ PUT_C(rv, "virt_blocks", INTEGER_OBJ((Integer)buf_meta_total(buf, kMTMetaLines)));
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
@@ -1292,7 +1278,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
uhp = buf->b_u_newhead;
}
if (uhp) {
- PUT(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark)));
+ PUT_C(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark)));
}
return rv;
@@ -1395,13 +1381,12 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l
/// @param lstate Lua state. When NULL the Array is initialized instead.
/// @param a Array to initialize
/// @param size Size of array
-static inline void init_line_array(lua_State *lstate, Array *a, size_t size)
+static inline void init_line_array(lua_State *lstate, Array *a, size_t size, Arena *arena)
{
if (lstate) {
lua_createtable(lstate, (int)size, 0);
} else {
- a->size = size;
- a->items = xcalloc(a->size, sizeof(Object));
+ *a = arena_array(arena, size);
}
}
@@ -1414,14 +1399,15 @@ static inline void init_line_array(lua_State *lstate, Array *a, size_t size)
/// @param a Array to push onto when not using Lua
/// @param s String to push
/// @param len Size of string
-/// @param idx 0-based index to place s
+/// @param idx 0-based index to place s (only used for Lua)
/// @param replace_nl Replace newlines ('\n') with null ('\0')
static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx,
- bool replace_nl)
+ bool replace_nl, Arena *arena)
{
if (lstate) {
// Vim represents NULs as NLs
if (s && replace_nl && strchr(s, '\n')) {
+ // TODO(bfredl): could manage scratch space in the arena, for the NUL case
char *tmp = xmemdupz(s, len);
strchrsub(tmp, '\n', '\0');
lua_pushlstring(lstate, tmp, len);
@@ -1432,15 +1418,15 @@ static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len,
lua_rawseti(lstate, -2, idx + 1);
} else {
String str = STRING_INIT;
- if (s) {
- str = cbuf_to_string(s, len);
+ if (len > 0) {
+ str = arena_string(arena, cbuf_as_string((char *)s, len));
if (replace_nl) {
// Vim represents NULs as NLs, but this may confuse clients.
strchrsub(str.data, '\n', '\0');
}
}
- a->items[idx] = STRING_OBJ(str);
+ ADD_C(*a, STRING_OBJ(str));
}
}
@@ -1451,27 +1437,17 @@ static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len,
/// @param n Number of lines to collect
/// @param replace_nl Replace newlines ("\n") with NUL
/// @param start Line number to start from
-/// @param start_idx First index to push to
+/// @param start_idx First index to push to (only used for Lua)
/// @param[out] l If not NULL, Lines are copied here
/// @param[out] lstate If not NULL, Lines are pushed into a table onto the stack
/// @param err[out] Error, if any
/// @return true unless `err` was set
-bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl,
- Array *l, lua_State *lstate, Error *err)
+void buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl,
+ Array *l, lua_State *lstate, Arena *arena)
{
for (size_t i = 0; i < n; i++) {
linenr_T lnum = start + (linenr_T)i;
-
- if (lnum >= MAXLNUM) {
- if (err != NULL) {
- api_set_error(err, kErrorTypeValidation, "Line index is too high");
- }
- return false;
- }
-
char *bufstr = ml_get_buf(buf, lnum);
- push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl);
+ push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl, arena);
}
-
- return true;
}
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index db0a918f5c..bafc45e543 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -96,15 +96,15 @@
/// - "belowright": |:belowright|.
/// - "topleft": |:topleft|.
/// - "botright": |:botright|.
-Dictionary nvim_parse_cmd(String str, Dict(empty) *opts, Error *err)
+Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(10) FUNC_API_FAST
{
- Dictionary result = ARRAY_DICT_INIT;
+ Dict(cmd) result = { 0 };
// Parse command line
exarg_T ea;
CmdParseInfo cmdinfo;
- char *cmdline = string_to_cstr(str);
+ char *cmdline = arena_memdupz(arena, str.data, str.size);
const char *errormsg = NULL;
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
@@ -124,22 +124,23 @@ Dictionary nvim_parse_cmd(String str, Dict(empty) *opts, Error *err)
// otherwise split arguments by whitespace.
if (ea.argt & EX_NOSPC) {
if (*ea.arg != NUL) {
- ADD(args, STRING_OBJ(cstrn_to_string(ea.arg, length)));
+ args = arena_array(arena, 1);
+ ADD_C(args, STRING_OBJ(cstrn_as_string(ea.arg, length)));
}
} else {
size_t end = 0;
size_t len = 0;
- char *buf = xcalloc(length, sizeof(char));
+ char *buf = arena_alloc(arena, length + 1, false);
bool done = false;
+ args = arena_array(arena, uc_nargs_upper_bound(ea.arg, length));
while (!done) {
done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
if (len > 0) {
- ADD(args, STRING_OBJ(cstrn_to_string(buf, len)));
+ ADD_C(args, STRING_OBJ(cstrn_as_string(buf, len)));
+ buf += len + 1;
}
}
-
- xfree(buf);
}
ucmd_T *cmd = NULL;
@@ -149,40 +150,32 @@ Dictionary nvim_parse_cmd(String str, Dict(empty) *opts, Error *err)
cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
}
- if (cmd != NULL) {
- PUT(result, "cmd", CSTR_TO_OBJ(cmd->uc_name));
- } else {
- PUT(result, "cmd", CSTR_TO_OBJ(get_command_name(NULL, ea.cmdidx)));
- }
+ char *name = (cmd != NULL ? cmd->uc_name : get_command_name(NULL, ea.cmdidx));
+ PUT_KEY(result, cmd, cmd, cstr_as_string(name));
if (ea.argt & EX_RANGE) {
- Array range = ARRAY_DICT_INIT;
+ Array range = arena_array(arena, 2);
if (ea.addr_count > 0) {
if (ea.addr_count > 1) {
- ADD(range, INTEGER_OBJ(ea.line1));
+ ADD_C(range, INTEGER_OBJ(ea.line1));
}
- ADD(range, INTEGER_OBJ(ea.line2));
+ ADD_C(range, INTEGER_OBJ(ea.line2));
}
- PUT(result, "range", ARRAY_OBJ(range));
+ PUT_KEY(result, cmd, range, range);
}
if (ea.argt & EX_COUNT) {
- if (ea.addr_count > 0) {
- PUT(result, "count", INTEGER_OBJ(ea.line2));
- } else if (cmd != NULL) {
- PUT(result, "count", INTEGER_OBJ(cmd->uc_def));
- } else {
- PUT(result, "count", INTEGER_OBJ(0));
- }
+ Integer count = ea.addr_count > 0 ? ea.line2 : (cmd != NULL ? cmd->uc_def : 0);
+ PUT_KEY(result, cmd, count, count);
}
if (ea.argt & EX_REGSTR) {
char reg[2] = { (char)ea.regname, NUL };
- PUT(result, "reg", CSTR_TO_OBJ(reg));
+ PUT_KEY(result, cmd, reg, CSTR_TO_ARENA_STR(arena, reg));
}
- PUT(result, "bang", BOOLEAN_OBJ(ea.forceit));
- PUT(result, "args", ARRAY_OBJ(args));
+ PUT_KEY(result, cmd, bang, ea.forceit);
+ PUT_KEY(result, cmd, args, args);
char nargs[2];
if (ea.argt & EX_EXTRA) {
@@ -201,9 +194,9 @@ Dictionary nvim_parse_cmd(String str, Dict(empty) *opts, Error *err)
nargs[0] = '0';
}
nargs[1] = '\0';
- PUT(result, "nargs", CSTR_TO_OBJ(nargs));
+ PUT_KEY(result, cmd, nargs, CSTR_TO_ARENA_OBJ(arena, nargs));
- const char *addr;
+ char *addr;
switch (ea.addr_type) {
case ADDR_LINES:
addr = "line";
@@ -233,38 +226,37 @@ Dictionary nvim_parse_cmd(String str, Dict(empty) *opts, Error *err)
addr = "?";
break;
}
- PUT(result, "addr", CSTR_TO_OBJ(addr));
- PUT(result, "nextcmd", CSTR_TO_OBJ(ea.nextcmd));
-
- Dictionary mods = ARRAY_DICT_INIT;
-
- Dictionary filter = ARRAY_DICT_INIT;
- PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat
- ? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat)
- : STATIC_CSTR_TO_OBJ(""));
- PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
- PUT(mods, "filter", DICTIONARY_OBJ(filter));
-
- PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT));
- PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT));
- PUT(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT));
- PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX));
- PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD));
- PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1));
- PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1));
- PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE));
- PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM));
- PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE));
- PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPALT));
- PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPJUMPS));
- PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPMARKS));
- PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPPATTERNS));
- PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS));
- PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE));
- PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT));
- PUT(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR));
-
- const char *split;
+ PUT_KEY(result, cmd, addr, CSTR_AS_OBJ(addr));
+ PUT_KEY(result, cmd, nextcmd, CSTR_AS_OBJ(ea.nextcmd));
+
+ // TODO(bfredl): nested keydict would be nice..
+ Dictionary mods = arena_dict(arena, 20);
+
+ Dictionary filter = arena_dict(arena, 2);
+ PUT_C(filter, "pattern", CSTR_TO_ARENA_OBJ(arena, cmdinfo.cmdmod.cmod_filter_pat));
+ PUT_C(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
+ PUT_C(mods, "filter", DICTIONARY_OBJ(filter));
+
+ PUT_C(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT));
+ PUT_C(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT));
+ PUT_C(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT));
+ PUT_C(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX));
+ PUT_C(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD));
+ PUT_C(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1));
+ PUT_C(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1));
+ PUT_C(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE));
+ PUT_C(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM));
+ PUT_C(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE));
+ PUT_C(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPALT));
+ PUT_C(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPJUMPS));
+ PUT_C(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPMARKS));
+ PUT_C(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPPATTERNS));
+ PUT_C(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS));
+ PUT_C(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE));
+ PUT_C(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT));
+ PUT_C(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR));
+
+ char *split;
if (cmdinfo.cmdmod.cmod_split & WSP_BOT) {
split = "botright";
} else if (cmdinfo.cmdmod.cmod_split & WSP_TOP) {
@@ -276,18 +268,17 @@ Dictionary nvim_parse_cmd(String str, Dict(empty) *opts, Error *err)
} else {
split = "";
}
- PUT(mods, "split", CSTR_TO_OBJ(split));
+ PUT_C(mods, "split", CSTR_AS_OBJ(split));
- PUT(result, "mods", DICTIONARY_OBJ(mods));
+ PUT_KEY(result, cmd, mods, mods);
- Dictionary magic = ARRAY_DICT_INIT;
- PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
- PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
- PUT(result, "magic", DICTIONARY_OBJ(magic));
+ Dictionary magic = arena_dict(arena, 2);
+ PUT_C(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
+ PUT_C(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
+ PUT_KEY(result, cmd, magic, magic);
undo_cmdmod(&cmdinfo.cmdmod);
end:
- xfree(cmdline);
return result;
}
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index b09645a819..dccaeb6922 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -245,20 +245,18 @@ void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, Error *er
/// @param index Line index
/// @param[out] err Error details, if any
/// @return Line string
-String buffer_get_line(Buffer buffer, Integer index, Error *err)
+String buffer_get_line(Buffer buffer, Integer index, Arena *arena, Error *err)
FUNC_API_DEPRECATED_SINCE(1)
{
String rv = { .size = 0 };
index = convert_index(index);
- Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, NULL, err);
+ Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, arena, NULL, err);
if (!ERROR_SET(err) && slice.size) {
rv = slice.items[0].data.string;
}
- xfree(slice.items);
-
return rv;
}
@@ -319,12 +317,13 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer,
Integer end,
Boolean include_start,
Boolean include_end,
+ Arena *arena,
Error *err)
FUNC_API_DEPRECATED_SINCE(1)
{
start = convert_index(start) + !include_start;
end = convert_index(end) + include_end;
- return nvim_buf_get_lines(0, buffer, start, end, false, NULL, err);
+ return nvim_buf_get_lines(0, buffer, start, end, false, arena, NULL, err);
}
/// Replaces a line range on the buffer
@@ -514,11 +513,11 @@ static int64_t convert_index(int64_t index)
/// @param name Option name
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info(String name, Error *err)
+Dictionary nvim_get_option_info(String name, Arena *arena, Error *err)
FUNC_API_SINCE(7)
FUNC_API_DEPRECATED_SINCE(11)
{
- return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err);
+ return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, arena, err);
}
/// Sets the global value of an option.
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 4e84b41a02..94f6059014 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -70,15 +70,15 @@ Integer nvim_create_namespace(String name)
/// Gets existing, non-anonymous |namespace|s.
///
/// @return dict that maps from names to namespace ids.
-Dictionary nvim_get_namespaces(void)
+Dictionary nvim_get_namespaces(Arena *arena)
FUNC_API_SINCE(5)
{
- Dictionary retval = ARRAY_DICT_INIT;
+ Dictionary retval = arena_dict(arena, map_size(&namespace_ids));
String name;
handle_T id;
map_foreach(&namespace_ids, name, id, {
- PUT(retval, name.data, INTEGER_OBJ(id));
+ PUT_C(retval, name.data, INTEGER_OBJ(id));
})
return retval;
@@ -105,73 +105,81 @@ bool ns_initialized(uint32_t ns)
return ns < (uint32_t)next_namespace_id;
}
-Array virt_text_to_array(VirtText vt, bool hl_name)
+Array virt_text_to_array(VirtText vt, bool hl_name, Arena *arena)
{
- Array chunks = ARRAY_DICT_INIT;
- Array hl_array = ARRAY_DICT_INIT;
+ Array chunks = arena_array(arena, kv_size(vt));
for (size_t i = 0; i < kv_size(vt); i++) {
- char *text = kv_A(vt, i).text;
- int hl_id = kv_A(vt, i).hl_id;
- if (text == NULL) {
+ size_t j = i;
+ for (; j < kv_size(vt); j++) {
+ if (kv_A(vt, j).text != NULL) {
+ break;
+ }
+ }
+
+ Array hl_array = arena_array(arena, i < j ? j - i + 1 : 0);
+ for (; i < j; i++) {
+ int hl_id = kv_A(vt, i).hl_id;
if (hl_id > 0) {
- ADD(hl_array, hl_group_name(hl_id, hl_name));
+ ADD_C(hl_array, hl_group_name(hl_id, hl_name));
}
- continue;
}
- Array chunk = ARRAY_DICT_INIT;
- ADD(chunk, CSTR_TO_OBJ(text));
+
+ char *text = kv_A(vt, i).text;
+ int hl_id = kv_A(vt, i).hl_id;
+ Array chunk = arena_array(arena, 2);
+ ADD_C(chunk, CSTR_AS_OBJ(text));
if (hl_array.size > 0) {
if (hl_id > 0) {
- ADD(hl_array, hl_group_name(hl_id, hl_name));
+ ADD_C(hl_array, hl_group_name(hl_id, hl_name));
}
- ADD(chunk, ARRAY_OBJ(hl_array));
- hl_array = (Array)ARRAY_DICT_INIT;
+ ADD_C(chunk, ARRAY_OBJ(hl_array));
} else if (hl_id > 0) {
- ADD(chunk, hl_group_name(hl_id, hl_name));
+ ADD_C(chunk, hl_group_name(hl_id, hl_name));
}
- ADD(chunks, ARRAY_OBJ(chunk));
+ ADD_C(chunks, ARRAY_OBJ(chunk));
}
- assert(hl_array.size == 0);
return chunks;
}
-static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_name)
+static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_name, Arena *arena)
{
MTKey start = extmark.start;
- Array rv = ARRAY_DICT_INIT;
+ Array rv = arena_array(arena, 4);
if (id) {
- ADD(rv, INTEGER_OBJ((Integer)start.id));
+ ADD_C(rv, INTEGER_OBJ((Integer)start.id));
}
- ADD(rv, INTEGER_OBJ(start.pos.row));
- ADD(rv, INTEGER_OBJ(start.pos.col));
+ ADD_C(rv, INTEGER_OBJ(start.pos.row));
+ ADD_C(rv, INTEGER_OBJ(start.pos.col));
if (add_dict) {
- Dictionary dict = ARRAY_DICT_INIT;
+ // TODO(bfredl): coding the size like this is a bit fragile.
+ // We want ArrayOf(Dict(set_extmark)) as the return type..
+ Dictionary dict = arena_dict(arena, ARRAY_SIZE(set_extmark_table));
- PUT(dict, "ns_id", INTEGER_OBJ((Integer)start.ns));
+ PUT_C(dict, "ns_id", INTEGER_OBJ((Integer)start.ns));
- PUT(dict, "right_gravity", BOOLEAN_OBJ(mt_right(start)));
+ PUT_C(dict, "right_gravity", BOOLEAN_OBJ(mt_right(start)));
if (mt_paired(start)) {
- PUT(dict, "end_row", INTEGER_OBJ(extmark.end_pos.row));
- PUT(dict, "end_col", INTEGER_OBJ(extmark.end_pos.col));
- PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
+ PUT_C(dict, "end_row", INTEGER_OBJ(extmark.end_pos.row));
+ PUT_C(dict, "end_col", INTEGER_OBJ(extmark.end_pos.col));
+ PUT_C(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
}
if (mt_no_undo(start)) {
- PUT(dict, "undo_restore", BOOLEAN_OBJ(false));
+ PUT_C(dict, "undo_restore", BOOLEAN_OBJ(false));
}
if (mt_invalidate(start)) {
- PUT(dict, "invalidate", BOOLEAN_OBJ(true));
+ PUT_C(dict, "invalidate", BOOLEAN_OBJ(true));
}
if (mt_invalid(start)) {
- PUT(dict, "invalid", BOOLEAN_OBJ(true));
+ PUT_C(dict, "invalid", BOOLEAN_OBJ(true));
}
- decor_to_dict_legacy(&dict, mt_decor(start), hl_name);
+ decor_to_dict_legacy(&dict, mt_decor(start), hl_name, arena);
- ADD(rv, DICTIONARY_OBJ(dict));
+ ADD_C(rv, DICTIONARY_OBJ(dict));
}
return rv;
@@ -190,7 +198,7 @@ static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_na
/// absent
ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
Integer id, Dict(get_extmark) *opts,
- Error *err)
+ Arena *arena, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -213,7 +221,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
if (extmark.start.pos.row < 0) {
return rv;
}
- return extmark_to_array(extmark, false, details, hl_name);
+ return extmark_to_array(extmark, false, details, hl_name, arena);
}
/// Gets |extmarks| in "traversal order" from a |charwise| region defined by
@@ -272,7 +280,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end,
- Dict(get_extmarks) *opts, Error *err)
+ Dict(get_extmarks) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -336,8 +344,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row,
u_col, (int64_t)limit, reverse, type, opts->overlap);
+ rv = arena_array(arena, kv_size(marks));
for (size_t i = 0; i < kv_size(marks); i++) {
- ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name)));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name, arena)));
}
kv_destroy(marks);
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
index 2f1b38d1e5..0ba33ca9a7 100644
--- a/src/nvim/api/keysets_defs.h
+++ b/src/nvim/api/keysets_defs.h
@@ -108,7 +108,7 @@ typedef struct {
} Dict(user_command);
typedef struct {
- OptionalKeys is_set__float_config_;
+ OptionalKeys is_set__win_config_;
Float row;
Float col;
Integer width;
@@ -131,7 +131,7 @@ typedef struct {
Boolean noautocmd;
Boolean fixed;
Boolean hide;
-} Dict(float_config);
+} Dict(win_config);
typedef struct {
Boolean is_lua;
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index fce4a85804..8128fdf67b 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -263,10 +263,10 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(
/// @see |nvim_get_commands()|
///
/// @return dictionary of all options
-Dictionary nvim_get_all_options_info(Error *err)
+Dictionary nvim_get_all_options_info(Arena *arena, Error *err)
FUNC_API_SINCE(7)
{
- return get_all_vimoptions();
+ return get_all_vimoptions(arena);
}
/// Gets the option information for one option from arbitrary buffer or window
@@ -302,7 +302,7 @@ Dictionary nvim_get_all_options_info(Error *err)
/// Implies {scope} is "local".
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err)
+Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(11)
{
OptIndex opt_idx = 0;
@@ -317,5 +317,5 @@ Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err)
buf_T *buf = (req_scope == kOptReqBuf) ? (buf_T *)from : curbuf;
win_T *win = (req_scope == kOptReqWin) ? (win_T *)from : curwin;
- return get_vimoption(name, scope, buf, win, err);
+ return get_vimoption(name, scope, buf, win, arena, err);
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 9ce1786fa0..8b45af7c71 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -425,12 +425,12 @@ String cstrn_as_string(char *str, size_t maxsize)
/// @param str the C string to use
/// @return The resulting String, or an empty String if
/// str was NULL
-String cstr_as_string(char *str) FUNC_ATTR_PURE
+String cstr_as_string(const char *str) FUNC_ATTR_PURE
{
if (str == NULL) {
return (String)STRING_INIT;
}
- return (String){ .data = str, .size = strlen(str) };
+ return (String){ .data = (char *)str, .size = strlen(str) };
}
/// Return the owned memory of a ga as a String
@@ -1014,6 +1014,54 @@ bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error
return true;
}
+Dictionary api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size, Arena *arena)
+{
+ Dictionary rv = arena_dict(arena, max_size);
+ for (size_t i = 0; table[i].str; i++) {
+ KeySetLink *field = &table[i];
+ bool is_set = true;
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)value;
+ is_set = ks->is_set_ & (1ULL << field->opt_index);
+ }
+
+ if (!is_set) {
+ continue;
+ }
+
+ char *mem = ((char *)value + field->ptr_off);
+ Object val = NIL;
+
+ if (field->type == kObjectTypeNil) {
+ val = *(Object *)mem;
+ } else if (field->type == kObjectTypeInteger) {
+ val = INTEGER_OBJ(*(Integer *)mem);
+ } else if (field->type == kObjectTypeFloat) {
+ val = FLOAT_OBJ(*(Float *)mem);
+ } else if (field->type == kObjectTypeBoolean) {
+ val = BOOLEAN_OBJ(*(Boolean *)mem);
+ } else if (field->type == kObjectTypeString) {
+ val = STRING_OBJ(*(String *)mem);
+ } else if (field->type == kObjectTypeArray) {
+ val = ARRAY_OBJ(*(Array *)mem);
+ } else if (field->type == kObjectTypeDictionary) {
+ val = DICTIONARY_OBJ(*(Dictionary *)mem);
+ } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
+ || field->type == kObjectTypeTabpage) {
+ val.data.integer = *(Integer *)mem;
+ val.type = field->type;
+ } else if (field->type == kObjectTypeLuaRef) {
+ // do nothing
+ } else {
+ abort();
+ }
+
+ PUT_C(rv, field->str, val);
+ }
+
+ return rv;
+}
+
void api_free_keydict(void *dict, KeySetLink *table)
{
for (size_t i = 0; table[i].str; i++) {
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 8a56d1704f..9ee812f45c 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -32,6 +32,8 @@
#define CSTR_AS_OBJ(s) STRING_OBJ(cstr_as_string(s))
#define CSTR_TO_OBJ(s) STRING_OBJ(cstr_to_string(s))
+#define CSTR_TO_ARENA_STR(arena, s) arena_string(arena, cstr_as_string(s))
+#define CSTR_TO_ARENA_OBJ(arena, s) STRING_OBJ(CSTR_TO_ARENA_STR(arena, s))
#define BUFFER_OBJ(s) ((Object) { \
.type = kObjectTypeBuffer, \
@@ -70,6 +72,9 @@
#define PUT_C(dict, k, v) \
kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v }))
+#define PUT_KEY(d, typ, key, v) \
+ do { (d).is_set__##typ##_ |= (1 << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0)
+
#define ADD(array, item) \
kv_push(array, item)
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 03e7e11601..c7280253c2 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -247,10 +247,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enable_rgb, Error *err)
FUNC_API_DEPRECATED_SINCE(1)
{
- Dictionary opts = ARRAY_DICT_INIT;
- PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb));
+ MAXSIZE_TEMP_DICT(opts, 1);
+ PUT_C(opts, "rgb", BOOLEAN_OBJ(enable_rgb));
nvim_ui_attach(channel_id, width, height, opts, err);
- api_free_dictionary(opts);
}
/// Tells the nvim server if focus was gained or lost by the GUI
@@ -794,7 +793,7 @@ void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cte
// system. So we add them here.
if (rgb_attrs.url >= 0) {
const char *url = hl_get_url((uint32_t)rgb_attrs.url);
- PUT_C(rgb, "url", STRING_OBJ(cstr_as_string((char *)url)));
+ PUT_C(rgb, "url", CSTR_AS_OBJ(url));
}
ADD_C(args, DICTIONARY_OBJ(rgb));
@@ -862,7 +861,7 @@ void remote_ui_put(UI *ui, const char *cell)
UIData *data = ui->data;
data->client_col++;
Array args = data->call_buf;
- ADD_C(args, CSTR_AS_OBJ((char *)cell));
+ ADD_C(args, CSTR_AS_OBJ(cell));
push_call(ui, "put", args);
}
@@ -1118,9 +1117,3 @@ void remote_ui_event(UI *ui, char *name, Array args)
free_ret:
arena_mem_free(arena_finish(&arena));
}
-
-void remote_ui_inspect(UI *ui, Dictionary *info)
-{
- UIData *data = ui->data;
- PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id));
-}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index edee50f411..78c2561bbd 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -656,10 +656,10 @@ void nvim_set_current_dir(String dir, Error *err)
///
/// @param[out] err Error details, if any
/// @return Current line string
-String nvim_get_current_line(Error *err)
+String nvim_get_current_line(Arena *arena, Error *err)
FUNC_API_SINCE(1)
{
- return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
+ return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, arena, err);
}
/// Sets the current line.
@@ -1351,14 +1351,13 @@ Integer nvim_get_color_by_name(String name)
/// (e.g. 65535).
///
/// @return Map of color names and RGB values.
-Dictionary nvim_get_color_map(void)
+Dictionary nvim_get_color_map(Arena *arena)
FUNC_API_SINCE(1)
{
- Dictionary colors = ARRAY_DICT_INIT;
+ Dictionary colors = arena_dict(arena, ARRAY_SIZE(color_name_table));
for (int i = 0; color_name_table[i].name != NULL; i++) {
- PUT(colors, color_name_table[i].name,
- INTEGER_OBJ(color_name_table[i].color));
+ PUT_C(colors, color_name_table[i].name, INTEGER_OBJ(color_name_table[i].color));
}
return colors;
}
@@ -1438,16 +1437,16 @@ Object nvim_load_context(Dictionary dict, Error *err)
/// "blocking" is true if Nvim is waiting for input.
///
/// @returns Dictionary { "mode": String, "blocking": Boolean }
-Dictionary nvim_get_mode(void)
+Dictionary nvim_get_mode(Arena *arena)
FUNC_API_SINCE(2) FUNC_API_FAST
{
- Dictionary rv = ARRAY_DICT_INIT;
- char modestr[MODE_MAX_LENGTH];
+ Dictionary rv = arena_dict(arena, 2);
+ char *modestr = arena_alloc(arena, MODE_MAX_LENGTH, false);
get_mode(modestr);
bool blocked = input_blocking();
- PUT(rv, "mode", CSTR_TO_OBJ(modestr));
- PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
+ PUT_C(rv, "mode", CSTR_AS_OBJ(modestr));
+ PUT_C(rv, "blocking", BOOLEAN_OBJ(blocked));
return rv;
}
@@ -1612,6 +1611,7 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
/// Gets information about a channel.
///
+/// @param chan channel_id, or 0 for current channel
/// @returns Dictionary describing a channel, with these keys:
/// - "id" Channel id.
/// - "argv" (optional) Job arguments list.
@@ -1633,12 +1633,17 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
/// the RPC channel), if provided by it via
/// |nvim_set_client_info()|.
///
-Dictionary nvim_get_chan_info(Integer chan, Error *err)
+Dictionary nvim_get_chan_info(uint64_t channel_id, Integer chan, Error *err)
FUNC_API_SINCE(4)
{
if (chan < 0) {
return (Dictionary)ARRAY_DICT_INIT;
}
+
+ if (chan == 0 && !is_internal_call(channel_id)) {
+ assert(channel_id <= INT64_MAX);
+ chan = (Integer)channel_id;
+ }
return channel_info((uint64_t)chan);
}
@@ -1842,14 +1847,14 @@ Float nvim__id_float(Float flt)
/// Gets internal stats.
///
/// @return Map of various internal stats.
-Dictionary nvim__stats(void)
-{
- Dictionary rv = ARRAY_DICT_INIT;
- PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
- PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip));
- PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
- PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
- PUT(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count));
+Dictionary nvim__stats(Arena *arena)
+{
+ Dictionary rv = arena_dict(arena, 5);
+ PUT_C(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
+ PUT_C(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip));
+ PUT_C(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
+ PUT_C(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
+ PUT_C(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count));
return rv;
}
@@ -1861,10 +1866,10 @@ Dictionary nvim__stats(void)
/// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|)
/// - "ext_..." Requested UI extensions, see |ui-option|
/// - "chan" |channel-id| of remote UI
-Array nvim_list_uis(void)
+Array nvim_list_uis(Arena *arena)
FUNC_API_SINCE(4)
{
- return ui_array();
+ return ui_array(arena);
}
/// Gets the immediate children of process `pid`.
@@ -2000,7 +2005,7 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
if (!highlight_use_hlstate()) {
- ADD_C(ret, ARRAY_OBJ(hl_inspect(attr)));
+ ADD_C(ret, ARRAY_OBJ(hl_inspect(attr, arena)));
}
return ret;
}
@@ -2145,7 +2150,7 @@ Array nvim_get_mark(String name, Dict(empty) *opts, Error *err)
/// |Dictionary| with these keys:
/// - start: (number) Byte index (0-based) of first character that uses the highlight.
/// - group: (string) Name of highlight group.
-Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *err)
+Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(8) FUNC_API_FAST
{
Dictionary result = ARRAY_DICT_INIT;
@@ -2254,58 +2259,61 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
&& global_stl_height() > 0)) ? Columns : wp->w_width;
}
- char buf[MAXPATHL];
+ result = arena_dict(arena, 3);
+ char *buf = arena_alloc(arena, MAXPATHL, false);
stl_hlrec_t *hltab;
+ size_t hltab_len = 0;
// Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back.
int p_crb_save = wp->w_p_crb;
wp->w_p_crb = false;
- int width = build_stl_str_hl(wp, buf, sizeof(buf), str.data, -1, 0, fillchar, maxwidth,
- opts->highlights ? &hltab : NULL, NULL,
+ int width = build_stl_str_hl(wp, buf, MAXPATHL, str.data, -1, 0, fillchar, maxwidth,
+ opts->highlights ? &hltab : NULL, &hltab_len, NULL,
statuscol_lnum ? &statuscol : NULL);
- PUT(result, "width", INTEGER_OBJ(width));
+ PUT_C(result, "width", INTEGER_OBJ(width));
// Restore original value of 'cursorbind'
wp->w_p_crb = p_crb_save;
if (opts->highlights) {
- Array hl_values = ARRAY_DICT_INIT;
- const char *grpname;
+ Array hl_values = arena_array(arena, hltab_len + 1);
char user_group[15]; // strlen("User") + strlen("2147483647") + NUL
// If first character doesn't have a defined highlight,
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || (hltab->start - buf) != 0) {
- Dictionary hl_info = ARRAY_DICT_INIT;
- grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
+ Dictionary hl_info = arena_dict(arena, 2);
+ const char *grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp,
+ opts->use_winbar, stc_hl_id);
- PUT(hl_info, "start", INTEGER_OBJ(0));
- PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
+ PUT_C(hl_info, "start", INTEGER_OBJ(0));
+ PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname));
- ADD(hl_values, DICTIONARY_OBJ(hl_info));
+ ADD_C(hl_values, DICTIONARY_OBJ(hl_info));
}
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
- Dictionary hl_info = ARRAY_DICT_INIT;
+ Dictionary hl_info = arena_dict(arena, 2);
- PUT(hl_info, "start", INTEGER_OBJ(sp->start - buf));
+ PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf));
+ const char *grpname;
if (sp->userhl == 0) {
grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
} else if (sp->userhl < 0) {
grpname = syn_id2name(-sp->userhl);
} else {
snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
- grpname = user_group;
+ grpname = arena_memdupz(arena, user_group, strlen(user_group));
}
- PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
- ADD(hl_values, DICTIONARY_OBJ(hl_info));
+ PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname));
+ ADD_C(hl_values, DICTIONARY_OBJ(hl_info));
}
- PUT(result, "highlights", ARRAY_OBJ(hl_values));
+ PUT_C(result, "highlights", ARRAY_OBJ(hl_values));
}
- PUT(result, "str", CSTR_TO_OBJ(buf));
+ PUT_C(result, "str", CSTR_AS_OBJ(buf));
return result;
}
@@ -2330,15 +2338,15 @@ void nvim_error_event(uint64_t channel_id, Integer lvl, String data)
/// @return Dictionary containing these keys:
/// - winid: (number) floating window id
/// - bufnr: (number) buffer id in floating window
-Dictionary nvim_complete_set(Integer index, Dict(complete_set) *opts)
+Dictionary nvim_complete_set(Integer index, Dict(complete_set) *opts, Arena *arena)
FUNC_API_SINCE(12)
{
- Dictionary rv = ARRAY_DICT_INIT;
+ Dictionary rv = arena_dict(arena, 2);
if (HAS_KEY(opts, complete_set, info)) {
win_T *wp = pum_set_info((int)index, opts->info.data);
if (wp) {
- PUT(rv, "winid", WINDOW_OBJ(wp->handle));
- PUT(rv, "bufnr", BUFFER_OBJ(wp->w_buffer->handle));
+ PUT_C(rv, "winid", WINDOW_OBJ(wp->handle));
+ PUT_C(rv, "bufnr", BUFFER_OBJ(wp->w_buffer->handle));
}
}
return rv;
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 041aaed976..52372b838e 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -200,10 +200,10 @@
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
-Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err)
+Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Error *err)
FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
-#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
+#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
@@ -213,7 +213,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
return 0;
}
- FloatConfig fconfig = FLOAT_CONFIG_INIT;
+ WinConfig fconfig = WIN_CONFIG_INIT;
if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
}
@@ -257,7 +257,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
restore_win(&switchwin, true);
}
if (wp) {
- wp->w_float_config = fconfig;
+ wp->w_config = fconfig;
}
} else {
wp = win_new_float(NULL, false, fconfig, err);
@@ -332,10 +332,10 @@ static int win_split_flags(WinSplit split, bool toplevel)
/// @param config Map defining the window configuration,
/// see |nvim_open_win()|
/// @param[out] err Error details, if any
-void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
+void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
FUNC_API_SINCE(6)
{
-#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
+#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
win_T *win = find_window_by_handle(window, err);
if (!win) {
return;
@@ -345,7 +345,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
bool has_split = HAS_KEY_X(config, split);
bool has_vertical = HAS_KEY_X(config, vertical);
// reuse old values, if not overridden
- FloatConfig fconfig = win->w_float_config;
+ WinConfig fconfig = win->w_config;
bool to_split = config->relative.size == 0
&& !(HAS_KEY_X(config, external) ? config->external : fconfig.external)
@@ -387,7 +387,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
}
}
}
- win->w_float_config = fconfig;
+ win->w_config = fconfig;
// If there's no "vertical" or "split" set, or if "split" is unchanged,
// then we can just change the size of the window.
@@ -477,7 +477,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
} else {
win_remove(win, win_tp == curtab ? NULL : win_tp);
ui_comp_remove_grid(&win->w_grid_alloc);
- if (win->w_float_config.external) {
+ if (win->w_config.external) {
for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
if (tp == curtab) {
continue;
@@ -539,30 +539,24 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
#undef HAS_KEY_X
}
-static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig,
- BorderTextType bordertext_type)
+#define PUT_KEY_X(d, key, value) PUT_KEY(d, win_config, key, value)
+static void config_put_bordertext(Dict(win_config) *config, WinConfig *fconfig,
+ BorderTextType bordertext_type, Arena *arena)
{
VirtText vt;
AlignTextPos align;
- char *field_name;
- char *field_pos_name;
switch (bordertext_type) {
case kBorderTextTitle:
vt = fconfig->title_chunks;
align = fconfig->title_pos;
- field_name = "title";
- field_pos_name = "title_pos";
break;
case kBorderTextFooter:
vt = fconfig->footer_chunks;
align = fconfig->footer_pos;
- field_name = "footer";
- field_pos_name = "footer_pos";
break;
}
- Array bordertext = virt_text_to_array(vt, true);
- PUT(config, field_name, ARRAY_OBJ(bordertext));
+ Array bordertext = virt_text_to_array(vt, true, arena);
char *pos;
switch (align) {
@@ -576,9 +570,16 @@ static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig,
pos = "right";
break;
}
- PUT(config, field_pos_name, CSTR_TO_OBJ(pos));
- return config;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ PUT_KEY_X(*config, title, ARRAY_OBJ(bordertext));
+ PUT_KEY_X(*config, title_pos, cstr_as_string(pos));
+ break;
+ case kBorderTextFooter:
+ PUT_KEY_X(*config, footer, ARRAY_OBJ(bordertext));
+ PUT_KEY_X(*config, footer_pos, cstr_as_string(pos));
+ }
}
/// Gets window configuration.
@@ -590,7 +591,7 @@ static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig,
/// @param window Window handle, or 0 for current window
/// @param[out] err Error details, if any
/// @return Map defining the window configuration, see |nvim_open_win()|
-Dictionary nvim_win_get_config(Window window, Error *err)
+Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err)
FUNC_API_SINCE(6)
{
/// Keep in sync with FloatRelative in buffer_defs.h
@@ -599,74 +600,71 @@ Dictionary nvim_win_get_config(Window window, Error *err)
/// Keep in sync with WinSplit in buffer_defs.h
static const char *const win_split_str[] = { "left", "right", "above", "below" };
- Dictionary rv = ARRAY_DICT_INIT;
+ Dict(win_config) rv = { 0 };
win_T *wp = find_window_by_handle(window, err);
if (!wp) {
return rv;
}
- FloatConfig *config = &wp->w_float_config;
+ WinConfig *config = &wp->w_config;
- PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable));
- PUT(rv, "external", BOOLEAN_OBJ(config->external));
- PUT(rv, "hide", BOOLEAN_OBJ(config->hide));
+ PUT_KEY_X(rv, focusable, config->focusable);
+ PUT_KEY_X(rv, external, config->external);
+ PUT_KEY_X(rv, hide, config->hide);
if (wp->w_floating) {
- PUT(rv, "width", INTEGER_OBJ(config->width));
- PUT(rv, "height", INTEGER_OBJ(config->height));
+ PUT_KEY_X(rv, width, config->width);
+ PUT_KEY_X(rv, height, config->height);
if (!config->external) {
if (config->relative == kFloatRelativeWindow) {
- PUT(rv, "win", INTEGER_OBJ(config->window));
+ PUT_KEY_X(rv, win, config->window);
if (config->bufpos.lnum >= 0) {
- Array pos = ARRAY_DICT_INIT;
- ADD(pos, INTEGER_OBJ(config->bufpos.lnum));
- ADD(pos, INTEGER_OBJ(config->bufpos.col));
- PUT(rv, "bufpos", ARRAY_OBJ(pos));
+ Array pos = arena_array(arena, 2);
+ ADD_C(pos, INTEGER_OBJ(config->bufpos.lnum));
+ ADD_C(pos, INTEGER_OBJ(config->bufpos.col));
+ PUT_KEY_X(rv, bufpos, pos);
}
}
- PUT(rv, "anchor", CSTR_TO_OBJ(float_anchor_str[config->anchor]));
- PUT(rv, "row", FLOAT_OBJ(config->row));
- PUT(rv, "col", FLOAT_OBJ(config->col));
- PUT(rv, "zindex", INTEGER_OBJ(config->zindex));
+ PUT_KEY_X(rv, anchor, cstr_as_string(float_anchor_str[config->anchor]));
+ PUT_KEY_X(rv, row, config->row);
+ PUT_KEY_X(rv, col, config->col);
+ PUT_KEY_X(rv, zindex, config->zindex);
}
if (config->border) {
- Array border = ARRAY_DICT_INIT;
+ Array border = arena_array(arena, 8);
for (size_t i = 0; i < 8; i++) {
- Array tuple = ARRAY_DICT_INIT;
-
- String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE);
+ String s = cstrn_as_string(config->border_chars[i], MAX_SCHAR_SIZE);
int hi_id = config->border_hl_ids[i];
char *hi_name = syn_id2name(hi_id);
if (hi_name[0]) {
- ADD(tuple, STRING_OBJ(s));
- ADD(tuple, CSTR_TO_OBJ(hi_name));
- ADD(border, ARRAY_OBJ(tuple));
+ Array tuple = arena_array(arena, 2);
+ ADD_C(tuple, STRING_OBJ(s));
+ ADD_C(tuple, CSTR_AS_OBJ(hi_name));
+ ADD_C(border, ARRAY_OBJ(tuple));
} else {
- ADD(border, STRING_OBJ(s));
+ ADD_C(border, STRING_OBJ(s));
}
}
- PUT(rv, "border", ARRAY_OBJ(border));
+ PUT_KEY_X(rv, border, ARRAY_OBJ(border));
if (config->title) {
- rv = config_put_bordertext(rv, config, kBorderTextTitle);
+ config_put_bordertext(&rv, config, kBorderTextTitle, arena);
}
if (config->footer) {
- rv = config_put_bordertext(rv, config, kBorderTextFooter);
+ config_put_bordertext(&rv, config, kBorderTextFooter, arena);
}
}
} else if (!config->external) {
- PUT(rv, "width", INTEGER_OBJ(wp->w_width));
- PUT(rv, "height", INTEGER_OBJ(wp->w_height));
+ PUT_KEY_X(rv, width, wp->w_width);
+ PUT_KEY_X(rv, height, wp->w_height);
WinSplit split = win_split_dir(wp);
- PUT(rv, "split", CSTR_TO_OBJ(win_split_str[split]));
+ PUT_KEY_X(rv, split, cstr_as_string(win_split_str[split]));
}
- if (wp->w_floating && !config->external) {
- PUT(rv, "relative", CSTR_TO_OBJ(float_relative_str[config->relative]));
- } else {
- PUT(rv, "relative", CSTR_TO_OBJ(""));
- }
+ const char *rel = (wp->w_floating && !config->external
+ ? float_relative_str[config->relative] : "");
+ PUT_KEY_X(rv, relative, cstr_as_string(rel));
return rv;
}
@@ -736,8 +734,8 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
return true;
}
-static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
- FloatConfig *fconfig, Error *err)
+static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, WinConfig *fconfig,
+ Error *err)
{
if (bordertext.type != kObjectTypeString && bordertext.type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "title/footer must be string or array");
@@ -795,7 +793,7 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
}
static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type,
- FloatConfig *fconfig, Error *err)
+ WinConfig *fconfig, Error *err)
{
AlignTextPos *align;
switch (bordertext_type) {
@@ -834,7 +832,7 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex
return true;
}
-static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
+static void parse_border_style(Object style, WinConfig *fconfig, Error *err)
{
struct {
const char *name;
@@ -939,10 +937,10 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
}
}
-static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf,
+static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, bool reconf,
bool new_win, Error *err)
{
-#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
+#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
bool has_relative = false, relative_is_win = false, is_split = false;
if (config->relative.size > 0) {
if (!parse_float_relative(config->relative, &fconfig->relative)) {
diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c
index e9108f72cc..a02c22deae 100644
--- a/src/nvim/arglist.c
+++ b/src/nvim/arglist.c
@@ -1093,11 +1093,6 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
// When the ":tab" modifier was used do this for all tab pages.
arg_all_close_unused_windows(&aall);
- // Now set the last used tabpage to where we started.
- if (valid_tabpage(new_lu_tp)) {
- lastused_tabpage = new_lu_tp;
- }
-
// Open a window for files in the argument list that don't have one.
// ARGCOUNT may change while doing this, because of autocommands.
if (count > aall.opened_len || count <= 0) {
@@ -1134,6 +1129,12 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
if (valid_tabpage(aall.new_curtab)) {
goto_tabpage_tp(aall.new_curtab, true, true);
}
+
+ // Now set the last used tabpage to where we started.
+ if (valid_tabpage(new_lu_tp)) {
+ lastused_tabpage = new_lu_tp;
+ }
+
if (win_valid(aall.new_curwin)) {
win_enter(aall.new_curwin, false);
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index a385161beb..1edc60f230 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -121,7 +121,7 @@ static void augroup_map_del(int id, const char *name)
{
if (name != NULL) {
String key;
- map_del(String, int)(&map_augroup_name_to_id, cstr_as_string((char *)name), &key);
+ map_del(String, int)(&map_augroup_name_to_id, cstr_as_string(name), &key);
api_free_string(key);
}
if (id > 0) {
@@ -476,7 +476,7 @@ void augroup_del(char *name, bool stupid_legacy_mode)
int augroup_find(const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- int existing_id = map_get(String, int)(&map_augroup_name_to_id, cstr_as_string((char *)name));
+ int existing_id = map_get(String, int)(&map_augroup_name_to_id, cstr_as_string(name));
if (existing_id == AUGROUP_DELETED) {
return existing_id;
}
@@ -1335,7 +1335,7 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
if (need_append) {
win_append(lastwin, auc_win);
pmap_put(int)(&window_handles, auc_win->handle, auc_win);
- win_config_float(auc_win, auc_win->w_float_config);
+ win_config_float(auc_win, auc_win->w_config);
}
// Prevent chdir() call in win_enter_ext(), through do_autochdir()
int save_acd = p_acd;
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 00cb7272c0..38c3ee13aa 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -2773,7 +2773,7 @@ void get_winopts(buf_T *buf)
curwin->w_changelistidx = wip->wi_changelistidx;
}
- if (curwin->w_float_config.style == kWinStyleMinimal) {
+ if (curwin->w_config.style == kWinStyleMinimal) {
didset_window_options(curwin, false);
win_set_minimal_style(curwin);
}
@@ -3326,7 +3326,7 @@ void maketitle(void)
if (*p_titlestring != NUL) {
if (stl_syntax & STL_IN_TITLE) {
build_stl_str_hl(curwin, buf, sizeof(buf), p_titlestring,
- kOptTitlestring, 0, 0, maxlen, NULL, NULL, NULL);
+ kOptTitlestring, 0, 0, maxlen, NULL, NULL, NULL, NULL);
title_str = buf;
} else {
title_str = p_titlestring;
@@ -3431,7 +3431,7 @@ void maketitle(void)
if (*p_iconstring != NUL) {
if (stl_syntax & STL_IN_ICON) {
build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring,
- kOptIconstring, 0, 0, 0, NULL, NULL, NULL);
+ kOptIconstring, 0, 0, 0, NULL, NULL, NULL, NULL);
} else {
icon_str = p_iconstring;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index ea014c3918..80e8b88182 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -913,6 +913,7 @@ typedef enum {
kBorderTextFooter = 1,
} BorderTextType;
+/// See ":help nvim_open_win()" for documentation.
typedef struct {
Window window;
lpos_T bufpos;
@@ -941,19 +942,19 @@ typedef struct {
bool noautocmd;
bool fixed;
bool hide;
-} FloatConfig;
-
-#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
- .bufpos = { -1, 0 }, \
- .row = 0, .col = 0, .anchor = 0, \
- .relative = 0, .external = false, \
- .focusable = true, \
- .split = 0, \
- .zindex = kZIndexFloatDefault, \
- .style = kWinStyleUnused, \
- .noautocmd = false, \
- .hide = false, \
- .fixed = false })
+} WinConfig;
+
+#define WIN_CONFIG_INIT ((WinConfig){ .height = 0, .width = 0, \
+ .bufpos = { -1, 0 }, \
+ .row = 0, .col = 0, .anchor = 0, \
+ .relative = 0, .external = false, \
+ .focusable = true, \
+ .split = 0, \
+ .zindex = kZIndexFloatDefault, \
+ .style = kWinStyleUnused, \
+ .noautocmd = false, \
+ .hide = false, \
+ .fixed = false })
// Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums().
@@ -1278,7 +1279,7 @@ struct window_S {
bool w_pos_changed; // true if window position changed
bool w_floating; ///< whether the window is floating
bool w_float_is_info; // the floating window is info float
- FloatConfig w_float_config;
+ WinConfig w_config;
// w_fraction is the fractional row of the cursor within the window, from
// 0 at the top row to FRACTION_MULT at the last row.
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index a91a890d0e..1a02ac78d7 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -55,36 +55,33 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, BufUpdateCallbacks cb
kv_push(buf->update_channels, channel_id);
if (send_buffer) {
- Array args = ARRAY_DICT_INIT;
- args.size = 6;
- args.items = xcalloc(args.size, sizeof(Object));
+ MAXSIZE_TEMP_ARRAY(args, 6);
// the first argument is always the buffer handle
- args.items[0] = BUFFER_OBJ(buf->handle);
- args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
+ ADD_C(args, BUFFER_OBJ(buf->handle));
+ ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
// the first line that changed (zero-indexed)
- args.items[2] = INTEGER_OBJ(0);
+ ADD_C(args, INTEGER_OBJ(0));
// the last line that was changed
- args.items[3] = INTEGER_OBJ(-1);
- Array linedata = ARRAY_DICT_INIT;
+ ADD_C(args, INTEGER_OBJ(-1));
// collect buffer contents
STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM");
size_t line_count = (size_t)buf->b_ml.ml_line_count;
- if (line_count >= 1) {
- linedata.size = line_count;
- linedata.items = xcalloc(line_count, sizeof(Object));
-
- buf_collect_lines(buf, line_count, 1, 0, true, &linedata, NULL, NULL);
+ Array linedata = ARRAY_DICT_INIT;
+ Arena arena = ARENA_EMPTY;
+ if (line_count > 0) {
+ linedata = arena_array(&arena, line_count);
+ buf_collect_lines(buf, line_count, 1, 0, true, &linedata, NULL, &arena);
}
- args.items[4] = ARRAY_OBJ(linedata);
- args.items[5] = BOOLEAN_OBJ(false);
+ ADD_C(args, ARRAY_OBJ(linedata));
+ ADD_C(args, BOOLEAN_OBJ(false));
rpc_send_event(channel_id, "nvim_buf_lines_event", args);
- api_free_array(args); // TODO(bfredl): no
+ arena_mem_free(arena_finish(&arena));
} else {
buf_updates_changedtick_single(buf, channel_id);
}
@@ -176,13 +173,10 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
}
if (thecb != LUA_NOREF) {
- Array args = ARRAY_DICT_INIT;
- Object items[1];
- args.size = 1;
- args.items = items;
+ MAXSIZE_TEMP_ARRAY(args, 1);
// the first argument is always the buffer handle
- args.items[0] = BUFFER_OBJ(buf->handle);
+ ADD_C(args, BUFFER_OBJ(buf->handle));
TEXTLOCK_WRAP({
nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
@@ -219,45 +213,43 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
// if one the channels doesn't work, put its ID here so we can remove it later
uint64_t badchannelid = 0;
+ Arena arena = ARENA_EMPTY;
+ Array linedata = ARRAY_DICT_INIT;
+ if (num_added > 0 && kv_size(buf->update_channels)) {
+ STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM");
+ linedata = arena_array(&arena, (size_t)num_added);
+ buf_collect_lines(buf, (size_t)num_added, firstline, 0, true, &linedata,
+ NULL, &arena);
+ }
+
// notify each of the active channels
for (size_t i = 0; i < kv_size(buf->update_channels); i++) {
uint64_t channelid = kv_A(buf->update_channels, i);
// send through the changes now channel contents now
- Array args = ARRAY_DICT_INIT;
- args.size = 6;
- args.items = xcalloc(args.size, sizeof(Object));
+ MAXSIZE_TEMP_ARRAY(args, 6);
// the first argument is always the buffer handle
- args.items[0] = BUFFER_OBJ(buf->handle);
+ ADD_C(args, BUFFER_OBJ(buf->handle));
// next argument is b:changedtick
- args.items[1] = send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL;
+ ADD_C(args, send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL);
// the first line that changed (zero-indexed)
- args.items[2] = INTEGER_OBJ(firstline - 1);
+ ADD_C(args, INTEGER_OBJ(firstline - 1));
// the last line that was changed
- args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed);
+ ADD_C(args, INTEGER_OBJ(firstline - 1 + num_removed));
// linedata of lines being swapped in
- Array linedata = ARRAY_DICT_INIT;
- if (num_added > 0) {
- STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM");
- linedata.size = (size_t)num_added;
- linedata.items = xcalloc((size_t)num_added, sizeof(Object));
- buf_collect_lines(buf, (size_t)num_added, firstline, 0, true, &linedata,
- NULL, NULL);
- }
- args.items[4] = ARRAY_OBJ(linedata);
- args.items[5] = BOOLEAN_OBJ(false);
+ ADD_C(args, ARRAY_OBJ(linedata));
+ ADD_C(args, BOOLEAN_OBJ(false));
if (!rpc_send_event(channelid, "nvim_buf_lines_event", args)) {
// We can't unregister the channel while we're iterating over the
// update_channels array, so we remember its ID to unregister it at
// the end.
badchannelid = channelid;
}
- api_free_array(args); // TODO(bfredl): no
}
// We can only ever remove one dead channel at a time. This is OK because the
@@ -268,38 +260,37 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
buf_updates_unregister(buf, badchannelid);
}
- // notify each of the active channels
+ // callbacks don't use linedata
+ arena_mem_free(arena_finish(&arena));
+
+ // notify each of the active callbacks
size_t j = 0;
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_lines != LUA_NOREF && (cb.preview || !cmdpreview)) {
- Array args = ARRAY_DICT_INIT;
- Object items[8];
- args.size = 6; // may be increased to 8 below
- args.items = items;
+ MAXSIZE_TEMP_ARRAY(args, 8); // 6 or 8 used
// the first argument is always the buffer handle
- args.items[0] = BUFFER_OBJ(buf->handle);
+ ADD_C(args, BUFFER_OBJ(buf->handle));
// next argument is b:changedtick
- args.items[1] = send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL;
+ ADD_C(args, send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL);
// the first line that changed (zero-indexed)
- args.items[2] = INTEGER_OBJ(firstline - 1);
+ ADD_C(args, INTEGER_OBJ(firstline - 1));
// the last line that was changed
- args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed);
+ ADD_C(args, INTEGER_OBJ(firstline - 1 + num_removed));
// the last line in the updated range
- args.items[4] = INTEGER_OBJ(firstline - 1 + num_added);
+ ADD_C(args, INTEGER_OBJ(firstline - 1 + num_added));
// byte count of previous contents
- args.items[5] = INTEGER_OBJ((Integer)deleted_bytes);
+ ADD_C(args, INTEGER_OBJ((Integer)deleted_bytes));
if (cb.utf_sizes) {
- args.size = 8;
- args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints);
- args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
+ ADD_C(args, INTEGER_OBJ((Integer)deleted_codepoints));
+ ADD_C(args, INTEGER_OBJ((Integer)deleted_codeunits));
}
Object res;
diff --git a/src/nvim/change.c b/src/nvim/change.c
index fa098a3220..2db3d007d7 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -163,6 +163,71 @@ void changed_internal(buf_T *buf)
need_maketitle = true; // set window title later
}
+/// Invalidate a window's w_valid flags and w_lines[] entries after changing lines.
+static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col, linenr_T lnume,
+ linenr_T xtra)
+{
+ // If the changed line is in a range of previously folded lines,
+ // compare with the first line in that range.
+ if (wp->w_cursor.lnum <= lnum) {
+ int i = find_wl_entry(wp, lnum);
+ if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) {
+ changed_line_abv_curs_win(wp);
+ }
+ }
+
+ if (wp->w_cursor.lnum > lnum) {
+ changed_line_abv_curs_win(wp);
+ } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
+ changed_cline_bef_curs(wp);
+ }
+ if (wp->w_botline >= lnum) {
+ // Assume that botline doesn't change (inserted lines make
+ // other lines scroll down below botline).
+ approximate_botline_win(wp);
+ }
+
+ // Check if any w_lines[] entries have become invalid.
+ // For entries below the change: Correct the lnums for inserted/deleted lines.
+ // Makes it possible to stop displaying after the change.
+ for (int i = 0; i < wp->w_lines_valid; i++) {
+ if (wp->w_lines[i].wl_valid) {
+ if (wp->w_lines[i].wl_lnum >= lnum) {
+ // Do not change wl_lnum at index zero, it is used to compare with w_topline.
+ // Invalidate it instead.
+ // If lines haven been inserted/deleted and the buffer has virt_lines,
+ // invalidate the line after the changed lines as some virt_lines may
+ // now be drawn above a different line.
+ if (i == 0 || wp->w_lines[i].wl_lnum < lnume
+ || (xtra != 0 && wp->w_lines[i].wl_lnum == lnume
+ && buf_meta_total(wp->w_buffer, kMTMetaLines) > 0)) {
+ // line included in change
+ wp->w_lines[i].wl_valid = false;
+ } else if (xtra != 0) {
+ // line below change
+ wp->w_lines[i].wl_lnum += xtra;
+ wp->w_lines[i].wl_lastlnum += xtra;
+ }
+ } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
+ // change somewhere inside this range of folded lines,
+ // may need to be redrawn
+ wp->w_lines[i].wl_valid = false;
+ }
+ }
+ }
+}
+
+/// Line changed_lines_invalidate_win(), but for all windows displaying a buffer.
+void changed_lines_invalidate_buf(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume,
+ linenr_T xtra)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf) {
+ changed_lines_invalidate_win(wp, lnum, col, lnume, xtra);
+ }
+ }
+}
+
/// Common code for when a change was made.
/// See changed_lines() for the arguments.
/// Careful: may trigger autocommands that reload the buffer.
@@ -296,55 +361,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
wp->w_cline_folded = folded;
}
- // If the changed line is in a range of previously folded lines,
- // compare with the first line in that range.
- if (wp->w_cursor.lnum <= lnum) {
- int i = find_wl_entry(wp, lnum);
- if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) {
- changed_line_abv_curs_win(wp);
- }
- }
-
- if (wp->w_cursor.lnum > lnum) {
- changed_line_abv_curs_win(wp);
- } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
- changed_cline_bef_curs(wp);
- }
- if (wp->w_botline >= lnum) {
- // Assume that botline doesn't change (inserted lines make
- // other lines scroll down below botline).
- approximate_botline_win(wp);
- }
-
- // Check if any w_lines[] entries have become invalid.
- // For entries below the change: Correct the lnums for
- // inserted/deleted lines. Makes it possible to stop displaying
- // after the change.
- for (int i = 0; i < wp->w_lines_valid; i++) {
- if (wp->w_lines[i].wl_valid) {
- if (wp->w_lines[i].wl_lnum >= lnum) {
- // Do not change wl_lnum at index zero, it is used to
- // compare with w_topline. Invalidate it instead.
- // If the buffer has virt_lines, invalidate the line
- // after the changed lines as the virt_lines for a
- // changed line may become invalid.
- if (i == 0 || wp->w_lines[i].wl_lnum < lnume
- || (wp->w_lines[i].wl_lnum == lnume
- && buf_meta_total(wp->w_buffer, kMTMetaLines) > 0)) {
- // line included in change
- wp->w_lines[i].wl_valid = false;
- } else if (xtra != 0) {
- // line below change
- wp->w_lines[i].wl_lnum += xtra;
- wp->w_lines[i].wl_lastlnum += xtra;
- }
- } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
- // change somewhere inside this range of folded lines,
- // may need to be redrawn
- wp->w_lines[i].wl_valid = false;
- }
- }
- }
+ changed_lines_invalidate_win(wp, lnum, col, lnume, xtra);
// Take care of side effects for setting w_topline when folds have
// changed. Esp. when the buffer was changed in another window.
@@ -353,20 +370,19 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
}
// If lines have been added or removed, relative numbering always
- // requires a redraw.
+ // requires an update even if cursor didn't move.
if (wp->w_p_rnu && xtra != 0) {
wp->w_last_cursor_lnum_rnu = 0;
- redraw_later(wp, UPD_VALID);
}
- // Cursor line highlighting probably need to be updated with
- // "UPD_VALID" if it's below the change.
- // If the cursor line is inside the change we need to redraw more.
- if (wp->w_p_cul) {
- if (xtra == 0) {
- redraw_later(wp, UPD_VALID);
- } else if (lnum <= wp->w_last_cursorline) {
- redraw_later(wp, UPD_SOME_VALID);
+ if (wp->w_p_cul && wp->w_last_cursorline >= lnum) {
+ if (wp->w_last_cursorline < lnume) {
+ // If 'cursorline' was inside the change, it has already
+ // been invalidated in w_lines[] by the loop above.
+ wp->w_last_cursorline = 0;
+ } else {
+ // If 'cursorline' was below the change, adjust its lnum.
+ wp->w_last_cursorline += xtra;
}
}
}
@@ -489,13 +505,13 @@ void deleted_lines_mark(linenr_T lnum, int count)
}
/// Marks the area to be redrawn after a change.
-/// Consider also calling changed_line_display_buf().
+/// Consider also calling changed_lines_invalidate_buf().
///
/// @param buf the buffer where lines were changed
/// @param lnum first line with change
/// @param lnume line below last changed line
/// @param xtra number of extra lines (negative when deleting)
-void buf_redraw_changed_lines_later(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
+void changed_lines_redraw_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
{
if (buf->b_mod_set) {
// find the maximum area that must be redisplayed
@@ -543,7 +559,7 @@ void buf_redraw_changed_lines_later(buf_T *buf, linenr_T lnum, linenr_T lnume, l
void changed_lines(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra,
bool do_buf_event)
{
- buf_redraw_changed_lines_later(buf, lnum, lnume, xtra);
+ changed_lines_redraw_buf(buf, lnum, lnume, xtra);
if (xtra == 0 && curwin->w_p_diff && curwin->w_buffer == buf && !diff_internal()) {
// When the number of lines doesn't change then mark_adjust() isn't
@@ -556,8 +572,7 @@ void changed_lines(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linen
redraw_later(wp, UPD_VALID);
wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
- buf_redraw_changed_lines_later(wp->w_buffer, wlnum,
- lnume - lnum + wlnum, 0);
+ changed_lines_redraw_buf(wp->w_buffer, wlnum, lnume - lnum + wlnum, 0);
}
}
}
@@ -1150,9 +1165,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// indent to use for the new line.
if (curbuf->b_p_ai || do_si) {
// count white space on current line
- newindent = get_indent_str_vtab(saved_line,
- curbuf->b_p_ts,
- curbuf->b_p_vts_array, false);
+ newindent = indent_size_ts(saved_line, curbuf->b_p_ts, curbuf->b_p_vts_array);
if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
newindent = second_line_indent; // for ^^D command in insert mode
}
@@ -1594,9 +1607,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// Recompute the indent, it may have changed.
if (curbuf->b_p_ai || do_si) {
- newindent = get_indent_str_vtab(leader,
- curbuf->b_p_ts,
- curbuf->b_p_vts_array, false);
+ newindent = indent_size_ts(leader, curbuf->b_p_ts, curbuf->b_p_vts_array);
}
// Add the indent offset
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 05148801f4..1dc1208696 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -962,10 +962,11 @@ Dictionary channel_info(uint64_t id)
}
/// Simple int64_t comparison function for use with qsort()
-static int int64_t_cmp(const void *a, const void *b)
+static int int64_t_cmp(const void *pa, const void *pb)
{
- int64_t diff = *(int64_t *)a - *(int64_t *)b;
- return (diff < 0) ? -1 : (diff > 0);
+ const int64_t a = *(const int64_t *)pa;
+ const int64_t b = *(const int64_t *)pb;
+ return a == b ? 0 : a > b ? 1 : -1;
}
Array channel_all_info(void)
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index b13ec25f50..e4828bcc31 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -10,6 +10,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/change.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
@@ -91,15 +92,18 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
}
}
-void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor)
+void decor_redraw(buf_T *buf, int row1, int row2, int col1, DecorInline decor)
{
if (decor.ext) {
DecorVirtText *vt = decor.data.ext.vt;
while (vt) {
bool below = (vt->flags & kVTIsLines) && !(vt->flags & kVTLinesAbove);
- redraw_buf_line_later(buf, row1 + 1 + below, true);
+ linenr_T vt_lnum = row1 + 1 + below;
+ redraw_buf_line_later(buf, vt_lnum, true);
if (vt->flags & kVTIsLines || vt->pos == kVPosInline) {
- changed_line_display_buf(buf);
+ // changed_lines_redraw_buf(buf, vt_lnum, vt_lnum + 1, 0);
+ colnr_T vt_col = vt->flags & kVTIsLines ? 0 : col1;
+ changed_lines_invalidate_buf(buf, vt_lnum, vt_col, vt_lnum + 1, 0);
}
vt = vt->next;
}
@@ -190,9 +194,9 @@ void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2)
}
}
-void buf_decor_remove(buf_T *buf, int row1, int row2, DecorInline decor, bool free)
+void buf_decor_remove(buf_T *buf, int row1, int row2, int col1, DecorInline decor, bool free)
{
- decor_redraw(buf, row1, row2, decor);
+ decor_redraw(buf, row1, row2, col1, decor);
if (decor.ext) {
uint32_t idx = decor.data.ext.sh_idx;
while (idx != DECOR_ID_INVALID) {
@@ -664,19 +668,24 @@ next_mark:
return attr;
}
-typedef struct {
- DecorSignHighlight *sh;
- uint32_t id;
-} SignItem;
-
int sign_item_cmp(const void *p1, const void *p2)
{
const SignItem *s1 = (SignItem *)p1;
const SignItem *s2 = (SignItem *)p2;
- int n = s2->sh->priority - s1->sh->priority;
- return n ? n : (n = (int)(s2->id - s1->id))
- ? n : (s2->sh->sign_add_id - s1->sh->sign_add_id);
+ if (s1->sh->priority != s2->sh->priority) {
+ return s1->sh->priority < s2->sh->priority ? 1 : -1;
+ }
+
+ if (s1->id != s2->id) {
+ return s1->id < s2->id ? 1 : -1;
+ }
+
+ if (s1->sh->sign_add_id != s2->sh->sign_add_id) {
+ return s1->sh->sign_add_id < s2->sh->sign_add_id ? 1 : -1;
+ }
+
+ return 0;
}
static const uint32_t sign_filter[4] = {[kMTMetaSignText] = kMTFilterSelect,
@@ -919,7 +928,9 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
}
/// This assumes maximum one entry of each kind, which will not always be the case.
-void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
+///
+/// NB: assumes caller has allocated enough space in dict for all fields!
+void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name, Arena *arena)
{
DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT;
DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT;
@@ -953,58 +964,58 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
}
if (sh_hl.hl_id) {
- PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name));
- PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol));
+ PUT_C(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name));
+ PUT_C(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol));
priority = sh_hl.priority;
}
if (sh_hl.flags & kSHConceal) {
char buf[MAX_SCHAR_SIZE];
schar_get(buf, sh_hl.text[0]);
- PUT(*dict, "conceal", CSTR_TO_OBJ(buf));
+ PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf));
}
if (sh_hl.flags & kSHSpellOn) {
- PUT(*dict, "spell", BOOLEAN_OBJ(true));
+ PUT_C(*dict, "spell", BOOLEAN_OBJ(true));
} else if (sh_hl.flags & kSHSpellOff) {
- PUT(*dict, "spell", BOOLEAN_OBJ(false));
+ PUT_C(*dict, "spell", BOOLEAN_OBJ(false));
}
if (sh_hl.flags & kSHUIWatched) {
- PUT(*dict, "ui_watched", BOOLEAN_OBJ(true));
+ PUT_C(*dict, "ui_watched", BOOLEAN_OBJ(true));
}
if (sh_hl.url != NULL) {
- PUT(*dict, "url", STRING_OBJ(cstr_to_string(sh_hl.url)));
+ PUT_C(*dict, "url", STRING_OBJ(cstr_as_string(sh_hl.url)));
}
if (virt_text) {
if (virt_text->hl_mode) {
- PUT(*dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[virt_text->hl_mode]));
+ PUT_C(*dict, "hl_mode", CSTR_AS_OBJ(hl_mode_str[virt_text->hl_mode]));
}
- Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name);
- PUT(*dict, "virt_text", ARRAY_OBJ(chunks));
- PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide));
- PUT(*dict, "virt_text_repeat_linebreak", BOOLEAN_OBJ(virt_text->flags & kVTRepeatLinebreak));
+ Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name, arena);
+ PUT_C(*dict, "virt_text", ARRAY_OBJ(chunks));
+ PUT_C(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide));
+ PUT_C(*dict, "virt_text_repeat_linebreak", BOOLEAN_OBJ(virt_text->flags & kVTRepeatLinebreak));
if (virt_text->pos == kVPosWinCol) {
- PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col));
+ PUT_C(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col));
}
- PUT(*dict, "virt_text_pos", CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos]));
+ PUT_C(*dict, "virt_text_pos", CSTR_AS_OBJ(virt_text_pos_str[virt_text->pos]));
priority = virt_text->priority;
}
if (virt_lines) {
- Array all_chunks = ARRAY_DICT_INIT;
+ Array all_chunks = arena_array(arena, kv_size(virt_lines->data.virt_lines));
bool virt_lines_leftcol = false;
for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) {
virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col;
- Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name);
+ Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name, arena);
ADD(all_chunks, ARRAY_OBJ(chunks));
}
- PUT(*dict, "virt_lines", ARRAY_OBJ(all_chunks));
- PUT(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove));
- PUT(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ PUT_C(*dict, "virt_lines", ARRAY_OBJ(all_chunks));
+ PUT_C(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove));
+ PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
priority = virt_lines->priority;
}
@@ -1012,11 +1023,11 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
if (sh_sign.text[0]) {
char buf[SIGN_WIDTH * MAX_SCHAR_SIZE];
describe_sign_text(buf, sh_sign.text);
- PUT(*dict, "sign_text", CSTR_TO_OBJ(buf));
+ PUT_C(*dict, "sign_text", CSTR_TO_ARENA_OBJ(arena, buf));
}
if (sh_sign.sign_name) {
- PUT(*dict, "sign_name", CSTR_TO_OBJ(sh_sign.sign_name));
+ PUT_C(*dict, "sign_name", CSTR_AS_OBJ(sh_sign.sign_name));
}
// uncrustify:off
@@ -1033,14 +1044,14 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
for (int j = 0; hls[j].name; j++) {
if (hls[j].val) {
- PUT(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name));
+ PUT_C(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name));
}
}
priority = sh_sign.priority;
}
if (priority != -1) {
- PUT(*dict, "priority", INTEGER_OBJ(priority));
+ PUT_C(*dict, "priority", INTEGER_OBJ(priority));
}
}
@@ -1068,7 +1079,7 @@ uint16_t decor_type_flags(DecorInline decor)
Object hl_group_name(int hl_id, bool hl_name)
{
if (hl_name) {
- return CSTR_TO_OBJ(syn_id2name(hl_id));
+ return CSTR_AS_OBJ(syn_id2name(hl_id));
} else {
return INTEGER_OBJ(hl_id);
}
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index b5c0755d44..c275fcd7d4 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -929,7 +929,8 @@ static int get_rightmost_vcol(win_T *wp, const int *color_cols)
/// @param lnum line to display
/// @param startrow first row relative to window grid
/// @param endrow last grid row to be redrawn
-/// @param col_rows only update the columns for this amount of rows
+/// @param col_rows set to the height of the line when only updating the columns,
+/// otherwise set to 0
/// @param spv 'spell' related variables kept between calls for "wp"
/// @param foldinfo fold info for this line
/// @param[in, out] providers decoration providers active this line
@@ -1556,6 +1557,35 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
win_col_offset = wlv.off;
+ // When only updating the columns and that's done, stop here.
+ if (col_rows > 0) {
+ win_put_linebuf(wp, wlv.row, 0, wlv.off, wlv.off, bg_attr, false);
+ // Need to update more screen lines if:
+ // - 'statuscolumn' needs to be drawn, or
+ // - LineNrAbove or LineNrBelow is used, or
+ // - still drawing filler lines.
+ if ((wlv.row + 1 - wlv.startrow < col_rows
+ && (statuscol.draw
+ || win_hl_attr(wp, HLF_LNA) != win_hl_attr(wp, HLF_N)
+ || win_hl_attr(wp, HLF_LNB) != win_hl_attr(wp, HLF_N)))
+ || wlv.filler_todo > 0) {
+ wlv.row++;
+ if (wlv.row == endrow) {
+ break;
+ }
+ wlv.filler_todo--;
+ if (wlv.filler_todo == 0 && (wp->w_botfill || end_fill)) {
+ break;
+ }
+ // win_line_start(wp, &wlv);
+ wlv.col = 0;
+ wlv.off = 0;
+ continue;
+ } else {
+ break;
+ }
+ }
+
// Check if 'breakindent' applies and show it.
if (!wp->w_briopt_sbr) {
handle_breakindent(wp, &wlv);
@@ -1580,24 +1610,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
apply_cursorline_highlight(wp, &wlv);
}
- // When still displaying '$' of change command, stop at cursor
- if (((dollar_vcol >= 0
- && wp == curwin
- && lnum == wp->w_cursor.lnum
- && wlv.vcol >= wp->w_virtcol)
- || col_rows > 0)
- && wlv.filler_todo <= 0) {
- if (col_rows == 0) {
- draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
- }
+ // When still displaying '$' of change command, stop at cursor.
+ if (dollar_vcol >= 0 && wp == curwin
+ && lnum == wp->w_cursor.lnum && wlv.vcol >= wp->w_virtcol) {
+ draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
// don't clear anything after wlv.col
win_put_linebuf(wp, wlv.row, 0, wlv.col, wlv.col, bg_attr, false);
- // update the 'statuscolumn' for the entire line size
- if (col_rows > 0 && statuscol.draw && ++wlv.row - wlv.startrow < col_rows) {
- draw_cols = true;
- wlv.off = 0;
- continue;
- }
// Pretend we have finished updating the window. Except when
// 'cursorcolumn' is set.
if (wp->w_p_cuc) {
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index b0c97f5be3..4df01d9476 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -159,8 +159,8 @@ void conceal_check_cursor_line(void)
/// There may be some time between setting Rows and Columns and (re)allocating
/// default_grid arrays. This happens when starting up and when
/// (manually) changing the screen size. Always use default_grid.rows and
-/// default_grid.Columns to access items in default_grid.chars[]. Use Rows
-/// and Columns for positioning text etc. where the final size of the screen is
+/// default_grid.cols to access items in default_grid.chars[]. Use Rows and
+/// Columns for positioning text etc. where the final size of the screen is
/// needed.
///
/// @return whether resizing has been done
@@ -738,7 +738,7 @@ int win_get_bordertext_col(int total_col, int text_width, AlignTextPos align)
static void win_redr_border(win_T *wp)
{
wp->w_redr_border = false;
- if (!(wp->w_floating && wp->w_float_config.border)) {
+ if (!(wp->w_floating && wp->w_config.border)) {
return;
}
@@ -746,9 +746,9 @@ static void win_redr_border(win_T *wp)
schar_T chars[8];
for (int i = 0; i < 8; i++) {
- chars[i] = schar_from_str(wp->w_float_config.border_chars[i]);
+ chars[i] = schar_from_str(wp->w_config.border_chars[i]);
}
- int *attrs = wp->w_float_config.border_attr;
+ int *attrs = wp->w_config.border_attr;
int *adj = wp->w_border_adj;
int irow = wp->w_height_inner + wp->w_winbar_height;
@@ -764,10 +764,10 @@ static void win_redr_border(win_T *wp)
grid_line_put_schar(i + adj[3], chars[1], attrs[1]);
}
- if (wp->w_float_config.title) {
- int title_col = win_get_bordertext_col(icol, wp->w_float_config.title_width,
- wp->w_float_config.title_pos);
- win_redr_bordertext(wp, wp->w_float_config.title_chunks, title_col);
+ if (wp->w_config.title) {
+ int title_col = win_get_bordertext_col(icol, wp->w_config.title_width,
+ wp->w_config.title_pos);
+ win_redr_bordertext(wp, wp->w_config.title_chunks, title_col);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
@@ -800,10 +800,10 @@ static void win_redr_border(win_T *wp)
grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]);
}
- if (wp->w_float_config.footer) {
- int footer_col = win_get_bordertext_col(icol, wp->w_float_config.footer_width,
- wp->w_float_config.footer_pos);
- win_redr_bordertext(wp, wp->w_float_config.footer_chunks, footer_col);
+ if (wp->w_config.footer) {
+ int footer_col = win_get_bordertext_col(icol, wp->w_config.footer_width,
+ wp->w_config.footer_pos);
+ win_redr_bordertext(wp, wp->w_config.footer_chunks, footer_col);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
@@ -1149,11 +1149,11 @@ void clearmode(void)
static void recording_mode(int attr)
{
- msg_puts_attr(_("recording"), attr);
if (shortmess(SHM_RECORDING)) {
return;
}
+ msg_puts_attr(_("recording"), attr);
char s[4];
snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
msg_puts_attr(s, attr);
@@ -1643,12 +1643,6 @@ static void win_update(win_T *wp)
top_end = 1;
}
}
-
- // When line numbers are displayed need to redraw all lines below
- // inserted/deleted lines.
- if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) {
- mod_bot = MAXLNUM;
- }
}
wp->w_redraw_top = 0; // reset for next time
@@ -2326,9 +2320,12 @@ static void win_update(win_T *wp)
idx++;
lnum += foldinfo.fi_lines + 1;
} else {
- if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) {
- // 'relativenumber' set and cursor moved vertically: The
- // text doesn't need to be drawn, but the number column does.
+ // If:
+ // - 'number' is set and below inserted/deleted lines, or
+ // - 'relativenumber' is set and cursor moved vertically,
+ // the text doesn't need to be redrawn, but the number column does.
+ if ((wp->w_p_nu && mod_top != 0 && lnum >= mod_bot && buf->b_mod_xlines != 0)
+ || (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum)) {
foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum
? cursorline_fi : fold_info(wp, lnum);
win_line(wp, lnum, srow, wp->w_grid.rows, wp->w_lines[idx].wl_size, &spv, info);
@@ -2346,6 +2343,7 @@ static void win_update(win_T *wp)
// 'statuscolumn' width has changed or errored, start from the top.
if (wp->w_redr_statuscol) {
+redr_statuscol:
wp->w_redr_statuscol = false;
idx = 0;
row = 0;
@@ -2429,6 +2427,10 @@ static void win_update(win_T *wp)
spellvars_T zero_spv = { 0 };
foldinfo_T zero_foldinfo = { 0 };
row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, 0, &zero_spv, zero_foldinfo);
+ if (wp->w_redr_statuscol) {
+ eof = false;
+ goto redr_statuscol;
+ }
}
} else if (dollar_vcol == -1) {
wp->w_botline = lnum;
@@ -2864,15 +2866,3 @@ bool win_cursorline_standout(const win_T *wp)
{
return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
}
-
-/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
-/// Also when concealing is on and 'concealcursor' is not active.
-void redraw_for_cursorline(win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
- && (wp->w_p_rnu || win_cursorline_standout(wp))) {
- // win_line() will redraw the number column and cursorline only.
- redraw_later(wp, UPD_VALID);
- }
-}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 41df039b85..b7b32883c2 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -363,7 +363,13 @@ static void insert_enter(InsertState *s)
ins_apply_autocmds(EVENT_INSERTLEAVE);
}
did_cursorhold = false;
- curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+
+ // ins_redraw() triggers TextChangedI only when no characters
+ // are in the typeahead buffer, so only reset curbuf->b_last_changedtick
+ // if the TextChangedI was not blocked by char_avail() (e.g. using :norm!)
+ if (!char_avail()) {
+ curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ }
}
static int insert_check(VimState *state)
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 655d6c9ab3..e4d2a219d9 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -2093,8 +2093,8 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
flags = tv_get_string_buf(&argvars[1], nbuf);
}
- nvim_feedkeys(cstr_as_string((char *)keys),
- cstr_as_string((char *)flags), true);
+ nvim_feedkeys(cstr_as_string(keys),
+ cstr_as_string(flags), true);
}
/// "filereadable()" function
@@ -4528,7 +4528,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
+ nlua_typval_eval(cstr_as_string(str), &argvars[1], rettv);
}
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index e265dc139a..70146a8602 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -406,18 +406,16 @@ static int sort_compare(const void *s1, const void *s2)
// number.
if (sort_nr) {
if (l1.st_u.num.is_number != l2.st_u.num.is_number) {
- result = l1.st_u.num.is_number - l2.st_u.num.is_number;
+ result = l1.st_u.num.is_number > l2.st_u.num.is_number ? 1 : -1;
} else {
result = l1.st_u.num.value == l2.st_u.num.value
? 0
- : l1.st_u.num.value > l2.st_u.num.value
- ? 1
- : -1;
+ : l1.st_u.num.value > l2.st_u.num.value ? 1 : -1;
}
} else if (sort_flt) {
result = l1.st_u.value_flt == l2.st_u.value_flt
- ? 0 : l1.st_u.value_flt > l2.st_u.value_flt
- ? 1 : -1;
+ ? 0
+ : l1.st_u.value_flt > l2.st_u.value_flt ? 1 : -1;
} else {
// We need to copy one line into "sortbuf1", because there is no
// guarantee that the first pointer becomes invalid when obtaining the
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 484b3572ab..8016e37ca7 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -551,7 +551,7 @@ void ex_listdo(exarg_T *eap)
break;
}
assert(wp);
- execute = !wp->w_floating || wp->w_float_config.focusable;
+ execute = !wp->w_floating || wp->w_config.focusable;
if (execute) {
win_goto(wp);
if (curwin != wp) {
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 93a2b9c16a..e753ad199a 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -75,7 +75,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
// not paired: we can revise in place
if (!invalid && mt_decor_any(old_mark)) {
mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_DECOR_SIGNTEXT;
- buf_decor_remove(buf, row, row, mt_decor(old_mark), true);
+ buf_decor_remove(buf, row, row, col, mt_decor(old_mark), true);
}
mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
mt_itr_rawkey(itr).flags |= flags;
@@ -84,7 +84,8 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
}
marktree_del_itr(buf->b_marktree, itr, false);
if (!invalid) {
- buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, mt_decor(old_mark), true);
+ buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.pos.col,
+ mt_decor(old_mark), true);
}
}
} else {
@@ -99,7 +100,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
revised:
if (decor_flags || decor.ext) {
buf_put_decor(buf, decor, row, end_row > -1 ? end_row : row);
- decor_redraw(buf, row, end_row > -1 ? end_row : row, decor);
+ decor_redraw(buf, row, end_row > -1 ? end_row : row, col, decor);
}
if (idp) {
@@ -115,6 +116,12 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool
return;
}
+ // Only the position before undo needs to be redrawn here,
+ // as the position after undo should be marked as changed.
+ if (!invalid && mt_decor_any(key) && key.pos.row != row) {
+ decor_redraw(buf, key.pos.row, key.pos.row, key.pos.col, mt_decor(key));
+ }
+
int row1 = 0;
int row2 = 0;
if (invalid) {
@@ -170,7 +177,7 @@ void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore)
if (mt_invalid(key)) {
decor_free(mt_decor(key));
} else {
- buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true);
+ buf_decor_remove(buf, key.pos.row, key2.pos.row, key.pos.col, mt_decor(key), true);
}
}
@@ -369,7 +376,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln
} else {
invalidated = true;
mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID;
- buf_decor_remove(buf, mark.pos.row, endpos.row, mt_decor(mark), false);
+ buf_decor_remove(buf, mark.pos.row, endpos.row, mark.pos.col, mt_decor(mark), false);
}
}
}
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 2eee1724c0..04fe43b712 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -55,15 +55,15 @@ local function add_function(fn)
-- for specifying errors
fn.parameters[#fn.parameters] = nil
end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
+ fn.has_lua_imp = true
+ fn.parameters[#fn.parameters] = nil
+ end
if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
-- return value is allocated in an arena
fn.arena_return = true
fn.parameters[#fn.parameters] = nil
end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
- fn.has_lua_imp = true
- fn.parameters[#fn.parameters] = nil
- end
end
end
@@ -212,6 +212,9 @@ for _, f in ipairs(functions) do
end
f_exported.parameters[i] = param
end
+ if startswith(f.return_type, 'Dict(') then
+ f_exported.return_type = 'Dictionary'
+ end
exported_functions[#exported_functions + 1] = f_exported
end
end
@@ -279,7 +282,7 @@ for _, k in ipairs(keysets) do
return k.name .. '_table[' .. idx .. '].str'
end)
- keysets_defs:write('extern KeySetLink ' .. k.name .. '_table[];\n')
+ keysets_defs:write('extern KeySetLink ' .. k.name .. '_table[' .. (1 + #neworder) .. '];\n')
local function typename(type)
if type == 'HLGroupID' then
@@ -532,7 +535,6 @@ for i = 1, #functions do
end
-- function call
- local call_args = table.concat(args, ', ')
output:write('\n ')
if fn.return_type ~= 'void' then
-- has a return value, prefix the call with a declaration
@@ -542,61 +544,53 @@ for i = 1, #functions do
-- write the function name and the opening parenthesis
output:write(fn.name .. '(')
+ local call_args = {}
if fn.receives_channel_id then
- -- if the function receives the channel id, pass it as first argument
- if #args > 0 or fn.can_fail then
- output:write('channel_id, ')
- if fn.receives_array_args then
- -- if the function receives the array args, pass it the second argument
- output:write('args, ')
- end
- output:write(call_args)
- else
- output:write('channel_id')
- if fn.receives_array_args then
- output:write(', args')
- end
- end
- else
- if fn.receives_array_args then
- if #args > 0 or fn.call_fail then
- output:write('args, ' .. call_args)
- else
- output:write('args')
- end
- else
- output:write(call_args)
- end
+ table.insert(call_args, 'channel_id')
+ end
+
+ if fn.receives_array_args then
+ table.insert(call_args, 'args')
+ end
+
+ for _, a in ipairs(args) do
+ table.insert(call_args, a)
end
if fn.arena_return then
- output:write(', arena')
+ table.insert(call_args, 'arena')
end
if fn.has_lua_imp then
- if #args > 0 then
- output:write(', NULL')
- else
- output:write('NULL')
- end
+ table.insert(call_args, 'NULL')
+ end
+
+ if fn.can_fail then
+ table.insert(call_args, 'error')
end
+ output:write(table.concat(call_args, ', '))
+ output:write(');\n')
+
if fn.can_fail then
-- if the function can fail, also pass a pointer to the local error object
- if #args > 0 then
- output:write(', error);\n')
- else
- output:write('error);\n')
- end
-- and check for the error
output:write('\n if (ERROR_SET(error)) {')
output:write('\n goto cleanup;')
output:write('\n }\n')
- else
- output:write(');\n')
end
- if fn.return_type ~= 'void' then
+ local ret_type = real_type(fn.return_type)
+ if string.match(ret_type, '^KeyDict_') then
+ local table = string.sub(ret_type, 9) .. '_table'
+ output:write(
+ '\n ret = DICTIONARY_OBJ(api_keydict_to_dict(&rv, '
+ .. table
+ .. ', ARRAY_SIZE('
+ .. table
+ .. '), arena));'
+ )
+ elseif ret_type ~= 'void' then
output:write('\n ret = ' .. string.upper(real_type(fn.return_type)) .. '_OBJ(rv);')
end
output:write('\n\ncleanup:')
@@ -653,11 +647,11 @@ local function include_headers(output_handle, headers_to_include)
end
end
-local function write_shifted_output(_, str)
+local function write_shifted_output(str, ...)
str = str:gsub('\n ', '\n')
str = str:gsub('^ ', '')
str = str:gsub(' +$', '')
- output:write(str)
+ output:write(string.format(str, ...))
end
-- start building lua output
@@ -688,9 +682,7 @@ local lua_c_functions = {}
local function process_function(fn)
local lua_c_function_name = ('nlua_api_%s'):format(fn.name)
write_shifted_output(
- output,
- string.format(
- [[
+ [[
static int %s(lua_State *lstate)
{
@@ -701,11 +693,10 @@ local function process_function(fn)
goto exit_0;
}
]],
- lua_c_function_name,
- #fn.parameters,
- #fn.parameters,
- (#fn.parameters == 1) and '' or 's'
- )
+ lua_c_function_name,
+ #fn.parameters,
+ #fn.parameters,
+ (#fn.parameters == 1) and '' or 's'
)
lua_c_functions[#lua_c_functions + 1] = {
binding = lua_c_function_name,
@@ -714,38 +705,29 @@ local function process_function(fn)
if not fn.fast then
write_shifted_output(
- output,
- string.format(
- [[
+ [[
if (!nlua_is_deferred_safe()) {
return luaL_error(lstate, e_luv_api_disabled, "%s");
}
]],
- fn.name
- )
+ fn.name
)
end
if fn.textlock then
- write_shifted_output(
- output,
- [[
+ write_shifted_output([[
if (text_locked()) {
- api_set_error(&err, kErrorTypeException, "%s", get_text_locked_msg());
+ api_set_error(&err, kErrorTypeException, "%%s", get_text_locked_msg());
goto exit_0;
}
- ]]
- )
+ ]])
elseif fn.textlock_allow_cmdwin then
- write_shifted_output(
- output,
- [[
+ write_shifted_output([[
if (textlock != 0 || expr_map_locked()) {
- api_set_error(&err, kErrorTypeException, "%s", e_textlock);
+ api_set_error(&err, kErrorTypeException, "%%s", e_textlock);
goto exit_0;
}
- ]]
- )
+ ]])
end
local cparams = ''
@@ -763,44 +745,37 @@ local function process_function(fn)
local seterr = ''
if string.match(param_type, '^KeyDict_') then
write_shifted_output(
- output,
- string.format(
- [[
- %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &err);]],
- param_type,
- cparam,
- cparam,
- param_type
- )
+ [[
+ %s %s = { 0 };
+ nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &err);
+ ]],
+ param_type,
+ cparam,
+ cparam,
+ param_type
)
cparam = '&' .. cparam
errshift = 1 -- free incomplete dict on error
else
write_shifted_output(
- output,
- string.format(
- [[
- const %s %s = nlua_pop_%s(lstate, %s&err);]],
- param[1],
- cparam,
- param_type,
- extra
- )
+ [[
+ const %s %s = nlua_pop_%s(lstate, %s&err);]],
+ param[1],
+ cparam,
+ param_type,
+ extra
)
- seterr = [[
- err_param = "]] .. param[2] .. [[";]]
+ seterr = '\n err_param = "' .. param[2] .. '";'
end
- write_shifted_output(
- output,
- string.format([[
+ write_shifted_output([[
if (ERROR_SET(&err)) {]] .. seterr .. [[
+
goto exit_%u;
}
]], #fn.parameters - j + errshift)
- )
free_code[#free_code + 1] = ('api_free_%s(%s);'):format(lc_param_type, cparam)
cparams = cparam .. ', ' .. cparams
end
@@ -809,12 +784,7 @@ local function process_function(fn)
end
if fn.arena_return then
cparams = cparams .. '&arena, '
- write_shifted_output(
- output,
- [[
- Arena arena = ARENA_EMPTY;
- ]]
- )
+ write_shifted_output(' Arena arena = ARENA_EMPTY;\n')
end
if fn.has_lua_imp then
@@ -831,27 +801,27 @@ local function process_function(fn)
local rev_i = #free_code - i + 1
local code = free_code[rev_i]
if i == 1 and not string.match(real_type(fn.parameters[1][1]), '^KeyDict_') then
- free_at_exit_code = free_at_exit_code .. ('\n %s'):format(code)
+ free_at_exit_code = free_at_exit_code .. ('\n %s'):format(code)
else
- free_at_exit_code = free_at_exit_code .. ('\n exit_%u:\n %s'):format(rev_i, code)
+ free_at_exit_code = free_at_exit_code .. ('\nexit_%u:\n %s'):format(rev_i, code)
end
end
local err_throw_code = [[
- exit_0:
- if (ERROR_SET(&err)) {
- luaL_where(lstate, 1);
- if (err_param) {
- lua_pushstring(lstate, "Invalid '");
- lua_pushstring(lstate, err_param);
- lua_pushstring(lstate, "': ");
- }
- lua_pushstring(lstate, err.msg);
- api_clear_error(&err);
- lua_concat(lstate, err_param ? 5 : 2);
- return lua_error(lstate);
+exit_0:
+ if (ERROR_SET(&err)) {
+ luaL_where(lstate, 1);
+ if (err_param) {
+ lua_pushstring(lstate, "Invalid '");
+ lua_pushstring(lstate, err_param);
+ lua_pushstring(lstate, "': ");
}
- ]]
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ lua_concat(lstate, err_param ? 5 : 2);
+ return lua_error(lstate);
+ }
+]]
local return_type
if fn.return_type ~= 'void' then
if fn.return_type:match('^ArrayOf') then
@@ -861,86 +831,62 @@ local function process_function(fn)
end
local free_retval
if fn.arena_return then
- free_retval = 'arena_mem_free(arena_finish(&arena));'
+ free_retval = ' arena_mem_free(arena_finish(&arena));'
else
- free_retval = 'api_free_' .. return_type:lower() .. '(ret);'
+ free_retval = ' api_free_' .. return_type:lower() .. '(ret);'
end
- write_shifted_output(
- output,
- string.format(
- [[
- const %s ret = %s(%s);
- ]],
- fn.return_type,
- fn.name,
- cparams
- )
- )
+ write_shifted_output(' %s ret = %s(%s);\n', fn.return_type, fn.name, cparams)
+ local ret_type = real_type(fn.return_type)
if fn.has_lua_imp then
-- only push onto the Lua stack if we haven't already
- write_shifted_output(
- output,
- string.format(
- [[
+ write_shifted_output(string.format(
+ [[
if (lua_gettop(lstate) == 0) {
nlua_push_%s(lstate, ret, true);
}
]],
- return_type
- )
+ return_type
+ ))
+ elseif string.match(ret_type, '^KeyDict_') then
+ write_shifted_output(
+ ' nlua_push_keydict(lstate, &ret, %s_table);\n',
+ string.sub(ret_type, 9)
)
else
local special = (fn.since ~= nil and fn.since < 11)
- write_shifted_output(
- output,
- string.format(
- [[
- nlua_push_%s(lstate, ret, %s);
- ]],
- return_type,
- tostring(special)
- )
- )
+ write_shifted_output(' nlua_push_%s(lstate, ret, %s);\n', return_type, tostring(special))
end
write_shifted_output(
- output,
- string.format(
- [[
+
+ [[
%s
%s
%s
return 1;
]],
- free_retval,
- free_at_exit_code,
- err_throw_code
- )
+ free_retval,
+ free_at_exit_code,
+ err_throw_code
)
else
write_shifted_output(
- output,
- string.format(
- [[
+ [[
%s(%s);
%s
%s
return 0;
]],
- fn.name,
- cparams,
- free_at_exit_code,
- err_throw_code
- )
+ fn.name,
+ cparams,
+ free_at_exit_code,
+ err_throw_code
)
end
- write_shifted_output(
- output,
- [[
+ write_shifted_output([[
}
- ]]
- )
+ ]])
end
for _, fn in ipairs(functions) do
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index a06e9fe542..22f7daa823 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -978,7 +978,7 @@ EXTERN const char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"
EXTERN const char line_msg[] INIT(= N_(" line "));
-EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing
+EXTERN FILE *time_fd INIT(= NULL); // Where to write --startuptime report.
// Some compilers warn for not using a return value, but in some situations we
// can't do anything useful with the value. Assign to this variable to avoid
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index e0caa3841b..b6a8a8bd9f 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -912,7 +912,7 @@ void win_grid_alloc(win_T *wp)
grid_alloc(grid_allocated, total_rows, total_cols,
wp->w_grid_alloc.valid, false);
grid_allocated->valid = true;
- if (wp->w_floating && wp->w_float_config.border) {
+ if (wp->w_floating && wp->w_config.border) {
wp->w_redr_border = true;
}
was_resized = true;
diff --git a/src/nvim/help.c b/src/nvim/help.c
index 879e2801a7..779772cedf 100644
--- a/src/nvim/help.c
+++ b/src/nvim/help.c
@@ -803,7 +803,7 @@ void get_local_additions(void)
linenr_T appended = lnum - lnum_start;
if (appended) {
mark_adjust(lnum_start + 1, (linenr_T)MAXLNUM, appended, 0, kExtmarkUndo);
- buf_redraw_changed_lines_later(curbuf, lnum_start + 1, lnum_start + 1, appended);
+ changed_lines_redraw_buf(curbuf, lnum_start + 1, lnum_start + 1, appended);
}
break;
}
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 17d743784f..e74ae817d0 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -116,12 +116,13 @@ retry: {}
// new attr id, send event to remote ui:s
int id = (int)k;
- Array inspect = hl_inspect(id);
+ Arena arena = ARENA_EMPTY;
+ Array inspect = hl_inspect(id, &arena);
// Note: internally we don't distinguish between cterm and rgb attributes,
// remote_ui_hl_attr_define will however.
ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect);
- api_free_array(inspect);
+ arena_mem_free(arena_finish(&arena));
return id;
}
@@ -129,13 +130,14 @@ retry: {}
void ui_send_all_hls(UI *ui)
{
for (size_t i = 1; i < set_size(&attr_entries); i++) {
- Array inspect = hl_inspect((int)i);
+ Arena arena = ARENA_EMPTY;
+ Array inspect = hl_inspect((int)i, &arena);
HlAttrs attr = attr_entry(i).attr;
remote_ui_hl_attr_define(ui, (Integer)i, attr, attr, inspect);
- api_free_array(inspect);
+ arena_mem_free(arena_finish(&arena));
}
for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
- remote_ui_hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]),
+ remote_ui_hl_group_set(ui, cstr_as_string(hlf_names[hlf]),
highlight_attr[hlf]);
}
}
@@ -204,7 +206,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
if (!valid_item && p->hl_def != LUA_NOREF && !recursive) {
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ((Integer)ns_id));
- ADD_C(args, CSTR_TO_OBJ(syn_id2name(hl_id)));
+ ADD_C(args, CSTR_AS_OBJ(syn_id2name(hl_id)));
ADD_C(args, BOOLEAN_OBJ(link));
// TODO(bfredl): preload the "global" attr dict?
@@ -367,7 +369,7 @@ void update_window_hl(win_T *wp, bool invalid)
// wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named.
// determine window specific background set in 'winhighlight'
- bool float_win = wp->w_floating && !wp->w_float_config.external;
+ bool float_win = wp->w_floating && !wp->w_config.external;
if (float_win && hl_def[HLF_NFLOAT] != 0) {
wp->w_hl_attr_normal = hl_def[HLF_NFLOAT];
} else if (hl_def[HLF_COUNT] > 0) {
@@ -380,19 +382,19 @@ void update_window_hl(win_T *wp, bool invalid)
wp->w_hl_attr_normal = hl_apply_winblend(wp, wp->w_hl_attr_normal);
}
- wp->w_float_config.shadow = false;
- if (wp->w_floating && wp->w_float_config.border) {
+ wp->w_config.shadow = false;
+ if (wp->w_floating && wp->w_config.border) {
for (int i = 0; i < 8; i++) {
int attr = hl_def[HLF_BORDER];
- if (wp->w_float_config.border_hl_ids[i]) {
+ if (wp->w_config.border_hl_ids[i]) {
attr = hl_get_ui_attr(ns_id, HLF_BORDER,
- wp->w_float_config.border_hl_ids[i], false);
+ wp->w_config.border_hl_ids[i], false);
}
attr = hl_apply_winblend(wp, attr);
if (syn_attr2entry(attr).hl_blend > 0) {
- wp->w_float_config.shadow = true;
+ wp->w_config.shadow = true;
}
- wp->w_float_config.border_attr[i] = attr;
+ wp->w_config.border_attr[i] = attr;
}
}
@@ -1176,17 +1178,30 @@ int object_to_color(Object val, char *key, bool rgb, Error *err)
}
}
-Array hl_inspect(int attr)
+Array hl_inspect(int attr, Arena *arena)
{
- // TODO(bfredl): use arena allocation
- Array ret = ARRAY_DICT_INIT;
- if (hlstate_active) {
- hl_inspect_impl(&ret, attr);
+ if (!hlstate_active) {
+ return (Array)ARRAY_DICT_INIT;
}
+ Array ret = arena_array(arena, hl_inspect_size(attr));
+ hl_inspect_impl(&ret, attr, arena);
return ret;
}
-static void hl_inspect_impl(Array *arr, int attr)
+static size_t hl_inspect_size(int attr)
+{
+ if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
+ return 0;
+ }
+
+ HlEntry e = attr_entry(attr);
+ if (e.kind == kHlCombine || e.kind == kHlBlend || e.kind == kHlBlendThrough) {
+ return hl_inspect_size(e.id1) + hl_inspect_size(e.id2);
+ }
+ return 1;
+}
+
+static void hl_inspect_impl(Array *arr, int attr, Arena *arena)
{
Dictionary item = ARRAY_DICT_INIT;
if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
@@ -1196,35 +1211,36 @@ static void hl_inspect_impl(Array *arr, int attr)
HlEntry e = attr_entry(attr);
switch (e.kind) {
case kHlSyntax:
- PUT(item, "kind", CSTR_TO_OBJ("syntax"));
- PUT(item, "hi_name",
- CSTR_TO_OBJ(syn_id2name(e.id1)));
+ item = arena_dict(arena, 3);
+ PUT_C(item, "kind", CSTR_AS_OBJ("syntax"));
+ PUT_C(item, "hi_name", CSTR_AS_OBJ(syn_id2name(e.id1)));
break;
case kHlUI:
- PUT(item, "kind", CSTR_TO_OBJ("ui"));
+ item = arena_dict(arena, 4);
+ PUT_C(item, "kind", CSTR_AS_OBJ("ui"));
const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1];
- PUT(item, "ui_name", CSTR_TO_OBJ(ui_name));
- PUT(item, "hi_name",
- CSTR_TO_OBJ(syn_id2name(e.id2)));
+ PUT_C(item, "ui_name", CSTR_AS_OBJ(ui_name));
+ PUT_C(item, "hi_name", CSTR_AS_OBJ(syn_id2name(e.id2)));
break;
case kHlTerminal:
- PUT(item, "kind", CSTR_TO_OBJ("term"));
+ item = arena_dict(arena, 2);
+ PUT_C(item, "kind", CSTR_AS_OBJ("term"));
break;
case kHlCombine:
case kHlBlend:
case kHlBlendThrough:
// attribute combination is associative, so flatten to an array
- hl_inspect_impl(arr, e.id1);
- hl_inspect_impl(arr, e.id2);
+ hl_inspect_impl(arr, e.id1, arena);
+ hl_inspect_impl(arr, e.id2, arena);
return;
case kHlUnknown:
case kHlInvalid:
return;
}
- PUT(item, "id", INTEGER_OBJ(attr));
- ADD(*arr, DICTIONARY_OBJ(item));
+ PUT_C(item, "id", INTEGER_OBJ(attr));
+ ADD_C(*arr, DICTIONARY_OBJ(item));
}
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 67b33e6ef7..75c23c5bc4 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1672,10 +1672,7 @@ Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Err
return rv;
cleanup:
- api_free_integer(id);
- api_free_boolean(link);
- Dictionary empty = ARRAY_DICT_INIT;
- return empty;
+ return (Dictionary)ARRAY_DICT_INIT;
}
/// Outputs a highlight when doing ":hi MyHighlight"
@@ -2241,7 +2238,7 @@ void highlight_changed(void)
HlAttrs attrs = syn_attr2entry(highlight_attr[hlf]);
msg_grid.blending = attrs.hl_blend > -1;
}
- ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]),
+ ui_call_hl_group_set(cstr_as_string(hlf_names[hlf]),
highlight_attr[hlf]);
highlight_attr_last[hlf] = highlight_attr[hlf];
}
diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h
index 8a4eb83827..47d58d20f2 100644
--- a/src/nvim/highlight_group.h
+++ b/src/nvim/highlight_group.h
@@ -12,7 +12,7 @@ typedef struct {
char *name;
RgbValue color;
} color_name_table_T;
-extern color_name_table_T color_name_table[];
+extern color_name_table_T color_name_table[700];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight_group.h.generated.h"
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 54aab72f81..279ccd7f9c 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -118,6 +118,7 @@ bool tabstop_set(char *var, colnr_T **array)
/// If "vts" is set then the tab widths are taken from that array,
/// otherwise the value of ts is used.
int tabstop_padding(colnr_T col, OptInt ts_arg, const colnr_T *vts)
+ FUNC_ATTR_PURE
{
OptInt ts = ts_arg == 0 ? 8 : ts_arg;
colnr_T tabcol = 0;
@@ -349,83 +350,96 @@ int get_sts_value(void)
return result;
}
-// Count the size (in window cells) of the indent in the current line.
+/// Count the size (in window cells) of the indent in the current line.
int get_indent(void)
{
- return get_indent_str_vtab(get_cursor_line_ptr(),
- curbuf->b_p_ts,
- curbuf->b_p_vts_array,
- false);
+ return indent_size_ts(get_cursor_line_ptr(), curbuf->b_p_ts, curbuf->b_p_vts_array);
}
-// Count the size (in window cells) of the indent in line "lnum".
+/// Count the size (in window cells) of the indent in line "lnum".
int get_indent_lnum(linenr_T lnum)
{
- return get_indent_str_vtab(ml_get(lnum),
- curbuf->b_p_ts,
- curbuf->b_p_vts_array,
- false);
+ return indent_size_ts(ml_get(lnum), curbuf->b_p_ts, curbuf->b_p_vts_array);
}
-// Count the size (in window cells) of the indent in line "lnum" of buffer
-// "buf".
+/// Count the size (in window cells) of the indent in line "lnum" of buffer "buf".
int get_indent_buf(buf_T *buf, linenr_T lnum)
{
- return get_indent_str_vtab(ml_get_buf(buf, lnum), buf->b_p_ts, buf->b_p_vts_array, false);
+ return indent_size_ts(ml_get_buf(buf, lnum), buf->b_p_ts, buf->b_p_vts_array);
}
-/// Count the size (in window cells) of the indent in line "ptr", with
-/// 'tabstop' at "ts".
-/// If @param list is true, count only screen size for tabs.
-int get_indent_str(const char *ptr, int ts, bool list)
- FUNC_ATTR_NONNULL_ALL
+/// Compute the size of the indent (in window cells) in line "ptr",
+/// without tabstops (count tab as ^I or <09>).
+int indent_size_no_ts(char const *ptr)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
- int count = 0;
-
- for (; *ptr; ptr++) {
- // Count a tab for what it is worth.
- if (*ptr == TAB) {
- if (!list || curwin->w_p_lcs_chars.tab1) {
- // count a tab for what it is worth
- count += ts - (count % ts);
- } else {
- // In list mode, when tab is not set, count screen char width
- // for Tab, displays: ^I
- count += ptr2cells(ptr);
- }
- } else if (*ptr == ' ') {
- // Count a space for one.
- count++;
+ int tab_size = byte2cells(TAB);
+
+ int vcol = 0;
+ while (true) {
+ char const c = *ptr++;
+ if (c == ' ') {
+ vcol++;
+ } else if (c == TAB) {
+ vcol += tab_size;
} else {
- break;
+ return vcol;
}
}
- return count;
}
-/// Count the size (in window cells) of the indent in line "ptr", using
-/// variable tabstops.
-/// if "list" is true, count only screen size for tabs.
-int get_indent_str_vtab(const char *ptr, OptInt ts, colnr_T *vts, bool list)
+/// Compute the size of the indent (in window cells) in line "ptr",
+/// using tabstops
+int indent_size_ts(char const *ptr, OptInt ts, colnr_T *vts)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_PURE
{
- int count = 0;
+ assert(char2cells(' ') == 1);
+
+ int vcol = 0;
+ int tabstop_width, next_tab_vcol;
+
+ if (vts == NULL || vts[0] < 1) { // tab has fixed width
+ // can ts be 0 ? This is from tabstop_padding().
+ tabstop_width = (int)(ts == 0 ? 8 : ts);
+ next_tab_vcol = tabstop_width;
+ } else { // tab has variable width
+ colnr_T *cur_tabstop = vts + 1;
+ colnr_T *const last_tabstop = vts + vts[0];
+
+ while (cur_tabstop != last_tabstop) {
+ int cur_vcol = vcol;
+ vcol += *cur_tabstop++;
+ assert(cur_vcol < vcol);
+
+ do {
+ char const c = *ptr++;
+ if (c == ' ') {
+ cur_vcol++;
+ } else if (c == TAB) {
+ break;
+ } else {
+ return cur_vcol;
+ }
+ } while (cur_vcol != vcol);
+ }
- for (; *ptr; ptr++) {
- if (*ptr == TAB) { // count a tab for what it is worth
- if (!list || curwin->w_p_lcs_chars.tab1) {
- count += tabstop_padding(count, ts, vts);
- } else {
- // In list mode, when tab is not set, count screen char width
- // for Tab, displays: ^I
- count += ptr2cells(ptr);
- }
- } else if (*ptr == ' ') {
- count++; // count a space for one
+ tabstop_width = *last_tabstop;
+ next_tab_vcol = vcol + tabstop_width;
+ }
+
+ assert(tabstop_width != 0);
+ while (true) {
+ char const c = *ptr++;
+ if (c == ' ') {
+ vcol++;
+ next_tab_vcol += (vcol == next_tab_vcol) ? tabstop_width : 0;
+ } else if (c == TAB) {
+ vcol = next_tab_vcol;
+ next_tab_vcol += tabstop_width;
} else {
- break;
+ return vcol;
}
}
- return count;
}
/// Set the indent of the current line.
@@ -792,49 +806,63 @@ int get_breakindent_win(win_T *wp, char *line)
{
static int prev_indent = 0; // cached indent value
static OptInt prev_ts = 0; // cached tabstop value
+ static colnr_T *prev_vts = NULL; // cached vartabs values
static int prev_fnum = 0; // cached buffer number
static char *prev_line = NULL; // cached copy of "line"
static varnumber_T prev_tick = 0; // changedtick of cached value
- static colnr_T *prev_vts = NULL; // cached vartabs values
- static int prev_list = 0; // cached list value
+ static int prev_list = 0; // cached list indent
static int prev_listopt = 0; // cached w_p_briopt_list value
+ static bool prev_no_ts = false; // cached no_ts value
+ static unsigned prev_dy_uhex = 0; // cached 'display' "uhex" value
static char *prev_flp = NULL; // cached formatlistpat value
int bri = 0;
// window width minus window margin space, i.e. what rests for text
const int eff_wwidth = wp->w_width_inner -
((wp->w_p_nu || wp->w_p_rnu)
- && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0);
-
- // used cached indent, unless
- // - buffer changed
- // - 'tabstop' changed
- // - buffer was changed
- // - 'briopt_list changed' changed or
- // - 'formatlistpattern' changed
- // - line changed
- // - 'vartabs' changed
+ && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL)
+ ? number_width(wp) + 1 : 0);
+
+ // In list mode, if 'listchars' "tab" isn't set, a TAB is displayed as ^I.
+ const bool no_ts = wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL;
+
+ // Used cached indent, unless
+ // - buffer changed, or
+ // - 'tabstop' changed, or
+ // - 'vartabstop' changed, or
+ // - buffer was changed, or
+ // - 'breakindentopt' "list" changed, or
+ // - 'list' or 'listchars' "tab" changed, or
+ // - 'display' "uhex" flag changed, or
+ // - 'formatlistpat' changed, or
+ // - line changed.
if (prev_fnum != wp->w_buffer->b_fnum
|| prev_ts != wp->w_buffer->b_p_ts
+ || prev_vts != wp->w_buffer->b_p_vts_array
|| prev_tick != buf_get_changedtick(wp->w_buffer)
|| prev_listopt != wp->w_briopt_list
+ || prev_no_ts != no_ts
+ || prev_dy_uhex != (dy_flags & DY_UHEX)
|| prev_flp == NULL
|| strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0
- || prev_line == NULL || strcmp(prev_line, line) != 0
- || prev_vts != wp->w_buffer->b_p_vts_array) {
+ || prev_line == NULL || strcmp(prev_line, line) != 0) {
prev_fnum = wp->w_buffer->b_fnum;
xfree(prev_line);
prev_line = xstrdup(line);
prev_ts = wp->w_buffer->b_p_ts;
- prev_tick = buf_get_changedtick(wp->w_buffer);
prev_vts = wp->w_buffer->b_p_vts_array;
if (wp->w_briopt_vcol == 0) {
- prev_indent = get_indent_str_vtab(line,
- wp->w_buffer->b_p_ts,
- wp->w_buffer->b_p_vts_array,
- wp->w_p_list);
+ if (no_ts) {
+ prev_indent = indent_size_no_ts(line);
+ } else {
+ prev_indent = indent_size_ts(line, wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array);
+ }
}
+ prev_tick = buf_get_changedtick(wp->w_buffer);
prev_listopt = wp->w_briopt_list;
prev_list = 0;
+ prev_no_ts = no_ts;
+ prev_dy_uhex = (dy_flags & DY_UHEX);
xfree(prev_flp);
prev_flp = xstrdup(get_flp_value(wp->w_buffer));
// add additional indent for numbered lists
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 7e6b6d4bd0..7667c49452 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -35,7 +35,7 @@
/// @param[in] str Prompt: question to ask user. Is always followed by
/// " (y/n)?".
/// @param[in] direct Determines what function to use to get user input. If
-/// true then ui_inchar() will be used, otherwise vgetc().
+/// true then os_inchar() will be used, otherwise vgetc().
/// I.e. when direct is true then characters are obtained
/// directly from the user without buffers involved.
///
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 2f54250392..9d3b400496 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1279,6 +1279,9 @@ void ins_compl_show_pum(void)
}
if (compl_match_array == NULL) {
+ if (compl_started && has_event(EVENT_COMPLETECHANGED)) {
+ trigger_complete_changed_event(cur);
+ }
return;
}
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 91f2db3ac9..3cf5b0e94f 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1360,3 +1360,46 @@ void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_
lua_pop(L, 1);
// []
}
+
+void nlua_push_keydict(lua_State *L, void *value, KeySetLink *table)
+{
+ lua_createtable(L, 0, 0);
+ for (size_t i = 0; table[i].str; i++) {
+ KeySetLink *field = &table[i];
+ bool is_set = true;
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)value;
+ is_set = ks->is_set_ & (1ULL << field->opt_index);
+ }
+
+ if (!is_set) {
+ continue;
+ }
+
+ char *mem = ((char *)value + field->ptr_off);
+
+ lua_pushstring(L, field->str);
+ if (field->type == kObjectTypeNil) {
+ nlua_push_Object(L, *(Object *)mem, false);
+ } else if (field->type == kObjectTypeInteger || field->type == kObjectTypeBuffer
+ || field->type == kObjectTypeWindow || field->type == kObjectTypeTabpage) {
+ lua_pushinteger(L, *(Integer *)mem);
+ } else if (field->type == kObjectTypeFloat) {
+ lua_pushnumber(L, *(Float *)mem);
+ } else if (field->type == kObjectTypeBoolean) {
+ lua_pushboolean(L, *(Boolean *)mem);
+ } else if (field->type == kObjectTypeString) {
+ nlua_push_String(L, *(String *)mem, false);
+ } else if (field->type == kObjectTypeArray) {
+ nlua_push_Array(L, *(Array *)mem, false);
+ } else if (field->type == kObjectTypeDictionary) {
+ nlua_push_Dictionary(L, *(Dictionary *)mem, false);
+ } else if (field->type == kObjectTypeLuaRef) {
+ nlua_pushref(L, *(LuaRef *)mem);
+ } else {
+ abort();
+ }
+
+ lua_rawset(L, -3);
+ }
+}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index de17aabca2..c1816a8860 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -56,6 +56,7 @@ typedef struct {
# include "lua/treesitter.c.generated.h"
#endif
+// TSParser
static struct luaL_Reg parser_meta[] = {
{ "__gc", parser_gc },
{ "__tostring", parser_tostring },
@@ -70,6 +71,7 @@ static struct luaL_Reg parser_meta[] = {
{ NULL, NULL }
};
+// TSTree
static struct luaL_Reg tree_meta[] = {
{ "__gc", tree_gc },
{ "__tostring", tree_tostring },
@@ -80,6 +82,7 @@ static struct luaL_Reg tree_meta[] = {
{ NULL, NULL }
};
+// TSNode
static struct luaL_Reg node_meta[] = {
{ "__tostring", node_tostring },
{ "__eq", node_eq },
@@ -119,6 +122,7 @@ static struct luaL_Reg node_meta[] = {
{ NULL, NULL }
};
+// TSQuery
static struct luaL_Reg query_meta[] = {
{ "__gc", query_gc },
{ "__tostring", query_tostring },
@@ -1649,8 +1653,10 @@ static int query_inspect(lua_State *L)
return 0;
}
- uint32_t n_pat = ts_query_pattern_count(query);
+ // TSQueryInfo
lua_createtable(L, 0, 2); // [retval]
+
+ uint32_t n_pat = ts_query_pattern_count(query);
lua_createtable(L, (int)n_pat, 1); // [retval, patterns]
for (size_t i = 0; i < n_pat; i++) {
uint32_t len;
diff --git a/src/nvim/main.c b/src/nvim/main.c
index f858313682..f2893dc9e3 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -399,6 +399,7 @@ int main(int argc, char **argv)
}
if (ui_client_channel_id) {
+ time_finish();
ui_client_run(remote_ui); // NORETURN
}
assert(!ui_client_channel_id && !use_builtin_ui);
@@ -695,6 +696,9 @@ void getout(int exitval)
assert(!ui_client_channel_id);
exiting = true;
+ // make sure startuptimes have been flushed
+ time_finish();
+
// On error during Ex mode, exit with a non-zero code.
// POSIX requires this, although it's not 100% clear from the standard.
if (exmode_active) {
@@ -1495,9 +1499,16 @@ static void init_params(mparm_T *paramp, int argc, char **argv)
/// Initialize global startuptime file if "--startuptime" passed as an argument.
static void init_startuptime(mparm_T *paramp)
{
+ bool is_embed = false;
+ for (int i = 1; i < paramp->argc - 1; i++) {
+ if (STRICMP(paramp->argv[i], "--embed") == 0) {
+ is_embed = true;
+ break;
+ }
+ }
for (int i = 1; i < paramp->argc - 1; i++) {
if (STRICMP(paramp->argv[i], "--startuptime") == 0) {
- time_fd = fopen(paramp->argv[i + 1], "a");
+ time_init(paramp->argv[i + 1], is_embed ? "Embedded" : "Primary/TUI");
time_start("--- NVIM STARTING ---");
break;
}
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index b1f7b59b21..fd353d8a67 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -445,24 +445,26 @@ int mb_get_class_tab(const char *p, const uint64_t *const chartab)
static bool intable(const struct interval *table, size_t n_items, int c)
FUNC_ATTR_PURE
{
+ assert(n_items > 0);
// first quick check for Latin1 etc. characters
if (c < table[0].first) {
return false;
}
+ assert(n_items <= SIZE_MAX / 2);
// binary search in table
- int bot = 0;
- int top = (int)(n_items - 1);
- while (top >= bot) {
- int mid = (bot + top) / 2;
+ size_t bot = 0;
+ size_t top = n_items;
+ do {
+ size_t mid = (bot + top) >> 1;
if (table[mid].last < c) {
bot = mid + 1;
} else if (table[mid].first > c) {
- top = mid - 1;
+ top = mid;
} else {
return true;
}
- }
+ } while (top > bot);
return false;
}
@@ -476,32 +478,28 @@ static bool intable(const struct interval *table, size_t n_items, int c)
/// gen_unicode_tables.lua, which must be manually invoked as needed.
int utf_char2cells(int c)
{
- // Use the value from setcellwidths() at 0x80 and higher, unless the
- // character is not printable.
- if (c >= 0x80 && vim_isprintc(c)) {
- int n = cw_value(c);
- if (n != 0) {
- return n;
- }
+ if (c < 0x80) {
+ return 1;
}
- if (c >= 0x100) {
- if (!utf_printable(c)) {
- return 6; // unprintable, displays <xxxx>
- }
- if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) {
- return 2;
- }
- if (p_emoji && intable(emoji_wide, ARRAY_SIZE(emoji_wide), c)) {
- return 2;
- }
- } else if (c >= 0x80 && !vim_isprintc(c)) {
- // Characters below 0x100 are influenced by 'isprint' option.
- return 4; // unprintable, displays <xx>
+ if (!vim_isprintc(c)) {
+ assert(c <= 0xFFFF);
+ // unprintable is displayed either as <xx> or <xxxx>
+ return c > 0xFF ? 6 : 4;
+ }
+
+ int n = cw_value(c);
+ if (n != 0) {
+ return n;
}
- if (c >= 0x80 && *p_ambw == 'd'
- && intable(ambiguous, ARRAY_SIZE(ambiguous), c)) {
+ if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) {
+ return 2;
+ }
+ if (p_emoji && intable(emoji_wide, ARRAY_SIZE(emoji_wide), c)) {
+ return 2;
+ }
+ if (*p_ambw == 'd' && intable(ambiguous, ARRAY_SIZE(ambiguous), c)) {
return 2;
}
@@ -534,7 +532,7 @@ int utf_ptr2cells(const char *p)
/// @param[in] p String to convert.
/// @param[in] len Length of the character in bytes, 0 or 1 if illegal.
///
-/// @return Unicode codepoint. A negative value When the sequence is illegal.
+/// @return Unicode codepoint. A negative value when the sequence is illegal.
int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -1092,9 +1090,52 @@ bool utf_iscomposing(int c)
return intable(combining, ARRAY_SIZE(combining), c);
}
+#ifdef __SSE2__
+
+# include <emmintrin.h>
+
// Return true for characters that can be displayed in a normal way.
// Only for characters of 0x100 and above!
bool utf_printable(int c)
+ FUNC_ATTR_CONST
+{
+ if (c < 0x180B || c > 0xFFFF) {
+ return c != 0x70F;
+ }
+
+# define L(v) ((int16_t)((v) - 1)) // lower bound (exclusive)
+# define H(v) ((int16_t)(v)) // upper bound (inclusive)
+
+ // Boundaries of unprintable characters.
+ // Some values are negative when converted to int16_t.
+ // Ranges must not wrap around when converted to int16_t.
+ __m128i const lo = _mm_setr_epi16(L(0x180b), L(0x200b), L(0x202a), L(0x2060),
+ L(0xd800), L(0xfeff), L(0xfff9), L(0xfffe));
+
+ __m128i const hi = _mm_setr_epi16(H(0x180e), H(0x200f), H(0x202e), H(0x206f),
+ H(0xdfff), H(0xfeff), H(0xfffb), H(0xffff));
+
+# undef L
+# undef H
+
+ __m128i value = _mm_set1_epi16((int16_t)c);
+
+ // Using _mm_cmplt_epi16() is less optimal, since it would require
+ // swapping operands (sse2 only has cmpgt instruction),
+ // and only the second operand can be a memory location.
+
+ // Character is printable when it is above/below both bounds of each range
+ // (corresponding bits in both masks are equal).
+ return _mm_movemask_epi8(_mm_cmpgt_epi16(value, lo))
+ == _mm_movemask_epi8(_mm_cmpgt_epi16(value, hi));
+}
+
+#else
+
+// Return true for characters that can be displayed in a normal way.
+// Only for characters of 0x100 and above!
+bool utf_printable(int c)
+ FUNC_ATTR_PURE
{
// Sorted list of non-overlapping intervals.
// 0xd800-0xdfff is reserved for UTF-16, actually illegal.
@@ -1107,6 +1148,8 @@ bool utf_printable(int c)
return !intable(nonprint, ARRAY_SIZE(nonprint), c);
}
+#endif
+
// Get class of a Unicode character.
// 0: white space
// 1: punctuation
@@ -2749,8 +2792,10 @@ static int tv_nr_compare(const void *a1, const void *a2)
{
const listitem_T *const li1 = tv_list_first(*(const list_T **)a1);
const listitem_T *const li2 = tv_list_first(*(const list_T **)a2);
+ const varnumber_T n1 = TV_LIST_ITEM_TV(li1)->vval.v_number;
+ const varnumber_T n2 = TV_LIST_ITEM_TV(li2)->vval.v_number;
- return (int)(TV_LIST_ITEM_TV(li1)->vval.v_number - TV_LIST_ITEM_TV(li2)->vval.v_number);
+ return n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
}
/// "setcellwidths()" function
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index 2603f0df55..2f17c706c1 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -61,7 +61,7 @@ static inline CharInfo utf_ptr2CharInfo(char const *p_in)
/// @param[in] p_in String to convert.
///
/// @return information abouth the character. When the sequence is illegal,
-/// 'value' is negative, 'len' is 1.
+/// "value" is negative, "len" is 1.
static inline CharInfo utf_ptr2CharInfo(char const *const p_in)
{
uint8_t const *const p = (uint8_t const *)p_in;
@@ -82,11 +82,9 @@ static inline StrCharInfo utfc_next(StrCharInfo cur)
REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE;
/// Return information about the next character.
-/// Composing and combining characters are
-/// considered a part of the current character.
+/// Composing and combining characters are considered a part of the current character.
///
-/// @param[in] cur Pointer to the current character. Must not point to NUL
-/// @param[in] cur_char Decoded charater at 'cur'.
+/// @param[in] cur Information about the current character in the string.
static inline StrCharInfo utfc_next(StrCharInfo cur)
{
int32_t prev_code = cur.chr.value;
diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h
index 97aa1a638b..058ec353e5 100644
--- a/src/nvim/mbyte_defs.h
+++ b/src/nvim/mbyte_defs.h
@@ -58,11 +58,11 @@ typedef struct {
} vimconv_T;
typedef struct {
- int32_t value; ///< code point
- int len; ///< length in bytes
+ int32_t value; ///< Code point.
+ int len; ///< Length in bytes.
} CharInfo;
typedef struct {
- char *ptr; ///< pointer to the first byte of the character
- CharInfo chr; ///< the character
+ char *ptr; ///< Pointer to the first byte of the character.
+ CharInfo chr; ///< Information about the character.
} StrCharInfo;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 9dd83d6c02..d879fa1423 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -129,7 +129,7 @@ bool keep_msg_more = false; // keep_msg was set by msgmore()
// Set: When the ruler or typeahead display is overwritten,
// scrolling the screen for some message.
// keep_msg Message to be displayed after redrawing the screen, in
-// main_loop().
+// Normal mode main loop.
// This is an allocated string or NULL when not used.
// Extended msg state, currently used for external UIs with ext_messages
@@ -210,7 +210,11 @@ void msg_grid_validate(void)
msg_grid_adj.target = &default_grid;
redraw_cmdline = true;
} else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != max_rows) {
+ int diff = msg_grid_pos - max_rows;
msg_grid_set_pos(max_rows, false);
+ if (diff > 0) {
+ grid_clear(&msg_grid_adj, Rows - diff, Rows, 0, Columns, HL_ATTR(HLF_MSG));
+ }
}
msg_grid_adj.cols = Columns;
@@ -3017,7 +3021,7 @@ void msg_ext_ui_flush(void)
msg_ext_emit_chunk();
if (msg_ext_chunks.size > 0) {
- ui_call_msg_show(cstr_as_string((char *)msg_ext_kind),
+ ui_call_msg_show(cstr_as_string(msg_ext_kind),
msg_ext_chunks, msg_ext_overwrite);
if (!msg_ext_overwrite) {
msg_ext_visible++;
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index c0a928416d..f4d1be5b2f 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -1571,9 +1571,6 @@ void nv_mousescroll(cmdarg_T *cap)
// Call the common mouse scroll function shared with other modes.
do_mousescroll(cap);
- if (curwin != old_curwin && curwin->w_p_cul) {
- redraw_for_cursorline(curwin);
- }
curwin->w_redr_status = true;
curwin = old_curwin;
curbuf = curwin->w_buffer;
@@ -1725,7 +1722,7 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
} else if (*gridp > 1) {
win_T *wp = get_win_by_grid_handle(*gridp);
if (wp && wp->w_grid_alloc.chars
- && !(wp->w_floating && !wp->w_float_config.focusable)) {
+ && !(wp->w_floating && !wp->w_config.focusable)) {
*rowp = MIN(*rowp - wp->w_grid.row_offset, wp->w_grid.rows - 1);
*colp = MIN(*colp - wp->w_grid.col_offset, wp->w_grid.cols - 1);
return wp;
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 1402b03f6d..551aa1bd4d 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -135,6 +135,18 @@ static void comp_botline(win_T *wp)
win_check_anchored_floats(wp);
}
+/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
+/// Also when concealing is on and 'concealcursor' is not active.
+static void redraw_for_cursorline(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
+ && (wp->w_p_rnu || win_cursorline_standout(wp))) {
+ // win_line() will redraw the number column and cursorline only.
+ redraw_later(wp, UPD_VALID);
+ }
+}
+
/// Redraw when w_virtcol changes and
/// - 'cursorcolumn' is set, or
/// - 'cursorlineopt' contains "screenline", or
@@ -583,19 +595,6 @@ void changed_line_abv_curs_win(win_T *wp)
|VALID_CHEIGHT|VALID_TOPLINE);
}
-/// Display of line has changed for "buf", invalidate cursor position and
-/// w_botline.
-void changed_line_display_buf(buf_T *buf)
-{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf) {
- wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
- |VALID_CROW|VALID_CHEIGHT
- |VALID_TOPLINE|VALID_BOTLINE|VALID_BOTLINE_AP);
- }
- }
-}
-
// Make sure the value of curwin->w_botline is valid.
void validate_botline(win_T *wp)
{
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 0178ef622b..36fa7e77fc 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -547,7 +547,7 @@ static void send_error(Channel *chan, MsgpackRpcRequestHandler handler, MessageT
static void send_request(Channel *channel, uint32_t id, const char *name, Array args)
{
- const String method = cstr_as_string((char *)name);
+ const String method = cstr_as_string(name);
channel_write(channel, serialize_request(channel->id,
id,
method,
@@ -558,7 +558,7 @@ static void send_request(Channel *channel, uint32_t id, const char *name, Array
static void send_event(Channel *channel, const char *name, Array args)
{
- const String method = cstr_as_string((char *)name);
+ const String method = cstr_as_string(name);
channel_write(channel, serialize_request(channel->id,
0,
method,
@@ -583,7 +583,7 @@ static void broadcast_event(const char *name, Array args)
goto end;
}
- const String method = cstr_as_string((char *)name);
+ const String method = cstr_as_string(name);
WBuffer *buffer = serialize_request(0,
0,
method,
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 9966e6129c..d69e43e6b3 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -375,7 +375,7 @@ static int nv_compare(const void *s1, const void *s2)
if (c2 < 0) {
c2 = -c2;
}
- return c1 - c2;
+ return c1 == c2 ? 0 : c1 > c2 ? 1 : -1;
}
/// Initialize the nv_cmd_idx[] table.
@@ -1451,9 +1451,7 @@ static int normal_check(VimState *state)
// has been done, close any file for startup messages.
if (time_fd != NULL) {
TIME_MSG("first screen update");
- TIME_MSG("--- NVIM STARTED ---");
- fclose(time_fd);
- time_fd = NULL;
+ time_finish();
}
// After the first screen update may start triggering WinScrolled
// autocmd events. Store all the scroll positions and sizes now.
diff --git a/src/nvim/option.c b/src/nvim/option.c
index db013b460e..42ccc1fbd4 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1790,7 +1790,7 @@ bool valid_name(const char *val, const char *allowed)
void check_blending(win_T *wp)
{
wp->w_grid_alloc.blending =
- wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow);
+ wp->w_p_winbl > 0 || (wp->w_floating && wp->w_config.shadow);
}
/// Handle setting `winhighlight' in window "wp"
@@ -6314,32 +6314,33 @@ int get_sidescrolloff_value(win_T *wp)
return (int)(wp->w_p_siso < 0 ? p_siso : wp->w_p_siso);
}
-Dictionary get_vimoption(String name, int scope, buf_T *buf, win_T *win, Error *err)
+Dictionary get_vimoption(String name, int scope, buf_T *buf, win_T *win, Arena *arena, Error *err)
{
OptIndex opt_idx = find_option_len(name.data, name.size);
VALIDATE_S(opt_idx != kOptInvalid, "option (not found)", name.data, {
return (Dictionary)ARRAY_DICT_INIT;
});
- return vimoption2dict(&options[opt_idx], scope, buf, win);
+ return vimoption2dict(&options[opt_idx], scope, buf, win, arena);
}
-Dictionary get_all_vimoptions(void)
+Dictionary get_all_vimoptions(Arena *arena)
{
- Dictionary retval = ARRAY_DICT_INIT;
+ Dictionary retval = arena_dict(arena, kOptIndexCount);
for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) {
- Dictionary opt_dict = vimoption2dict(&options[opt_idx], OPT_GLOBAL, curbuf, curwin);
- PUT(retval, options[opt_idx].fullname, DICTIONARY_OBJ(opt_dict));
+ Dictionary opt_dict = vimoption2dict(&options[opt_idx], OPT_GLOBAL, curbuf, curwin, arena);
+ PUT_C(retval, options[opt_idx].fullname, DICTIONARY_OBJ(opt_dict));
}
return retval;
}
-static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win)
+static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win,
+ Arena *arena)
{
- Dictionary dict = ARRAY_DICT_INIT;
+ Dictionary dict = arena_dict(arena, 13);
- PUT(dict, "name", CSTR_TO_OBJ(opt->fullname));
- PUT(dict, "shortname", CSTR_TO_OBJ(opt->shortname));
+ PUT_C(dict, "name", CSTR_AS_OBJ(opt->fullname));
+ PUT_C(dict, "shortname", CSTR_AS_OBJ(opt->shortname));
const char *scope;
if (opt->indir & PV_BUF) {
@@ -6350,14 +6351,14 @@ static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, wi
scope = "global";
}
- PUT(dict, "scope", CSTR_TO_OBJ(scope));
+ PUT_C(dict, "scope", CSTR_AS_OBJ(scope));
// welcome to the jungle
- PUT(dict, "global_local", BOOLEAN_OBJ(opt->indir & PV_BOTH));
- PUT(dict, "commalist", BOOLEAN_OBJ(opt->flags & P_COMMA));
- PUT(dict, "flaglist", BOOLEAN_OBJ(opt->flags & P_FLAGLIST));
+ PUT_C(dict, "global_local", BOOLEAN_OBJ(opt->indir & PV_BOTH));
+ PUT_C(dict, "commalist", BOOLEAN_OBJ(opt->flags & P_COMMA));
+ PUT_C(dict, "flaglist", BOOLEAN_OBJ(opt->flags & P_FLAGLIST));
- PUT(dict, "was_set", BOOLEAN_OBJ(opt->flags & P_WAS_SET));
+ PUT_C(dict, "was_set", BOOLEAN_OBJ(opt->flags & P_WAS_SET));
LastSet last_set = { .channel_id = 0 };
if (req_scope == OPT_GLOBAL) {
@@ -6375,16 +6376,16 @@ static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, wi
}
}
- PUT(dict, "last_set_sid", INTEGER_OBJ(last_set.script_ctx.sc_sid));
- PUT(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum));
- PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id));
+ PUT_C(dict, "last_set_sid", INTEGER_OBJ(last_set.script_ctx.sc_sid));
+ PUT_C(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum));
+ PUT_C(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id));
// TODO(bfredl): do you even nocp?
OptVal def = optval_from_varp(get_opt_idx(opt), &opt->def_val);
- PUT(dict, "type", CSTR_TO_OBJ(optval_type_get_name(def.type)));
- PUT(dict, "default", optval_as_object(optval_copy(def)));
- PUT(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & P_NODUP)));
+ PUT_C(dict, "type", CSTR_AS_OBJ(optval_type_get_name(def.type)));
+ PUT_C(dict, "default", optval_as_object(def));
+ PUT_C(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & P_NODUP)));
return dict;
}
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index 3dc82f8fdf..a389516bd3 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -218,7 +218,7 @@ enum {
SHM_INTRO = 'I', ///< Intro messages.
SHM_COMPLETIONMENU = 'c', ///< Completion menu messages.
SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages.
- SHM_RECORDING = 'q', ///< Short recording message.
+ SHM_RECORDING = 'q', ///< No recording message.
SHM_FILEINFO = 'F', ///< No file info messages.
SHM_SEARCHCOUNT = 'S', ///< No search stats: '[1/10]'
};
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 5c0d249ac3..b993a50b18 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -7329,7 +7329,7 @@ return {
match", "Pattern not found", "Back at original", etc.
C don't give messages while scanning for ins-completion *shm-C*
items, for instance "scanning tags"
- q use "recording" instead of "recording @a" *shm-q*
+ q do not show "recording @a" when recording a macro *shm-q*
F don't give the file info when editing a file, like *shm-F*
`:silent` was used for the command
S do not show search count message when searching, e.g. *shm-S*
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 566d51f30a..ade745df2c 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -95,7 +95,7 @@ int os_chdir(const char *path)
}
int err = uv_chdir(path);
if (err == 0) {
- ui_call_chdir(cstr_as_string((char *)path));
+ ui_call_chdir(cstr_as_string(path));
}
return err;
}
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index abba49f0e8..fab360c9af 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -95,7 +95,7 @@ static void cursorhold_event(void **argv)
static void create_cursorhold_event(bool events_enabled)
{
// If events are enabled and the queue has any items, this function should not
- // have been called(inbuf_poll would return kInputAvail)
+ // have been called (inbuf_poll would return kInputAvail).
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
assert(!events_enabled || multiqueue_empty(main_loop.events));
@@ -110,7 +110,7 @@ static void restart_cursorhold_wait(int tb_change_cnt)
/// Low level input function
///
-/// wait until either the input buffer is non-empty or, if `events` is not NULL
+/// Wait until either the input buffer is non-empty or, if `events` is not NULL
/// until `events` is non-empty.
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
{
@@ -543,7 +543,7 @@ bool os_input_ready(MultiQueue *events)
{
return (typebuf_was_filled // API call filled typeahead
|| rbuffer_size(input_buffer) // Input buffer filled
- || pending_events(events)); // Events must be processed
+ || pending_events(events)); // Events must be processed
}
// Exit because of an input read error.
diff --git a/src/nvim/plines.h b/src/nvim/plines.h
index 461e79e3ad..658206e1be 100644
--- a/src/nvim/plines.h
+++ b/src/nvim/plines.h
@@ -18,23 +18,23 @@ enum {
/// Argument for char size functions.
typedef struct {
win_T *win;
- char *line; ///< start of the line
+ char *line; ///< Start of the line.
- bool use_tabstop; ///< use tabstop for tab insted of counting it as ^I
- int indent_width; ///< width of showbreak and breakindent on wrapped lines
- /// INT_MIN if not yet calculated
+ bool use_tabstop; ///< Use 'tabstop' instead of char2cells() for a TAB.
+ int indent_width; ///< Width of 'showbreak' and 'breakindent' on wrapped
+ ///< parts of lines, INT_MIN if not yet calculated.
- int virt_row; ///< line number, -1 if no virtual text
- int cur_text_width_left; ///< width of virtual text left of cursor
- int cur_text_width_right; ///< width of virtual text right of cursor
+ int virt_row; ///< Row for virtual text, -1 if no virtual text.
+ int cur_text_width_left; ///< Width of virtual text left of cursor.
+ int cur_text_width_right; ///< Width of virtual text right of cursor.
- int max_head_vcol; ///< see charsize_regular()
+ int max_head_vcol; ///< See charsize_regular().
MarkTreeIter iter[1];
} CharsizeArg;
typedef struct {
int width;
- int head; // size of breakindent etc. before the character (included in width)
+ int head; ///< Size of 'breakindent' etc. before the character (included in width).
} CharSize;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 0350ff6928..e34d6fd97f 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -488,7 +488,7 @@ void pum_redraw(void)
if (ui_has(kUIMultigrid)) {
const char *anchor = pum_above ? "SW" : "NW";
int row_off = pum_above ? -pum_height : 0;
- ui_call_win_float_pos(pum_grid.handle, -1, cstr_as_string((char *)anchor), pum_anchor_grid,
+ ui_call_win_float_pos(pum_grid.handle, -1, cstr_as_string(anchor), pum_anchor_grid,
pum_row - row_off, pum_left_col, false, pum_grid.zindex);
}
@@ -669,7 +669,7 @@ void pum_redraw(void)
/// @return NULL when no enough room to show
static win_T *pum_create_float_preview(bool enter)
{
- FloatConfig config = FLOAT_CONFIG_INIT;
+ WinConfig config = WIN_CONFIG_INIT;
config.relative = kFloatRelativeEditor;
// when pum_above is SW otherwise is NW
config.anchor = pum_above ? kFloatAnchorSouth : 0;
@@ -744,16 +744,16 @@ static void pum_adjust_float_position(win_T *wp, int height, int width)
{
// when floating window in right and right no enough room to show
// but left has enough room, adjust floating window to left.
- if (wp->w_float_config.width < width && wp->w_float_config.col > pum_col) {
+ if (wp->w_config.width < width && wp->w_config.col > pum_col) {
if ((pum_col - 2) > width) {
- wp->w_float_config.width = width;
- wp->w_float_config.col = pum_col - width - 1;
+ wp->w_config.width = width;
+ wp->w_config.col = pum_col - width - 1;
}
}
- wp->w_float_config.width = MIN(wp->w_float_config.width, width);
- wp->w_float_config.height = MIN(Rows, height);
- wp->w_float_config.hide = false;
- win_config_float(wp, wp->w_float_config);
+ wp->w_config.width = MIN(wp->w_config.width, width);
+ wp->w_config.height = MIN(Rows, height);
+ wp->w_config.hide = false;
+ win_config_float(wp, wp->w_config);
}
/// used in nvim_complete_set
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index f7776ef74f..b88b08d3f0 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -45,6 +45,7 @@ typedef struct {
#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
static proftime_T prof_wait_time;
+static char *startuptime_buf = NULL; // --startuptime buffer
/// Gets the current time.
///
@@ -907,7 +908,7 @@ void time_start(const char *message)
// initialize the global variables
g_prev_time = g_start_time = profile_start();
- fprintf(time_fd, "\n\ntimes in msec\n");
+ fprintf(time_fd, "\ntimes in msec\n");
fprintf(time_fd, " clock self+sourced self: sourced script\n");
fprintf(time_fd, " clock elapsed: other lines\n\n");
@@ -944,3 +945,47 @@ void time_msg(const char *mesg, const proftime_T *start)
g_prev_time = now;
fprintf(time_fd, ": %s\n", mesg);
}
+
+/// Initializes the `time_fd` stream for the --startuptime report.
+///
+/// @param fname startuptime report file path
+/// @param process_name name of the current Nvim process to write in the report.
+void time_init(const char *fname, const char *process_name)
+{
+ const size_t bufsize = 8192; // Big enough for the entire --startuptime report.
+ time_fd = fopen(fname, "a");
+ if (time_fd == NULL) {
+ semsg(_(e_notopen), fname);
+ return;
+ }
+ startuptime_buf = xmalloc(sizeof(char) * (bufsize + 1));
+ // The startuptime file is (potentially) written by multiple Nvim processes concurrently. So each
+ // report is buffered, and flushed to disk (`time_finish`) once after startup. `_IOFBF` mode
+ // ensures the buffer is not auto-flushed ("controlled buffering").
+ int r = setvbuf(time_fd, startuptime_buf, _IOFBF, bufsize + 1);
+ if (r != 0) {
+ XFREE_CLEAR(startuptime_buf);
+ fclose(time_fd);
+ time_fd = NULL;
+ ELOG("time_init: setvbuf failed: %d %s", r, uv_err_name(r));
+ semsg("time_init: setvbuf failed: %d %s", r, uv_err_name(r));
+ return;
+ }
+ fprintf(time_fd, "--- Startup times for process: %s ---\n", process_name);
+}
+
+/// Flushes the startuptimes to disk for the current process
+void time_finish(void)
+{
+ if (time_fd == NULL) {
+ return;
+ }
+ assert(startuptime_buf != NULL);
+ TIME_MSG("--- NVIM STARTED ---\n");
+
+ // flush buffer to disk
+ fclose(time_fd);
+ time_fd = NULL;
+
+ XFREE_CLEAR(startuptime_buf);
+}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 273a924876..48e41c290d 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -3197,7 +3197,11 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
const int idx1 = ((const fuzzyItem_T *)s1)->idx;
const int idx2 = ((const fuzzyItem_T *)s2)->idx;
- return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+ if (v1 == v2) {
+ return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
+ } else {
+ return v1 > v2 ? -1 : 1;
+ }
}
/// Fuzzy search the string "str" in a list of "items" and return the matching
@@ -3436,7 +3440,11 @@ static int fuzzy_match_str_compare(const void *const s1, const void *const s2)
const int idx1 = ((fuzmatch_str_T *)s1)->idx;
const int idx2 = ((fuzmatch_str_T *)s2)->idx;
- return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+ if (v1 == v2) {
+ return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
+ } else {
+ return v1 > v2 ? -1 : 1;
+ }
}
/// Sort fuzzy matches by score
@@ -3465,7 +3473,11 @@ static int fuzzy_match_func_compare(const void *const s1, const void *const s2)
if (*str1 == '<' && *str2 != '<') {
return 1;
}
- return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+ if (v1 == v2) {
+ return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
+ } else {
+ return v1 > v2 ? -1 : 1;
+ }
}
/// Sort fuzzy matches of function names by score.
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 61c43e271b..2c8685adc7 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1875,9 +1875,7 @@ static int compare_file_marks(const void *a, const void *b)
const FileMarks *const *const b_fms = b;
return ((*a_fms)->greatest_timestamp == (*b_fms)->greatest_timestamp
? 0
- : ((*a_fms)->greatest_timestamp > (*b_fms)->greatest_timestamp
- ? -1
- : 1));
+ : ((*a_fms)->greatest_timestamp > (*b_fms)->greatest_timestamp ? -1 : 1));
}
/// Parse msgpack object that has given length
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index dc09bcc5a0..05e6814bd3 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -55,8 +55,8 @@
# include "sign.c.generated.h"
#endif
-static PMap(cstr_t) sign_map INIT( = MAP_INIT);
-static kvec_t(Integer) sign_ns INIT( = MAP_INIT);
+static PMap(cstr_t) sign_map = MAP_INIT;
+static kvec_t(Integer) sign_ns = KV_INITIAL_VALUE;
static char *cmds[] = {
"define",
@@ -84,7 +84,7 @@ static int64_t group_get_ns(const char *group)
return UINT32_MAX; // All namespaces
}
// Specific or non-existing namespace
- int ns = map_get(String, int)(&namespace_ids, cstr_as_string((char *)group));
+ int ns = map_get(String, int)(&namespace_ids, cstr_as_string(group));
return ns ? ns : -1;
}
@@ -168,24 +168,22 @@ static int buf_findsign(buf_T *buf, int id, char *group)
}
/// qsort() function to sort signs by line number, priority, id and recency.
-int sign_cmp(const void *p1, const void *p2)
+static int sign_row_cmp(const void *p1, const void *p2)
{
const MTKey *s1 = (MTKey *)p1;
const MTKey *s2 = (MTKey *)p2;
- int n = s1->pos.row - s2->pos.row;
- if (n) {
- return n;
+ if (s1->pos.row != s2->pos.row) {
+ return s1->pos.row > s2->pos.row ? 1 : -1;
}
DecorSignHighlight *sh1 = decor_find_sign(mt_decor(*s1));
DecorSignHighlight *sh2 = decor_find_sign(mt_decor(*s2));
assert(sh1 && sh2);
+ SignItem si1 = { sh1, s1->id };
+ SignItem si2 = { sh2, s2->id };
- n = sh2->priority - sh1->priority;
-
- return n ? n : (n = (int)(s2->id - s1->id))
- ? n : (sh2->sign_add_id - sh1->sign_add_id);
+ return sign_item_cmp(&si1, &si2);
}
/// Delete the specified sign(s)
@@ -241,7 +239,7 @@ static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum)
// Sort to remove the highest priority sign at a specific line number.
if (kv_size(signs)) {
- qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp);
extmark_del_id(buf, kv_A(signs, 0).ns, kv_A(signs, 0).id);
kv_destroy(signs);
} else if (atlnum > 0) {
@@ -296,7 +294,7 @@ static void sign_list_placed(buf_T *rbuf, char *group)
}
if (kv_size(signs)) {
- qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp);
for (size_t i = 0; i < kv_size(signs); i++) {
namebuf[0] = '\0';
@@ -994,7 +992,7 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const
}
if (kv_size(signs)) {
- qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp);
for (size_t i = 0; i < kv_size(signs); i++) {
tv_list_append_dict(l, sign_get_placed_info_dict(kv_A(signs, i)));
}
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index ce6d7d18d6..ad2a2b737d 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -1,5 +1,6 @@
#pragma once
+#include "nvim/decoration_defs.h"
#include "nvim/types_defs.h"
/// Sign attributes. Used by the screen refresh routines.
@@ -19,5 +20,10 @@ typedef struct {
int sn_num_hl; // highlight ID for line number
} sign_T;
+typedef struct {
+ DecorSignHighlight *sh;
+ uint32_t id;
+} SignItem;
+
enum { SIGN_SHOW_MAX = 9, }; ///< Maximum number of signs shown on a single line
enum { SIGN_DEF_PRIO = 10, }; ///< Default sign highlight priority
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
index 887ad3a62a..5c0e295f88 100644
--- a/src/nvim/spellsuggest.c
+++ b/src/nvim/spellsuggest.c
@@ -3221,10 +3221,10 @@ 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;
+ int n = p1->st_score == p2->st_score ? 0 : p1->st_score > p2->st_score ? 1 : -1;
if (n == 0) {
- n = p1->st_altscore - p2->st_altscore;
+ n = p1->st_altscore == p2->st_altscore ? 0 : p1->st_altscore > p2->st_altscore ? 1 : -1;
if (n == 0) {
n = STRICMP(p1->st_word, p2->st_word);
}
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 527acee306..0df060ecf4 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -79,7 +79,7 @@ getkey:
// mapping engine.
os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
- if (!multiqueue_empty(main_loop.events)) {
+ if (!input_available() && !multiqueue_empty(main_loop.events)) {
key = K_EVENT;
} else {
goto getkey;
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index f79e829a7f..0a716067c6 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -411,7 +411,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
// might change the option value and free the memory.
stl = xstrdup(stl);
build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_idx, opt_scope,
- fillchar, maxwidth, &hltab, &tabtab, NULL);
+ fillchar, maxwidth, &hltab, NULL, &tabtab, NULL);
xfree(stl);
ewp->w_p_crb = p_crb_save;
@@ -665,7 +665,7 @@ static void ui_ext_tabline_update(void)
win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin;
get_trans_bufname(cwp->w_buffer);
- PUT_C(tab_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff))));
+ PUT_C(tab_info, "name", CSTR_TO_ARENA_OBJ(&arena, NameBuff));
ADD_C(tabs, DICTIONARY_OBJ(tab_info));
}
@@ -686,7 +686,7 @@ static void ui_ext_tabline_update(void)
PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle));
get_trans_bufname(buf);
- PUT_C(buffer_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff))));
+ PUT_C(buffer_info, "name", CSTR_TO_ARENA_OBJ(&arena, NameBuff));
ADD_C(buffers, DICTIONARY_OBJ(buffer_info));
}
@@ -880,7 +880,7 @@ int build_statuscol_str(win_T *wp, linenr_T lnum, linenr_T relnum, char *buf, st
StlClickRecord *clickrec;
char *stc = xstrdup(wp->w_p_stc);
int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, kOptStatuscolumn, OPT_LOCAL, 0,
- stcp->width, &stcp->hlrec, fillclick ? &clickrec : NULL, stcp);
+ stcp->width, &stcp->hlrec, NULL, fillclick ? &clickrec : NULL, stcp);
xfree(stc);
if (fillclick) {
@@ -922,7 +922,7 @@ int build_statuscol_str(win_T *wp, linenr_T lnum, linenr_T relnum, char *buf, st
/// @return The final width of the statusline
int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex opt_idx,
int opt_scope, schar_T fillchar, int maxwidth, stl_hlrec_t **hltab,
- StlClickRecord **tabtab, statuscol_T *stcp)
+ size_t *hltab_len, StlClickRecord **tabtab, statuscol_T *stcp)
{
static size_t stl_items_len = 20; // Initial value, grows as needed.
static stl_item_t *stl_items = NULL;
@@ -2132,6 +2132,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
sp->start = NULL;
sp->userhl = 0;
}
+ if (hltab_len) {
+ *hltab_len = (size_t)itemcnt;
+ }
// Store the info about tab pages labels.
if (tabtab != NULL) {
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index d4bbb8a924..5565bd1055 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -137,6 +137,7 @@ struct TUIData {
int sync;
} unibi_ext;
char *space_buf;
+ size_t space_buf_len;
bool stopped;
int seen_error_exit;
int width;
@@ -1055,10 +1056,7 @@ void tui_grid_resize(TUIData *tui, Integer g, Integer width, Integer height)
{
UGrid *grid = &tui->grid;
ugrid_resize(grid, (int)width, (int)height);
-
- xfree(tui->space_buf);
- tui->space_buf = xmalloc((size_t)width * sizeof(*tui->space_buf));
- memset(tui->space_buf, ' ', (size_t)width);
+ ensure_space_buf_size(tui, (size_t)width);
// resize might not always be followed by a clear before flush
// so clip the invalid region
@@ -1642,6 +1640,15 @@ static void invalidate(TUIData *tui, int top, int bot, int left, int right)
}
}
+static void ensure_space_buf_size(TUIData *tui, size_t len)
+{
+ if (len > tui->space_buf_len) {
+ tui->space_buf = xrealloc(tui->space_buf, len * sizeof *tui->space_buf);
+ memset(tui->space_buf + tui->space_buf_len, ' ', len - tui->space_buf_len);
+ tui->space_buf_len = len;
+ }
+}
+
/// Tries to get the user's wanted dimensions (columns and rows) for the entire
/// application (i.e., the host terminal).
void tui_guess_size(TUIData *tui)
@@ -1678,6 +1685,7 @@ void tui_guess_size(TUIData *tui)
tui->width = width;
tui->height = height;
+ ensure_space_buf_size(tui, (size_t)tui->width);
// Redraw on SIGWINCH event if size didn't change. #23411
ui_client_set_size(width, height);
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 2744f68951..debd2fe511 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -229,7 +229,7 @@ void ui_refresh(void)
}
ui_ext[i] = ext_widgets[i];
if (i < kUIGlobalCount) {
- ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]),
+ ui_call_option_set(cstr_as_string(ui_ext_names[i]),
BOOLEAN_OBJ(ext_widgets[i]));
}
}
@@ -451,8 +451,7 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
return;
}
if (ui_ext_names[ext][0] != '_' || active) {
- remote_ui_option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
- BOOLEAN_OBJ(active));
+ remote_ui_option_set(ui, cstr_as_string(ui_ext_names[ext]), BOOLEAN_OBJ(active));
}
if (ext == kUITermColors) {
ui_default_colors_set();
@@ -646,34 +645,35 @@ bool ui_has(UIExtension ext)
return ui_ext[ext];
}
-Array ui_array(void)
+Array ui_array(Arena *arena)
{
- Array all_uis = ARRAY_DICT_INIT;
+ Array all_uis = arena_array(arena, ui_count);
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
- Dictionary info = ARRAY_DICT_INIT;
- PUT(info, "width", INTEGER_OBJ(ui->width));
- PUT(info, "height", INTEGER_OBJ(ui->height));
- PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
- PUT(info, "override", BOOLEAN_OBJ(ui->override));
+ Dictionary info = arena_dict(arena, 10 + kUIExtCount);
+ PUT_C(info, "width", INTEGER_OBJ(ui->width));
+ PUT_C(info, "height", INTEGER_OBJ(ui->height));
+ PUT_C(info, "rgb", BOOLEAN_OBJ(ui->rgb));
+ PUT_C(info, "override", BOOLEAN_OBJ(ui->override));
// TUI fields. (`stdin_fd` is intentionally omitted.)
- PUT(info, "term_name", CSTR_TO_OBJ(ui->term_name));
+ PUT_C(info, "term_name", CSTR_AS_OBJ(ui->term_name));
// term_background is deprecated. Populate with an empty string
- PUT(info, "term_background", CSTR_TO_OBJ(""));
+ PUT_C(info, "term_background", STATIC_CSTR_AS_OBJ(""));
- PUT(info, "term_colors", INTEGER_OBJ(ui->term_colors));
- PUT(info, "stdin_tty", BOOLEAN_OBJ(ui->stdin_tty));
- PUT(info, "stdout_tty", BOOLEAN_OBJ(ui->stdout_tty));
+ PUT_C(info, "term_colors", INTEGER_OBJ(ui->term_colors));
+ PUT_C(info, "stdin_tty", BOOLEAN_OBJ(ui->stdin_tty));
+ PUT_C(info, "stdout_tty", BOOLEAN_OBJ(ui->stdout_tty));
for (UIExtension j = 0; j < kUIExtCount; j++) {
if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
- PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
+ PUT_C(info, (char *)ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
}
}
- remote_ui_inspect(ui, &info);
- ADD(all_uis, DICTIONARY_OBJ(info));
+ PUT_C(info, "chan", INTEGER_OBJ((Integer)ui->data->channel_id));
+
+ ADD_C(all_uis, DICTIONARY_OBJ(info));
}
return all_uis;
}
@@ -692,9 +692,9 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
if (wp->w_floating) {
if (width != wp->w_width || height != wp->w_height) {
- wp->w_float_config.width = width;
- wp->w_float_config.height = height;
- win_config_float(wp, wp->w_float_config);
+ wp->w_config.width = width;
+ wp->w_config.height = height;
+ win_config_float(wp, wp->w_config);
}
} else {
// non-positive indicates no request
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 547ac605e7..6081268e53 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2390,9 +2390,7 @@ static void u_undoredo(bool undo, bool do_buf_event)
// When text has been changed, possibly the start of the next line
// may have SpellCap that should be removed or it needs to be
// displayed. Schedule the next line for redrawing just in case.
- // Also just in case the line had a sign which needs to be removed.
- if ((spell_check_window(curwin) || buf_meta_total(curbuf, kMTMetaSignText))
- && bot <= curbuf->b_ml.ml_line_count) {
+ if (spell_check_window(curwin) && bot <= curbuf->b_ml.ml_line_count) {
redrawWinline(curwin, bot);
}
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
index e20c3f6044..729e57cb2b 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -1136,6 +1136,20 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf,
return true;
}
+size_t uc_nargs_upper_bound(const char *arg, size_t arglen)
+{
+ bool was_white = true; // space before first arg
+ size_t nargs = 0;
+ for (size_t i = 0; i < arglen; i++) {
+ bool is_white = ascii_iswhite(arg[i]);
+ if (was_white && !is_white) {
+ nargs++;
+ }
+ was_white = is_white;
+ }
+ return nargs;
+}
+
/// split and quote args for <f-args>
static char *uc_split_args(const char *arg, char **args, const size_t *arglens, size_t argc,
size_t *lenp)
diff --git a/src/nvim/window.c b/src/nvim/window.c
index d07ae22c31..f7f22c85ae 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -328,13 +328,13 @@ newwindow:
wp = lastwin; // wrap around
}
while (wp != NULL && wp->w_floating
- && !wp->w_float_config.focusable) {
+ && !wp->w_config.focusable) {
wp = wp->w_prev;
}
} else { // go to next window
wp = curwin->w_next;
while (wp != NULL && wp->w_floating
- && !wp->w_float_config.focusable) {
+ && !wp->w_config.focusable) {
wp = wp->w_next;
}
if (wp == NULL) {
@@ -669,7 +669,7 @@ wingotofile:
beep_flush();
break;
}
- FloatConfig config = FLOAT_CONFIG_INIT;
+ WinConfig config = WIN_CONFIG_INIT;
config.width = curwin->w_width;
config.height = curwin->w_height;
config.external = true;
@@ -763,7 +763,7 @@ void ui_ext_win_position(win_T *wp, bool validate)
return;
}
- FloatConfig c = wp->w_float_config;
+ WinConfig c = wp->w_config;
if (!c.external) {
ScreenGrid *grid = &default_grid;
Float row = c.row;
@@ -794,9 +794,9 @@ void ui_ext_win_position(win_T *wp, bool validate)
}
}
- wp->w_grid_alloc.zindex = wp->w_float_config.zindex;
+ wp->w_grid_alloc.zindex = wp->w_config.zindex;
if (ui_has(kUIMultigrid)) {
- String anchor = cstr_as_string((char *)float_anchor_str[c.anchor]);
+ String anchor = cstr_as_string(float_anchor_str[c.anchor]);
if (!c.hide) {
ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
grid->handle, row, col, c.focusable,
@@ -830,7 +830,7 @@ void ui_ext_win_position(win_T *wp, bool validate)
ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
wp->w_height_outer, wp->w_width_outer, valid, false);
ui_check_cursor_grid(wp->w_grid_alloc.handle);
- wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
+ wp->w_grid_alloc.focusable = wp->w_config.focusable;
if (!valid) {
wp->w_grid_alloc.valid = false;
redraw_later(wp, UPD_NOT_VALID);
@@ -1213,7 +1213,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir)
new_frame(wp);
wp->w_floating = false;
// non-floating window doesn't store float config or have a border.
- wp->w_float_config = FLOAT_CONFIG_INIT;
+ wp->w_config = WIN_CONFIG_INIT;
CLEAR_FIELD(wp->w_border_adj);
}
@@ -2757,7 +2757,7 @@ int win_close(win_T *win, bool free_buf, bool force)
if (win->w_floating) {
ui_comp_remove_grid(&win->w_grid_alloc);
assert(first_tabpage != NULL); // suppress clang "Dereference of NULL pointer"
- if (win->w_float_config.external) {
+ if (win->w_config.external) {
for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
if (tp == curtab) {
continue;
@@ -3879,7 +3879,7 @@ void win_alloc_first(void)
void win_alloc_aucmd_win(int idx)
{
Error err = ERROR_INIT;
- FloatConfig fconfig = FLOAT_CONFIG_INIT;
+ WinConfig fconfig = WIN_CONFIG_INIT;
fconfig.width = Columns;
fconfig.height = 5;
fconfig.focusable = false;
@@ -4328,7 +4328,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab)
for (win_T *wp = old_curtab->tp_firstwin; wp; wp = next_wp) {
next_wp = wp->w_next;
if (wp->w_floating) {
- if (wp->w_float_config.external) {
+ if (wp->w_config.external) {
win_remove(wp, old_curtab);
win_append(lastwin_nofloating(), wp);
} else {
@@ -4339,8 +4339,8 @@ static void tabpage_check_windows(tabpage_T *old_curtab)
}
for (win_T *wp = firstwin; wp; wp = wp->w_next) {
- if (wp->w_floating && !wp->w_float_config.external) {
- win_config_float(wp, wp->w_float_config);
+ if (wp->w_floating && !wp->w_config.external) {
+ win_config_float(wp, wp->w_config);
}
wp->w_pos_changed = true;
}
@@ -4983,7 +4983,7 @@ win_T *win_alloc(win_T *after, bool hidden)
new_wp->w_cursor.lnum = 1;
new_wp->w_scbind_pos = 1;
new_wp->w_floating = 0;
- new_wp->w_float_config = FLOAT_CONFIG_INIT;
+ new_wp->w_config = WIN_CONFIG_INIT;
new_wp->w_viewport_invalid = true;
new_wp->w_viewport_last_topline = 1;
@@ -5097,8 +5097,8 @@ void win_free(win_T *wp, tabpage_T *tp)
}
// free the border text
- clear_virttext(&wp->w_float_config.title_chunks);
- clear_virttext(&wp->w_float_config.footer_chunks);
+ clear_virttext(&wp->w_config.title_chunks);
+ clear_virttext(&wp->w_config.footer_chunks);
clear_matches(wp);
@@ -5622,7 +5622,7 @@ int win_comp_pos(void)
for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
// float might be anchored to moved window
- if (wp->w_float_config.relative == kFloatRelativeWindow) {
+ if (wp->w_config.relative == kFloatRelativeWindow) {
wp->w_pos_changed = true;
}
}
@@ -5681,8 +5681,8 @@ void win_setheight_win(int height, win_T *win)
height = MAX(height, (int)(win == curwin ? MAX(p_wmh, 1) : p_wmh) + win->w_winbar_height);
if (win->w_floating) {
- win->w_float_config.height = height;
- win_config_float(win, win->w_float_config);
+ win->w_config.height = height;
+ win_config_float(win, win->w_config);
redraw_later(win, UPD_VALID);
} else {
frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height);
@@ -5893,8 +5893,8 @@ void win_setwidth_win(int width, win_T *wp)
width = 0;
}
if (wp->w_floating) {
- wp->w_float_config.width = width;
- win_config_float(wp, wp->w_float_config);
+ wp->w_config.width = width;
+ win_config_float(wp, wp->w_config);
redraw_later(wp, UPD_NOT_VALID);
} else {
frame_setwidth(wp->w_frame, width + wp->w_vsep_width);
@@ -7359,9 +7359,17 @@ static bool frame_check_width(const frame_T *topfrp, int width)
}
/// Simple int comparison function for use with qsort()
-static int int_cmp(const void *a, const void *b)
+static int int_cmp(const void *pa, const void *pb)
{
- return *(const int *)a - *(const int *)b;
+ const int a = *(const int *)pa;
+ const int b = *(const int *)pb;
+ if (a > b) {
+ return 1;
+ }
+ if (a < b) {
+ return -1;
+ }
+ return 0;
}
/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
index 1d22590ac0..83a04a1bcf 100644
--- a/src/nvim/winfloat.c
+++ b/src/nvim/winfloat.c
@@ -38,7 +38,7 @@
/// @param last make the window the last one in the window list.
/// Only used when allocating the autocommand window.
/// @param config must already have been validated!
-win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
+win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
{
if (wp == NULL) {
wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
@@ -138,7 +138,7 @@ int win_border_width(win_T *wp)
return wp->w_border_adj[1] + wp->w_border_adj[3];
}
-void win_config_float(win_T *wp, FloatConfig fconfig)
+void win_config_float(win_T *wp, WinConfig fconfig)
{
wp->w_width = MAX(fconfig.width, 1);
wp->w_height = MAX(fconfig.height, 1);
@@ -161,17 +161,17 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
}
}
- bool change_external = fconfig.external != wp->w_float_config.external;
- bool change_border = (fconfig.border != wp->w_float_config.border
+ bool change_external = fconfig.external != wp->w_config.external;
+ bool change_border = (fconfig.border != wp->w_config.border
|| memcmp(fconfig.border_hl_ids,
- wp->w_float_config.border_hl_ids,
+ wp->w_config.border_hl_ids,
sizeof fconfig.border_hl_ids) != 0);
- wp->w_float_config = fconfig;
+ wp->w_config = fconfig;
- bool has_border = wp->w_floating && wp->w_float_config.border;
+ bool has_border = wp->w_floating && wp->w_config.border;
for (int i = 0; i < 4; i++) {
- int new_adj = has_border && wp->w_float_config.border_chars[2 * i + 1][0];
+ int new_adj = has_border && wp->w_config.border_chars[2 * i + 1][0];
if (new_adj != wp->w_border_adj[i]) {
change_border = true;
wp->w_border_adj[i] = new_adj;
@@ -193,11 +193,11 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
}
// compute initial position
- if (wp->w_float_config.relative == kFloatRelativeWindow) {
- int row = (int)wp->w_float_config.row;
- int col = (int)wp->w_float_config.col;
+ if (wp->w_config.relative == kFloatRelativeWindow) {
+ int row = (int)wp->w_config.row;
+ int col = (int)wp->w_config.col;
Error dummy = ERROR_INIT;
- win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
+ win_T *parent = find_window_by_handle(wp->w_config.window, &dummy);
if (parent) {
row += parent->w_winrow;
col += parent->w_wincol;
@@ -207,9 +207,9 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
grid_adjust(&grid, &row_off, &col_off);
row += row_off;
col += col_off;
- if (wp->w_float_config.bufpos.lnum >= 0) {
- pos_T pos = { wp->w_float_config.bufpos.lnum + 1,
- wp->w_float_config.bufpos.col, 0 };
+ if (wp->w_config.bufpos.lnum >= 0) {
+ pos_T pos = { wp->w_config.bufpos.lnum + 1,
+ wp->w_config.bufpos.col, 0 };
int trow, tcol, tcolc, tcole;
textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
row += trow - 1;
@@ -233,7 +233,9 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
static int float_zindex_cmp(const void *a, const void *b)
{
- return (*(win_T **)b)->w_float_config.zindex - (*(win_T **)a)->w_float_config.zindex;
+ int za = (*(win_T **)a)->w_config.zindex;
+ int zb = (*(win_T **)b)->w_config.zindex;
+ return za == zb ? 0 : za < zb ? 1 : -1;
}
void win_float_remove(bool bang, int count)
@@ -263,8 +265,8 @@ void win_check_anchored_floats(win_T *win)
{
for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
// float might be anchored to moved window
- if (wp->w_float_config.relative == kFloatRelativeWindow
- && wp->w_float_config.window == win->handle) {
+ if (wp->w_config.relative == kFloatRelativeWindow
+ && wp->w_config.window == win->handle) {
wp->w_pos_changed = true;
}
}
@@ -273,7 +275,7 @@ void win_check_anchored_floats(win_T *win)
void win_reconfig_floats(void)
{
for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
- win_config_float(wp, wp->w_float_config);
+ win_config_float(wp, wp->w_config);
}
}
diff --git a/test/benchmark/screenpos_spec.lua b/test/benchmark/screenpos_spec.lua
index 27891a58a4..8a80712dfa 100644
--- a/test/benchmark/screenpos_spec.lua
+++ b/test/benchmark/screenpos_spec.lua
@@ -4,7 +4,7 @@ local Screen = require('test.functional.ui.screen')
local function rand_utf8(count, seed)
math.randomseed(seed)
- local symbols = { 'i', 'À', 'Ⴀ', '𐀀' }
+ local symbols = { 'i', 'À', 'Ⱡ', '𐀀' }
local s = ''
for _ = 1, count do
s = s .. symbols[math.random(1, #symbols)]
@@ -28,6 +28,12 @@ local benchmark_chars = {
end,
},
{
+ name = '3 byte utf-8',
+ line = function(count, _)
+ return ('Ⱡ'):rep(count)
+ end,
+ },
+ {
name = 'random 1-4 byte utf-8',
line = function(count, i)
return rand_utf8(count, 123456 + i)
@@ -174,63 +180,72 @@ local two_byte_results = {
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
}
+local three_byte_results = {
+ screen = [=[
+ ⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠ|*49
+ ⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠⱠ^Ⱡ |
+ |
+ ]=],
+ value = { col = 100, curscol = 100, endcol = 100, row = 50 },
+}
benchmarks({
{
ascii_results,
two_byte_results,
+ three_byte_results,
{ -- random
screen = [=[
- Ⴀ𐀀ii𐀀ႠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⴀ𐀀ႠiiႠ𐀀iii𐀀ÀႠÀႠႠÀiiÀႠÀiႠi𐀀ÀÀ𐀀𐀀Ⴀ𐀀𐀀Ⴀ𐀀iÀႠ𐀀i𐀀ÀÀႠiiÀ𐀀Ⴀ𐀀À𐀀ii𐀀ÀÀ𐀀ÀႠႠ𐀀𐀀𐀀𐀀ii𐀀ႠႠiႠႠ𐀀ႠÀ𐀀ÀÀiiႠ|
- 𐀀𐀀ÀႠÀ𐀀𐀀iႠÀ𐀀𐀀i𐀀𐀀𐀀𐀀ႠႠႠiiiiÀÀ𐀀ႠÀႠÀi𐀀i𐀀ႠႠÀiÀႠi𐀀𐀀À𐀀iÀ𐀀ÀÀÀ𐀀𐀀iÀႠ𐀀𐀀Ⴀ𐀀𐀀ÀiႠiiႠႠÀÀÀÀi𐀀ÀÀႠ𐀀Àiii𐀀iႠႠႠi𐀀ႠÀi𐀀iႠ𐀀ႠiႠ|
- ႠႠႠÀi𐀀ÀiႠÀiႠiႠ𐀀ÀiiiÀiiÀÀႠ𐀀À𐀀ÀႠÀÀÀႠ𐀀iႠႠ𐀀iႠÀ𐀀À𐀀ႠiÀÀÀÀiiÀiÀiÀႠiÀi𐀀iÀÀႠ𐀀𐀀ႠÀ𐀀iႠ𐀀i𐀀i𐀀ÀႠi𐀀iႠÀÀiiႠ𐀀𐀀ႠiÀiÀiiÀ|
- iႠ𐀀iiႠÀႠ𐀀iÀÀÀႠÀiÀ𐀀𐀀𐀀ÀႠiႠiÀiႠiႠ𐀀𐀀ႠႠi𐀀ႠႠ𐀀𐀀Ⴀ𐀀ÀiiႠÀႠႠ𐀀iÀiÀÀ𐀀𐀀𐀀ႠႠ𐀀iii𐀀À𐀀Ⴀ𐀀iႠii𐀀i𐀀i𐀀ÀiÀi𐀀Ⴀ𐀀𐀀i𐀀iÀÀiႠ𐀀Ⴀ𐀀ÀÀÀÀ|
- 𐀀ÀႠiÀႠÀÀÀႠ𐀀À𐀀𐀀𐀀i𐀀Ài𐀀𐀀iႠ𐀀𐀀ÀႠ𐀀𐀀À𐀀𐀀ႠÀÀႠႠႠiႠÀÀii𐀀Ⴀii𐀀Ⴀi𐀀Ài𐀀Ⴀ𐀀𐀀𐀀𐀀iÀ𐀀ႠÀiÀ𐀀ႠႠi𐀀i𐀀ႠႠÀႠ𐀀ႠÀႠ𐀀ÀႠiiႠ𐀀𐀀Ⴀ𐀀Ⴀ𐀀𐀀À𐀀𐀀Ài|
- iÀÀiÀÀiႠÀiiႠiÀiiiÀႠÀ𐀀Ài𐀀iiÀÀÀiႠႠiiႠ𐀀iÀ𐀀𐀀𐀀ႠÀÀ𐀀iiႠ𐀀Ⴀ𐀀ႠÀႠ𐀀Ài𐀀i𐀀ÀiÀႠႠ𐀀ÀႠiiႠÀ𐀀𐀀Ⴀii𐀀𐀀i𐀀𐀀ÀÀiÀ𐀀i𐀀𐀀i𐀀ÀiiiႠÀÀÀ|
- ÀÀႠႠÀႠႠÀiiiÀႠÀႠႠi𐀀ႠÀ𐀀iႠÀ𐀀ႠÀiiÀÀÀÀႠ𐀀𐀀À𐀀iiÀ𐀀𐀀iႠႠ𐀀iiႠႠ𐀀ÀÀÀ𐀀Ⴀ𐀀À𐀀ÀႠi𐀀ႠÀႠÀႠႠiÀ𐀀iÀÀႠ𐀀ႠႠ𐀀𐀀iႠႠႠ𐀀𐀀À𐀀iÀÀi𐀀iႠႠ𐀀𐀀|
- ÀÀiiႠÀ𐀀Ài𐀀iiÀ𐀀iÀႠÀÀi𐀀𐀀ႠႠ𐀀iiႠႠiÀ𐀀iႠÀÀiႠ𐀀ÀÀiiႠႠiÀÀ𐀀À𐀀iႠÀÀႠiႠ𐀀iႠ𐀀ÀiiႠÀÀႠÀÀiÀ𐀀𐀀ႠႠÀႠÀÀႠ𐀀À𐀀ႠႠi𐀀À𐀀𐀀ႠႠ𐀀iႠi𐀀Ⴀ|
- ÀÀႠ𐀀𐀀ÀႠႠႠÀႠiႠႠႠÀiiiႠiiiÀi𐀀𐀀iႠႠiiÀiႠႠႠÀÀiii𐀀ÀiႠÀi𐀀i𐀀𐀀ÀiÀiÀÀiÀ𐀀iÀÀÀiÀႠႠÀiÀ𐀀À𐀀iႠÀႠ𐀀ii𐀀𐀀iႠiÀiႠ𐀀Ⴀ𐀀Ⴀ𐀀𐀀ႠiÀႠ|
- ÀႠii𐀀i𐀀iÀÀÀÀÀ𐀀ÀiÀ𐀀𐀀ÀႠiiiiiÀႠ𐀀ÀÀÀႠi𐀀iႠiႠiÀႠႠ𐀀𐀀ii𐀀𐀀iÀÀÀႠiiÀ𐀀iÀiÀ𐀀Ⴀ𐀀Ⴀ𐀀i𐀀iÀႠiÀ𐀀i𐀀ႠiႠÀ𐀀iႠÀႠiႠiÀÀÀÀ𐀀Ⴀ𐀀iႠႠÀ|
- 𐀀Ⴀ𐀀ႠÀ𐀀iႠiÀi𐀀i𐀀𐀀𐀀ႠÀႠiÀႠႠ𐀀ÀiÀiႠi𐀀ÀႠi𐀀𐀀ÀÀi𐀀À𐀀ÀiiႠ𐀀iႠÀÀÀiii𐀀ႠiႠ𐀀𐀀𐀀ႠႠiႠ𐀀𐀀iiႠiiiii𐀀𐀀𐀀𐀀𐀀ÀÀÀÀi𐀀𐀀ႠႠ𐀀iÀ𐀀ÀiiÀii|
- Ⴀ𐀀𐀀iႠi𐀀Ⴀ𐀀ႠႠiÀÀႠi𐀀ÀiႠႠႠ𐀀ÀႠiႠႠiႠႠÀ𐀀ႠiiiÀ𐀀𐀀𐀀ÀÀiႠi𐀀ႠÀ𐀀ÀႠÀi𐀀ႠiÀႠiiႠႠÀႠiÀÀႠÀÀÀÀ𐀀iÀႠႠiႠႠ𐀀ÀÀÀႠiÀÀiÀႠ𐀀iÀႠiiႠ𐀀|
- iÀႠi𐀀ÀႠÀÀÀiÀÀႠ𐀀À𐀀𐀀𐀀ႠiiiႠiiႠ𐀀À𐀀iii𐀀À𐀀ႠႠi𐀀iiႠÀ𐀀𐀀i𐀀ႠÀ𐀀𐀀i𐀀ÀiႠÀÀiÀÀi𐀀𐀀Ⴀ𐀀À𐀀i𐀀iÀႠႠႠ𐀀ÀÀÀႠÀÀႠႠႠႠႠi𐀀𐀀iiႠÀi𐀀𐀀ႠႠ|
- iiႠႠႠ𐀀ÀႠ𐀀Àiii𐀀ÀÀii𐀀À𐀀iiႠÀႠiiႠ𐀀Ⴀ𐀀ÀႠ𐀀iÀiႠiiÀÀiÀÀiႠiႠႠiႠ𐀀Ⴀi𐀀𐀀ÀÀÀÀiႠii𐀀ႠÀ𐀀Ⴀ𐀀i𐀀ႠiႠႠ𐀀i𐀀ႠiႠႠ𐀀ÀiႠ𐀀iÀi𐀀𐀀ÀÀ𐀀𐀀i|
- iÀÀi𐀀ÀÀÀ𐀀ÀႠ𐀀ÀÀÀ𐀀𐀀𐀀iiႠÀÀႠ𐀀ႠÀ𐀀iiiÀÀiiႠ𐀀𐀀ႠiiႠiÀiႠႠÀ𐀀ႠiႠÀ𐀀i𐀀𐀀𐀀ႠÀ𐀀Ⴀ𐀀ÀÀÀႠႠ𐀀𐀀À𐀀iiႠႠႠႠႠÀ𐀀𐀀iႠႠiiÀiႠÀiÀႠiÀ𐀀À𐀀i|
- ÀႠႠ𐀀ÀႠi𐀀i𐀀ႠÀÀÀiiiႠ𐀀ႠÀႠÀÀႠ𐀀ÀÀiÀiÀÀႠÀႠiႠiÀ𐀀iႠÀ𐀀𐀀𐀀ÀႠႠ𐀀ႠႠ𐀀ÀႠႠႠii𐀀ÀÀÀÀiÀi𐀀ႠႠÀiႠiႠÀႠ𐀀ÀiႠႠiÀႠi𐀀𐀀𐀀ÀÀiiÀÀ𐀀𐀀ÀႠ|
- À𐀀ÀÀÀÀ𐀀ÀÀ𐀀i𐀀ႠÀႠiႠÀÀ𐀀ႠiÀÀiႠÀႠႠÀ𐀀iÀ𐀀i𐀀𐀀ႠÀÀÀႠႠiÀiiiiႠႠi𐀀ÀႠi𐀀i𐀀i𐀀𐀀𐀀ႠÀႠiiiÀi𐀀ÀႠi𐀀iiÀÀႠÀÀႠiiÀ𐀀À𐀀ÀÀ𐀀ႠÀႠÀ𐀀iႠ|
- Ⴀ𐀀ÀiႠ𐀀iiÀ𐀀À𐀀𐀀À𐀀ÀÀiiiiiiႠႠiiÀi𐀀iÀ𐀀ÀÀiႠ𐀀ÀႠÀÀÀiÀႠႠ𐀀À𐀀𐀀iÀ𐀀𐀀ii𐀀ႠÀ𐀀ii𐀀𐀀iႠÀ𐀀ÀÀÀiÀii𐀀iႠ𐀀ႠႠ𐀀ÀႠiiiႠႠႠi𐀀ÀÀiÀ𐀀𐀀À|
- ႠiÀ𐀀ႠÀ𐀀iÀႠi𐀀ÀiiiÀÀ𐀀ÀႠ𐀀ႠႠႠÀႠ𐀀ႠÀiiÀi𐀀i𐀀𐀀iႠÀႠiႠiiÀÀ𐀀𐀀𐀀Ài𐀀𐀀iiႠiႠÀႠ𐀀iiႠÀi𐀀ႠႠႠi𐀀ÀÀiÀႠ𐀀𐀀ÀiႠÀiiÀÀႠ𐀀ႠiiÀႠႠÀiÀ|
- i𐀀𐀀Ài𐀀𐀀ႠÀiÀÀႠႠi𐀀𐀀ÀÀႠÀÀႠႠႠႠ𐀀ÀiႠiiႠႠÀiiiႠႠÀ𐀀Ⴀii𐀀i𐀀ႠÀ𐀀ÀÀႠႠiÀiÀÀiႠÀiiiiÀႠiiÀႠႠႠÀÀi𐀀À𐀀ႠႠ𐀀Ⴀi𐀀ႠႠ𐀀𐀀𐀀iႠÀÀႠ𐀀ႠႠ|
- iÀÀႠႠႠiiÀii𐀀𐀀ႠÀiii𐀀ႠÀ𐀀ႠiiÀ𐀀iႠ𐀀ÀႠ𐀀Ⴀ𐀀𐀀Ⴀi𐀀ÀiႠÀႠ𐀀ÀÀi𐀀ႠiÀÀÀÀႠÀႠiႠiႠႠ𐀀𐀀ÀႠÀႠ𐀀𐀀ÀÀ𐀀ႠႠ𐀀𐀀i𐀀Ⴀii𐀀iii𐀀Ⴀi𐀀Ⴀ𐀀Ⴀi𐀀𐀀ÀႠi|
- À𐀀𐀀ÀႠÀiiiÀiiÀႠi𐀀iiiႠiiႠÀÀ𐀀ႠÀ𐀀ႠႠႠ𐀀i𐀀ÀÀiႠiiiႠiiÀÀiႠ𐀀À𐀀i𐀀Ⴀ𐀀ႠÀiႠႠ𐀀ÀႠႠ𐀀𐀀ÀiႠÀÀႠiiႠi𐀀i𐀀ႠÀÀiႠ𐀀Ⴀ𐀀iႠႠ𐀀ÀÀÀiÀÀii|
- ႠÀ𐀀𐀀ႠiႠÀ𐀀ÀÀÀႠႠÀႠ𐀀ÀiႠii𐀀𐀀ÀႠ𐀀iÀÀiiÀiÀÀ𐀀iႠÀiÀ𐀀𐀀À𐀀Ⴀ𐀀𐀀iiႠႠ𐀀ႠÀႠÀiÀiÀ𐀀iႠႠiÀi𐀀Ⴀ𐀀iiiÀႠ𐀀ÀႠiÀiiiႠiiÀႠÀiႠiÀÀiႠÀi|
- 𐀀𐀀ÀÀi𐀀À𐀀𐀀ÀႠႠႠ𐀀À𐀀𐀀À𐀀ÀiႠႠ𐀀𐀀ႠÀiႠiႠÀ𐀀ÀiႠiiii𐀀iႠiႠÀ𐀀ÀႠ𐀀𐀀iÀႠiÀi𐀀𐀀ႠÀiႠiÀႠႠ𐀀iႠ𐀀ÀႠÀii𐀀ႠÀႠii𐀀Ⴀ𐀀ÀÀi𐀀ÀÀiÀiÀႠ𐀀ÀႠႠ|
- 𐀀iiii𐀀iႠÀÀႠiÀႠ𐀀ႠiÀiႠi𐀀iႠႠႠÀႠႠÀႠ𐀀iÀႠႠii𐀀iii𐀀À𐀀ႠႠႠÀiÀÀ𐀀iiiiႠႠႠi𐀀𐀀iiႠ𐀀ႠiႠ𐀀i𐀀𐀀ÀÀႠႠ𐀀iႠiႠÀÀÀႠႠÀ𐀀iÀႠႠႠႠÀ𐀀iÀ|
- ÀÀÀ𐀀ÀÀႠiႠÀiiÀÀ𐀀Ⴀ𐀀Àii𐀀ႠႠ𐀀iÀÀ𐀀𐀀Ⴀ𐀀iႠ𐀀iႠÀႠႠ𐀀ÀÀ𐀀𐀀À𐀀ÀႠiႠÀiÀ𐀀iÀi𐀀ႠiႠ𐀀i𐀀iiႠႠiႠÀÀ𐀀Ⴀ𐀀ႠႠ𐀀𐀀𐀀ႠႠiႠႠ𐀀ႠႠႠ𐀀ÀiႠႠi𐀀iÀÀÀ|
- ÀÀႠiiÀÀÀႠÀ𐀀𐀀iÀÀÀiÀi𐀀iႠiႠ𐀀iÀÀÀ𐀀𐀀𐀀iÀiÀÀiÀÀi𐀀i𐀀𐀀ႠÀ𐀀ii𐀀𐀀Ⴀ𐀀À𐀀iÀÀႠ𐀀iÀÀÀႠÀÀ𐀀𐀀iႠ𐀀ႠiÀႠi𐀀𐀀𐀀iႠ𐀀ႠႠႠi𐀀𐀀ÀÀÀ𐀀ÀႠÀiii|
- iiႠi𐀀ÀÀiႠ𐀀𐀀ႠႠÀ𐀀𐀀iÀႠiႠÀႠÀiÀiႠÀ𐀀𐀀ႠiÀႠႠႠႠ𐀀iiႠÀÀ𐀀ÀiÀ𐀀Ⴀ𐀀ÀiႠႠ𐀀À𐀀ႠiiႠiႠ𐀀iႠ𐀀ÀÀ𐀀ÀÀiႠÀi𐀀ÀႠii𐀀𐀀𐀀ÀÀ𐀀iႠ𐀀iႠÀႠႠiii𐀀|
- iÀiႠÀi𐀀À𐀀𐀀iiÀ𐀀𐀀𐀀Ài𐀀𐀀ႠÀÀ𐀀ii𐀀𐀀i𐀀ii𐀀Ⴀ𐀀𐀀𐀀ႠÀႠ𐀀ÀႠ𐀀iÀÀÀႠÀÀ𐀀𐀀iႠႠiÀ𐀀ÀႠ𐀀iiÀÀiႠႠႠႠ𐀀Ⴀ𐀀Ⴀ𐀀À𐀀iႠႠiႠ𐀀iiii𐀀Ⴀi𐀀ÀiႠ𐀀ÀÀii|
- ႠÀii𐀀ÀႠႠ𐀀𐀀i𐀀iiႠ𐀀i𐀀Ⴀ𐀀À𐀀𐀀ႠႠiiÀiiÀi𐀀ii𐀀𐀀iiiÀiiႠiÀ𐀀𐀀ÀÀÀ𐀀ÀႠ𐀀Ài𐀀À𐀀ÀiiÀÀ𐀀Ⴀ𐀀ႠႠiiÀÀႠ𐀀𐀀i𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀ႠiႠ𐀀ႠႠÀ𐀀ႠÀÀÀÀÀ|
- iႠႠႠ𐀀ÀႠ𐀀𐀀ÀiÀ𐀀À𐀀iiÀ𐀀𐀀iႠiiႠႠÀiႠÀႠ𐀀ႠÀÀ𐀀iiÀ𐀀𐀀ႠÀiÀ𐀀iႠiႠÀႠiiiႠiiႠႠႠiiÀ𐀀iÀ𐀀iႠႠ𐀀Ⴀi𐀀ႠႠÀiႠ𐀀i𐀀𐀀𐀀iiႠiÀ𐀀ÀႠႠÀÀÀ𐀀i𐀀|
- ÀÀÀii𐀀ႠiႠႠiႠ𐀀ႠiႠ𐀀ÀÀi𐀀𐀀ÀÀႠႠiÀÀiÀႠÀ𐀀ႠÀႠ𐀀𐀀𐀀iႠiႠiႠ𐀀À𐀀𐀀ÀႠ𐀀𐀀iiÀႠ𐀀i𐀀𐀀𐀀iiႠÀ𐀀𐀀ÀiႠi𐀀ႠiÀ𐀀iÀႠÀiÀႠႠႠ𐀀ÀႠ𐀀Ⴀ𐀀À𐀀ႠÀiii|
- 𐀀ÀÀ𐀀𐀀iႠႠ𐀀ႠiႠii𐀀𐀀ႠႠÀÀႠÀႠiÀႠႠ𐀀ÀႠႠÀ𐀀ii𐀀𐀀𐀀ii𐀀𐀀Ⴀii𐀀𐀀ÀÀÀiႠÀiiiiiႠiÀႠႠÀ𐀀𐀀ႠÀႠÀiiiႠ𐀀ÀÀႠÀi𐀀ႠiÀiÀi𐀀ÀႠiႠiiႠ𐀀iÀÀÀ|
- ႠiiÀii𐀀ÀÀi𐀀𐀀ႠÀÀႠႠ𐀀ii𐀀ÀႠiႠႠÀ𐀀𐀀ႠႠiႠႠii𐀀iÀiiiႠ𐀀iiႠÀÀÀÀ𐀀ႠÀi𐀀iႠi𐀀ii𐀀Ⴀ𐀀ႠÀiii𐀀𐀀ÀÀÀiiႠÀ𐀀Ⴀ𐀀ႠÀႠႠ𐀀𐀀𐀀𐀀𐀀À𐀀ÀႠႠi𐀀ႠႠ|
- ÀႠ𐀀iiႠႠႠÀÀ𐀀iÀiÀ𐀀ႠႠÀiÀႠÀ𐀀ÀÀÀiႠ𐀀𐀀ႠÀ𐀀ÀႠÀÀ𐀀𐀀𐀀𐀀𐀀ÀÀ𐀀𐀀iÀႠႠiႠiÀiiiႠiÀÀiႠÀ𐀀𐀀Ài𐀀iႠÀ𐀀ႠÀÀÀ𐀀𐀀𐀀ÀႠiiႠ𐀀ÀႠÀÀ𐀀iÀႠÀႠ𐀀À𐀀|
- 𐀀Ⴀ𐀀ႠႠႠႠႠႠႠiiႠ𐀀ÀÀ𐀀iÀႠiႠÀÀႠÀ𐀀i𐀀𐀀Ⴀ𐀀ႠႠÀႠႠ𐀀ႠႠ𐀀𐀀ÀႠႠiÀÀÀÀÀiႠÀ𐀀ႠÀÀ𐀀iÀi𐀀iႠ𐀀Ⴀ𐀀Ⴀii𐀀iႠႠႠႠႠႠi𐀀iÀÀ𐀀ႠÀiÀႠiÀ𐀀𐀀ii𐀀𐀀𐀀À|
- iႠi𐀀ÀႠi𐀀Ⴀ𐀀Àiii𐀀Ⴀii𐀀Ⴀii𐀀𐀀Ⴀ𐀀ႠÀÀii𐀀ႠႠ𐀀i𐀀𐀀ႠiiႠÀÀiႠÀiႠႠÀႠÀÀiÀi𐀀iႠႠ𐀀ႠÀ𐀀iႠÀÀ𐀀i𐀀𐀀ÀiႠႠÀiÀiiiႠႠႠ𐀀À𐀀ÀÀiÀÀႠÀႠ𐀀ÀႠ|
- 𐀀𐀀Ⴀ𐀀ႠÀÀ𐀀iiÀi𐀀𐀀iiÀÀ𐀀𐀀𐀀iႠ𐀀À𐀀iႠႠႠÀႠiÀÀiÀႠiiiÀiÀÀႠႠႠÀÀႠႠiÀiႠႠႠႠÀiÀႠiÀ𐀀À𐀀À𐀀𐀀iiiႠ𐀀𐀀𐀀ÀႠ𐀀ÀiÀÀiႠÀÀႠႠÀႠiiႠi𐀀i𐀀|
- iÀiiႠiiiiႠÀ𐀀ÀÀÀiÀi𐀀iiiႠ𐀀𐀀ႠÀiႠÀႠiႠÀiႠÀႠiÀႠÀႠÀÀÀÀiÀႠi𐀀ႠiႠi𐀀Ⴀ𐀀À𐀀i𐀀𐀀ႠiiÀႠ𐀀ႠÀႠႠႠii𐀀𐀀iiiiii𐀀À𐀀iÀiiÀႠÀႠiႠi𐀀|
- À𐀀i𐀀ႠÀiႠ𐀀ႠÀႠ𐀀𐀀ႠႠiႠiiiႠÀႠÀႠႠÀ𐀀𐀀Ⴀ𐀀𐀀i𐀀ႠÀ𐀀iႠႠiႠiႠ𐀀Ⴀiii𐀀𐀀À𐀀Ⴀ𐀀ႠÀÀႠÀ𐀀iÀႠÀiႠÀÀ𐀀Ⴀii𐀀ႠiiiiႠÀ𐀀Ài𐀀𐀀ႠiႠÀÀ𐀀𐀀ႠiႠႠÀÀ|
- Ⴀ𐀀ÀႠႠ𐀀𐀀iႠႠ𐀀iÀÀiÀ𐀀ႠÀ𐀀𐀀𐀀𐀀iႠ𐀀À𐀀ႠႠ𐀀Ⴀ𐀀𐀀iႠiႠႠ𐀀ႠႠÀÀႠႠÀÀႠ𐀀𐀀ႠÀÀii𐀀𐀀𐀀ÀÀႠ𐀀i𐀀Ⴀ𐀀iiiÀÀÀႠiÀiÀ𐀀ii𐀀𐀀iႠႠႠii𐀀iiႠႠi𐀀ÀÀ𐀀i|
- 𐀀ÀÀ𐀀𐀀ႠÀ𐀀ႠႠႠÀ𐀀Ⴀ𐀀ii𐀀Ⴀ𐀀𐀀ႠႠ𐀀À𐀀𐀀𐀀ႠiႠႠႠ𐀀ႠÀi𐀀𐀀Ⴀ𐀀Ài𐀀ႠÀÀi𐀀À𐀀iႠiႠႠ𐀀iiÀiႠႠÀ𐀀À𐀀iiႠႠႠႠ𐀀ÀÀႠႠႠiÀႠ𐀀i𐀀i𐀀iiÀ𐀀i𐀀ႠiÀÀÀiÀ|
- Ⴀii𐀀i𐀀ႠiÀiiÀÀÀ𐀀Àii𐀀ႠÀႠi𐀀ႠႠiႠႠi𐀀i𐀀𐀀iႠႠ𐀀𐀀iႠ𐀀iႠႠ𐀀ÀiiႠiႠiii𐀀ÀÀÀi𐀀ႠiÀႠႠႠÀႠႠႠႠႠႠÀiiÀႠi𐀀ÀÀiÀႠ𐀀ÀiႠႠÀ𐀀𐀀iiÀ𐀀𐀀À|
- iႠႠiႠiiႠÀÀႠ𐀀iÀÀiÀ𐀀iiႠÀ𐀀i𐀀ႠႠ𐀀iႠႠ𐀀À𐀀𐀀iiႠႠႠ𐀀ႠiႠi𐀀iႠ𐀀ႠႠÀiႠ𐀀𐀀Ⴀ𐀀Ⴀi𐀀iႠႠÀ𐀀À𐀀ÀႠႠ𐀀ÀႠႠi𐀀Ⴀi𐀀iÀႠÀ𐀀À𐀀ႠÀ𐀀ႠÀÀi𐀀Ⴀ𐀀iiÀ|
- ႠႠႠ𐀀ႠiÀႠႠiiiiiiႠi𐀀i𐀀ႠÀ𐀀i𐀀𐀀ႠႠÀႠi𐀀ÀÀÀÀႠ𐀀ႠႠ𐀀i𐀀iiÀ𐀀Ài𐀀𐀀i𐀀i𐀀𐀀ÀႠႠႠii𐀀ÀiiÀiႠiႠ𐀀iiႠႠႠႠ𐀀i𐀀ii𐀀iiÀÀ𐀀𐀀ÀႠ𐀀ÀႠ𐀀iÀ𐀀𐀀|
- iႠÀiႠii𐀀𐀀ÀiႠႠiiÀ𐀀ÀÀ𐀀𐀀ႠÀႠ𐀀iႠiiႠiiÀi𐀀ႠႠႠiÀi𐀀𐀀ÀႠÀÀႠi𐀀iÀႠÀႠÀ𐀀𐀀À𐀀𐀀À𐀀ႠiÀÀi𐀀iÀÀ𐀀ÀႠႠႠi𐀀iႠႠi𐀀iiႠႠႠÀiÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀À|
- ÀiႠÀÀႠÀÀÀႠႠÀႠii𐀀i𐀀i𐀀iiႠiÀiÀÀÀႠႠiႠiiÀÀÀႠÀႠÀÀÀႠii𐀀Ⴀ𐀀Ⴀi𐀀ÀႠႠiÀÀႠi𐀀Ⴀ𐀀𐀀ÀႠႠ𐀀iႠႠ𐀀iÀiÀÀႠÀÀ𐀀i𐀀𐀀ÀႠiÀႠႠ𐀀𐀀ÀႠႠiႠÀi|
- ÀiႠÀiiiÀႠ𐀀𐀀iႠ𐀀𐀀iÀÀÀႠÀႠiÀiÀi𐀀Ⴀ𐀀À𐀀iiႠ𐀀ÀiÀႠႠ𐀀iiiႠ𐀀Ài𐀀𐀀𐀀𐀀𐀀𐀀𐀀ÀႠÀ𐀀ÀiÀ𐀀ÀÀ𐀀iႠႠ𐀀Ⴀ𐀀i𐀀𐀀iii𐀀𐀀𐀀𐀀ႠႠi𐀀ii𐀀𐀀ႠႠႠ𐀀ÀiႠÀႠ|
- À𐀀ႠÀ𐀀𐀀𐀀À𐀀ÀiÀႠiiႠႠÀႠႠiႠÀÀ𐀀𐀀i𐀀𐀀𐀀ႠiÀႠÀÀ𐀀𐀀𐀀À𐀀ႠႠiÀiÀi𐀀ႠiÀiiႠÀ𐀀ÀiiiႠႠiႠ𐀀ႠiÀ𐀀ÀႠÀÀi𐀀ႠiႠiiႠiÀiiႠÀႠiiÀi𐀀Ⴀ𐀀𐀀iႠi|
- ÀÀ𐀀iÀÀÀ𐀀Ⴀ𐀀𐀀ÀႠႠ𐀀Ⴀ𐀀Ⴀ𐀀ႠÀ𐀀i𐀀ÀÀiÀÀ𐀀À𐀀𐀀𐀀iÀiႠiiÀႠÀiႠii𐀀𐀀iÀii𐀀Ⴀ𐀀ႠÀႠiႠiႠ𐀀ÀႠÀ𐀀i𐀀iႠႠ𐀀ႠႠႠÀÀÀii𐀀Ⴀ𐀀𐀀i𐀀i𐀀𐀀iႠi𐀀À𐀀𐀀^Ⴀ |
+ Ⱡ𐀀ii𐀀ⱠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⱡ𐀀ⱠiiⱠ𐀀iii𐀀ÀⱠÀⱠⱠÀiiÀⱠÀiⱠi𐀀ÀÀ𐀀𐀀Ⱡ𐀀𐀀Ⱡ𐀀iÀⱠ𐀀i𐀀ÀÀⱠiiÀ𐀀Ⱡ𐀀À𐀀ii𐀀ÀÀ𐀀ÀⱠⱠ𐀀𐀀𐀀𐀀ii𐀀ⱠⱠiⱠⱠ𐀀ⱠÀ𐀀ÀÀiiⱠ|
+ 𐀀𐀀ÀⱠÀ𐀀𐀀iⱠÀ𐀀𐀀i𐀀𐀀𐀀𐀀ⱠⱠⱠiiiiÀÀ𐀀ⱠÀⱠÀi𐀀i𐀀ⱠⱠÀiÀⱠi𐀀𐀀À𐀀iÀ𐀀ÀÀÀ𐀀𐀀iÀⱠ𐀀𐀀Ⱡ𐀀𐀀ÀiⱠiiⱠⱠÀÀÀÀi𐀀ÀÀⱠ𐀀Àiii𐀀iⱠⱠⱠi𐀀ⱠÀi𐀀iⱠ𐀀ⱠiⱠ|
+ ⱠⱠⱠÀi𐀀ÀiⱠÀiⱠiⱠ𐀀ÀiiiÀiiÀÀⱠ𐀀À𐀀ÀⱠÀÀÀⱠ𐀀iⱠⱠ𐀀iⱠÀ𐀀À𐀀ⱠiÀÀÀÀiiÀiÀiÀⱠiÀi𐀀iÀÀⱠ𐀀𐀀ⱠÀ𐀀iⱠ𐀀i𐀀i𐀀ÀⱠi𐀀iⱠÀÀiiⱠ𐀀𐀀ⱠiÀiÀiiÀ|
+ iⱠ𐀀iiⱠÀⱠ𐀀iÀÀÀⱠÀiÀ𐀀𐀀𐀀ÀⱠiⱠiÀiⱠiⱠ𐀀𐀀ⱠⱠi𐀀ⱠⱠ𐀀𐀀Ⱡ𐀀ÀiiⱠÀⱠⱠ𐀀iÀiÀÀ𐀀𐀀𐀀ⱠⱠ𐀀iii𐀀À𐀀Ⱡ𐀀iⱠii𐀀i𐀀i𐀀ÀiÀi𐀀Ⱡ𐀀𐀀i𐀀iÀÀiⱠ𐀀Ⱡ𐀀ÀÀÀÀ|
+ 𐀀ÀⱠiÀⱠÀÀÀⱠ𐀀À𐀀𐀀𐀀i𐀀Ài𐀀𐀀iⱠ𐀀𐀀ÀⱠ𐀀𐀀À𐀀𐀀ⱠÀÀⱠⱠⱠiⱠÀÀii𐀀Ⱡii𐀀Ⱡi𐀀Ài𐀀Ⱡ𐀀𐀀𐀀𐀀iÀ𐀀ⱠÀiÀ𐀀ⱠⱠi𐀀i𐀀ⱠⱠÀⱠ𐀀ⱠÀⱠ𐀀ÀⱠiiⱠ𐀀𐀀Ⱡ𐀀Ⱡ𐀀𐀀À𐀀𐀀Ài|
+ iÀÀiÀÀiⱠÀiiⱠiÀiiiÀⱠÀ𐀀Ài𐀀iiÀÀÀiⱠⱠiiⱠ𐀀iÀ𐀀𐀀𐀀ⱠÀÀ𐀀iiⱠ𐀀Ⱡ𐀀ⱠÀⱠ𐀀Ài𐀀i𐀀ÀiÀⱠⱠ𐀀ÀⱠiiⱠÀ𐀀𐀀Ⱡii𐀀𐀀i𐀀𐀀ÀÀiÀ𐀀i𐀀𐀀i𐀀ÀiiiⱠÀÀÀ|
+ ÀÀⱠⱠÀⱠⱠÀiiiÀⱠÀⱠⱠi𐀀ⱠÀ𐀀iⱠÀ𐀀ⱠÀiiÀÀÀÀⱠ𐀀𐀀À𐀀iiÀ𐀀𐀀iⱠⱠ𐀀iiⱠⱠ𐀀ÀÀÀ𐀀Ⱡ𐀀À𐀀ÀⱠi𐀀ⱠÀⱠÀⱠⱠiÀ𐀀iÀÀⱠ𐀀ⱠⱠ𐀀𐀀iⱠⱠⱠ𐀀𐀀À𐀀iÀÀi𐀀iⱠⱠ𐀀𐀀|
+ ÀÀiiⱠÀ𐀀Ài𐀀iiÀ𐀀iÀⱠÀÀi𐀀𐀀ⱠⱠ𐀀iiⱠⱠiÀ𐀀iⱠÀÀiⱠ𐀀ÀÀiiⱠⱠiÀÀ𐀀À𐀀iⱠÀÀⱠiⱠ𐀀iⱠ𐀀ÀiiⱠÀÀⱠÀÀiÀ𐀀𐀀ⱠⱠÀⱠÀÀⱠ𐀀À𐀀ⱠⱠi𐀀À𐀀𐀀ⱠⱠ𐀀iⱠi𐀀Ⱡ|
+ ÀÀⱠ𐀀𐀀ÀⱠⱠⱠÀⱠiⱠⱠⱠÀiiiⱠiiiÀi𐀀𐀀iⱠⱠiiÀiⱠⱠⱠÀÀiii𐀀ÀiⱠÀi𐀀i𐀀𐀀ÀiÀiÀÀiÀ𐀀iÀÀÀiÀⱠⱠÀiÀ𐀀À𐀀iⱠÀⱠ𐀀ii𐀀𐀀iⱠiÀiⱠ𐀀Ⱡ𐀀Ⱡ𐀀𐀀ⱠiÀⱠ|
+ ÀⱠii𐀀i𐀀iÀÀÀÀÀ𐀀ÀiÀ𐀀𐀀ÀⱠiiiiiÀⱠ𐀀ÀÀÀⱠi𐀀iⱠiⱠiÀⱠⱠ𐀀𐀀ii𐀀𐀀iÀÀÀⱠiiÀ𐀀iÀiÀ𐀀Ⱡ𐀀Ⱡ𐀀i𐀀iÀⱠiÀ𐀀i𐀀ⱠiⱠÀ𐀀iⱠÀⱠiⱠiÀÀÀÀ𐀀Ⱡ𐀀iⱠⱠÀ|
+ 𐀀Ⱡ𐀀ⱠÀ𐀀iⱠiÀi𐀀i𐀀𐀀𐀀ⱠÀⱠiÀⱠⱠ𐀀ÀiÀiⱠi𐀀ÀⱠi𐀀𐀀ÀÀi𐀀À𐀀ÀiiⱠ𐀀iⱠÀÀÀiii𐀀ⱠiⱠ𐀀𐀀𐀀ⱠⱠiⱠ𐀀𐀀iiⱠiiiii𐀀𐀀𐀀𐀀𐀀ÀÀÀÀi𐀀𐀀ⱠⱠ𐀀iÀ𐀀ÀiiÀii|
+ Ⱡ𐀀𐀀iⱠi𐀀Ⱡ𐀀ⱠⱠiÀÀⱠi𐀀ÀiⱠⱠⱠ𐀀ÀⱠiⱠⱠiⱠⱠÀ𐀀ⱠiiiÀ𐀀𐀀𐀀ÀÀiⱠi𐀀ⱠÀ𐀀ÀⱠÀi𐀀ⱠiÀⱠiiⱠⱠÀⱠiÀÀⱠÀÀÀÀ𐀀iÀⱠⱠiⱠⱠ𐀀ÀÀÀⱠiÀÀiÀⱠ𐀀iÀⱠiiⱠ𐀀|
+ iÀⱠi𐀀ÀⱠÀÀÀiÀÀⱠ𐀀À𐀀𐀀𐀀ⱠiiiⱠiiⱠ𐀀À𐀀iii𐀀À𐀀ⱠⱠi𐀀iiⱠÀ𐀀𐀀i𐀀ⱠÀ𐀀𐀀i𐀀ÀiⱠÀÀiÀÀi𐀀𐀀Ⱡ𐀀À𐀀i𐀀iÀⱠⱠⱠ𐀀ÀÀÀⱠÀÀⱠⱠⱠⱠⱠi𐀀𐀀iiⱠÀi𐀀𐀀ⱠⱠ|
+ iiⱠⱠⱠ𐀀ÀⱠ𐀀Àiii𐀀ÀÀii𐀀À𐀀iiⱠÀⱠiiⱠ𐀀Ⱡ𐀀ÀⱠ𐀀iÀiⱠiiÀÀiÀÀiⱠiⱠⱠiⱠ𐀀Ⱡi𐀀𐀀ÀÀÀÀiⱠii𐀀ⱠÀ𐀀Ⱡ𐀀i𐀀ⱠiⱠⱠ𐀀i𐀀ⱠiⱠⱠ𐀀ÀiⱠ𐀀iÀi𐀀𐀀ÀÀ𐀀𐀀i|
+ iÀÀi𐀀ÀÀÀ𐀀ÀⱠ𐀀ÀÀÀ𐀀𐀀𐀀iiⱠÀÀⱠ𐀀ⱠÀ𐀀iiiÀÀiiⱠ𐀀𐀀ⱠiiⱠiÀiⱠⱠÀ𐀀ⱠiⱠÀ𐀀i𐀀𐀀𐀀ⱠÀ𐀀Ⱡ𐀀ÀÀÀⱠⱠ𐀀𐀀À𐀀iiⱠⱠⱠⱠⱠÀ𐀀𐀀iⱠⱠiiÀiⱠÀiÀⱠiÀ𐀀À𐀀i|
+ ÀⱠⱠ𐀀ÀⱠi𐀀i𐀀ⱠÀÀÀiiiⱠ𐀀ⱠÀⱠÀÀⱠ𐀀ÀÀiÀiÀÀⱠÀⱠiⱠiÀ𐀀iⱠÀ𐀀𐀀𐀀ÀⱠⱠ𐀀ⱠⱠ𐀀ÀⱠⱠⱠii𐀀ÀÀÀÀiÀi𐀀ⱠⱠÀiⱠiⱠÀⱠ𐀀ÀiⱠⱠiÀⱠi𐀀𐀀𐀀ÀÀiiÀÀ𐀀𐀀ÀⱠ|
+ À𐀀ÀÀÀÀ𐀀ÀÀ𐀀i𐀀ⱠÀⱠiⱠÀÀ𐀀ⱠiÀÀiⱠÀⱠⱠÀ𐀀iÀ𐀀i𐀀𐀀ⱠÀÀÀⱠⱠiÀiiiiⱠⱠi𐀀ÀⱠi𐀀i𐀀i𐀀𐀀𐀀ⱠÀⱠiiiÀi𐀀ÀⱠi𐀀iiÀÀⱠÀÀⱠiiÀ𐀀À𐀀ÀÀ𐀀ⱠÀⱠÀ𐀀iⱠ|
+ Ⱡ𐀀ÀiⱠ𐀀iiÀ𐀀À𐀀𐀀À𐀀ÀÀiiiiiiⱠⱠiiÀi𐀀iÀ𐀀ÀÀiⱠ𐀀ÀⱠÀÀÀiÀⱠⱠ𐀀À𐀀𐀀iÀ𐀀𐀀ii𐀀ⱠÀ𐀀ii𐀀𐀀iⱠÀ𐀀ÀÀÀiÀii𐀀iⱠ𐀀ⱠⱠ𐀀ÀⱠiiiⱠⱠⱠi𐀀ÀÀiÀ𐀀𐀀À|
+ ⱠiÀ𐀀ⱠÀ𐀀iÀⱠi𐀀ÀiiiÀÀ𐀀ÀⱠ𐀀ⱠⱠⱠÀⱠ𐀀ⱠÀiiÀi𐀀i𐀀𐀀iⱠÀⱠiⱠiiÀÀ𐀀𐀀𐀀Ài𐀀𐀀iiⱠiⱠÀⱠ𐀀iiⱠÀi𐀀ⱠⱠⱠi𐀀ÀÀiÀⱠ𐀀𐀀ÀiⱠÀiiÀÀⱠ𐀀ⱠiiÀⱠⱠÀiÀ|
+ i𐀀𐀀Ài𐀀𐀀ⱠÀiÀÀⱠⱠi𐀀𐀀ÀÀⱠÀÀⱠⱠⱠⱠ𐀀ÀiⱠiiⱠⱠÀiiiⱠⱠÀ𐀀Ⱡii𐀀i𐀀ⱠÀ𐀀ÀÀⱠⱠiÀiÀÀiⱠÀiiiiÀⱠiiÀⱠⱠⱠÀÀi𐀀À𐀀ⱠⱠ𐀀Ⱡi𐀀ⱠⱠ𐀀𐀀𐀀iⱠÀÀⱠ𐀀ⱠⱠ|
+ iÀÀⱠⱠⱠiiÀii𐀀𐀀ⱠÀiii𐀀ⱠÀ𐀀ⱠiiÀ𐀀iⱠ𐀀ÀⱠ𐀀Ⱡ𐀀𐀀Ⱡi𐀀ÀiⱠÀⱠ𐀀ÀÀi𐀀ⱠiÀÀÀÀⱠÀⱠiⱠiⱠⱠ𐀀𐀀ÀⱠÀⱠ𐀀𐀀ÀÀ𐀀ⱠⱠ𐀀𐀀i𐀀Ⱡii𐀀iii𐀀Ⱡi𐀀Ⱡ𐀀Ⱡi𐀀𐀀ÀⱠi|
+ À𐀀𐀀ÀⱠÀiiiÀiiÀⱠi𐀀iiiⱠiiⱠÀÀ𐀀ⱠÀ𐀀ⱠⱠⱠ𐀀i𐀀ÀÀiⱠiiiⱠiiÀÀiⱠ𐀀À𐀀i𐀀Ⱡ𐀀ⱠÀiⱠⱠ𐀀ÀⱠⱠ𐀀𐀀ÀiⱠÀÀⱠiiⱠi𐀀i𐀀ⱠÀÀiⱠ𐀀Ⱡ𐀀iⱠⱠ𐀀ÀÀÀiÀÀii|
+ ⱠÀ𐀀𐀀ⱠiⱠÀ𐀀ÀÀÀⱠⱠÀⱠ𐀀ÀiⱠii𐀀𐀀ÀⱠ𐀀iÀÀiiÀiÀÀ𐀀iⱠÀiÀ𐀀𐀀À𐀀Ⱡ𐀀𐀀iiⱠⱠ𐀀ⱠÀⱠÀiÀiÀ𐀀iⱠⱠiÀi𐀀Ⱡ𐀀iiiÀⱠ𐀀ÀⱠiÀiiiⱠiiÀⱠÀiⱠiÀÀiⱠÀi|
+ 𐀀𐀀ÀÀi𐀀À𐀀𐀀ÀⱠⱠⱠ𐀀À𐀀𐀀À𐀀ÀiⱠⱠ𐀀𐀀ⱠÀiⱠiⱠÀ𐀀ÀiⱠiiii𐀀iⱠiⱠÀ𐀀ÀⱠ𐀀𐀀iÀⱠiÀi𐀀𐀀ⱠÀiⱠiÀⱠⱠ𐀀iⱠ𐀀ÀⱠÀii𐀀ⱠÀⱠii𐀀Ⱡ𐀀ÀÀi𐀀ÀÀiÀiÀⱠ𐀀ÀⱠⱠ|
+ 𐀀iiii𐀀iⱠÀÀⱠiÀⱠ𐀀ⱠiÀiⱠi𐀀iⱠⱠⱠÀⱠⱠÀⱠ𐀀iÀⱠⱠii𐀀iii𐀀À𐀀ⱠⱠⱠÀiÀÀ𐀀iiiiⱠⱠⱠi𐀀𐀀iiⱠ𐀀ⱠiⱠ𐀀i𐀀𐀀ÀÀⱠⱠ𐀀iⱠiⱠÀÀÀⱠⱠÀ𐀀iÀⱠⱠⱠⱠÀ𐀀iÀ|
+ ÀÀÀ𐀀ÀÀⱠiⱠÀiiÀÀ𐀀Ⱡ𐀀Àii𐀀ⱠⱠ𐀀iÀÀ𐀀𐀀Ⱡ𐀀iⱠ𐀀iⱠÀⱠⱠ𐀀ÀÀ𐀀𐀀À𐀀ÀⱠiⱠÀiÀ𐀀iÀi𐀀ⱠiⱠ𐀀i𐀀iiⱠⱠiⱠÀÀ𐀀Ⱡ𐀀ⱠⱠ𐀀𐀀𐀀ⱠⱠiⱠⱠ𐀀ⱠⱠⱠ𐀀ÀiⱠⱠi𐀀iÀÀÀ|
+ ÀÀⱠiiÀÀÀⱠÀ𐀀𐀀iÀÀÀiÀi𐀀iⱠiⱠ𐀀iÀÀÀ𐀀𐀀𐀀iÀiÀÀiÀÀi𐀀i𐀀𐀀ⱠÀ𐀀ii𐀀𐀀Ⱡ𐀀À𐀀iÀÀⱠ𐀀iÀÀÀⱠÀÀ𐀀𐀀iⱠ𐀀ⱠiÀⱠi𐀀𐀀𐀀iⱠ𐀀ⱠⱠⱠi𐀀𐀀ÀÀÀ𐀀ÀⱠÀiii|
+ iiⱠi𐀀ÀÀiⱠ𐀀𐀀ⱠⱠÀ𐀀𐀀iÀⱠiⱠÀⱠÀiÀiⱠÀ𐀀𐀀ⱠiÀⱠⱠⱠⱠ𐀀iiⱠÀÀ𐀀ÀiÀ𐀀Ⱡ𐀀ÀiⱠⱠ𐀀À𐀀ⱠiiⱠiⱠ𐀀iⱠ𐀀ÀÀ𐀀ÀÀiⱠÀi𐀀ÀⱠii𐀀𐀀𐀀ÀÀ𐀀iⱠ𐀀iⱠÀⱠⱠiii𐀀|
+ iÀiⱠÀi𐀀À𐀀𐀀iiÀ𐀀𐀀𐀀Ài𐀀𐀀ⱠÀÀ𐀀ii𐀀𐀀i𐀀ii𐀀Ⱡ𐀀𐀀𐀀ⱠÀⱠ𐀀ÀⱠ𐀀iÀÀÀⱠÀÀ𐀀𐀀iⱠⱠiÀ𐀀ÀⱠ𐀀iiÀÀiⱠⱠⱠⱠ𐀀Ⱡ𐀀Ⱡ𐀀À𐀀iⱠⱠiⱠ𐀀iiii𐀀Ⱡi𐀀ÀiⱠ𐀀ÀÀii|
+ ⱠÀii𐀀ÀⱠⱠ𐀀𐀀i𐀀iiⱠ𐀀i𐀀Ⱡ𐀀À𐀀𐀀ⱠⱠiiÀiiÀi𐀀ii𐀀𐀀iiiÀiiⱠiÀ𐀀𐀀ÀÀÀ𐀀ÀⱠ𐀀Ài𐀀À𐀀ÀiiÀÀ𐀀Ⱡ𐀀ⱠⱠiiÀÀⱠ𐀀𐀀i𐀀𐀀ⱠⱠ𐀀𐀀𐀀𐀀ⱠiⱠ𐀀ⱠⱠÀ𐀀ⱠÀÀÀÀÀ|
+ iⱠⱠⱠ𐀀ÀⱠ𐀀𐀀ÀiÀ𐀀À𐀀iiÀ𐀀𐀀iⱠiiⱠⱠÀiⱠÀⱠ𐀀ⱠÀÀ𐀀iiÀ𐀀𐀀ⱠÀiÀ𐀀iⱠiⱠÀⱠiiiⱠiiⱠⱠⱠiiÀ𐀀iÀ𐀀iⱠⱠ𐀀Ⱡi𐀀ⱠⱠÀiⱠ𐀀i𐀀𐀀𐀀iiⱠiÀ𐀀ÀⱠⱠÀÀÀ𐀀i𐀀|
+ ÀÀÀii𐀀ⱠiⱠⱠiⱠ𐀀ⱠiⱠ𐀀ÀÀi𐀀𐀀ÀÀⱠⱠiÀÀiÀⱠÀ𐀀ⱠÀⱠ𐀀𐀀𐀀iⱠiⱠiⱠ𐀀À𐀀𐀀ÀⱠ𐀀𐀀iiÀⱠ𐀀i𐀀𐀀𐀀iiⱠÀ𐀀𐀀ÀiⱠi𐀀ⱠiÀ𐀀iÀⱠÀiÀⱠⱠⱠ𐀀ÀⱠ𐀀Ⱡ𐀀À𐀀ⱠÀiii|
+ 𐀀ÀÀ𐀀𐀀iⱠⱠ𐀀ⱠiⱠii𐀀𐀀ⱠⱠÀÀⱠÀⱠiÀⱠⱠ𐀀ÀⱠⱠÀ𐀀ii𐀀𐀀𐀀ii𐀀𐀀Ⱡii𐀀𐀀ÀÀÀiⱠÀiiiiiⱠiÀⱠⱠÀ𐀀𐀀ⱠÀⱠÀiiiⱠ𐀀ÀÀⱠÀi𐀀ⱠiÀiÀi𐀀ÀⱠiⱠiiⱠ𐀀iÀÀÀ|
+ ⱠiiÀii𐀀ÀÀi𐀀𐀀ⱠÀÀⱠⱠ𐀀ii𐀀ÀⱠiⱠⱠÀ𐀀𐀀ⱠⱠiⱠⱠii𐀀iÀiiiⱠ𐀀iiⱠÀÀÀÀ𐀀ⱠÀi𐀀iⱠi𐀀ii𐀀Ⱡ𐀀ⱠÀiii𐀀𐀀ÀÀÀiiⱠÀ𐀀Ⱡ𐀀ⱠÀⱠⱠ𐀀𐀀𐀀𐀀𐀀À𐀀ÀⱠⱠi𐀀ⱠⱠ|
+ ÀⱠ𐀀iiⱠⱠⱠÀÀ𐀀iÀiÀ𐀀ⱠⱠÀiÀⱠÀ𐀀ÀÀÀiⱠ𐀀𐀀ⱠÀ𐀀ÀⱠÀÀ𐀀𐀀𐀀𐀀𐀀ÀÀ𐀀𐀀iÀⱠⱠiⱠiÀiiiⱠiÀÀiⱠÀ𐀀𐀀Ài𐀀iⱠÀ𐀀ⱠÀÀÀ𐀀𐀀𐀀ÀⱠiiⱠ𐀀ÀⱠÀÀ𐀀iÀⱠÀⱠ𐀀À𐀀|
+ 𐀀Ⱡ𐀀ⱠⱠⱠⱠⱠⱠⱠiiⱠ𐀀ÀÀ𐀀iÀⱠiⱠÀÀⱠÀ𐀀i𐀀𐀀Ⱡ𐀀ⱠⱠÀⱠⱠ𐀀ⱠⱠ𐀀𐀀ÀⱠⱠiÀÀÀÀÀiⱠÀ𐀀ⱠÀÀ𐀀iÀi𐀀iⱠ𐀀Ⱡ𐀀Ⱡii𐀀iⱠⱠⱠⱠⱠⱠi𐀀iÀÀ𐀀ⱠÀiÀⱠiÀ𐀀𐀀ii𐀀𐀀𐀀À|
+ iⱠi𐀀ÀⱠi𐀀Ⱡ𐀀Àiii𐀀Ⱡii𐀀Ⱡii𐀀𐀀Ⱡ𐀀ⱠÀÀii𐀀ⱠⱠ𐀀i𐀀𐀀ⱠiiⱠÀÀiⱠÀiⱠⱠÀⱠÀÀiÀi𐀀iⱠⱠ𐀀ⱠÀ𐀀iⱠÀÀ𐀀i𐀀𐀀ÀiⱠⱠÀiÀiiiⱠⱠⱠ𐀀À𐀀ÀÀiÀÀⱠÀⱠ𐀀ÀⱠ|
+ 𐀀𐀀Ⱡ𐀀ⱠÀÀ𐀀iiÀi𐀀𐀀iiÀÀ𐀀𐀀𐀀iⱠ𐀀À𐀀iⱠⱠⱠÀⱠiÀÀiÀⱠiiiÀiÀÀⱠⱠⱠÀÀⱠⱠiÀiⱠⱠⱠⱠÀiÀⱠiÀ𐀀À𐀀À𐀀𐀀iiiⱠ𐀀𐀀𐀀ÀⱠ𐀀ÀiÀÀiⱠÀÀⱠⱠÀⱠiiⱠi𐀀i𐀀|
+ iÀiiⱠiiiiⱠÀ𐀀ÀÀÀiÀi𐀀iiiⱠ𐀀𐀀ⱠÀiⱠÀⱠiⱠÀiⱠÀⱠiÀⱠÀⱠÀÀÀÀiÀⱠi𐀀ⱠiⱠi𐀀Ⱡ𐀀À𐀀i𐀀𐀀ⱠiiÀⱠ𐀀ⱠÀⱠⱠⱠii𐀀𐀀iiiiii𐀀À𐀀iÀiiÀⱠÀⱠiⱠi𐀀|
+ À𐀀i𐀀ⱠÀiⱠ𐀀ⱠÀⱠ𐀀𐀀ⱠⱠiⱠiiiⱠÀⱠÀⱠⱠÀ𐀀𐀀Ⱡ𐀀𐀀i𐀀ⱠÀ𐀀iⱠⱠiⱠiⱠ𐀀Ⱡiii𐀀𐀀À𐀀Ⱡ𐀀ⱠÀÀⱠÀ𐀀iÀⱠÀiⱠÀÀ𐀀Ⱡii𐀀ⱠiiiiⱠÀ𐀀Ài𐀀𐀀ⱠiⱠÀÀ𐀀𐀀ⱠiⱠⱠÀÀ|
+ Ⱡ𐀀ÀⱠⱠ𐀀𐀀iⱠⱠ𐀀iÀÀiÀ𐀀ⱠÀ𐀀𐀀𐀀𐀀iⱠ𐀀À𐀀ⱠⱠ𐀀Ⱡ𐀀𐀀iⱠiⱠⱠ𐀀ⱠⱠÀÀⱠⱠÀÀⱠ𐀀𐀀ⱠÀÀii𐀀𐀀𐀀ÀÀⱠ𐀀i𐀀Ⱡ𐀀iiiÀÀÀⱠiÀiÀ𐀀ii𐀀𐀀iⱠⱠⱠii𐀀iiⱠⱠi𐀀ÀÀ𐀀i|
+ 𐀀ÀÀ𐀀𐀀ⱠÀ𐀀ⱠⱠⱠÀ𐀀Ⱡ𐀀ii𐀀Ⱡ𐀀𐀀ⱠⱠ𐀀À𐀀𐀀𐀀ⱠiⱠⱠⱠ𐀀ⱠÀi𐀀𐀀Ⱡ𐀀Ài𐀀ⱠÀÀi𐀀À𐀀iⱠiⱠⱠ𐀀iiÀiⱠⱠÀ𐀀À𐀀iiⱠⱠⱠⱠ𐀀ÀÀⱠⱠⱠiÀⱠ𐀀i𐀀i𐀀iiÀ𐀀i𐀀ⱠiÀÀÀiÀ|
+ Ⱡii𐀀i𐀀ⱠiÀiiÀÀÀ𐀀Àii𐀀ⱠÀⱠi𐀀ⱠⱠiⱠⱠi𐀀i𐀀𐀀iⱠⱠ𐀀𐀀iⱠ𐀀iⱠⱠ𐀀ÀiiⱠiⱠiii𐀀ÀÀÀi𐀀ⱠiÀⱠⱠⱠÀⱠⱠⱠⱠⱠⱠÀiiÀⱠi𐀀ÀÀiÀⱠ𐀀ÀiⱠⱠÀ𐀀𐀀iiÀ𐀀𐀀À|
+ iⱠⱠiⱠiiⱠÀÀⱠ𐀀iÀÀiÀ𐀀iiⱠÀ𐀀i𐀀ⱠⱠ𐀀iⱠⱠ𐀀À𐀀𐀀iiⱠⱠⱠ𐀀ⱠiⱠi𐀀iⱠ𐀀ⱠⱠÀiⱠ𐀀𐀀Ⱡ𐀀Ⱡi𐀀iⱠⱠÀ𐀀À𐀀ÀⱠⱠ𐀀ÀⱠⱠi𐀀Ⱡi𐀀iÀⱠÀ𐀀À𐀀ⱠÀ𐀀ⱠÀÀi𐀀Ⱡ𐀀iiÀ|
+ ⱠⱠⱠ𐀀ⱠiÀⱠⱠiiiiiiⱠi𐀀i𐀀ⱠÀ𐀀i𐀀𐀀ⱠⱠÀⱠi𐀀ÀÀÀÀⱠ𐀀ⱠⱠ𐀀i𐀀iiÀ𐀀Ài𐀀𐀀i𐀀i𐀀𐀀ÀⱠⱠⱠii𐀀ÀiiÀiⱠiⱠ𐀀iiⱠⱠⱠⱠ𐀀i𐀀ii𐀀iiÀÀ𐀀𐀀ÀⱠ𐀀ÀⱠ𐀀iÀ𐀀𐀀|
+ iⱠÀiⱠii𐀀𐀀ÀiⱠⱠiiÀ𐀀ÀÀ𐀀𐀀ⱠÀⱠ𐀀iⱠiiⱠiiÀi𐀀ⱠⱠⱠiÀi𐀀𐀀ÀⱠÀÀⱠi𐀀iÀⱠÀⱠÀ𐀀𐀀À𐀀𐀀À𐀀ⱠiÀÀi𐀀iÀÀ𐀀ÀⱠⱠⱠi𐀀iⱠⱠi𐀀iiⱠⱠⱠÀiÀ𐀀𐀀Ⱡ𐀀ÀÀ𐀀À|
+ ÀiⱠÀÀⱠÀÀÀⱠⱠÀⱠii𐀀i𐀀i𐀀iiⱠiÀiÀÀÀⱠⱠiⱠiiÀÀÀⱠÀⱠÀÀÀⱠii𐀀Ⱡ𐀀Ⱡi𐀀ÀⱠⱠiÀÀⱠi𐀀Ⱡ𐀀𐀀ÀⱠⱠ𐀀iⱠⱠ𐀀iÀiÀÀⱠÀÀ𐀀i𐀀𐀀ÀⱠiÀⱠⱠ𐀀𐀀ÀⱠⱠiⱠÀi|
+ ÀiⱠÀiiiÀⱠ𐀀𐀀iⱠ𐀀𐀀iÀÀÀⱠÀⱠiÀiÀi𐀀Ⱡ𐀀À𐀀iiⱠ𐀀ÀiÀⱠⱠ𐀀iiiⱠ𐀀Ài𐀀𐀀𐀀𐀀𐀀𐀀𐀀ÀⱠÀ𐀀ÀiÀ𐀀ÀÀ𐀀iⱠⱠ𐀀Ⱡ𐀀i𐀀𐀀iii𐀀𐀀𐀀𐀀ⱠⱠi𐀀ii𐀀𐀀ⱠⱠⱠ𐀀ÀiⱠÀⱠ|
+ À𐀀ⱠÀ𐀀𐀀𐀀À𐀀ÀiÀⱠiiⱠⱠÀⱠⱠiⱠÀÀ𐀀𐀀i𐀀𐀀𐀀ⱠiÀⱠÀÀ𐀀𐀀𐀀À𐀀ⱠⱠiÀiÀi𐀀ⱠiÀiiⱠÀ𐀀ÀiiiⱠⱠiⱠ𐀀ⱠiÀ𐀀ÀⱠÀÀi𐀀ⱠiⱠiiⱠiÀiiⱠÀⱠiiÀi𐀀Ⱡ𐀀𐀀iⱠi|
+ ÀÀ𐀀iÀÀÀ𐀀Ⱡ𐀀𐀀ÀⱠⱠ𐀀Ⱡ𐀀Ⱡ𐀀ⱠÀ𐀀i𐀀ÀÀiÀÀ𐀀À𐀀𐀀𐀀iÀiⱠiiÀⱠÀiⱠii𐀀𐀀iÀii𐀀Ⱡ𐀀ⱠÀⱠiⱠiⱠ𐀀ÀⱠÀ𐀀i𐀀iⱠⱠ𐀀ⱠⱠⱠÀÀÀii𐀀Ⱡ𐀀𐀀i𐀀i𐀀𐀀iⱠi𐀀À𐀀𐀀^Ⱡ |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
@@ -239,58 +254,59 @@ benchmarks({
{
ascii_results,
two_byte_results,
+ three_byte_results,
{ -- random
screen = [=[
- Ⴀ𐀀ii𐀀ႠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⴀ𐀀ႠiiႠ𐀀iii𐀀ÀႠÀႠႠÀiiÀႠÀiႠi𐀀ÀÀ𐀀𐀀Ⴀ𐀀𐀀Ⴀ𐀀iÀႠ𐀀i𐀀ÀÀႠiiÀ𐀀Ⴀ𐀀À𐀀ii𐀀ÀÀ𐀀ÀႠႠ𐀀𐀀𐀀𐀀ii𐀀ႠႠiႠႠ𐀀ႠÀ𐀀ÀÀiiႠ|
- iiႠ𐀀iÀÀiႠႠÀi𐀀ႠႠÀ𐀀𐀀ÀiiiiÀiႠ𐀀iႠÀiႠiႠႠÀiÀ𐀀ႠiiႠႠÀ𐀀Àii𐀀ႠÀႠiႠÀႠÀႠii𐀀Ài𐀀ႠႠ𐀀ÀÀÀi𐀀ÀÀÀ𐀀iႠ𐀀iႠÀ𐀀iႠi𐀀ÀiÀ𐀀ႠႠiÀ𐀀𐀀Ⴀi|
- iÀiiiႠÀÀ𐀀ႠႠႠi𐀀À𐀀𐀀iiiÀÀiiÀႠÀ𐀀À𐀀ႠႠ𐀀𐀀ႠႠႠi𐀀iiÀႠ𐀀ႠႠႠÀiႠiႠiÀÀÀi𐀀iႠ𐀀ÀÀiႠ𐀀iÀÀi𐀀i𐀀𐀀ÀiÀႠ𐀀𐀀iႠ𐀀ÀÀiÀÀႠ𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀Ⴀ𐀀𐀀|
- ÀႠiႠiÀ𐀀i𐀀ႠႠiႠႠÀ𐀀ÀÀÀÀ𐀀𐀀ÀႠႠ𐀀ႠÀÀiႠ𐀀i𐀀Ⴀ𐀀ÀႠi𐀀ႠÀႠÀ𐀀ႠႠ𐀀i𐀀iႠÀi𐀀i𐀀𐀀À𐀀iÀiႠႠႠ𐀀ÀiÀႠÀ𐀀ÀÀÀi𐀀𐀀𐀀ႠÀi𐀀𐀀À𐀀À𐀀𐀀iiႠiÀi𐀀i𐀀Ⴀ|
- Ⴀ𐀀i𐀀𐀀ÀiÀႠႠႠႠႠÀÀႠႠÀႠ𐀀ii𐀀ÀႠiႠiii𐀀i𐀀i𐀀𐀀𐀀À𐀀ii𐀀iÀiiiÀÀႠiiiႠiiႠÀ𐀀À𐀀𐀀ÀႠ𐀀iÀÀiiÀiÀ𐀀iႠi𐀀𐀀À𐀀ÀiiႠ𐀀iÀ𐀀𐀀iႠႠÀÀႠႠiiÀ|
- 𐀀ÀiႠႠÀ𐀀𐀀𐀀i𐀀i𐀀i𐀀ႠÀ𐀀ÀiiÀႠ𐀀ÀÀÀi𐀀ႠÀiÀႠi𐀀ႠÀiiÀÀÀiiiÀiႠႠiÀ𐀀ႠႠ𐀀iÀႠÀႠႠiÀÀႠÀႠÀÀii𐀀Ⴀi𐀀iiÀÀÀiႠ𐀀i𐀀𐀀i𐀀iiÀ𐀀𐀀𐀀ႠÀiႠ𐀀|
- i𐀀ÀႠiႠi𐀀ႠiႠ𐀀Ⴀi𐀀ႠÀ𐀀𐀀𐀀ႠÀiiiii𐀀Ⴀ𐀀iiiÀiiÀ𐀀𐀀𐀀À𐀀𐀀Ⴀ𐀀ႠÀ𐀀ႠႠႠiÀÀÀÀii𐀀i𐀀ÀiiႠÀiÀ𐀀iႠႠiÀႠii𐀀i𐀀Ⴀ𐀀𐀀iႠႠÀ𐀀ႠiiiႠႠÀÀ𐀀iÀႠ|
- Ⴀ𐀀𐀀ႠႠ𐀀À𐀀ÀႠ𐀀ÀႠÀ𐀀𐀀iႠႠÀÀiÀႠ𐀀ÀiÀႠi𐀀ႠÀ𐀀𐀀𐀀𐀀Ⴀ𐀀iႠÀ𐀀iÀ𐀀iÀ𐀀iÀÀႠi𐀀iÀႠi𐀀ႠiiႠÀ𐀀À𐀀ႠႠÀÀi𐀀ႠႠ𐀀iiႠÀiႠ𐀀𐀀𐀀𐀀ႠႠႠÀႠiÀႠiÀÀ𐀀À|
- ÀႠÀÀ𐀀i𐀀iႠÀÀÀႠ𐀀𐀀ÀႠÀÀiii𐀀𐀀iiÀiiႠÀÀႠiÀiÀÀ𐀀i𐀀i𐀀ႠiႠႠiႠÀiiÀႠ𐀀ႠႠÀiÀႠ𐀀𐀀iÀ𐀀Ⴀ𐀀iÀ𐀀ႠÀÀႠÀÀÀ𐀀𐀀i𐀀𐀀À𐀀𐀀ii𐀀À𐀀𐀀ႠÀ𐀀ႠႠႠ𐀀𐀀|
- ÀÀÀÀiႠiႠႠႠiႠ𐀀ႠÀÀÀ𐀀ÀÀiႠÀ𐀀ÀiႠÀႠÀႠႠÀÀႠiÀႠႠiiႠÀ𐀀ႠႠÀiႠ𐀀iÀႠ𐀀Ⴀ𐀀Ⴀ𐀀iႠÀႠi𐀀𐀀Ⴀ𐀀iÀ𐀀ÀႠ𐀀ÀÀႠ𐀀Ⴀi𐀀iႠÀ𐀀𐀀𐀀𐀀i𐀀i𐀀𐀀𐀀ÀႠiÀÀ𐀀i|
- 𐀀𐀀iiÀ𐀀ÀႠ𐀀𐀀𐀀𐀀𐀀Àiii𐀀𐀀𐀀Ⴀ𐀀𐀀i𐀀ÀÀ𐀀iiÀiiiiÀ𐀀iႠiႠiÀႠÀႠÀiႠႠႠႠႠႠႠႠႠÀiiႠiÀႠÀ𐀀iiႠÀႠiႠႠÀiႠ𐀀iႠ𐀀iiႠÀ𐀀𐀀Àii𐀀i𐀀ÀႠÀÀiÀÀ|
- 𐀀𐀀𐀀i𐀀iÀ𐀀𐀀iÀ𐀀Ài𐀀𐀀ႠႠ𐀀ႠÀi𐀀𐀀ÀÀiiiႠiႠ𐀀iႠÀႠÀ𐀀ႠÀ𐀀ႠiiiiÀiႠÀiiiႠႠÀ𐀀Ⴀ𐀀𐀀𐀀ÀႠiÀႠiiiiႠiႠ𐀀Ⴀ𐀀ÀႠÀii𐀀i𐀀Ⴀ𐀀À𐀀iႠႠ𐀀iႠiiii𐀀|
- iႠÀÀႠÀÀ𐀀𐀀𐀀iiiÀ𐀀À𐀀iÀÀi𐀀À𐀀ÀႠÀiÀii𐀀ႠႠii𐀀ႠႠ𐀀𐀀ÀႠ𐀀ÀÀ𐀀ÀÀÀÀi𐀀ÀႠ𐀀À𐀀ÀiiႠ𐀀ÀÀႠiႠ𐀀Ⴀ𐀀ÀÀ𐀀ႠႠ𐀀ႠႠÀ𐀀ႠႠႠ𐀀iiÀႠÀႠiႠi𐀀ii𐀀Ài|
- Ⴀ𐀀ႠÀ𐀀Ⴀi𐀀iÀ𐀀Ⴀ𐀀𐀀iÀiÀ𐀀iႠi𐀀ႠÀiႠႠiÀ𐀀iႠiÀႠi𐀀𐀀iႠiႠႠ𐀀ÀÀi𐀀iÀႠႠ𐀀ႠÀ𐀀𐀀𐀀ႠႠiÀÀiႠႠႠ𐀀𐀀ႠႠႠႠÀiႠ𐀀Ài𐀀iÀႠii𐀀ÀႠii𐀀Ⴀ𐀀ÀႠÀ𐀀Ⴀi|
- 𐀀ႠႠႠႠႠ𐀀Ⴀ𐀀ÀÀ𐀀𐀀iÀÀႠiÀiiႠÀiႠ𐀀𐀀ÀÀiÀႠ𐀀ÀÀႠ𐀀À𐀀À𐀀iႠႠÀiႠÀiiiႠÀiÀÀÀ𐀀iႠႠႠÀÀiႠႠႠ𐀀ii𐀀ii𐀀iႠႠii𐀀iႠÀi𐀀𐀀𐀀ii𐀀ÀႠႠiႠÀ𐀀ႠႠ|
- 𐀀iႠ𐀀ႠႠႠÀÀ𐀀iiiÀÀႠii𐀀i𐀀ÀÀႠ𐀀ႠႠiႠႠႠiႠ𐀀i𐀀À𐀀ႠႠႠi𐀀Ài𐀀ႠÀ𐀀ÀႠiiiiႠiiႠႠi𐀀𐀀ÀႠ𐀀ႠiÀÀ𐀀iiiႠÀiႠi𐀀À𐀀𐀀i𐀀𐀀iiႠ𐀀À𐀀iiႠႠÀ𐀀iႠ|
- ႠႠÀÀႠႠ𐀀iÀႠ𐀀iႠႠÀÀ𐀀ÀiႠ𐀀iÀႠႠႠ𐀀𐀀À𐀀ii𐀀𐀀À𐀀𐀀ÀႠႠႠÀႠiiႠ𐀀𐀀ii𐀀ႠႠÀႠiႠi𐀀ႠႠ𐀀i𐀀𐀀𐀀𐀀Ⴀ𐀀iÀiႠႠ𐀀À𐀀ÀႠႠႠÀÀ𐀀𐀀iÀႠi𐀀𐀀ÀiႠႠÀÀiÀÀ|
- 𐀀ႠÀiÀ𐀀ႠႠ𐀀i𐀀ႠႠ𐀀𐀀𐀀𐀀𐀀𐀀iႠÀႠႠiiÀႠ𐀀𐀀i𐀀ႠႠ𐀀ႠႠႠ𐀀𐀀iÀiÀႠ𐀀À𐀀À𐀀𐀀ႠiiiႠiiiႠiႠ𐀀ÀiiiႠ𐀀À𐀀ÀႠႠÀ𐀀À𐀀𐀀𐀀ႠiႠi𐀀ႠÀÀႠiiႠႠႠႠ𐀀ÀႠ𐀀𐀀|
- Ⴀ𐀀iÀÀႠ𐀀ႠႠÀႠÀ𐀀ႠႠÀiiÀÀÀႠ𐀀i𐀀Ⴀ𐀀ႠÀ𐀀𐀀iႠႠႠႠiiႠႠႠÀiႠÀiiႠÀ𐀀Ⴀ𐀀Ⴀi𐀀𐀀ႠÀ𐀀ÀႠႠ𐀀ÀႠÀႠ𐀀Ⴀ𐀀iႠi𐀀ÀႠႠii𐀀ÀႠÀ𐀀Ⴀ𐀀Ài𐀀À𐀀ÀÀႠiႠႠii𐀀|
- ÀႠႠႠÀႠႠii𐀀Ài𐀀Ⴀi𐀀i𐀀i𐀀Ⴀ𐀀ÀÀÀiÀÀi𐀀ÀÀÀ𐀀i𐀀iÀ𐀀𐀀ႠiÀi𐀀𐀀𐀀ႠÀiÀ𐀀𐀀iÀÀႠႠ𐀀ႠႠႠႠÀÀÀÀiiÀ𐀀iiႠႠႠi𐀀ႠÀi𐀀ÀႠႠÀ𐀀ႠiiႠ𐀀𐀀i𐀀Ⴀi𐀀𐀀À|
- ÀႠiiÀi𐀀ႠÀÀiÀႠi𐀀ÀႠႠ𐀀ႠiÀiiႠiiႠႠ𐀀ii𐀀i𐀀𐀀𐀀𐀀i𐀀ႠႠÀႠÀÀiÀÀÀႠÀႠႠÀÀႠ𐀀ÀÀ𐀀ÀÀႠÀ𐀀𐀀ÀiႠ𐀀ႠႠ𐀀iÀ𐀀Ⴀ𐀀iÀႠႠ𐀀iÀiiii𐀀ii𐀀𐀀ႠႠႠ𐀀Ⴀ|
- 𐀀ÀiÀႠÀiiÀÀiiႠႠႠi𐀀ÀÀiႠ𐀀iႠႠÀ𐀀𐀀𐀀ÀႠႠÀ𐀀ႠÀÀႠ𐀀ÀÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀ÀႠ𐀀ႠÀiÀ𐀀iႠ𐀀ႠႠ𐀀𐀀À𐀀iii𐀀iiႠÀႠiႠÀႠ𐀀Ⴀ𐀀i𐀀𐀀ÀႠႠi𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀À𐀀Ⴀ𐀀|
- 𐀀iiÀႠiႠiÀႠ𐀀i𐀀iii𐀀Ⴀ𐀀i𐀀iÀÀi𐀀Ⴀii𐀀ÀiÀiiiÀႠÀ𐀀ÀÀႠ𐀀Ⴀ𐀀iiÀi𐀀i𐀀𐀀i𐀀ႠiiiÀႠႠႠiiÀ𐀀À𐀀𐀀iÀ𐀀iႠÀႠÀÀi𐀀ႠiÀႠÀ𐀀𐀀iÀÀ𐀀i𐀀𐀀ÀÀႠ𐀀|
- ÀiÀႠႠႠႠii𐀀ÀÀ𐀀𐀀𐀀Ⴀi𐀀À𐀀ÀႠiiÀi𐀀Ⴀii𐀀iÀÀ𐀀ႠiႠ𐀀ႠiiiႠÀÀiÀÀÀÀ𐀀ႠႠii𐀀À𐀀ÀiÀi𐀀ÀÀi𐀀iႠiÀi𐀀ÀiÀi𐀀ÀiÀႠ𐀀i𐀀Ⴀi𐀀𐀀𐀀ႠႠ𐀀ႠÀႠÀႠi|
- i𐀀ႠiÀႠႠÀÀ𐀀𐀀ii𐀀ÀÀ𐀀iÀiÀႠÀiiii𐀀ÀiÀႠi𐀀i𐀀𐀀i𐀀𐀀iႠ𐀀iÀi𐀀ÀÀÀÀiႠiÀႠÀÀႠiiÀÀႠႠi𐀀iႠiiႠi𐀀Ⴀ𐀀𐀀ÀႠႠÀႠiႠႠÀ𐀀iiÀႠႠႠ𐀀𐀀Ⴀi𐀀Ⴀi|
- ii𐀀iÀÀÀÀÀÀiÀ𐀀À𐀀iiႠiႠႠi𐀀À𐀀ÀႠÀ𐀀ႠႠ𐀀𐀀𐀀iႠႠiiႠÀÀႠÀiiႠÀႠႠÀ𐀀𐀀Ⴀ𐀀ÀÀÀÀႠ𐀀𐀀𐀀ႠႠÀႠ𐀀ÀiႠiÀႠiÀÀ𐀀ii𐀀iiiÀႠÀႠႠ𐀀ႠÀiÀÀ𐀀ႠႠႠÀ|
- 𐀀𐀀À𐀀𐀀iÀႠ𐀀ႠiႠÀÀ𐀀iÀÀ𐀀À𐀀iÀÀႠႠÀiii𐀀À𐀀ÀႠÀႠႠÀႠႠi𐀀ÀÀÀi𐀀À𐀀ႠiÀi𐀀i𐀀i𐀀ÀiÀÀiÀÀ𐀀𐀀À𐀀ႠÀ𐀀ႠÀႠ𐀀ႠiÀ𐀀𐀀ÀiÀÀ𐀀𐀀𐀀À𐀀Ⴀi𐀀i𐀀i𐀀Ài|
- 𐀀𐀀iႠ𐀀i𐀀ÀႠႠÀ𐀀iÀ𐀀ÀiႠႠi𐀀iiႠÀ𐀀ÀiiÀႠ𐀀Ⴀ𐀀ÀÀiÀiႠi𐀀À𐀀𐀀iÀiÀiႠi𐀀ႠႠႠi𐀀À𐀀ÀႠႠ𐀀Ⴀ𐀀ÀÀႠiÀiÀ𐀀Ⴀ𐀀ÀႠiႠႠÀÀÀi𐀀i𐀀Ⴀi𐀀À𐀀ii𐀀ႠÀ𐀀Ⴀ|
- ႠiႠ𐀀iÀႠႠ𐀀i𐀀À𐀀iÀÀ𐀀𐀀ÀႠႠÀႠÀ𐀀iiiÀ𐀀i𐀀iÀ𐀀ႠႠ𐀀iÀႠ𐀀ႠÀi𐀀iiii𐀀iႠႠ𐀀ÀiÀ𐀀Àii𐀀Ⴀ𐀀𐀀ႠiÀii𐀀𐀀Ⴀ𐀀𐀀ႠiႠ𐀀iႠiႠi𐀀iiiႠႠႠi𐀀iiÀi𐀀Ⴀ|
- i𐀀i𐀀ÀÀÀ𐀀ÀiÀႠiÀiiႠÀÀÀiÀiiii𐀀i𐀀ÀÀiiiႠÀiÀႠÀiႠ𐀀iiႠiႠႠiÀi𐀀ႠႠ𐀀ÀႠiႠ𐀀ႠÀiiႠÀ𐀀ÀႠႠ𐀀ႠiÀi𐀀À𐀀𐀀iiÀ𐀀𐀀ÀiႠႠiႠ𐀀ÀႠiÀÀႠ𐀀i|
- Ài𐀀𐀀𐀀iÀi𐀀Ài𐀀À𐀀ႠႠ𐀀ႠÀiiÀႠ𐀀i𐀀i𐀀𐀀ႠiႠÀ𐀀𐀀Ⴀ𐀀iÀ𐀀ÀÀႠiႠႠiÀ𐀀iÀ𐀀ႠiÀÀÀÀႠiiÀ𐀀𐀀ÀႠႠiÀ𐀀iiÀ𐀀À𐀀iÀiÀÀ𐀀iÀiÀÀiiÀ𐀀ÀႠႠÀiiÀÀႠ|
- 𐀀ႠÀႠiႠႠÀ𐀀ÀiiÀ𐀀iÀႠႠႠႠiÀÀi𐀀iÀi𐀀iiiႠ𐀀iႠ𐀀𐀀𐀀𐀀ÀÀÀႠi𐀀iႠi𐀀ႠÀႠႠ𐀀𐀀À𐀀iiÀႠ𐀀𐀀ႠႠ𐀀𐀀ÀiႠÀÀÀႠ𐀀𐀀ÀiႠ𐀀𐀀iÀÀiÀ𐀀ႠÀi𐀀𐀀ႠႠႠႠ𐀀Ⴀi|
- iÀi𐀀ႠႠÀ𐀀𐀀i𐀀Àii𐀀ÀiÀÀiÀiÀÀ𐀀ÀÀ𐀀À𐀀ႠႠႠÀÀÀႠii𐀀ႠÀÀႠႠi𐀀ႠႠiႠႠ𐀀Ⴀ𐀀ÀiÀiiii𐀀ÀiႠÀiiiiႠႠiiႠÀÀÀႠÀႠ𐀀𐀀𐀀iiႠႠ𐀀ႠÀႠ𐀀iႠႠ𐀀𐀀Ⴀ|
- À𐀀À𐀀ႠႠႠiÀ𐀀ÀႠÀႠႠiiႠii𐀀ႠÀÀÀ𐀀iႠiiiiiÀ𐀀ÀÀiÀÀႠ𐀀𐀀iiႠi𐀀𐀀Ài𐀀ႠÀÀiႠႠႠႠ𐀀iႠiႠႠႠႠႠÀÀႠiiÀiႠ𐀀iÀiiႠiiii𐀀ii𐀀À𐀀ႠÀ𐀀Ⴀ𐀀ÀႠ|
- ÀႠÀiiiiႠiiႠiÀi𐀀𐀀ႠÀÀ𐀀Ài𐀀Ài𐀀ÀiႠÀ𐀀ႠႠႠႠ𐀀ႠiÀ𐀀iႠႠÀႠi𐀀Ài𐀀ႠiiႠ𐀀Ⴀii𐀀ÀÀÀႠ𐀀ÀÀÀႠÀiÀႠiႠiႠi𐀀𐀀À𐀀𐀀𐀀ÀႠiႠႠႠႠÀiiÀႠ𐀀À𐀀𐀀À|
- iÀ𐀀ÀႠႠÀiÀi𐀀Ài𐀀ÀiႠႠÀ𐀀iႠÀ𐀀i𐀀ÀiiÀÀÀႠÀÀႠ𐀀À𐀀À𐀀ÀÀÀÀႠi𐀀iႠÀ𐀀𐀀ÀႠiÀiႠႠiÀ𐀀ႠႠÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀ႠႠÀÀiÀi𐀀𐀀𐀀À𐀀ÀႠ𐀀iႠႠ𐀀𐀀𐀀i𐀀ႠÀ𐀀Ⴀ|
- Ⴀi𐀀ÀÀ𐀀ႠÀiÀi𐀀i𐀀ÀႠ𐀀Ⴀ𐀀ÀႠ𐀀ႠÀႠႠႠ𐀀𐀀ÀiiiiႠႠi𐀀ႠÀႠÀ𐀀Ⴀ𐀀i𐀀À𐀀𐀀𐀀Ⴀ𐀀ÀiÀÀႠႠiiႠiÀiႠႠÀiÀÀႠႠÀÀႠÀ𐀀ႠiႠ𐀀𐀀i𐀀i𐀀𐀀ÀႠÀႠႠႠÀÀiiÀ𐀀|
- ႠႠႠiiÀႠႠiÀႠ𐀀ÀiႠႠÀႠiÀႠႠÀÀi𐀀ÀÀiÀ𐀀𐀀i𐀀i𐀀iiÀÀiႠ𐀀Ⴀ𐀀𐀀𐀀ÀiiႠ𐀀Ài𐀀iiiiÀiႠႠii𐀀Ⴀi𐀀iႠႠ𐀀ÀÀႠ𐀀iÀႠႠႠiÀ𐀀𐀀iÀႠiႠÀ𐀀ÀႠÀiႠ𐀀À|
- 𐀀ႠႠႠiႠႠiiii𐀀𐀀i𐀀Àiiii𐀀À𐀀Ⴀi𐀀iႠ𐀀ႠiÀiÀႠi𐀀𐀀ÀiÀiiÀÀÀ𐀀𐀀i𐀀À𐀀ÀႠÀiiÀႠ𐀀ႠႠ𐀀𐀀Ⴀ𐀀ÀÀiÀ𐀀iႠ𐀀𐀀iÀÀႠi𐀀iႠiÀ𐀀ႠႠ𐀀ÀÀႠiÀ𐀀ÀႠႠÀႠ|
- Ⴀii𐀀𐀀ႠÀiႠႠÀÀ𐀀ÀÀÀÀÀÀÀႠiႠႠÀÀi𐀀ÀiႠÀ𐀀𐀀i𐀀ႠÀii𐀀Ⴀ𐀀𐀀À𐀀𐀀ÀiÀ𐀀i𐀀𐀀ႠÀiÀÀႠiiႠႠiႠÀiÀႠÀi𐀀iÀ𐀀À𐀀𐀀ႠႠi𐀀ႠÀiÀÀÀႠÀiÀÀႠiႠ𐀀iÀ|
- 𐀀ÀႠiÀႠႠႠÀÀႠÀႠ𐀀iiiiÀiÀÀႠ𐀀Ⴀiii𐀀𐀀iiႠiÀ𐀀𐀀i𐀀ÀiiÀႠ𐀀𐀀Ⴀ𐀀Ⴀ𐀀Ⴀii𐀀ႠiႠÀiႠႠÀÀÀႠÀ𐀀Ⴀ𐀀𐀀𐀀À𐀀Ⴀ𐀀Ⴀ𐀀ႠÀ𐀀ႠႠiႠ𐀀𐀀ÀiiiÀ𐀀ÀiÀiႠÀ𐀀À|
- i𐀀ႠiႠi𐀀ii𐀀𐀀iiiႠႠÀÀiiii𐀀ÀiႠႠÀi𐀀ÀÀÀÀiÀiiႠ𐀀ÀႠiႠႠiÀႠ𐀀ÀႠႠ𐀀ÀÀÀ𐀀ႠÀ𐀀À𐀀iႠi𐀀iÀÀi𐀀iÀÀiႠႠ𐀀ႠiÀÀiÀ𐀀iႠ𐀀ႠÀႠÀii𐀀𐀀ႠႠi𐀀|
- iႠ𐀀À𐀀𐀀Ài𐀀ÀႠ𐀀Ⴀ𐀀𐀀ႠÀ𐀀ႠႠႠiÀ𐀀ÀiႠႠႠÀႠÀႠiÀႠi𐀀ÀÀÀႠÀiÀႠÀÀÀiii𐀀𐀀ÀiiႠÀi𐀀iÀ𐀀À𐀀ÀiiÀÀÀiÀiÀÀi𐀀iiiiÀ𐀀ÀႠႠiiႠi𐀀iiႠ𐀀À𐀀𐀀|
- ႠႠÀÀiႠ𐀀iႠiÀÀႠႠi𐀀ႠÀÀiႠ𐀀ႠႠÀÀÀii𐀀𐀀iiႠ𐀀iႠ𐀀iႠႠ𐀀Ài𐀀iiÀÀႠႠ𐀀Ⴀ𐀀𐀀𐀀i𐀀ÀÀi𐀀𐀀ႠiÀi𐀀iÀiiႠႠÀႠႠiႠÀiႠႠႠÀÀÀ𐀀ႠÀ𐀀ႠÀႠႠiÀÀႠ𐀀|
- Àii𐀀i𐀀iႠÀÀႠႠÀii𐀀ႠႠiÀiÀiႠÀiႠ𐀀Ⴀi𐀀𐀀𐀀À𐀀𐀀𐀀𐀀𐀀iႠ𐀀ႠႠi𐀀i𐀀iႠ𐀀ႠႠÀႠ𐀀iႠႠႠiႠႠ𐀀ႠႠႠႠ𐀀ႠÀÀႠႠႠႠÀ𐀀ႠÀႠÀiiÀiÀiÀႠi𐀀𐀀𐀀𐀀À𐀀𐀀𐀀À|
- ႠiÀiႠi𐀀𐀀ÀiႠiႠÀ𐀀iÀii𐀀ႠÀ𐀀𐀀ႠÀiÀÀ𐀀ႠÀÀႠ𐀀iÀiႠ𐀀𐀀Ⴀ𐀀ႠႠႠÀ𐀀iÀႠiႠÀÀ𐀀ႠÀႠႠႠႠÀ𐀀𐀀À𐀀Ⴀ𐀀À𐀀iႠi𐀀i𐀀Ⴀi𐀀Ⴀ𐀀𐀀iiiႠiႠ𐀀À𐀀ႠÀ𐀀𐀀iÀÀi|
- Ⴀi𐀀ÀÀiÀi𐀀iႠÀ𐀀𐀀ႠiႠÀႠÀ𐀀iÀÀÀႠ𐀀𐀀iÀÀiႠ𐀀ii𐀀i𐀀𐀀iiiႠႠႠÀÀiႠÀ𐀀i𐀀ÀiႠÀÀႠႠi𐀀ႠÀ𐀀À𐀀iiႠႠ𐀀𐀀iÀႠÀi𐀀À𐀀𐀀iÀ𐀀ii𐀀𐀀À𐀀iÀ𐀀𐀀iÀi𐀀|
- iႠÀiiiႠ𐀀ႠiÀႠႠႠႠ𐀀À𐀀ႠႠ𐀀ႠႠi𐀀𐀀𐀀ႠÀÀ𐀀𐀀Ⴀ𐀀iÀ𐀀ႠiႠÀ𐀀ႠႠiÀi𐀀ÀiÀႠႠÀ𐀀𐀀iiÀႠ𐀀ႠႠi𐀀𐀀ÀႠÀiႠႠiÀÀ𐀀iÀÀÀ𐀀𐀀ÀႠii𐀀ÀiiÀႠÀႠÀi𐀀𐀀iႠ|
- iÀ𐀀ႠÀi𐀀iႠႠii𐀀ႠÀ𐀀Ài𐀀𐀀iႠ𐀀iÀi𐀀À𐀀iÀÀiÀ𐀀ÀÀiiiÀႠႠi𐀀ႠiiiႠi𐀀iÀÀ𐀀𐀀ႠႠႠÀiiႠÀႠÀႠiႠi𐀀ႠÀÀ𐀀Ⴀ𐀀i𐀀ႠÀÀ𐀀iÀ𐀀Ⴀ𐀀iÀ𐀀Ⴀ𐀀ႠÀႠÀÀ𐀀|
- ႠÀ𐀀𐀀ÀiÀiႠiႠႠi𐀀𐀀𐀀ÀႠႠi𐀀À𐀀i𐀀𐀀𐀀𐀀iiÀÀÀÀ𐀀Ⴀ𐀀ii𐀀i𐀀iÀi𐀀ႠႠ𐀀iÀ𐀀𐀀𐀀ႠႠႠ𐀀𐀀𐀀𐀀i𐀀𐀀ႠiႠ𐀀i𐀀iႠi𐀀i𐀀ÀႠ𐀀iႠႠႠ𐀀À𐀀𐀀iiႠi𐀀ÀÀÀiii^𐀀 |
+ Ⱡ𐀀ii𐀀ⱠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⱡ𐀀ⱠiiⱠ𐀀iii𐀀ÀⱠÀⱠⱠÀiiÀⱠÀiⱠi𐀀ÀÀ𐀀𐀀Ⱡ𐀀𐀀Ⱡ𐀀iÀⱠ𐀀i𐀀ÀÀⱠiiÀ𐀀Ⱡ𐀀À𐀀ii𐀀ÀÀ𐀀ÀⱠⱠ𐀀𐀀𐀀𐀀ii𐀀ⱠⱠiⱠⱠ𐀀ⱠÀ𐀀ÀÀiiⱠ|
+ iiⱠ𐀀iÀÀiⱠⱠÀi𐀀ⱠⱠÀ𐀀𐀀ÀiiiiÀiⱠ𐀀iⱠÀiⱠiⱠⱠÀiÀ𐀀ⱠiiⱠⱠÀ𐀀Àii𐀀ⱠÀⱠiⱠÀⱠÀⱠii𐀀Ài𐀀ⱠⱠ𐀀ÀÀÀi𐀀ÀÀÀ𐀀iⱠ𐀀iⱠÀ𐀀iⱠi𐀀ÀiÀ𐀀ⱠⱠiÀ𐀀𐀀Ⱡi|
+ iÀiiiⱠÀÀ𐀀ⱠⱠⱠi𐀀À𐀀𐀀iiiÀÀiiÀⱠÀ𐀀À𐀀ⱠⱠ𐀀𐀀ⱠⱠⱠi𐀀iiÀⱠ𐀀ⱠⱠⱠÀiⱠiⱠiÀÀÀi𐀀iⱠ𐀀ÀÀiⱠ𐀀iÀÀi𐀀i𐀀𐀀ÀiÀⱠ𐀀𐀀iⱠ𐀀ÀÀiÀÀⱠ𐀀𐀀ⱠⱠ𐀀𐀀𐀀𐀀Ⱡ𐀀𐀀|
+ ÀⱠiⱠiÀ𐀀i𐀀ⱠⱠiⱠⱠÀ𐀀ÀÀÀÀ𐀀𐀀ÀⱠⱠ𐀀ⱠÀÀiⱠ𐀀i𐀀Ⱡ𐀀ÀⱠi𐀀ⱠÀⱠÀ𐀀ⱠⱠ𐀀i𐀀iⱠÀi𐀀i𐀀𐀀À𐀀iÀiⱠⱠⱠ𐀀ÀiÀⱠÀ𐀀ÀÀÀi𐀀𐀀𐀀ⱠÀi𐀀𐀀À𐀀À𐀀𐀀iiⱠiÀi𐀀i𐀀Ⱡ|
+ Ⱡ𐀀i𐀀𐀀ÀiÀⱠⱠⱠⱠⱠÀÀⱠⱠÀⱠ𐀀ii𐀀ÀⱠiⱠiii𐀀i𐀀i𐀀𐀀𐀀À𐀀ii𐀀iÀiiiÀÀⱠiiiⱠiiⱠÀ𐀀À𐀀𐀀ÀⱠ𐀀iÀÀiiÀiÀ𐀀iⱠi𐀀𐀀À𐀀ÀiiⱠ𐀀iÀ𐀀𐀀iⱠⱠÀÀⱠⱠiiÀ|
+ 𐀀ÀiⱠⱠÀ𐀀𐀀𐀀i𐀀i𐀀i𐀀ⱠÀ𐀀ÀiiÀⱠ𐀀ÀÀÀi𐀀ⱠÀiÀⱠi𐀀ⱠÀiiÀÀÀiiiÀiⱠⱠiÀ𐀀ⱠⱠ𐀀iÀⱠÀⱠⱠiÀÀⱠÀⱠÀÀii𐀀Ⱡi𐀀iiÀÀÀiⱠ𐀀i𐀀𐀀i𐀀iiÀ𐀀𐀀𐀀ⱠÀiⱠ𐀀|
+ i𐀀ÀⱠiⱠi𐀀ⱠiⱠ𐀀Ⱡi𐀀ⱠÀ𐀀𐀀𐀀ⱠÀiiiii𐀀Ⱡ𐀀iiiÀiiÀ𐀀𐀀𐀀À𐀀𐀀Ⱡ𐀀ⱠÀ𐀀ⱠⱠⱠiÀÀÀÀii𐀀i𐀀ÀiiⱠÀiÀ𐀀iⱠⱠiÀⱠii𐀀i𐀀Ⱡ𐀀𐀀iⱠⱠÀ𐀀ⱠiiiⱠⱠÀÀ𐀀iÀⱠ|
+ Ⱡ𐀀𐀀ⱠⱠ𐀀À𐀀ÀⱠ𐀀ÀⱠÀ𐀀𐀀iⱠⱠÀÀiÀⱠ𐀀ÀiÀⱠi𐀀ⱠÀ𐀀𐀀𐀀𐀀Ⱡ𐀀iⱠÀ𐀀iÀ𐀀iÀ𐀀iÀÀⱠi𐀀iÀⱠi𐀀ⱠiiⱠÀ𐀀À𐀀ⱠⱠÀÀi𐀀ⱠⱠ𐀀iiⱠÀiⱠ𐀀𐀀𐀀𐀀ⱠⱠⱠÀⱠiÀⱠiÀÀ𐀀À|
+ ÀⱠÀÀ𐀀i𐀀iⱠÀÀÀⱠ𐀀𐀀ÀⱠÀÀiii𐀀𐀀iiÀiiⱠÀÀⱠiÀiÀÀ𐀀i𐀀i𐀀ⱠiⱠⱠiⱠÀiiÀⱠ𐀀ⱠⱠÀiÀⱠ𐀀𐀀iÀ𐀀Ⱡ𐀀iÀ𐀀ⱠÀÀⱠÀÀÀ𐀀𐀀i𐀀𐀀À𐀀𐀀ii𐀀À𐀀𐀀ⱠÀ𐀀ⱠⱠⱠ𐀀𐀀|
+ ÀÀÀÀiⱠiⱠⱠⱠiⱠ𐀀ⱠÀÀÀ𐀀ÀÀiⱠÀ𐀀ÀiⱠÀⱠÀⱠⱠÀÀⱠiÀⱠⱠiiⱠÀ𐀀ⱠⱠÀiⱠ𐀀iÀⱠ𐀀Ⱡ𐀀Ⱡ𐀀iⱠÀⱠi𐀀𐀀Ⱡ𐀀iÀ𐀀ÀⱠ𐀀ÀÀⱠ𐀀Ⱡi𐀀iⱠÀ𐀀𐀀𐀀𐀀i𐀀i𐀀𐀀𐀀ÀⱠiÀÀ𐀀i|
+ 𐀀𐀀iiÀ𐀀ÀⱠ𐀀𐀀𐀀𐀀𐀀Àiii𐀀𐀀𐀀Ⱡ𐀀𐀀i𐀀ÀÀ𐀀iiÀiiiiÀ𐀀iⱠiⱠiÀⱠÀⱠÀiⱠⱠⱠⱠⱠⱠⱠⱠⱠÀiiⱠiÀⱠÀ𐀀iiⱠÀⱠiⱠⱠÀiⱠ𐀀iⱠ𐀀iiⱠÀ𐀀𐀀Àii𐀀i𐀀ÀⱠÀÀiÀÀ|
+ 𐀀𐀀𐀀i𐀀iÀ𐀀𐀀iÀ𐀀Ài𐀀𐀀ⱠⱠ𐀀ⱠÀi𐀀𐀀ÀÀiiiⱠiⱠ𐀀iⱠÀⱠÀ𐀀ⱠÀ𐀀ⱠiiiiÀiⱠÀiiiⱠⱠÀ𐀀Ⱡ𐀀𐀀𐀀ÀⱠiÀⱠiiiiⱠiⱠ𐀀Ⱡ𐀀ÀⱠÀii𐀀i𐀀Ⱡ𐀀À𐀀iⱠⱠ𐀀iⱠiiii𐀀|
+ iⱠÀÀⱠÀÀ𐀀𐀀𐀀iiiÀ𐀀À𐀀iÀÀi𐀀À𐀀ÀⱠÀiÀii𐀀ⱠⱠii𐀀ⱠⱠ𐀀𐀀ÀⱠ𐀀ÀÀ𐀀ÀÀÀÀi𐀀ÀⱠ𐀀À𐀀ÀiiⱠ𐀀ÀÀⱠiⱠ𐀀Ⱡ𐀀ÀÀ𐀀ⱠⱠ𐀀ⱠⱠÀ𐀀ⱠⱠⱠ𐀀iiÀⱠÀⱠiⱠi𐀀ii𐀀Ài|
+ Ⱡ𐀀ⱠÀ𐀀Ⱡi𐀀iÀ𐀀Ⱡ𐀀𐀀iÀiÀ𐀀iⱠi𐀀ⱠÀiⱠⱠiÀ𐀀iⱠiÀⱠi𐀀𐀀iⱠiⱠⱠ𐀀ÀÀi𐀀iÀⱠⱠ𐀀ⱠÀ𐀀𐀀𐀀ⱠⱠiÀÀiⱠⱠⱠ𐀀𐀀ⱠⱠⱠⱠÀiⱠ𐀀Ài𐀀iÀⱠii𐀀ÀⱠii𐀀Ⱡ𐀀ÀⱠÀ𐀀Ⱡi|
+ 𐀀ⱠⱠⱠⱠⱠ𐀀Ⱡ𐀀ÀÀ𐀀𐀀iÀÀⱠiÀiiⱠÀiⱠ𐀀𐀀ÀÀiÀⱠ𐀀ÀÀⱠ𐀀À𐀀À𐀀iⱠⱠÀiⱠÀiiiⱠÀiÀÀÀ𐀀iⱠⱠⱠÀÀiⱠⱠⱠ𐀀ii𐀀ii𐀀iⱠⱠii𐀀iⱠÀi𐀀𐀀𐀀ii𐀀ÀⱠⱠiⱠÀ𐀀ⱠⱠ|
+ 𐀀iⱠ𐀀ⱠⱠⱠÀÀ𐀀iiiÀÀⱠii𐀀i𐀀ÀÀⱠ𐀀ⱠⱠiⱠⱠⱠiⱠ𐀀i𐀀À𐀀ⱠⱠⱠi𐀀Ài𐀀ⱠÀ𐀀ÀⱠiiiiⱠiiⱠⱠi𐀀𐀀ÀⱠ𐀀ⱠiÀÀ𐀀iiiⱠÀiⱠi𐀀À𐀀𐀀i𐀀𐀀iiⱠ𐀀À𐀀iiⱠⱠÀ𐀀iⱠ|
+ ⱠⱠÀÀⱠⱠ𐀀iÀⱠ𐀀iⱠⱠÀÀ𐀀ÀiⱠ𐀀iÀⱠⱠⱠ𐀀𐀀À𐀀ii𐀀𐀀À𐀀𐀀ÀⱠⱠⱠÀⱠiiⱠ𐀀𐀀ii𐀀ⱠⱠÀⱠiⱠi𐀀ⱠⱠ𐀀i𐀀𐀀𐀀𐀀Ⱡ𐀀iÀiⱠⱠ𐀀À𐀀ÀⱠⱠⱠÀÀ𐀀𐀀iÀⱠi𐀀𐀀ÀiⱠⱠÀÀiÀÀ|
+ 𐀀ⱠÀiÀ𐀀ⱠⱠ𐀀i𐀀ⱠⱠ𐀀𐀀𐀀𐀀𐀀𐀀iⱠÀⱠⱠiiÀⱠ𐀀𐀀i𐀀ⱠⱠ𐀀ⱠⱠⱠ𐀀𐀀iÀiÀⱠ𐀀À𐀀À𐀀𐀀ⱠiiiⱠiiiⱠiⱠ𐀀ÀiiiⱠ𐀀À𐀀ÀⱠⱠÀ𐀀À𐀀𐀀𐀀ⱠiⱠi𐀀ⱠÀÀⱠiiⱠⱠⱠⱠ𐀀ÀⱠ𐀀𐀀|
+ Ⱡ𐀀iÀÀⱠ𐀀ⱠⱠÀⱠÀ𐀀ⱠⱠÀiiÀÀÀⱠ𐀀i𐀀Ⱡ𐀀ⱠÀ𐀀𐀀iⱠⱠⱠⱠiiⱠⱠⱠÀiⱠÀiiⱠÀ𐀀Ⱡ𐀀Ⱡi𐀀𐀀ⱠÀ𐀀ÀⱠⱠ𐀀ÀⱠÀⱠ𐀀Ⱡ𐀀iⱠi𐀀ÀⱠⱠii𐀀ÀⱠÀ𐀀Ⱡ𐀀Ài𐀀À𐀀ÀÀⱠiⱠⱠii𐀀|
+ ÀⱠⱠⱠÀⱠⱠii𐀀Ài𐀀Ⱡi𐀀i𐀀i𐀀Ⱡ𐀀ÀÀÀiÀÀi𐀀ÀÀÀ𐀀i𐀀iÀ𐀀𐀀ⱠiÀi𐀀𐀀𐀀ⱠÀiÀ𐀀𐀀iÀÀⱠⱠ𐀀ⱠⱠⱠⱠÀÀÀÀiiÀ𐀀iiⱠⱠⱠi𐀀ⱠÀi𐀀ÀⱠⱠÀ𐀀ⱠiiⱠ𐀀𐀀i𐀀Ⱡi𐀀𐀀À|
+ ÀⱠiiÀi𐀀ⱠÀÀiÀⱠi𐀀ÀⱠⱠ𐀀ⱠiÀiiⱠiiⱠⱠ𐀀ii𐀀i𐀀𐀀𐀀𐀀i𐀀ⱠⱠÀⱠÀÀiÀÀÀⱠÀⱠⱠÀÀⱠ𐀀ÀÀ𐀀ÀÀⱠÀ𐀀𐀀ÀiⱠ𐀀ⱠⱠ𐀀iÀ𐀀Ⱡ𐀀iÀⱠⱠ𐀀iÀiiii𐀀ii𐀀𐀀ⱠⱠⱠ𐀀Ⱡ|
+ 𐀀ÀiÀⱠÀiiÀÀiiⱠⱠⱠi𐀀ÀÀiⱠ𐀀iⱠⱠÀ𐀀𐀀𐀀ÀⱠⱠÀ𐀀ⱠÀÀⱠ𐀀ÀÀ𐀀𐀀Ⱡ𐀀ÀÀ𐀀ÀⱠ𐀀ⱠÀiÀ𐀀iⱠ𐀀ⱠⱠ𐀀𐀀À𐀀iii𐀀iiⱠÀⱠiⱠÀⱠ𐀀Ⱡ𐀀i𐀀𐀀ÀⱠⱠi𐀀𐀀ⱠⱠ𐀀𐀀𐀀𐀀À𐀀Ⱡ𐀀|
+ 𐀀iiÀⱠiⱠiÀⱠ𐀀i𐀀iii𐀀Ⱡ𐀀i𐀀iÀÀi𐀀Ⱡii𐀀ÀiÀiiiÀⱠÀ𐀀ÀÀⱠ𐀀Ⱡ𐀀iiÀi𐀀i𐀀𐀀i𐀀ⱠiiiÀⱠⱠⱠiiÀ𐀀À𐀀𐀀iÀ𐀀iⱠÀⱠÀÀi𐀀ⱠiÀⱠÀ𐀀𐀀iÀÀ𐀀i𐀀𐀀ÀÀⱠ𐀀|
+ ÀiÀⱠⱠⱠⱠii𐀀ÀÀ𐀀𐀀𐀀Ⱡi𐀀À𐀀ÀⱠiiÀi𐀀Ⱡii𐀀iÀÀ𐀀ⱠiⱠ𐀀ⱠiiiⱠÀÀiÀÀÀÀ𐀀ⱠⱠii𐀀À𐀀ÀiÀi𐀀ÀÀi𐀀iⱠiÀi𐀀ÀiÀi𐀀ÀiÀⱠ𐀀i𐀀Ⱡi𐀀𐀀𐀀ⱠⱠ𐀀ⱠÀⱠÀⱠi|
+ i𐀀ⱠiÀⱠⱠÀÀ𐀀𐀀ii𐀀ÀÀ𐀀iÀiÀⱠÀiiii𐀀ÀiÀⱠi𐀀i𐀀𐀀i𐀀𐀀iⱠ𐀀iÀi𐀀ÀÀÀÀiⱠiÀⱠÀÀⱠiiÀÀⱠⱠi𐀀iⱠiiⱠi𐀀Ⱡ𐀀𐀀ÀⱠⱠÀⱠiⱠⱠÀ𐀀iiÀⱠⱠⱠ𐀀𐀀Ⱡi𐀀Ⱡi|
+ ii𐀀iÀÀÀÀÀÀiÀ𐀀À𐀀iiⱠiⱠⱠi𐀀À𐀀ÀⱠÀ𐀀ⱠⱠ𐀀𐀀𐀀iⱠⱠiiⱠÀÀⱠÀiiⱠÀⱠⱠÀ𐀀𐀀Ⱡ𐀀ÀÀÀÀⱠ𐀀𐀀𐀀ⱠⱠÀⱠ𐀀ÀiⱠiÀⱠiÀÀ𐀀ii𐀀iiiÀⱠÀⱠⱠ𐀀ⱠÀiÀÀ𐀀ⱠⱠⱠÀ|
+ 𐀀𐀀À𐀀𐀀iÀⱠ𐀀ⱠiⱠÀÀ𐀀iÀÀ𐀀À𐀀iÀÀⱠⱠÀiii𐀀À𐀀ÀⱠÀⱠⱠÀⱠⱠi𐀀ÀÀÀi𐀀À𐀀ⱠiÀi𐀀i𐀀i𐀀ÀiÀÀiÀÀ𐀀𐀀À𐀀ⱠÀ𐀀ⱠÀⱠ𐀀ⱠiÀ𐀀𐀀ÀiÀÀ𐀀𐀀𐀀À𐀀Ⱡi𐀀i𐀀i𐀀Ài|
+ 𐀀𐀀iⱠ𐀀i𐀀ÀⱠⱠÀ𐀀iÀ𐀀ÀiⱠⱠi𐀀iiⱠÀ𐀀ÀiiÀⱠ𐀀Ⱡ𐀀ÀÀiÀiⱠi𐀀À𐀀𐀀iÀiÀiⱠi𐀀ⱠⱠⱠi𐀀À𐀀ÀⱠⱠ𐀀Ⱡ𐀀ÀÀⱠiÀiÀ𐀀Ⱡ𐀀ÀⱠiⱠⱠÀÀÀi𐀀i𐀀Ⱡi𐀀À𐀀ii𐀀ⱠÀ𐀀Ⱡ|
+ ⱠiⱠ𐀀iÀⱠⱠ𐀀i𐀀À𐀀iÀÀ𐀀𐀀ÀⱠⱠÀⱠÀ𐀀iiiÀ𐀀i𐀀iÀ𐀀ⱠⱠ𐀀iÀⱠ𐀀ⱠÀi𐀀iiii𐀀iⱠⱠ𐀀ÀiÀ𐀀Àii𐀀Ⱡ𐀀𐀀ⱠiÀii𐀀𐀀Ⱡ𐀀𐀀ⱠiⱠ𐀀iⱠiⱠi𐀀iiiⱠⱠⱠi𐀀iiÀi𐀀Ⱡ|
+ i𐀀i𐀀ÀÀÀ𐀀ÀiÀⱠiÀiiⱠÀÀÀiÀiiii𐀀i𐀀ÀÀiiiⱠÀiÀⱠÀiⱠ𐀀iiⱠiⱠⱠiÀi𐀀ⱠⱠ𐀀ÀⱠiⱠ𐀀ⱠÀiiⱠÀ𐀀ÀⱠⱠ𐀀ⱠiÀi𐀀À𐀀𐀀iiÀ𐀀𐀀ÀiⱠⱠiⱠ𐀀ÀⱠiÀÀⱠ𐀀i|
+ Ài𐀀𐀀𐀀iÀi𐀀Ài𐀀À𐀀ⱠⱠ𐀀ⱠÀiiÀⱠ𐀀i𐀀i𐀀𐀀ⱠiⱠÀ𐀀𐀀Ⱡ𐀀iÀ𐀀ÀÀⱠiⱠⱠiÀ𐀀iÀ𐀀ⱠiÀÀÀÀⱠiiÀ𐀀𐀀ÀⱠⱠiÀ𐀀iiÀ𐀀À𐀀iÀiÀÀ𐀀iÀiÀÀiiÀ𐀀ÀⱠⱠÀiiÀÀⱠ|
+ 𐀀ⱠÀⱠiⱠⱠÀ𐀀ÀiiÀ𐀀iÀⱠⱠⱠⱠiÀÀi𐀀iÀi𐀀iiiⱠ𐀀iⱠ𐀀𐀀𐀀𐀀ÀÀÀⱠi𐀀iⱠi𐀀ⱠÀⱠⱠ𐀀𐀀À𐀀iiÀⱠ𐀀𐀀ⱠⱠ𐀀𐀀ÀiⱠÀÀÀⱠ𐀀𐀀ÀiⱠ𐀀𐀀iÀÀiÀ𐀀ⱠÀi𐀀𐀀ⱠⱠⱠⱠ𐀀Ⱡi|
+ iÀi𐀀ⱠⱠÀ𐀀𐀀i𐀀Àii𐀀ÀiÀÀiÀiÀÀ𐀀ÀÀ𐀀À𐀀ⱠⱠⱠÀÀÀⱠii𐀀ⱠÀÀⱠⱠi𐀀ⱠⱠiⱠⱠ𐀀Ⱡ𐀀ÀiÀiiii𐀀ÀiⱠÀiiiiⱠⱠiiⱠÀÀÀⱠÀⱠ𐀀𐀀𐀀iiⱠⱠ𐀀ⱠÀⱠ𐀀iⱠⱠ𐀀𐀀Ⱡ|
+ À𐀀À𐀀ⱠⱠⱠiÀ𐀀ÀⱠÀⱠⱠiiⱠii𐀀ⱠÀÀÀ𐀀iⱠiiiiiÀ𐀀ÀÀiÀÀⱠ𐀀𐀀iiⱠi𐀀𐀀Ài𐀀ⱠÀÀiⱠⱠⱠⱠ𐀀iⱠiⱠⱠⱠⱠⱠÀÀⱠiiÀiⱠ𐀀iÀiiⱠiiii𐀀ii𐀀À𐀀ⱠÀ𐀀Ⱡ𐀀ÀⱠ|
+ ÀⱠÀiiiiⱠiiⱠiÀi𐀀𐀀ⱠÀÀ𐀀Ài𐀀Ài𐀀ÀiⱠÀ𐀀ⱠⱠⱠⱠ𐀀ⱠiÀ𐀀iⱠⱠÀⱠi𐀀Ài𐀀ⱠiiⱠ𐀀Ⱡii𐀀ÀÀÀⱠ𐀀ÀÀÀⱠÀiÀⱠiⱠiⱠi𐀀𐀀À𐀀𐀀𐀀ÀⱠiⱠⱠⱠⱠÀiiÀⱠ𐀀À𐀀𐀀À|
+ iÀ𐀀ÀⱠⱠÀiÀi𐀀Ài𐀀ÀiⱠⱠÀ𐀀iⱠÀ𐀀i𐀀ÀiiÀÀÀⱠÀÀⱠ𐀀À𐀀À𐀀ÀÀÀÀⱠi𐀀iⱠÀ𐀀𐀀ÀⱠiÀiⱠⱠiÀ𐀀ⱠⱠÀ𐀀𐀀Ⱡ𐀀ÀÀ𐀀ⱠⱠÀÀiÀi𐀀𐀀𐀀À𐀀ÀⱠ𐀀iⱠⱠ𐀀𐀀𐀀i𐀀ⱠÀ𐀀Ⱡ|
+ Ⱡi𐀀ÀÀ𐀀ⱠÀiÀi𐀀i𐀀ÀⱠ𐀀Ⱡ𐀀ÀⱠ𐀀ⱠÀⱠⱠⱠ𐀀𐀀ÀiiiiⱠⱠi𐀀ⱠÀⱠÀ𐀀Ⱡ𐀀i𐀀À𐀀𐀀𐀀Ⱡ𐀀ÀiÀÀⱠⱠiiⱠiÀiⱠⱠÀiÀÀⱠⱠÀÀⱠÀ𐀀ⱠiⱠ𐀀𐀀i𐀀i𐀀𐀀ÀⱠÀⱠⱠⱠÀÀiiÀ𐀀|
+ ⱠⱠⱠiiÀⱠⱠiÀⱠ𐀀ÀiⱠⱠÀⱠiÀⱠⱠÀÀi𐀀ÀÀiÀ𐀀𐀀i𐀀i𐀀iiÀÀiⱠ𐀀Ⱡ𐀀𐀀𐀀ÀiiⱠ𐀀Ài𐀀iiiiÀiⱠⱠii𐀀Ⱡi𐀀iⱠⱠ𐀀ÀÀⱠ𐀀iÀⱠⱠⱠiÀ𐀀𐀀iÀⱠiⱠÀ𐀀ÀⱠÀiⱠ𐀀À|
+ 𐀀ⱠⱠⱠiⱠⱠiiii𐀀𐀀i𐀀Àiiii𐀀À𐀀Ⱡi𐀀iⱠ𐀀ⱠiÀiÀⱠi𐀀𐀀ÀiÀiiÀÀÀ𐀀𐀀i𐀀À𐀀ÀⱠÀiiÀⱠ𐀀ⱠⱠ𐀀𐀀Ⱡ𐀀ÀÀiÀ𐀀iⱠ𐀀𐀀iÀÀⱠi𐀀iⱠiÀ𐀀ⱠⱠ𐀀ÀÀⱠiÀ𐀀ÀⱠⱠÀⱠ|
+ Ⱡii𐀀𐀀ⱠÀiⱠⱠÀÀ𐀀ÀÀÀÀÀÀÀⱠiⱠⱠÀÀi𐀀ÀiⱠÀ𐀀𐀀i𐀀ⱠÀii𐀀Ⱡ𐀀𐀀À𐀀𐀀ÀiÀ𐀀i𐀀𐀀ⱠÀiÀÀⱠiiⱠⱠiⱠÀiÀⱠÀi𐀀iÀ𐀀À𐀀𐀀ⱠⱠi𐀀ⱠÀiÀÀÀⱠÀiÀÀⱠiⱠ𐀀iÀ|
+ 𐀀ÀⱠiÀⱠⱠⱠÀÀⱠÀⱠ𐀀iiiiÀiÀÀⱠ𐀀Ⱡiii𐀀𐀀iiⱠiÀ𐀀𐀀i𐀀ÀiiÀⱠ𐀀𐀀Ⱡ𐀀Ⱡ𐀀Ⱡii𐀀ⱠiⱠÀiⱠⱠÀÀÀⱠÀ𐀀Ⱡ𐀀𐀀𐀀À𐀀Ⱡ𐀀Ⱡ𐀀ⱠÀ𐀀ⱠⱠiⱠ𐀀𐀀ÀiiiÀ𐀀ÀiÀiⱠÀ𐀀À|
+ i𐀀ⱠiⱠi𐀀ii𐀀𐀀iiiⱠⱠÀÀiiii𐀀ÀiⱠⱠÀi𐀀ÀÀÀÀiÀiiⱠ𐀀ÀⱠiⱠⱠiÀⱠ𐀀ÀⱠⱠ𐀀ÀÀÀ𐀀ⱠÀ𐀀À𐀀iⱠi𐀀iÀÀi𐀀iÀÀiⱠⱠ𐀀ⱠiÀÀiÀ𐀀iⱠ𐀀ⱠÀⱠÀii𐀀𐀀ⱠⱠi𐀀|
+ iⱠ𐀀À𐀀𐀀Ài𐀀ÀⱠ𐀀Ⱡ𐀀𐀀ⱠÀ𐀀ⱠⱠⱠiÀ𐀀ÀiⱠⱠⱠÀⱠÀⱠiÀⱠi𐀀ÀÀÀⱠÀiÀⱠÀÀÀiii𐀀𐀀ÀiiⱠÀi𐀀iÀ𐀀À𐀀ÀiiÀÀÀiÀiÀÀi𐀀iiiiÀ𐀀ÀⱠⱠiiⱠi𐀀iiⱠ𐀀À𐀀𐀀|
+ ⱠⱠÀÀiⱠ𐀀iⱠiÀÀⱠⱠi𐀀ⱠÀÀiⱠ𐀀ⱠⱠÀÀÀii𐀀𐀀iiⱠ𐀀iⱠ𐀀iⱠⱠ𐀀Ài𐀀iiÀÀⱠⱠ𐀀Ⱡ𐀀𐀀𐀀i𐀀ÀÀi𐀀𐀀ⱠiÀi𐀀iÀiiⱠⱠÀⱠⱠiⱠÀiⱠⱠⱠÀÀÀ𐀀ⱠÀ𐀀ⱠÀⱠⱠiÀÀⱠ𐀀|
+ Àii𐀀i𐀀iⱠÀÀⱠⱠÀii𐀀ⱠⱠiÀiÀiⱠÀiⱠ𐀀Ⱡi𐀀𐀀𐀀À𐀀𐀀𐀀𐀀𐀀iⱠ𐀀ⱠⱠi𐀀i𐀀iⱠ𐀀ⱠⱠÀⱠ𐀀iⱠⱠⱠiⱠⱠ𐀀ⱠⱠⱠⱠ𐀀ⱠÀÀⱠⱠⱠⱠÀ𐀀ⱠÀⱠÀiiÀiÀiÀⱠi𐀀𐀀𐀀𐀀À𐀀𐀀𐀀À|
+ ⱠiÀiⱠi𐀀𐀀ÀiⱠiⱠÀ𐀀iÀii𐀀ⱠÀ𐀀𐀀ⱠÀiÀÀ𐀀ⱠÀÀⱠ𐀀iÀiⱠ𐀀𐀀Ⱡ𐀀ⱠⱠⱠÀ𐀀iÀⱠiⱠÀÀ𐀀ⱠÀⱠⱠⱠⱠÀ𐀀𐀀À𐀀Ⱡ𐀀À𐀀iⱠi𐀀i𐀀Ⱡi𐀀Ⱡ𐀀𐀀iiiⱠiⱠ𐀀À𐀀ⱠÀ𐀀𐀀iÀÀi|
+ Ⱡi𐀀ÀÀiÀi𐀀iⱠÀ𐀀𐀀ⱠiⱠÀⱠÀ𐀀iÀÀÀⱠ𐀀𐀀iÀÀiⱠ𐀀ii𐀀i𐀀𐀀iiiⱠⱠⱠÀÀiⱠÀ𐀀i𐀀ÀiⱠÀÀⱠⱠi𐀀ⱠÀ𐀀À𐀀iiⱠⱠ𐀀𐀀iÀⱠÀi𐀀À𐀀𐀀iÀ𐀀ii𐀀𐀀À𐀀iÀ𐀀𐀀iÀi𐀀|
+ iⱠÀiiiⱠ𐀀ⱠiÀⱠⱠⱠⱠ𐀀À𐀀ⱠⱠ𐀀ⱠⱠi𐀀𐀀𐀀ⱠÀÀ𐀀𐀀Ⱡ𐀀iÀ𐀀ⱠiⱠÀ𐀀ⱠⱠiÀi𐀀ÀiÀⱠⱠÀ𐀀𐀀iiÀⱠ𐀀ⱠⱠi𐀀𐀀ÀⱠÀiⱠⱠiÀÀ𐀀iÀÀÀ𐀀𐀀ÀⱠii𐀀ÀiiÀⱠÀⱠÀi𐀀𐀀iⱠ|
+ iÀ𐀀ⱠÀi𐀀iⱠⱠii𐀀ⱠÀ𐀀Ài𐀀𐀀iⱠ𐀀iÀi𐀀À𐀀iÀÀiÀ𐀀ÀÀiiiÀⱠⱠi𐀀ⱠiiiⱠi𐀀iÀÀ𐀀𐀀ⱠⱠⱠÀiiⱠÀⱠÀⱠiⱠi𐀀ⱠÀÀ𐀀Ⱡ𐀀i𐀀ⱠÀÀ𐀀iÀ𐀀Ⱡ𐀀iÀ𐀀Ⱡ𐀀ⱠÀⱠÀÀ𐀀|
+ ⱠÀ𐀀𐀀ÀiÀiⱠiⱠⱠi𐀀𐀀𐀀ÀⱠⱠi𐀀À𐀀i𐀀𐀀𐀀𐀀iiÀÀÀÀ𐀀Ⱡ𐀀ii𐀀i𐀀iÀi𐀀ⱠⱠ𐀀iÀ𐀀𐀀𐀀ⱠⱠⱠ𐀀𐀀𐀀𐀀i𐀀𐀀ⱠiⱠ𐀀i𐀀iⱠi𐀀i𐀀ÀⱠ𐀀iⱠⱠⱠ𐀀À𐀀𐀀iiⱠi𐀀ÀÀÀiii^𐀀 |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
@@ -299,58 +315,59 @@ benchmarks({
{
ascii_results,
two_byte_results,
+ three_byte_results,
{ -- random
screen = [=[
- Ⴀ𐀀ii𐀀ႠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⴀ𐀀ႠiiႠ𐀀iii𐀀ÀႠÀႠႠÀiiÀႠÀiႠi𐀀ÀÀ𐀀𐀀Ⴀ𐀀𐀀Ⴀ𐀀iÀႠ𐀀i𐀀ÀÀႠiiÀ𐀀Ⴀ𐀀À𐀀ii𐀀ÀÀ𐀀ÀႠႠ𐀀𐀀𐀀𐀀ii𐀀ႠႠiႠႠ𐀀ႠÀ𐀀ÀÀiiႠ|
- 𐀀𐀀ÀႠÀ𐀀𐀀iႠÀ𐀀𐀀i𐀀𐀀𐀀𐀀ႠႠႠiiiiÀÀ𐀀ႠÀႠÀi𐀀i𐀀ႠႠÀiÀႠi𐀀𐀀À𐀀iÀ𐀀ÀÀÀ𐀀𐀀iÀႠ𐀀𐀀Ⴀ𐀀𐀀ÀiႠiiႠႠÀÀÀÀi𐀀ÀÀႠ𐀀Àiii𐀀iႠႠႠi𐀀ႠÀi𐀀iႠ𐀀ႠiႠ|
- iiႠ𐀀iÀÀiႠႠÀi𐀀ႠႠÀ𐀀𐀀ÀiiiiÀiႠ𐀀iႠÀiႠiႠႠÀiÀ𐀀ႠiiႠႠÀ𐀀Àii𐀀ႠÀႠiႠÀႠÀႠii𐀀Ài𐀀ႠႠ𐀀ÀÀÀi𐀀ÀÀÀ𐀀iႠ𐀀iႠÀ𐀀iႠi𐀀ÀiÀ𐀀ႠႠiÀ𐀀𐀀Ⴀi|
- À𐀀𐀀iÀiÀÀÀÀႠႠႠ𐀀iÀÀiႠ𐀀À𐀀ႠÀiiႠ𐀀iiႠႠ𐀀iÀiႠႠÀႠÀ𐀀Ài𐀀iႠ𐀀𐀀iiႠÀႠiÀÀÀiÀiiÀ𐀀i𐀀ÀÀႠ𐀀𐀀𐀀i𐀀𐀀ႠႠi𐀀À𐀀iႠi𐀀ႠႠiiiÀႠ𐀀ႠÀiÀiႠႠ|
- iÀiiiႠÀÀ𐀀ႠႠႠi𐀀À𐀀𐀀iiiÀÀiiÀႠÀ𐀀À𐀀ႠႠ𐀀𐀀ႠႠႠi𐀀iiÀႠ𐀀ႠႠႠÀiႠiႠiÀÀÀi𐀀iႠ𐀀ÀÀiႠ𐀀iÀÀi𐀀i𐀀𐀀ÀiÀႠ𐀀𐀀iႠ𐀀ÀÀiÀÀႠ𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀Ⴀ𐀀𐀀|
- 𐀀ÀÀႠÀ𐀀ÀÀiÀÀÀႠiiႠiiÀႠÀiႠÀiÀiႠႠ𐀀ÀÀÀႠiiÀႠ𐀀iÀi𐀀ႠႠ𐀀𐀀ÀÀ𐀀ÀiÀÀႠi𐀀iÀႠ𐀀À𐀀ႠႠÀ𐀀Ⴀiii𐀀ႠiiႠiÀႠႠiႠÀႠ𐀀ႠÀÀႠ𐀀À𐀀ÀiÀÀႠႠÀÀ|
- ÀႠiႠiÀ𐀀i𐀀ႠႠiႠႠÀ𐀀ÀÀÀÀ𐀀𐀀ÀႠႠ𐀀ႠÀÀiႠ𐀀i𐀀Ⴀ𐀀ÀႠi𐀀ႠÀႠÀ𐀀ႠႠ𐀀i𐀀iႠÀi𐀀i𐀀𐀀À𐀀iÀiႠႠႠ𐀀ÀiÀႠÀ𐀀ÀÀÀi𐀀𐀀𐀀ႠÀi𐀀𐀀À𐀀À𐀀𐀀iiႠiÀi𐀀i𐀀Ⴀ|
- 𐀀𐀀i𐀀ÀႠႠ𐀀𐀀𐀀iႠႠ𐀀À𐀀ÀႠiÀ𐀀𐀀ႠÀi𐀀𐀀iiiႠ𐀀𐀀iႠÀÀ𐀀ႠiiÀႠႠÀ𐀀𐀀ႠÀႠႠÀiႠႠÀႠÀႠiႠႠ𐀀𐀀𐀀iႠႠႠiႠႠii𐀀ÀႠi𐀀ÀÀႠႠi𐀀À𐀀Ⴀ𐀀ÀÀ𐀀Ⴀ𐀀iႠiiႠႠ|
- Ⴀ𐀀i𐀀𐀀ÀiÀႠႠႠႠႠÀÀႠႠÀႠ𐀀ii𐀀ÀႠiႠiii𐀀i𐀀i𐀀𐀀𐀀À𐀀ii𐀀iÀiiiÀÀႠiiiႠiiႠÀ𐀀À𐀀𐀀ÀႠ𐀀iÀÀiiÀiÀ𐀀iႠi𐀀𐀀À𐀀ÀiiႠ𐀀iÀ𐀀𐀀iႠႠÀÀႠႠiiÀ|
- i𐀀𐀀𐀀ÀÀi𐀀ႠႠႠႠႠÀiiÀ𐀀𐀀ii𐀀Ⴀ𐀀Ài𐀀iႠiÀÀႠÀ𐀀ÀႠiႠÀi𐀀𐀀iiႠ𐀀i𐀀ႠÀiႠii𐀀𐀀À𐀀𐀀ႠႠÀႠiÀiႠÀÀi𐀀i𐀀ႠÀiႠႠႠ𐀀𐀀ÀiႠႠႠÀÀi𐀀ÀႠႠÀiႠ𐀀ႠÀ|
- 𐀀ÀiႠႠÀ𐀀𐀀𐀀i𐀀i𐀀i𐀀ႠÀ𐀀ÀiiÀႠ𐀀ÀÀÀi𐀀ႠÀiÀႠi𐀀ႠÀiiÀÀÀiiiÀiႠႠiÀ𐀀ႠႠ𐀀iÀႠÀႠႠiÀÀႠÀႠÀÀii𐀀Ⴀi𐀀iiÀÀÀiႠ𐀀i𐀀𐀀i𐀀iiÀ𐀀𐀀𐀀ႠÀiႠ𐀀|
- À𐀀ႠႠႠႠ𐀀ÀiႠႠiÀ𐀀i𐀀ÀႠÀႠiiÀiÀÀiႠ𐀀𐀀𐀀Ⴀ𐀀ÀႠi𐀀𐀀iႠႠႠiiႠÀi𐀀𐀀𐀀iႠÀÀÀႠi𐀀À𐀀iiiႠÀႠiÀ𐀀iႠ𐀀ii𐀀𐀀𐀀ÀႠႠÀÀႠႠႠႠiÀi𐀀Àiii𐀀ii𐀀𐀀À|
- i𐀀ÀႠiႠi𐀀ႠiႠ𐀀Ⴀi𐀀ႠÀ𐀀𐀀𐀀ႠÀiiiii𐀀Ⴀ𐀀iiiÀiiÀ𐀀𐀀𐀀À𐀀𐀀Ⴀ𐀀ႠÀ𐀀ႠႠႠiÀÀÀÀii𐀀i𐀀ÀiiႠÀiÀ𐀀iႠႠiÀႠii𐀀i𐀀Ⴀ𐀀𐀀iႠႠÀ𐀀ႠiiiႠႠÀÀ𐀀iÀႠ|
- 𐀀iii𐀀ÀႠiႠÀ𐀀𐀀i𐀀ÀႠ𐀀𐀀ႠႠÀiႠ𐀀𐀀iႠ𐀀ႠiiႠiiႠÀ𐀀𐀀ႠiÀÀႠÀiÀႠ𐀀ÀႠ𐀀ႠÀi𐀀Ⴀi𐀀𐀀𐀀𐀀𐀀À𐀀𐀀𐀀i𐀀iÀ𐀀À𐀀ÀÀÀ𐀀ႠႠ𐀀iiÀ𐀀ÀÀÀႠÀ𐀀ႠÀႠÀiÀiiÀႠ|
- Ⴀ𐀀𐀀ႠႠ𐀀À𐀀ÀႠ𐀀ÀႠÀ𐀀𐀀iႠႠÀÀiÀႠ𐀀ÀiÀႠi𐀀ႠÀ𐀀𐀀𐀀𐀀Ⴀ𐀀iႠÀ𐀀iÀ𐀀iÀ𐀀iÀÀႠi𐀀iÀႠi𐀀ႠiiႠÀ𐀀À𐀀ႠႠÀÀi𐀀ႠႠ𐀀iiႠÀiႠ𐀀𐀀𐀀𐀀ႠႠႠÀႠiÀႠiÀÀ𐀀À|
- ÀႠ𐀀iiiÀÀ𐀀ÀႠiႠ𐀀ႠႠႠ𐀀iÀ𐀀ႠiႠ𐀀i𐀀ÀÀiႠ𐀀ÀiiႠႠiÀÀ𐀀ÀiႠiÀ𐀀i𐀀ÀiÀ𐀀ÀႠiÀiႠႠi𐀀iႠÀiÀÀႠႠiÀiႠÀႠi𐀀𐀀ႠiÀႠii𐀀ႠiiႠi𐀀Ⴀi𐀀ÀiÀÀ𐀀|
- ÀႠÀÀ𐀀i𐀀iႠÀÀÀႠ𐀀𐀀ÀႠÀÀiii𐀀𐀀iiÀiiႠÀÀႠiÀiÀÀ𐀀i𐀀i𐀀ႠiႠႠiႠÀiiÀႠ𐀀ႠႠÀiÀႠ𐀀𐀀iÀ𐀀Ⴀ𐀀iÀ𐀀ႠÀÀႠÀÀÀ𐀀𐀀i𐀀𐀀À𐀀𐀀ii𐀀À𐀀𐀀ႠÀ𐀀ႠႠႠ𐀀𐀀|
- Ⴀ𐀀i𐀀𐀀i𐀀𐀀Ⴀ𐀀iÀ𐀀ÀiႠiiÀÀiÀÀÀiÀiiႠႠ𐀀iႠiÀi𐀀Ⴀ𐀀ႠÀ𐀀Ⴀ𐀀𐀀𐀀ႠÀႠ𐀀ႠiiႠiiiႠÀÀiÀ𐀀ÀÀ𐀀ÀႠÀ𐀀iiÀÀiÀiÀÀÀႠႠÀÀii𐀀ႠÀÀiႠiÀÀ𐀀iiiÀ|
- ÀÀÀÀiႠiႠႠႠiႠ𐀀ႠÀÀÀ𐀀ÀÀiႠÀ𐀀ÀiႠÀႠÀႠႠÀÀႠiÀႠႠiiႠÀ𐀀ႠႠÀiႠ𐀀iÀႠ𐀀Ⴀ𐀀Ⴀ𐀀iႠÀႠi𐀀𐀀Ⴀ𐀀iÀ𐀀ÀႠ𐀀ÀÀႠ𐀀Ⴀi𐀀iႠÀ𐀀𐀀𐀀𐀀i𐀀i𐀀𐀀𐀀ÀႠiÀÀ𐀀i|
- i𐀀iÀ𐀀i𐀀iႠႠႠÀÀiiiႠi𐀀iÀÀ𐀀iႠÀÀ𐀀ii𐀀i𐀀𐀀ÀÀÀÀiÀiiÀiiiÀiÀi𐀀𐀀ႠႠÀႠႠÀiÀÀႠiႠႠiÀႠÀiႠႠ𐀀ႠႠႠÀႠÀႠ𐀀iiႠႠႠ𐀀iÀ𐀀iႠ𐀀iÀiÀiÀi|
- 𐀀𐀀iiÀ𐀀ÀႠ𐀀𐀀𐀀𐀀𐀀Àiii𐀀𐀀𐀀Ⴀ𐀀𐀀i𐀀ÀÀ𐀀iiÀiiiiÀ𐀀iႠiႠiÀႠÀႠÀiႠႠႠႠႠႠႠႠႠÀiiႠiÀႠÀ𐀀iiႠÀႠiႠႠÀiႠ𐀀iႠ𐀀iiႠÀ𐀀𐀀Àii𐀀i𐀀ÀႠÀÀiÀÀ|
- Ⴀ𐀀iÀiÀÀÀÀႠi𐀀ႠႠi𐀀ႠႠႠ𐀀ႠႠ𐀀iÀÀÀiÀi𐀀Ài𐀀𐀀𐀀ႠÀiႠiႠ𐀀𐀀Àiii𐀀ÀÀiႠÀiႠ𐀀𐀀i𐀀ÀÀiiÀiႠႠi𐀀À𐀀ÀႠÀႠiiiiii𐀀Ⴀ𐀀ÀႠ𐀀ႠႠႠ𐀀𐀀iႠႠႠႠiÀ|
- 𐀀𐀀𐀀i𐀀iÀ𐀀𐀀iÀ𐀀Ài𐀀𐀀ႠႠ𐀀ႠÀi𐀀𐀀ÀÀiiiႠiႠ𐀀iႠÀႠÀ𐀀ႠÀ𐀀ႠiiiiÀiႠÀiiiႠႠÀ𐀀Ⴀ𐀀𐀀𐀀ÀႠiÀႠiiiiႠiႠ𐀀Ⴀ𐀀ÀႠÀii𐀀i𐀀Ⴀ𐀀À𐀀iႠႠ𐀀iႠiiii𐀀|
- ÀÀÀႠႠÀ𐀀À𐀀À𐀀iႠ𐀀𐀀𐀀À𐀀ႠႠ𐀀ÀiÀiÀi𐀀Ⴀ𐀀iiႠiiÀႠÀႠ𐀀ႠÀi𐀀𐀀iÀiÀႠi𐀀À𐀀𐀀ÀႠ𐀀𐀀iႠÀ𐀀Ⴀ𐀀ÀiÀÀႠÀiÀ𐀀ႠႠႠiiÀi𐀀ÀႠiÀÀÀiႠႠiÀÀiÀႠÀႠÀ|
- iႠÀÀႠÀÀ𐀀𐀀𐀀iiiÀ𐀀À𐀀iÀÀi𐀀À𐀀ÀႠÀiÀii𐀀ႠႠii𐀀ႠႠ𐀀𐀀ÀႠ𐀀ÀÀ𐀀ÀÀÀÀi𐀀ÀႠ𐀀À𐀀ÀiiႠ𐀀ÀÀႠiႠ𐀀Ⴀ𐀀ÀÀ𐀀ႠႠ𐀀ႠႠÀ𐀀ႠႠႠ𐀀iiÀႠÀႠiႠi𐀀ii𐀀Ài|
- ÀiÀႠ𐀀À𐀀𐀀ii𐀀𐀀ႠႠiiiiႠiÀiႠiÀiÀ𐀀ÀiႠÀiiÀÀÀ𐀀𐀀Ⴀ𐀀ႠႠ𐀀iÀiႠ𐀀ii𐀀ႠÀ𐀀ႠiႠÀiiႠÀႠ𐀀Ài𐀀Àii𐀀iiÀiႠÀiႠႠÀiÀ𐀀i𐀀ႠÀ𐀀iÀÀႠii𐀀iÀ𐀀|
- Ⴀ𐀀ႠÀ𐀀Ⴀi𐀀iÀ𐀀Ⴀ𐀀𐀀iÀiÀ𐀀iႠi𐀀ႠÀiႠႠiÀ𐀀iႠiÀႠi𐀀𐀀iႠiႠႠ𐀀ÀÀi𐀀iÀႠႠ𐀀ႠÀ𐀀𐀀𐀀ႠႠiÀÀiႠႠႠ𐀀𐀀ႠႠႠႠÀiႠ𐀀Ài𐀀iÀႠii𐀀ÀႠii𐀀Ⴀ𐀀ÀႠÀ𐀀Ⴀi|
- À𐀀iႠႠiႠi𐀀iႠii𐀀Ⴀi𐀀ii𐀀iႠÀ𐀀ႠÀÀi𐀀iÀ𐀀iÀ𐀀ႠÀႠiႠÀႠi𐀀Ⴀ𐀀ÀႠႠiႠ𐀀iႠiÀ𐀀À𐀀ÀႠi𐀀𐀀iÀi𐀀À𐀀Ⴀ𐀀ႠႠi𐀀𐀀𐀀iႠÀ𐀀ÀႠÀ𐀀i𐀀i𐀀iÀႠÀÀÀ𐀀iႠ𐀀|
- 𐀀ႠႠႠႠႠ𐀀Ⴀ𐀀ÀÀ𐀀𐀀iÀÀႠiÀiiႠÀiႠ𐀀𐀀ÀÀiÀႠ𐀀ÀÀႠ𐀀À𐀀À𐀀iႠႠÀiႠÀiiiႠÀiÀÀÀ𐀀iႠႠႠÀÀiႠႠႠ𐀀ii𐀀ii𐀀iႠႠii𐀀iႠÀi𐀀𐀀𐀀ii𐀀ÀႠႠiႠÀ𐀀ႠႠ|
- 𐀀iiႠi𐀀ÀႠ𐀀ÀiႠÀÀiÀÀÀÀႠ𐀀ÀiiႠi𐀀iႠi𐀀ÀႠÀ𐀀𐀀𐀀iÀiÀ𐀀ႠiÀ𐀀ႠႠÀÀi𐀀Ⴀ𐀀i𐀀ႠÀႠiႠiႠiii𐀀ÀႠႠ𐀀𐀀ÀႠ𐀀𐀀ii𐀀ÀiiÀÀႠiÀ𐀀ÀÀÀiÀႠÀ𐀀Ài𐀀Ⴀ|
- 𐀀iႠ𐀀ႠႠႠÀÀ𐀀iiiÀÀႠii𐀀i𐀀ÀÀႠ𐀀ႠႠiႠႠႠiႠ𐀀i𐀀À𐀀ႠႠႠi𐀀Ài𐀀ႠÀ𐀀ÀႠiiiiႠiiႠႠi𐀀𐀀ÀႠ𐀀ႠiÀÀ𐀀iiiႠÀiႠi𐀀À𐀀𐀀i𐀀𐀀iiႠ𐀀À𐀀iiႠႠÀ𐀀iႠ|
- ÀႠ𐀀ႠႠႠÀii𐀀iiႠႠ𐀀iႠ𐀀iiႠii𐀀𐀀𐀀iiႠiÀ𐀀ႠႠÀÀÀiႠႠ𐀀À𐀀ႠႠႠ𐀀ÀiႠ𐀀ÀiႠ𐀀i𐀀ÀÀ𐀀Ⴀ𐀀Ⴀ𐀀iÀ𐀀iiiÀႠiÀႠiÀႠႠÀႠii𐀀ႠႠႠÀii𐀀i𐀀iiႠiÀ𐀀𐀀|
- ႠႠÀÀႠႠ𐀀iÀႠ𐀀iႠႠÀÀ𐀀ÀiႠ𐀀iÀႠႠႠ𐀀𐀀À𐀀ii𐀀𐀀À𐀀𐀀ÀႠႠႠÀႠiiႠ𐀀𐀀ii𐀀ႠႠÀႠiႠi𐀀ႠႠ𐀀i𐀀𐀀𐀀𐀀Ⴀ𐀀iÀiႠႠ𐀀À𐀀ÀႠႠႠÀÀ𐀀𐀀iÀႠi𐀀𐀀ÀiႠႠÀÀiÀÀ|
- ÀiႠii𐀀ÀÀႠi𐀀ÀႠi𐀀À𐀀ii𐀀iႠi𐀀ÀႠ𐀀À𐀀ÀÀႠႠ𐀀i𐀀ႠiiႠ𐀀ÀÀ𐀀À𐀀i𐀀𐀀i𐀀Ⴀ𐀀Ⴀi𐀀𐀀𐀀i𐀀Ài𐀀𐀀ÀႠႠi𐀀ÀiÀႠÀiiiÀÀႠႠi𐀀ÀiÀiႠÀÀiÀ𐀀𐀀ÀiiiiÀ|
- 𐀀ႠÀiÀ𐀀ႠႠ𐀀i𐀀ႠႠ𐀀𐀀𐀀𐀀𐀀𐀀iႠÀႠႠiiÀႠ𐀀𐀀i𐀀ႠႠ𐀀ႠႠႠ𐀀𐀀iÀiÀႠ𐀀À𐀀À𐀀𐀀ႠiiiႠiiiႠiႠ𐀀ÀiiiႠ𐀀À𐀀ÀႠႠÀ𐀀À𐀀𐀀𐀀ႠiႠi𐀀ႠÀÀႠiiႠႠႠႠ𐀀ÀႠ𐀀𐀀|
- Ⴀ𐀀ႠႠÀ𐀀iႠ𐀀ႠiiႠi𐀀ÀႠႠ𐀀iÀiÀÀiႠi𐀀iiÀ𐀀ႠႠႠiÀႠ𐀀ႠႠႠÀ𐀀ႠiႠ𐀀𐀀ÀႠÀÀ𐀀ÀႠႠiႠ𐀀𐀀iiÀÀ𐀀À𐀀iႠiÀ𐀀iÀ𐀀𐀀iiiiii𐀀ÀiႠ𐀀𐀀i𐀀Ài𐀀À𐀀𐀀i𐀀Ⴀ|
- Ⴀ𐀀iÀÀႠ𐀀ႠႠÀႠÀ𐀀ႠႠÀiiÀÀÀႠ𐀀i𐀀Ⴀ𐀀ႠÀ𐀀𐀀iႠႠႠႠiiႠႠႠÀiႠÀiiႠÀ𐀀Ⴀ𐀀Ⴀi𐀀𐀀ႠÀ𐀀ÀႠႠ𐀀ÀႠÀႠ𐀀Ⴀ𐀀iႠi𐀀ÀႠႠii𐀀ÀႠÀ𐀀Ⴀ𐀀Ài𐀀À𐀀ÀÀႠiႠႠii𐀀|
- ႠÀႠႠႠiႠ𐀀Ⴀ𐀀Ⴀ𐀀ႠiiႠÀ𐀀ÀiiÀi𐀀iÀÀiiiiÀႠÀႠÀႠiiႠ𐀀𐀀ႠÀÀ𐀀𐀀À𐀀ÀႠႠ𐀀𐀀iii𐀀iiÀ𐀀ႠiႠႠÀ𐀀ÀÀii𐀀ႠÀႠi𐀀𐀀Ⴀi𐀀ÀႠႠ𐀀ÀÀÀ𐀀iÀႠႠ𐀀ÀÀi𐀀i|
- ÀႠႠႠÀႠႠii𐀀Ài𐀀Ⴀi𐀀i𐀀i𐀀Ⴀ𐀀ÀÀÀiÀÀi𐀀ÀÀÀ𐀀i𐀀iÀ𐀀𐀀ႠiÀi𐀀𐀀𐀀ႠÀiÀ𐀀𐀀iÀÀႠႠ𐀀ႠႠႠႠÀÀÀÀiiÀ𐀀iiႠႠႠi𐀀ႠÀi𐀀ÀႠႠÀ𐀀ႠiiႠ𐀀𐀀i𐀀Ⴀi𐀀𐀀À|
- 𐀀À𐀀Ⴀ𐀀𐀀iÀÀiႠiiÀႠÀiÀႠÀႠႠ𐀀iႠႠÀiႠႠႠႠ𐀀iႠÀÀႠႠiႠ𐀀ÀiႠႠi𐀀𐀀ႠiÀ𐀀ÀiÀi𐀀i𐀀Ⴀi𐀀ႠÀiiÀႠÀi𐀀Ài𐀀ÀÀÀi𐀀𐀀ÀႠi𐀀ႠiႠÀiÀiႠ𐀀ii𐀀𐀀𐀀À|
- ÀႠiiÀi𐀀ႠÀÀiÀႠi𐀀ÀႠႠ𐀀ႠiÀiiႠiiႠႠ𐀀ii𐀀i𐀀𐀀𐀀𐀀i𐀀ႠႠÀႠÀÀiÀÀÀႠÀႠႠÀÀႠ𐀀ÀÀ𐀀ÀÀႠÀ𐀀𐀀ÀiႠ𐀀ႠႠ𐀀iÀ𐀀Ⴀ𐀀iÀႠႠ𐀀iÀiiii𐀀ii𐀀𐀀ႠႠႠ𐀀Ⴀ|
- ႠÀ𐀀ႠႠÀÀiႠႠ𐀀iႠႠႠÀႠ𐀀ႠႠ𐀀iႠ𐀀𐀀𐀀𐀀ႠiÀ𐀀ႠႠÀ𐀀Ⴀ𐀀ÀÀii𐀀Ⴀ𐀀ÀÀ𐀀ÀÀႠ𐀀ႠÀiÀ𐀀𐀀À𐀀À𐀀𐀀iႠi𐀀ÀႠႠႠႠ𐀀ႠႠÀÀ𐀀𐀀𐀀ÀÀiiႠÀÀ𐀀ႠႠႠiÀႠÀႠႠiÀႠ𐀀|
- 𐀀ÀiÀႠÀiiÀÀiiႠႠႠi𐀀ÀÀiႠ𐀀iႠႠÀ𐀀𐀀𐀀ÀႠႠÀ𐀀ႠÀÀႠ𐀀ÀÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀ÀႠ𐀀ႠÀiÀ𐀀iႠ𐀀ႠႠ𐀀𐀀À𐀀iii𐀀iiႠÀႠiႠÀႠ𐀀Ⴀ𐀀i𐀀𐀀ÀႠႠi𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀À𐀀Ⴀ𐀀|
- Àiiiii𐀀iႠÀÀÀiiႠiii𐀀𐀀ÀiÀႠÀÀiႠႠ𐀀iiÀÀႠ𐀀ႠiÀႠ𐀀𐀀ii𐀀iႠÀ𐀀iiႠ𐀀Ⴀ𐀀𐀀i𐀀Ⴀ𐀀i𐀀𐀀𐀀ÀႠiႠiႠi𐀀iiiÀii𐀀𐀀Àii𐀀À𐀀Ⴀ𐀀𐀀Ⴀ𐀀i𐀀ႠÀႠii𐀀Ⴀ|
- 𐀀iiÀႠiႠiÀႠ𐀀i𐀀iii𐀀Ⴀ𐀀i𐀀iÀÀi𐀀Ⴀii𐀀ÀiÀiiiÀႠÀ𐀀ÀÀႠ𐀀Ⴀ𐀀iiÀi𐀀i𐀀𐀀i𐀀ႠiiiÀႠႠႠiiÀ𐀀À𐀀𐀀iÀ𐀀iႠÀႠÀÀi𐀀ႠiÀႠÀ𐀀𐀀iÀÀ𐀀i𐀀𐀀ÀÀႠ𐀀|
- 𐀀iႠႠÀ𐀀ÀႠÀ𐀀iႠ𐀀Àii𐀀i𐀀𐀀ÀÀi𐀀𐀀𐀀iiႠÀ𐀀ii𐀀Ⴀ𐀀𐀀iႠi𐀀iÀ𐀀À𐀀ႠႠ𐀀À𐀀i𐀀𐀀iႠiiÀÀႠႠႠiÀÀiÀÀ𐀀𐀀𐀀À𐀀𐀀𐀀ႠÀႠ𐀀iÀ𐀀𐀀Ⴀ𐀀ႠႠii𐀀𐀀ÀÀႠÀi𐀀𐀀i|
- ÀiÀႠႠႠႠii𐀀ÀÀ𐀀𐀀𐀀Ⴀi𐀀À𐀀ÀႠiiÀi𐀀Ⴀii𐀀iÀÀ𐀀ႠiႠ𐀀ႠiiiႠÀÀiÀÀÀÀ𐀀ႠႠii𐀀À𐀀ÀiÀi𐀀ÀÀi𐀀iႠiÀi𐀀ÀiÀi𐀀ÀiÀႠ𐀀i𐀀Ⴀi𐀀𐀀𐀀ႠႠ𐀀ႠÀႠÀႠi|
- À𐀀𐀀i𐀀Ài𐀀𐀀ႠiႠÀႠiiႠiႠÀ𐀀𐀀ÀiÀ𐀀𐀀ÀÀႠႠႠ𐀀ႠiÀႠႠÀ𐀀Ⴀi𐀀𐀀ÀiÀ𐀀À𐀀iႠi𐀀𐀀ÀÀ𐀀iႠiႠႠ𐀀ÀÀ𐀀𐀀ÀiÀÀ𐀀ÀÀ𐀀i𐀀ÀÀ𐀀𐀀ÀÀႠii𐀀Ⴀ𐀀Ⴀ𐀀iiÀÀÀi𐀀À|
- i𐀀ႠiÀႠႠÀÀ𐀀𐀀ii𐀀ÀÀ𐀀iÀiÀႠÀiiii𐀀ÀiÀႠi𐀀i𐀀𐀀i𐀀𐀀iႠ𐀀iÀi𐀀ÀÀÀÀiႠiÀႠÀÀႠiiÀÀႠႠi𐀀iႠiiႠi𐀀Ⴀ𐀀𐀀ÀႠႠÀႠiႠႠÀ𐀀iiÀႠႠႠ𐀀𐀀Ⴀi𐀀Ⴀi|
- ႠÀႠ𐀀ÀႠ𐀀iႠÀ𐀀ႠiႠii𐀀ÀႠÀÀ𐀀i𐀀Ⴀi𐀀ႠiÀ𐀀ႠÀÀÀiÀÀÀႠ𐀀𐀀ႠiiႠ𐀀ÀႠiiÀiiႠႠi𐀀ÀiiႠ𐀀iÀႠÀi𐀀À𐀀ÀiÀÀÀi𐀀ÀÀႠႠiÀiႠ𐀀ii𐀀ႠÀiႠÀႠႠi^i |
+ Ⱡ𐀀ii𐀀ⱠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⱡ𐀀ⱠiiⱠ𐀀iii𐀀ÀⱠÀⱠⱠÀiiÀⱠÀiⱠi𐀀ÀÀ𐀀𐀀Ⱡ𐀀𐀀Ⱡ𐀀iÀⱠ𐀀i𐀀ÀÀⱠiiÀ𐀀Ⱡ𐀀À𐀀ii𐀀ÀÀ𐀀ÀⱠⱠ𐀀𐀀𐀀𐀀ii𐀀ⱠⱠiⱠⱠ𐀀ⱠÀ𐀀ÀÀiiⱠ|
+ 𐀀𐀀ÀⱠÀ𐀀𐀀iⱠÀ𐀀𐀀i𐀀𐀀𐀀𐀀ⱠⱠⱠiiiiÀÀ𐀀ⱠÀⱠÀi𐀀i𐀀ⱠⱠÀiÀⱠi𐀀𐀀À𐀀iÀ𐀀ÀÀÀ𐀀𐀀iÀⱠ𐀀𐀀Ⱡ𐀀𐀀ÀiⱠiiⱠⱠÀÀÀÀi𐀀ÀÀⱠ𐀀Àiii𐀀iⱠⱠⱠi𐀀ⱠÀi𐀀iⱠ𐀀ⱠiⱠ|
+ iiⱠ𐀀iÀÀiⱠⱠÀi𐀀ⱠⱠÀ𐀀𐀀ÀiiiiÀiⱠ𐀀iⱠÀiⱠiⱠⱠÀiÀ𐀀ⱠiiⱠⱠÀ𐀀Àii𐀀ⱠÀⱠiⱠÀⱠÀⱠii𐀀Ài𐀀ⱠⱠ𐀀ÀÀÀi𐀀ÀÀÀ𐀀iⱠ𐀀iⱠÀ𐀀iⱠi𐀀ÀiÀ𐀀ⱠⱠiÀ𐀀𐀀Ⱡi|
+ À𐀀𐀀iÀiÀÀÀÀⱠⱠⱠ𐀀iÀÀiⱠ𐀀À𐀀ⱠÀiiⱠ𐀀iiⱠⱠ𐀀iÀiⱠⱠÀⱠÀ𐀀Ài𐀀iⱠ𐀀𐀀iiⱠÀⱠiÀÀÀiÀiiÀ𐀀i𐀀ÀÀⱠ𐀀𐀀𐀀i𐀀𐀀ⱠⱠi𐀀À𐀀iⱠi𐀀ⱠⱠiiiÀⱠ𐀀ⱠÀiÀiⱠⱠ|
+ iÀiiiⱠÀÀ𐀀ⱠⱠⱠi𐀀À𐀀𐀀iiiÀÀiiÀⱠÀ𐀀À𐀀ⱠⱠ𐀀𐀀ⱠⱠⱠi𐀀iiÀⱠ𐀀ⱠⱠⱠÀiⱠiⱠiÀÀÀi𐀀iⱠ𐀀ÀÀiⱠ𐀀iÀÀi𐀀i𐀀𐀀ÀiÀⱠ𐀀𐀀iⱠ𐀀ÀÀiÀÀⱠ𐀀𐀀ⱠⱠ𐀀𐀀𐀀𐀀Ⱡ𐀀𐀀|
+ 𐀀ÀÀⱠÀ𐀀ÀÀiÀÀÀⱠiiⱠiiÀⱠÀiⱠÀiÀiⱠⱠ𐀀ÀÀÀⱠiiÀⱠ𐀀iÀi𐀀ⱠⱠ𐀀𐀀ÀÀ𐀀ÀiÀÀⱠi𐀀iÀⱠ𐀀À𐀀ⱠⱠÀ𐀀Ⱡiii𐀀ⱠiiⱠiÀⱠⱠiⱠÀⱠ𐀀ⱠÀÀⱠ𐀀À𐀀ÀiÀÀⱠⱠÀÀ|
+ ÀⱠiⱠiÀ𐀀i𐀀ⱠⱠiⱠⱠÀ𐀀ÀÀÀÀ𐀀𐀀ÀⱠⱠ𐀀ⱠÀÀiⱠ𐀀i𐀀Ⱡ𐀀ÀⱠi𐀀ⱠÀⱠÀ𐀀ⱠⱠ𐀀i𐀀iⱠÀi𐀀i𐀀𐀀À𐀀iÀiⱠⱠⱠ𐀀ÀiÀⱠÀ𐀀ÀÀÀi𐀀𐀀𐀀ⱠÀi𐀀𐀀À𐀀À𐀀𐀀iiⱠiÀi𐀀i𐀀Ⱡ|
+ 𐀀𐀀i𐀀ÀⱠⱠ𐀀𐀀𐀀iⱠⱠ𐀀À𐀀ÀⱠiÀ𐀀𐀀ⱠÀi𐀀𐀀iiiⱠ𐀀𐀀iⱠÀÀ𐀀ⱠiiÀⱠⱠÀ𐀀𐀀ⱠÀⱠⱠÀiⱠⱠÀⱠÀⱠiⱠⱠ𐀀𐀀𐀀iⱠⱠⱠiⱠⱠii𐀀ÀⱠi𐀀ÀÀⱠⱠi𐀀À𐀀Ⱡ𐀀ÀÀ𐀀Ⱡ𐀀iⱠiiⱠⱠ|
+ Ⱡ𐀀i𐀀𐀀ÀiÀⱠⱠⱠⱠⱠÀÀⱠⱠÀⱠ𐀀ii𐀀ÀⱠiⱠiii𐀀i𐀀i𐀀𐀀𐀀À𐀀ii𐀀iÀiiiÀÀⱠiiiⱠiiⱠÀ𐀀À𐀀𐀀ÀⱠ𐀀iÀÀiiÀiÀ𐀀iⱠi𐀀𐀀À𐀀ÀiiⱠ𐀀iÀ𐀀𐀀iⱠⱠÀÀⱠⱠiiÀ|
+ i𐀀𐀀𐀀ÀÀi𐀀ⱠⱠⱠⱠⱠÀiiÀ𐀀𐀀ii𐀀Ⱡ𐀀Ài𐀀iⱠiÀÀⱠÀ𐀀ÀⱠiⱠÀi𐀀𐀀iiⱠ𐀀i𐀀ⱠÀiⱠii𐀀𐀀À𐀀𐀀ⱠⱠÀⱠiÀiⱠÀÀi𐀀i𐀀ⱠÀiⱠⱠⱠ𐀀𐀀ÀiⱠⱠⱠÀÀi𐀀ÀⱠⱠÀiⱠ𐀀ⱠÀ|
+ 𐀀ÀiⱠⱠÀ𐀀𐀀𐀀i𐀀i𐀀i𐀀ⱠÀ𐀀ÀiiÀⱠ𐀀ÀÀÀi𐀀ⱠÀiÀⱠi𐀀ⱠÀiiÀÀÀiiiÀiⱠⱠiÀ𐀀ⱠⱠ𐀀iÀⱠÀⱠⱠiÀÀⱠÀⱠÀÀii𐀀Ⱡi𐀀iiÀÀÀiⱠ𐀀i𐀀𐀀i𐀀iiÀ𐀀𐀀𐀀ⱠÀiⱠ𐀀|
+ À𐀀ⱠⱠⱠⱠ𐀀ÀiⱠⱠiÀ𐀀i𐀀ÀⱠÀⱠiiÀiÀÀiⱠ𐀀𐀀𐀀Ⱡ𐀀ÀⱠi𐀀𐀀iⱠⱠⱠiiⱠÀi𐀀𐀀𐀀iⱠÀÀÀⱠi𐀀À𐀀iiiⱠÀⱠiÀ𐀀iⱠ𐀀ii𐀀𐀀𐀀ÀⱠⱠÀÀⱠⱠⱠⱠiÀi𐀀Àiii𐀀ii𐀀𐀀À|
+ i𐀀ÀⱠiⱠi𐀀ⱠiⱠ𐀀Ⱡi𐀀ⱠÀ𐀀𐀀𐀀ⱠÀiiiii𐀀Ⱡ𐀀iiiÀiiÀ𐀀𐀀𐀀À𐀀𐀀Ⱡ𐀀ⱠÀ𐀀ⱠⱠⱠiÀÀÀÀii𐀀i𐀀ÀiiⱠÀiÀ𐀀iⱠⱠiÀⱠii𐀀i𐀀Ⱡ𐀀𐀀iⱠⱠÀ𐀀ⱠiiiⱠⱠÀÀ𐀀iÀⱠ|
+ 𐀀iii𐀀ÀⱠiⱠÀ𐀀𐀀i𐀀ÀⱠ𐀀𐀀ⱠⱠÀiⱠ𐀀𐀀iⱠ𐀀ⱠiiⱠiiⱠÀ𐀀𐀀ⱠiÀÀⱠÀiÀⱠ𐀀ÀⱠ𐀀ⱠÀi𐀀Ⱡi𐀀𐀀𐀀𐀀𐀀À𐀀𐀀𐀀i𐀀iÀ𐀀À𐀀ÀÀÀ𐀀ⱠⱠ𐀀iiÀ𐀀ÀÀÀⱠÀ𐀀ⱠÀⱠÀiÀiiÀⱠ|
+ Ⱡ𐀀𐀀ⱠⱠ𐀀À𐀀ÀⱠ𐀀ÀⱠÀ𐀀𐀀iⱠⱠÀÀiÀⱠ𐀀ÀiÀⱠi𐀀ⱠÀ𐀀𐀀𐀀𐀀Ⱡ𐀀iⱠÀ𐀀iÀ𐀀iÀ𐀀iÀÀⱠi𐀀iÀⱠi𐀀ⱠiiⱠÀ𐀀À𐀀ⱠⱠÀÀi𐀀ⱠⱠ𐀀iiⱠÀiⱠ𐀀𐀀𐀀𐀀ⱠⱠⱠÀⱠiÀⱠiÀÀ𐀀À|
+ ÀⱠ𐀀iiiÀÀ𐀀ÀⱠiⱠ𐀀ⱠⱠⱠ𐀀iÀ𐀀ⱠiⱠ𐀀i𐀀ÀÀiⱠ𐀀ÀiiⱠⱠiÀÀ𐀀ÀiⱠiÀ𐀀i𐀀ÀiÀ𐀀ÀⱠiÀiⱠⱠi𐀀iⱠÀiÀÀⱠⱠiÀiⱠÀⱠi𐀀𐀀ⱠiÀⱠii𐀀ⱠiiⱠi𐀀Ⱡi𐀀ÀiÀÀ𐀀|
+ ÀⱠÀÀ𐀀i𐀀iⱠÀÀÀⱠ𐀀𐀀ÀⱠÀÀiii𐀀𐀀iiÀiiⱠÀÀⱠiÀiÀÀ𐀀i𐀀i𐀀ⱠiⱠⱠiⱠÀiiÀⱠ𐀀ⱠⱠÀiÀⱠ𐀀𐀀iÀ𐀀Ⱡ𐀀iÀ𐀀ⱠÀÀⱠÀÀÀ𐀀𐀀i𐀀𐀀À𐀀𐀀ii𐀀À𐀀𐀀ⱠÀ𐀀ⱠⱠⱠ𐀀𐀀|
+ Ⱡ𐀀i𐀀𐀀i𐀀𐀀Ⱡ𐀀iÀ𐀀ÀiⱠiiÀÀiÀÀÀiÀiiⱠⱠ𐀀iⱠiÀi𐀀Ⱡ𐀀ⱠÀ𐀀Ⱡ𐀀𐀀𐀀ⱠÀⱠ𐀀ⱠiiⱠiiiⱠÀÀiÀ𐀀ÀÀ𐀀ÀⱠÀ𐀀iiÀÀiÀiÀÀÀⱠⱠÀÀii𐀀ⱠÀÀiⱠiÀÀ𐀀iiiÀ|
+ ÀÀÀÀiⱠiⱠⱠⱠiⱠ𐀀ⱠÀÀÀ𐀀ÀÀiⱠÀ𐀀ÀiⱠÀⱠÀⱠⱠÀÀⱠiÀⱠⱠiiⱠÀ𐀀ⱠⱠÀiⱠ𐀀iÀⱠ𐀀Ⱡ𐀀Ⱡ𐀀iⱠÀⱠi𐀀𐀀Ⱡ𐀀iÀ𐀀ÀⱠ𐀀ÀÀⱠ𐀀Ⱡi𐀀iⱠÀ𐀀𐀀𐀀𐀀i𐀀i𐀀𐀀𐀀ÀⱠiÀÀ𐀀i|
+ i𐀀iÀ𐀀i𐀀iⱠⱠⱠÀÀiiiⱠi𐀀iÀÀ𐀀iⱠÀÀ𐀀ii𐀀i𐀀𐀀ÀÀÀÀiÀiiÀiiiÀiÀi𐀀𐀀ⱠⱠÀⱠⱠÀiÀÀⱠiⱠⱠiÀⱠÀiⱠⱠ𐀀ⱠⱠⱠÀⱠÀⱠ𐀀iiⱠⱠⱠ𐀀iÀ𐀀iⱠ𐀀iÀiÀiÀi|
+ 𐀀𐀀iiÀ𐀀ÀⱠ𐀀𐀀𐀀𐀀𐀀Àiii𐀀𐀀𐀀Ⱡ𐀀𐀀i𐀀ÀÀ𐀀iiÀiiiiÀ𐀀iⱠiⱠiÀⱠÀⱠÀiⱠⱠⱠⱠⱠⱠⱠⱠⱠÀiiⱠiÀⱠÀ𐀀iiⱠÀⱠiⱠⱠÀiⱠ𐀀iⱠ𐀀iiⱠÀ𐀀𐀀Àii𐀀i𐀀ÀⱠÀÀiÀÀ|
+ Ⱡ𐀀iÀiÀÀÀÀⱠi𐀀ⱠⱠi𐀀ⱠⱠⱠ𐀀ⱠⱠ𐀀iÀÀÀiÀi𐀀Ài𐀀𐀀𐀀ⱠÀiⱠiⱠ𐀀𐀀Àiii𐀀ÀÀiⱠÀiⱠ𐀀𐀀i𐀀ÀÀiiÀiⱠⱠi𐀀À𐀀ÀⱠÀⱠiiiiii𐀀Ⱡ𐀀ÀⱠ𐀀ⱠⱠⱠ𐀀𐀀iⱠⱠⱠⱠiÀ|
+ 𐀀𐀀𐀀i𐀀iÀ𐀀𐀀iÀ𐀀Ài𐀀𐀀ⱠⱠ𐀀ⱠÀi𐀀𐀀ÀÀiiiⱠiⱠ𐀀iⱠÀⱠÀ𐀀ⱠÀ𐀀ⱠiiiiÀiⱠÀiiiⱠⱠÀ𐀀Ⱡ𐀀𐀀𐀀ÀⱠiÀⱠiiiiⱠiⱠ𐀀Ⱡ𐀀ÀⱠÀii𐀀i𐀀Ⱡ𐀀À𐀀iⱠⱠ𐀀iⱠiiii𐀀|
+ ÀÀÀⱠⱠÀ𐀀À𐀀À𐀀iⱠ𐀀𐀀𐀀À𐀀ⱠⱠ𐀀ÀiÀiÀi𐀀Ⱡ𐀀iiⱠiiÀⱠÀⱠ𐀀ⱠÀi𐀀𐀀iÀiÀⱠi𐀀À𐀀𐀀ÀⱠ𐀀𐀀iⱠÀ𐀀Ⱡ𐀀ÀiÀÀⱠÀiÀ𐀀ⱠⱠⱠiiÀi𐀀ÀⱠiÀÀÀiⱠⱠiÀÀiÀⱠÀⱠÀ|
+ iⱠÀÀⱠÀÀ𐀀𐀀𐀀iiiÀ𐀀À𐀀iÀÀi𐀀À𐀀ÀⱠÀiÀii𐀀ⱠⱠii𐀀ⱠⱠ𐀀𐀀ÀⱠ𐀀ÀÀ𐀀ÀÀÀÀi𐀀ÀⱠ𐀀À𐀀ÀiiⱠ𐀀ÀÀⱠiⱠ𐀀Ⱡ𐀀ÀÀ𐀀ⱠⱠ𐀀ⱠⱠÀ𐀀ⱠⱠⱠ𐀀iiÀⱠÀⱠiⱠi𐀀ii𐀀Ài|
+ ÀiÀⱠ𐀀À𐀀𐀀ii𐀀𐀀ⱠⱠiiiiⱠiÀiⱠiÀiÀ𐀀ÀiⱠÀiiÀÀÀ𐀀𐀀Ⱡ𐀀ⱠⱠ𐀀iÀiⱠ𐀀ii𐀀ⱠÀ𐀀ⱠiⱠÀiiⱠÀⱠ𐀀Ài𐀀Àii𐀀iiÀiⱠÀiⱠⱠÀiÀ𐀀i𐀀ⱠÀ𐀀iÀÀⱠii𐀀iÀ𐀀|
+ Ⱡ𐀀ⱠÀ𐀀Ⱡi𐀀iÀ𐀀Ⱡ𐀀𐀀iÀiÀ𐀀iⱠi𐀀ⱠÀiⱠⱠiÀ𐀀iⱠiÀⱠi𐀀𐀀iⱠiⱠⱠ𐀀ÀÀi𐀀iÀⱠⱠ𐀀ⱠÀ𐀀𐀀𐀀ⱠⱠiÀÀiⱠⱠⱠ𐀀𐀀ⱠⱠⱠⱠÀiⱠ𐀀Ài𐀀iÀⱠii𐀀ÀⱠii𐀀Ⱡ𐀀ÀⱠÀ𐀀Ⱡi|
+ À𐀀iⱠⱠiⱠi𐀀iⱠii𐀀Ⱡi𐀀ii𐀀iⱠÀ𐀀ⱠÀÀi𐀀iÀ𐀀iÀ𐀀ⱠÀⱠiⱠÀⱠi𐀀Ⱡ𐀀ÀⱠⱠiⱠ𐀀iⱠiÀ𐀀À𐀀ÀⱠi𐀀𐀀iÀi𐀀À𐀀Ⱡ𐀀ⱠⱠi𐀀𐀀𐀀iⱠÀ𐀀ÀⱠÀ𐀀i𐀀i𐀀iÀⱠÀÀÀ𐀀iⱠ𐀀|
+ 𐀀ⱠⱠⱠⱠⱠ𐀀Ⱡ𐀀ÀÀ𐀀𐀀iÀÀⱠiÀiiⱠÀiⱠ𐀀𐀀ÀÀiÀⱠ𐀀ÀÀⱠ𐀀À𐀀À𐀀iⱠⱠÀiⱠÀiiiⱠÀiÀÀÀ𐀀iⱠⱠⱠÀÀiⱠⱠⱠ𐀀ii𐀀ii𐀀iⱠⱠii𐀀iⱠÀi𐀀𐀀𐀀ii𐀀ÀⱠⱠiⱠÀ𐀀ⱠⱠ|
+ 𐀀iiⱠi𐀀ÀⱠ𐀀ÀiⱠÀÀiÀÀÀÀⱠ𐀀ÀiiⱠi𐀀iⱠi𐀀ÀⱠÀ𐀀𐀀𐀀iÀiÀ𐀀ⱠiÀ𐀀ⱠⱠÀÀi𐀀Ⱡ𐀀i𐀀ⱠÀⱠiⱠiⱠiii𐀀ÀⱠⱠ𐀀𐀀ÀⱠ𐀀𐀀ii𐀀ÀiiÀÀⱠiÀ𐀀ÀÀÀiÀⱠÀ𐀀Ài𐀀Ⱡ|
+ 𐀀iⱠ𐀀ⱠⱠⱠÀÀ𐀀iiiÀÀⱠii𐀀i𐀀ÀÀⱠ𐀀ⱠⱠiⱠⱠⱠiⱠ𐀀i𐀀À𐀀ⱠⱠⱠi𐀀Ài𐀀ⱠÀ𐀀ÀⱠiiiiⱠiiⱠⱠi𐀀𐀀ÀⱠ𐀀ⱠiÀÀ𐀀iiiⱠÀiⱠi𐀀À𐀀𐀀i𐀀𐀀iiⱠ𐀀À𐀀iiⱠⱠÀ𐀀iⱠ|
+ ÀⱠ𐀀ⱠⱠⱠÀii𐀀iiⱠⱠ𐀀iⱠ𐀀iiⱠii𐀀𐀀𐀀iiⱠiÀ𐀀ⱠⱠÀÀÀiⱠⱠ𐀀À𐀀ⱠⱠⱠ𐀀ÀiⱠ𐀀ÀiⱠ𐀀i𐀀ÀÀ𐀀Ⱡ𐀀Ⱡ𐀀iÀ𐀀iiiÀⱠiÀⱠiÀⱠⱠÀⱠii𐀀ⱠⱠⱠÀii𐀀i𐀀iiⱠiÀ𐀀𐀀|
+ ⱠⱠÀÀⱠⱠ𐀀iÀⱠ𐀀iⱠⱠÀÀ𐀀ÀiⱠ𐀀iÀⱠⱠⱠ𐀀𐀀À𐀀ii𐀀𐀀À𐀀𐀀ÀⱠⱠⱠÀⱠiiⱠ𐀀𐀀ii𐀀ⱠⱠÀⱠiⱠi𐀀ⱠⱠ𐀀i𐀀𐀀𐀀𐀀Ⱡ𐀀iÀiⱠⱠ𐀀À𐀀ÀⱠⱠⱠÀÀ𐀀𐀀iÀⱠi𐀀𐀀ÀiⱠⱠÀÀiÀÀ|
+ ÀiⱠii𐀀ÀÀⱠi𐀀ÀⱠi𐀀À𐀀ii𐀀iⱠi𐀀ÀⱠ𐀀À𐀀ÀÀⱠⱠ𐀀i𐀀ⱠiiⱠ𐀀ÀÀ𐀀À𐀀i𐀀𐀀i𐀀Ⱡ𐀀Ⱡi𐀀𐀀𐀀i𐀀Ài𐀀𐀀ÀⱠⱠi𐀀ÀiÀⱠÀiiiÀÀⱠⱠi𐀀ÀiÀiⱠÀÀiÀ𐀀𐀀ÀiiiiÀ|
+ 𐀀ⱠÀiÀ𐀀ⱠⱠ𐀀i𐀀ⱠⱠ𐀀𐀀𐀀𐀀𐀀𐀀iⱠÀⱠⱠiiÀⱠ𐀀𐀀i𐀀ⱠⱠ𐀀ⱠⱠⱠ𐀀𐀀iÀiÀⱠ𐀀À𐀀À𐀀𐀀ⱠiiiⱠiiiⱠiⱠ𐀀ÀiiiⱠ𐀀À𐀀ÀⱠⱠÀ𐀀À𐀀𐀀𐀀ⱠiⱠi𐀀ⱠÀÀⱠiiⱠⱠⱠⱠ𐀀ÀⱠ𐀀𐀀|
+ Ⱡ𐀀ⱠⱠÀ𐀀iⱠ𐀀ⱠiiⱠi𐀀ÀⱠⱠ𐀀iÀiÀÀiⱠi𐀀iiÀ𐀀ⱠⱠⱠiÀⱠ𐀀ⱠⱠⱠÀ𐀀ⱠiⱠ𐀀𐀀ÀⱠÀÀ𐀀ÀⱠⱠiⱠ𐀀𐀀iiÀÀ𐀀À𐀀iⱠiÀ𐀀iÀ𐀀𐀀iiiiii𐀀ÀiⱠ𐀀𐀀i𐀀Ài𐀀À𐀀𐀀i𐀀Ⱡ|
+ Ⱡ𐀀iÀÀⱠ𐀀ⱠⱠÀⱠÀ𐀀ⱠⱠÀiiÀÀÀⱠ𐀀i𐀀Ⱡ𐀀ⱠÀ𐀀𐀀iⱠⱠⱠⱠiiⱠⱠⱠÀiⱠÀiiⱠÀ𐀀Ⱡ𐀀Ⱡi𐀀𐀀ⱠÀ𐀀ÀⱠⱠ𐀀ÀⱠÀⱠ𐀀Ⱡ𐀀iⱠi𐀀ÀⱠⱠii𐀀ÀⱠÀ𐀀Ⱡ𐀀Ài𐀀À𐀀ÀÀⱠiⱠⱠii𐀀|
+ ⱠÀⱠⱠⱠiⱠ𐀀Ⱡ𐀀Ⱡ𐀀ⱠiiⱠÀ𐀀ÀiiÀi𐀀iÀÀiiiiÀⱠÀⱠÀⱠiiⱠ𐀀𐀀ⱠÀÀ𐀀𐀀À𐀀ÀⱠⱠ𐀀𐀀iii𐀀iiÀ𐀀ⱠiⱠⱠÀ𐀀ÀÀii𐀀ⱠÀⱠi𐀀𐀀Ⱡi𐀀ÀⱠⱠ𐀀ÀÀÀ𐀀iÀⱠⱠ𐀀ÀÀi𐀀i|
+ ÀⱠⱠⱠÀⱠⱠii𐀀Ài𐀀Ⱡi𐀀i𐀀i𐀀Ⱡ𐀀ÀÀÀiÀÀi𐀀ÀÀÀ𐀀i𐀀iÀ𐀀𐀀ⱠiÀi𐀀𐀀𐀀ⱠÀiÀ𐀀𐀀iÀÀⱠⱠ𐀀ⱠⱠⱠⱠÀÀÀÀiiÀ𐀀iiⱠⱠⱠi𐀀ⱠÀi𐀀ÀⱠⱠÀ𐀀ⱠiiⱠ𐀀𐀀i𐀀Ⱡi𐀀𐀀À|
+ 𐀀À𐀀Ⱡ𐀀𐀀iÀÀiⱠiiÀⱠÀiÀⱠÀⱠⱠ𐀀iⱠⱠÀiⱠⱠⱠⱠ𐀀iⱠÀÀⱠⱠiⱠ𐀀ÀiⱠⱠi𐀀𐀀ⱠiÀ𐀀ÀiÀi𐀀i𐀀Ⱡi𐀀ⱠÀiiÀⱠÀi𐀀Ài𐀀ÀÀÀi𐀀𐀀ÀⱠi𐀀ⱠiⱠÀiÀiⱠ𐀀ii𐀀𐀀𐀀À|
+ ÀⱠiiÀi𐀀ⱠÀÀiÀⱠi𐀀ÀⱠⱠ𐀀ⱠiÀiiⱠiiⱠⱠ𐀀ii𐀀i𐀀𐀀𐀀𐀀i𐀀ⱠⱠÀⱠÀÀiÀÀÀⱠÀⱠⱠÀÀⱠ𐀀ÀÀ𐀀ÀÀⱠÀ𐀀𐀀ÀiⱠ𐀀ⱠⱠ𐀀iÀ𐀀Ⱡ𐀀iÀⱠⱠ𐀀iÀiiii𐀀ii𐀀𐀀ⱠⱠⱠ𐀀Ⱡ|
+ ⱠÀ𐀀ⱠⱠÀÀiⱠⱠ𐀀iⱠⱠⱠÀⱠ𐀀ⱠⱠ𐀀iⱠ𐀀𐀀𐀀𐀀ⱠiÀ𐀀ⱠⱠÀ𐀀Ⱡ𐀀ÀÀii𐀀Ⱡ𐀀ÀÀ𐀀ÀÀⱠ𐀀ⱠÀiÀ𐀀𐀀À𐀀À𐀀𐀀iⱠi𐀀ÀⱠⱠⱠⱠ𐀀ⱠⱠÀÀ𐀀𐀀𐀀ÀÀiiⱠÀÀ𐀀ⱠⱠⱠiÀⱠÀⱠⱠiÀⱠ𐀀|
+ 𐀀ÀiÀⱠÀiiÀÀiiⱠⱠⱠi𐀀ÀÀiⱠ𐀀iⱠⱠÀ𐀀𐀀𐀀ÀⱠⱠÀ𐀀ⱠÀÀⱠ𐀀ÀÀ𐀀𐀀Ⱡ𐀀ÀÀ𐀀ÀⱠ𐀀ⱠÀiÀ𐀀iⱠ𐀀ⱠⱠ𐀀𐀀À𐀀iii𐀀iiⱠÀⱠiⱠÀⱠ𐀀Ⱡ𐀀i𐀀𐀀ÀⱠⱠi𐀀𐀀ⱠⱠ𐀀𐀀𐀀𐀀À𐀀Ⱡ𐀀|
+ Àiiiii𐀀iⱠÀÀÀiiⱠiii𐀀𐀀ÀiÀⱠÀÀiⱠⱠ𐀀iiÀÀⱠ𐀀ⱠiÀⱠ𐀀𐀀ii𐀀iⱠÀ𐀀iiⱠ𐀀Ⱡ𐀀𐀀i𐀀Ⱡ𐀀i𐀀𐀀𐀀ÀⱠiⱠiⱠi𐀀iiiÀii𐀀𐀀Àii𐀀À𐀀Ⱡ𐀀𐀀Ⱡ𐀀i𐀀ⱠÀⱠii𐀀Ⱡ|
+ 𐀀iiÀⱠiⱠiÀⱠ𐀀i𐀀iii𐀀Ⱡ𐀀i𐀀iÀÀi𐀀Ⱡii𐀀ÀiÀiiiÀⱠÀ𐀀ÀÀⱠ𐀀Ⱡ𐀀iiÀi𐀀i𐀀𐀀i𐀀ⱠiiiÀⱠⱠⱠiiÀ𐀀À𐀀𐀀iÀ𐀀iⱠÀⱠÀÀi𐀀ⱠiÀⱠÀ𐀀𐀀iÀÀ𐀀i𐀀𐀀ÀÀⱠ𐀀|
+ 𐀀iⱠⱠÀ𐀀ÀⱠÀ𐀀iⱠ𐀀Àii𐀀i𐀀𐀀ÀÀi𐀀𐀀𐀀iiⱠÀ𐀀ii𐀀Ⱡ𐀀𐀀iⱠi𐀀iÀ𐀀À𐀀ⱠⱠ𐀀À𐀀i𐀀𐀀iⱠiiÀÀⱠⱠⱠiÀÀiÀÀ𐀀𐀀𐀀À𐀀𐀀𐀀ⱠÀⱠ𐀀iÀ𐀀𐀀Ⱡ𐀀ⱠⱠii𐀀𐀀ÀÀⱠÀi𐀀𐀀i|
+ ÀiÀⱠⱠⱠⱠii𐀀ÀÀ𐀀𐀀𐀀Ⱡi𐀀À𐀀ÀⱠiiÀi𐀀Ⱡii𐀀iÀÀ𐀀ⱠiⱠ𐀀ⱠiiiⱠÀÀiÀÀÀÀ𐀀ⱠⱠii𐀀À𐀀ÀiÀi𐀀ÀÀi𐀀iⱠiÀi𐀀ÀiÀi𐀀ÀiÀⱠ𐀀i𐀀Ⱡi𐀀𐀀𐀀ⱠⱠ𐀀ⱠÀⱠÀⱠi|
+ À𐀀𐀀i𐀀Ài𐀀𐀀ⱠiⱠÀⱠiiⱠiⱠÀ𐀀𐀀ÀiÀ𐀀𐀀ÀÀⱠⱠⱠ𐀀ⱠiÀⱠⱠÀ𐀀Ⱡi𐀀𐀀ÀiÀ𐀀À𐀀iⱠi𐀀𐀀ÀÀ𐀀iⱠiⱠⱠ𐀀ÀÀ𐀀𐀀ÀiÀÀ𐀀ÀÀ𐀀i𐀀ÀÀ𐀀𐀀ÀÀⱠii𐀀Ⱡ𐀀Ⱡ𐀀iiÀÀÀi𐀀À|
+ i𐀀ⱠiÀⱠⱠÀÀ𐀀𐀀ii𐀀ÀÀ𐀀iÀiÀⱠÀiiii𐀀ÀiÀⱠi𐀀i𐀀𐀀i𐀀𐀀iⱠ𐀀iÀi𐀀ÀÀÀÀiⱠiÀⱠÀÀⱠiiÀÀⱠⱠi𐀀iⱠiiⱠi𐀀Ⱡ𐀀𐀀ÀⱠⱠÀⱠiⱠⱠÀ𐀀iiÀⱠⱠⱠ𐀀𐀀Ⱡi𐀀Ⱡi|
+ ⱠÀⱠ𐀀ÀⱠ𐀀iⱠÀ𐀀ⱠiⱠii𐀀ÀⱠÀÀ𐀀i𐀀Ⱡi𐀀ⱠiÀ𐀀ⱠÀÀÀiÀÀÀⱠ𐀀𐀀ⱠiiⱠ𐀀ÀⱠiiÀiiⱠⱠi𐀀ÀiiⱠ𐀀iÀⱠÀi𐀀À𐀀ÀiÀÀÀi𐀀ÀÀⱠⱠiÀiⱠ𐀀ii𐀀ⱠÀiⱠÀⱠⱠi^i |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 5cf48412a8..15b05e4de7 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -95,6 +95,14 @@ describe('API', function()
assert_alive()
end)
+ it('input is processed first when followed immediately by non-fast events', function()
+ api.nvim_set_current_line('ab')
+ async_meths.nvim_input('x')
+ async_meths.nvim_exec_lua('_G.res1 = vim.api.nvim_get_current_line()', {})
+ async_meths.nvim_exec_lua('_G.res2 = vim.api.nvim_get_current_line()', {})
+ eq({ 'b', 'b' }, exec_lua('return { _G.res1, _G.res2 }'))
+ end)
+
it('does not set CA_COMMAND_BUSY #7254', function()
command('split')
command('autocmd WinEnter * startinsert')
@@ -2441,7 +2449,6 @@ describe('API', function()
}
it('returns {} for invalid channel', function()
- eq({}, api.nvim_get_chan_info(0))
eq({}, api.nvim_get_chan_info(-1))
-- more preallocated numbers might be added, try something high
eq({}, api.nvim_get_chan_info(10))
@@ -2449,6 +2456,8 @@ describe('API', function()
it('stream=stdio channel', function()
eq({ [1] = testinfo, [2] = stderr }, api.nvim_list_chans())
+ -- 0 should return current channel
+ eq(testinfo, api.nvim_get_chan_info(0))
eq(testinfo, api.nvim_get_chan_info(1))
eq(stderr, api.nvim_get_chan_info(2))
@@ -2514,6 +2523,7 @@ describe('API', function()
"Vim:Error invoking 'nvim_set_current_buf' on channel 3 (amazing-cat):\nWrong type for argument 1 when calling nvim_set_current_buf, expecting Buffer",
pcall_err(eval, 'rpcrequest(3, "nvim_set_current_buf", -1)')
)
+ eq(info, eval('rpcrequest(3, "nvim_get_chan_info", 0)'))
end)
it('stream=job :terminal channel', function()
diff --git a/test/functional/autocmd/textchanged_spec.lua b/test/functional/autocmd/textchanged_spec.lua
index 850d67a18d..d501560dc1 100644
--- a/test/functional/autocmd/textchanged_spec.lua
+++ b/test/functional/autocmd/textchanged_spec.lua
@@ -180,3 +180,14 @@ it('TextChangedI and TextChanged', function()
validate_mixed_textchangedi({ 's', '<esc>' })
validate_mixed_textchangedi({ 'S', '<esc>' })
end)
+
+-- oldtest: Test_TextChanged_with_norm()
+it('TextChanged is triggered after :norm that enters Insert mode', function()
+ exec([[
+ let g:a = 0
+ au TextChanged * let g:a += 1
+ ]])
+ eq(0, eval('g:a'))
+ feed(':norm! ia<CR>')
+ eq(1, eval('g:a'))
+end)
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index f4a9c0c8d7..cc58226f48 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -73,6 +73,7 @@ describe('startup', function()
os.remove(testfile)
end)
clear({ args = { '--startuptime', testfile } })
+ assert_log('Embedded', testfile, 100)
assert_log('sourcing', testfile, 100)
assert_log("require%('vim%._editor'%)", testfile, 100)
end)
diff --git a/test/functional/editor/fold_spec.lua b/test/functional/editor/fold_spec.lua
index 35632bb2f8..7950f6aea4 100644
--- a/test/functional/editor/fold_spec.lua
+++ b/test/functional/editor/fold_spec.lua
@@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local insert = helpers.insert
+local exec = helpers.exec
local feed = helpers.feed
local expect = helpers.expect
local command = helpers.command
@@ -9,7 +10,7 @@ local fn = helpers.fn
local eq = helpers.eq
local neq = helpers.neq
-describe('Folds', function()
+describe('Folding', function()
local tempfname = 'Xtest-fold.txt'
setup(clear)
@@ -423,6 +424,42 @@ a]],
eq(14, fn.foldclosedend(11))
end)
+ it('fdm=expr works correctly with :move #18668', function()
+ exec([[
+ set foldmethod=expr foldexpr=indent(v:lnum)
+ let content = ['', '', 'Line1', ' Line2', ' Line3',
+ \ 'Line4', ' Line5', ' Line6',
+ \ 'Line7', ' Line8', ' Line9']
+ call append(0, content)
+ normal! zM
+ call cursor(4, 1)
+ move 2
+ move 1
+ ]])
+ expect([[
+
+ Line2
+ Line3
+
+ Line1
+ Line4
+ Line5
+ Line6
+ Line7
+ Line8
+ Line9
+ ]])
+ eq(0, fn.foldlevel(1))
+ eq(3, fn.foldclosedend(2))
+ eq(0, fn.foldlevel(4))
+ eq(0, fn.foldlevel(5))
+ eq(0, fn.foldlevel(6))
+ eq(8, fn.foldclosedend(7))
+ eq(0, fn.foldlevel(9))
+ eq(11, fn.foldclosedend(10))
+ eq(0, fn.foldlevel(12))
+ end)
+
it('no folds remain if :delete makes buffer empty #19671', function()
command('setlocal foldmethod=manual')
fn.setline(1, { 'foo', 'bar', 'baz' })
@@ -445,7 +482,7 @@ a]],
eq(1, fn.foldlevel(1))
end)
- it('updates correctly with indent method and visual blockwise insertion #22898', function()
+ it('fdm=indent updates correctly with visual blockwise insertion #22898', function()
insert([[
a
b
@@ -456,7 +493,7 @@ a]],
eq(1, fn.foldlevel(2))
end)
- it("doesn't open folds with indent method when inserting lower foldlevel line", function()
+ it("fdm=indent doesn't open folds when inserting lower foldlevel line", function()
insert([[
insert an unindented line under this line
keep the lines under this line folded
diff --git a/test/functional/legacy/listlbr_utf8_spec.lua b/test/functional/legacy/listlbr_utf8_spec.lua
index d7f4c71af2..2788e7ae9f 100644
--- a/test/functional/legacy/listlbr_utf8_spec.lua
+++ b/test/functional/legacy/listlbr_utf8_spec.lua
@@ -1,12 +1,14 @@
-- Test for linebreak and list option in utf-8 mode
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local source = helpers.source
local feed = helpers.feed
+local exec = helpers.exec
local clear, expect = helpers.clear, helpers.expect
describe('linebreak', function()
- setup(clear)
+ before_each(clear)
-- luacheck: ignore 621 (Indentation)
-- luacheck: ignore 613 (Trailing whitespaces in a string)
@@ -208,4 +210,29 @@ describe('linebreak', function()
a b c¶
Screen attributes are the same!]])
end)
+
+ -- oldtest: Test_visual_ends_before_showbreak()
+ it("Visual area is correct when it ends before multibyte 'showbreak'", function()
+ local screen = Screen.new(60, 8)
+ screen:set_default_attr_ids({
+ [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText
+ [1] = { background = Screen.colors.LightGrey }, -- Visual
+ [2] = { bold = true }, -- ModeMsg
+ })
+ screen:attach()
+ exec([[
+ let &wrap = v:true
+ let &linebreak = v:true
+ let &showbreak = '↪ '
+ eval ['xxxxx ' .. 'y'->repeat(&columns - 6) .. ' zzzz']->setline(1)
+ normal! wvel
+ ]])
+ screen:expect([[
+ xxxxx |
+ {0:↪ }{1:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}^ {1: }|
+ {0:↪ }zzzz |
+ {0:~ }|*4
+ {2:-- VISUAL --} |
+ ]])
+ end)
end)
diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua
index 593d03fa90..a87398b158 100644
--- a/test/functional/legacy/messages_spec.lua
+++ b/test/functional/legacy/messages_spec.lua
@@ -458,7 +458,6 @@ describe('messages', function()
-- do the same with 'cmdheight' set to 2
feed('q')
command('set ch=2')
- command('mode') -- FIXME: bottom is invalid after scrolling
screen:expect([[
^ |
{0:~ }|*7
@@ -688,7 +687,6 @@ describe('messages', function()
tabnew
set cmdheight=2
]])
- command('mode') -- FIXME: bottom is invalid after scrolling
screen:expect([[
{2: [No Name] }{1: [No Name] }{3: }{2:X}|
^ |
diff --git a/test/functional/legacy/number_spec.lua b/test/functional/legacy/number_spec.lua
new file mode 100644
index 0000000000..c112532eed
--- /dev/null
+++ b/test/functional/legacy/number_spec.lua
@@ -0,0 +1,306 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local command = helpers.command
+local exec = helpers.exec
+local feed = helpers.feed
+
+describe("'number' and 'relativenumber'", function()
+ before_each(clear)
+
+ -- oldtest: Test_relativenumber_colors()
+ it('LineNr, LineNrAbove and LineNrBelow', function()
+ local screen = Screen.new(50, 10)
+ screen:set_default_attr_ids({
+ [1] = { foreground = Screen.colors.Red },
+ [2] = { foreground = Screen.colors.Blue },
+ [3] = { foreground = Screen.colors.Green },
+ })
+ screen:attach()
+ exec([[
+ call setline(1, range(200))
+ 111
+ set number relativenumber
+ hi LineNr guifg=red
+ ]])
+ screen:expect([[
+ {1: 4 }106 |
+ {1: 3 }107 |
+ {1: 2 }108 |
+ {1: 1 }109 |
+ {1:111 }^110 |
+ {1: 1 }111 |
+ {1: 2 }112 |
+ {1: 3 }113 |
+ {1: 4 }114 |
+ |
+ ]])
+ command('hi LineNrAbove guifg=blue')
+ screen:expect([[
+ {2: 4 }106 |
+ {2: 3 }107 |
+ {2: 2 }108 |
+ {2: 1 }109 |
+ {1:111 }^110 |
+ {1: 1 }111 |
+ {1: 2 }112 |
+ {1: 3 }113 |
+ {1: 4 }114 |
+ |
+ ]])
+ command('hi LineNrBelow guifg=green')
+ screen:expect([[
+ {2: 4 }106 |
+ {2: 3 }107 |
+ {2: 2 }108 |
+ {2: 1 }109 |
+ {1:111 }^110 |
+ {3: 1 }111 |
+ {3: 2 }112 |
+ {3: 3 }113 |
+ {3: 4 }114 |
+ |
+ ]])
+ command('hi clear LineNrAbove')
+ screen:expect([[
+ {1: 4 }106 |
+ {1: 3 }107 |
+ {1: 2 }108 |
+ {1: 1 }109 |
+ {1:111 }^110 |
+ {3: 1 }111 |
+ {3: 2 }112 |
+ {3: 3 }113 |
+ {3: 4 }114 |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_relativenumber_colors_wrapped()
+ it('LineNr, LineNrAbove and LineNrBelow with wrapped lines', function()
+ local screen = Screen.new(50, 20)
+ screen:set_default_attr_ids({
+ [1] = { background = Screen.colors.Red, foreground = Screen.colors.Black },
+ [2] = { background = Screen.colors.Blue, foreground = Screen.colors.Black },
+ [3] = { background = Screen.colors.Green, foreground = Screen.colors.Black },
+ [4] = { bold = true, foreground = Screen.colors.Blue },
+ })
+ screen:attach()
+ exec([[
+ set display=lastline scrolloff=0
+ call setline(1, range(200)->map('v:val->string()->repeat(40)'))
+ 111
+ set number relativenumber
+ hi LineNr guibg=red guifg=black
+ hi LineNrAbove guibg=blue guifg=black
+ hi LineNrBelow guibg=green guifg=black
+ ]])
+ screen:expect([[
+ {2: 2 }1081081081081081081081081081081081081081081081|
+ {2: }0810810810810810810810810810810810810810810810|
+ {2: }8108108108108108108108108108 |
+ {2: 1 }1091091091091091091091091091091091091091091091|
+ {2: }0910910910910910910910910910910910910910910910|
+ {2: }9109109109109109109109109109 |
+ {1:111 }^1101101101101101101101101101101101101101101101|
+ {1: }1011011011011011011011011011011011011011011011|
+ {1: }0110110110110110110110110110 |
+ {3: 1 }1111111111111111111111111111111111111111111111|
+ {3: }1111111111111111111111111111111111111111111111|
+ {3: }1111111111111111111111111111 |
+ {3: 2 }1121121121121121121121121121121121121121121121|
+ {3: }1211211211211211211211211211211211211211211211|
+ {3: }2112112112112112112112112112 |
+ {3: 3 }1131131131131131131131131131131131131131131131|
+ {3: }1311311311311311311311311311311311311311311311|
+ {3: }3113113113113113113113113113 |
+ {3: 4 }1141141141141141141141141141141141141141141{4:@@@}|
+ |
+ ]])
+ feed('k')
+ screen:expect([[
+ {2: 1 }1081081081081081081081081081081081081081081081|
+ {2: }0810810810810810810810810810810810810810810810|
+ {2: }8108108108108108108108108108 |
+ {1:110 }^1091091091091091091091091091091091091091091091|
+ {1: }0910910910910910910910910910910910910910910910|
+ {1: }9109109109109109109109109109 |
+ {3: 1 }1101101101101101101101101101101101101101101101|
+ {3: }1011011011011011011011011011011011011011011011|
+ {3: }0110110110110110110110110110 |
+ {3: 2 }1111111111111111111111111111111111111111111111|
+ {3: }1111111111111111111111111111111111111111111111|
+ {3: }1111111111111111111111111111 |
+ {3: 3 }1121121121121121121121121121121121121121121121|
+ {3: }1211211211211211211211211211211211211211211211|
+ {3: }2112112112112112112112112112 |
+ {3: 4 }1131131131131131131131131131131131131131131131|
+ {3: }1311311311311311311311311311311311311311311311|
+ {3: }3113113113113113113113113113 |
+ {3: 5 }1141141141141141141141141141141141141141141{4:@@@}|
+ |
+ ]])
+ feed('2j')
+ screen:expect([[
+ {2: 3 }1081081081081081081081081081081081081081081081|
+ {2: }0810810810810810810810810810810810810810810810|
+ {2: }8108108108108108108108108108 |
+ {2: 2 }1091091091091091091091091091091091091091091091|
+ {2: }0910910910910910910910910910910910910910910910|
+ {2: }9109109109109109109109109109 |
+ {2: 1 }1101101101101101101101101101101101101101101101|
+ {2: }1011011011011011011011011011011011011011011011|
+ {2: }0110110110110110110110110110 |
+ {1:112 }^1111111111111111111111111111111111111111111111|
+ {1: }1111111111111111111111111111111111111111111111|
+ {1: }1111111111111111111111111111 |
+ {3: 1 }1121121121121121121121121121121121121121121121|
+ {3: }1211211211211211211211211211211211211211211211|
+ {3: }2112112112112112112112112112 |
+ {3: 2 }1131131131131131131131131131131131131131131131|
+ {3: }1311311311311311311311311311311311311311311311|
+ {3: }3113113113113113113113113113 |
+ {3: 3 }1141141141141141141141141141141141141141141{4:@@@}|
+ |
+ ]])
+ feed('2j')
+ screen:expect([[
+ {2: 5 }1081081081081081081081081081081081081081081081|
+ {2: }0810810810810810810810810810810810810810810810|
+ {2: }8108108108108108108108108108 |
+ {2: 4 }1091091091091091091091091091091091091091091091|
+ {2: }0910910910910910910910910910910910910910910910|
+ {2: }9109109109109109109109109109 |
+ {2: 3 }1101101101101101101101101101101101101101101101|
+ {2: }1011011011011011011011011011011011011011011011|
+ {2: }0110110110110110110110110110 |
+ {2: 2 }1111111111111111111111111111111111111111111111|
+ {2: }1111111111111111111111111111111111111111111111|
+ {2: }1111111111111111111111111111 |
+ {2: 1 }1121121121121121121121121121121121121121121121|
+ {2: }1211211211211211211211211211211211211211211211|
+ {2: }2112112112112112112112112112 |
+ {1:114 }^1131131131131131131131131131131131131131131131|
+ {1: }1311311311311311311311311311311311311311311311|
+ {1: }3113113113113113113113113113 |
+ {3: 1 }1141141141141141141141141141141141141141141{4:@@@}|
+ |
+ ]])
+ feed('k')
+ screen:expect([[
+ {2: 4 }1081081081081081081081081081081081081081081081|
+ {2: }0810810810810810810810810810810810810810810810|
+ {2: }8108108108108108108108108108 |
+ {2: 3 }1091091091091091091091091091091091091091091091|
+ {2: }0910910910910910910910910910910910910910910910|
+ {2: }9109109109109109109109109109 |
+ {2: 2 }1101101101101101101101101101101101101101101101|
+ {2: }1011011011011011011011011011011011011011011011|
+ {2: }0110110110110110110110110110 |
+ {2: 1 }1111111111111111111111111111111111111111111111|
+ {2: }1111111111111111111111111111111111111111111111|
+ {2: }1111111111111111111111111111 |
+ {1:113 }^1121121121121121121121121121121121121121121121|
+ {1: }1211211211211211211211211211211211211211211211|
+ {1: }2112112112112112112112112112 |
+ {3: 1 }1131131131131131131131131131131131131131131131|
+ {3: }1311311311311311311311311311311311311311311311|
+ {3: }3113113113113113113113113113 |
+ {3: 2 }1141141141141141141141141141141141141141141{4:@@@}|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_relativenumber_callback()
+ it('relative line numbers are updated if cursor is moved from timer', function()
+ local screen = Screen.new(50, 8)
+ screen:set_default_attr_ids({
+ [1] = { foreground = Screen.colors.Brown }, -- LineNr
+ [2] = { bold = true, foreground = Screen.colors.Blue1 }, -- NonText
+ })
+ screen:attach()
+ exec([[
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set relativenumber
+ call cursor(4, 1)
+
+ func Func(timer)
+ call cursor(1, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ ]])
+ screen:expect({
+ grid = [[
+ {1: 3 }aaaaa |
+ {1: 2 }bbbbb |
+ {1: 1 }ccccc |
+ {1: 0 }^ddddd |
+ {2:~ }|*3
+ |
+ ]],
+ timeout = 100,
+ })
+ screen:expect({
+ grid = [[
+ {1: 0 }^aaaaa |
+ {1: 1 }bbbbb |
+ {1: 2 }ccccc |
+ {1: 3 }ddddd |
+ {2:~ }|*3
+ |
+ ]],
+ })
+ end)
+
+ -- oldtest: Test_number_insert_delete_lines()
+ it('line numbers are updated when deleting/inserting lines', function()
+ local screen = Screen.new(50, 8)
+ screen:set_default_attr_ids({
+ [1] = { foreground = Screen.colors.Brown }, -- LineNr
+ [2] = { bold = true, foreground = Screen.colors.Blue1 }, -- NonText
+ })
+ screen:attach()
+ exec([[
+ call setline(1, range(1, 7))
+ set number
+ call cursor(2, 1)
+ ]])
+ local snapshot1 = [[
+ {1: 1 }1 |
+ {1: 2 }^2 |
+ {1: 3 }3 |
+ {1: 4 }4 |
+ {1: 5 }5 |
+ {1: 6 }6 |
+ {1: 7 }7 |
+ |
+ ]]
+ screen:expect(snapshot1)
+ feed('dd')
+ screen:expect([[
+ {1: 1 }1 |
+ {1: 2 }^3 |
+ {1: 3 }4 |
+ {1: 4 }5 |
+ {1: 5 }6 |
+ {1: 6 }7 |
+ {2:~ }|
+ |
+ ]])
+ feed('P')
+ screen:expect(snapshot1)
+ feed('2dd')
+ screen:expect([[
+ {1: 1 }1 |
+ {1: 2 }^4 |
+ {1: 3 }5 |
+ {1: 4 }6 |
+ {1: 5 }7 |
+ {2:~ }|*2
+ |
+ ]])
+ feed('P')
+ screen:expect(snapshot1)
+ end)
+end)
diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua
index f86d73a82b..6f76f46e75 100644
--- a/test/functional/lua/snippet_spec.lua
+++ b/test/functional/lua/snippet_spec.lua
@@ -216,4 +216,16 @@ describe('vim.snippet', function()
feed('foo')
eq({ 'public function foo() {', '\t', '}' }, buf_lines(0))
end)
+
+ it('jumps through adjacent tabstops', function()
+ test_expand_success(
+ { 'for i=1,${1:to}${2:,step} do\n\t$3\nend' },
+ { 'for i=1,to,step do', '\t', 'end' }
+ )
+ feed('10')
+ feed('<Tab>')
+ poke_eventloop()
+ feed(',2')
+ eq({ 'for i=1,10,2 do', '\t', 'end' }, buf_lines(0))
+ end)
end)
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
index ee8dfc7a60..705c182df7 100644
--- a/test/functional/plugin/lsp/diagnostic_spec.lua
+++ b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -193,7 +193,7 @@ describe('vim.lsp.diagnostic', function()
PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
underline = false,
virtual_text = {
- severity_limit = ...
+ severity = { min = ... }
},
})
@@ -212,11 +212,11 @@ describe('vim.lsp.diagnostic', function()
end
-- No messages with Error or higher
- eq(0, get_extmark_count_with_severity('Error'))
+ eq(0, get_extmark_count_with_severity('ERROR'))
-- But now we don't filter it
- eq(1, get_extmark_count_with_severity('Warning'))
- eq(1, get_extmark_count_with_severity('Hint'))
+ eq(1, get_extmark_count_with_severity('WARN'))
+ eq(1, get_extmark_count_with_severity('HINT'))
end)
it('correctly handles UTF-16 offsets', function()
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index ce76861b9a..fb153b83ca 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -139,30 +139,6 @@ describe('LSP', function()
end)
end)
end)
-
- describe('lsp._cmd_parts test', function()
- local function _cmd_parts(input)
- return exec_lua(
- [[
- lsp = require('vim.lsp')
- return lsp._cmd_parts(...)
- ]],
- input
- )
- end
- it('should valid cmd argument', function()
- eq(true, pcall(_cmd_parts, { 'nvim' }))
- eq(true, pcall(_cmd_parts, { 'nvim', '--head' }))
- end)
-
- it('should invalid cmd argument', function()
- eq('.../lsp.lua:0: cmd: expected list, got nvim', pcall_err(_cmd_parts, 'nvim'))
- eq(
- '.../lsp.lua:0: cmd argument: expected string, got number',
- pcall_err(_cmd_parts, { 'nvim', 1 })
- )
- end)
- end)
end)
describe('LSP', function()
diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua
index b878b9eb43..05db1b3c8c 100644
--- a/test/functional/terminal/helpers.lua
+++ b/test/functional/terminal/helpers.lua
@@ -97,18 +97,19 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts)
[1] = { reverse = true }, -- focused cursor
[2] = { background = 11 }, -- unfocused cursor
[3] = { bold = true },
- [4] = { foreground = 12 },
+ [4] = { foreground = 12 }, -- NonText in :terminal session
[5] = { bold = true, reverse = true },
- -- 6 was a duplicate item
- [7] = { foreground = 130 },
- [8] = { foreground = 15, background = 1 }, -- error message
+ [6] = { foreground = 81 }, -- SpecialKey in :terminal session
+ [7] = { foreground = 130 }, -- LineNr in host session
+ [8] = { foreground = 15, background = 1 }, -- ErrorMsg in :terminal session
[9] = { foreground = 4 },
- [10] = { foreground = 121 }, -- "Press ENTER" in embedded :terminal session.
- [11] = { foreground = tonumber('0x00000b') },
+ [10] = { foreground = 121 }, -- MoreMsg in :terminal session
+ [11] = { foreground = 11 }, -- LineNr in :terminal session
[12] = { underline = true },
[13] = { underline = true, reverse = true },
[14] = { underline = true, reverse = true, bold = true },
[15] = { underline = true, foreground = 12 },
+ [16] = { background = 248, foreground = 0 }, -- Visual in :terminal session
})
screen:attach(screen_opts or { rgb = false })
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 121664ae84..d63338845a 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -338,18 +338,13 @@ describe('TUI', function()
feed_data('\022\007') -- ctrl+g
feed_data('\022\022') -- ctrl+v
feed_data('\022\013') -- ctrl+m
- local attrs = screen:get_default_attr_ids()
- attrs[11] = { foreground = 81 }
- screen:expect(
- [[
- {11:^G^V^M}{1: } |
+ screen:expect([[
+ {6:^G^V^M}{1: } |
{4:~ }|*3
{5:[No Name] [+] }|
{3:-- INSERT --} |
{3:-- TERMINAL --} |
- ]],
- attrs
- )
+ ]])
end)
local function test_mouse_wheel(esc)
@@ -660,6 +655,20 @@ describe('TUI', function()
end
screen:expect_unchanged()
if esc then
+ feed_data('\027[<64;5;1M')
+ else
+ api.nvim_input_mouse('wheel', 'up', '', 0, 0, 4)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }{14: foo }{4: }|
+ {4:~ }{13: bar }{4: }|
+ {4:~ }{13: baz }{4: }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
feed_data('\027[<35;7;4M')
else
api.nvim_input_mouse('move', '', '', 0, 3, 6)
@@ -674,6 +683,20 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
if esc then
+ feed_data('\027[<65;7;4M')
+ else
+ api.nvim_input_mouse('wheel', 'down', '', 0, 3, 6)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }{13: foo }{4: }|
+ {4:~ }{14: bar }{4: }|
+ {4:~ }{13: baz }{4: }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
feed_data('\027[<0;7;3M')
else
api.nvim_input_mouse('left', 'press', '', 0, 2, 6)
@@ -978,8 +1001,7 @@ describe('TUI', function()
it('paste: select-mode', function()
feed_data('ithis is line 1\nthis is line 2\nline 3 is here\n\027')
wait_for_mode('n')
- screen:expect {
- grid = [[
+ screen:expect([[
this is line 1 |
this is line 2 |
line 3 is here |
@@ -987,24 +1009,29 @@ describe('TUI', function()
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Select-mode. Use <C-n> to move down.
feed_data('gg04lgh\14\14')
- wait_for_mode('s')
+ screen:expect([[
+ this{16: is line 1} |
+ {16:this is line 2} |
+ {16:line}{1: }3 is here |
+ |
+ {5:[No Name] [+] }|
+ {3:-- SELECT --} |
+ {3:-- TERMINAL --} |
+ ]])
feed_data('\027[200~')
feed_data('just paste it™')
feed_data('\027[201~')
- screen:expect {
- grid = [[
+ screen:expect([[
thisjust paste it{1:™}3 is here |
|
{4:~ }|*2
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Undo.
feed_data('u')
expect_child_buf_lines {
@@ -1028,27 +1055,23 @@ describe('TUI', function()
child_exec_lua('vim.o.statusline="^^^^^^^"')
child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test'))
feed_data('i')
- screen:expect {
- grid = [[
+ screen:expect([[
tty ready |
{1: } |
|*2
{5:^^^^^^^ }|
{3:-- TERMINAL --} |*2
- ]],
- }
+ ]])
feed_data('\027[200~')
feed_data('hallo')
feed_data('\027[201~')
- screen:expect {
- grid = [[
+ screen:expect([[
tty ready |
hallo{1: } |
|*2
{5:^^^^^^^ }|
{3:-- TERMINAL --} |*2
- ]],
- }
+ ]])
end)
it('paste: normal-mode (+CRLF #10872)', function()
@@ -1060,38 +1083,27 @@ describe('TUI', function()
local expected_crlf = { 'line 1', 'ESC:\027 / CR: ', 'x' }
local expected_grid1 = [[
line 1 |
- ESC:{11:^[} / CR: |
+ ESC:{6:^[} / CR: |
{1:x} |
{4:~ }|
{5:[No Name] [+] 3,1 All}|
|
{3:-- TERMINAL --} |
]]
- local expected_attr = {
- [1] = { reverse = true },
- [3] = { bold = true },
- [4] = { foreground = tonumber('0x00000c') },
- [5] = { bold = true, reverse = true },
- [11] = { foreground = tonumber('0x000051') },
- [12] = { reverse = true, foreground = tonumber('0x000051') },
- }
-- "bracketed paste"
feed_data('\027[200~' .. table.concat(expected_lf, '\n') .. '\027[201~')
- screen:expect { grid = expected_grid1, attr_ids = expected_attr }
+ screen:expect(expected_grid1)
-- Dot-repeat/redo.
feed_data('.')
- screen:expect {
- grid = [[
- ESC:{11:^[} / CR: |
+ screen:expect([[
+ ESC:{6:^[} / CR: |
xline 1 |
- ESC:{11:^[} / CR: |
+ ESC:{6:^[} / CR: |
{1:x} |
{5:[No Name] [+] 5,1 Bot}|
|
{3:-- TERMINAL --} |
- ]],
- attr_ids = expected_attr,
- }
+ ]])
-- Undo.
feed_data('u')
expect_child_buf_lines(expected_crlf)
@@ -1103,7 +1115,7 @@ describe('TUI', function()
wait_for_mode('n')
-- CRLF input
feed_data('\027[200~' .. table.concat(expected_lf, '\r\n') .. '\027[201~')
- screen:expect { grid = expected_grid1, attr_ids = expected_attr }
+ screen:expect(expected_grid1)
expect_child_buf_lines(expected_crlf)
end)
@@ -1112,35 +1124,39 @@ describe('TUI', function()
feed_data('\027:""') -- Enter Cmdline-mode.
feed_data('\027[D') -- <Left> to place cursor between quotes.
wait_for_mode('c')
+ screen:expect([[
+ foo |
+ |
+ {4:~ }|*2
+ {5:[No Name] [+] }|
+ :"{1:"} |
+ {3:-- TERMINAL --} |
+ ]])
-- "bracketed paste"
feed_data('\027[200~line 1\nline 2\n')
wait_for_mode('c')
feed_data('line 3\nline 4\n\027[201~')
wait_for_mode('c')
- screen:expect {
- grid = [[
+ screen:expect([[
foo |
|
{4:~ }|*2
{5:[No Name] [+] }|
:"line 1{1:"} |
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Dot-repeat/redo.
feed_data('\027[27u')
wait_for_mode('n')
feed_data('.')
- screen:expect {
- grid = [[
+ screen:expect([[
foo |*2
{1: } |
{4:~ }|
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
end)
it('paste: cmdline-mode collects chunks of unfinished line', function()
@@ -1148,11 +1164,13 @@ describe('TUI', function()
retry(nil, nil, function()
local _, cmdline = child_session:request('nvim_call_function', 'getcmdline', {})
eq(expected, cmdline)
+ local _, pos = child_session:request('nvim_call_function', 'getcmdpos', {})
+ eq(#expected, pos) -- Cursor is just before the last char.
end)
end
feed_data('\027:""') -- Enter Cmdline-mode.
feed_data('\027[D') -- <Left> to place cursor between quotes.
- wait_for_mode('c')
+ expect_cmdline('""')
feed_data('\027[200~stuff 1 ')
expect_cmdline('"stuff 1 "')
-- Discards everything after the first line.
@@ -1180,20 +1198,17 @@ describe('TUI', function()
-- Prepare something for dot-repeat/redo.
feed_data('ifoo\n\027[27u')
wait_for_mode('n')
- screen:expect {
- grid = [[
+ screen:expect([[
foo |
{1: } |
{4:~ }|*2
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Start pasting...
feed_data('\027[200~line 1\nline 2\n')
- screen:expect {
- grid = [[
+ screen:expect([[
foo |
|
{5: }|
@@ -1201,52 +1216,39 @@ describe('TUI', function()
{8:ake fail} |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Remaining chunks are discarded after vim.paste() failure.
feed_data('line 3\nline 4\n')
feed_data('line 5\nline 6\n')
feed_data('line 7\nline 8\n')
-- Stop paste.
feed_data('\027[201~')
- feed_data('\n') -- <CR>
+ feed_data('\n') -- <CR> to dismiss hit-enter prompt
expect_child_buf_lines({ 'foo', '' })
- --Dot-repeat/redo is not modified by failed paste.
+ -- Dot-repeat/redo is not modified by failed paste.
feed_data('.')
- screen:expect {
- grid = [[
+ screen:expect([[
foo |*2
{1: } |
{4:~ }|
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Editor should still work after failed/drained paste.
feed_data('ityped input...\027[27u')
- screen:expect {
- grid = [[
+ screen:expect([[
foo |*2
typed input..{1:.} |
{4:~ }|
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
-- Paste works if vim.paste() succeeds.
- child_session:request(
- 'nvim_exec_lua',
- [[
- vim.paste = _G.save_paste_fn
- ]],
- {}
- )
+ child_session:request('nvim_exec_lua', [[vim.paste = _G.save_paste_fn]], {})
feed_data('\027[200~line A\nline B\n\027[201~')
- feed_data('\n') -- <CR>
- screen:expect {
- grid = [[
+ screen:expect([[
foo |
typed input...line A |
line B |
@@ -1254,8 +1256,7 @@ describe('TUI', function()
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
end)
it('paste: vim.paste() cancel (retval=false) #10865', function()
@@ -1284,8 +1285,7 @@ describe('TUI', function()
{}
)
feed_data('\027[200~fail 1\nfail 2\n\027[201~')
- screen:expect {
- grid = [[
+ screen:expect([[
|
{4:~ }|
{5: }|
@@ -1293,13 +1293,11 @@ describe('TUI', function()
{8:hanges, 'modifiable' is off} |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
- ]],
- }
- feed_data('\n') -- <Enter>
+ ]])
+ feed_data('\n') -- <Enter> to dismiss hit-enter prompt
child_session:request('nvim_command', 'set modifiable')
feed_data('\027[200~success 1\nsuccess 2\n\027[201~')
- screen:expect {
- grid = [[
+ screen:expect([[
success 1 |
success 2 |
{1: } |
@@ -1307,8 +1305,7 @@ describe('TUI', function()
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
end)
it('paste: exactly 64 bytes #10311', function()
@@ -1337,15 +1334,13 @@ describe('TUI', function()
wait_for_mode('c')
-- "bracketed paste"
feed_data('\027[200~' .. expected .. '\027[201~')
- screen:expect {
- grid = [[
+ screen:expect([[
|
{4:~ }|*3
{5:[No Name] }|
:<{1: } |
{3:-- TERMINAL --} |
- ]],
- }
+ ]])
end)
it('paste: big burst of input', function()
@@ -1387,9 +1382,10 @@ describe('TUI', function()
it('paste: forwards spurious "start paste" code', function()
-- If multiple "start paste" sequences are sent without a corresponding
-- "stop paste" sequence, only the first occurrence should be consumed.
-
+ feed_data('i')
+ wait_for_mode('i')
-- Send the "start paste" sequence.
- feed_data('i\027[200~')
+ feed_data('\027[200~')
feed_data('\npasted from terminal (1)\n')
-- Send spurious "start paste" sequence.
feed_data('\027[200~')
@@ -1397,8 +1393,7 @@ describe('TUI', function()
-- Send the "stop paste" sequence.
feed_data('\027[201~')
- screen:expect {
- grid = [[
+ screen:expect([[
|
pasted from terminal (1) |
{6:^[}[200~ |
@@ -1406,22 +1401,14 @@ describe('TUI', function()
{5:[No Name] [+] }|
{3:-- INSERT --} |
{3:-- TERMINAL --} |
- ]],
- attr_ids = {
- [1] = { reverse = true },
- [2] = { background = tonumber('0x00000b') },
- [3] = { bold = true },
- [4] = { foreground = tonumber('0x00000c') },
- [5] = { bold = true, reverse = true },
- [6] = { foreground = tonumber('0x000051') },
- },
- }
+ ]])
end)
it('paste: ignores spurious "stop paste" code', function()
-- If "stop paste" sequence is received without a preceding "start paste"
-- sequence, it should be ignored.
feed_data('i')
+ wait_for_mode('i')
-- Send "stop paste" sequence.
feed_data('\027[201~')
screen:expect([[
@@ -1435,15 +1422,7 @@ describe('TUI', function()
it('paste: split "start paste" code', function()
feed_data('i')
- screen:expect {
- grid = [[
- {1: } |
- {4:~ }|*3
- {5:[No Name] }|
- {3:-- INSERT --} |
- {3:-- TERMINAL --} |
- ]],
- }
+ wait_for_mode('i')
-- Send split "start paste" sequence.
feed_data('\027[2')
feed_data('00~pasted from terminal\027[201~')
@@ -1458,15 +1437,7 @@ describe('TUI', function()
it('paste: split "stop paste" code', function()
feed_data('i')
- screen:expect {
- grid = [[
- {1: } |
- {4:~ }|*3
- {5:[No Name] }|
- {3:-- INSERT --} |
- {3:-- TERMINAL --} |
- ]],
- }
+ wait_for_mode('i')
-- Send split "stop paste" sequence.
feed_data('\027[200~pasted from terminal\027[20')
feed_data('1~')
@@ -1494,15 +1465,7 @@ describe('TUI', function()
{}
)
feed_data('i')
- screen:expect {
- grid = [[
- {1: } |
- {4:~ }|*3
- {5:[No Name] }|
- {3:-- INSERT --} |
- {3:-- TERMINAL --} |
- ]],
- }
+ wait_for_mode('i')
feed_data('\027[200~pasted') -- phase 1
screen:expect([[
pasted{1: } |
@@ -2095,6 +2058,48 @@ describe('TUI', function()
]],
}
end)
+
+ it('no heap-buffer-overflow when changing &columns', function()
+ -- Set a different bg colour and change $TERM to something dumber so the `print_spaces()`
+ -- codepath in `clear_region()` is hit.
+ local screen = thelpers.setup_child_nvim({
+ '-u',
+ 'NONE',
+ '-i',
+ 'NONE',
+ '--cmd',
+ 'set notermguicolors | highlight Normal ctermbg=red',
+ '--cmd',
+ 'call setline(1, ["a"->repeat(&columns)])',
+ }, { env = { TERM = 'ansi' } })
+
+ screen:expect {
+ grid = [[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ ~ |*3
+ [No Name] [+] 1,1 All|
+ |
+ -- TERMINAL -- |
+ ]],
+ attr_ids = {},
+ }
+
+ feed_data(':set columns=12\n')
+ screen:expect {
+ grid = [[
+ aaaaaaaaaaaa |*4
+ < [+] 1,1 |
+ |
+ -- TERMINAL -- |
+ ]],
+ attr_ids = {},
+ }
+
+ -- Wider than TUI, so screen state will look weird.
+ -- Wait for the statusline to redraw to confirm that the TUI lives and ASAN is happy.
+ feed_data(':set columns=99|set stl=redrawn%m\n')
+ screen:expect({ any = 'redrawn%[%+%]' })
+ end)
end)
describe('TUI UIEnter/UILeave', function()
diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua
new file mode 100644
index 0000000000..a3d44ff906
--- /dev/null
+++ b/test/functional/treesitter/inspect_tree_spec.lua
@@ -0,0 +1,115 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local insert = helpers.insert
+local dedent = helpers.dedent
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+
+describe('vim.treesitter.inspect_tree', function()
+ before_each(clear)
+
+ local expect_tree = function(x)
+ local expected = vim.split(vim.trim(dedent(x)), '\n')
+ local actual = helpers.buf_lines(0) ---@type string[]
+ eq(expected, actual)
+ end
+
+ it('working', function()
+ insert([[
+ print()
+ ]])
+
+ exec_lua([[
+ vim.treesitter.start(0, 'lua')
+ vim.treesitter.inspect_tree()
+ ]])
+
+ expect_tree [[
+ (chunk ; [0, 0] - [2, 0]
+ (function_call ; [0, 0] - [0, 7]
+ name: (identifier) ; [0, 0] - [0, 5]
+ arguments: (arguments))) ; [0, 5] - [0, 7]
+ ]]
+ end)
+
+ it('can toggle to show anonymous nodes', function()
+ insert([[
+ print()
+ ]])
+
+ exec_lua([[
+ vim.treesitter.start(0, 'lua')
+ vim.treesitter.inspect_tree()
+ ]])
+ feed('a')
+
+ expect_tree [[
+ (chunk ; [0, 0] - [2, 0]
+ (function_call ; [0, 0] - [0, 7]
+ name: (identifier) ; [0, 0] - [0, 5]
+ arguments: (arguments ; [0, 5] - [0, 7]
+ "(" ; [0, 5] - [0, 6]
+ ")"))) ; [0, 6] - [0, 7]
+ ]]
+ end)
+
+ it('works for injected trees', function()
+ insert([[
+ ```lua
+ return
+ ```
+ ]])
+
+ exec_lua([[
+ vim.treesitter.start(0, 'markdown')
+ vim.treesitter.get_parser():parse()
+ vim.treesitter.inspect_tree()
+ ]])
+
+ expect_tree [[
+ (document ; [0, 0] - [4, 0]
+ (section ; [0, 0] - [4, 0]
+ (fenced_code_block ; [0, 0] - [3, 0]
+ (fenced_code_block_delimiter) ; [0, 0] - [0, 3]
+ (info_string ; [0, 3] - [0, 6]
+ (language)) ; [0, 3] - [0, 6]
+ (block_continuation) ; [1, 0] - [1, 0]
+ (code_fence_content ; [1, 0] - [2, 0]
+ (chunk ; [1, 0] - [2, 0]
+ (return_statement)) ; [1, 0] - [1, 6]
+ (block_continuation)) ; [2, 0] - [2, 0]
+ (fenced_code_block_delimiter)))) ; [2, 0] - [2, 3]
+ ]]
+ end)
+
+ it('can toggle to show languages', function()
+ insert([[
+ ```lua
+ return
+ ```
+ ]])
+
+ exec_lua([[
+ vim.treesitter.start(0, 'markdown')
+ vim.treesitter.get_parser():parse()
+ vim.treesitter.inspect_tree()
+ ]])
+ feed('I')
+
+ expect_tree [[
+ (document ; [0, 0] - [4, 0] markdown
+ (section ; [0, 0] - [4, 0] markdown
+ (fenced_code_block ; [0, 0] - [3, 0] markdown
+ (fenced_code_block_delimiter) ; [0, 0] - [0, 3] markdown
+ (info_string ; [0, 3] - [0, 6] markdown
+ (language)) ; [0, 3] - [0, 6] markdown
+ (block_continuation) ; [1, 0] - [1, 0] markdown
+ (code_fence_content ; [1, 0] - [2, 0] markdown
+ (chunk ; [1, 0] - [2, 0] lua
+ (return_statement)) ; [1, 0] - [1, 6] lua
+ (block_continuation)) ; [2, 0] - [2, 0] markdown
+ (fenced_code_block_delimiter)))) ; [2, 0] - [2, 3] markdown
+ ]]
+ end)
+end)
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index fd8a06d898..951188614b 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -2230,6 +2230,44 @@ describe('extmark decorations', function()
]]}
end)
+ it('virtual text is drawn correctly after delete and undo #27368', function()
+ insert('aaa\nbbb\nccc\nddd\neee')
+ command('vsplit')
+ api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_text = {{'EOL'}} })
+ feed('3gg')
+ screen:expect{grid=[[
+ aaa │aaa |
+ bbb │bbb |
+ ^ccc EOL │ccc EOL |
+ ddd │ddd |
+ eee │eee |
+ {1:~ }│{1:~ }|*8
+ {41:[No Name] [+] }{40:[No Name] [+] }|
+ |
+ ]]}
+ feed('dd')
+ screen:expect{grid=[[
+ aaa │aaa |
+ bbb │bbb |
+ ^ddd EOL │ddd EOL |
+ eee │eee |
+ {1:~ }│{1:~ }|*9
+ {41:[No Name] [+] }{40:[No Name] [+] }|
+ |
+ ]]}
+ command('silent undo')
+ screen:expect{grid=[[
+ aaa │aaa |
+ bbb │bbb |
+ ^ccc EOL │ccc EOL |
+ ddd │ddd |
+ eee │eee |
+ {1:~ }│{1:~ }|*8
+ {41:[No Name] [+] }{40:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
it('works with both hl_group and sign_hl_group', function()
screen:try_resize(screen._width, 3)
insert('abcdefghijklmn')
@@ -3882,16 +3920,31 @@ if (h->n_buckets < new_n_buckets) { // expand
it('works with one line', function()
insert(example_text2)
- feed 'gg'
+ feed '2gg'
+ screen:expect{grid=[[
+ if (h->n_buckets < new_n_buckets) { // expand |
+ ^khkey_t *new_keys = (khkey_t *)krealloc((void *)|
+ h->keys, new_n_buckets * sizeof(khkey_t)); |
+ h->keys = new_keys; |
+ if (kh_is_map && val_size) { |
+ char *new_vals = krealloc( h->vals_buf, new_n_|
+ buckets * val_size); |
+ h->vals_buf = new_vals; |
+ } |
+ } |
+ {1:~ }|
+ |
+ ]]}
+
api.nvim_buf_set_extmark(0, ns, 1, 33, {
virt_lines={ {{">> ", "NonText"}, {"krealloc", "Identifier"}, {": change the size of an allocation"}}};
virt_lines_above=true;
})
screen:expect{grid=[[
- ^if (h->n_buckets < new_n_buckets) { // expand |
+ if (h->n_buckets < new_n_buckets) { // expand |
{1:>> }{2:krealloc}: change the size of an allocation |
- khkey_t *new_keys = (khkey_t *)krealloc((void *)|
+ ^khkey_t *new_keys = (khkey_t *)krealloc((void *)|
h->keys, new_n_buckets * sizeof(khkey_t)); |
h->keys = new_keys; |
if (kh_is_map && val_size) { |
@@ -3955,7 +4008,6 @@ if (h->n_buckets < new_n_buckets) { // expand
api.nvim_buf_set_extmark(0, ns, 5, 0, {
virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unnecessary?", "Comment"}} };
})
- -- TODO: what about the cursor??
screen:expect{grid=[[
if (h->n_buckets < new_n_buckets) { // expand |
khkey_t *new_keys = (khkey_t *) |
@@ -3972,7 +4024,6 @@ if (h->n_buckets < new_n_buckets) { // expand
]]}
api.nvim_buf_clear_namespace(0, ns, 0, -1)
- -- Cursor should be drawn on the correct line. #22704
screen:expect{grid=[[
if (h->n_buckets < new_n_buckets) { // expand |
khkey_t *new_keys = (khkey_t *) |
@@ -4604,22 +4655,24 @@ if (h->n_buckets < new_n_buckets) { // expand
]]}
feed('gg')
- feed('dd')
+ feed('yyp')
screen:expect{grid=[[
- ^line2 |
+ line1 |
foo |
bar |
baz |
+ ^line1 |
+ line2 |
line3 |
line4 |
line5 |
- {1:~ }|*4
+ {1:~ }|*2
|
]]}
- feed('yyp')
+ feed('dd')
screen:expect{grid=[[
- line2 |
+ line1 |
foo |
bar |
baz |
@@ -4630,6 +4683,19 @@ if (h->n_buckets < new_n_buckets) { // expand
{1:~ }|*3
|
]]}
+
+ feed('kdd')
+ screen:expect([[
+ ^line2 |
+ foo |
+ bar |
+ baz |
+ line3 |
+ line4 |
+ line5 |
+ {1:~ }|*4
+ |
+ ]])
end)
end)
@@ -5160,6 +5226,18 @@ l5
|
]]}
end)
+
+ it('correct sort order with multiple namespaces and same id', function()
+ local ns2 = api.nvim_create_namespace('')
+ api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1', id = 1})
+ api.nvim_buf_set_extmark(0, ns2, 0, 0, {sign_text = 'S2', id = 1})
+
+ screen:expect{grid=[[
+ S1S2^ |
+ {2:~ }|*8
+ |
+ ]]}
+ end)
end)
describe('decorations: virt_text', function()
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 397c1f6132..60ba9f3958 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -1101,6 +1101,15 @@ describe('float window', function()
local win = api.nvim_open_win(buf, false, {relative='editor', width=20, height=2, row=3, col=5, zindex=60})
local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20, zindex=60, hide=false}
eq(expected, api.nvim_win_get_config(win))
+ eq(true, exec_lua([[
+ local expected, win = ...
+ local actual = vim.api.nvim_win_get_config(win)
+ for k,v in pairs(expected) do
+ if v ~= actual[k] then
+ error(k)
+ end
+ end
+ return true]], expected, win))
eq({external=false, focusable=true, hide=false, relative='',split="left",width=40,height=6}, api.nvim_win_get_config(0))
diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua
index 95d209496f..01f08cd6f7 100644
--- a/test/functional/ui/fold_spec.lua
+++ b/test/functional/ui/fold_spec.lua
@@ -56,7 +56,7 @@ describe('folded lines', function()
})
end)
- it('work with more than one signcolumn', function()
+ it('with more than one signcolumn', function()
command('set signcolumn=yes:9')
feed('i<cr><esc>')
feed('vkzf')
@@ -296,7 +296,7 @@ describe('folded lines', function()
describe("when 'cursorline' is set", function()
local function cursorline_tests(foldtext)
- local sfx = not foldtext and ' (disabled foldtext)' or ''
+ local sfx = not foldtext and ' (transparent foldtext)' or ''
it('with high-priority CursorLine' .. sfx, function()
command('hi! CursorLine guibg=NONE guifg=Red gui=NONE')
test_folded_cursorline(foldtext)
@@ -320,7 +320,7 @@ describe('folded lines', function()
cursorline_tests(false)
end)
- it('work with spell', function()
+ it('with spell', function()
command('set spell')
insert(content1)
@@ -339,7 +339,7 @@ describe('folded lines', function()
end
end)
- it('work with matches', function()
+ it('with matches', function()
insert(content1)
command('highlight MyWord gui=bold guibg=red guifg=white')
command("call matchadd('MyWord', '\\V' . 'test', -1)")
@@ -358,7 +358,7 @@ describe('folded lines', function()
end
end)
- it('works with multibyte fillchars', function()
+ it('with multibyte fillchars', function()
insert([[
aa
bb
@@ -535,7 +535,7 @@ describe('folded lines', function()
end
end)
- it('works with split', function()
+ it('with split', function()
insert([[
aa
bb
@@ -695,7 +695,7 @@ describe('folded lines', function()
end
end)
- it('works with vsplit', function()
+ it('with vsplit', function()
insert([[
aa
bb
@@ -867,7 +867,7 @@ describe('folded lines', function()
end
end)
- it('works with tab', function()
+ it('with tabpages', function()
insert([[
aa
bb
@@ -1006,7 +1006,7 @@ describe('folded lines', function()
end
end)
- it('works with multibyte text', function()
+ it('with multibyte text', function()
eq(true, api.nvim_get_option_value('arabicshape', {}))
insert([[
å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢͟ العَرَبِيَّة
@@ -1198,8 +1198,8 @@ describe('folded lines', function()
end
end)
- it('work in cmdline window', function()
- feed_command('set foldmethod=manual')
+ it('in cmdline window', function()
+ feed_command('set foldmethod=manual foldcolumn=1')
feed_command('let x = 1')
feed_command('/alpha')
feed_command('/omega')
@@ -1214,22 +1214,22 @@ describe('folded lines', function()
{3:[Command Line] }|
[3:---------------------------------------------]|
## grid 2
- |
+ {7: } |
## grid 3
: |
## grid 4
- {1::}set foldmethod=manual |
- {1::}let x = 1 |
- {1::}^ |
+ {1::}{7: }set foldmethod=manual foldcolumn=1 |
+ {1::}{7: }let x = 1 |
+ {1::}{7: }^ |
{1:~ }|
]])
else
screen:expect([[
- |
+ {7: } |
{2:[No Name] }|
- {1::}set foldmethod=manual |
- {1::}let x = 1 |
- {1::}^ |
+ {1::}{7: }set foldmethod=manual foldcolumn=1 |
+ {1::}{7: }let x = 1 |
+ {1::}{7: }^ |
{1:~ }|
{3:[Command Line] }|
: |
@@ -1246,20 +1246,20 @@ describe('folded lines', function()
{3:[Command Line] }|
[3:---------------------------------------------]|
## grid 2
- |
+ {7: } |
## grid 3
: |
## grid 4
- {1::}{5:^+-- 2 lines: set foldmethod=manual·········}|
- {1::} |
+ {1::}{7:+}{5:^+-- 2 lines: set foldmethod=manual foldcol}|
+ {1::}{7: } |
{1:~ }|*2
]])
else
screen:expect([[
- |
+ {7: } |
{2:[No Name] }|
- {1::}{5:^+-- 2 lines: set foldmethod=manual·········}|
- {1::} |
+ {1::}{7:+}{5:^+-- 2 lines: set foldmethod=manual foldcol}|
+ {1::}{7: } |
{1:~ }|*2
{3:[Command Line] }|
: |
@@ -1273,14 +1273,14 @@ describe('folded lines', function()
[2:---------------------------------------------]|*7
[3:---------------------------------------------]|
## grid 2
- ^ |
+ {7: }^ |
{1:~ }|*6
## grid 3
: |
]])
else
screen:expect([[
- ^ |
+ {7: }^ |
{1:~ }|*6
: |
]])
@@ -1296,22 +1296,22 @@ describe('folded lines', function()
{3:[Command Line] }|
[3:---------------------------------------------]|
## grid 2
- |
+ {7: } |
## grid 3
/ |
## grid 5
- {1:/}alpha |
- {1:/}{6:omega} |
- {1:/}^ |
+ {1:/}{7: }alpha |
+ {1:/}{7: }{6:omega} |
+ {1:/}{7: }^ |
{1:~ }|
]])
else
screen:expect([[
- |
+ {7: } |
{2:[No Name] }|
- {1:/}alpha |
- {1:/}{6:omega} |
- {1:/}^ |
+ {1:/}{7: }alpha |
+ {1:/}{7: }{6:omega} |
+ {1:/}{7: }^ |
{1:~ }|
{3:[Command Line] }|
/ |
@@ -1328,18 +1328,18 @@ describe('folded lines', function()
{3:[Command Line] }|
[3:---------------------------------------------]|
## grid 2
- |
+ {7: } |
## grid 3
/ |
## grid 5
- {1:/}{5:^+-- 3 lines: alpha·························}|
+ {1:/}{7:+}{5:^+-- 3 lines: alpha························}|
{1:~ }|*3
]])
else
screen:expect([[
- |
+ {7: } |
{2:[No Name] }|
- {1:/}{5:^+-- 3 lines: alpha·························}|
+ {1:/}{7:+}{5:^+-- 3 lines: alpha························}|
{1:~ }|*3
{3:[Command Line] }|
/ |
@@ -1347,7 +1347,7 @@ describe('folded lines', function()
end
end)
- it('work with autoresize', function()
+ it('foldcolumn autoresize', function()
fn.setline(1, 'line 1')
fn.setline(2, 'line 2')
fn.setline(3, 'line 3')
@@ -1501,7 +1501,7 @@ describe('folded lines', function()
end
end)
- it('does not crash when foldtext is longer than columns #12988', function()
+ it('no crash when foldtext is longer than columns #12988', function()
exec([[
function! MyFoldText() abort
return repeat('-', &columns + 100)
@@ -1531,55 +1531,6 @@ describe('folded lines', function()
assert_alive()
end)
- it('work correctly with :move #18668', function()
- screen:try_resize(45, 12)
- exec([[
- set foldmethod=expr foldexpr=indent(v:lnum)
- let content = ['', '', 'Line1', ' Line2', ' Line3',
- \ 'Line4', ' Line5', ' Line6',
- \ 'Line7', ' Line8', ' Line9']
- call append(0, content)
- normal! zM
- call cursor(4, 1)
- move 2
- move 1
- ]])
- if multigrid then
- screen:expect([[
- ## grid 1
- [2:---------------------------------------------]|*11
- [3:---------------------------------------------]|
- ## grid 2
- |
- {5:^+-- 2 lines: Line2··························}|
- |
- Line1 |
- Line4 |
- {5:+-- 2 lines: Line5··························}|
- Line7 |
- {5:+-- 2 lines: Line8··························}|
- |
- {1:~ }|*2
- ## grid 3
- |
- ]])
- else
- screen:expect([[
- |
- {5:^+-- 2 lines: Line2··························}|
- |
- Line1 |
- Line4 |
- {5:+-- 2 lines: Line5··························}|
- Line7 |
- {5:+-- 2 lines: Line8··························}|
- |
- {1:~ }|*2
- |
- ]])
- end
- end)
-
it('fold text is shown when text has been scrolled to the right #19123', function()
insert(content1)
command('set number nowrap')
@@ -2541,7 +2492,7 @@ describe('folded lines', function()
end
end)
- it('support foldtext with virtual text format', function()
+ it('foldtext with virtual text format', function()
screen:try_resize(30, 7)
insert(content1)
command('hi! CursorLine guibg=NONE guifg=Red gui=NONE')
@@ -2666,7 +2617,7 @@ describe('folded lines', function()
end
end)
- it('supports disabled foldtext', function()
+ it('transparent foldtext', function()
screen:try_resize(30, 7)
insert(content1)
command('hi! CursorLine guibg=NONE guifg=Red gui=NONE')
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index af6ae5bf46..d2320db2a1 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -31,7 +31,7 @@ describe('highlight: `:syntax manual`', function()
clear()
screen = Screen.new(20, 5)
screen:attach()
- --syntax highlight for vimcscripts "echo"
+ -- syntax highlight for vimscript's "echo"
screen:set_default_attr_ids({
[0] = { bold = true, foreground = Screen.colors.Blue },
[1] = { bold = true, foreground = Screen.colors.Brown },
@@ -1705,117 +1705,6 @@ describe('MsgSeparator highlight and msgsep fillchar', function()
end)
end)
-describe("'number' and 'relativenumber' highlight", function()
- before_each(clear)
-
- it('LineNr, LineNrAbove and LineNrBelow', function()
- local screen = Screen.new(20, 10)
- screen:set_default_attr_ids({
- [1] = { foreground = Screen.colors.Red },
- [2] = { foreground = Screen.colors.Blue },
- [3] = { foreground = Screen.colors.Green },
- })
- screen:attach()
- command('set number relativenumber')
- command('call setline(1, range(50))')
- command('highlight LineNr guifg=Red')
- feed('4j')
- screen:expect([[
- {1: 4 }0 |
- {1: 3 }1 |
- {1: 2 }2 |
- {1: 1 }3 |
- {1:5 }^4 |
- {1: 1 }5 |
- {1: 2 }6 |
- {1: 3 }7 |
- {1: 4 }8 |
- |
- ]])
- command('highlight LineNrAbove guifg=Blue')
- screen:expect([[
- {2: 4 }0 |
- {2: 3 }1 |
- {2: 2 }2 |
- {2: 1 }3 |
- {1:5 }^4 |
- {1: 1 }5 |
- {1: 2 }6 |
- {1: 3 }7 |
- {1: 4 }8 |
- |
- ]])
- command('highlight LineNrBelow guifg=Green')
- screen:expect([[
- {2: 4 }0 |
- {2: 3 }1 |
- {2: 2 }2 |
- {2: 1 }3 |
- {1:5 }^4 |
- {3: 1 }5 |
- {3: 2 }6 |
- {3: 3 }7 |
- {3: 4 }8 |
- |
- ]])
- feed('3j')
- screen:expect([[
- {2: 7 }0 |
- {2: 6 }1 |
- {2: 5 }2 |
- {2: 4 }3 |
- {2: 3 }4 |
- {2: 2 }5 |
- {2: 1 }6 |
- {1:8 }^7 |
- {3: 1 }8 |
- |
- ]])
- end)
-
- -- oldtest: Test_relativenumber_callback()
- it('relative number highlight is updated if cursor is moved from timer', function()
- local screen = Screen.new(50, 8)
- screen:set_default_attr_ids({
- [1] = { foreground = Screen.colors.Brown }, -- LineNr
- [2] = { bold = true, foreground = Screen.colors.Blue1 }, -- NonText
- })
- screen:attach()
- exec([[
- call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
- set relativenumber
- call cursor(4, 1)
-
- func Func(timer)
- call cursor(1, 1)
- endfunc
-
- call timer_start(300, 'Func')
- ]])
- screen:expect({
- grid = [[
- {1: 3 }aaaaa |
- {1: 2 }bbbbb |
- {1: 1 }ccccc |
- {1: 0 }^ddddd |
- {2:~ }|*3
- |
- ]],
- timeout = 100,
- })
- screen:expect({
- grid = [[
- {1: 0 }^aaaaa |
- {1: 1 }bbbbb |
- {1: 2 }ccccc |
- {1: 3 }ddddd |
- {2:~ }|*3
- |
- ]],
- })
- end)
-end)
-
describe("'winhighlight' highlight", function()
local screen
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 9d1b6163d2..31b1464589 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -1555,6 +1555,23 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
]])
eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
end)
+
+ it('bottom of screen is cleared after increasing &cmdheight #20360', function()
+ command('set laststatus=2')
+ screen:expect([[
+ ^ |
+ {1:~ }|*4
+ {3:[No Name] }|
+ |
+ ]])
+ command('set cmdheight=4')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {3:[No Name] }|
+ |*4
+ ]])
+ end)
end)
it('calling screenstring() after redrawing between messages without UI #20999', function()
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index 6a1b3fb0ed..7f551c5ee5 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -188,6 +188,9 @@ describe('ui/mouse/input', function()
|
]])
feed('<LeftMouse><11,0>')
+ -- Prevent the case where screen:expect() with "unchanged" returns too early,
+ -- causing the click position to be overwritten by the next drag.
+ poke_eventloop()
screen:expect {
grid = [[
{tab: + foo }{sel: + bar }{fill: }{tab:X}|
@@ -282,6 +285,9 @@ describe('ui/mouse/input', function()
|
]])
feed('<LeftMouse><11,0>')
+ -- Prevent the case where screen:expect() with "unchanged" returns too early,
+ -- causing the click position to be overwritten by the next drag.
+ poke_eventloop()
screen:expect {
grid = [[
{tab: + foo }{sel: + bar }{fill: }{tab:X}|
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index 1f7d187016..1f0d20f66d 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -3784,9 +3784,7 @@ describe('builtin popupmenu', function()
{n: bar }|
{n: baz }|
]],
- float_pos = {
- [4] = { -1, 'NW', 2, 1, 17, false, 250 },
- },
+ float_pos = { [4] = { -1, 'NW', 2, 1, 17, false, 250 } },
}
else
feed('<RightMouse><18,0>')
@@ -3969,7 +3967,38 @@ describe('builtin popupmenu', function()
end
eq(true, screen.options.mousemoveevent)
if multigrid then
- api.nvim_input_mouse('move', '', '', 2, 3, 6)
+ api.nvim_input_mouse('wheel', 'up', '', 2, 0, 4)
+ screen:expect({
+ grid = [[
+ ## grid 1
+ [2:--------------------------------]|*5
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|*4
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {s: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]],
+ float_pos = { [4] = { -1, 'NW', 2, 1, 3, false, 250 } },
+ })
+ else
+ feed('<ScrollWheelUp><4,0>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{s: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ eq(true, screen.options.mousemoveevent)
+ if multigrid then
+ api.nvim_input_mouse('move', '', '', 4, 2, 3)
screen:expect({
grid = [[
## grid 1
@@ -4000,7 +4029,38 @@ describe('builtin popupmenu', function()
end
eq(true, screen.options.mousemoveevent)
if multigrid then
- api.nvim_input_mouse('left', 'press', '', 2, 2, 6)
+ api.nvim_input_mouse('wheel', 'down', '', 4, 2, 3)
+ screen:expect({
+ grid = [[
+ ## grid 1
+ [2:--------------------------------]|*5
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|*4
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {n: foo }|
+ {s: bar }|
+ {n: baz }|
+ ]],
+ float_pos = { [4] = { -1, 'NW', 2, 1, 3, false, 250 } },
+ })
+ else
+ feed('<ScrollWheelDown><6,3>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{s: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ eq(true, screen.options.mousemoveevent)
+ if multigrid then
+ api.nvim_input_mouse('left', 'press', '', 4, 1, 3)
screen:expect({
grid = [[
## grid 1
diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua
index 2058f9a59d..3a3ff25c39 100644
--- a/test/functional/ui/statuscolumn_spec.lua
+++ b/test/functional/ui/statuscolumn_spec.lua
@@ -515,6 +515,22 @@ describe('statuscolumn', function()
set cpoptions-=n nocursorline relativenumber
set stc=%{v:virtnum<0?'virtual':(!v:virtnum?'buffer':'wrapped')}%=%{'\ '.v:virtnum.'\ '.v:lnum.'\ '.v:relnum}
]])
+ screen:expect([[
+ {1:buffer 0 12 3}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 12 3}aaaaaaaaaaa |
+ {1:buffer 0 13 2}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 13 2}aaaaaaaaaaa |
+ {1:buffer 0 14 1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 14 1}aaaaaaaaaaa |
+ {1:buffer 0 15 0}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 15 0}aaaaaaaaaaa^ aaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 2 15 0}aaaaaaaaaaaaaaaaaaaaaaa |
+ {1:virtual-3 15 0}virt_line1 |
+ {1:virtual-2 15 0}virt_line2 |
+ {1:virtual-1 15 0}END |
+ {0:~ }|
+ |
+ ]])
feed('kk')
screen:expect([[
{1:buffer 0 12 1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
index 7926411dcd..0a28ae6147 100644
--- a/test/old/testdir/test_autocmd.vim
+++ b/test/old/testdir/test_autocmd.vim
@@ -2533,7 +2533,25 @@ func Test_TextChangedI_with_setline()
call assert_equal('', getline(1))
call assert_equal('', getline(2))
- call test_override('starting', 0)
+ call test_override('char_avail', 0)
+ bwipe!
+endfunc
+
+func Test_TextChanged_with_norm()
+ " For unknown reason this fails on MS-Windows
+ CheckNotMSWindows
+ CheckFeature terminal
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
+ call assert_equal('running', term_getstatus(buf))
+ call term_sendkeys(buf, ":let g:a=0\<cr>")
+ call term_wait(buf, 50)
+ call term_sendkeys(buf, ":au! TextChanged * :let g:a+=1\<cr>")
+ call term_wait(buf, 50)
+ call term_sendkeys(buf, ":norm! ia\<cr>")
+ call term_wait(buf, 50)
+ call term_sendkeys(buf, ":echo g:a\<cr>")
+ call term_wait(buf, 50)
+ call WaitForAssert({-> assert_match('^1.*$', term_getline(buf, 3))})
bwipe!
endfunc
diff --git a/test/old/testdir/test_breakindent.vim b/test/old/testdir/test_breakindent.vim
index 426f8ab278..3141cb9e8b 100644
--- a/test/old/testdir/test_breakindent.vim
+++ b/test/old/testdir/test_breakindent.vim
@@ -450,7 +450,7 @@ func Test_breakindent12()
\ "~ ",
\ ]
call s:compare_lines(expect, lines)
- call s:close_windows('set nuw=4 listchars=')
+ call s:close_windows('set nuw=4 listchars&')
endfunc
func Test_breakindent12_vartabs()
@@ -467,7 +467,7 @@ func Test_breakindent12_vartabs()
\ "~ ",
\ ]
call s:compare_lines(expect, lines)
- call s:close_windows('set nuw=4 listchars= vts&')
+ call s:close_windows('set nuw=4 listchars& vts&')
endfunc
func Test_breakindent13()
@@ -1126,5 +1126,51 @@ func Test_linebreak_list()
bwipe!
endfunc
+func Test_breakindent_change_display_uhex()
+ call s:test_windows('setl briopt=min:0 list listchars=eol:$')
+ redraw!
+ let lines = s:screen_lines(line('.'), 20)
+ let expect = [
+ \ "^Iabcdefghijklmnopqr",
+ \ " stuvwxyzABCDEFGHIJ",
+ \ " KLMNOP$ "
+ \ ]
+ call s:compare_lines(expect, lines)
+ set display+=uhex
+ redraw!
+ let lines = s:screen_lines(line('.'), 20)
+ let expect = [
+ \ "<09>abcdefghijklmnop",
+ \ " qrstuvwxyzABCDEF",
+ \ " GHIJKLMNOP$ "
+ \ ]
+ call s:compare_lines(expect, lines)
+ set display&
+
+ call s:close_windows()
+endfunc
+
+func Test_breakindent_list_split()
+ 10new
+ 61vsplit
+ setlocal tabstop=8 breakindent list listchars=tab:<->,eol:$
+ put =s:input
+ 30vsplit
+ setlocal listchars=eol:$
+ let expect = [
+ \ "^IabcdefghijklmnopqrstuvwxyzAB|<------>abcdefghijklmnopqrstuv",
+ \ " CDEFGHIJKLMNOP$ | wxyzABCDEFGHIJKLMNOP$ ",
+ \ "~ |~ "
+ \ ]
+ redraw!
+ let lines = s:screen_lines(line('.'), 61)
+ call s:compare_lines(expect, lines)
+ wincmd p
+ redraw!
+ let lines = s:screen_lines(line('.'), 61)
+ call s:compare_lines(expect, lines)
+
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_highlight.vim b/test/old/testdir/test_highlight.vim
index f7f4d9832b..bcaa1dac5d 100644
--- a/test/old/testdir/test_highlight.vim
+++ b/test/old/testdir/test_highlight.vim
@@ -541,7 +541,7 @@ func Test_cursorline_after_yank()
call writefile([
\ 'set cul rnu',
\ 'call setline(1, ["","1","2","3",""])',
- \ ], 'Xtest_cursorline_yank')
+ \ ], 'Xtest_cursorline_yank', 'D')
let buf = RunVimInTerminal('-S Xtest_cursorline_yank', {'rows': 8})
call TermWait(buf)
call term_sendkeys(buf, "Gy3k")
@@ -552,25 +552,25 @@ func Test_cursorline_after_yank()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_cursorline_yank')
endfunc
-" test for issue https://github.com/vim/vim/issues/4862
+" Test for issue #4862: pasting above 'cursorline' redraws properly.
func Test_put_before_cursorline()
new
only!
- call setline(1, 'A')
+ call setline(1, ['A', 'B', 'C'])
+ call cursor(2, 1)
redraw
- let std_attr = screenattr(1, 1)
+ let std_attr = screenattr(2, 1)
set cursorline
redraw
- let cul_attr = screenattr(1, 1)
+ let cul_attr = screenattr(2, 1)
normal yyP
redraw
- " Line 1 has cursor so it should be highlighted with CursorLine.
- call assert_equal(cul_attr, screenattr(1, 1))
- " And CursorLine highlighting from the second line should be gone.
- call assert_equal(std_attr, screenattr(2, 1))
+ " Line 2 has cursor so it should be highlighted with CursorLine.
+ call assert_equal(cul_attr, screenattr(2, 1))
+ " And CursorLine highlighting from line 3 should be gone.
+ call assert_equal(std_attr, screenattr(3, 1))
set nocursorline
bwipe!
endfunc
diff --git a/test/old/testdir/test_listlbr_utf8.vim b/test/old/testdir/test_listlbr_utf8.vim
index 1bbbd2d2ae..313ff30cc4 100644
--- a/test/old/testdir/test_listlbr_utf8.vim
+++ b/test/old/testdir/test_listlbr_utf8.vim
@@ -9,6 +9,7 @@ CheckFeature conceal
CheckFeature signs
source view_util.vim
+source screendump.vim
func s:screen_lines(lnum, width) abort
return ScreenLines(a:lnum, a:width)
@@ -358,4 +359,24 @@ func Test_unprintable_char_on_wrap_column()
call s:close_windows()
endfunc
+" Test that Visual selection is drawn correctly when 'linebreak' is set and
+" selection ends before multibyte 'showbreak'.
+func Test_visual_ends_before_showbreak()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ &wrap = true
+ &linebreak = true
+ &showbreak = '↪ '
+ ['xxxxx ' .. 'y'->repeat(&columns - 6) .. ' zzzz']->setline(1)
+ normal! wvel
+ END
+ call writefile(lines, 'XvisualEndsBeforeShowbreak', 'D')
+ let buf = RunVimInTerminal('-S XvisualEndsBeforeShowbreak', #{rows: 6})
+ call VerifyScreenDump(buf, 'Test_visual_ends_before_showbreak', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_number.vim b/test/old/testdir/test_number.vim
index 1232db80c5..b57c1ed2c5 100644
--- a/test/old/testdir/test_number.vim
+++ b/test/old/testdir/test_number.vim
@@ -146,8 +146,7 @@ func Test_number_with_linewrap1()
call s:close_windows()
endfunc
-" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI
-func XTest_number_with_linewrap2()
+func Test_number_with_linewrap2()
call s:test_windows(3, 20)
normal! 61ia
setl number wrap
@@ -164,8 +163,7 @@ func XTest_number_with_linewrap2()
call s:close_windows()
endfunc
-" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI
-func XTest_number_with_linewrap3()
+func Test_number_with_linewrap3()
call s:test_windows(4, 20)
normal! 81ia
setl number wrap
@@ -174,7 +172,7 @@ func XTest_number_with_linewrap3()
call s:validate_cursor()
let lines = s:screen_lines(1, 4)
let expect = [
-\ "aaaaaaaa",
+\ "<<<aaaaa",
\ "aaaaaaaa",
\ "aaaaaaaa",
\ "a ",
@@ -276,11 +274,9 @@ func Test_relativenumber_colors()
set number relativenumber
hi LineNr ctermfg=red
[CODE]
- call writefile(lines, 'XTest_relnr')
+ call writefile(lines, 'XTest_relnr', 'D')
- " Check that the balloon shows up after a mouse move
let buf = RunVimInTerminal('-S XTest_relnr', {'rows': 10, 'cols': 50})
- call TermWait(buf, 50)
" Default colors
call VerifyScreenDump(buf, 'Test_relnr_colors_1', {})
@@ -295,7 +291,36 @@ func Test_relativenumber_colors()
" clean up
call StopVimInTerminal(buf)
- call delete('XTest_relnr')
+endfunc
+
+func Test_relativenumber_colors_wrapped()
+ CheckScreendump
+
+ let lines =<< trim [CODE]
+ set display=lastline scrolloff=0
+ call setline(1, range(200)->map('v:val->string()->repeat(40)'))
+ 111
+ set number relativenumber
+ hi LineNr ctermbg=red ctermfg=black
+ hi LineNrAbove ctermbg=blue ctermfg=black
+ hi LineNrBelow ctermbg=green ctermfg=black
+ [CODE]
+ call writefile(lines, 'XTest_relnr_wrap', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_relnr_wrap', {'rows': 20, 'cols': 50})
+
+ call VerifyScreenDump(buf, 'Test_relnr_colors_wrapped_1', {})
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_wrapped_2', {})
+ call term_sendkeys(buf, "2j")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_wrapped_3', {})
+ call term_sendkeys(buf, "2j")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_wrapped_4', {})
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_wrapped_5', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
endfunc
func Test_relativenumber_callback()
@@ -313,14 +338,38 @@ func Test_relativenumber_callback()
call timer_start(300, 'Func')
END
- call writefile(lines, 'Xrnu_timer')
+ call writefile(lines, 'Xrnu_timer', 'D')
let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8})
call TermWait(buf, 310)
call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {})
call StopVimInTerminal(buf)
- call delete('Xrnu_timer')
+endfunc
+
+" Test that line numbers below inserted/deleted lines are updated.
+func Test_number_insert_delete_lines()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 7))
+ set number
+ call cursor(2, 1)
+ END
+ call writefile(lines, 'Xnumber_insert_delete_lines', 'D')
+
+ let buf = RunVimInTerminal('-S Xnumber_insert_delete_lines', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_number_insert_delete_lines_1', {})
+ call term_sendkeys(buf, "dd")
+ call VerifyScreenDump(buf, 'Test_number_insert_delete_lines_2', {})
+ call term_sendkeys(buf, "P")
+ call VerifyScreenDump(buf, 'Test_number_insert_delete_lines_1', {})
+ call term_sendkeys(buf, "2dd")
+ call VerifyScreenDump(buf, 'Test_number_insert_delete_lines_3', {})
+ call term_sendkeys(buf, "P")
+ call VerifyScreenDump(buf, 'Test_number_insert_delete_lines_1', {})
+
+ call StopVimInTerminal(buf)
endfunc
" Test for displaying line numbers with 'rightleft'
diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index ca6a4c0728..56c881b958 100644
--- a/test/old/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -1142,6 +1142,10 @@ func Test_CompleteChanged()
let g:event = copy(v:event)
let g:item = get(v:event, 'completed_item', {})
let g:word = get(g:item, 'word', v:null)
+ let l:line = getline('.')
+ if g:word == v:null && l:line == "bc"
+ let g:word = l:line
+ endif
endfunction
augroup AAAAA_Group
au!
@@ -1161,6 +1165,8 @@ func Test_CompleteChanged()
call assert_equal(v:null, g:word)
call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx')
call assert_equal('foobar', g:word)
+ call feedkeys("S\<C-N>bc", 'tx')
+ call assert_equal("bc", g:word)
func Omni_test(findstart, base)
if a:findstart
diff --git a/test/old/testdir/test_registers.vim b/test/old/testdir/test_registers.vim
index b570e745f1..e113bd9e75 100644
--- a/test/old/testdir/test_registers.vim
+++ b/test/old/testdir/test_registers.vim
@@ -175,7 +175,7 @@ func Test_recording_status_in_ex_line()
call assert_equal('recording @x', Screenline(&lines))
set shortmess=q
redraw!
- call assert_equal('recording', Screenline(&lines))
+ call assert_equal('', Screenline(&lines)) " Nvim: shm+=q fully hides message
set shortmess&
norm q
redraw!
diff --git a/test/old/testdir/test_tabpage.vim b/test/old/testdir/test_tabpage.vim
index d335f3c1ee..adb9e13269 100644
--- a/test/old/testdir/test_tabpage.vim
+++ b/test/old/testdir/test_tabpage.vim
@@ -156,10 +156,13 @@ func Test_tabpage_drop()
tab split f3
normal! gt
call assert_equal(1, tabpagenr())
+ tab drop f4
+ call assert_equal(1, tabpagenr('#'))
tab drop f3
- call assert_equal(3, tabpagenr())
- call assert_equal(1, tabpagenr('#'))
+ call assert_equal(4, tabpagenr())
+ call assert_equal(2, tabpagenr('#'))
+ bwipe!
bwipe!
bwipe!
bwipe!
diff --git a/test/unit/indent_spec.lua b/test/unit/indent_spec.lua
index f93f6d7581..7902918c54 100644
--- a/test/unit/indent_spec.lua
+++ b/test/unit/indent_spec.lua
@@ -1,6 +1,8 @@
local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)
+local to_cstr = helpers.to_cstr
+local ffi = helpers.ffi
local eq = helpers.eq
local indent = helpers.cimport('./src/nvim/indent.h')
@@ -28,3 +30,31 @@ describe('get_sts_value', function()
eq(tabstop, indent.get_sts_value())
end)
end)
+
+describe('indent_size_ts()', function()
+ itp('works for spaces', function()
+ local line = to_cstr((' '):rep(7) .. 'a ')
+ eq(7, indent.indent_size_ts(line, 100, nil))
+ end)
+
+ itp('works for tabs and spaces', function()
+ local line = to_cstr(' \t \t \t\t a ')
+ eq(19, indent.indent_size_ts(line, 4, nil))
+ end)
+
+ itp('works for tabs and spaces with empty vts', function()
+ local vts = ffi.new('int[1]') -- zero initialized => first element (size) == 0
+ local line = to_cstr(' \t \t \t\t a ')
+ eq(23, indent.indent_size_ts(line, 4, vts))
+ end)
+
+ itp('works for tabs and spaces with vts', function()
+ local vts = ffi.new('int[3]')
+ vts[0] = 2 -- zero indexed
+ vts[1] = 7
+ vts[2] = 2
+
+ local line = to_cstr(' \t \t \t\t a ')
+ eq(18, indent.indent_size_ts(line, 4, vts))
+ end)
+end)
diff --git a/test/unit/statusline_spec.lua b/test/unit/statusline_spec.lua
index f1294e6b5a..83ba4176c5 100644
--- a/test/unit/statusline_spec.lua
+++ b/test/unit/statusline_spec.lua
@@ -43,6 +43,7 @@ describe('build_stl_str_hl', function()
maximum_cell_count,
NULL,
NULL,
+ NULL,
NULL
)
end