aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/add-reviewers.yml2
-rw-r--r--.github/workflows/issue-open-check.yml2
-rw-r--r--.github/workflows/remove-reviewers.yml2
-rw-r--r--.github/workflows/response.yml4
-rw-r--r--cmake.config/iwyu/mapping.imp3
-rw-r--r--cmake.deps/deps.txt8
-rw-r--r--runtime/doc/api.txt8
-rw-r--r--runtime/doc/autocmd.txt4
-rw-r--r--runtime/doc/change.txt6
-rw-r--r--runtime/doc/cmdline.txt1
-rw-r--r--runtime/doc/eval.txt8
-rw-r--r--runtime/doc/lua.txt32
-rw-r--r--runtime/doc/mbyte.txt3
-rw-r--r--runtime/doc/news.txt20
-rw-r--r--runtime/doc/provider.txt31
-rw-r--r--runtime/doc/vim_diff.txt13
-rw-r--r--runtime/lua/vim/_defaults.lua15
-rw-r--r--runtime/lua/vim/_init_packages.lua1
-rw-r--r--runtime/lua/vim/_meta/api.lua7
-rw-r--r--runtime/lua/vim/_meta/options.lua2
-rw-r--r--runtime/lua/vim/snippet.lua72
-rw-r--r--runtime/lua/vim/termcap.lua60
-rw-r--r--runtime/lua/vim/text.lua32
-rw-r--r--runtime/lua/vim/ui/clipboard/osc52.lua107
-rw-r--r--runtime/plugin/osc52.lua36
-rwxr-xr-xscripts/gen_vimdoc.py4
-rw-r--r--src/nvim/api/deprecated.c1
-rw-r--r--src/nvim/api/options.c4
-rw-r--r--src/nvim/api/private/converter.c1
-rw-r--r--src/nvim/api/ui.c16
-rw-r--r--src/nvim/api/win_config.c2
-rw-r--r--src/nvim/arabic.c3
-rw-r--r--src/nvim/autocmd.c1
-rw-r--r--src/nvim/base64.c6
-rw-r--r--src/nvim/buffer.c8
-rw-r--r--src/nvim/buffer_defs.h6
-rw-r--r--src/nvim/change.c17
-rw-r--r--src/nvim/charset.c19
-rw-r--r--src/nvim/cmdexpand.c2
-rw-r--r--src/nvim/cmdhist.c3
-rw-r--r--src/nvim/context.c2
-rw-r--r--src/nvim/cursor.c4
-rw-r--r--src/nvim/digraph.c2
-rw-r--r--src/nvim/drawline.c259
-rw-r--r--src/nvim/edit.c17
-rw-r--r--src/nvim/eval.c3
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval/funcs.c33
-rw-r--r--src/nvim/eval/vars.c2
-rw-r--r--src/nvim/eval/window.c3
-rw-r--r--src/nvim/event/libuv_process.c1
-rw-r--r--src/nvim/event/loop.c2
-rw-r--r--src/nvim/event/loop.h1
-rw-r--r--src/nvim/event/process.c2
-rw-r--r--src/nvim/event/socket.c1
-rw-r--r--src/nvim/event/stream.c1
-rw-r--r--src/nvim/ex_cmds.c118
-rw-r--r--src/nvim/ex_docmd.c3
-rw-r--r--src/nvim/ex_eval.c1
-rw-r--r--src/nvim/ex_getln.c1
-rw-r--r--src/nvim/extmark.c1
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/grid.c65
-rw-r--r--src/nvim/grid_defs.h6
-rw-r--r--src/nvim/indent.c1
-rw-r--r--src/nvim/input.c3
-rw-r--r--src/nvim/insexpand.c25
-rw-r--r--src/nvim/linematch.c1
-rw-r--r--src/nvim/lua/base64.c5
-rw-r--r--src/nvim/lua/executor.c12
-rw-r--r--src/nvim/lua/stdlib.c2
-rw-r--r--src/nvim/lua/xdiff.c1
-rw-r--r--src/nvim/main.c3
-rw-r--r--src/nvim/match.c2
-rw-r--r--src/nvim/mbyte.c123
-rw-r--r--src/nvim/mbyte.h1
-rw-r--r--src/nvim/memline.c2
-rw-r--r--src/nvim/memory.c1
-rw-r--r--src/nvim/message.c12
-rw-r--r--src/nvim/move.c14
-rw-r--r--src/nvim/normal.c12
-rw-r--r--src/nvim/option_vars.h1
-rw-r--r--src/nvim/optionstr.c5
-rw-r--r--src/nvim/path.c1
-rw-r--r--src/nvim/plines.c6
-rw-r--r--src/nvim/quickfix.c6
-rw-r--r--src/nvim/regexp.c59
-rw-r--r--src/nvim/spellsuggest.c2
-rw-r--r--src/nvim/tui/input.c138
-rw-r--r--src/nvim/tui/input.h18
-rw-r--r--src/nvim/tui/tui.c219
-rw-r--r--src/nvim/tui/tui.h16
-rw-r--r--src/nvim/ui.c1
-rw-r--r--src/nvim/window.c274
-rw-r--r--src/nvim/winfloat.c288
-rw-r--r--src/nvim/winfloat.h8
-rw-r--r--test/functional/editor/completion_spec.lua27
-rw-r--r--test/functional/legacy/messages_spec.lua6
-rw-r--r--test/functional/legacy/scroll_opt_spec.lua16
-rw-r--r--test/functional/lua/snippet_spec.lua82
-rw-r--r--test/functional/lua/text_spec.lua23
-rw-r--r--test/functional/terminal/tui_spec.lua25
-rw-r--r--test/functional/ui/fold_spec.lua38
-rw-r--r--test/functional/ui/multibyte_spec.lua30
-rw-r--r--test/functional/ui/output_spec.lua4
-rw-r--r--test/old/testdir/crash/bt_quickfix1_poc5
-rw-r--r--test/old/testdir/crash/bt_quickfix_poc9
-rw-r--r--test/old/testdir/crash/crash_scrollbar2
-rw-r--r--test/old/testdir/crash/editing_arg_idx_POC_1bin0 -> 398 bytes
-rw-r--r--test/old/testdir/crash/poc1bin0 -> 3264 bytes
-rw-r--r--test/old/testdir/crash/poc_huaf1bin0 -> 1541 bytes
-rw-r--r--test/old/testdir/crash/poc_huaf2bin0 -> 3238 bytes
-rw-r--r--test/old/testdir/crash/poc_huaf3bin0 -> 4053 bytes
-rw-r--r--test/old/testdir/crash/poc_tagfunc.vim6
-rw-r--r--test/old/testdir/crash/vim_msg_trunc_poc1
-rw-r--r--test/old/testdir/crash/vim_regsub_both_pocbin0 -> 244 bytes
-rw-r--r--test/old/testdir/test_crash.vim139
-rw-r--r--test/old/testdir/test_excmd.vim4
-rw-r--r--test/old/testdir/test_normal.vim5
-rw-r--r--test/old/testdir/test_scroll_opt.vim19
-rw-r--r--test/old/testdir/test_spell.vim9
-rw-r--r--test/old/testdir/test_substitute.vim16
-rw-r--r--test/unit/mbyte_spec.lua243
123 files changed, 1730 insertions, 1348 deletions
diff --git a/.github/workflows/add-reviewers.yml b/.github/workflows/add-reviewers.yml
index 9b79290008..22c68b6ef7 100644
--- a/.github/workflows/add-reviewers.yml
+++ b/.github/workflows/add-reviewers.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: 'Request reviewers'
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
script: |
const script = require('./.github/scripts/reviews.js')
diff --git a/.github/workflows/issue-open-check.yml b/.github/workflows/issue-open-check.yml
index aef1a90c38..eac1c2ee4d 100644
--- a/.github/workflows/issue-open-check.yml
+++ b/.github/workflows/issue-open-check.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- name: check issue title
id: check-issue
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
script: |
const title = context.payload.issue.title;
diff --git a/.github/workflows/remove-reviewers.yml b/.github/workflows/remove-reviewers.yml
index bf509b07a8..3fe7493b93 100644
--- a/.github/workflows/remove-reviewers.yml
+++ b/.github/workflows/remove-reviewers.yml
@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: 'Remove reviewers'
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
script: |
const script = require('./.github/scripts/remove-reviewers.js')
diff --git a/.github/workflows/response.yml b/.github/workflows/response.yml
index a8ea88765b..663ae6ad87 100644
--- a/.github/workflows/response.yml
+++ b/.github/workflows/response.yml
@@ -14,7 +14,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v4
- - uses: actions/github-script@v6
+ - uses: actions/github-script@v7
with:
script: |
const script = require('./.github/scripts/close_unresponsive.js')
@@ -28,7 +28,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v4
- - uses: actions/github-script@v6
+ - uses: actions/github-script@v7
with:
script: |
const script = require('./.github/scripts/remove_response_label.js')
diff --git a/cmake.config/iwyu/mapping.imp b/cmake.config/iwyu/mapping.imp
index 6aaf52d3ff..465ab2118d 100644
--- a/cmake.config/iwyu/mapping.imp
+++ b/cmake.config/iwyu/mapping.imp
@@ -19,6 +19,7 @@
{ include: [ '"arabic.h.generated.h"', private, '"nvim/arabic.h"', public ] },
{ include: [ '"arglist.h.generated.h"', private, '"nvim/arglist.h"', public ] },
{ include: [ '"autocmd.h.generated.h"', private, '"nvim/autocmd.h"', public ] },
+ { include: [ '"base64.h.generated.h"', private, '"nvim/base64.h"', public ] },
{ include: [ '"buffer.h.generated.h"', private, '"nvim/buffer.h"', public ] },
{ include: [ '"buffer_updates.h.generated.h"', private, '"nvim/buffer_updates.h"', public ] },
{ include: [ '"bufwrite.h.generated.h"', private, '"nvim/bufwrite.h"', public ] },
@@ -84,6 +85,7 @@
{ include: [ '"linematch.h.generated.h"', private, '"nvim/linematch.h"', public ] },
{ include: [ '"locale.h.generated.h"', private, '"nvim/locale.h"', public ] },
{ include: [ '"log.h.generated.h"', private, '"nvim/log.h"', public ] },
+ { include: [ '"lua/base64.h.generated.h"', private, '"nvim/lua/base64.h"', public ] },
{ include: [ '"lua/converter.h.generated.h"', private, '"nvim/lua/converter.h"', public ] },
{ include: [ '"lua/executor.h.generated.h"', private, '"nvim/lua/executor.h"', public ] },
{ include: [ '"lua/secure.h.generated.h"', private, '"nvim/lua/secure.h"', public ] },
@@ -162,6 +164,7 @@
{ include: [ '"viml/parser/expressions.h.generated.h"', private, '"nvim/viml/parser/expressions.h"', public ] },
{ include: [ '"viml/parser/parser.h.generated.h"', private, '"nvim/viml/parser/parser.h"', public ] },
{ include: [ '"window.h.generated.h"', private, '"nvim/window.h"', public ] },
+ { include: [ '"winfloat.h.generated.h"', private, '"nvim/winfloat.h"', public ] },
# Generated to normal headers with a different name: header.h.generated.h -> nvim/some_other_header.h
{ include: [ '"api/private/dispatch_wrappers.h.generated.h"', private, '"nvim/api/private/dispatch.h"', public ] },
diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
index fc63aaec95..72eabac649 100644
--- a/cmake.deps/deps.txt
+++ b/cmake.deps/deps.txt
@@ -4,8 +4,8 @@ LIBUV_SHA256 d50af7e6d72526db137e66fad812421c8a1cae09d146b0ec2bb9a22c5f23ba93
MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz
MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba
-LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/113a168b792cd367822ec04cdc2ef32facd28efa.tar.gz
-LUAJIT_SHA256 a1a6512a2ab3ce5a41c69f3841b6fd9fddb046bff2c39ebb9a34ed0083ab0853
+LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/43d0a19158ceabaa51b0462c1ebc97612b420a2e.tar.gz
+LUAJIT_SHA256 4fefa19bc5600928fb13c928bf5325eaa1c78f2c1738a8ac9552154ef178bb9a
LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz
LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333
@@ -13,8 +13,8 @@ LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333
UNIBILIUM_URL https://github.com/neovim/unibilium/archive/d72c3598e7ac5d1ebf86ee268b8b4ed95c0fa628.tar.gz
UNIBILIUM_SHA256 9c4747c862ab5e3076dcf8fa8f0ea7a6b50f20ec5905618b9536655596797487
-LIBTERMKEY_URL https://github.com/neovim/libtermkey/archive/v0.22.tar.gz
-LIBTERMKEY_SHA256 81cac2b685c9ada4ead4ea788fb69ff74fc1947ad188ed0264c646fe12b496ba
+LIBTERMKEY_URL https://github.com/neovim/libtermkey/archive/dcb198a85c520ce38450a5970c97f8ff03b50f0e.tar.gz
+LIBTERMKEY_SHA256 5eb3e50af54312817bd56fa63b9f8dbd12ec11cedbcf8a7aa0fd79950cc83259
LIBVTERM_URL https://github.com/neovim/libvterm/archive/v0.3.3.tar.gz
LIBVTERM_SHA256 0babe3ab42c354925dadede90d352f054aa9c4ae6842ea803a20c9741e172e56
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 1b11d8571a..57491b34d6 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -3598,13 +3598,13 @@ nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()*
|RPC| only
nvim_ui_term_event({event}, {value}) *nvim_ui_term_event()*
- Tells Nvim when a terminal event has occurred: sets |v:termresponse| and
- fires |TermResponse|.
+ Tells Nvim when a terminal event has occurred
The following terminal events are supported:
- • "osc_response": The terminal sent a OSC response sequence to Nvim. The
- payload is the received OSC sequence.
+ • "termresponse": The terminal sent an OSC or DCS response sequence to
+ Nvim. The payload is the received response. Sets |v:termresponse| and
+ fires |TermResponse|.
Attributes: ~
|RPC| only
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 6b698b0868..c6f6559e37 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -987,8 +987,8 @@ TermClose When a |terminal| job ends.
Sets these |v:event| keys:
status
*TermResponse*
-TermResponse When Nvim receives a OSC response from the
- terminal. Sets |v:termresponse|. When used
+TermResponse When Nvim receives an OSC or DCS response from
+ the terminal. Sets |v:termresponse|. When used
from Lua, the response string is included in
the "data" field of the autocommand callback.
May be triggered halfway through another event
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index 2c47421b02..e1bb7c5fc7 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -619,9 +619,9 @@ original user.
current line only. When [count] is given, replace in
[count] lines, starting with the last line in [range].
When [range] is omitted start in the current line.
- *E939*
- [count] must be a positive number. Also see
- |cmdline-ranges|.
+ *E939* *E1510*
+ [count] must be a positive number (max 2147483647)
+ Also see |cmdline-ranges|.
See |:s_flags| for [flags].
The delimiter doesn't need to be /, see
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index 291286d8f7..8bed8a9ffc 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -339,6 +339,7 @@ terminals)
A positive number represents the absolute index of an entry
as it is given in the first column of a :history listing.
This number remains fixed even if other entries are deleted.
+ (see |E1510|)
A negative number means the relative position of an entry,
counted from the newest entry (which has index -1) backwards.
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 2223829548..a73932be00 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2318,10 +2318,10 @@ v:t_string Value of |String| type. Read-only. See: |type()|
v:t_blob Value of |Blob| type. Read-only. See: |type()|
*v:termresponse* *termresponse-variable*
-v:termresponse The value of the most recent OSC escape sequence received by
- Nvim from the terminal. This can be read in a |TermResponse|
- event handler after querying the terminal using another escape
- sequence.
+v:termresponse The value of the most recent OSC or DCS escape sequence
+ received by Nvim from the terminal. This can be read in a
+ |TermResponse| event handler after querying the terminal using
+ another escape sequence.
*v:testing* *testing-variable*
v:testing Must be set before using `test_garbagecollect_now()`.
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index ada07a71f8..596b58d4ff 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -3697,16 +3697,16 @@ totable({f}, {...}) *vim.iter.totable()*
==============================================================================
Lua module: vim.snippet *vim.snippet*
-active() *snippet.active()*
+vim.snippet.active() *vim.snippet.active()*
Returns `true` if there's an active snippet in the current buffer.
Return: ~
(boolean)
-exit() *snippet.exit()*
+vim.snippet.exit() *vim.snippet.exit()*
Exits the current snippet.
-expand({input}) *snippet.expand()*
+vim.snippet.expand({input}) *vim.snippet.expand()*
Expands the given snippet text. Refer to https://microsoft.github.io/language-server-protocol/specification/#snippet_syntax for the specification of valid input.
Tabstops are highlighted with hl-SnippetTabstop.
@@ -3714,7 +3714,7 @@ expand({input}) *snippet.expand()*
Parameters: ~
• {input} (string)
-jump({direction}) *snippet.jump()*
+vim.snippet.jump({direction}) *vim.snippet.jump()*
Jumps within the active snippet in the given direction. If the jump isn't
possible, the function call does nothing.
@@ -3732,7 +3732,7 @@ jump({direction}) *snippet.jump()*
• {direction} (vim.snippet.Direction) Navigation direction. -1 for
previous, 1 for next.
-jumpable({direction}) *snippet.jumpable()*
+vim.snippet.jumpable({direction}) *vim.snippet.jumpable()*
Returns `true` if there is an active snippet which can be jumped in the
given direction. You can use this function to navigate a snippet as
follows: >lua
@@ -3752,4 +3752,26 @@ jumpable({direction}) *snippet.jumpable()*
Return: ~
(boolean)
+
+==============================================================================
+Lua module: vim.text *vim.text*
+
+vim.text.hexdecode({enc}) *vim.text.hexdecode()*
+ Hex decode a string.
+
+ Parameters: ~
+ • {enc} (string) String to decode
+
+ Return: ~
+ (string) Decoded string
+
+vim.text.hexencode({str}) *vim.text.hexencode()*
+ Hex encode a string.
+
+ Parameters: ~
+ • {str} (string) String to encode
+
+ Return: ~
+ (string) Hex encoded string
+
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt
index aedef87a09..0a7e0baad3 100644
--- a/runtime/doc/mbyte.txt
+++ b/runtime/doc/mbyte.txt
@@ -646,7 +646,8 @@ widespread as file format.
A composing or combining character is used to change the meaning of the
character before it. The combining characters are drawn on top of the
preceding character.
-Up to six combining characters can be displayed.
+Too big combined characters cannot be displayed, but they can still be
+inspected using the |g8| and |ga| commands described below.
When editing text a composing character is mostly considered part of the
preceding character. For example "x" will delete a character and its
following composing characters by default.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 818f0e5a57..8073a4f162 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -15,6 +15,13 @@ BREAKING CHANGES *news-breaking*
The following changes may require adaptations in user config or plugins.
+• In some cases, the cursor in the Nvim |TUI| would blink even without
+ configuring 'guicursor' as mentioned in |cursor-blinking|. This was a bug
+ that has now been fixed. If your cursor has stopped blinking, add the
+ following (or similar, adapted to user preference) to your |config| file: >vim
+
+ set guicursor+=n-v-c:blinkon500-blinkoff500
+<
• |vim.tbl_islist()| now checks whether a table is actually list-like (i.e.,
has integer keys without gaps and starting from 1). For the previous
behavior (only check for integer keys, allow gaps or not starting with 1),
@@ -217,11 +224,15 @@ The following new APIs and features were added.
read escape sequence responses from the terminal.
• A clipboard provider which uses OSC 52 to copy the selection to the system
- clipboard is now bundled by default. |clipboard-osc52|
+ clipboard is now bundled by default and will be automatically enabled under
+ certain conditions. |clipboard-osc52|
• The 'termsync' option asks the terminal emulator to buffer screen updates
until the redraw cycle is complete. Requires support from the terminal.
+• Added |vim.text.hexencode()| and |vim.text.hexdecode()| to convert strings
+ to and from byte representations.
+
==============================================================================
CHANGED FEATURES *news-changed*
@@ -298,6 +309,13 @@ The following changes to existing APIs or features add new behavior.
Note that syntax highlighting of code examples requires a matching parser
and may be affected by custom queries.
+• Support for rendering multibyte characters using composing characters has been
+ enhanced. The maximum limit have been increased from 1+6 codepoints to
+ 31 bytes, which is guaranteed to fit all chars from before but often more.
+
+ NOTE: the regexp engine still has a hard-coded limit of considering
+ 6 composing chars only.
+
==============================================================================
REMOVED FEATURES *news-removed*
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index 23bde05072..b8182347f8 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -260,27 +260,40 @@ using OSC 52. OSC 52 is an Operating System Command control sequence that
writes the copied text to the terminal emulator. If the terminal emulator
supports OSC 52 then it will write the copied text into the system clipboard.
-This is most useful when using Nvim remotely (e.g. via ssh) as Nvim does not
-have direct access to the system clipboard in that case.
+Nvim will attempt to automatically determine if the host terminal emulator
+supports the OSC 52 sequence and enable the OSC 52 clipboard provider if it
+does as long as all of the following are true:
-Because not all terminal emulators support OSC 52, this provider must be opted
-into explicitly by setting the following |g:clipboard| definition: >lua
+ • Nvim is running in the |TUI|
+ • |g:clipboard| is unset
+ • 'clipboard' is not set to "unnamed" or "unnamedplus"
+ • $SSH_TTY is set
+
+If any of the above conditions are not met then the OSC 52 clipboard provider
+will not be used by default and Nvim will fall back to discovering a
+|clipboard-tool| through the usual process.
+
+To force Nvim to use the OSC 52 provider you can use the following
+|g:clipboard| definition: >lua
vim.g.clipboard = {
name = 'OSC 52',
copy = {
- ['+'] = require('vim.ui.clipboard.osc52').copy,
- ['*'] = require('vim.ui.clipboard.osc52').copy,
+ ['+'] = require('vim.ui.clipboard.osc52').copy('+'),
+ ['*'] = require('vim.ui.clipboard.osc52').copy('*'),
},
paste = {
- ['+'] = require('vim.ui.clipboard.osc52').paste,
- ['*'] = require('vim.ui.clipboard.osc52').paste,
+ ['+'] = require('vim.ui.clipboard.osc52').paste('+'),
+ ['*'] = require('vim.ui.clipboard.osc52').paste('*'),
},
}
<
Note that not all terminal emulators support reading from the system clipboard
(and even for those that do, users should be aware of the security
-implications), so using OSC 52 for pasting may not be possible.
+implications), so using OSC 52 for pasting may not be possible (and not
+necessary, because you can |paste| instead using your system paste function).
+Users may need to configure their terminal emulator to allow reading from the
+clipboard.
<
==============================================================================
Paste *provider-paste* *paste*
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index fb23535c76..cf9b3cf0e5 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -723,9 +723,16 @@ Options:
<
*'macatsui'*
*'maxcombine'* *'mco'*
- Nvim always displays up to 6 combining characters. You can still edit
- text with more than 6 combining characters, you just can't see them.
- Use |g8| or |ga|. See |mbyte-combining|.
+ Nvim counts maximum character sizes in bytes, not codepoints. This is
+ guaranteed to be big enough to always fit all chars properly displayed
+ in vim with 'maxcombine' set to 6.
+
+ You can still edit text with larger characters than fits in the screen buffer,
+ you just can't see them. Use |g8| or |ga|. See |mbyte-combining|.
+
+ NOTE: the rexexp engine still has a hard-coded limit of considering
+ 6 composing chars only.
+
*'maxmem'* Nvim delegates memory-management to the OS.
*'maxmemtot'* Nvim delegates memory-management to the OS.
printoptions
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index 870603c9f3..09d6d43e7a 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -190,14 +190,17 @@ do
--- @param c string Color as a string of hex chars
--- @return number? Intensity of the color
local function parsecolor(c)
- local len = #c
- assert(len > 0 and len <= 4, 'Invalid hex color string')
- if not c:match('^0x') then
- c = string.format('0x%s', c)
+ if #c == 0 or #c > 4 then
+ return nil
end
- local max = tonumber(string.format('0x%s', string.rep('f', len)))
- return tonumber(c) / max
+ local val = tonumber(c, 16)
+ if not val then
+ return nil
+ end
+
+ local max = tonumber(string.rep('f', #c), 16)
+ return val / max
end
--- Parse an OSC 11 response
diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua
index 8750afba34..4a961970cc 100644
--- a/runtime/lua/vim/_init_packages.lua
+++ b/runtime/lua/vim/_init_packages.lua
@@ -57,6 +57,7 @@ vim._submodules = {
fs = true,
iter = true,
re = true,
+ text = true,
}
-- These are for loading runtime modules in the vim namespace lazily.
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 70a8b0aec2..006996ad4e 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -2065,11 +2065,12 @@ function vim.api.nvim_ui_set_focus(gained) end
--- @param value any
function vim.api.nvim_ui_set_option(name, value) end
---- Tells Nvim when a terminal event has occurred.
+--- Tells Nvim when a terminal event has occurred
--- The following terminal events are supported:
---
---- • "osc_response": The terminal sent a OSC response sequence to Nvim. The
---- payload is the received OSC sequence.
+--- • "termresponse": The terminal sent an OSC or DCS response sequence to
+--- Nvim. The payload is the received response. Sets `v:termresponse` and
+--- fires `TermResponse`.
---
---
--- @param event string Event name
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 6d693ca036..d2bdab4d28 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -2576,7 +2576,7 @@ vim.go.fp = vim.go.formatprg
--- security reasons.
---
--- @type boolean
-vim.o.fsync = false
+vim.o.fsync = true
vim.o.fs = vim.o.fsync
vim.go.fsync = vim.o.fsync
vim.go.fs = vim.go.fsync
diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua
index 94c69795a4..32a8ea0b0d 100644
--- a/runtime/lua/vim/snippet.lua
+++ b/runtime/lua/vim/snippet.lua
@@ -104,8 +104,9 @@ end
--- @class vim.snippet.Tabstop
--- @field extmark_id integer
---- @field index integer
--- @field bufnr integer
+--- @field index integer
+--- @field choices? string[]
local Tabstop = {}
--- Creates a new tabstop.
@@ -114,8 +115,9 @@ local Tabstop = {}
--- @param index integer
--- @param bufnr integer
--- @param range Range4
+--- @param choices? string[]
--- @return vim.snippet.Tabstop
-function Tabstop.new(index, bufnr, range)
+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,
end_right_gravity = true,
@@ -125,7 +127,7 @@ function Tabstop.new(index, bufnr, range)
})
local self = setmetatable(
- { index = index, bufnr = bufnr, extmark_id = extmark_id },
+ { extmark_id = extmark_id, bufnr = bufnr, index = index, choices = choices },
{ __index = Tabstop }
)
@@ -173,9 +175,9 @@ local Session = {}
--- @package
--- @param bufnr integer
--- @param snippet_extmark integer
---- @param tabstop_ranges table<integer, Range4[]>
+--- @param tabstop_data table<integer, { range: Range4, choices?: string[] }[]>
--- @return vim.snippet.Session
-function Session.new(bufnr, snippet_extmark, tabstop_ranges)
+function Session.new(bufnr, snippet_extmark, tabstop_data)
local self = setmetatable({
bufnr = bufnr,
extmark_id = snippet_extmark,
@@ -184,10 +186,10 @@ function Session.new(bufnr, snippet_extmark, tabstop_ranges)
}, { __index = Session })
-- Create the tabstops.
- for index, ranges in pairs(tabstop_ranges) do
- for _, range in ipairs(ranges) do
+ for index, ranges in pairs(tabstop_data) do
+ for _, data in ipairs(ranges) do
self.tabstops[index] = self.tabstops[index] or {}
- table.insert(self.tabstops[index], Tabstop.new(index, self.bufnr, range))
+ table.insert(self.tabstops[index], Tabstop.new(index, self.bufnr, data.range, data.choices))
end
end
@@ -222,6 +224,22 @@ end
--- @field private _session? vim.snippet.Session
local M = { session = nil }
+--- Displays the choices for the given tabstop as completion items.
+---
+--- @param tabstop vim.snippet.Tabstop
+local function display_choices(tabstop)
+ assert(tabstop.choices, 'Tabstop has no choices')
+
+ local start_col = tabstop:get_range()[2] + 1
+ local matches = vim.iter.map(function(choice)
+ return { word = choice }
+ end, tabstop.choices)
+
+ vim.defer_fn(function()
+ vim.fn.complete(start_col, matches)
+ end, 100)
+end
+
--- Select the given tabstop range.
---
--- @param tabstop vim.snippet.Tabstop
@@ -246,17 +264,25 @@ local function select_tabstop(tabstop)
local range = tabstop:get_range()
local mode = vim.fn.mode()
+ if vim.fn.pumvisible() ~= 0 then
+ -- Close the choice completion menu if open.
+ vim.fn.complete(vim.fn.col('.'), {})
+ end
+
-- Move the cursor to the start of the tabstop.
vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
- -- For empty and the final tabstop, start insert mode at the end of the range.
- if tabstop.index == 0 or (range[1] == range[3] and range[2] == range[4]) then
+ -- For empty, choice and the final tabstops, start insert mode at the end of the range.
+ if tabstop.choices or tabstop.index == 0 or (range[1] == range[3] and range[2] == range[4]) then
if mode ~= 'i' then
if mode == 's' then
feedkeys('<Esc>')
end
vim.cmd.startinsert({ bang = range[4] >= #vim.api.nvim_get_current_line() })
end
+ if tabstop.choices then
+ display_choices(tabstop)
+ end
else
-- Else, select the tabstop's text.
if mode ~= 'n' then
@@ -297,7 +323,6 @@ local function setup_autocmds(bufnr)
return true
end
- -- Update the current tabstop to be the one containing the cursor.
for tabstop_index, tabstops in pairs(M._session.tabstops) do
for _, tabstop in ipairs(tabstops) do
local range = tabstop:get_range()
@@ -305,7 +330,6 @@ local function setup_autocmds(bufnr)
(cursor_row > range[1] or (cursor_row == range[1] and cursor_col >= range[2]))
and (cursor_row < range[3] or (cursor_row == range[3] and cursor_col <= range[4]))
then
- M._session.current_tabstop = tabstop
if tabstop_index ~= 0 then
return
end
@@ -377,14 +401,16 @@ function M.expand(input)
end
-- Keep track of tabstop nodes during expansion.
- --- @type table<integer, Range4[]>
- local tabstop_ranges = {}
+ --- @type table<integer, { range: Range4, choices?: string[] }[]>
+ local tabstop_data = {}
--- @param index integer
- --- @param placeholder string?
- local function add_tabstop(index, placeholder)
- tabstop_ranges[index] = tabstop_ranges[index] or {}
- table.insert(tabstop_ranges[index], compute_tabstop_range(snippet_text, placeholder))
+ --- @param placeholder? string
+ --- @param choices? string[]
+ local function add_tabstop(index, placeholder, choices)
+ tabstop_data[index] = tabstop_data[index] or {}
+ local range = compute_tabstop_range(snippet_text, placeholder)
+ table.insert(tabstop_data[index], { range = range, choices = choices })
end
--- Appends the given text to the snippet, taking care of indentation.
@@ -428,7 +454,7 @@ function M.expand(input)
append_to_snippet(value)
elseif type == G.NodeType.Choice then
--- @cast data vim.snippet.ChoiceData
- append_to_snippet(data.values[1])
+ add_tabstop(data.tabstop, nil, data.values)
elseif type == G.NodeType.Variable then
--- @cast data vim.snippet.VariableData
-- Try to get the variable's value.
@@ -436,7 +462,7 @@ function M.expand(input)
if not value then
-- Unknown variable, make this a tabstop and use the variable name as a placeholder.
value = data.name
- local tabstop_indexes = vim.tbl_keys(tabstop_ranges)
+ local tabstop_indexes = vim.tbl_keys(tabstop_data)
local index = math.max(unpack((#tabstop_indexes == 0 and { 0 }) or tabstop_indexes)) + 1
add_tabstop(index, value)
end
@@ -449,8 +475,8 @@ function M.expand(input)
-- $0, which defaults to the end of the snippet, defines the final cursor position.
-- Make sure the snippet has exactly one of these.
- if vim.tbl_contains(vim.tbl_keys(tabstop_ranges), 0) then
- assert(#tabstop_ranges[0] == 1, 'Snippet has multiple $0 tabstops')
+ if vim.tbl_contains(vim.tbl_keys(tabstop_data), 0) then
+ assert(#tabstop_data[0] == 1, 'Snippet has multiple $0 tabstops')
else
add_tabstop(0)
end
@@ -469,7 +495,7 @@ function M.expand(input)
right_gravity = false,
end_right_gravity = true,
})
- M._session = Session.new(bufnr, snippet_extmark, tabstop_ranges)
+ M._session = Session.new(bufnr, snippet_extmark, tabstop_data)
-- Jump to the first tabstop.
M.jump(1)
diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua
new file mode 100644
index 0000000000..0eefc5eee4
--- /dev/null
+++ b/runtime/lua/vim/termcap.lua
@@ -0,0 +1,60 @@
+local M = {}
+
+--- Query the host terminal emulator for terminfo capabilities.
+---
+--- This function sends the XTGETTCAP DCS sequence to the host terminal emulator asking the terminal
+--- to send us its terminal capabilities. These are strings that are normally taken from a terminfo
+--- file, however an up to date terminfo database is not always available (particularly on remote
+--- machines), and many terminals continue to misidentify themselves or do not provide their own
+--- terminfo file, making the terminfo database unreliable.
+---
+--- Querying the terminal guarantees that we get a truthful answer, but only if the host terminal
+--- emulator supports the XTGETTCAP sequence.
+---
+--- @param caps string|table A terminal capability or list of capabilities to query
+--- @param cb function(cap:string, seq:string) Function to call when a response is received
+function M.query(caps, cb)
+ vim.validate({
+ caps = { caps, { 'string', 'table' } },
+ cb = { cb, 'f' },
+ })
+
+ if type(caps) ~= 'table' then
+ caps = { caps }
+ end
+
+ local count = #caps
+
+ vim.api.nvim_create_autocmd('TermResponse', {
+ callback = function(args)
+ local resp = args.data ---@type string
+ local k, v = resp:match('^\027P1%+r(%x+)=(%x+)$')
+ if k and v then
+ local cap = vim.text.hexdecode(k)
+ local seq =
+ vim.text.hexdecode(v):gsub('\\E', '\027'):gsub('%%p%d', ''):gsub('\\(%d+)', string.char)
+
+ -- TODO: When libtermkey is patched to accept BEL as an OSC terminator, this workaround can
+ -- be removed
+ seq = seq:gsub('\007$', '\027\\')
+
+ cb(cap, seq)
+
+ count = count - 1
+ if count == 0 then
+ return true
+ end
+ end
+ end,
+ })
+
+ local encoded = {} ---@type string[]
+ for i = 1, #caps do
+ encoded[i] = vim.text.hexencode(caps[i])
+ end
+
+ local query = string.format('\027P+q%s\027\\', table.concat(encoded, ';'))
+ io.stdout:write(query)
+end
+
+return M
diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua
new file mode 100644
index 0000000000..cfb0f9b821
--- /dev/null
+++ b/runtime/lua/vim/text.lua
@@ -0,0 +1,32 @@
+--- Text processing functions.
+
+local M = {}
+
+--- Hex encode a string.
+---
+--- @param str string String to encode
+--- @return string Hex encoded string
+function M.hexencode(str)
+ local bytes = { str:byte(1, #str) }
+ local enc = {} ---@type string[]
+ for i = 1, #bytes do
+ enc[i] = string.format('%02X', bytes[i])
+ end
+ return table.concat(enc)
+end
+
+--- Hex decode a string.
+---
+--- @param enc string String to decode
+--- @return string Decoded string
+function M.hexdecode(enc)
+ assert(#enc % 2 == 0, 'string must have an even number of hex characters')
+ local str = {} ---@type string[]
+ for i = 1, #enc, 2 do
+ local n = assert(tonumber(enc:sub(i, i + 1), 16))
+ str[#str + 1] = string.char(n)
+ end
+ return table.concat(str)
+end
+
+return M
diff --git a/runtime/lua/vim/ui/clipboard/osc52.lua b/runtime/lua/vim/ui/clipboard/osc52.lua
index 035a6abb86..6483f0387d 100644
--- a/runtime/lua/vim/ui/clipboard/osc52.lua
+++ b/runtime/lua/vim/ui/clipboard/osc52.lua
@@ -1,60 +1,75 @@
local M = {}
-function M.copy(lines)
- local s = table.concat(lines, '\n')
- io.stdout:write(string.format('\027]52;;%s\027\\', vim.base64.encode(s)))
+--- Return the OSC 52 escape sequence
+---
+--- @param clipboard string The clipboard to read from or write to
+--- @param contents string The Base64 encoded contents to write to the clipboard, or '?' to read
+--- from the clipboard
+local function osc52(clipboard, contents)
+ return string.format('\027]52;%s;%s\027\\', clipboard, contents)
end
-function M.paste()
- local contents = nil
- local id = vim.api.nvim_create_autocmd('TermResponse', {
- callback = function(args)
- local resp = args.data ---@type string
- local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)')
- if encoded then
- contents = vim.base64.decode(encoded)
- return true
- end
- end,
- })
-
- io.stdout:write('\027]52;;?\027\\')
-
- local ok, res
-
- -- Wait 1s first for terminals that respond quickly
- ok, res = vim.wait(1000, function()
- return contents ~= nil
- end)
-
- if res == -1 then
- -- If no response was received after 1s, print a message and keep waiting
- vim.api.nvim_echo(
- { { 'Waiting for OSC 52 response from the terminal. Press Ctrl-C to interrupt...' } },
- false,
- {}
- )
- ok, res = vim.wait(9000, function()
+function M.copy(reg)
+ local clipboard = reg == '+' and 'c' or 'p'
+ return function(lines)
+ local s = table.concat(lines, '\n')
+ io.stdout:write(osc52(clipboard, vim.base64.encode(s)))
+ end
+end
+
+function M.paste(reg)
+ local clipboard = reg == '+' and 'c' or 'p'
+ return function()
+ local contents = nil
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
+ callback = function(args)
+ local resp = args.data ---@type string
+ local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)')
+ if encoded then
+ contents = vim.base64.decode(encoded)
+ return true
+ end
+ end,
+ })
+
+ io.stdout:write(osc52(clipboard, '?'))
+
+ local ok, res
+
+ -- Wait 1s first for terminals that respond quickly
+ ok, res = vim.wait(1000, function()
return contents ~= nil
end)
- end
- if not ok then
- vim.api.nvim_del_autocmd(id)
if res == -1 then
- vim.notify(
- 'Timed out waiting for a clipboard response from the terminal',
- vim.log.levels.WARN
+ -- If no response was received after 1s, print a message and keep waiting
+ vim.api.nvim_echo(
+ { { 'Waiting for OSC 52 response from the terminal. Press Ctrl-C to interrupt...' } },
+ false,
+ {}
)
- elseif res == -2 then
- -- Clear message area
- vim.api.nvim_echo({ { '' } }, false, {})
+ ok, res = vim.wait(9000, function()
+ return contents ~= nil
+ end)
+ end
+
+ if not ok then
+ vim.api.nvim_del_autocmd(id)
+ if res == -1 then
+ vim.notify(
+ 'Timed out waiting for a clipboard response from the terminal',
+ vim.log.levels.WARN
+ )
+ elseif res == -2 then
+ -- Clear message area
+ vim.api.nvim_echo({ { '' } }, false, {})
+ end
+ return 0
end
- return 0
- end
- -- If we get here, contents should be non-nil
- return vim.split(assert(contents), '\n')
+ -- If we get here, contents should be non-nil
+ return vim.split(assert(contents), '\n')
+ end
end
return M
diff --git a/runtime/plugin/osc52.lua b/runtime/plugin/osc52.lua
new file mode 100644
index 0000000000..374b70066f
--- /dev/null
+++ b/runtime/plugin/osc52.lua
@@ -0,0 +1,36 @@
+local tty = vim.iter(vim.api.nvim_list_uis()):any(function(ui)
+ return ui.chan == 1 and ui.stdout_tty
+end)
+
+if not tty or vim.g.clipboard ~= nil or vim.o.clipboard ~= '' or not os.getenv('SSH_TTY') then
+ return
+end
+
+require('vim.termcap').query('Ms', function(cap, seq)
+ assert(cap == 'Ms')
+
+ -- Check 'clipboard' and g:clipboard again to avoid a race condition
+ if vim.o.clipboard ~= '' or vim.g.clipboard ~= nil then
+ return
+ end
+
+ -- If the terminal reports a sequence other than OSC 52 for the Ms capability
+ -- then ignore it. We only support OSC 52 (for now)
+ if not seq:match('^\027%]52') then
+ return
+ end
+
+ local osc52 = require('vim.ui.clipboard.osc52')
+
+ vim.g.clipboard = {
+ name = 'OSC 52',
+ copy = {
+ ['+'] = osc52.copy('+'),
+ ['*'] = osc52.copy('*'),
+ },
+ paste = {
+ ['+'] = osc52.paste('+'),
+ ['*'] = osc52.paste('*'),
+ },
+ }
+end)
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index c738242c5d..925e6f98e6 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -167,6 +167,7 @@ CONFIG = {
'version.lua',
'iter.lua',
'snippet.lua',
+ 'text.lua',
],
'files': [
'runtime/lua/vim/iter.lua',
@@ -184,6 +185,7 @@ CONFIG = {
'runtime/lua/vim/version.lua',
'runtime/lua/vim/_inspector.lua',
'runtime/lua/vim/snippet.lua',
+ 'runtime/lua/vim/text.lua',
'runtime/lua/vim/_meta/builtin.lua',
'runtime/lua/vim/_meta/diff.lua',
'runtime/lua/vim/_meta/mpack.lua',
@@ -246,6 +248,8 @@ CONFIG = {
'base64': 'vim.base64',
'regex': 'vim.regex',
'spell': 'vim.spell',
+ 'snippet': 'vim.snippet',
+ 'text': 'vim.text',
},
'append_only': [
'shared.lua',
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 8398a3a5b1..ff9f8ff18e 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -1,4 +1,3 @@
-#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index 61debb70fe..b0053dbb34 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -1,7 +1,6 @@
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
-#include <stdlib.h>
#include <string.h>
#include "nvim/api/keysets.h"
@@ -13,9 +12,10 @@
#include "nvim/buffer.h"
#include "nvim/eval/window.h"
#include "nvim/globals.h"
+#include "nvim/macros.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/types.h"
+#include "nvim/option_vars.h"
#include "nvim/vim.h"
#include "nvim/window.h"
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 1188b04bdc..6e6d054374 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -2,7 +2,6 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
#include "klib/kvec.h"
#include "nvim/api/private/converter.h"
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index c898925af8..c1fc986029 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -2,6 +2,7 @@
#include <inttypes.h>
#include <msgpack/pack.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -512,12 +513,13 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
ui->pum_pos = true;
}
-/// Tells Nvim when a terminal event has occurred: sets |v:termresponse| and fires |TermResponse|.
+/// Tells Nvim when a terminal event has occurred
///
/// The following terminal events are supported:
///
-/// - "osc_response": The terminal sent a OSC response sequence to Nvim. The
-/// payload is the received OSC sequence.
+/// - "termresponse": The terminal sent an OSC or DCS response sequence to
+/// Nvim. The payload is the received response. Sets
+/// |v:termresponse| and fires |TermResponse|.
///
/// @param channel_id
/// @param event Event name
@@ -526,14 +528,14 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *err)
FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY
{
- if (strequal("osc_response", event.data)) {
+ if (strequal("termresponse", event.data)) {
if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "osc_response must be a string");
+ api_set_error(err, kErrorTypeValidation, "termresponse must be a string");
return;
}
- const String osc_response = value.data.string;
- set_vim_var_string(VV_TERMRESPONSE, osc_response.data, (ptrdiff_t)osc_response.size);
+ const String termresponse = value.data.string;
+ set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);
apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value);
}
}
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 83c8ba832c..4b16e26103 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -1,5 +1,4 @@
#include <stdbool.h>
-#include <stdlib.h>
#include <string.h>
#include "klib/kvec.h"
@@ -25,6 +24,7 @@
#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.c.generated.h"
diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c
index 50ef761066..226b042471 100644
--- a/src/nvim/arabic.c
+++ b/src/nvim/arabic.c
@@ -19,14 +19,11 @@
#include <stdbool.h>
#include <stddef.h>
-#include <stdint.h>
#include "nvim/arabic.h"
#include "nvim/ascii.h"
#include "nvim/macros.h"
-#include "nvim/mbyte.h"
#include "nvim/option_vars.h"
-#include "nvim/vim.h"
// Unicode values for Arabic characters.
enum {
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 80573696d4..65e902ed4b 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -48,6 +48,7 @@
#include "nvim/ui_compositor.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "auevents_name_map.generated.h"
diff --git a/src/nvim/base64.c b/src/nvim/base64.c
index c647019fb1..5bc0c34f06 100644
--- a/src/nvim/base64.c
+++ b/src/nvim/base64.c
@@ -1,7 +1,9 @@
#include <assert.h>
#include <stddef.h>
+#include <stdint.h>
#include <string.h>
+#include "auto/config.h"
#include "nvim/base64.h"
#include "nvim/memory.h"
@@ -9,6 +11,10 @@
# include ENDIAN_INCLUDE_FILE
#endif
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "base64.c.generated.h" // IWYU pragma: export
+#endif
+
static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Indices are 1-based because we use 0 to indicate a letter that is not part of the alphabet
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 8c522a6c44..deec0662a1 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -84,6 +84,7 @@
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/pos.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
@@ -101,6 +102,7 @@
#include "nvim/version.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.c.generated.h"
@@ -4154,6 +4156,10 @@ bool buf_contents_changed(buf_T *buf)
aco_save_T aco;
aucmd_prepbuf(&aco, newbuf);
+ // We don't want to trigger autocommands now, they may have nasty
+ // side-effects like wiping buffers
+ block_autocmds();
+
if (ml_open(curbuf) == OK
&& readfile(buf->b_ffname, buf->b_fname,
0, 0, (linenr_T)MAXLNUM,
@@ -4178,6 +4184,8 @@ bool buf_contents_changed(buf_T *buf)
wipe_buffer(newbuf, false);
}
+ unblock_autocmds();
+
return differ;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 73fad15239..039484d2e2 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -264,9 +264,6 @@ struct wininfo_S {
#define SYNFLD_START 0 // use level of item at start of line
#define SYNFLD_MINIMUM 1 // use lowest local minimum level on line
-// avoid #ifdefs for when b_spell is not available
-#define B_SPELL(buf) ((buf)->b_spell)
-
typedef struct qf_info_S qf_info_T;
// Used for :syntime: timing of executing a syntax pattern.
@@ -695,8 +692,7 @@ struct file_buffer {
bool b_help; // true for help file buffer (when set b_p_bt
// is "help")
bool b_spell; // True for a spell file buffer, most fields
- // are not used! Use the B_SPELL macro to
- // access b_spell without #ifdef.
+ // are not used!
char *b_prompt_text; // set by prompt_setprompt()
Callback b_prompt_callback; // set by prompt_setcallback()
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 46c21da384..aa58779f5b 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -262,9 +262,9 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
&& (last < wp->w_topline
|| (wp->w_topline >= lnum
&& wp->w_topline < lnume
- && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), (colnr_T)MAXCOL)
- <= (unsigned)(wp->w_skipcol + sms_marker_overlap(wp, win_col_off(wp)
- - win_col_off2(wp)))))) {
+ && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), MAXCOL)
+ <= (wp->w_skipcol
+ + sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp)))))) {
wp->w_skipcol = 0;
}
@@ -665,7 +665,7 @@ void ins_bytes_len(char *p, size_t len)
/// convert bytes to a character.
void ins_char(int c)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
size_t n = (size_t)utf_char2bytes(c, buf);
// When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
@@ -869,12 +869,9 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// If 'delcombine' is set and deleting (less than) one character, only
// delete the last combining character.
- if (p_deco && use_delcombine
- && utfc_ptr2len(oldp + col) >= count) {
- int cc[MAX_MCO];
-
- (void)utfc_ptr2char(oldp + col, cc);
- if (cc[0] != NUL) {
+ if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) {
+ char *p0 = oldp + col;
+ if (utf_composinglike(p0, p0 + utf_ptr2len(p0))) {
// Find the last composing char, there can be several.
int n = col;
do {
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 0adcc09ec7..5dfc9c444d 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -302,15 +302,13 @@ size_t transstr_len(const char *const s, bool untab)
while (*p) {
const size_t l = (size_t)utfc_ptr2len(p);
if (l > 1) {
- int pcc[MAX_MCO + 1];
- pcc[0] = utfc_ptr2char(p, &pcc[1]);
-
- if (vim_isprintc(pcc[0])) {
+ if (vim_isprintc(utf_ptr2char(p))) {
len += l;
} else {
- for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ for (size_t off = 0; off < l; off += (size_t)utf_ptr2len(p + off)) {
+ int c = utf_ptr2char(p + off);
char hexbuf[9];
- len += transchar_hex(hexbuf, pcc[i]);
+ len += transchar_hex(hexbuf, c);
}
}
p += l;
@@ -349,16 +347,15 @@ size_t transstr_buf(const char *const s, const ssize_t slen, char *const buf, co
if (buf_p + l > buf_e) {
break; // Exceeded `buf` size.
}
- int pcc[MAX_MCO + 1];
- pcc[0] = utfc_ptr2char(p, &pcc[1]);
- if (vim_isprintc(pcc[0])) {
+ if (vim_isprintc(utf_ptr2char(p))) {
memmove(buf_p, p, l);
buf_p += l;
} else {
- for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ for (size_t off = 0; off < l; off += (size_t)utf_ptr2len(p + off)) {
+ int c = utf_ptr2char(p + off);
char hexbuf[9]; // <up to 6 bytes>NUL
- const size_t hexlen = transchar_hex(hexbuf, pcc[i]);
+ const size_t hexlen = transchar_hex(hexbuf, c);
if (buf_p + hexlen > buf_e) {
break;
}
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index cba06976c9..487a3ec482 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -7,7 +7,6 @@
#include <stdlib.h>
#include <string.h>
-#include "auto/config.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
@@ -50,7 +49,6 @@
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
index e2916d1641..1f1d7d2eab 100644
--- a/src/nvim/cmdhist.c
+++ b/src/nvim/cmdhist.c
@@ -21,7 +21,6 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_vars.h"
-#include "nvim/pos.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
#include "nvim/types.h"
@@ -31,8 +30,6 @@
# include "cmdhist.c.generated.h"
#endif
-static const char e_val_too_large[] = N_("E1510: Value too large: %s");
-
static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; ///< lastused entry
/// identifying (unique) number of newest history entry
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 3114fc8ab5..857f26af37 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -16,11 +16,9 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
-#include "nvim/gettext.h"
#include "nvim/hashtab.h"
#include "nvim/keycodes.h"
#include "nvim/memory.h"
-#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/shada.h"
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 4e8457eb2d..32ee1d6c08 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -111,7 +111,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
col = wcol;
if ((addspaces || finetune) && !VIsual_active) {
- curwin->w_curswant = (int)linetabsize(curwin, pos->lnum) + one_more;
+ curwin->w_curswant = linetabsize(curwin, pos->lnum) + one_more;
if (curwin->w_curswant > 0) {
curwin->w_curswant--;
}
@@ -125,7 +125,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
&& curwin->w_width_inner != 0
&& wcol >= (colnr_T)width
&& width > 0) {
- csize = (int)linetabsize(curwin, pos->lnum);
+ csize = linetabsize(curwin, pos->lnum);
if (csize > 0) {
csize--;
}
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index bc0ce99c5e..1bff78f90a 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -1654,7 +1654,7 @@ static void registerdigraph(int char1, int char2, int n)
bool check_digraph_chars_valid(int char1, int char2)
{
if (char2 == 0) {
- char msg[MB_MAXBYTES + 1];
+ char msg[MB_MAXCHAR + 1];
msg[utf_char2bytes(char1, msg)] = NUL;
semsg(_(e_digraph_must_be_just_two_characters_str), msg);
return false;
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index d08154e24e..172c72145b 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -228,14 +228,12 @@ static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells
const char *p = *pp;
int cells = utf_ptr2cells(p);
int c_len = utfc_ptr2len(p);
- int u8c, u8cc[MAX_MCO];
assert(maxcells > 0);
if (cells > maxcells) {
dest[0] = schar_from_ascii(' ');
return 1;
}
- u8c = utfc_ptr2char(p, u8cc);
if (*p == TAB) {
cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
}
@@ -247,16 +245,14 @@ static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells
for (int c = 0; c < cells; c++) {
dest[c] = schar_from_ascii(' ');
}
- goto done;
- } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) {
- dest[0] = schar_from_ascii(*p);
} else {
- dest[0] = schar_from_cc(u8c, u8cc);
- }
- if (cells > 1) {
- dest[1] = 0;
+ int u8c;
+ dest[0] = utfc_ptr2schar(p, &u8c);
+ if (cells > 1) {
+ dest[1] = 0;
+ }
}
-done:
+
*pp += c_len;
return cells;
}
@@ -897,16 +893,6 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t
}
}
-static bool check_mb_utf8(int *c, int *u8cc)
-{
- if (utf_char2len(*c) > 1) {
- *u8cc = 0;
- *c = 0xc0;
- return true;
- }
- return false;
-}
-
static colnr_T get_trailcol(win_T *wp, const char *ptr, const char *line)
{
colnr_T trailcol = MAXCOL;
@@ -1002,7 +988,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
{
winlinevars_T wlv; // variables passed between functions
- int c = 0; // init for GCC
colnr_T vcol_prev = -1; // "wlv.vcol" of previous character
char *line; // current line
char *ptr; // current position in "line"
@@ -1047,8 +1032,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
int multi_attr = 0; // attributes desired by multibyte
int mb_l = 1; // multi-byte byte length
int mb_c = 0; // decoded multi-byte character
- bool mb_utf8 = false; // screen char is UTF-8 char
- int u8cc[MAX_MCO]; // composing UTF-8 chars
+ schar_T mb_schar; // complete screen char
int change_start = MAXCOL; // first col of changed area
int change_end = -1; // last col of changed area
bool in_multispace = false; // in multiple consecutive spaces
@@ -1898,34 +1882,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// For the '$' of the 'list' option, n_extra == 1, p_extra == "".
if (wlv.n_extra > 0) {
if (wlv.c_extra != NUL || (wlv.n_extra == 1 && wlv.c_final != NUL)) {
- c = (wlv.n_extra == 1 && wlv.c_final != NUL) ? wlv.c_final : wlv.c_extra;
- mb_c = c; // doesn't handle non-utf-8 multi-byte!
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ mb_c = (wlv.n_extra == 1 && wlv.c_final != NUL) ? wlv.c_final : wlv.c_extra;
+ mb_schar = schar_from_char(mb_c);
+ wlv.n_extra--;
} else {
assert(wlv.p_extra != NULL);
- c = (uint8_t)(*wlv.p_extra);
- mb_c = c;
- // If the UTF-8 character is more than one byte:
- // Decode it into "mb_c".
mb_l = utfc_ptr2len(wlv.p_extra);
- mb_utf8 = false;
- if (mb_l > wlv.n_extra) {
- mb_l = 1;
- } else if (mb_l > 1) {
- mb_c = utfc_ptr2char(wlv.p_extra, u8cc);
- mb_utf8 = true;
- c = 0xc0;
- }
- if (mb_l == 0) { // at the NUL at end-of-line
+ mb_schar = utfc_ptr2schar(wlv.p_extra, &mb_c);
+ // mb_l=0 at the end-of-line NUL
+ if (mb_l > wlv.n_extra || mb_l == 0) {
mb_l = 1;
}
// If a double-width char doesn't fit display a '>' in the last column.
+ // Don't advance the pointer but put the character at the start of the next line.
if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
+ mb_c = '>';
mb_l = 1;
(void)mb_l;
+ mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
if (wlv.cul_attr) {
@@ -1933,18 +1908,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
? hl_combine_attr(wlv.cul_attr, multi_attr)
: hl_combine_attr(multi_attr, wlv.cul_attr);
}
-
- // put the pointer back to output the double-width
- // character at the start of the next line.
- wlv.n_extra++;
- wlv.p_extra--;
} else {
- wlv.n_extra -= mb_l - 1;
- wlv.p_extra += mb_l - 1;
+ wlv.n_extra -= mb_l;
+ wlv.p_extra += mb_l;
}
- wlv.p_extra++;
}
- wlv.n_extra--;
// Only restore search_attr and area_attr after "n_extra" in
// the next screen line is also done.
@@ -1973,58 +1941,40 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
}
} else if (has_fold) {
// skip writing the buffer line itself
- c = NUL;
+ mb_c = NUL;
} else {
- int c0;
char *prev_ptr = ptr;
- // Get a character from the line itself.
- c0 = c = (uint8_t)(*ptr);
- mb_c = c;
-
- if (c == NUL) {
+ // first byte of next char
+ int c0 = (uint8_t)(*ptr);
+ if (c0 == NUL) {
// no more cells to skip
wlv.skip_cells = 0;
}
- // If the UTF-8 character is more than one byte: Decode it
- // into "mb_c".
+ // Get a character from the line itself.
mb_l = utfc_ptr2len(ptr);
- mb_utf8 = false;
- if (mb_l > 1) {
- mb_c = utfc_ptr2char(ptr, u8cc);
- // Overlong encoded ASCII or ASCII with composing char
- // is displayed normally, except a NUL.
- if (mb_c < 0x80) {
- c0 = c = mb_c;
- }
- mb_utf8 = true;
+ mb_schar = utfc_ptr2schar(ptr, &mb_c);
- // At start of the line we can have a composing char.
- // Draw it as a space with a composing char.
- if (utf_iscomposing(mb_c)) {
- for (int i = MAX_MCO - 1; i > 0; i--) {
- u8cc[i] = u8cc[i - 1];
- }
- u8cc[0] = mb_c;
- mb_c = ' ';
- }
+ // Overlong encoded ASCII or ASCII with composing char
+ // is displayed normally, except a NUL.
+ if (mb_l > 1 && mb_c < 0x80) {
+ c0 = mb_c;
}
- if ((mb_l == 1 && c >= 0x80)
+ if ((mb_l == 1 && c0 >= 0x80)
|| (mb_l >= 1 && mb_c == 0)
|| (mb_l > 1 && (!vim_isprintc(mb_c)))) {
// Illegal UTF-8 byte: display as <xx>.
- // Non-BMP character : display as ? or fullwidth ?.
+ // Non-printable character : display as ? or fullwidth ?.
transchar_hex(wlv.extra, mb_c);
if (wp->w_p_rl) { // reverse
rl_mirror_ascii(wlv.extra, NULL);
}
wlv.p_extra = wlv.extra;
- c = (uint8_t)(*wlv.p_extra);
mb_c = mb_ptr2char_adv((const char **)&wlv.p_extra);
- mb_utf8 = (c >= 0x80);
+ mb_schar = schar_from_char(mb_c);
wlv.n_extra = (int)strlen(wlv.p_extra);
wlv.c_extra = NUL;
wlv.c_final = NUL;
@@ -2040,10 +1990,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// last column; the character is displayed at the start of the
// next line.
if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
- mb_utf8 = false;
+ mb_c = '>';
mb_l = 1;
+ mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
// Put pointer back so that the character will be
// displayed at the start of the next line.
@@ -2059,15 +2008,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.n_extra = 1;
wlv.c_extra = MB_FILLER_CHAR;
wlv.c_final = NUL;
- c = ' ';
+ mb_c = ' ';
+ mb_l = 1;
+ mb_schar = schar_from_ascii(mb_c);
if (area_attr == 0 && search_attr == 0) {
wlv.n_attr = wlv.n_extra + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
saved_attr2 = wlv.char_attr; // save current attr
}
- mb_c = c;
- mb_utf8 = false;
- mb_l = 1;
}
ptr++;
@@ -2106,11 +2054,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// no concealing past the end of the line, it interferes
// with line highlighting.
- if (c == NUL) {
- syntax_flags = 0;
- } else {
- syntax_flags = get_syntax_info(&syntax_seqnr);
- }
+ syntax_flags = (mb_c == 0) ? 0 : get_syntax_info(&syntax_seqnr);
}
if (has_decor && v > 0) {
@@ -2145,7 +2089,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
spell_attr = 0;
// do not calculate cap_col at the end of the line or when
// only white space is following
- if (c != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) {
+ if (mb_c != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) {
char *p;
hlf_T spell_hlf = HLF_COUNT;
v -= mb_l - 1;
@@ -2219,13 +2163,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
//
// So only allow to linebreak, once we have found chars not in
// 'breakat' in the line.
- if (wp->w_p_lbr && !wlv.need_lbr && c != NUL
+ if (wp->w_p_lbr && !wlv.need_lbr && mb_c != NUL
&& !vim_isbreak((uint8_t)(*ptr))) {
wlv.need_lbr = true;
}
// Found last space before word: check for line break.
- if (wp->w_p_lbr && c0 == c && wlv.need_lbr
- && vim_isbreak(c) && !vim_isbreak((uint8_t)(*ptr))) {
+ if (wp->w_p_lbr && c0 == mb_c && mb_c < 128 && wlv.need_lbr
+ && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) {
int mb_off = utf_head_off(line, ptr - 1);
char *p = ptr - (mb_off + 1);
chartabsize_T cts;
@@ -2236,33 +2180,33 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1;
clear_chartabsize_arg(&cts);
- if (on_last_col && c != TAB) {
+ if (on_last_col && mb_c != TAB) {
// Do not continue search/match highlighting over the
// line break, but for TABs the highlighting should
// include the complete width of the character
search_attr = 0;
}
- if (c == TAB && wlv.n_extra + wlv.col > grid->cols) {
+ if (mb_c == TAB && wlv.n_extra + wlv.col > grid->cols) {
wlv.n_extra = tabstop_padding(wlv.vcol, wp->w_buffer->b_p_ts,
wp->w_buffer->b_p_vts_array) - 1;
}
wlv.c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
wlv.c_final = NUL;
- if (ascii_iswhite(c)) {
- if (c == TAB) {
+ if (mb_c < 128 && ascii_iswhite(mb_c)) {
+ if (mb_c == TAB) {
// See "Tab alignment" below.
FIX_FOR_BOGUSCOLS;
}
if (!wp->w_p_list) {
- c = ' ';
+ mb_c = ' ';
+ mb_schar = schar_from_ascii(mb_c);
}
}
}
if (wp->w_p_list) {
- in_multispace = c == ' ' && (*ptr == ' '
- || (prev_ptr > line && prev_ptr[-1] == ' '));
+ in_multispace = mb_c == ' ' && (*ptr == ' ' || (prev_ptr > line && prev_ptr[-1] == ' '));
if (!in_multispace) {
multispace_pos = 0;
}
@@ -2272,61 +2216,56 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// But not when the character is followed by a composing
// character (use mb_l to check that).
if (wp->w_p_list
- && ((((c == 160 && mb_l == 1)
- || (mb_utf8
- && ((mb_c == 160 && mb_l == 2)
- || (mb_c == 0x202f && mb_l == 3))))
+ && ((((mb_c == 160 && mb_l == 2) || (mb_c == 0x202f && mb_l == 3))
&& wp->w_p_lcs_chars.nbsp)
- || (c == ' '
+ || (mb_c == ' '
&& mb_l == 1
&& (wp->w_p_lcs_chars.space
|| (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
&& ptr - line >= leadcol
&& ptr - line <= trailcol))) {
if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
- c = wp->w_p_lcs_chars.multispace[multispace_pos++];
+ mb_c = wp->w_p_lcs_chars.multispace[multispace_pos++];
if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else {
- c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
+ mb_c = (mb_c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
}
wlv.n_attr = 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ mb_schar = schar_from_char(mb_c);
}
- if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol)
- || (leadcol != 0 && ptr < line + leadcol))) {
+ if (mb_c == ' ' && mb_l == 1 && ((trailcol != MAXCOL && ptr > line + trailcol)
+ || (leadcol != 0 && ptr < line + leadcol))) {
if (leadcol != 0 && in_multispace && ptr < line + leadcol
&& wp->w_p_lcs_chars.leadmultispace != NULL) {
- c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
+ mb_c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
- c = wp->w_p_lcs_chars.trail;
+ mb_c = wp->w_p_lcs_chars.trail;
} else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
- c = wp->w_p_lcs_chars.lead;
+ mb_c = wp->w_p_lcs_chars.lead;
} else if (leadcol != 0 && wp->w_p_lcs_chars.space) {
- c = wp->w_p_lcs_chars.space;
+ mb_c = wp->w_p_lcs_chars.space;
}
wlv.n_attr = 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ mb_schar = schar_from_char(mb_c);
}
}
// Handling of non-printable characters.
- if (!vim_isprintc(c)) {
+ if (!vim_isprintc(mb_c)) {
// when getting a character from the file, we may have to
// turn it into something else on the way to putting it on the screen.
- if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
+ if (mb_c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
int tab_len = 0;
colnr_T vcol_adjusted = wlv.vcol; // removed showbreak length
char *const sbr = get_showbreak_value(wp);
@@ -2369,7 +2308,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
if (wlv.n_extra > 0) {
len += wlv.n_extra - tab_len;
}
- c = wp->w_p_lcs_chars.tab1;
+ mb_c = wp->w_p_lcs_chars.tab1;
p = get_extra_buf((size_t)len + 1);
memset(p, ' ', (size_t)len);
p[len] = NUL;
@@ -2417,11 +2356,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
}
}
- mb_utf8 = false; // don't draw as UTF-8
if (wp->w_p_list) {
- c = (wlv.n_extra == 0 && wp->w_p_lcs_chars.tab3)
- ? wp->w_p_lcs_chars.tab3
- : wp->w_p_lcs_chars.tab1;
+ mb_c = (wlv.n_extra == 0 && wp->w_p_lcs_chars.tab3)
+ ? wp->w_p_lcs_chars.tab3 : wp->w_p_lcs_chars.tab1;
if (wp->w_p_lbr && wlv.p_extra != NULL && *wlv.p_extra != NUL) {
wlv.c_extra = NUL; // using p_extra from above
} else {
@@ -2431,14 +2368,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.n_attr = tab_len + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
} else {
wlv.c_final = NUL;
wlv.c_extra = ' ';
- c = ' ';
+ mb_c = ' ';
}
- } else if (c == NUL
+ mb_schar = schar_from_char(mb_c);
+ } else if (mb_c == NUL
&& (wp->w_p_list
|| ((wlv.fromcol >= 0 || fromcol_prev >= 0)
&& wlv.tocol > wlv.vcol
@@ -2462,20 +2398,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.n_extra = 0;
}
if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
- c = wp->w_p_lcs_chars.eol;
+ mb_c = wp->w_p_lcs_chars.eol;
} else {
- c = ' ';
+ mb_c = ' ';
}
lcs_eol_one = -1;
ptr--; // put it back at the NUL
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
wlv.n_attr = 1;
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
- } else if (c != NUL) {
- wlv.p_extra = transchar_buf(wp->w_buffer, c);
+ mb_schar = schar_from_char(mb_c);
+ } else if (mb_c != NUL) {
+ wlv.p_extra = transchar_buf(wp->w_buffer, mb_c);
if (wlv.n_extra == 0) {
- wlv.n_extra = byte2cells(c) - 1;
+ wlv.n_extra = byte2cells(mb_c) - 1;
}
if ((dy_flags & DY_UHEX) && wp->w_p_rl) {
rl_mirror_ascii(wlv.p_extra, NULL); // reverse "<12>"
@@ -2485,7 +2420,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
if (wp->w_p_lbr) {
char *p;
- c = (uint8_t)(*wlv.p_extra);
+ mb_c = (uint8_t)(*wlv.p_extra);
p = get_extra_buf((size_t)wlv.n_extra + 1);
memset(p, ' ', (size_t)wlv.n_extra);
strncpy(p, // NOLINT(runtime/printf)
@@ -2494,20 +2429,21 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
p[wlv.n_extra] = NUL;
wlv.p_extra = p;
} else {
- wlv.n_extra = byte2cells(c) - 1;
- c = (uint8_t)(*wlv.p_extra++);
+ wlv.n_extra = byte2cells(mb_c) - 1;
+ mb_c = (uint8_t)(*wlv.p_extra++);
}
wlv.n_attr = wlv.n_extra + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_8);
saved_attr2 = wlv.char_attr; // save current attr
- mb_utf8 = false; // don't draw as UTF-8
+ mb_schar = schar_from_ascii(mb_c);
} else if (VIsual_active
&& (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
&& virtual_active()
&& wlv.tocol != MAXCOL
&& wlv.vcol < wlv.tocol
&& wlv.col < grid->cols) {
- c = ' ';
+ mb_c = ' ';
+ mb_schar = schar_from_char(mb_c);
ptr--; // put it back at the NUL
}
}
@@ -2527,18 +2463,18 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// First time at this concealed item: display one
// character.
if (has_match_conc && match_conc) {
- c = match_conc;
+ mb_c = match_conc;
} else if (decor_conceal && decor_state.conceal_char) {
- c = decor_state.conceal_char;
+ mb_c = decor_state.conceal_char;
if (decor_state.conceal_attr) {
wlv.char_attr = decor_state.conceal_attr;
}
} else if (syn_get_sub_char() != NUL) {
- c = syn_get_sub_char();
+ mb_c = syn_get_sub_char();
} else if (wp->w_p_lcs_chars.conceal != NUL) {
- c = wp->w_p_lcs_chars.conceal;
+ mb_c = wp->w_p_lcs_chars.conceal;
} else {
- c = ' ';
+ mb_c = ' ';
}
prev_syntax_id = syntax_seqnr;
@@ -2557,8 +2493,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
is_concealing = true;
wlv.skip_cells = 1;
}
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ mb_schar = schar_from_char(mb_c);
} else {
prev_syntax_id = 0;
is_concealing = false;
@@ -2601,8 +2536,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
&& (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) : wp->w_leftcol > 0)
&& wlv.filler_todo <= 0
&& wlv.draw_state > WL_STC
- && c != NUL) {
- c = wp->w_p_lcs_chars.prec;
+ && mb_c != NUL) {
+ mb_c = wp->w_p_lcs_chars.prec;
lcs_prec_todo = NUL;
if (utf_char2cells(mb_c) > 1) {
// Double-width character being overwritten by the "precedes"
@@ -2613,15 +2548,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.n_attr = 2;
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
}
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ mb_schar = schar_from_char(mb_c);
saved_attr3 = wlv.char_attr; // save current attr
wlv.char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
n_attr3 = 1;
}
// At end of the text line or just after the last character.
- if (c == NUL && eol_hl_off == 0) {
+ if (mb_c == NUL && eol_hl_off == 0) {
// flag to indicate whether prevcol equals startcol of search_hl or
// one of the matches
bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl,
@@ -2675,7 +2609,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
}
// At end of the text line.
- if (c == NUL) {
+ if (mb_c == NUL) {
// Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
if (wp->w_p_wrap) {
v = wlv.startrow == 0 ? wp->w_skipcol : 0;
@@ -2821,10 +2755,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
|| lcs_eol_one > 0
|| (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
|| has_more_inline_virt(&wlv, v)) {
- c = wp->w_p_lcs_chars.ext;
+ mb_c = wp->w_p_lcs_chars.ext;
wlv.char_attr = win_hl_attr(wp, HLF_AT);
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ mb_schar = schar_from_char(mb_c);
}
}
@@ -2870,11 +2803,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// Skip characters that are left of the screen for 'nowrap'.
if (wlv.draw_state < WL_LINE || wlv.skip_cells <= 0) {
// Store the character.
- if (mb_utf8) {
- linebuf_char[wlv.off] = schar_from_cc(mb_c, u8cc);
- } else {
- linebuf_char[wlv.off] = schar_from_ascii((char)c);
- }
+ linebuf_char[wlv.off] = mb_schar;
if (multi_attr) {
linebuf_attr[wlv.off] = multi_attr;
multi_attr = 0;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index ce547b55fe..eb5ea2c873 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1462,7 +1462,7 @@ void edit_putchar(int c, bool highlight)
pc_status = PC_STATUS_SET;
}
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
grid_line_puts(pc_col, buf, utf_char2bytes(c, buf), attr);
grid_line_flush();
}
@@ -2176,7 +2176,7 @@ void insertchar(int c, int flags, int second_indent)
int cc;
if ((cc = utf_char2len(c)) > 1) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
utf_char2bytes(c, buf);
buf[cc] = NUL;
@@ -3681,7 +3681,6 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
int cc;
int temp = 0; // init for GCC
bool did_backspace = false;
- int cpc[MAX_MCO]; // composing characters
bool call_fix_indent = false;
// can't delete anything in an empty file
@@ -3910,15 +3909,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (State & REPLACE_FLAG) {
replace_do_bs(-1);
} else {
- const int l_p_deco = p_deco;
- if (l_p_deco) {
- (void)utfc_ptr2char(get_cursor_pos_ptr(), cpc);
+ bool has_composing = false;
+ if (p_deco) {
+ char *p0 = get_cursor_pos_ptr();
+ has_composing = utf_composinglike(p0, p0 + utf_ptr2len(p0));
}
(void)del_char(false);
// If there are combining characters and 'delcombine' is set
- // move the cursor back. Don't back up before the base
- // character.
- if (l_p_deco && cpc[0] != NUL) {
+ // move the cursor back. Don't back up before the base character.
+ if (has_composing) {
inc_cursor();
}
if (revins_chars) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 6d2c276df4..c073f30547 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -2,7 +2,6 @@
#include <assert.h>
#include <ctype.h>
-#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@@ -7118,7 +7117,7 @@ dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE
/// Set v:char to character "c".
void set_vim_var_char(int c)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(c, buf)] = NUL;
set_vim_var_string(VV_CHAR, buf, -1);
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 90e51c5c12..d4bf52c619 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -2,6 +2,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index c6909245af..8ef208f291 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -5134,7 +5134,7 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- char buf[MB_MAXBYTES];
+ char buf[MB_MAXCHAR];
const int len = utf_char2bytes((int)num, buf);
rettv->v_type = VAR_STRING;
@@ -6891,7 +6891,7 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
- char buf[MB_MAXBYTES + 1];
+ char buf[MAX_SCHAR_SIZE + 1];
schar_get(buf, grid_getchar(grid, row, col, NULL));
c = utf_ptr2char(buf);
}
@@ -6907,24 +6907,22 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ScreenGrid *grid;
screenchar_adjust(&grid, &row, &col);
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
- tv_list_alloc_ret(rettv, 0);
return;
}
- char buf[MB_MAXBYTES + 1];
+ char buf[MAX_SCHAR_SIZE + 1];
schar_get(buf, grid_getchar(grid, row, col, NULL));
- int pcc[MAX_MCO];
- int c = utfc_ptr2char(buf, pcc);
- int composing_len = 0;
- while (composing_len < MAX_MCO && pcc[composing_len] != 0) {
- composing_len++;
- }
- tv_list_alloc_ret(rettv, composing_len + 1);
- tv_list_append_number(rettv->vval.v_list, c);
- for (int i = 0; i < composing_len; i++) {
- tv_list_append_number(rettv->vval.v_list, pcc[i]);
- }
+
+ // schar values are already processed chars which are always NUL-terminated.
+ // A single [0] is expected when char is NUL.
+ size_t i = 0;
+ do {
+ int c = utf_ptr2char(buf + i);
+ tv_list_append_number(rettv->vval.v_list, c);
+ i += (size_t)utf_ptr2len(buf + i);
+ } while (buf[i] != NUL);
}
/// "screencol()" function
@@ -6957,7 +6955,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
return;
}
- char buf[MB_MAXBYTES + 1];
+ char buf[MAX_SCHAR_SIZE + 1];
schar_get(buf, grid_getchar(grid, row, col, NULL));
rettv->vval.v_string = xstrdup(buf);
}
@@ -7413,8 +7411,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
char *const csearch = tv_dict_get_string(d, "char", false);
if (csearch != NULL) {
- int pcc[MAX_MCO];
- const int c = utfc_ptr2char(csearch, pcc);
+ int c = utf_ptr2char(csearch);
set_last_csearch(c, csearch, utfc_ptr2len(csearch));
}
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 8cc3903f7a..3fd33720c9 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -17,7 +17,6 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
@@ -34,6 +33,7 @@
#include "nvim/message.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/search.h"
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index c0607a4a34..bcc29dfeed 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -26,6 +26,7 @@
#include "nvim/types.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/window.c.generated.h"
@@ -635,7 +636,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (wp == NULL || targetwin == NULL || wp == targetwin
|| !win_valid(wp) || !win_valid(targetwin)
- || win_valid_floating(wp) || win_valid_floating(targetwin)) {
+ || win_float_valid(wp) || win_float_valid(targetwin)) {
emsg(_(e_invalwindow));
rettv->vval.v_number = -1;
return;
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 73dec2bcab..85fec65177 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -8,7 +8,6 @@
#include "nvim/event/process.h"
#include "nvim/event/stream.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/os/os.h"
#include "nvim/ui_client.h"
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 3d74fe7d6d..d61666e6d4 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -17,6 +17,7 @@ void loop_init(Loop *loop, void *data)
{
uv_loop_init(&loop->uv);
loop->recursive = 0;
+ loop->closing = false;
loop->uv.data = loop;
loop->children = kl_init(WatcherPtr);
loop->events = multiqueue_new_parent(loop_on_put, loop);
@@ -149,6 +150,7 @@ static void loop_walk_cb(uv_handle_t *handle, void *arg)
bool loop_close(Loop *loop, bool wait)
{
bool rv = true;
+ loop->closing = true;
uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 58216f7ec3..977ed8a1ee 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -40,6 +40,7 @@ typedef struct loop {
uv_async_t async;
uv_mutex_t mutex;
int recursive;
+ bool closing; ///< Set to true if loop_close() has been called
} Loop;
#define CREATE_EVENT(multiqueue, handler, argc, ...) \
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index a6646c3a7f..b69612337c 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -1,7 +1,6 @@
#include <assert.h>
#include <inttypes.h>
#include <signal.h>
-#include <stdlib.h>
#include <uv.h>
#include "klib/klist.h"
@@ -10,7 +9,6 @@
#include "nvim/event/process.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/os/process.h"
#include "nvim/os/pty_process.h"
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 62326de075..542fb707fd 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -12,7 +12,6 @@
#include "nvim/event/stream.h"
#include "nvim/gettext.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 49b5be23c8..17c1b0a072 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -7,7 +7,6 @@
#include "nvim/event/loop.h"
#include "nvim/event/stream.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/rbuffer.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 94f981dc2b..d92be6404b 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -73,6 +73,7 @@
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
@@ -130,17 +131,22 @@ static const char e_non_numeric_argument_to_z[]
/// ":ascii" and "ga" implementation
void do_ascii(exarg_T *eap)
{
- char *dig;
- int cc[MAX_MCO];
- int c = utfc_ptr2char(get_cursor_pos_ptr(), cc);
- if (c == NUL) {
+ char *data = get_cursor_pos_ptr();
+ size_t len = (size_t)utfc_ptr2len(data);
+
+ if (len == 0) {
msg("NUL", 0);
return;
}
- size_t iobuff_len = 0;
+ bool need_clear = true;
+ msg_sb_eol();
+ msg_start();
+
+ int c = utf_ptr2char(data);
+ size_t off = 0;
- int ci = 0;
+ // TODO(bfredl): merge this with the main loop
if (c < 0x80) {
if (c == NL) { // NUL is stored as NL.
c = NUL;
@@ -159,46 +165,29 @@ void do_ascii(exarg_T *eap)
char buf2[20];
buf2[0] = NUL;
- dig = get_digraph_for_char(cval);
+ char *dig = get_digraph_for_char(cval);
if (dig != NULL) {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"),
- transchar(c), buf1, buf2, cval, cval, cval, dig);
+ vim_snprintf(IObuff, sizeof(IObuff),
+ _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"),
+ transchar(c), buf1, buf2, cval, cval, cval, dig);
} else {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- _("<%s>%s%s %d, Hex %02x, Octal %03o"),
- transchar(c), buf1, buf2, cval, cval, cval);
- }
-
- c = cc[ci++];
- }
-
-#define SPACE_FOR_DESC (1 + 1 + 1 + MB_MAXBYTES + 16 + 4 + 3 + 3 + 1)
- // Space for description:
- // - 1 byte for separator (starting from second entry)
- // - 1 byte for "<"
- // - 1 byte for space to draw composing character on (optional, but really
- // mostly required)
- // - up to MB_MAXBYTES bytes for character itself
- // - 16 bytes for raw text ("> , Hex , Octal ").
- // - at least 4 bytes for hexadecimal representation
- // - at least 3 bytes for decimal representation
- // - at least 3 bytes for octal representation
- // - 1 byte for NUL
- //
- // Taking into account MAX_MCO and characters which need 8 bytes for
- // hexadecimal representation, but not taking translation into account:
- // resulting string will occupy less then 400 bytes (conservative estimate).
- //
- // Less then 1000 bytes if translation multiplies number of bytes needed for
- // raw text by 6, so it should always fit into 1025 bytes reserved for IObuff.
+ vim_snprintf(IObuff, sizeof(IObuff),
+ _("<%s>%s%s %d, Hex %02x, Octal %03o"),
+ transchar(c), buf1, buf2, cval, cval, cval);
+ }
+
+ msg_multiline(IObuff, 0, true, &need_clear);
+
+ off += (size_t)utf_ptr2len(data); // needed for overlong ascii?
+ }
// Repeat for combining characters, also handle multiby here.
- while (c >= 0x80 && iobuff_len < sizeof(IObuff) - SPACE_FOR_DESC) {
+ while (off < len) {
+ c = utf_ptr2char(data + off);
+
+ size_t iobuff_len = 0;
// This assumes every multi-byte char is printable...
- if (iobuff_len > 0) {
+ if (off > 0) {
IObuff[iobuff_len++] = ' ';
}
IObuff[iobuff_len++] = '<';
@@ -207,32 +196,30 @@ void do_ascii(exarg_T *eap)
}
iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len);
- dig = get_digraph_for_char(c);
+ char *dig = get_digraph_for_char(c);
if (dig != NULL) {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- (c < 0x10000
- ? _("> %d, Hex %04x, Oct %o, Digr %s")
- : _("> %d, Hex %08x, Oct %o, Digr %s")),
- c, c, c, dig);
+ vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
+ (c < 0x10000
+ ? _("> %d, Hex %04x, Oct %o, Digr %s")
+ : _("> %d, Hex %08x, Oct %o, Digr %s")),
+ c, c, c, dig);
} else {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- (c < 0x10000
- ? _("> %d, Hex %04x, Octal %o")
- : _("> %d, Hex %08x, Octal %o")),
- c, c, c);
- }
- if (ci == MAX_MCO) {
- break;
+ vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
+ (c < 0x10000
+ ? _("> %d, Hex %04x, Octal %o")
+ : _("> %d, Hex %08x, Octal %o")),
+ c, c, c);
}
- c = cc[ci++];
- }
- if (ci != MAX_MCO && c != 0) {
- xstrlcpy(IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len);
+
+ msg_multiline(IObuff, 0, true, &need_clear);
+
+ off += (size_t)utf_ptr2len(data + off); // needed for overlong ascii?
}
- msg(IObuff, 0);
+ if (need_clear) {
+ msg_clr_eos();
+ }
+ msg_end();
}
/// ":left", ":center" and ":right": align text.
@@ -3410,10 +3397,15 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
// check for a trailing count
cmd = skipwhite(cmd);
if (ascii_isdigit(*cmd)) {
- i = getdigits_int(&cmd, true, 0);
+ i = getdigits_int(&cmd, true, INT_MAX);
if (i <= 0 && !eap->skip && subflags.do_error) {
emsg(_(e_zerocount));
return 0;
+ } else if (i >= INT_MAX) {
+ char buf[20];
+ vim_snprintf(buf, sizeof(buf), "%d", i);
+ semsg(_(e_val_too_large), buf);
+ return 0;
}
eap->line1 = eap->line2;
eap->line2 += (linenr_T)i - 1;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 13043930b2..0ca6e8bedb 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -83,6 +83,7 @@
#include "nvim/usercmd.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
static const char e_ambiguous_use_of_user_defined_command[]
= N_("E464: Ambiguous use of user-defined command");
@@ -3551,7 +3552,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
if (i == '-') {
lnum -= n;
} else {
- if (n >= INT32_MAX - lnum) {
+ if (lnum >= 0 && n >= INT32_MAX - lnum) {
*errormsg = _(e_line_number_out_of_range);
goto error;
}
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 00abade4b0..4ec5be5157 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -23,7 +23,6 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_vars.h"
-#include "nvim/pos.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 69025d81c7..cae3a65825 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -38,7 +38,6 @@
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index dd32bdbea7..ff9fa55388 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -26,7 +26,6 @@
// code for redrawing the line with the deleted decoration.
#include <assert.h>
-#include <sys/types.h>
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 2e9fcf8e75..4144a7c8ac 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1022,11 +1022,13 @@ EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight
EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
-EXTERN char e_stray_closing_curly_str[]
+EXTERN const char e_stray_closing_curly_str[]
INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
-EXTERN char e_missing_close_curly_str[]
+EXTERN const char e_missing_close_curly_str[]
INIT(= N_("E1279: Missing '}': %s"));
+EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
+
EXTERN const char e_undobang_cannot_redo_or_move_branch[]
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index f21b7e3a90..6320abe4ea 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -68,21 +68,6 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off)
}
}
-/// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell.
-schar_T schar_from_cc(int c, int u8cc[MAX_MCO])
-{
- char buf[MAX_SCHAR_SIZE];
- int len = utf_char2bytes(c, buf);
- for (int i = 0; i < MAX_MCO; i++) {
- if (u8cc[i] == 0) {
- break;
- }
- len += utf_char2bytes(u8cc[i], buf + len);
- }
- buf[len] = 0;
- return schar_from_buf(buf, (size_t)len);
-}
-
schar_T schar_from_str(char *str)
{
if (str == NULL) {
@@ -243,22 +228,21 @@ void line_do_arabic_shape(schar_T *buf, int cols)
schar_get(scbuf, buf[i]);
char scbuf_new[MAX_SCHAR_SIZE];
- int len = utf_char2bytes(c0new, scbuf_new);
+ size_t len = (size_t)utf_char2bytes(c0new, scbuf_new);
if (c1new) {
- len += utf_char2bytes(c1new, scbuf_new + len);
+ len += (size_t)utf_char2bytes(c1new, scbuf_new + len);
}
int off = utf_char2len(c0) + (c1 ? utf_char2len(c1) : 0);
size_t rest = strlen(scbuf + off);
- if (rest + (size_t)off + 1 > MAX_SCHAR_SIZE) {
- // TODO(bfredl): this cannot happen just yet, as we only construct
- // schar_T values with up to MAX_MCO+1 composing codepoints. When code
- // is improved so that MAX_SCHAR_SIZE becomes the only/sharp limit,
- // we need be able to peel off a composing char which doesn't fit anymore.
- abort();
+ if (rest + len + 1 > MAX_SCHAR_SIZE) {
+ // Too bigly, discard one code-point.
+ // This should be enough as c0 cannot grow more than from 2 to 4 bytes
+ // (base arabic to extended arabic)
+ rest -= (size_t)utf_cp_head_off(scbuf + off, scbuf + off + rest - 1) + 1;
}
memcpy(scbuf_new + len, scbuf + off, rest);
- buf[i] = schar_from_buf(scbuf_new, (size_t)len + rest);
+ buf[i] = schar_from_buf(scbuf_new, len + rest);
next:
c0prev = c0;
@@ -289,9 +273,9 @@ static bool grid_invalid_row(ScreenGrid *grid, int row)
return grid->attrs[grid->line_offset[row]] < 0;
}
-/// Get a single character directly from grid.chars into "bytes", which must
-/// have a size of "MB_MAXBYTES + 1".
-/// If "attrp" is not NULL, return the character's attribute in "*attrp".
+/// Get a single character directly from grid.chars
+///
+/// @param[out] attrp set to the character's attribute (optional)
schar_T grid_getchar(ScreenGrid *grid, int row, int col, int *attrp)
{
grid_adjust(&grid, &row, &col);
@@ -385,42 +369,35 @@ int grid_line_puts(int col, const char *text, int textlen, int attr)
{
const char *ptr = text;
int len = textlen;
- int u8cc[MAX_MCO];
assert(grid_line_grid);
int start_col = col;
int max_col = grid_line_maxcol;
- while (col < max_col
- && (len < 0 || (int)(ptr - text) < len)
- && *ptr != NUL) {
+ while (col < max_col && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) {
// check if this is the first byte of a multibyte
int mbyte_blen = len > 0
? utfc_ptr2len_len(ptr, (int)((text + len) - ptr))
: utfc_ptr2len(ptr);
- int u8c = len >= 0
- ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr))
- : utfc_ptr2char(ptr, u8cc);
- int mbyte_cells = utf_char2cells(u8c);
+ int firstc;
+ schar_T schar = len >= 0
+ ? utfc_ptr2schar_len(ptr, (int)((text + len) - ptr), &firstc)
+ : utfc_ptr2schar(ptr, &firstc);
+ int mbyte_cells = utf_char2cells(firstc);
if (mbyte_cells > 2) {
mbyte_cells = 1;
- u8c = 0xFFFD;
- u8cc[0] = 0;
+
+ schar = schar_from_char(0xFFFD);
}
if (col + mbyte_cells > max_col) {
// Only 1 cell left, but character requires 2 cells:
// display a '>' in the last column to avoid wrapping. */
- u8c = '>';
- u8cc[0] = 0;
+ schar = schar_from_ascii('>');
mbyte_cells = 1;
}
- schar_T buf;
- // TODO(bfredl): why not just keep the original byte sequence.
- buf = schar_from_cc(u8c, u8cc);
-
// When at the start of the text and overwriting the right half of a
// two-cell character in the same grid, truncate that into a '>'.
if (ptr == text && col > grid_line_first && col < grid_line_last
@@ -428,7 +405,7 @@ int grid_line_puts(int col, const char *text, int textlen, int attr)
linebuf_char[col - 1] = schar_from_ascii('>');
}
- linebuf_char[col] = buf;
+ linebuf_char[col] = schar;
linebuf_attr[col] = attr;
linebuf_vcol[col] = -1;
if (mbyte_cells == 2) {
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 11e736fc0c..3cc2d788d3 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -7,8 +7,8 @@
#include "nvim/pos.h"
#include "nvim/types.h"
-#define MAX_MCO 6 // fixed value for 'maxcombine'
-// Includes final NUL. at least 4*(MAX_MCO+1)+1
+// Includes final NUL. MAX_MCO is no longer used, but at least 4*(MAX_MCO+1)+1=29
+// ensures we can fit all composed chars which did fit before.
#define MAX_SCHAR_SIZE 32
// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianness)
@@ -35,7 +35,7 @@ enum {
/// we can avoid sending bigger updates than necessary to the Ul layer.
///
/// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can
-/// contain up to MAX_MCO composing characters after the base character.
+/// contain composing characters as many as fits in MAX_SCHAR_SIZE-1 bytes
/// The composing characters are to be drawn on top of the original character.
/// The content after the NUL is not defined (so comparison must be done a
/// single cell at a time). Double-width characters are stored in the left cell,
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 1bf2379bd9..89cf374152 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -1,5 +1,4 @@
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 2f5eb49ce0..d6ade22fdb 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -180,6 +180,9 @@ int get_number(int colon, int *mouse_used)
ui_cursor_goto(msg_row, msg_col);
int c = safe_vgetc();
if (ascii_isdigit(c)) {
+ if (n > INT_MAX / 10) {
+ return 0;
+ }
n = n * 10 + c - '0';
msg_putchar(c);
typed++;
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index bd32c6e2dc..c2bec8b045 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -21,7 +21,6 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
@@ -1744,7 +1743,7 @@ void ins_compl_addleader(int c)
return;
}
if ((cc = utf_char2len(c)) > 1) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
utf_char2bytes(c, buf);
buf[cc] = NUL;
@@ -3326,24 +3325,10 @@ static void get_next_bufname_token(void)
{
FOR_ALL_BUFFERS(b) {
if (b->b_p_bl && b->b_sfname != NULL) {
- char *start = get_past_head(b->b_sfname);
- char *current = start;
- char *p = (char *)path_next_component(start);
- while (true) {
- int len = (int)(p - current) - (*p == NUL ? 0 : 1);
- // treat . as a separator, unless it is the first char in a filename
- char *dot = strchr(current, '.');
- if (dot && *p == NUL && *current != '.') {
- len = (int)(dot - current);
- p = dot + 1;
- }
- ins_compl_add(current, len, NULL, NULL, false, NULL, 0,
+ char *tail = path_tail(b->b_sfname);
+ if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) {
+ ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0,
p_ic ? CP_ICASE : 0, false);
- if (*p == NUL) {
- break;
- }
- current = p;
- p = (char *)path_next_component(p);
}
}
}
@@ -3436,7 +3421,7 @@ static int ins_compl_get_exp(pos_T *ini)
compl_started = true;
} else {
// Mark a buffer scanned when it has been scanned completely
- if (type == 0 || type == CTRL_X_PATH_PATTERNS) {
+ if (buf_valid(st.ins_buf) && (type == 0 || type == CTRL_X_PATH_PATTERNS)) {
assert(st.ins_buf);
st.ins_buf->b_scanned = true;
}
diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c
index 1524731fab..d835bd5dc1 100644
--- a/src/nvim/linematch.c
+++ b/src/nvim/linematch.c
@@ -8,6 +8,7 @@
#include "nvim/linematch.h"
#include "nvim/macros.h"
#include "nvim/memory.h"
+#include "nvim/pos.h"
#define LN_MAX_BUFS 8
#define LN_DECISION_MAX 255 // pow(2, LN_MAX_BUFS(8)) - 1 = 255
diff --git a/src/nvim/lua/base64.c b/src/nvim/lua/base64.c
index 3f246839d5..c1f43a37d7 100644
--- a/src/nvim/lua/base64.c
+++ b/src/nvim/lua/base64.c
@@ -1,11 +1,16 @@
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
+#include <stddef.h>
#include "nvim/base64.h"
#include "nvim/lua/base64.h"
#include "nvim/memory.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/base64.c.generated.h"
+#endif
+
static int nlua_base64_encode(lua_State *L)
{
if (lua_gettop(L) < 1) {
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index aed96a539a..12304b6f11 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -366,17 +366,17 @@ static void nlua_schedule_event(void **argv)
static int nlua_schedule(lua_State *const lstate)
FUNC_ATTR_NONNULL_ALL
{
- // If Nvim is exiting don't schedule tasks to run in the future. Any refs
- // allocated here will not be cleaned up otherwise
- if (exiting) {
- return 0;
- }
-
if (lua_type(lstate, 1) != LUA_TFUNCTION) {
lua_pushliteral(lstate, "vim.schedule: expected function");
return lua_error(lstate);
}
+ // If main_loop is closing don't schedule tasks to run in the future,
+ // otherwise any refs allocated here will not be cleaned up.
+ if (main_loop.closing) {
+ return 0;
+ }
+
LuaRef cb = nlua_ref_global(lstate, 1);
multiqueue_put(main_loop.events, nlua_schedule_event,
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 5072d14c0e..a200b0a32f 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -224,7 +224,7 @@ static int nlua_str_utf_start(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
if (offset < 0 || offset > (intptr_t)s1_len) {
return luaL_error(lstate, "index out of range");
}
- int head_offset = utf_cp_head_off(s1, s1 + offset - 1);
+ int head_offset = -utf_cp_head_off(s1, s1 + offset - 1);
lua_pushinteger(lstate, head_offset);
return 1;
}
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index f3f78b79f5..29e3bbefd0 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -13,6 +13,7 @@
#include "nvim/lua/xdiff.h"
#include "nvim/macros.h"
#include "nvim/memory.h"
+#include "nvim/pos.h"
#include "nvim/vim.h"
#include "xdiff/xdiff.h"
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 8728861145..3fc4b98c6c 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -65,7 +65,6 @@
#include "nvim/os/stdpaths_defs.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
@@ -696,7 +695,7 @@ void getout(int exitval)
for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) {
next_tp = tp->tp_next;
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
- if (wp->w_buffer == NULL) {
+ if (wp->w_buffer == NULL || !buf_valid(wp->w_buffer)) {
// Autocmd must have close the buffer already, skip.
continue;
}
diff --git a/src/nvim/match.c b/src/nvim/match.c
index 3420455e5f..0cd0426cff 100644
--- a/src/nvim/match.c
+++ b/src/nvim/match.c
@@ -939,7 +939,7 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->mit_id);
if (cur->mit_conceal_char) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(cur->mit_conceal_char, buf)] = NUL;
tv_dict_add_str(dict, S_LEN("conceal"), buf);
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 0d468889a4..3a13aeddb8 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -48,6 +48,7 @@
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
+#include "nvim/grid.h"
#include "nvim/grid_defs.h"
#include "nvim/iconv.h"
#include "nvim/keycodes.h"
@@ -722,80 +723,68 @@ bool utf_composinglike(const char *p1, const char *p2)
return arabic_combine(utf_ptr2char(p1), c2);
}
-/// Convert a UTF-8 string to a wide character
+/// Get the screen char at the beginning of a string
///
-/// Also gets up to #MAX_MCO composing characters.
+/// Caller is expected to check for things like unprintable chars etc
+/// If first char in string is a composing char, prepend a space to display it correctly.
///
-/// @param[out] pcc Location where to store composing characters. Must have
-/// space at least for #MAX_MCO + 1 elements.
+/// If "p" starts with an invalid sequence, zero is returned.
///
-/// @return leading character.
-int utfc_ptr2char(const char *p, int *pcc)
+/// @param[out] firstc (required) The first codepoint of the screen char,
+/// or the first byte of an invalid sequence
+///
+/// @return the char
+schar_T utfc_ptr2schar(const char *p, int *firstc)
+ FUNC_ATTR_NONNULL_ALL
{
- int i = 0;
-
int c = utf_ptr2char(p);
- int len = utf_ptr2len(p);
-
- // Only accept a composing char when the first char isn't illegal.
- if ((len > 1 || (uint8_t)(*p) < 0x80)
- && (uint8_t)p[len] >= 0x80
- && utf_composinglike(p, p + len)) {
- int cc = utf_ptr2char(p + len);
- while (true) {
- pcc[i++] = cc;
- if (i == MAX_MCO) {
- break;
- }
- len += utf_ptr2len(p + len);
- if ((uint8_t)p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char(p + len))) {
- break;
- }
- }
- }
+ *firstc = c; // NOT optional, you are gonna need it
+ bool first_compose = utf_iscomposing(c);
+ size_t maxlen = MAX_SCHAR_SIZE - 1 - first_compose;
+ size_t len = (size_t)utfc_ptr2len_len(p, (int)maxlen);
- if (i < MAX_MCO) { // last composing char must be 0
- pcc[i] = 0;
+ if (len == 1 && (uint8_t)(*p) >= 0x80) {
+ return 0; // invalid sequence
}
- return c;
+ return schar_from_buf_first(p, len, first_compose);
}
-// Convert a UTF-8 byte string to a wide character. Also get up to MAX_MCO
-// composing characters. Use no more than p[maxlen].
-//
-// @param [out] pcc: composing chars, last one is 0
-int utfc_ptr2char_len(const char *p, int *pcc, int maxlen)
+/// Get the screen char at the beginning of a string with length
+///
+/// Like utfc_ptr2schar but use no more than p[maxlen].
+schar_T utfc_ptr2schar_len(const char *p, int maxlen, int *firstc)
+ FUNC_ATTR_NONNULL_ALL
{
assert(maxlen > 0);
- int i = 0;
+ size_t len = (size_t)utf_ptr2len_len(p, maxlen);
+ if (len > (size_t)maxlen || (len == 1 && (uint8_t)(*p) >= 0x80) || len == 0) {
+ // invalid or truncated sequence
+ *firstc = (uint8_t)(*p);
+ return 0;
+ }
- int len = utf_ptr2len_len(p, maxlen);
- // Is it safe to use utf_ptr2char()?
- bool safe = len > 1 && len <= maxlen;
- int c = safe ? utf_ptr2char(p) : (uint8_t)(*p);
+ int c = utf_ptr2char(p);
+ *firstc = c;
+ bool first_compose = utf_iscomposing(c);
+ maxlen = MIN(maxlen, MAX_SCHAR_SIZE - 1 - first_compose);
+ len = (size_t)utfc_ptr2len_len(p, maxlen);
- // Only accept a composing char when the first char isn't illegal.
- if ((safe || c < 0x80) && len < maxlen && (uint8_t)p[len] >= 0x80) {
- for (; i < MAX_MCO; i++) {
- int len_cc = utf_ptr2len_len(p + len, maxlen - len);
- safe = len_cc > 1 && len_cc <= maxlen - len;
- if (!safe || (pcc[i] = utf_ptr2char(p + len)) < 0x80
- || !(i == 0 ? utf_composinglike(p, p + len) : utf_iscomposing(pcc[i]))) {
- break;
- }
- len += len_cc;
- }
- }
+ return schar_from_buf_first(p, len, first_compose);
+}
- if (i < MAX_MCO) {
- // last composing char must be 0
- pcc[i] = 0;
+/// Caller must ensure there is space for `first_compose`
+static schar_T schar_from_buf_first(const char *buf, size_t len, bool first_compose)
+{
+ if (first_compose) {
+ char cbuf[MAX_SCHAR_SIZE];
+ cbuf[0] = ' ';
+ memcpy(cbuf + 1, buf, len);
+ return schar_from_buf(cbuf, len + 1);
+ } else {
+ return schar_from_buf(buf, len);
}
-
- return c;
-#undef ISCOMPOSING
}
/// Get the length of a UTF-8 byte sequence representing a single codepoint
@@ -878,8 +867,7 @@ int utfc_ptr2len(const char *const p)
return 1;
}
- // Check for composing characters. We can handle only the first six, but
- // skip all of them (otherwise the cursor would get stuck).
+ // Check for composing characters.
int prevlen = 0;
while (true) {
if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) {
@@ -1815,12 +1803,12 @@ int utf_cp_tail_off(const char *base, const char *p_in)
/// Return the offset from "p" to the first byte of the codepoint it points
/// to. Can start anywhere in a stream of bytes.
/// Note: Unlike `utf_head_off`, this counts individual codepoints of composed characters
-/// separately and returns a negative offset.
+/// separately.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
-/// @return 0 if invalid sequence, else offset to previous codepoint
+/// @return 0 if invalid sequence, else number of bytes to previous codepoint
int utf_cp_head_off(const char *base, const char *p)
{
int i;
@@ -1830,17 +1818,20 @@ int utf_cp_head_off(const char *base, const char *p)
}
// Find the first character that is not 10xx.xxxx
- for (i = 0; p - i > base; i--) {
- if (((uint8_t)p[i] & 0xc0) != 0x80) {
+ for (i = 0; p - i >= base; i++) {
+ if (((uint8_t)p[-i] & 0xc0) != 0x80) {
break;
}
}
- // Find the last character that is 10xx.xxxx
- for (int j = 0; ((uint8_t)p[j + 1] & 0xc0) == 0x80; j++) {}
+ // Find the last character that is 10xx.xxxx (condition terminates on NUL)
+ int j = 1;
+ while (((uint8_t)p[j] & 0xc0) == 0x80) {
+ j++;
+ }
// Check for illegal sequence.
- if (utf8len_tab[(uint8_t)p[i]] == 1) {
+ if (utf8len_tab[(uint8_t)p[-i]] != j + i) {
return 0;
}
return i;
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index 1d1a9439ad..c177f14ce2 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -7,6 +7,7 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/func_attr.h"
+#include "nvim/grid_defs.h"
#include "nvim/mbyte_defs.h"
#include "nvim/os/os_defs.h"
#include "nvim/types.h"
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 087661799a..a77e6dc41d 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -334,7 +334,7 @@ int ml_open(buf_T *buf)
// Only works when there's a swapfile, otherwise it's done when the file
// is created.
mf_put(mfp, hp, true, false);
- if (!buf->b_help && !B_SPELL(buf)) {
+ if (!buf->b_help && !buf->b_spell) {
(void)mf_sync(mfp, 0);
}
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 4c7e42321d..732c9ca39d 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -657,7 +657,6 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/edit.h"
# include "nvim/ex_cmds.h"
# include "nvim/ex_docmd.h"
-# include "nvim/ex_getln.h"
# include "nvim/file_search.h"
# include "nvim/getchar.h"
# include "nvim/grid.h"
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 8be8581537..9e9aa1fcd6 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -139,7 +139,7 @@ static int msg_grid_pos_at_flush = 0;
static void ui_ext_msg_set_pos(int row, bool scrolled)
{
- char buf[MAX_MCO + 1];
+ char buf[MB_MAXCHAR + 1];
size_t size = (size_t)utf_char2bytes(curwin->w_p_fcs_chars.msgsep, buf);
buf[size] = '\0';
ui_call_msg_set_pos(msg_grid.handle, row, scrolled,
@@ -468,7 +468,7 @@ void trunc_string(const char *s, char *buf, int room_in, int buflen)
buf[e + 3 + len - 1] = NUL;
} else {
// can't fit in the "...", just truncate it
- buf[e - 1] = NUL;
+ buf[buflen - 1] = NUL;
}
}
@@ -1471,7 +1471,7 @@ void msg_putchar(int c)
void msg_putchar_attr(int c, int attr)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
if (IS_SPECIAL(c)) {
buf[0] = (char)K_SPECIAL;
@@ -1560,12 +1560,6 @@ int msg_outtrans_len(const char *msgstr, int len, int attr)
mode_displayed = false;
}
- // If the string starts with a composing character first draw a space on
- // which the composing char can be drawn.
- if (utf_iscomposing(utf_ptr2char(msgstr))) {
- msg_puts_attr(" ", attr);
- }
-
// Go over the string. Special characters are translated and printed.
// Normal characters are printed several at a time.
while (--len >= 0 && !got_int) {
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 9427719988..b10bdd8ffe 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -45,6 +45,7 @@
#include "nvim/types.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
typedef struct {
linenr_T lnum; // line number
@@ -64,8 +65,9 @@ int adjust_plines_for_skipcol(win_T *wp)
}
int width = wp->w_width_inner - win_col_off(wp);
- if (wp->w_skipcol >= width) {
- return (wp->w_skipcol - width) / (width + win_col_off2(wp)) + 1;
+ int w2 = width + win_col_off2(wp);
+ if (wp->w_skipcol >= width && w2 > 0) {
+ return (wp->w_skipcol - width) / w2 + 1;
}
return 0;
@@ -1236,8 +1238,8 @@ bool scrolldown(linenr_T line_count, int byfold)
curwin->w_topline = first;
} else {
if (do_sms) {
- int size = (int)win_linetabsize(curwin, curwin->w_topline,
- ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+ int size = win_linetabsize(curwin, curwin->w_topline,
+ ml_get(curwin->w_topline), MAXCOL);
if (size > width1) {
curwin->w_skipcol = width1;
size -= width1;
@@ -1333,7 +1335,7 @@ bool scrollup(linenr_T line_count, int byfold)
if (do_sms || (byfold && hasAnyFolding(curwin)) || win_may_fill(curwin)) {
int width1 = curwin->w_width_inner - curwin_col_off();
int width2 = width1 + curwin_col_off2();
- unsigned size = 0;
+ int size = 0;
const colnr_T prev_skipcol = curwin->w_skipcol;
if (do_sms) {
@@ -1358,7 +1360,7 @@ bool scrollup(linenr_T line_count, int byfold)
// the end of the line, then advance to the next line.
int add = curwin->w_skipcol > 0 ? width2 : width1;
curwin->w_skipcol += add;
- if ((unsigned)curwin->w_skipcol >= size) {
+ if (curwin->w_skipcol >= size) {
if (lnum == curbuf->b_ml.ml_line_count) {
// at the last screen line, can't scroll further
curwin->w_skipcol -= add;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index f0f3d35468..38fdff95d7 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2466,7 +2466,7 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
/// @return true if able to move cursor, false otherwise.
static bool nv_screengo(oparg_T *oap, int dir, int dist)
{
- int linelen = (int)linetabsize(curwin, curwin->w_cursor.lnum);
+ int linelen = linetabsize(curwin, curwin->w_cursor.lnum);
bool retval = true;
bool atend = false;
int col_off1; // margin offset for first screen line
@@ -2530,7 +2530,7 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist)
}
cursor_up_inner(curwin, 1);
- linelen = (int)linetabsize(curwin, curwin->w_cursor.lnum);
+ linelen = linetabsize(curwin, curwin->w_cursor.lnum);
if (linelen > width1) {
int w = (((linelen - width1 - 1) / width2) + 1) * width2;
assert(curwin->w_curswant <= INT_MAX - w);
@@ -2563,7 +2563,7 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist)
if (curwin->w_curswant >= width1) {
curwin->w_curswant -= width2;
}
- linelen = (int)linetabsize(curwin, curwin->w_cursor.lnum);
+ linelen = linetabsize(curwin, curwin->w_cursor.lnum);
}
}
}
@@ -2695,6 +2695,10 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg)
if (nchar == K_DEL || nchar == K_KDEL) {
n /= 10;
} else if (ascii_isdigit(nchar)) {
+ if (n > INT_MAX / 10) {
+ clearopbeep(cap->oap);
+ break;
+ }
n = n * 10 + (nchar - '0');
} else if (nchar == CAR) {
win_setheight(n);
@@ -5487,7 +5491,7 @@ static void nv_g_cmd(cmdarg_T *cap)
case 'M':
oap->motion_type = kMTCharWise;
oap->inclusive = false;
- i = (int)linetabsize(curwin, curwin->w_cursor.lnum);
+ i = linetabsize(curwin, curwin->w_cursor.lnum);
if (cap->count0 > 0 && cap->count0 <= 100) {
coladvance((colnr_T)(i * cap->count0 / 100));
} else {
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index f0c752a2b1..0193e43de7 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -556,6 +556,7 @@ EXTERN char *p_mp; ///< 'makeprg'
EXTERN char *p_mps; ///< 'matchpairs'
EXTERN OptInt p_mat; ///< 'matchtime'
EXTERN OptInt p_mco; ///< 'maxcombine'
+#define MAX_MCO 6 // fixed value for 'maxcombine'
EXTERN OptInt p_mfd; ///< 'maxfuncdepth'
EXTERN OptInt p_mmd; ///< 'maxmapdepth'
EXTERN OptInt p_mmp; ///< 'maxmempattern'
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index e363f02644..bee08940b4 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -3,7 +3,6 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
@@ -15,7 +14,6 @@
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
@@ -32,7 +30,6 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
@@ -41,13 +38,11 @@
#include "nvim/os/os.h"
#include "nvim/pos.h"
#include "nvim/regexp.h"
-#include "nvim/runtime.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/types.h"
-#include "nvim/ui.h"
#include "nvim/vim.h"
#include "nvim/window.h"
diff --git a/src/nvim/path.c b/src/nvim/path.c
index dc7e0d9645..1cd663bde4 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -29,7 +29,6 @@
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
#include "nvim/vim.h"
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index 3a168320e4..07c77a5d72 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -83,18 +83,18 @@ int linetabsize_col(int startcol, char *s)
/// @param len
///
/// @return Number of characters the string will take on the screen.
-unsigned win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
+int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
{
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
win_linetabsize_cts(&cts, len);
clear_chartabsize_arg(&cts);
- return (unsigned)cts.cts_vcol;
+ return cts.cts_vcol;
}
/// Return the number of cells line "lnum" of window "wp" will take on the
/// screen, taking into account the size of a tab and inline virtual text.
-unsigned linetabsize(win_T *wp, linenr_T lnum)
+int linetabsize(win_T *wp, linenr_T lnum)
{
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL);
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 19b34b52b4..2ddee313a3 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -262,10 +262,8 @@ static const char *e_current_location_list_was_changed =
#define IS_QF_LIST(qfl) ((qfl)->qfl_type == QFLT_QUICKFIX)
#define IS_LL_LIST(qfl) ((qfl)->qfl_type == QFLT_LOCATION)
-//
// Return location list for window 'wp'
// For location list window, return the referenced location list
-//
#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? (wp)->w_llist_ref : (wp)->w_llist)
// Macro to loop through all the items in a quickfix list
@@ -3863,13 +3861,11 @@ static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index)
static int is_qf_win(const win_T *win, const qf_info_T *qi)
FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- //
// A window displaying the quickfix buffer will have the w_llist_ref field
// set to NULL.
// A window displaying a location list buffer will have the w_llist_ref
// pointing to the location list.
- //
- if (bt_quickfix(win->w_buffer)) {
+ if (buf_valid(win->w_buffer) && bt_quickfix(win->w_buffer)) {
if ((IS_QF_STACK(qi) && win->w_llist_ref == NULL)
|| (IS_LL_STACK(qi) && win->w_llist_ref == qi)) {
return true;
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 8151d2e12a..dc7ff30513 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -7,9 +7,11 @@
// #define REGEXP_DEBUG
#include <assert.h>
+#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <string.h>
#include <sys/types.h>
@@ -22,6 +24,7 @@
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
+#include "nvim/grid_defs.h"
#include "nvim/keycodes.h"
#include "nvim/macros.h"
#include "nvim/mark.h"
@@ -1291,9 +1294,7 @@ static bool reg_match_visual(void)
rex.line = (uint8_t *)reg_getline(rex.lnum);
rex.input = rex.line + col;
- unsigned cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
- assert(cols_u <= MAXCOL);
- colnr_T cols = (colnr_T)cols_u;
+ colnr_T cols = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
if (cols < start || cols > end - (*p_sel == 'e')) {
return false;
}
@@ -1639,41 +1640,46 @@ static void do_lower(int *d, int c)
char *regtilde(char *source, int magic, bool preview)
{
char *newsub = source;
- char *tmpsub;
- char *p;
- int len;
- int prevlen;
- for (p = newsub; *p; p++) {
+ for (char *p = newsub; *p; p++) {
if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) {
if (reg_prev_sub != NULL) {
// length = len(newsub) - 1 + len(prev_sub) + 1
- prevlen = (int)strlen(reg_prev_sub);
- tmpsub = xmalloc(strlen(newsub) + (size_t)prevlen);
+ // Avoid making the text longer than MAXCOL, it will cause
+ // trouble at some point.
+ size_t prevsublen = strlen(reg_prev_sub);
+ size_t newsublen = strlen(newsub);
+ if (prevsublen > MAXCOL || newsublen > MAXCOL
+ || newsublen + prevsublen > MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
+
+ char *tmpsub = xmalloc(newsublen + prevsublen);
// copy prefix
- len = (int)(p - newsub); // not including ~
- memmove(tmpsub, newsub, (size_t)len);
+ size_t prefixlen = (size_t)(p - newsub); // not including ~
+ memmove(tmpsub, newsub, prefixlen);
// interpret tilde
- memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen);
+ memmove(tmpsub + prefixlen, reg_prev_sub, prevsublen);
// copy postfix
if (!magic) {
- p++; // back off backslash
+ p++; // back off backslash
}
- STRCPY(tmpsub + len + prevlen, p + 1);
+ STRCPY(tmpsub + prefixlen + prevsublen, p + 1);
- if (newsub != source) { // already allocated newsub
+ if (newsub != source) { // allocated newsub before
xfree(newsub);
}
newsub = tmpsub;
- p = newsub + len + prevlen;
+ p = newsub + prefixlen + prevsublen;
} else if (magic) {
- STRMOVE(p, p + 1); // remove '~'
+ STRMOVE(p, p + 1); // remove '~'
} else {
- STRMOVE(p, p + 2); // remove '\~'
+ STRMOVE(p, p + 2); // remove '\~'
}
p--;
} else {
- if (*p == '\\' && p[1]) { // skip escaped characters
+ if (*p == '\\' && p[1]) { // skip escaped characters
p++;
}
p += utfc_ptr2len(p) - 1;
@@ -6021,11 +6027,10 @@ static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out)
break;
case RE_VCOL:
- if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL
- ? curwin : rex.reg_win,
- rex.reg_firstlnum + rex.lnum,
- (char *)rex.line,
- (colnr_T)(rex.input - rex.line)) + 1,
+ if (!re_num_cmp((unsigned)win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win,
+ rex.reg_firstlnum + rex.lnum,
+ (char *)rex.line,
+ (colnr_T)(rex.input - rex.line)) + 1,
scan)) {
status = RA_NOMATCH;
}
@@ -14746,9 +14751,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
result = col > t->state->val * ts;
}
if (!result) {
- uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
+ int lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
assert(t->state->val >= 0);
- result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1);
+ result = nfa_re_num_cmp((uintmax_t)t->state->val, op, (uintmax_t)lts + 1);
}
if (result) {
add_here = true;
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
index 15a58a4434..dab278e383 100644
--- a/src/nvim/spellsuggest.c
+++ b/src/nvim/spellsuggest.c
@@ -3019,7 +3019,7 @@ static int soundfold_find(slang_T *slang, char *word)
static bool similar_chars(slang_T *slang, int c1, int c2)
{
int m1, m2;
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
hashitem_T *hi;
if (c1 >= 256) {
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index db1281a0b5..6b9361848a 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -4,22 +4,20 @@
#include <string.h>
#include <uv.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/charset.h"
#include "nvim/event/defs.h"
-#include "nvim/log.h"
#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/map.h"
#include "nvim/memory.h"
#include "nvim/option_vars.h"
#include "nvim/os/os.h"
+#include "nvim/strings.h"
#include "nvim/tui/input.h"
#include "nvim/tui/input_defs.h"
#include "nvim/tui/tui.h"
-#include "nvim/types.h"
#include "nvim/ui_client.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
@@ -124,7 +122,7 @@ void tinput_init(TermInput *input, Loop *loop)
input->loop = loop;
input->paste = 0;
input->in_fd = STDIN_FILENO;
- input->extkeys_type = kExtkeysNone;
+ input->key_encoding = kKeyEncodingLegacy;
input->ttimeout = (bool)p_ttimeout;
input->ttimeoutlen = p_ttm;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
@@ -446,40 +444,9 @@ static void tk_getkeys(TermInput *input, bool force)
} else if (key.type == TERMKEY_TYPE_MOUSE) {
forward_mouse_event(input, &key);
} else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) {
- // There is no specified limit on the number of parameters a CSI sequence can contain, so just
- // allocate enough space for a large upper bound
- long args[16];
- size_t nargs = 16;
- unsigned long cmd;
- if (termkey_interpret_csi(input->tk, &key, args, &nargs, &cmd) == TERMKEY_RES_KEY) {
- uint8_t intermediate = (cmd >> 16) & 0xFF;
- uint8_t initial = (cmd >> 8) & 0xFF;
- uint8_t command = cmd & 0xFF;
-
- // Currently unused
- (void)intermediate;
-
- if (input->waiting_for_csiu_response > 0) {
- if (initial == '?' && command == 'u') {
- // The first (and only) argument contains the current progressive
- // enhancement flags. Only enable CSI u mode if the first bit
- // (disambiguate escape codes) is not already set
- if (nargs > 0 && (args[0] & 0x1) == 0) {
- input->extkeys_type = kExtkeysCSIu;
- } else {
- input->extkeys_type = kExtkeysNone;
- }
- } else if (initial == '?' && command == 'c') {
- // Received Primary Device Attributes response
- input->waiting_for_csiu_response = 0;
- tui_enable_extkeys(input->tui_data);
- } else {
- input->waiting_for_csiu_response--;
- }
- }
- }
- } else if (key.type == TERMKEY_TYPE_OSC) {
- handle_osc_event(input, &key);
+ handle_unknown_csi(input, &key);
+ } else if (key.type == TERMKEY_TYPE_OSC || key.type == TERMKEY_TYPE_DCS) {
+ handle_term_response(input, &key);
} else if (key.type == TERMKEY_TYPE_MODEREPORT) {
handle_modereport(input, &key);
}
@@ -580,36 +547,105 @@ static HandleState handle_bracketed_paste(TermInput *input)
return kNotApplicable;
}
-static void handle_osc_event(TermInput *input, const TermKeyKey *key)
+/// Handle an OSC or DCS response sequence from the terminal.
+static void handle_term_response(TermInput *input, const TermKeyKey *key)
FUNC_ATTR_NONNULL_ALL
{
const char *str = NULL;
if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) {
assert(str != NULL);
- // Send an event to nvim core. This will update the v:termresponse variable and fire the
- // TermResponse event
+ // Send an event to nvim core. This will update the v:termresponse variable
+ // and fire the TermResponse event
MAXSIZE_TEMP_ARRAY(args, 2);
- ADD_C(args, STATIC_CSTR_AS_OBJ("osc_response"));
+ ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse"));
- // libtermkey strips the OSC bytes from the response. We add it back in so that downstream
- // consumers of v:termresponse can differentiate between OSC and CSI events.
+ // libtermkey strips the OSC/DCS bytes from the response. We add it back in
+ // so that downstream consumers of v:termresponse can differentiate between
+ // the two.
StringBuilder response = KV_INITIAL_VALUE;
- kv_printf(response, "\x1b]%s", str);
+ switch (key->type) {
+ case TERMKEY_TYPE_OSC:
+ kv_printf(response, "\x1b]%s", str);
+ break;
+ case TERMKEY_TYPE_DCS:
+ kv_printf(response, "\x1bP%s", str);
+ break;
+ default:
+ // Key type already checked for OSC/DCS in termkey_interpret_string
+ UNREACHABLE;
+ }
+
ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size)));
rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args);
kv_destroy(response);
}
}
+/// Handle a mode report (DECRPM) sequence from the terminal.
static void handle_modereport(TermInput *input, const TermKeyKey *key)
FUNC_ATTR_NONNULL_ALL
{
- // termkey_interpret_modereport incorrectly sign extends the mode so we parse the response
- // ourselves
- int mode = (uint8_t)key->code.mouse[1] << 8 | (uint8_t)key->code.mouse[2];
- TerminalModeState value = (uint8_t)key->code.mouse[3];
- tui_dec_report_mode(input->tui_data, (TerminalDecMode)mode, value);
+ int initial;
+ int mode;
+ int value;
+ if (termkey_interpret_modereport(input->tk, key, &initial, &mode, &value) == TERMKEY_RES_KEY) {
+ (void)initial; // Unused
+ tui_handle_term_mode(input->tui_data, (TermMode)mode, (TermModeState)value);
+ }
+}
+
+/// Handle a CSI sequence from the terminal that is unrecognized by libtermkey.
+static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // There is no specified limit on the number of parameters a CSI sequence can
+ // contain, so just allocate enough space for a large upper bound
+ long args[16];
+ size_t nargs = 16;
+ unsigned long cmd;
+ if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) {
+ return;
+ }
+
+ uint8_t intermediate = (cmd >> 16) & 0xFF;
+ uint8_t initial = (cmd >> 8) & 0xFF;
+ uint8_t command = cmd & 0xFF;
+
+ // Currently unused
+ (void)intermediate;
+
+ switch (command) {
+ case 'u':
+ switch (initial) {
+ case '?':
+ // Kitty keyboard protocol query response.
+ if (input->waiting_for_kkp_response) {
+ input->waiting_for_kkp_response = false;
+ input->key_encoding = kKeyEncodingKitty;
+ tui_set_key_encoding(input->tui_data);
+ }
+
+ break;
+ }
+ break;
+ case 'c':
+ switch (initial) {
+ case '?':
+ // Primary Device Attributes response
+ if (input->waiting_for_kkp_response) {
+ input->waiting_for_kkp_response = false;
+
+ // Enable the fallback key encoding (if any)
+ tui_set_key_encoding(input->tui_data);
+ }
+
+ break;
+ }
+ break;
+ default:
+ break;
+ }
}
static void handle_raw_buffer(TermInput *input, bool force)
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index 01514269be..2d72d1978c 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -14,18 +14,22 @@
#include "nvim/types.h"
typedef enum {
- kExtkeysNone,
- kExtkeysCSIu,
- kExtkeysXterm,
-} ExtkeysType;
+ kKeyEncodingLegacy, ///< Legacy key encoding
+ kKeyEncodingKitty, ///< Kitty keyboard protocol encoding
+ kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS)
+} KeyEncoding;
-typedef struct term_input {
+typedef struct {
int in_fd;
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
int8_t paste;
bool ttimeout;
- int8_t waiting_for_csiu_response;
- ExtkeysType extkeys_type;
+
+ bool waiting_for_kkp_response; ///< True if we are expecting to receive a response to a query for
+ ///< Kitty keyboard protocol support
+
+ KeyEncoding key_encoding; ///< The key encoding used by the terminal emulator
+
OptInt ttimeoutlen;
TermKey *tk;
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index aff7b100d8..93b891afff 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -34,6 +34,7 @@
#include "nvim/tui/input.h"
#include "nvim/tui/terminfo.h"
#include "nvim/tui/tui.h"
+#include "nvim/types.h"
#include "nvim/ugrid.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
@@ -75,9 +76,6 @@ struct TUIData {
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
size_t bufpos;
- char norm[CNORM_COMMAND_MAX_SIZE];
- char invis[CNORM_COMMAND_MAX_SIZE];
- size_t normlen, invislen;
TermInput input;
uv_loop_t write_loop;
unibi_term *ut;
@@ -136,8 +134,6 @@ struct TUIData {
int save_title, restore_title;
int set_underline_style;
int set_underline_color;
- int enable_extended_keys, disable_extended_keys;
- int get_extkeys;
int sync;
} unibi_ext;
char *space_buf;
@@ -185,44 +181,41 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term)
*term = tui->term;
}
-void tui_enable_extkeys(TUIData *tui)
+void tui_set_key_encoding(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
{
- TermInput input = tui->input;
- unibi_term *ut = tui->ut;
-
- switch (input.extkeys_type) {
- case kExtkeysCSIu:
- tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
- "\x1b[>1u");
- tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
- "\x1b[<1u");
+ switch (tui->input.key_encoding) {
+ case kKeyEncodingKitty:
+ out(tui, S_LEN("\x1b[>1u"));
break;
- case kExtkeysXterm:
- tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
- "\x1b[>4;2m");
- tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
- "\x1b[>4;0m");
+ case kKeyEncodingXterm:
+ out(tui, S_LEN("\x1b[>4;2m"));
break;
- default:
+ case kKeyEncodingLegacy:
break;
}
-
- unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys);
}
-static size_t unibi_pre_fmt_str(TUIData *tui, unsigned unibi_index, char *buf, size_t len)
+static void tui_reset_key_encoding(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
{
- const char *str = unibi_get_str(tui->ut, unibi_index);
- if (!str) {
- return 0U;
+ switch (tui->input.key_encoding) {
+ case kKeyEncodingKitty:
+ out(tui, S_LEN("\x1b[<1u"));
+ break;
+ case kKeyEncodingXterm:
+ out(tui, S_LEN("\x1b[>4;0m"));
+ break;
+ case kKeyEncodingLegacy:
+ break;
}
- return unibi_run(str, tui->params, buf, len);
}
-/// Request the terminal's DEC mode (DECRQM).
+/// Request the terminal's mode (DECRQM).
///
/// @see handle_modereport
-static void tui_dec_request_mode(TUIData *tui, TerminalDecMode mode)
+static void tui_request_term_mode(TUIData *tui, TermMode mode)
+ FUNC_ATTR_NONNULL_ALL
{
// 5 bytes for \x1b[?$p, 1 byte for null terminator, 6 bytes for mode digits (more than enough)
char buf[12];
@@ -231,22 +224,22 @@ static void tui_dec_request_mode(TUIData *tui, TerminalDecMode mode)
out(tui, buf, (size_t)len);
}
-/// Handle a DECRPM response from the terminal.
-void tui_dec_report_mode(TUIData *tui, TerminalDecMode mode, TerminalModeState state)
+/// Handle a mode report (DECRPM) from the terminal.
+void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
+ FUNC_ATTR_NONNULL_ALL
{
- assert(tui);
switch (state) {
- case kTerminalModeNotRecognized:
- case kTerminalModePermanentlySet:
- case kTerminalModePermanentlyReset:
+ case kTermModeNotRecognized:
+ case kTermModePermanentlySet:
+ case kTermModePermanentlyReset:
// If the mode is not recognized, or if the terminal emulator does not allow it to be changed,
// then there is nothing to do
break;
- case kTerminalModeSet:
- case kTerminalModeReset:
+ case kTermModeSet:
+ case kTermModeReset:
// The terminal supports changing the given mode
switch (mode) {
- case kDecModeSynchronizedOutput:
+ case kTermModeSynchronizedOutput:
// Ref: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync",
"\x1b[?2026%?%p1%{1}%-%tl%eh%;");
@@ -254,6 +247,21 @@ void tui_dec_report_mode(TUIData *tui, TerminalDecMode mode, TerminalModeState s
}
}
+/// Query the terminal emulator to see if it supports Kitty's keyboard protocol.
+///
+/// Write CSI ? u followed by a primary device attributes request (CSI c). If
+/// a primary device attributes response is received without first receiving an
+/// answer to the progressive enhancement query (CSI u), then the terminal does
+/// not support the Kitty keyboard protocol.
+///
+/// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
+static void tui_query_kitty_keyboard(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ tui->input.waiting_for_kkp_response = true;
+ out(tui, S_LEN("\x1b[?u\x1b[c"));
+}
+
static void terminfo_start(TUIData *tui)
{
tui->scroll_region_is_full_screen = true;
@@ -287,9 +295,6 @@ static void terminfo_start(TUIData *tui)
tui->unibi_ext.set_cursor_style = -1;
tui->unibi_ext.reset_cursor_style = -1;
tui->unibi_ext.set_underline_color = -1;
- tui->unibi_ext.enable_extended_keys = -1;
- tui->unibi_ext.disable_extended_keys = -1;
- tui->unibi_ext.get_extkeys = -1;
tui->unibi_ext.sync = -1;
tui->out_fd = STDOUT_FILENO;
tui->out_isatty = os_isatty(tui->out_fd);
@@ -352,10 +357,6 @@ static void terminfo_start(TUIData *tui)
|| terminfo_is_term_family(term, "win32con")
|| terminfo_is_term_family(term, "interix");
tui->bce = unibi_get_bool(tui->ut, unibi_back_color_erase);
- tui->normlen = unibi_pre_fmt_str(tui, unibi_cursor_normal,
- tui->norm, sizeof tui->norm);
- tui->invislen = unibi_pre_fmt_str(tui, unibi_cursor_invisible,
- tui->invis, sizeof tui->invis);
// Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(tui->ut, unibi_max_colors);
// Enter alternate screen, save title, and clear.
@@ -370,11 +371,10 @@ static void terminfo_start(TUIData *tui)
// Query support for mode 2026 (Synchronized Output). Some terminals also
// support an older DCS sequence for synchronized output, but we will only use
// mode 2026
- tui_dec_request_mode(tui, kDecModeSynchronizedOutput);
+ tui_request_term_mode(tui, kTermModeSynchronizedOutput);
- // Query the terminal to see if it supports CSI u
- tui->input.waiting_for_csiu_response = 5;
- unibi_out_ext(tui, tui->unibi_ext.get_extkeys);
+ // Query the terminal to see if it supports Kitty's keyboard protocol
+ tui_query_kitty_keyboard(tui);
int ret;
uv_loop_init(&tui->write_loop);
@@ -417,8 +417,10 @@ static void terminfo_stop(TUIData *tui)
// Reset cursor to normal before exiting alternate screen.
unibi_out(tui, unibi_cursor_normal);
unibi_out(tui, unibi_keypad_local);
- // Disable extended keys before exiting alternate screen.
- unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys);
+
+ // Reset the key encoding
+ tui_reset_key_encoding(tui);
+
// May restore old title before exiting alternate screen.
tui_set_title(tui, (String)STRING_INIT);
if (ui_client_exit_status == 0) {
@@ -1151,9 +1153,7 @@ void tui_set_mode(TUIData *tui, ModeShape mode)
HlAttrs aep = kv_A(tui->attrs, c.id);
tui->want_invisible = aep.hl_blend == 100;
- if (tui->want_invisible) {
- unibi_out(tui, unibi_cursor_invisible);
- } else if (aep.rgb_ae_attr & HL_INVERSE) {
+ if (!tui->want_invisible && aep.rgb_ae_attr & HL_INVERSE) {
// We interpret "inverse" as "default" (no termcode for "inverse"...).
// Hopefully the user's default cursor color is inverse.
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
@@ -1305,18 +1305,37 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege
invalidate(tui, 0, tui->grid.height, 0, tui->grid.width);
}
-/// Enable synchronized output. When enabled, the terminal emulator will preserve the last rendered
-/// state on subsequent re-renders. It will continue to process incoming events. When synchronized
-/// mode is disabled again the emulator renders using the most recent state. This avoids tearing
-/// when the terminal updates the screen faster than Nvim can redraw it.
-static void tui_sync_output(TUIData *tui, bool enable)
+/// Begin flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
+/// begin a synchronized update. Otherwise, hide the cursor to avoid cursor jumping.
+static void tui_flush_start(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
{
- if (!tui->sync_output) {
- return;
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(tui->params[0], 1);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+ } else if (!tui->is_invisible) {
+ unibi_out(tui, unibi_cursor_invisible);
+ tui->is_invisible = true;
}
+}
- UNIBI_SET_NUM_VAR(tui->params[0], enable ? 1 : 0);
- unibi_out_ext(tui, tui->unibi_ext.sync);
+/// Finish flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
+/// end a synchronized update. Otherwise, make the cursor visible again.
+static void tui_flush_end(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(tui->params[0], 0);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+ }
+ bool should_invisible = tui->busy || tui->want_invisible;
+ if (tui->is_invisible && !should_invisible) {
+ unibi_out(tui, unibi_cursor_normal);
+ tui->is_invisible = false;
+ } else if (!tui->is_invisible && should_invisible) {
+ unibi_out(tui, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
}
void tui_flush(TUIData *tui)
@@ -1335,7 +1354,7 @@ void tui_flush(TUIData *tui)
tui_busy_stop(tui); // avoid hidden cursor
}
- tui_sync_output(tui, true);
+ tui_flush_start(tui);
while (kv_size(tui->invalid_regions)) {
Rect r = kv_pop(tui->invalid_regions);
@@ -1364,7 +1383,7 @@ void tui_flush(TUIData *tui)
cursor_goto(tui, tui->row, tui->col);
- tui_sync_output(tui, false);
+ tui_flush_end(tui);
flush_buf(tui);
}
@@ -1946,12 +1965,6 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- // Query the terminal to see if it supports CSI u key encoding by writing CSI
- // ? u followed by a request for the primary device attributes (CSI c)
- // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
- tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys",
- "\x1b[?u\x1b[c");
-
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
// See http://fedoraproject.org/wiki/Features/256_Color_Terminals
@@ -2244,71 +2257,29 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in
}
if (!kitty && (vte_version == 0 || vte_version >= 5400)) {
- // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u
- tui->input.extkeys_type = kExtkeysXterm;
+ // Fallback to Xterm's modifyOtherKeys if terminal does not support the
+ // Kitty keyboard protocol
+ tui->input.key_encoding = kKeyEncodingXterm;
}
}
static void flush_buf(TUIData *tui)
{
uv_write_t req;
- uv_buf_t bufs[3];
- uv_buf_t *bufp = &bufs[0];
-
- // The content of the output for each condition is shown in the following
- // table. Therefore, if tui->bufpos == 0 and N/A or invis + norm, there is
- // no need to output it.
- //
- // | is_invisible | !is_invisible
- // ------+-----------------+--------------+---------------
- // busy | want_invisible | N/A | invis
- // | !want_invisible | N/A | invis
- // ------+-----------------+--------------+---------------
- // !busy | want_invisible | N/A | invis
- // | !want_invisible | norm | invis + norm
- // ------+-----------------+--------------+---------------
- //
- if (tui->bufpos <= 0
- && ((tui->is_invisible && tui->busy)
- || (tui->is_invisible && !tui->busy && tui->want_invisible)
- || (!tui->is_invisible && !tui->busy && !tui->want_invisible))) {
- return;
- }
-
- if (!tui->is_invisible) {
- // cursor is visible. Write a "cursor invisible" command before writing the
- // buffer.
- bufp->base = tui->invis;
- bufp->len = UV_BUF_LEN(tui->invislen);
- bufp++;
- tui->is_invisible = true;
- }
+ uv_buf_t buf;
- if (tui->bufpos > 0) {
- bufp->base = tui->buf;
- bufp->len = UV_BUF_LEN(tui->bufpos);
- bufp++;
+ if (tui->bufpos <= 0) {
+ return;
}
- if (!tui->busy) {
- assert(tui->is_invisible);
- // not busy and the cursor is invisible. Write a "cursor normal" command
- // after writing the buffer.
- if (!tui->want_invisible) {
- bufp->base = tui->norm;
- bufp->len = UV_BUF_LEN(tui->normlen);
- bufp++;
- tui->is_invisible = false;
- }
- }
+ buf.base = tui->buf;
+ buf.len = UV_BUF_LEN(tui->bufpos);
if (tui->screenshot) {
- for (size_t i = 0; i < (size_t)(bufp - bufs); i++) {
- fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot);
- }
+ fwrite(buf.base, buf.len, 1, tui->screenshot);
} else {
int ret
- = uv_write(&req, (uv_stream_t *)&tui->output_handle, bufs, (unsigned)(bufp - bufs), NULL);
+ = uv_write(&req, (uv_stream_t *)&tui->output_handle, &buf, 1, NULL);
if (ret) {
ELOG("uv_write failed: %s", uv_strerror(ret));
}
diff --git a/src/nvim/tui/tui.h b/src/nvim/tui/tui.h
index 29afdef4de..1cd283cf49 100644
--- a/src/nvim/tui/tui.h
+++ b/src/nvim/tui/tui.h
@@ -6,16 +6,16 @@
typedef struct TUIData TUIData;
typedef enum {
- kDecModeSynchronizedOutput = 2026,
-} TerminalDecMode;
+ kTermModeSynchronizedOutput = 2026,
+} TermMode;
typedef enum {
- kTerminalModeNotRecognized = 0,
- kTerminalModeSet = 1,
- kTerminalModeReset = 2,
- kTerminalModePermanentlySet = 3,
- kTerminalModePermanentlyReset = 4,
-} TerminalModeState;
+ kTermModeNotRecognized = 0,
+ kTermModeSet = 1,
+ kTermModeReset = 2,
+ kTermModePermanentlySet = 3,
+ kTermModePermanentlyReset = 4,
+} TermModeState;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/tui.h.generated.h"
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 3e5bfba315..a2791be583 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -35,6 +35,7 @@
#include "nvim/ui_compositor.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.c.generated.h"
diff --git a/src/nvim/window.c b/src/nvim/window.c
index a49dcd95bc..00524b2f56 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -56,7 +56,6 @@
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
-#include "nvim/optionstr.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
@@ -75,6 +74,7 @@
#include "nvim/undo.h"
#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.c.generated.h"
@@ -734,204 +734,6 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
RedrawingDisabled--;
}
-/// Create a new float.
-///
-/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
-/// It must then already belong to the current tabpage!
-/// @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)
-{
- if (wp == NULL) {
- wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
- win_init(wp, curwin, 0);
- } else {
- assert(!last);
- assert(!wp->w_floating);
- if (firstwin == wp && lastwin_nofloating() == wp) {
- // last non-float
- api_set_error(err, kErrorTypeException,
- "Cannot change last window into float");
- return NULL;
- } else if (!win_valid(wp)) {
- api_set_error(err, kErrorTypeException,
- "Cannot change window from different tabpage into float");
- return NULL;
- }
- int dir;
- winframe_remove(wp, &dir, NULL);
- XFREE_CLEAR(wp->w_frame);
- (void)win_comp_pos(); // recompute window positions
- win_remove(wp, NULL);
- win_append(lastwin_nofloating(), wp);
- }
- wp->w_floating = true;
- wp->w_status_height = 0;
- wp->w_winbar_height = 0;
- wp->w_hsep_height = 0;
- wp->w_vsep_width = 0;
-
- win_config_float(wp, fconfig);
- win_set_inner_size(wp, true);
- wp->w_pos_changed = true;
- redraw_later(wp, UPD_VALID);
- return wp;
-}
-
-void win_set_minimal_style(win_T *wp)
-{
- wp->w_p_nu = false;
- wp->w_p_rnu = false;
- wp->w_p_cul = false;
- wp->w_p_cuc = false;
- wp->w_p_spell = false;
- wp->w_p_list = false;
-
- // Hide EOB region: use " " fillchar and cleared highlighting
- if (wp->w_p_fcs_chars.eob != ' ') {
- char *old = wp->w_p_fcs;
- wp->w_p_fcs = ((*old == NUL)
- ? xstrdup("eob: ")
- : concat_str(old, ",eob: "));
- free_string_option(old);
- }
-
- // TODO(bfredl): this could use a highlight namespace directly,
- // and avoid peculiarities around window options
- char *old = wp->w_p_winhl;
- wp->w_p_winhl = ((*old == NUL)
- ? xstrdup("EndOfBuffer:")
- : concat_str(old, ",EndOfBuffer:"));
- free_string_option(old);
- parse_winhl_opt(wp);
-
- // signcolumn: use 'auto'
- if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
- free_string_option(wp->w_p_scl);
- wp->w_p_scl = xstrdup("auto");
- }
-
- // foldcolumn: use '0'
- if (wp->w_p_fdc[0] != '0') {
- free_string_option(wp->w_p_fdc);
- wp->w_p_fdc = xstrdup("0");
- }
-
- // colorcolumn: cleared
- if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
- free_string_option(wp->w_p_cc);
- wp->w_p_cc = xstrdup("");
- }
-
- // statuscolumn: cleared
- if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
- free_string_option(wp->w_p_stc);
- wp->w_p_stc = xstrdup("");
- }
-}
-
-void win_config_float(win_T *wp, FloatConfig fconfig)
-{
- wp->w_width = MAX(fconfig.width, 1);
- wp->w_height = MAX(fconfig.height, 1);
-
- if (fconfig.relative == kFloatRelativeCursor) {
- fconfig.relative = kFloatRelativeWindow;
- fconfig.row += curwin->w_wrow;
- fconfig.col += curwin->w_wcol;
- fconfig.window = curwin->handle;
- } else if (fconfig.relative == kFloatRelativeMouse) {
- int row = mouse_row, col = mouse_col, grid = mouse_grid;
- win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win != NULL) {
- fconfig.relative = kFloatRelativeWindow;
- fconfig.row += row;
- fconfig.col += col;
- fconfig.window = mouse_win->handle;
- }
- }
-
- bool change_external = fconfig.external != wp->w_float_config.external;
- bool change_border = (fconfig.border != wp->w_float_config.border
- || memcmp(fconfig.border_hl_ids,
- wp->w_float_config.border_hl_ids,
- sizeof fconfig.border_hl_ids) != 0);
-
- wp->w_float_config = fconfig;
-
- bool has_border = wp->w_floating && wp->w_float_config.border;
- for (int i = 0; i < 4; i++) {
- int new_adj = has_border && wp->w_float_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;
- }
- }
-
- if (!ui_has(kUIMultigrid)) {
- wp->w_height = MIN(wp->w_height, Rows - win_border_height(wp));
- wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
- }
-
- win_set_inner_size(wp, true);
- must_redraw = MAX(must_redraw, UPD_VALID);
-
- wp->w_pos_changed = true;
- if (change_external || change_border) {
- wp->w_hl_needs_update = true;
- redraw_later(wp, UPD_NOT_VALID);
- }
-
- // 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;
- Error dummy = ERROR_INIT;
- win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
- if (parent) {
- row += parent->w_winrow;
- col += parent->w_wincol;
- ScreenGrid *grid = &parent->w_grid;
- int row_off = 0, col_off = 0;
- 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 };
- int trow, tcol, tcolc, tcole;
- textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
- row += trow - 1;
- col += tcol - 1;
- }
- }
- api_clear_error(&dummy);
- wp->w_winrow = row;
- wp->w_wincol = col;
- } else {
- wp->w_winrow = (int)fconfig.row;
- wp->w_wincol = (int)fconfig.col;
- }
-
- // changing border style while keeping border only requires redrawing border
- if (fconfig.border) {
- wp->w_redr_border = true;
- redraw_later(wp, UPD_VALID);
- }
-}
-
-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) {
- wp->w_pos_changed = true;
- }
- }
-}
-
/// Return the number of fold columns to display
int win_fdccol_count(win_T *wp)
{
@@ -1659,7 +1461,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// The windows will both edit the same buffer.
// WSP_NEWLOC may be specified in flags to prevent the location list from
// being copied.
-static void win_init(win_T *newp, win_T *oldp, int flags)
+void win_init(win_T *newp, win_T *oldp, int flags)
{
newp->w_buffer = oldp->w_buffer;
newp->w_s = &(oldp->w_buffer->b_s);
@@ -1736,24 +1538,6 @@ static void win_init_some(win_T *newp, win_T *oldp)
win_copy_options(oldp, newp);
}
-/// Return true if "win" is floating window in the current tab page.
-///
-/// @param win window to check
-bool win_valid_floating(const win_T *win)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (win == NULL) {
- return false;
- }
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp == win) {
- return wp->w_floating;
- }
- }
- return false;
-}
-
/// Check if "win" is a pointer to an existing window in the current tabpage.
///
/// @param win window to check
@@ -2877,6 +2661,9 @@ int win_close(win_T *win, bool free_buf, bool force)
reset_VIsual_and_resel(); // stop Visual mode
other_buffer = true;
+ if (!win_valid(win)) {
+ return FAIL;
+ }
win->w_closing = true;
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
if (!win_valid(win)) {
@@ -3992,6 +3779,12 @@ void close_others(int message, int forceit)
continue;
}
+ // autoccommands messed this one up
+ if (!buf_valid(wp->w_buffer) && win_valid(wp)) {
+ wp->w_buffer = NULL;
+ win_close(wp, false, false);
+ continue;
+ }
// Check if it's allowed to abandon this window
int r = can_abandon(wp->w_buffer, forceit);
if (!win_valid(wp)) { // autocommands messed wp up
@@ -5112,7 +4905,7 @@ win_T *buf_jump_open_tab(buf_T *buf)
/// @param hidden allocate a window structure and link it in the window if
// false.
-static win_T *win_alloc(win_T *after, bool hidden)
+win_T *win_alloc(win_T *after, bool hidden)
{
static int last_win_id = LOWEST_WIN_ID - 1;
@@ -5790,13 +5583,6 @@ int win_comp_pos(void)
return row + global_stl_height();
}
-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);
- }
-}
-
// Update the position of the windows in frame "topfrp", using the width and
// height of the frames.
// "*row" and "*col" are the top-left position of the frame. They are updated
@@ -6754,16 +6540,6 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
wp->w_redr_status = true;
}
-static int win_border_height(win_T *wp)
-{
- return wp->w_border_adj[0] + wp->w_border_adj[2];
-}
-
-static int win_border_width(win_T *wp)
-{
- return wp->w_border_adj[1] + wp->w_border_adj[3];
-}
-
/// Set the width of a window.
void win_new_width(win_T *wp, int width)
{
@@ -7654,29 +7430,3 @@ win_T *lastwin_nofloating(void)
}
return res;
}
-
-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;
-}
-
-void win_float_remove(bool bang, int count)
-{
- kvec_t(win_T *) float_win_arr = KV_INITIAL_VALUE;
- for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
- kv_push(float_win_arr, wp);
- }
- qsort(float_win_arr.items, float_win_arr.size, sizeof(win_T *), float_zindex_cmp);
- for (size_t i = 0; i < float_win_arr.size; i++) {
- if (win_close(float_win_arr.items[i], false, false) == FAIL) {
- break;
- }
- if (!bang) {
- count--;
- if (count == 0) {
- break;
- }
- }
- }
- kv_destroy(float_win_arr);
-}
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
new file mode 100644
index 0000000000..7fff17e1c4
--- /dev/null
+++ b/src/nvim/winfloat.c
@@ -0,0 +1,288 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "klib/kvec.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/drawscreen.h"
+#include "nvim/globals.h"
+#include "nvim/grid.h"
+#include "nvim/macros.h"
+#include "nvim/memory.h"
+#include "nvim/mouse.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/optionstr.h"
+#include "nvim/pos.h"
+#include "nvim/strings.h"
+#include "nvim/ui.h"
+#include "nvim/vim.h"
+#include "nvim/window.h"
+#include "nvim/winfloat.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "winfloat.c.generated.h"
+#endif
+
+/// Create a new float.
+///
+/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
+/// It must then already belong to the current tabpage!
+/// @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)
+{
+ if (wp == NULL) {
+ wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
+ win_init(wp, curwin, 0);
+ } else {
+ assert(!last);
+ assert(!wp->w_floating);
+ if (firstwin == wp && lastwin_nofloating() == wp) {
+ // last non-float
+ api_set_error(err, kErrorTypeException,
+ "Cannot change last window into float");
+ return NULL;
+ } else if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException,
+ "Cannot change window from different tabpage into float");
+ return NULL;
+ }
+ int dir;
+ winframe_remove(wp, &dir, NULL);
+ XFREE_CLEAR(wp->w_frame);
+ (void)win_comp_pos(); // recompute window positions
+ win_remove(wp, NULL);
+ win_append(lastwin_nofloating(), wp);
+ }
+ wp->w_floating = true;
+ wp->w_status_height = 0;
+ wp->w_winbar_height = 0;
+ wp->w_hsep_height = 0;
+ wp->w_vsep_width = 0;
+
+ win_config_float(wp, fconfig);
+ win_set_inner_size(wp, true);
+ wp->w_pos_changed = true;
+ redraw_later(wp, UPD_VALID);
+ return wp;
+}
+
+void win_set_minimal_style(win_T *wp)
+{
+ wp->w_p_nu = false;
+ wp->w_p_rnu = false;
+ wp->w_p_cul = false;
+ wp->w_p_cuc = false;
+ wp->w_p_spell = false;
+ wp->w_p_list = false;
+
+ // Hide EOB region: use " " fillchar and cleared highlighting
+ if (wp->w_p_fcs_chars.eob != ' ') {
+ char *old = wp->w_p_fcs;
+ wp->w_p_fcs = ((*old == NUL)
+ ? xstrdup("eob: ")
+ : concat_str(old, ",eob: "));
+ free_string_option(old);
+ }
+
+ // TODO(bfredl): this could use a highlight namespace directly,
+ // and avoid peculiarities around window options
+ char *old = wp->w_p_winhl;
+ wp->w_p_winhl = ((*old == NUL)
+ ? xstrdup("EndOfBuffer:")
+ : concat_str(old, ",EndOfBuffer:"));
+ free_string_option(old);
+ parse_winhl_opt(wp);
+
+ // signcolumn: use 'auto'
+ if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
+ free_string_option(wp->w_p_scl);
+ wp->w_p_scl = xstrdup("auto");
+ }
+
+ // foldcolumn: use '0'
+ if (wp->w_p_fdc[0] != '0') {
+ free_string_option(wp->w_p_fdc);
+ wp->w_p_fdc = xstrdup("0");
+ }
+
+ // colorcolumn: cleared
+ if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
+ free_string_option(wp->w_p_cc);
+ wp->w_p_cc = xstrdup("");
+ }
+
+ // statuscolumn: cleared
+ if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
+ free_string_option(wp->w_p_stc);
+ wp->w_p_stc = xstrdup("");
+ }
+}
+
+int win_border_height(win_T *wp)
+{
+ return wp->w_border_adj[0] + wp->w_border_adj[2];
+}
+
+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)
+{
+ wp->w_width = MAX(fconfig.width, 1);
+ wp->w_height = MAX(fconfig.height, 1);
+
+ if (fconfig.relative == kFloatRelativeCursor) {
+ fconfig.relative = kFloatRelativeWindow;
+ fconfig.row += curwin->w_wrow;
+ fconfig.col += curwin->w_wcol;
+ fconfig.window = curwin->handle;
+ } else if (fconfig.relative == kFloatRelativeMouse) {
+ int row = mouse_row, col = mouse_col, grid = mouse_grid;
+ win_T *mouse_win = mouse_find_win(&grid, &row, &col);
+ if (mouse_win != NULL) {
+ fconfig.relative = kFloatRelativeWindow;
+ fconfig.row += row;
+ fconfig.col += col;
+ fconfig.window = mouse_win->handle;
+ }
+ }
+
+ bool change_external = fconfig.external != wp->w_float_config.external;
+ bool change_border = (fconfig.border != wp->w_float_config.border
+ || memcmp(fconfig.border_hl_ids,
+ wp->w_float_config.border_hl_ids,
+ sizeof fconfig.border_hl_ids) != 0);
+
+ wp->w_float_config = fconfig;
+
+ bool has_border = wp->w_floating && wp->w_float_config.border;
+ for (int i = 0; i < 4; i++) {
+ int new_adj = has_border && wp->w_float_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;
+ }
+ }
+
+ if (!ui_has(kUIMultigrid)) {
+ wp->w_height = MIN(wp->w_height, Rows - win_border_height(wp));
+ wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
+ }
+
+ win_set_inner_size(wp, true);
+ must_redraw = MAX(must_redraw, UPD_VALID);
+
+ wp->w_pos_changed = true;
+ if (change_external || change_border) {
+ wp->w_hl_needs_update = true;
+ redraw_later(wp, UPD_NOT_VALID);
+ }
+
+ // 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;
+ Error dummy = ERROR_INIT;
+ win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
+ if (parent) {
+ row += parent->w_winrow;
+ col += parent->w_wincol;
+ ScreenGrid *grid = &parent->w_grid;
+ int row_off = 0, col_off = 0;
+ 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 };
+ int trow, tcol, tcolc, tcole;
+ textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
+ row += trow - 1;
+ col += tcol - 1;
+ }
+ }
+ api_clear_error(&dummy);
+ wp->w_winrow = row;
+ wp->w_wincol = col;
+ } else {
+ wp->w_winrow = (int)fconfig.row;
+ wp->w_wincol = (int)fconfig.col;
+ }
+
+ // changing border style while keeping border only requires redrawing border
+ if (fconfig.border) {
+ wp->w_redr_border = true;
+ redraw_later(wp, UPD_VALID);
+ }
+}
+
+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;
+}
+
+void win_float_remove(bool bang, int count)
+{
+ kvec_t(win_T *) float_win_arr = KV_INITIAL_VALUE;
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ kv_push(float_win_arr, wp);
+ }
+ qsort(float_win_arr.items, float_win_arr.size, sizeof(win_T *), float_zindex_cmp);
+ for (size_t i = 0; i < float_win_arr.size; i++) {
+ if (win_close(float_win_arr.items[i], false, false) == FAIL) {
+ break;
+ }
+ if (!bang) {
+ count--;
+ if (count == 0) {
+ break;
+ }
+ }
+ }
+ kv_destroy(float_win_arr);
+}
+
+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) {
+ wp->w_pos_changed = true;
+ }
+ }
+}
+
+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);
+ }
+}
+
+/// Return true if "win" is floating window in the current tab page.
+///
+/// @param win window to check
+bool win_float_valid(const win_T *win)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (win == NULL) {
+ return false;
+ }
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp == win) {
+ return wp->w_floating;
+ }
+ }
+ return false;
+}
diff --git a/src/nvim/winfloat.h b/src/nvim/winfloat.h
new file mode 100644
index 0000000000..c66f897ec0
--- /dev/null
+++ b/src/nvim/winfloat.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "nvim/api/private/defs.h"
+#include "nvim/buffer_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "winfloat.h.generated.h"
+#endif
diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
index 51f30543e3..cbaf401f06 100644
--- a/test/functional/editor/completion_spec.lua
+++ b/test/functional/editor/completion_spec.lua
@@ -1231,25 +1231,30 @@ describe('completion', function()
it('complete with f flag #25598', function()
screen:try_resize(20, 9)
- local bufname = 'foo/bar.txt'
- local hidden = 'fooA/.hidden'
- if helpers.is_os('win') then
- bufname = 'C:\\foo\\bar.txt'
- hidden = 'C:\\fooA\\.hidden'
- end
- command('set complete+=f | edit '..bufname..' | edit '..hidden..' | enew')
+ command('set complete+=f | edit foo | edit bar |edit foa |edit .hidden')
feed('i<C-n>')
-
screen:expect{grid=[[
foo^ |
{2:foo }{0: }|
{1:bar }{0: }|
- {1:txt }{0: }|
- {1:fooA }{0: }|
+ {1:foa }{0: }|
{1:.hidden }{0: }|
{0:~ }|
{0:~ }|
- {3:-- }{4:match 1 of 5} |
+ {0:~ }|
+ {3:-- }{4:match 1 of 4} |
+ ]]}
+ feed('<Esc>ccf<C-n>')
+ screen:expect{grid=[[
+ foo^ |
+ {2:foo }{0: }|
+ {1:foa }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- }{4:match 1 of 2} |
]]}
end)
end)
diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua
index a604e68822..7536506aa3 100644
--- a/test/functional/legacy/messages_spec.lua
+++ b/test/functional/legacy/messages_spec.lua
@@ -6,6 +6,7 @@ local exec = helpers.exec
local feed = helpers.feed
local meths = helpers.meths
local nvim_dir = helpers.nvim_dir
+local assert_alive = helpers.assert_alive
before_each(clear)
@@ -758,4 +759,9 @@ describe('messages', function()
]])
os.remove('b.txt')
end)
+
+ it('no crash when truncating overlong message', function()
+ pcall(command, 'source test/old/testdir/crash/vim_msg_trunc_poc')
+ assert_alive()
+ end)
end)
diff --git a/test/functional/legacy/scroll_opt_spec.lua b/test/functional/legacy/scroll_opt_spec.lua
index d4e4702f5e..8ac1141c2b 100644
--- a/test/functional/legacy/scroll_opt_spec.lua
+++ b/test/functional/legacy/scroll_opt_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local exec = helpers.exec
local feed = helpers.feed
+local assert_alive = helpers.assert_alive
before_each(clear)
@@ -1007,6 +1008,21 @@ describe('smoothscroll', function()
]])
end)
+ -- oldtest: Test_smoothscroll_crash()
+ it('does not crash with small window and cpo+=n', function()
+ screen:try_resize(40, 12)
+ exec([[
+ 20 new
+ vsp
+ put =repeat('aaaa', 20)
+ set nu fdc=1 smoothscroll cpo+=n
+ vert resize 0
+ exe "norm! 0\<c-e>"
+ ]])
+ feed('2<C-E>')
+ assert_alive()
+ end)
+
it("works with virt_lines above and below", function()
screen:try_resize(55, 7)
exec([=[
diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua
index 70337d1572..f0b3b44139 100644
--- a/test/functional/lua/snippet_spec.lua
+++ b/test/functional/lua/snippet_spec.lua
@@ -1,11 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
+local buf_lines = helpers.buf_lines
local clear = helpers.clear
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local feed = helpers.feed
local matches = helpers.matches
local pcall_err = helpers.pcall_err
+local sleep = helpers.sleep
describe('vim.snippet', function()
before_each(function()
@@ -22,7 +24,7 @@ describe('vim.snippet', function()
--- @param expected string[]
--- @param settings? string
--- @param prefix? string
- local function test_success(snippet, expected, settings, prefix)
+ local function test_expand_success(snippet, expected, settings, prefix)
if settings then
exec_lua(settings)
end
@@ -30,17 +32,17 @@ describe('vim.snippet', function()
feed('i' .. prefix)
end
exec_lua('vim.snippet.expand(...)', table.concat(snippet, '\n'))
- eq(expected, helpers.buf_lines(0))
+ eq(expected, buf_lines(0))
end
--- @param snippet string
--- @param err string
- local function test_fail(snippet, err)
+ local function test_expand_fail(snippet, err)
matches(err, pcall_err(exec_lua, string.format('vim.snippet.expand("%s")', snippet)))
end
it('adds base indentation to inserted text', function()
- test_success(
+ test_expand_success(
{ 'function $1($2)', ' $0', 'end' },
{ ' function ()', ' ', ' end' },
'',
@@ -49,11 +51,11 @@ describe('vim.snippet', function()
end)
it('adds indentation based on the start of snippet lines', function()
- test_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' })
+ test_expand_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' })
end)
it('replaces tabs with spaces when expandtab is set', function()
- test_success(
+ test_expand_success(
{ 'function $1($2)', '\t$0', 'end' },
{ 'function ()', ' ', 'end' },
[[
@@ -64,7 +66,7 @@ describe('vim.snippet', function()
end)
it('respects tabs when expandtab is not set', function()
- test_success(
+ test_expand_success(
{ 'function $1($2)', '\t$0', 'end' },
{ 'function ()', '\t', 'end' },
'vim.o.expandtab = false'
@@ -72,28 +74,28 @@ describe('vim.snippet', function()
end)
it('inserts known variable value', function()
- test_success({ '; print($TM_CURRENT_LINE)' }, { 'foo; print(foo)' }, nil, 'foo')
+ test_expand_success({ '; print($TM_CURRENT_LINE)' }, { 'foo; print(foo)' }, nil, 'foo')
end)
it('uses default when variable is not set', function()
- test_success({ 'print(${TM_CURRENT_WORD:foo})' }, { 'print(foo)' })
+ test_expand_success({ 'print(${TM_CURRENT_WORD:foo})' }, { 'print(foo)' })
end)
it('replaces unknown variables by placeholders', function()
- test_success({ 'print($UNKNOWN)' }, { 'print(UNKNOWN)' })
+ test_expand_success({ 'print($UNKNOWN)' }, { 'print(UNKNOWN)' })
end)
it('does not jump outside snippet range', function()
- test_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' })
+ test_expand_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' })
eq(false, exec_lua('return vim.snippet.jumpable(-1)'))
feed('<Tab><Tab>i')
eq(false, exec_lua('return vim.snippet.jumpable(1)'))
end)
it('navigates backwards', function()
- test_success({ 'function $1($2) end' }, { 'function () end' })
+ test_expand_success({ 'function $1($2) end' }, { 'function () end' })
feed('<Tab><S-Tab>foo')
- eq({ 'function foo() end' }, helpers.buf_lines(0))
+ eq({ 'function foo() end' }, buf_lines(0))
end)
it('visits all tabstops', function()
@@ -101,7 +103,7 @@ describe('vim.snippet', function()
return exec_lua('return vim.api.nvim_win_get_cursor(0)')
end
- test_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' })
+ test_expand_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' })
eq({ 1, 9 }, cursor())
feed('<Tab>')
eq({ 1, 10 }, cursor())
@@ -110,65 +112,91 @@ describe('vim.snippet', function()
end)
it('syncs text of tabstops with equal indexes', function()
- test_success({ 'var double = ${1:x} + ${1:x}' }, { 'var double = x + x' })
+ test_expand_success({ 'var double = ${1:x} + ${1:x}' }, { 'var double = x + x' })
feed('123')
- eq({ 'var double = 123 + 123' }, helpers.buf_lines(0))
+ eq({ 'var double = 123 + 123' }, buf_lines(0))
end)
it('cancels session with changes outside the snippet', function()
- test_success({ 'print($1)' }, { 'print()' })
+ test_expand_success({ 'print($1)' }, { 'print()' })
feed('<Esc>O-- A comment')
eq(false, exec_lua('return vim.snippet.active()'))
- eq({ '-- A comment', 'print()' }, helpers.buf_lines(0))
+ eq({ '-- A comment', 'print()' }, buf_lines(0))
end)
it('handles non-consecutive tabstops', function()
- test_success({ 'class $1($3) {', ' $0', '}' }, { 'class () {', ' ', '}' })
+ test_expand_success({ 'class $1($3) {', ' $0', '}' }, { 'class () {', ' ', '}' })
feed('Foo') -- First tabstop
feed('<Tab><Tab>') -- Jump to $0
feed('// Inside') -- Insert text
- eq({ 'class Foo() {', ' // Inside', '}' }, helpers.buf_lines(0))
+ eq({ 'class Foo() {', ' // Inside', '}' }, buf_lines(0))
end)
it('handles multiline placeholders', function()
- test_success(
+ test_expand_success(
{ 'public void foo() {', ' ${0:// TODO Auto-generated', ' throw;}', '}' },
{ 'public void foo() {', ' // TODO Auto-generated', ' throw;', '}' }
)
end)
it('inserts placeholder in all tabstops when the first tabstop has the placeholder', function()
- test_success(
+ test_expand_success(
{ 'for (${1:int} ${2:x} = ${3:0}; $2 < ${4:N}; $2++) {', ' $0', '}' },
{ 'for (int x = 0; x < N; x++) {', ' ', '}' }
)
end)
it('inserts placeholder in all tabstops when a later tabstop has the placeholder', function()
- test_success(
+ test_expand_success(
{ 'for (${1:int} $2 = ${3:0}; ${2:x} < ${4:N}; $2++) {', ' $0', '}' },
{ 'for (int x = 0; x < N; x++) {', ' ', '}' }
)
end)
it('errors with multiple placeholders for the same index', function()
- test_fail('class ${1:Foo} { void ${1:foo}() {} }', 'multiple placeholders for tabstop $1')
+ test_expand_fail('class ${1:Foo} { void ${1:foo}() {} }', 'multiple placeholders for tabstop $1')
end)
it('errors with multiple $0 tabstops', function()
- test_fail('function $1() { $0 }$0', 'multiple $0 tabstops')
+ test_expand_fail('function $1() { $0 }$0', 'multiple $0 tabstops')
end)
it('cancels session when deleting the snippet', function()
- test_success({ 'local function $1()', ' $0', 'end' }, { 'local function ()', ' ', 'end' })
+ test_expand_success({ 'local function $1()', ' $0', 'end' }, { 'local function ()', ' ', 'end' })
feed('<esc>Vjjd')
eq(false, exec_lua('return vim.snippet.active()'))
end)
it('cancels session when inserting outside snippet region', function()
feed('i<cr>')
- test_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' })
+ test_expand_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' })
feed('<esc>O-- A comment')
eq(false, exec_lua('return vim.snippet.active()'))
end)
+
+ it('inserts choice', function ()
+ test_expand_success({ 'console.${1|assert,log,error|}()' }, { 'console.()' })
+ sleep(100)
+ feed('<Down><C-y>')
+ eq({ 'console.log()' }, buf_lines(0))
+ end)
+
+ it('closes the choice completion menu when jumping', function ()
+ test_expand_success({ 'console.${1|assert,log,error|}($2)' }, { 'console.()' })
+ sleep(100)
+ exec_lua('vim.snippet.jump(1)')
+ eq(0, exec_lua('return vim.fn.pumvisible()'))
+ end)
+
+ it('jumps to next tabstop after inserting choice', function()
+ test_expand_success(
+ { '${1|public,protected,private|} function ${2:name}() {', '\t$0', '}' },
+ { ' function name() {', '\t', '}' }
+ )
+ sleep(100)
+ feed('<C-y><Tab>')
+ sleep(10)
+ feed('foo')
+ eq({ 'public function foo() {', '\t', '}' }, buf_lines(0))
+ end)
end)
diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua
new file mode 100644
index 0000000000..68206557c3
--- /dev/null
+++ b/test/functional/lua/text_spec.lua
@@ -0,0 +1,23 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+
+describe('vim.text', function()
+ before_each(clear)
+
+ describe('hexencode() and hexdecode()', function()
+ it('works', function()
+ local cases = {
+ { 'Hello world!', '48656C6C6F20776F726C6421' },
+ { '😂', 'F09F9882' },
+ }
+
+ for _, v in ipairs(cases) do
+ local input, output = unpack(v)
+ eq(output, vim.text.hexencode(input))
+ eq(input, vim.text.hexdecode(output))
+ end
+ end)
+ end)
+end)
+
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 960870fb46..b17eed00f9 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -1785,6 +1785,31 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
end)
+
+ it('supports hiding cursor', function()
+ child_session:request('nvim_command',
+ "let g:id = jobstart([v:progpath, '--clean', '--headless'])")
+ feed_data(':call jobwait([g:id])\n')
+ screen:expect([[
+ |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ :call jobwait([g:id]) |
+ {3:-- TERMINAL --} |
+ ]])
+ feed_data('\003')
+ screen:expect([[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ Type :qa and press <Enter> to exit Nvim |
+ {3:-- TERMINAL --} |
+ ]])
+ end)
end)
describe('TUI', function()
diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua
index 9a0182ea29..1addf7088e 100644
--- a/test/functional/ui/fold_spec.lua
+++ b/test/functional/ui/fold_spec.lua
@@ -1102,8 +1102,6 @@ describe("folded lines", function()
end)
it("works with multibyte text", function()
- -- Currently the only allowed value of 'maxcombine'
- eq(6, meths.get_option_value('maxcombine', {}))
eq(true, meths.get_option_value('arabicshape', {}))
insert([[
å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢͟ العَرَبِيَّة
@@ -1120,7 +1118,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- å 语 x̎͂̀̂͛͛ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
+ å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
möre tex^t |
{1:~ }|
{1:~ }|
@@ -1132,7 +1130,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- å 语 x̎͂̀̂͛͛ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
+ å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
möre tex^t |
{1:~ }|
{1:~ }|
@@ -1156,7 +1154,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ ﺎﻠﻋَﺮَﺒِﻳَّﺓ·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1168,7 +1166,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ ﺎﻠﻋَﺮَﺒِﻳَّﺓ·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1192,7 +1190,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1204,7 +1202,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1228,7 +1226,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة···········}|
+ {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة···········}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1240,7 +1238,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة···········}|
+ {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة···········}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1265,7 +1263,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:···········ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}{8: 1 }{7: +}|
+ {5:···········ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}{8: 1 }{7: +}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1277,7 +1275,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:···········ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}{8: 1 }{7: +}|
+ {5:···········ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}{8: 1 }{7: +}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1301,7 +1299,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:·················ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1313,7 +1311,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:·················ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1337,7 +1335,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:·················ﺔﻴَّﺑِﺮَﻌَﻟﺍ x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ﺔﻴَّﺑِﺮَﻌَﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1349,7 +1347,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:·················ﺔﻴَّﺑِﺮَﻌَﻟﺍ x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ﺔﻴَّﺑِﺮَﻌَﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1373,7 +1371,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̎͂̀̂͛͛ 语 å|
+ ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1385,7 +1383,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̎͂̀̂͛͛ 语 å|
+ ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1409,7 +1407,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- ةيَّبِرَعَ^لا x̎͂̀̂͛͛ 语 å|
+ ةيَّبِرَعَ^لا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1421,7 +1419,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- ةيَّبِرَعَ^لا x̎͂̀̂͛͛ 语 å|
+ ةيَّبِرَعَ^لا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua
index 077dd1a779..d72bf27d6b 100644
--- a/test/functional/ui/multibyte_spec.lua
+++ b/test/functional/ui/multibyte_spec.lua
@@ -228,6 +228,36 @@ describe("multibyte rendering", function()
]]}
end)
+
+ it('works with arabicshape and multiple composing chars', function()
+ -- this tests an important edge case: arabicshape might increase the byte size of the base
+ -- character in a way so that the last composing char no longer fits. use "g8" on the text
+ -- to observe what is happening (the final E1 80 B7 gets deleted with 'arabicshape')
+ -- If we would increase the schar_t size, say from 32 to 64 bytes, we need to extend the
+ -- test text with even more zalgo energy to still touch this edge case.
+
+ meths.buf_set_lines(0,0,-1,true, {"سلام့̀́̂̃̄̅̆̇̈̉̊̋̌"})
+ command('set noarabicshape')
+
+ screen:expect{grid=[[
+ ^سلام့̀́̂̃̄̅̆̇̈̉̊̋̌ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command('set arabicshape')
+ screen:expect{grid=[[
+ ^ﺱﻼﻣ̀́̂̃̄̅̆̇̈̉̊̋̌ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
end)
describe('multibyte rendering: statusline', function()
diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua
index 0dd1f0325c..7b93b74eac 100644
--- a/test/functional/ui/output_spec.lua
+++ b/test/functional/ui/output_spec.lua
@@ -225,8 +225,8 @@ describe("shell command :!", function()
å |
ref: å̲ |
1: å̲ |
- 2: å ̲ |
- 3: å ̲ |
+ 2: å ̲ |
+ 3: å ̲ |
|
{3:Press ENTER or type command to continue}^ |
]])
diff --git a/test/old/testdir/crash/bt_quickfix1_poc b/test/old/testdir/crash/bt_quickfix1_poc
new file mode 100644
index 0000000000..97993fde52
--- /dev/null
+++ b/test/old/testdir/crash/bt_quickfix1_poc
@@ -0,0 +1,5 @@
+au BufReadPre * exe 'sn' .. expand("<abuf>")
+call writefile([''],'X')
+sil! e X
+call writefile([''],'X')
+sil! e X
diff --git a/test/old/testdir/crash/bt_quickfix_poc b/test/old/testdir/crash/bt_quickfix_poc
new file mode 100644
index 0000000000..bf02b4dcb8
--- /dev/null
+++ b/test/old/testdir/crash/bt_quickfix_poc
@@ -0,0 +1,9 @@
+comman!-narg=* Xexpr <mods>lex<args>
+auto BufReadPre * exe"sn" ..expand("<abuf>")
+fu Xautocmd_changelist()
+cal writefile(['Xtestfile2:4:4'],'Xerr')
+ sil! edi Xerr
+Xexpr 'Xtestfile:4:4'
+endf
+call Xautocmd_changelist()
+call Xautocmd_changelist() \ No newline at end of file
diff --git a/test/old/testdir/crash/crash_scrollbar b/test/old/testdir/crash/crash_scrollbar
new file mode 100644
index 0000000000..1de5905228
--- /dev/null
+++ b/test/old/testdir/crash/crash_scrollbar
@@ -0,0 +1,2 @@
+" this goes to insert mode and presses key k_VerScrollbar which may cause a redraw in exmode, which used ot crash Vim
+norm oX
diff --git a/test/old/testdir/crash/editing_arg_idx_POC_1 b/test/old/testdir/crash/editing_arg_idx_POC_1
new file mode 100644
index 0000000000..5d048d0340
--- /dev/null
+++ b/test/old/testdir/crash/editing_arg_idx_POC_1
Binary files differ
diff --git a/test/old/testdir/crash/poc1 b/test/old/testdir/crash/poc1
new file mode 100644
index 0000000000..ec223f16b8
--- /dev/null
+++ b/test/old/testdir/crash/poc1
Binary files differ
diff --git a/test/old/testdir/crash/poc_huaf1 b/test/old/testdir/crash/poc_huaf1
new file mode 100644
index 0000000000..0d0ea475c1
--- /dev/null
+++ b/test/old/testdir/crash/poc_huaf1
Binary files differ
diff --git a/test/old/testdir/crash/poc_huaf2 b/test/old/testdir/crash/poc_huaf2
new file mode 100644
index 0000000000..4867e0f956
--- /dev/null
+++ b/test/old/testdir/crash/poc_huaf2
Binary files differ
diff --git a/test/old/testdir/crash/poc_huaf3 b/test/old/testdir/crash/poc_huaf3
new file mode 100644
index 0000000000..7e38a9a17c
--- /dev/null
+++ b/test/old/testdir/crash/poc_huaf3
Binary files differ
diff --git a/test/old/testdir/crash/poc_tagfunc.vim b/test/old/testdir/crash/poc_tagfunc.vim
new file mode 100644
index 0000000000..49d9b6f719
--- /dev/null
+++ b/test/old/testdir/crash/poc_tagfunc.vim
@@ -0,0 +1,6 @@
+fu Tagfunc(t,f,o)
+ bw
+endf
+set tagfunc=Tagfunc
+n0
+sil0norm0i
diff --git a/test/old/testdir/crash/vim_msg_trunc_poc b/test/old/testdir/crash/vim_msg_trunc_poc
new file mode 100644
index 0000000000..73b04bec35
--- /dev/null
+++ b/test/old/testdir/crash/vim_msg_trunc_poc
@@ -0,0 +1 @@
+lv\ngggggi;norm:᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌󠁲᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌
diff --git a/test/old/testdir/crash/vim_regsub_both_poc b/test/old/testdir/crash/vim_regsub_both_poc
new file mode 100644
index 0000000000..19a57114be
--- /dev/null
+++ b/test/old/testdir/crash/vim_regsub_both_poc
Binary files differ
diff --git a/test/old/testdir/test_crash.vim b/test/old/testdir/test_crash.vim
index 445fe8d5a7..b093b053c5 100644
--- a/test/old/testdir/test_crash.vim
+++ b/test/old/testdir/test_crash.vim
@@ -5,21 +5,142 @@ source screendump.vim
CheckScreendump
func Test_crash1()
+ CheckNotBSD
+ CheckExecutable dash
+ " Test 7 fails on Mac ...
+ CheckNotMac
+
" The following used to crash Vim
- let opts = #{wait_for_ruler: 0, rows: 20}
- let args = ' -u NONE -i NONE -n -e -s -S '
- let buf = RunVimInTerminal(args .. ' crash/poc_huaf1', opts)
- call VerifyScreenDump(buf, 'Test_crash_01', {})
- exe buf .. "bw!"
+ let opts = #{cmd: 'sh'}
+ let vim = GetVimProg()
- let buf = RunVimInTerminal(args .. ' crash/poc_huaf2', opts)
- call VerifyScreenDump(buf, 'Test_crash_01', {})
+ let buf = RunVimInTerminal('sh', opts)
+
+ let file = 'crash/poc_huaf1'
+ let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'"
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 1: [OK]" > X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 50)
+
+ let file = 'crash/poc_huaf2'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 2: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 50)
+
+ let file = 'crash/poc_huaf3'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 3: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 100)
+
+ let file = 'crash/bt_quickfix_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 4: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ " clean up
+ call delete('Xerr')
+ " This test takes a bit longer
+ call TermWait(buf, 1000)
+
+ let file = 'crash/poc_tagfunc.vim'
+ let args = printf(cmn_args, vim, file)
+ " using || because this poc causes vim to exit with exitstatus != 0
+ call term_sendkeys(buf, args ..
+ \ ' || echo "crash 5: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+
+ call TermWait(buf, 100)
+
+ let file = 'crash/bt_quickfix1_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 6: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ " clean up
+ call delete('X')
+ call TermWait(buf, 3000)
+
+ let file = 'crash/vim_regsub_both_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 7: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 3000)
+
+ let file = 'crash/vim_msg_trunc_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' || echo "crash 8: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 3000)
+
+ let file = 'crash/crash_scrollbar'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 9: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 1000)
+
+ let file = 'crash/editing_arg_idx_POC_1'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' || echo "crash 10: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 1000)
+ call delete('Xerr')
+ call delete('@')
+
+ " clean up
exe buf .. "bw!"
- let buf = RunVimInTerminal(args .. ' crash/poc_huaf3', opts)
- call VerifyScreenDump(buf, 'Test_crash_01', {})
+ sp X_crash1_result.txt
+
+ let expected = [
+ \ 'crash 1: [OK]',
+ \ 'crash 2: [OK]',
+ \ 'crash 3: [OK]',
+ \ 'crash 4: [OK]',
+ \ 'crash 5: [OK]',
+ \ 'crash 6: [OK]',
+ \ 'crash 7: [OK]',
+ \ 'crash 8: [OK]',
+ \ 'crash 9: [OK]',
+ \ 'crash 10: [OK]',
+ \ ]
+
+ call assert_equal(expected, getline(1, '$'))
+ bw!
+
+ call delete('X_crash1_result.txt')
+endfunc
+
+func Test_crash1_2()
+ CheckNotBSD
+ CheckExecutable dash
+
+ " The following used to crash Vim
+ let opts = #{cmd: 'sh'}
+ let vim = GetVimProg()
+ let result = 'X_crash1_1_result.txt'
+
+ let buf = RunVimInTerminal('sh', opts)
+
+ let file = 'crash/poc1'
+ let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'"
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 1: [OK]" > '.. result .. "\<cr>")
+ call TermWait(buf, 150)
+
+ " clean up
exe buf .. "bw!"
+ exe "sp " .. result
+
+ let expected = [
+ \ 'crash 1: [OK]',
+ \ ]
+
+ call assert_equal(expected, getline(1, '$'))
+ bw!
+
+ call delete(result)
endfunc
func Test_crash2()
diff --git a/test/old/testdir/test_excmd.vim b/test/old/testdir/test_excmd.vim
index 15c83709ad..c729ff4929 100644
--- a/test/old/testdir/test_excmd.vim
+++ b/test/old/testdir/test_excmd.vim
@@ -745,5 +745,9 @@ func Test_write_after_rename()
bwipe!
endfunc
+" catch address lines overflow
+func Test_ex_address_range_overflow()
+ call assert_fails(':--+foobar', 'E492:')
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim
index f5ef2cc4ca..6a8c15bd48 100644
--- a/test/old/testdir/test_normal.vim
+++ b/test/old/testdir/test_normal.vim
@@ -4164,4 +4164,9 @@ func Test_normal33_g_cmd_nonblank()
bw!
endfunc
+func Test_normal34_zet_large()
+ " shouldn't cause overflow
+ norm! z9765405999999999999
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim
index f428e77579..f2e7bc6b56 100644
--- a/test/old/testdir/test_scroll_opt.vim
+++ b/test/old/testdir/test_scroll_opt.vim
@@ -929,4 +929,23 @@ func Test_smoothscroll_cursor_top()
call StopVimInTerminal(buf)
endfunc
+" Division by zero, shouldn't crash
+func Test_smoothscroll_crash()
+ CheckScreendump
+
+ let lines =<< trim END
+ 20 new
+ vsp
+ put =repeat('aaaa', 20)
+ set nu fdc=1 smoothscroll cpo+=n
+ vert resize 0
+ exe "norm! 0\<c-e>"
+ END
+ call writefile(lines, 'XSmoothScrollCrash', 'D')
+ let buf = RunVimInTerminal('-u NONE -S XSmoothScrollCrash', #{rows: 12, cols:40})
+ call term_sendkeys(buf, "2\<C-E>\<C-L>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim
index b2fc40ee08..a19b64a7de 100644
--- a/test/old/testdir/test_spell.vim
+++ b/test/old/testdir/test_spell.vim
@@ -1081,6 +1081,15 @@ func Test_spell_compatible()
call StopVimInTerminal(buf)
endfunc
+func Test_z_equal_with_large_count()
+ split
+ set spell
+ call setline(1, "ff")
+ norm 0z=337203685477580
+ set nospell
+ bwipe!
+endfunc
+
let g:test_data_aff1 = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/test/old/testdir/test_substitute.vim b/test/old/testdir/test_substitute.vim
index 8dff0cda52..75062f13aa 100644
--- a/test/old/testdir/test_substitute.vim
+++ b/test/old/testdir/test_substitute.vim
@@ -206,6 +206,7 @@ func Test_substitute_count()
call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
\ getline(1, '$'))
+ call assert_fails('s/./b/2147483647', 'E1510:')
bwipe!
endfunc
@@ -1415,6 +1416,21 @@ func Test_substitute_short_cmd()
bw!
endfunc
+" Check handling expanding "~" resulting in extremely long text.
+" FIXME: disabled, it takes too long to run on CI
+"func Test_substitute_tilde_too_long()
+" enew!
+"
+" s/.*/ixxx
+" s//~~~~~~~~~AAAAAAA@(
+"
+" " Either fails with "out of memory" or "text too long".
+" " This can take a long time.
+" call assert_fails('sil! norm &&&&&&&&&', ['E1240:\|E342:'])
+"
+" bwipe!
+"endfunc
+
" This should be done last to reveal a memory leak when vim_regsub_both() is
" called to evaluate an expression but it is not used in a second call.
func Test_z_substitute_expr_leak()
diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua
index fdb1bceab0..cd94624570 100644
--- a/test/unit/mbyte_spec.lua
+++ b/test/unit/mbyte_spec.lua
@@ -4,17 +4,9 @@ local itp = helpers.gen_itp(it)
local ffi = helpers.ffi
local eq = helpers.eq
-local mbyte = helpers.cimport("./src/nvim/mbyte.h")
-local charset = helpers.cimport('./src/nvim/charset.h')
+local lib = helpers.cimport('./src/nvim/mbyte.h', './src/nvim/charset.h', './src/nvim/grid.h')
describe('mbyte', function()
- -- Array for composing characters
- local intp = ffi.typeof('int[?]')
- local function to_intp()
- -- how to get MAX_MCO from globals.h?
- return intp(7, 1)
- end
-
-- Convert from bytes to string
local function to_string(bytes)
local s = {}
@@ -30,14 +22,14 @@ describe('mbyte', function()
itp('utf_ptr2char', function()
-- For strings with length 1 the first byte is returned.
for c = 0, 255 do
- eq(c, mbyte.utf_ptr2char(to_string({c, 0})))
+ eq(c, lib.utf_ptr2char(to_string({c, 0})))
end
-- Some ill formed byte sequences that should not be recognized as UTF-8
-- First byte: 0xc0 or 0xc1
-- Second byte: 0x80 .. 0xbf
- --eq(0x00c0, mbyte.utf_ptr2char(to_string({0xc0, 0x80})))
- --eq(0x00c1, mbyte.utf_ptr2char(to_string({0xc1, 0xbf})))
+ --eq(0x00c0, lib.utf_ptr2char(to_string({0xc0, 0x80})))
+ --eq(0x00c1, lib.utf_ptr2char(to_string({0xc1, 0xbf})))
--
-- Sequences with more than four bytes
end)
@@ -47,240 +39,133 @@ describe('mbyte', function()
local char_p = ffi.typeof('char[?]')
for c = n * 0x1000, n * 0x1000 + 0xFFF do
local p = char_p(4, 0)
- mbyte.utf_char2bytes(c, p)
- eq(c, mbyte.utf_ptr2char(p))
- eq(charset.vim_iswordc(c), charset.vim_iswordp(p))
+ lib.utf_char2bytes(c, p)
+ eq(c, lib.utf_ptr2char(p))
+ eq(lib.vim_iswordc(c), lib.vim_iswordp(p))
end
end)
end
- describe('utfc_ptr2char_len', function()
+ describe('utfc_ptr2schar_len', function()
+ local function test_seq(seq)
+ local firstc = ffi.new("int[1]")
+ local buf = ffi.new("char[32]")
+ lib.schar_get(buf, lib.utfc_ptr2schar_len(to_string(seq), #seq, firstc))
+ return {ffi.string(buf), firstc[0]}
+ end
+
+ local function byte(val)
+ return {string.char(val), val}
+ end
itp('1-byte sequences', function()
- local pcc = to_intp()
- for c = 0, 255 do
- eq(c, mbyte.utfc_ptr2char_len(to_string({c}), pcc, 1))
- eq(0, pcc[0])
+ eq({'', 0}, test_seq{0})
+ for c = 1, 127 do
+ eq(byte(c), test_seq{c})
+ end
+ for c = 128, 255 do
+ eq({'', c}, test_seq{c})
end
end)
itp('2-byte sequences', function()
- local pcc = to_intp()
-- No combining characters
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x7f}), pcc, 2))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x7f})
-- No combining characters
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x80}), pcc, 2))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x80})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f}), pcc, 2))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80}), pcc, 2))
- eq(0, pcc[0])
+ eq({'\xc2\x80', 0x80}, test_seq{0xc2, 0x80})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0xc0}), pcc, 2))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0xc0})
end)
itp('3-byte sequences', function()
- local pcc = to_intp()
-
-- No second UTF-8 character
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x80, 0x80}), pcc, 3))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x80, 0x80})
-- No combining character
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xc2, 0x80}), pcc, 3))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0xc2, 0x80})
-- Combining character is U+0300
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80}), pcc, 3))
- eq(0x0300, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\x7f\xcc\x80", 0x7f}, test_seq{0x7f, 0xcc, 0x80})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f, 0xcc}), pcc, 3))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f, 0xcc})
-- Incomplete combining character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc}), pcc, 3))
- eq(0, pcc[0])
+ eq({"\xc2\x80", 0x80}, test_seq{0xc2, 0x80, 0xcc})
- -- One UTF-8 character
- pcc = to_intp()
- eq(0x20d0, mbyte.utfc_ptr2char_len(to_string({0xe2, 0x83, 0x90}), pcc, 3))
- eq(0, pcc[0])
+ -- One UTF-8 character (composing only)
+ eq({" \xe2\x83\x90", 0x20d0}, test_seq{0xe2, 0x83, 0x90})
end)
itp('4-byte sequences', function()
- local pcc = to_intp()
-- No following combining character
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x7f, 0xcc, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x7f, 0xcc, 0x80})
-- No second UTF-8 character
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xc2, 0xcc, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0xc2, 0xcc, 0x80})
-- Combining character U+0300
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc}), pcc, 4))
- eq(0x0300, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\x7f\xcc\x80", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f, 0xcc, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f, 0xcc, 0x80})
-- No following UTF-8 character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0xcc}), pcc, 4))
- eq(0, pcc[0])
+ eq({"\xc2\x80", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0xcc})
-- Combining character U+0301
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0x81}), pcc, 4))
- eq(0x0301, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\xc2\x80\xcc\x81", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0x81})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80})
end)
itp('5+-byte sequences', function()
- local pcc = to_intp()
-
-- No following combining character
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x7f, 0xcc, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x7f, 0xcc, 0x80, 0x80})
-- No second UTF-8 character
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xc2, 0xcc, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0xc2, 0xcc, 0x80, 0x80})
-- Combining character U+0300
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc}), pcc, 5))
- eq(0x0300, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\x7f\xcc\x80", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x00})
-- Combining characters U+0300 and U+0301
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc, 0x81}), pcc, 5))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0000, pcc[2])
+ eq({"\x7f\xcc\x80\xcc\x81", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81})
-- Combining characters U+0300, U+0301, U+0302
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82}), pcc, 7))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0000, pcc[3])
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82})
-- Combining characters U+0300, U+0301, U+0302, U+0303
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83}), pcc, 9))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0000, pcc[4])
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83})
-- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84}), pcc, 11))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0304, pcc[4])
- eq(0x0000, pcc[5])
- -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304,
- -- U+0305
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85}), pcc, 13))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0304, pcc[4])
- eq(0x0305, pcc[5])
- eq(1, pcc[6])
-
- -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304,
- -- U+0305, U+0306, but only save six (= MAX_MCO).
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85, 0xcc, 0x86}), pcc, 15))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0304, pcc[4])
- eq(0x0305, pcc[5])
- eq(0x0001, pcc[6])
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84})
+ -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85})
- -- Only three following combining characters U+0300, U+0301, U+0302
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85}), pcc, 13))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0000, pcc[3])
+ -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305, U+0306
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85\xcc\x86", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85, 0xcc, 0x86})
+ -- Only three following combining characters U+0300, U+0301, U+0302
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f, 0xcc, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f, 0xcc, 0x80, 0x80})
-- No following UTF-8 character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0xcc, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xc2\x80", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0xcc, 0x80})
-- Combining character U+0301
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0x81, 0x7f}), pcc, 5))
- eq(0x0301, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\xc2\x80\xcc\x81", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0x81, 0x7f})
-- Combining character U+0301
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0x81, 0xcc}), pcc, 5))
- eq(0x0301, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\xc2\x80\xcc\x81", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0x81, 0xcc})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80, 0x7f}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0x7f})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0x80})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80, 0xcc}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0xcc})
-- Combining characters U+1AB0 and U+0301
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string(
- {0xf4, 0x80, 0x80, 0x80, 0xe1, 0xaa, 0xb0, 0xcc, 0x81}), pcc, 9))
- eq(0x1ab0, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0000, pcc[2])
+ eq({"\xf4\x80\x80\x80\xe1\xaa\xb0\xcc\x81", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0xe1, 0xaa, 0xb0, 0xcc, 0x81})
end)
end)