aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/netrw.vim2
-rw-r--r--runtime/doc/api.txt2
-rw-r--r--runtime/doc/change.txt1
-rw-r--r--runtime/doc/deprecated.txt1
-rw-r--r--runtime/doc/lsp.txt23
-rw-r--r--runtime/doc/motion.txt2
-rw-r--r--runtime/doc/news.txt6
-rw-r--r--runtime/doc/treesitter.txt23
-rw-r--r--runtime/doc/visual.txt1
-rw-r--r--runtime/ftplugin/kotlin.vim33
-rw-r--r--runtime/indent/kotlin.vim60
-rw-r--r--runtime/lua/vim/_meta/api.lua5
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua1
-rw-r--r--runtime/lua/vim/filetype.lua1
-rw-r--r--runtime/lua/vim/lsp/handlers.lua15
-rw-r--r--runtime/lua/vim/lsp/util.lua178
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua3
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua57
-rw-r--r--runtime/queries/markdown/highlights.scm8
-rw-r--r--runtime/queries/markdown_inline/highlights.scm8
-rw-r--r--runtime/syntax/kotlin.vim157
-rw-r--r--src/nvim/api/keysets.h1
-rw-r--r--src/nvim/api/ui.c15
-rw-r--r--src/nvim/api/vim.c9
-rw-r--r--src/nvim/api/win_config.c14
-rw-r--r--src/nvim/buffer_defs.h11
-rw-r--r--src/nvim/drawline.c48
-rw-r--r--src/nvim/drawscreen.c18
-rw-r--r--src/nvim/eval.c4
-rw-r--r--src/nvim/grid.c178
-rw-r--r--src/nvim/grid.h37
-rw-r--r--src/nvim/grid_defs.h11
-rw-r--r--src/nvim/highlight.c2
-rw-r--r--src/nvim/map.c19
-rw-r--r--src/nvim/map.h38
-rw-r--r--src/nvim/map_glyph_cache.c102
-rw-r--r--src/nvim/map_key_impl.c.h2
-rw-r--r--src/nvim/map_value_impl.c.h2
-rw-r--r--src/nvim/mouse.c16
-rw-r--r--src/nvim/msgpack_rpc/unpacker.c5
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/option.c7
-rw-r--r--src/nvim/os/pty_process_win.c2
-rw-r--r--src/nvim/regexp.c51
-rw-r--r--src/nvim/statusline.c3
-rw-r--r--src/nvim/tui/tui.c36
-rw-r--r--src/nvim/types.h6
-rw-r--r--src/nvim/ugrid.c4
-rw-r--r--src/nvim/ugrid.h4
-rw-r--r--src/nvim/ui_compositor.c26
-rw-r--r--src/nvim/window.c4
-rw-r--r--test/README.md22
-rw-r--r--test/functional/plugin/lsp/utils_spec.lua34
-rw-r--r--test/functional/plugin/lsp_spec.lua2
-rw-r--r--test/functional/terminal/tui_spec.lua15
-rw-r--r--test/functional/ui/float_spec.lua65
-rw-r--r--test/functional/ui/fold_spec.lua125
-rw-r--r--test/functional/ui/highlight_spec.lua14
-rw-r--r--test/functional/ui/multibyte_spec.lua16
-rw-r--r--test/old/testdir/test_filetype.vim1
-rw-r--r--test/old/testdir/test_visual.vim14
61 files changed, 1224 insertions, 348 deletions
diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index be170d8aec..2dd5cf45bf 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -2951,7 +2951,7 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
" to process this detection correctly.
" call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand("<slnum>"))
" call Decho("..did_filetype()=".did_filetype())
- setl ft=
+" setl ft=
" call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
let iskkeep= &isk
setl isk-=/
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index ffd90ec3d7..f04fe9bb87 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -3177,6 +3177,8 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
• noautocmd: If true then no buffer-related autocommand
events such as |BufEnter|, |BufLeave| or |BufWinEnter| may
fire from calling this function.
+ • fixed: If true when anchor is NW or SW, the float window
+ would be kept fixed even if the window would be truncated.
Return: ~
Window handle, or 0 on error
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index 894557818b..2c47421b02 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -196,6 +196,7 @@ gR Enter Virtual Replace mode: Each character you type
*v_r*
{Visual}r{char} Replace all selected characters by {char}.
+ CTRL-C will be inserted literally.
*v_C*
{Visual}["x]C Delete the highlighted lines [into register x] and
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index 2e9312cf74..407d7ae9fb 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -141,6 +141,7 @@ LSP FUNCTIONS
`progress` of |vim.lsp.client|
- *vim.lsp.get_active_clients()* Use |vim.lsp.get_clients()|
- *vim.lsp.for_each_buffer_client()* Use |vim.lsp.get_clients()|
+- *vim.lsp.util.trim_empty_lines()* Use |vim.split()| with `trimempty` instead.
TREESITTER FUNCTIONS
- *vim.treesitter.language.require_language()* Use |vim.treesitter.language.add()|
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 5103cc223f..15292cd7cf 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1678,20 +1678,24 @@ convert_input_to_markdown_lines({input}, {contents})
window for `textDocument/hover`, for parsing the result of
`textDocument/signatureHelp`, and potentially others.
+ Note that if the input is of type `MarkupContent` and its kind is
+ `plaintext`, then the corresponding value is returned without further
+ modifications.
+
Parameters: ~
• {input} (`MarkedString` | `MarkedString[]` | `MarkupContent`)
• {contents} (table|nil) List of strings to extend with converted
lines. Defaults to {}.
Return: ~
- (table) {contents} extended with lines of converted markdown.
+ string[] extended with lines of converted markdown.
See also: ~
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
*vim.lsp.util.convert_signature_help_to_markdown_lines()*
convert_signature_help_to_markdown_lines({signature_help}, {ft}, {triggers})
- Converts `textDocument/SignatureHelp` response to markdown lines.
+ Converts `textDocument/signatureHelp` response to markdown lines.
Parameters: ~
• {signature_help} (table) Response of `textDocument/SignatureHelp`
@@ -1908,10 +1912,6 @@ open_floating_preview({contents}, {syntax}, {opts})
height when wrap is enabled
• max_width: (integer) maximal width of floating window
• max_height: (integer) maximal height of floating window
- • pad_top: (integer) number of lines to pad contents at
- top
- • pad_bottom: (integer) number of lines to pad contents at
- bottom
• focus_id: (string) if a popup with this id is opened,
then focus it
• close_events: (table) list of events that closes the
@@ -2005,8 +2005,6 @@ stylize_markdown({bufnr}, {contents}, {opts})
• wrap_at character to wrap at for computing height
• max_width maximal width of floating window
• max_height maximal height of floating window
- • pad_top number of lines to pad contents at top
- • pad_bottom number of lines to pad contents at bottom
• separator insert separator after code block
Return: ~
@@ -2035,15 +2033,6 @@ text_document_completion_list_to_complete_items({result}, {prefix})
See also: ~
• complete-items
-trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()*
- Removes empty lines from the beginning and end.
-
- Parameters: ~
- • {lines} (table) list of lines to trim
-
- Return: ~
- (table) trimmed list of lines
-
*vim.lsp.util.try_trim_markdown_code_blocks()*
try_trim_markdown_code_blocks({lines})
Accepts markdown lines and tries to reduce them to a filetype if they
diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 05244cde91..dc92601bfc 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -660,6 +660,7 @@ i` *v_i`* *i`*
Special case: With a count of 2 the quotes are
included, but no extra white space as with a"/a'/a`.
+ *o_object-select*
When used after an operator:
For non-block objects:
For the "a" commands: The operator applies to the object and the white
@@ -675,6 +676,7 @@ For a block object:
the surrounding braces are excluded. For the "a" commands, the braces
are included.
+ *v_object-select*
When used in Visual mode:
When start and end of the Visual area are the same (just after typing "v"):
One object is selected, the same as for using an operator.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 7c971097fb..240eb152d7 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -232,6 +232,11 @@ The following changes to existing APIs or features add new behavior.
In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to
return such ranges even if they started before the specified position.
+• LSP hover and signature help now use Treesitter for highlighting of Markdown
+ content.
+ Note that syntax highlighting of code examples requires a matching parser
+ and may be affected by custom queries.
+
==============================================================================
REMOVED FEATURES *news-removed*
@@ -267,6 +272,7 @@ release.
- |vim.lsp.util.get_progress_messages()| Use |vim.lsp.status()| instead.
- |vim.lsp.get_active_clients()| Use |vim.lsp.get_clients()| instead.
- |vim.lsp.for_each_buffer_client()| Use |vim.lsp.get_clients()| instead.
+ - |vim.lsp.util.trim_empty_lines()| Use |vim.split()| with `trimempty` instead.
• `vim.loop` has been renamed to `vim.uv`.
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index e1e280c4db..10f689bad7 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -1101,10 +1101,12 @@ LanguageTree:for_each_tree({fn}) *LanguageTree:for_each_tree()*
• {fn} fun(tree: TSTree, ltree: LanguageTree)
LanguageTree:included_regions() *LanguageTree:included_regions()*
- Gets the set of included regions
+ Gets the set of included regions managed by this LanguageTree . This can be different from the regions set by injection query, because a
+ partial |LanguageTree:parse()| drops the regions outside the requested
+ range.
Return: ~
- Range6[][]
+ table<integer, Range6[]>
LanguageTree:invalidate({reload}) *LanguageTree:invalidate()*
Invalidates this parser and all its children
@@ -1113,10 +1115,12 @@ LanguageTree:invalidate({reload}) *LanguageTree:invalidate()*
• {reload} (boolean|nil)
LanguageTree:is_valid({exclude_children}) *LanguageTree:is_valid()*
- Determines whether this tree is valid. If the tree is invalid, call `parse()` . This will return the updated tree.
+ Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest state of the
+ source. If invalid, user should call |LanguageTree:parse()|.
Parameters: ~
- • {exclude_children} (boolean|nil)
+ • {exclude_children} (boolean|nil) whether to ignore the validity of
+ children (default `false`)
Return: ~
(boolean)
@@ -1165,7 +1169,7 @@ LanguageTree:parse({range}) *LanguageTree:parse()*
injections).
Return: ~
- TSTree[]
+ table<integer, TSTree>
*LanguageTree:register_cbs()*
LanguageTree:register_cbs({cbs}, {recursive})
@@ -1207,7 +1211,12 @@ LanguageTree:tree_for_range({range}, {opts})
TSTree|nil
LanguageTree:trees() *LanguageTree:trees()*
- Returns all trees this language tree contains. Does not include child
- languages.
+ Returns all trees of the regions parsed by this parser. Does not include
+ child languages. The result is list-like if
+ • this LanguageTree is the root, in which case the result is empty or a singleton list; or
+ • the root LanguageTree is fully parsed.
+
+ Return: ~
+ table<integer, TSTree>
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt
index 6ca486e8cf..0d1ea937c0 100644
--- a/runtime/doc/visual.txt
+++ b/runtime/doc/visual.txt
@@ -169,6 +169,7 @@ If you want to highlight exactly the same area as the last time, you can use
CTRL-C In Visual mode: Stop Visual mode. When insert mode is
pending (the mode message shows
"-- (insert) VISUAL --"), it is also stopped.
+ On MS-Windows, you may need to press CTRL-Break.
==============================================================================
3. Changing the Visual area *visual-change*
diff --git a/runtime/ftplugin/kotlin.vim b/runtime/ftplugin/kotlin.vim
new file mode 100644
index 0000000000..b21de603ea
--- /dev/null
+++ b/runtime/ftplugin/kotlin.vim
@@ -0,0 +1,33 @@
+" Vim filetype plugin file
+" Language: Kotlin
+" Maintainer: Alexander Udalov
+" URL: https://github.com/udalov/kotlin-vim
+" Last Change: 7 November 2021
+" 2023 Sep 17 by Vim Project (browsefilter)
+
+if exists('b:did_ftplugin') | finish | endif
+let b:did_ftplugin = 1
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,://
+setlocal commentstring=//\ %s
+
+setlocal formatoptions-=t formatoptions+=croqnl
+silent! setlocal formatoptions+=j
+
+setlocal includeexpr=substitute(v:fname,'\\.','/','g')
+setlocal suffixesadd=.kt
+
+let b:undo_ftplugin = "setlocal comments< commentstring< ".
+ \ "formatoptions< includeexpr< suffixesadd<"
+
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
+ let b:browsefilter = "Kotlin Source Files (*.kt, *kts)\t*.kt;*.kts\n" .
+ \ "All Files (*.*)\t*.*\n"
+ let b:undo_ftplugin .= " | unlet! b:browsefilter"
+endif
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
diff --git a/runtime/indent/kotlin.vim b/runtime/indent/kotlin.vim
new file mode 100644
index 0000000000..590a5074d1
--- /dev/null
+++ b/runtime/indent/kotlin.vim
@@ -0,0 +1,60 @@
+" Vim indent file
+" Language: Kotlin
+" Maintainer: Alexander Udalov
+" URL: https://github.com/udalov/kotlin-vim
+" Last Change: 7 November 2021
+" 2023 Sep 17 by Vim Project (undo_indent)
+
+if exists('b:did_indent')
+ finish
+endif
+let b:did_indent = 1
+
+setlocal cinoptions& cinoptions+=j1,L0
+setlocal indentexpr=GetKotlinIndent()
+setlocal indentkeys=0},0),!^F,o,O,e,<CR>
+setlocal autoindent " TODO ?
+
+let b:undo_indent = "setlocal autoindent< cinoptions< indentexpr< indentkeys<"
+
+" TODO teach it to count bracket balance, etc.
+function! GetKotlinIndent()
+ if v:lnum == 0
+ return 0
+ endif
+
+ let prev_num = prevnonblank(v:lnum - 1)
+ let prev = getline(prev_num)
+ let prev_indent = indent(prev_num)
+ let cur = getline(v:lnum)
+
+ if cur =~ '^\s*\*'
+ return cindent(v:lnum)
+ endif
+
+ if prev =~ '^\s*\*/'
+ let st = prev
+ while st > 1
+ if getline(st) =~ '^\s*/\*'
+ break
+ endif
+ let st = st - 1
+ endwhile
+ return indent(st)
+ endif
+
+ let prev_open_paren = prev =~ '^.*(\s*$'
+ let cur_close_paren = cur =~ '^\s*).*$'
+ let prev_open_brace = prev =~ '^.*\({\|->\)\s*$'
+ let cur_close_brace = cur =~ '^\s*}.*$'
+
+ if prev_open_paren && !cur_close_paren || prev_open_brace && !cur_close_brace
+ return prev_indent + shiftwidth()
+ endif
+
+ if cur_close_paren && !prev_open_paren || cur_close_brace && !prev_open_brace
+ return prev_indent - shiftwidth()
+ endif
+
+ return prev_indent
+endfunction
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 6573c68493..41bb5636b8 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -80,6 +80,9 @@ function vim.api.nvim__id_float(flt) end
function vim.api.nvim__inspect_cell(grid, row, col) end
--- @private
+function vim.api.nvim__invalidate_glyph_cache() end
+
+--- @private
--- @return any[]
function vim.api.nvim__runtime_inspect() end
@@ -1608,6 +1611,8 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • noautocmd: If true then no buffer-related autocommand
--- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may
--- fire from calling this function.
+--- • fixed: If true when anchor is NW or SW, the float window
+--- would be kept fixed even if the window would be truncated.
--- @return integer
function vim.api.nvim_open_win(buffer, enter, config) end
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
index 4d08563ce2..b249f6629f 100644
--- a/runtime/lua/vim/_meta/api_keysets.lua
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -112,6 +112,7 @@ error('Cannot require a meta file')
--- @field footer_pos? string
--- @field style? string
--- @field noautocmd? boolean
+--- @field fixed? boolean
--- @class vim.api.keyset.get_autocmds
--- @field event? any
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index d847c28f5c..c7f025f9e7 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -592,6 +592,7 @@ local extension = {
ly = 'lilypond',
ily = 'lilypond',
liquid = 'liquid',
+ liq = 'liquidsoap',
cl = 'lisp',
L = 'lisp',
lisp = 'lisp',
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 4ea3dde81c..d43d9a7cfa 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -371,15 +371,21 @@ function M.hover(_, result, ctx, config)
end
return
end
- local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
- markdown_lines = util.trim_empty_lines(markdown_lines)
- if vim.tbl_isempty(markdown_lines) then
+ local format = 'markdown'
+ local contents ---@type string[]
+ if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
+ format = 'plaintext'
+ contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
+ else
+ contents = util.convert_input_to_markdown_lines(result.contents)
+ end
+ if vim.tbl_isempty(contents) then
if config.silent ~= true then
vim.notify('No information available')
end
return
end
- return util.open_floating_preview(markdown_lines, 'markdown', config)
+ return util.open_floating_preview(contents, format, config)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@@ -470,7 +476,6 @@ function M.signature_help(_, result, ctx, config)
vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
local ft = vim.bo[ctx.bufnr].filetype
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
- lines = util.trim_empty_lines(lines)
if vim.tbl_isempty(lines) then
if config.silent ~= true then
print('No signature help available')
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 54721865b7..988057f5f9 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -102,7 +102,7 @@ end
local function split_lines(value)
value = string.gsub(value, '\r\n?', '\n')
- return split(value, '\n', { plain = true })
+ return split(value, '\n', { plain = true, trimempty = true })
end
local function create_window_without_focus()
@@ -877,9 +877,12 @@ end
--- window for `textDocument/hover`, for parsing the result of
--- `textDocument/signatureHelp`, and potentially others.
---
+--- Note that if the input is of type `MarkupContent` and its kind is `plaintext`,
+--- then the corresponding value is returned without further modifications.
+---
---@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`)
---@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}.
----@return table {contents} extended with lines of converted markdown.
+---@return string[] extended with lines of converted markdown.
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
function M.convert_input_to_markdown_lines(input, contents)
contents = contents or {}
@@ -887,27 +890,13 @@ function M.convert_input_to_markdown_lines(input, contents)
if type(input) == 'string' then
list_extend(contents, split_lines(input))
else
- assert(type(input) == 'table', 'Expected a table for Hover.contents')
+ assert(type(input) == 'table', 'Expected a table for LSP input')
-- MarkupContent
if input.kind then
- -- The kind can be either plaintext or markdown.
- -- If it's plaintext, then wrap it in a <text></text> block
-
- -- Some servers send input.value as empty, so let's ignore this :(
local value = input.value or ''
-
- if input.kind == 'plaintext' then
- -- wrap this in a <text></text> block so that stylize_markdown
- -- can properly process it as plaintext
- value = string.format('<text>\n%s\n</text>', value)
- end
-
- -- assert(type(value) == 'string')
list_extend(contents, split_lines(value))
-- MarkupString variation 2
elseif input.language then
- -- Some servers send input.value as empty, so let's ignore this :(
- -- assert(type(input.value) == 'string')
table.insert(contents, '```' .. input.language)
list_extend(contents, split_lines(input.value or ''))
table.insert(contents, '```')
@@ -925,7 +914,7 @@ function M.convert_input_to_markdown_lines(input, contents)
return contents
end
---- Converts `textDocument/SignatureHelp` response to markdown lines.
+--- Converts `textDocument/signatureHelp` response to markdown lines.
---
---@param signature_help table Response of `textDocument/SignatureHelp`
---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block
@@ -955,10 +944,10 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
end
local label = signature.label
if ft then
- -- wrap inside a code block so stylize_markdown can render it properly
+ -- wrap inside a code block for proper rendering
label = ('```%s\n%s\n```'):format(ft, label)
end
- list_extend(contents, split(label, '\n', { plain = true }))
+ list_extend(contents, split(label, '\n', { plain = true, trimempty = true }))
if signature.documentation then
-- if LSP returns plain string, we treat it as plaintext. This avoids
-- special characters like underscore or similar from being interpreted
@@ -1223,7 +1212,7 @@ function M.preview_location(location, opts)
local syntax = vim.bo[bufnr].syntax
if syntax == '' then
-- When no syntax is set, we use filetype as fallback. This might not result
- -- in a valid syntax definition. See also ft detection in stylize_markdown.
+ -- in a valid syntax definition.
-- An empty syntax is more common now with TreeSitter, since TS disables syntax.
syntax = vim.bo[bufnr].filetype
end
@@ -1240,36 +1229,65 @@ local function find_window_by_var(name, value)
end
end
---- Trims empty lines from input and pad top and bottom with empty lines
----
----@param contents table of lines to trim and pad
----@param opts table with optional fields
---- - pad_top number of lines to pad contents at top (default 0)
---- - pad_bottom number of lines to pad contents at bottom (default 0)
----@return table table of trimmed and padded lines
-function M._trim(contents, opts)
- validate({
- contents = { contents, 't' },
- opts = { opts, 't', true },
- })
- opts = opts or {}
- contents = M.trim_empty_lines(contents)
- if opts.pad_top then
- for _ = 1, opts.pad_top do
- table.insert(contents, 1, '')
+---Returns true if the line is empty or only contains whitespace.
+---@param line string
+---@return boolean
+local function is_blank_line(line)
+ return line and line:match('^%s*$')
+end
+
+---Returns true if the line corresponds to a Markdown thematic break.
+---@param line string
+---@return boolean
+local function is_separator_line(line)
+ return line and line:match('^ ? ? ?%-%-%-+%s*$')
+end
+
+---Replaces separator lines by the given divider and removing surrounding blank lines.
+---@param contents string[]
+---@param divider string
+---@return string[]
+local function replace_separators(contents, divider)
+ local trimmed = {}
+ local l = 1
+ while l <= #contents do
+ local line = contents[l]
+ if is_separator_line(line) then
+ if l > 1 and is_blank_line(contents[l - 1]) then
+ table.remove(trimmed)
+ end
+ table.insert(trimmed, divider)
+ if is_blank_line(contents[l + 1]) then
+ l = l + 1
+ end
+ else
+ table.insert(trimmed, line)
end
+ l = l + 1
end
- if opts.pad_bottom then
- for _ = 1, opts.pad_bottom do
- table.insert(contents, '')
+
+ return trimmed
+end
+
+---Collapses successive blank lines in the input table into a single one.
+---@param contents string[]
+---@return string[]
+local function collapse_blank_lines(contents)
+ local collapsed = {}
+ local l = 1
+ while l <= #contents do
+ local line = contents[l]
+ if is_blank_line(line) then
+ while is_blank_line(contents[l + 1]) do
+ l = l + 1
+ end
end
+ table.insert(collapsed, line)
+ l = l + 1
end
- return contents
+ return collapsed
end
---- Generates a table mapping markdown code block lang to vim syntax,
---- based on g:markdown_fenced_languages
----@return table table of lang -> syntax mappings
local function get_markdown_fences()
local fences = {}
for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do
@@ -1297,8 +1315,6 @@ end
--- - wrap_at character to wrap at for computing height
--- - max_width maximal width of floating window
--- - max_height maximal height of floating window
---- - pad_top number of lines to pad contents at top
---- - pad_bottom number of lines to pad contents at bottom
--- - separator insert separator after code block
---@return table stripped content
function M.stylize_markdown(bufnr, contents, opts)
@@ -1335,7 +1351,7 @@ function M.stylize_markdown(bufnr, contents, opts)
end
-- Clean up
- contents = M._trim(contents, opts)
+ contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true })
local stripped = {}
local highlights = {}
@@ -1484,6 +1500,45 @@ function M.stylize_markdown(bufnr, contents, opts)
return stripped
end
+--- @class lsp.util.NormalizeMarkdownOptions
+--- @field width integer Thematic breaks are expanded to this size. Defaults to 80.
+
+--- Normalizes Markdown input to a canonical form.
+---
+--- The returned Markdown adheres to the GitHub Flavored Markdown (GFM)
+--- specification.
+---
+--- The following transformations are made:
+---
+--- 1. Carriage returns ('\r') and empty lines at the beginning and end are removed
+--- 2. Successive empty lines are collapsed into a single empty line
+--- 3. Thematic breaks are expanded to the given width
+---
+---@private
+---@param contents string[]
+---@param opts? lsp.util.NormalizeMarkdownOptions
+---@return string[] table of lines containing normalized Markdown
+---@see https://github.github.com/gfm
+function M._normalize_markdown(contents, opts)
+ validate({
+ contents = { contents, 't' },
+ opts = { opts, 't', true },
+ })
+ opts = opts or {}
+
+ -- 1. Carriage returns are removed
+ contents = vim.split(table.concat(contents, '\n'):gsub('\r', ''), '\n', { trimempty = true })
+
+ -- 2. Successive empty lines are collapsed into a single empty line
+ contents = collapse_blank_lines(contents)
+
+ -- 3. Thematic breaks are expanded to the given width
+ local divider = string.rep('─', opts.width or 80)
+ contents = replace_separators(contents, divider)
+
+ return contents
+end
+
--- Closes the preview window
---
---@param winnr integer window id of preview window
@@ -1620,8 +1675,6 @@ end
--- - wrap_at: (integer) character to wrap at for computing height when wrap is enabled
--- - max_width: (integer) maximal width of floating window
--- - max_height: (integer) maximal height of floating window
---- - pad_top: (integer) number of lines to pad contents at top
---- - pad_bottom: (integer) number of lines to pad contents at bottom
--- - focus_id: (string) if a popup with this id is opened, then focus it
--- - close_events: (table) list of events that closes the floating window
--- - focusable: (boolean, default true) Make float focusable
@@ -1629,8 +1682,7 @@ end
--- is also `true`, focus an existing floating window with the same
--- {focus_id}
---@return integer bufnr of newly created float window
----@return integer winid of newly created float window
----preview window
+---@return integer winid of newly created float window preview window
function M.open_floating_preview(contents, syntax, opts)
validate({
contents = { contents, 't' },
@@ -1639,7 +1691,6 @@ function M.open_floating_preview(contents, syntax, opts)
})
opts = opts or {}
opts.wrap = opts.wrap ~= false -- wrapping by default
- opts.stylize_markdown = opts.stylize_markdown ~= false and vim.g.syntax_on ~= nil
opts.focus = opts.focus ~= false
opts.close_events = opts.close_events or { 'CursorMoved', 'CursorMovedI', 'InsertCharPre' }
@@ -1671,16 +1722,21 @@ function M.open_floating_preview(contents, syntax, opts)
api.nvim_win_close(existing_float, true)
end
+ -- Create the buffer
local floating_bufnr = api.nvim_create_buf(false, true)
- local do_stylize = syntax == 'markdown' and opts.stylize_markdown
-
- -- Clean up input: trim empty lines from the end, pad
- contents = M._trim(contents, opts)
+ -- Set up the contents, using treesitter for markdown
+ local do_stylize = syntax == 'markdown' and vim.g.syntax_on ~= nil
if do_stylize then
- -- applies the syntax and sets the lines to the buffer
- contents = M.stylize_markdown(floating_bufnr, contents, opts)
+ local width = M._make_floating_popup_size(contents, opts)
+ contents = M._normalize_markdown(contents, { width = width })
+ vim.bo[floating_bufnr].filetype = 'markdown'
+ vim.treesitter.start(floating_bufnr)
+ api.nvim_buf_set_lines(floating_bufnr, 0, -1, false, contents)
else
+ -- Clean up input: trim empty lines
+ contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true })
+
if syntax then
vim.bo[floating_bufnr].syntax = syntax
end
@@ -1697,9 +1753,9 @@ function M.open_floating_preview(contents, syntax, opts)
local float_option = M.make_floating_popup_options(width, height, opts)
local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
+
if do_stylize then
vim.wo[floating_winnr].conceallevel = 2
- vim.wo[floating_winnr].concealcursor = 'n'
end
-- disable folding
vim.wo[floating_winnr].foldenable = false
@@ -1708,6 +1764,7 @@ function M.open_floating_preview(contents, syntax, opts)
vim.bo[floating_bufnr].modifiable = false
vim.bo[floating_bufnr].bufhidden = 'wipe'
+
api.nvim_buf_set_keymap(
floating_bufnr,
'n',
@@ -1908,6 +1965,7 @@ function M.symbols_to_items(symbols, bufnr)
end
--- Removes empty lines from the beginning and end.
+---@deprecated use `vim.split()` with `trimempty` instead
---@param lines table list of lines to trim
---@return table trimmed list of lines
function M.trim_empty_lines(lines)
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index 9a94f12c16..d01b7be3b0 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -50,7 +50,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
---@class TSParser
----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[]
+---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: true): TSTree, Range6[]
+---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: false|nil): TSTree, Range4[]
---@field reset fun(self: TSParser)
---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[]
---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index b555ee231b..f931291ed7 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -78,13 +78,14 @@ local TSCallbackNames = {
---@field private _opts table Options
---@field private _parser TSParser Parser for language
---@field private _has_regions boolean
----@field private _regions Range6[][]?
+---@field private _regions table<integer, Range6[]>?
---List of regions this tree should manage and parse. If nil then regions are
---taken from _trees. This is mostly a short-lived cache for included_regions()
---@field private _lang string Language name
---@field private _parent_lang? string Parent language name
---@field private _source (integer|string) Buffer or string to parse
----@field private _trees TSTree[] Reference to parsed tree (one for each language)
+---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language).
+---Each key is the index of region, which is synced with _regions and _valid.
---@field private _valid boolean|table<integer,boolean> If the parsed tree is valid
---@field private _logger? fun(logtype: string, msg: string)
---@field private _logfile? file*
@@ -211,7 +212,7 @@ function LanguageTree:_log(...)
end
local info = debug.getinfo(2, 'nl')
- local nregions = #self:included_regions()
+ local nregions = vim.tbl_count(self:included_regions())
local prefix =
string.format('%s:%d: (#regions=%d) ', info.name or '???', info.currentline or 0, nregions)
@@ -244,8 +245,13 @@ function LanguageTree:invalidate(reload)
end
end
---- Returns all trees this language tree contains.
+--- Returns all trees of the regions parsed by this parser.
--- Does not include child languages.
+--- The result is list-like if
+--- * this LanguageTree is the root, in which case the result is empty or a singleton list; or
+--- * the root LanguageTree is fully parsed.
+---
+---@return table<integer, TSTree>
function LanguageTree:trees()
return self._trees
end
@@ -255,16 +261,15 @@ function LanguageTree:lang()
return self._lang
end
---- Determines whether this tree is valid.
---- If the tree is invalid, call `parse()`.
---- This will return the updated tree.
----@param exclude_children boolean|nil
+--- Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest
+--- state of the source. If invalid, user should call |LanguageTree:parse()|.
+---@param exclude_children boolean|nil whether to ignore the validity of children (default `false`)
---@return boolean
function LanguageTree:is_valid(exclude_children)
local valid = self._valid
if type(valid) == 'table' then
- for i = 1, #self:included_regions() do
+ for i, _ in pairs(self:included_regions()) do
if not valid[i] then
return false
end
@@ -328,7 +333,7 @@ end
--- @private
--- @param range boolean|Range?
---- @return integer[] changes
+--- @return Range6[] changes
--- @return integer no_regions_parsed
--- @return number total_parse_time
function LanguageTree:_parse_regions(range)
@@ -370,7 +375,7 @@ function LanguageTree:_add_injections()
local seen_langs = {} ---@type table<string,boolean>
local query_time, injections_by_lang = tcall(self._get_injections, self)
- for lang, injection_ranges in pairs(injections_by_lang) do
+ for lang, injection_regions in pairs(injections_by_lang) do
local has_lang = pcall(language.add, lang)
-- Child language trees should just be ignored if not found, since
@@ -383,7 +388,7 @@ function LanguageTree:_add_injections()
child = self:add_child(lang)
end
- child:set_included_regions(injection_ranges)
+ child:set_included_regions(injection_regions)
seen_langs[lang] = true
end
end
@@ -408,14 +413,14 @@ end
--- Set to `true` to run a complete parse of the source (Note: Can be slow!)
--- Set to `false|nil` to only parse regions with empty ranges (typically
--- only the root tree without injections).
---- @return TSTree[]
+--- @return table<integer, TSTree>
function LanguageTree:parse(range)
if self:is_valid() then
self:_log('valid')
return self._trees
end
- local changes --- @type Range6?
+ local changes --- @type Range6[]?
-- Collect some stats
local no_regions_parsed = 0
@@ -565,7 +570,7 @@ function LanguageTree:_iter_regions(fn)
local all_valid = true
- for i, region in ipairs(self:included_regions()) do
+ for i, region in pairs(self:included_regions()) do
if was_valid or self._valid[i] then
self._valid[i] = fn(i, region)
if not self._valid[i] then
@@ -601,7 +606,7 @@ end
--- nodes, which is useful for templating languages like ERB and EJS.
---
---@private
----@param new_regions Range6[][] List of regions this tree should manage and parse.
+---@param new_regions (Range4|Range6|TSNode)[][] List of regions this tree should manage and parse.
function LanguageTree:set_included_regions(new_regions)
self._has_regions = true
@@ -609,16 +614,20 @@ function LanguageTree:set_included_regions(new_regions)
for _, region in ipairs(new_regions) do
for i, range in ipairs(region) do
if type(range) == 'table' and #range == 4 then
- region[i] = Range.add_bytes(self._source, range)
+ region[i] = Range.add_bytes(self._source, range --[[@as Range4]])
elseif type(range) == 'userdata' then
region[i] = { range:range(true) }
end
end
end
+ -- included_regions is not guaranteed to be list-like, but this is still sound, i.e. if
+ -- new_regions is different from included_regions, then outdated regions in included_regions are
+ -- invalidated. For example, if included_regions = new_regions ++ hole ++ outdated_regions, then
+ -- outdated_regions is invalidated by _iter_regions in else branch.
if #self:included_regions() ~= #new_regions then
-- TODO(lewis6991): inefficient; invalidate trees incrementally
- for _, t in ipairs(self._trees) do
+ for _, t in pairs(self._trees) do
self:_do_callback('changedtree', t:included_ranges(true), t)
end
self._trees = {}
@@ -632,20 +641,22 @@ function LanguageTree:set_included_regions(new_regions)
self._regions = new_regions
end
----Gets the set of included regions
----@return Range6[][]
+---Gets the set of included regions managed by this LanguageTree. This can be different from the
+---regions set by injection query, because a partial |LanguageTree:parse()| drops the regions
+---outside the requested range.
+---@return table<integer, Range6[]>
function LanguageTree:included_regions()
if self._regions then
return self._regions
end
- if not self._has_regions or #self._trees == 0 then
+ if not self._has_regions or next(self._trees) == nil then
-- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1}
return { {} }
end
local regions = {} ---@type Range6[][]
- for i, _ in ipairs(self._trees) do
+ for i, _ in pairs(self._trees) do
regions[i] = self._trees[i]:included_ranges(true)
end
@@ -801,7 +812,7 @@ local function combine_regions(regions)
return result
end
---- Gets language injection points by language.
+--- Gets language injection regions by language.
---
--- This is where most of the injection processing occurs.
---
diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm
index e78d233cc6..2cc5546bac 100644
--- a/runtime/queries/markdown/highlights.scm
+++ b/runtime/queries/markdown/highlights.scm
@@ -61,3 +61,11 @@
] @string.escape
(inline) @spell
+
+;; Conceal backticks
+(fenced_code_block
+ (fenced_code_block_delimiter) @conceal
+ (#set! conceal ""))
+(fenced_code_block
+ (info_string (language) @conceal
+ (#set! conceal "")))
diff --git a/runtime/queries/markdown_inline/highlights.scm b/runtime/queries/markdown_inline/highlights.scm
index cd5da530d7..c75da478af 100644
--- a/runtime/queries/markdown_inline/highlights.scm
+++ b/runtime/queries/markdown_inline/highlights.scm
@@ -92,3 +92,11 @@
"]"
] @conceal
(#set! conceal ""))
+
+;; Replace common HTML entities.
+((entity_reference) @conceal (#eq? @conceal "&nbsp;") (#set! conceal ""))
+((entity_reference) @conceal (#eq? @conceal "&lt;") (#set! conceal "<"))
+((entity_reference) @conceal (#eq? @conceal "&gt;") (#set! conceal ">"))
+((entity_reference) @conceal (#eq? @conceal "&amp;") (#set! conceal "&"))
+((entity_reference) @conceal (#eq? @conceal "&quot;") (#set! conceal "\""))
+((entity_reference) @conceal (#any-of? @conceal "&ensp;" "&emsp;") (#set! conceal " "))
diff --git a/runtime/syntax/kotlin.vim b/runtime/syntax/kotlin.vim
new file mode 100644
index 0000000000..9b85b8ef5c
--- /dev/null
+++ b/runtime/syntax/kotlin.vim
@@ -0,0 +1,157 @@
+" Vim syntax file
+" Language: Kotlin
+" Maintainer: Alexander Udalov
+" URL: https://github.com/udalov/kotlin-vim
+" Last Change: 30 December 2022
+
+if exists('b:current_syntax')
+ finish
+endif
+
+syn keyword ktStatement break continue return
+syn keyword ktConditional if else when
+syn keyword ktRepeat do for while
+syn keyword ktOperator in is by
+syn keyword ktKeyword get set out super this where
+syn keyword ktException try catch finally throw
+
+syn keyword ktInclude import package
+
+" Generated stdlib class names {{{
+" The following is generated by https://github.com/udalov/kotlin-vim/blob/master/extra/generate-stdlib-class-names.main.kts
+syn keyword ktType AbstractCollection AbstractCoroutineContextElement AbstractCoroutineContextKey AbstractDoubleTimeSource AbstractIterator AbstractList AbstractLongTimeSource
+syn keyword ktType AbstractMap AbstractMutableCollection AbstractMutableList AbstractMutableMap AbstractMutableSet AbstractSet AccessDeniedException Accessor Annotation
+syn keyword ktType AnnotationRetention AnnotationTarget Any Appendable ArithmeticException Array ArrayDeque ArrayList AssertionError Boolean BooleanArray BooleanIterator
+syn keyword ktType BuilderInference Byte ByteArray ByteIterator CName CallsInPlace CancellationException Char CharArray CharCategory CharDirectionality CharIterator CharProgression
+syn keyword ktType CharRange CharSequence CharacterCodingException Charsets ClassCastException Cloneable ClosedFloatingPointRange ClosedRange Collection Comparable
+syn keyword ktType ComparableTimeMark Comparator ConcurrentModificationException ConditionalEffect ContextFunctionTypeParams Continuation ContinuationInterceptor ContractBuilder
+syn keyword ktType CopyActionContext CopyActionResult CoroutineContext DeepRecursiveFunction DeepRecursiveScope Delegates Deprecated DeprecatedSinceKotlin DeprecationLevel
+syn keyword ktType Destructured Double DoubleArray DoubleIterator DslMarker Duration DurationUnit Effect Element EmptyCoroutineContext Entry Enum EnumEntries Error Exception
+syn keyword ktType ExperimentalContracts ExperimentalJsExport ExperimentalMultiplatform ExperimentalObjCName ExperimentalObjCRefinement ExperimentalPathApi ExperimentalStdlibApi
+syn keyword ktType ExperimentalSubclassOptIn ExperimentalTime ExperimentalTypeInference ExperimentalUnsignedTypes ExtensionFunctionType FileAlreadyExistsException
+syn keyword ktType FileSystemException FileTreeWalk FileVisitorBuilder FileWalkDirection Float FloatArray FloatIterator FreezingIsDeprecated Function Function0 Function1 Function10
+syn keyword ktType Function11 Function12 Function13 Function14 Function15 Function16 Function17 Function18 Function19 Function2 Function20 Function21 Function22 Function3 Function4
+syn keyword ktType Function5 Function6 Function7 Function8 Function9 FunctionN Getter Grouping HashMap HashSet HiddenFromObjC HidesFromObjC Ignore IllegalArgumentException
+syn keyword ktType IllegalStateException IndexOutOfBoundsException IndexedValue Int IntArray IntIterator IntProgression IntRange InvocationKind Iterable Iterator JsExport JsName
+syn keyword ktType JvmDefault JvmDefaultWithCompatibility JvmDefaultWithoutCompatibility JvmField JvmInline JvmMultifileClass JvmName JvmOverloads JvmRecord JvmSerializableLambda
+syn keyword ktType JvmStatic JvmSuppressWildcards JvmSynthetic JvmWildcard KAnnotatedElement KCallable KClass KClassifier KDeclarationContainer KFunction KMutableProperty
+syn keyword ktType KMutableProperty0 KMutableProperty1 KMutableProperty2 KParameter KProperty KProperty0 KProperty1 KProperty2 KType KTypeParameter KTypeProjection KVariance
+syn keyword ktType KVisibility Key Kind KotlinNullPointerException KotlinReflectionNotSupportedError KotlinVersion Lazy LazyThreadSafetyMode Level LinkedHashMap LinkedHashSet List
+syn keyword ktType ListIterator Long LongArray LongIterator LongProgression LongRange Map MatchGroup MatchGroupCollection MatchNamedGroupCollection MatchResult Metadata Monotonic
+syn keyword ktType MustBeDocumented MutableCollection MutableEntry MutableIterable MutableIterator MutableList MutableListIterator MutableMap MutableSet NoSuchElementException
+syn keyword ktType NoSuchFileException NoWhenBranchMatchedException NotImplementedError Nothing NullPointerException Number NumberFormatException ObjCName ObservableProperty
+syn keyword ktType OnErrorAction OnErrorResult OpenEndRange OptIn OptionalExpectation OverloadResolutionByLambdaReturnType Pair ParameterName PathWalkOption
+syn keyword ktType PropertyDelegateProvider PublishedApi PurelyImplements Random RandomAccess ReadOnlyProperty ReadWriteProperty RefinesInSwift Regex RegexOption Repeatable
+syn keyword ktType ReplaceWith RequiresOptIn RestrictsSuspension Result Retention Returns ReturnsNotNull RuntimeException Sequence SequenceScope Set Setter SharedImmutable Short
+syn keyword ktType ShortArray ShortIterator ShouldRefineInSwift SimpleEffect SinceKotlin Strictfp String StringBuilder SubclassOptInRequired Suppress Synchronized Target
+syn keyword ktType TestTimeSource ThreadLocal Throwable Throws TimeMark TimeSource TimedValue Transient Triple TypeCastException Typography UByte UByteArray UInt UIntArray
+syn keyword ktType UIntProgression UIntRange ULong ULongArray ULongProgression ULongRange UShort UShortArray UninitializedPropertyAccessException Unit UnsafeVariance
+syn keyword ktType UnsupportedOperationException ValueTimeMark Volatile WithComparableMarks
+" }}}
+
+syn keyword ktModifier annotation companion enum inner abstract final open override sealed vararg dynamic expect actual suspend
+syn keyword ktStructure class object interface typealias fun val var constructor init
+
+syn keyword ktReservedKeyword typeof
+
+syn keyword ktBoolean true false
+syn keyword ktConstant null
+
+syn keyword ktModifier reified external inline noinline crossinline
+
+syn match ktModifier "\v<data>\ze\@=.*<(class|object)>"
+syn match ktModifier "\v<value>\ze\@=.*<class>"
+syn match ktModifier "\v<(tailrec|operator|infix)>\ze\@=.*<fun>"
+syn match ktModifier "\v<const>\ze\@=.*<val>"
+syn match ktModifier "\v<lateinit>\ze\@=.*<var>"
+syn match ktModifier "\v<(internal|private|protected|public)>\ze\@=.*<(class|object|interface|typealias|fun|val|var|constructor|get|set)>"
+
+syn match ktOperator "\v\?:|::|\<\=? | \>\=?|[!=]\=\=?|<as>\??|[-*+/%]\=?|[!&|]"
+
+syn keyword ktTodo TODO FIXME XXX contained
+syn match ktShebang "\v^#!.*$"
+syn match ktLineComment "\v//.*$" contains=ktTodo,@Spell
+syn region ktComment matchgroup=ktCommentMatchGroup start="/\*" end="\*/" contains=ktComment,ktTodo,@Spell
+
+syn region ktDocComment start="/\*\*" end="\*/" contains=ktDocTag,ktTodo,@Spell
+syn match ktDocTag "\v\@(author|constructor|receiver|return|since|suppress)>" contained
+syn match ktDocTag "\v\@(exception|param|property|throws|see|sample)>\s*\S+" contains=ktDocTagParam contained
+syn match ktDocTagParam "\v(\s|\[)\S+" contained
+syn match ktComment "/\*\*/"
+
+syn match ktSpecialCharError "\v\\." contained
+syn match ktSpecialChar "\v\\([tbnr'"$\\]|u\x{4})" contained
+syn region ktString start='"' skip='\\"' end='"' contains=ktSimpleInterpolation,ktComplexInterpolation,ktSpecialChar,ktSpecialCharError,@Spell
+syn region ktString start='"""' end='""""*' contains=ktSimpleInterpolation,ktComplexInterpolation,@Spell
+syn match ktCharacter "\v'[^']*'" contains=ktSpecialChar,ktSpecialCharError
+syn match ktCharacter "\v'\\''" contains=ktSpecialChar
+syn match ktCharacter "\v'[^\\]'"
+
+syn match ktAnnotation "\v(\w)@<!\@[[:alnum:]_.]*(:[[:alnum:]_.]*)?"
+syn match ktLabel "\v\w+\@"
+syn match ktLabel "\v(\w)@<=\@\w+"
+
+syn match ktSimpleInterpolation "\v\$\h\w*" contained
+syn region ktComplexInterpolation matchgroup=ktComplexInterpolationBrace start="\v\$\{" end="\v\}" contains=ALLBUT,ktSimpleInterpolation,ktTodo,ktSpecialCharError,ktSpecialChar,ktDocTag,ktDocTagParam
+
+syn match ktNumber "\v<\d+[_[:digit:]]*(uL?|UL?|[LFf])?"
+syn match ktNumber "\v<0[Xx]\x+[_[:xdigit:]]*(uL?|UL?|L)?"
+syn match ktNumber "\v<0[Bb][01]+[_01]*(uL?|UL?|L)?"
+syn match ktFloat "\v<\d*(\d[eE][-+]?\d+|\.\d+([eE][-+]?\d+)?)[Ff]?"
+
+syn match ktEscapedName "\v`.*`"
+
+syn match ktExclExcl "!!"
+syn match ktArrow "->"
+
+syn region ktFold start="{" end="}" transparent fold
+
+exec "syntax sync ccomment ktComment minlines=10"
+
+hi def link ktStatement Statement
+hi def link ktConditional Conditional
+hi def link ktRepeat Repeat
+hi def link ktOperator Operator
+hi def link ktKeyword Keyword
+hi def link ktException Exception
+hi def link ktReservedKeyword Error
+
+hi def link ktInclude Include
+
+hi def link ktType Type
+hi def link ktModifier StorageClass
+hi def link ktStructure Structure
+hi def link ktTypedef Typedef
+
+hi def link ktBoolean Boolean
+hi def link ktConstant Constant
+
+hi def link ktTodo Todo
+hi def link ktShebang Comment
+hi def link ktLineComment Comment
+hi def link ktComment Comment
+hi def link ktCommentMatchGroup Comment
+hi def link ktDocComment Comment
+hi def link ktDocTag Special
+hi def link ktDocTagParam Identifier
+
+hi def link ktSpecialChar SpecialChar
+hi def link ktSpecialCharError Error
+hi def link ktString String
+hi def link ktCharacter Character
+
+hi def link ktAnnotation Identifier
+hi def link ktLabel Identifier
+
+hi def link ktSimpleInterpolation Identifier
+hi def link ktComplexInterpolationBrace Identifier
+
+hi def link ktNumber Number
+hi def link ktFloat Float
+
+hi def link ktExclExcl Special
+hi def link ktArrow Structure
+
+let b:current_syntax = 'kotlin'
+
+" vim:foldmethod=marker
diff --git a/src/nvim/api/keysets.h b/src/nvim/api/keysets.h
index 4e5e7af619..736ca9ce07 100644
--- a/src/nvim/api/keysets.h
+++ b/src/nvim/api/keysets.h
@@ -112,6 +112,7 @@ typedef struct {
String footer_pos;
String style;
Boolean noautocmd;
+ Boolean fixed;
} Dict(float_config);
typedef struct {
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 70c97be984..0ea2310042 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -833,8 +833,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
bool was_space = false;
for (size_t i = 0; i < ncells; i++) {
repeat++;
- if (i == ncells - 1 || attrs[i] != attrs[i + 1]
- || strcmp(chunk[i], chunk[i + 1]) != 0) {
+ if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
@@ -859,7 +858,9 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
nelem++;
mpack_array(buf, csize);
- mpack_str(buf, chunk[i]);
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ mpack_str(buf, sc_buf);
if (csize >= 2) {
mpack_uint(buf, (uint32_t)attrs[i]);
if (csize >= 3) {
@@ -869,7 +870,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
data->ncells_pending += MIN(repeat, 2);
last_hl = attrs[i];
repeat = 0;
- was_space = strequal(chunk[i], " ");
+ was_space = chunk[i] == schar_from_ascii(' ');
}
}
// If the last chunk was all spaces, add a clearing chunk even if there are
@@ -893,8 +894,10 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
remote_ui_highlight_set(ui, attrs[i]);
- remote_ui_put(ui, chunk[i]);
- if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ remote_ui_put(ui, sc_buf);
+ if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
data->client_col = -1; // force cursor update
}
}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 916409b973..0a94b8aafc 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1947,7 +1947,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
}
ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
- ADD_C(ret, CSTR_AS_OBJ((char *)g->chars[off]));
+ char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false);
+ schar_get(sc_buf, g->chars[off]);
+ ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
@@ -1963,6 +1965,11 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+void nvim__invalidate_glyph_cache(void)
+{
+ schar_cache_clear_force();
+}
+
Object nvim__unpack(String str, Error *err)
FUNC_API_FAST
{
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 35b9e73d81..ebdbd896a5 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -16,7 +16,7 @@
#include "nvim/drawscreen.h"
#include "nvim/extmark_defs.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
@@ -163,6 +163,8 @@
/// - noautocmd: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
+/// - fixed: If true when anchor is NW or SW, the float window
+/// would be kept fixed even if the window would be truncated.
///
/// @param[out] err Error details, if any
///
@@ -351,7 +353,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
for (size_t i = 0; i < 8; i++) {
Array tuple = ARRAY_DICT_INIT;
- String s = cstrn_to_string(config->border_chars[i], sizeof(schar_T));
+ String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE);
int hi_id = config->border_hl_ids[i];
char *hi_name = syn_id2name(hi_id);
@@ -523,7 +525,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
- schar_T chars[8];
+ char chars[8][MAX_SCHAR_SIZE];
bool shadow_color;
} defaults[] = {
{ "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
@@ -534,7 +536,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ NULL, { { NUL } }, false },
};
- schar_T *chars = fconfig->border_chars;
+ char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars;
int *hl_ids = fconfig->border_hl_ids;
fconfig->border = true;
@@ -848,6 +850,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
fconfig->noautocmd = config->noautocmd;
}
+ if (HAS_KEY_X(config, fixed)) {
+ fconfig->fixed = config->fixed;
+ }
+
return true;
#undef HAS_KEY_X
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 3f55dbbc00..ff0fca1a56 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -954,7 +954,7 @@ typedef struct {
WinStyle style;
bool border;
bool shadow;
- schar_T border_chars[8];
+ char border_chars[8][MAX_SCHAR_SIZE];
int border_hl_ids[8];
int border_attr[8];
bool title;
@@ -966,6 +966,7 @@ typedef struct {
VirtText footer_chunks;
int footer_width;
bool noautocmd;
+ bool fixed;
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
@@ -975,7 +976,8 @@ typedef struct {
.focusable = true, \
.zindex = kZIndexFloatDefault, \
.style = kWinStyleUnused, \
- .noautocmd = false })
+ .noautocmd = false, \
+ .fixed = false })
// Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums().
@@ -1241,6 +1243,8 @@ struct window_S {
// this window, w_allbuf_opt is for all buffers in this window.
winopt_T w_onebuf_opt;
winopt_T w_allbuf_opt;
+ // transform a pointer to a "onebuf" option into a "allbuf" option
+#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T))
// A few options have local flags for P_INSECURE.
uint32_t w_p_stl_flags; // flags for 'statusline'
@@ -1256,9 +1260,6 @@ struct window_S {
int w_briopt_list; // additional indent for lists
int w_briopt_vcol; // indent for specific column
- // transform a pointer to a "onebuf" option into a "allbuf" option
-#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T))
-
long w_scbind_pos;
ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary.
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 811cfc1eb2..8b4786a98e 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -106,6 +106,8 @@ typedef struct {
int c_extra; ///< extra chars, all the same
int c_final; ///< final char, mandatory if set
+ int n_closing; ///< number of chars in fdc which will be closing
+
bool extra_for_extmark; ///< n_extra set for inline virtual text
// saved "extra" items for when draw_state becomes WL_LINE (again)
@@ -221,11 +223,11 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b
if (*p == TAB) {
cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
for (int c = 0; c < cells; c++) {
- schar_from_ascii(dest[c], ' ');
+ dest[c] = schar_from_ascii(' ');
}
goto done;
} else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) {
- schar_from_ascii(dest[0], *p);
+ dest[0] = schar_from_ascii(*p);
s->prev_c = u8c;
} else {
if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
@@ -252,10 +254,10 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b
} else {
s->prev_c = u8c;
}
- schar_from_cc(dest[0], u8c, u8cc);
+ dest[0] = schar_from_cc(u8c, u8cc);
}
if (cells > 1) {
- dest[1][0] = 0;
+ dest[1] = 0;
}
done:
s->p += c_len;
@@ -348,17 +350,17 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
max_col - col, false, vcol);
// If we failed to emit a char, we still need to put a space and advance.
if (cells < 1) {
- schar_from_ascii(linebuf_char[col], ' ');
+ linebuf_char[col] = schar_from_ascii(' ');
cells = 1;
}
for (int c = 0; c < cells; c++) {
linebuf_attr[col++] = attr;
}
- if (col < max_col && linebuf_char[col][0] == 0) {
+ if (col < max_col && linebuf_char[col] == 0) {
// If the left half of a double-width char is overwritten,
// change the right half to a space so that grid redraws properly,
// but don't advance the current column.
- schar_from_ascii(linebuf_char[col], ' ');
+ linebuf_char[col] = schar_from_ascii(' ');
}
vcol += cells;
}
@@ -384,7 +386,8 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv)
// Allocate a buffer, "wlv->extra[]" may already be in use.
xfree(wlv->p_extra_free);
wlv->p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1);
- wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum);
+ wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum,
+ &wlv->n_closing);
wlv->p_extra_free[wlv->n_extra] = NUL;
wlv->p_extra = wlv->p_extra_free;
wlv->c_extra = NUL;
@@ -405,7 +408,7 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv)
///
/// Assume monocell characters
/// @return number of chars added to \param p
-size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
+size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing)
{
int i = 0;
int level;
@@ -447,16 +450,23 @@ size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
}
}
+ int n_closing_val = i;
+
if (closed) {
if (symbol != 0) {
// rollback previous write
char_counter -= (size_t)len;
memset(&p[char_counter], ' ', (size_t)len);
+ n_closing_val--;
}
len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
char_counter += (size_t)len;
}
+ if (n_closing) {
+ *n_closing = n_closing_val;
+ }
+
return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
}
@@ -2737,7 +2747,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.col += n;
} else {
// Add a blank character to highlight.
- schar_from_ascii(linebuf_char[wlv.off], ' ');
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
}
if (area_attr == 0 && !has_fold) {
// Use attributes from match with highest priority among
@@ -2839,7 +2849,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
int col_stride = wp->w_p_rl ? -1 : 1;
while (wp->w_p_rl ? wlv.col >= 0 : wlv.col < grid->cols) {
- schar_from_ascii(linebuf_char[wlv.off], ' ');
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
linebuf_vcol[wlv.off] = MAXCOL;
wlv.col += col_stride;
if (draw_color_col) {
@@ -2873,7 +2883,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// logical line
int n = wp->w_p_rl ? -1 : 1;
while (wlv.col >= 0 && wlv.col < grid->cols) {
- schar_from_ascii(linebuf_char[wlv.off], ' ');
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol];
linebuf_vcol[wlv.off] = wlv.vcol;
wlv.off += n;
@@ -2973,9 +2983,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.col--;
}
if (mb_utf8) {
- schar_from_cc(linebuf_char[wlv.off], mb_c, u8cc);
+ linebuf_char[wlv.off] = schar_from_cc(mb_c, u8cc);
} else {
- schar_from_ascii(linebuf_char[wlv.off], (char)c);
+ linebuf_char[wlv.off] = schar_from_ascii((char)c);
}
if (multi_attr) {
linebuf_attr[wlv.off] = multi_attr;
@@ -2986,12 +2996,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
linebuf_vcol[wlv.off] = wlv.vcol;
+ if (wlv.draw_state == WL_FOLD) {
+ linebuf_vcol[wlv.off] = -2;
+ if (wlv.n_closing > 0) {
+ linebuf_vcol[wlv.off] = -3;
+ wlv.n_closing--;
+ }
+ }
+
if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns.
wlv.off++;
wlv.col++;
// UTF-8: Put a 0 in the second screen char.
- linebuf_char[wlv.off][0] = 0;
+ linebuf_char[wlv.off] = 0;
linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1];
if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) {
wlv.vcol++;
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index f71a47a596..e3e9fc8170 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -444,6 +444,15 @@ int update_screen(void)
display_tick++; // let syntax code know we're in a next round of
// display updating
+ // glyph cache full, very rare
+ if (schar_cache_clear_if_full()) {
+ // must use CLEAR, as the contents of screen buffers cannot be
+ // compared to their previous state here.
+ // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs)
+ // we need to revalidate these here as well!
+ type = MAX(type, UPD_CLEAR);
+ }
+
// Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now.
if (msg_did_scroll) {
@@ -736,7 +745,10 @@ static void win_redr_border(win_T *wp)
ScreenGrid *grid = &wp->w_grid_alloc;
- schar_T *chars = wp->w_float_config.border_chars;
+ schar_T chars[8];
+ for (int i = 0; i < 8; i++) {
+ chars[i] = schar_from_str(wp->w_float_config.border_chars[i]);
+ }
int *attrs = wp->w_float_config.border_attr;
int *adj = wp->w_border_adj;
@@ -770,7 +782,7 @@ static void win_redr_border(win_T *wp)
grid_puts_line_flush(false);
}
if (adj[1]) {
- int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
+ int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
grid_puts_line_start(grid, i + adj[0]);
grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]);
grid_puts_line_flush(false);
@@ -784,7 +796,7 @@ static void win_redr_border(win_T *wp)
}
for (int i = 0; i < icol; i++) {
- int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
+ int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]);
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index a279b6d051..21c7cdee7d 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1271,8 +1271,11 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
/// it in "*cp". Doesn't give error messages.
int eval_foldexpr(win_T *wp, int *cp)
{
+ const sctx_T saved_sctx = current_sctx;
const bool use_sandbox = was_set_insecurely(wp, "foldexpr", OPT_LOCAL);
+
char *arg = wp->w_p_fde;
+ current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx;
emsg_off++;
if (use_sandbox) {
@@ -1309,6 +1312,7 @@ int eval_foldexpr(win_T *wp, int *cp)
}
textlock--;
clear_evalarg(&EVALARG_EVALUATE, NULL);
+ current_sctx = saved_sctx;
return (int)retval;
}
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index fa7f270172..cf6cd2f04e 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -17,6 +17,7 @@
#include "nvim/arabic.h"
#include "nvim/buffer_defs.h"
+#include "nvim/drawscreen.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -36,6 +37,15 @@
// Per-cell attributes
static size_t linebuf_size = 0;
+// Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string.
+// Then it instead stores an index into glyph_cache.keys[] which is a flat char array.
+// The hash part is used by schar_from_buf() to quickly lookup glyphs which already
+// has been interned. schar_get() should used to convert a schar_T value
+// back to a string buffer.
+//
+// The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL).
+static Set(glyph) glyph_cache = SET_INIT;
+
/// Determine if dedicated window grid should be used or the default_grid
///
/// If UI did not request multigrid support, draw all windows on the
@@ -56,25 +66,119 @@ 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.
-int schar_from_cc(char *p, int c, int u8cc[MAX_MCO])
+schar_T schar_from_cc(int c, int u8cc[MAX_MCO])
{
- int len = utf_char2bytes(c, p);
+ 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], p + len);
+ 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) {
+ return 0;
+ }
+ return schar_from_buf(str, strlen(str));
+}
+
+/// @param buf need not be NUL terminated, but may not contain embedded NULs.
+///
+/// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte)
+schar_T schar_from_buf(const char *buf, size_t len)
+{
+ assert(len < MAX_SCHAR_SIZE);
+ if (len <= 4) {
+ schar_T sc = 0;
+ memcpy((char *)&sc, buf, len);
+ return sc;
+ } else {
+ String str = { .data = (char *)buf, .size = len };
+
+ MHPutStatus status;
+ uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status);
+ assert(idx < 0xFFFFFF);
+#ifdef ORDER_BIG_ENDIAN
+ return idx + ((uint32_t)0xFF << 24);
+#else
+ return 0xFF + (idx << 8);
+#endif
+ }
+}
+
+/// Check if cache is full, and if it is, clear it.
+///
+/// This should normally only be called in update_screen()
+///
+/// @return true if cache was clered, and all your screen buffers now are hosed
+/// and you need to use UPD_CLEAR
+bool schar_cache_clear_if_full(void)
+{
+ // note: critical max is really (1<<24)-1. This gives us some marginal
+ // until next time update_screen() is called
+ if (glyph_cache.h.n_keys > (1<<21)) {
+ set_clear(glyph, &glyph_cache);
+ return true;
+ }
+ return false;
+}
+
+/// For testing. The condition in schar_cache_clear_force is hard to
+/// reach, so this function can be used to force a cache clear in a test.
+void schar_cache_clear_force(void)
+{
+ set_clear(glyph, &glyph_cache);
+ must_redraw = UPD_CLEAR;
+}
+
+bool schar_high(schar_T sc)
+{
+#ifdef ORDER_BIG_ENDIAN
+ return ((sc & 0xFF000000) == 0xFF000000);
+#else
+ return ((sc & 0xFF) == 0xFF);
+#endif
+}
+
+void schar_get(char *buf_out, schar_T sc)
+{
+ if (schar_high(sc)) {
+#ifdef ORDER_BIG_ENDIAN
+ uint32_t idx = sc & (0x00FFFFFF);
+#else
+ uint32_t idx = sc >> 8;
+#endif
+ if (idx >= glyph_cache.h.n_keys) {
+ abort();
+ }
+ xstrlcpy(buf_out, &glyph_cache.keys[idx], 32);
+ } else {
+ memcpy(buf_out, (char *)&sc, 4);
+ buf_out[4] = NUL;
}
- p[len] = 0;
- return len;
}
+/// @return ascii char or NUL if not ascii
+char schar_get_ascii(schar_T sc)
+{
+#ifdef ORDER_BIG_ENDIAN
+ return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL;
+#else
+ return (sc < 0x80) ? (char)sc : NUL;
+#endif
+}
/// clear a line in the grid starting at "off" until "width" characters
/// are cleared.
void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
{
for (int col = 0; col < width; col++) {
- schar_from_ascii(grid->chars[off + (size_t)col], ' ');
+ grid->chars[off + (size_t)col] = schar_from_ascii(' ');
}
int fill = valid ? 0 : -1;
(void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
@@ -93,7 +197,7 @@ bool grid_invalid_row(ScreenGrid *grid, int row)
static int line_off2cells(schar_T *line, size_t off, size_t max_off)
{
- return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1;
+ return (off + 1 < max_off && line[off + 1] == 0) ? 2 : 1;
}
/// Return number of display cells for char at grid->chars[off].
@@ -124,7 +228,7 @@ int grid_fix_col(ScreenGrid *grid, int col, int row)
col += coloff;
if (grid->chars != NULL && col > 0
- && grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) {
+ && grid->chars[grid->line_offset[row] + (size_t)col] == 0) {
return col - 1 - coloff;
}
return col - coloff;
@@ -155,7 +259,7 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp)
if (attrp != NULL) {
*attrp = grid->attrs[off];
}
- schar_copy(bytes, grid->chars[off]);
+ schar_get(bytes, grid->chars[off]);
}
/// put string '*text' on the window grid at position 'row' and 'col', with
@@ -185,12 +289,12 @@ void grid_puts_line_start(ScreenGrid *grid, int row)
put_dirty_grid = grid;
}
-void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr)
+void grid_put_schar(ScreenGrid *grid, int row, int col, schar_T schar, int attr)
{
assert(put_dirty_row == row);
size_t off = grid->line_offset[row] + (size_t)col;
- if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) {
- schar_copy(grid->chars[off], schar);
+ if (grid->attrs[off] != attr || grid->chars[off] != schar || rdb_flags & RDB_NODELTA) {
+ grid->chars[off] = schar;
grid->attrs[off] = attr;
put_dirty_first = MIN(put_dirty_first, col);
@@ -293,10 +397,12 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int
}
schar_T buf;
- schar_from_cc(buf, u8c, u8cc);
+ // TODO(bfredl): why not just keep the original byte sequence. arabshape is
+ // an edge case, treat it as such..
+ buf = schar_from_cc(u8c, u8cc);
- int need_redraw = schar_cmp(grid->chars[off], buf)
- || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0)
+ int need_redraw = grid->chars[off] != buf
+ || (mbyte_cells == 2 && grid->chars[off + 1] != 0)
|| grid->attrs[off] != attr
|| exmode_active
|| rdb_flags & RDB_NODELTA;
@@ -320,15 +426,15 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int
// 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 > 0 && grid->chars[off][0] == 0) {
- schar_from_ascii(grid->chars[off - 1], '>');
+ if (ptr == text && col > 0 && grid->chars[off] == 0) {
+ grid->chars[off - 1] = schar_from_ascii('>');
}
- schar_copy(grid->chars[off], buf);
+ grid->chars[off] = buf;
grid->attrs[off] = attr;
grid->vcols[off] = -1;
if (mbyte_cells == 2) {
- grid->chars[off + 1][0] = 0;
+ grid->chars[off + 1] = 0;
grid->attrs[off + 1] = attr;
grid->vcols[off + 1] = -1;
}
@@ -429,12 +535,12 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
int dirty_last = 0;
int col = start_col;
- schar_from_char(sc, c1);
+ sc = schar_from_char(c1);
size_t lineoff = grid->line_offset[row];
for (col = start_col; col < end_col; col++) {
size_t off = lineoff + (size_t)col;
- if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
- schar_copy(grid->chars[off], sc);
+ if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
+ grid->chars[off] = sc;
grid->attrs[off] = attr;
if (dirty_first == INT_MAX) {
dirty_first = col;
@@ -443,7 +549,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
}
grid->vcols[off] = -1;
if (col == start_col) {
- schar_from_char(sc, c2);
+ sc = schar_from_char(c2);
}
}
if (dirty_last > dirty_first) {
@@ -483,11 +589,10 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols)
{
return (cols > 0
- && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to])
+ && ((linebuf_char[off_from] != grid->chars[off_to]
|| linebuf_attr[off_from] != grid->attrs[off_to]
|| (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1
- && schar_cmp(linebuf_char[off_from + 1],
- grid->chars[off_to + 1])))
+ && linebuf_char[off_from + 1] != grid->chars[off_to + 1]))
|| rdb_flags & RDB_NODELTA));
}
@@ -544,7 +649,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (wp->w_p_nu && wp->w_p_rnu) {
// do not overwrite the line number, change "123 text" to
// "123<<<xt".
- while (skip < max_off_from && ascii_isdigit(*linebuf_char[off])) {
+ while (skip < max_off_from && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
off++;
skip++;
}
@@ -554,9 +659,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (line_off2cells(linebuf_char, off, max_off_from) > 1) {
// When the first half of a double-width character is
// overwritten, change the second half to a space.
- schar_from_ascii(linebuf_char[off + 1], ' ');
+ linebuf_char[off + 1] = schar_from_ascii(' ');
}
- schar_from_ascii(linebuf_char[off], '<');
+ linebuf_char[off] = schar_from_ascii('<');
linebuf_attr[off] = HL_ATTR(HLF_AT);
off++;
}
@@ -565,8 +670,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (rlflag) {
// Clear rest first, because it's left of the text.
if (clear_width > 0) {
- while (col <= endcol && grid->chars[off_to][0] == ' '
- && grid->chars[off_to][1] == NUL
+ while (col <= endcol && grid->chars[off_to] == schar_from_ascii(' ')
&& grid->attrs[off_to] == bg_attr) {
off_to++;
col++;
@@ -619,9 +723,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
clear_next = true;
}
- schar_copy(grid->chars[off_to], linebuf_char[off_from]);
+ grid->chars[off_to] = linebuf_char[off_from];
if (char_cells == 2) {
- schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]);
+ grid->chars[off_to + 1] = linebuf_char[off_from + 1];
}
grid->attrs[off_to] = linebuf_attr[off_from];
@@ -645,7 +749,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (clear_next) {
// Clear the second half of a double-wide character of which the left
// half was overwritten with a single-wide character.
- schar_from_ascii(grid->chars[off_to], ' ');
+ grid->chars[off_to] = schar_from_ascii(' ');
end_dirty++;
}
@@ -654,12 +758,10 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
// blank out the rest of the line
// TODO(bfredl): we could cache winline widths
while (col < clear_width) {
- if (grid->chars[off_to][0] != ' '
- || grid->chars[off_to][1] != NUL
+ if (grid->chars[off_to] != schar_from_ascii(' ')
|| grid->attrs[off_to] != bg_attr
|| rdb_flags & RDB_NODELTA) {
- grid->chars[off_to][0] = ' ';
- grid->chars[off_to][1] = NUL;
+ grid->chars[off_to] = schar_from_ascii(' ');
grid->attrs[off_to] = bg_attr;
if (start_dirty == -1) {
start_dirty = col;
diff --git a/src/nvim/grid.h b/src/nvim/grid.h
index 0db97345d1..9cdc6c6677 100644
--- a/src/nvim/grid.h
+++ b/src/nvim/grid.h
@@ -33,30 +33,25 @@ EXTERN colnr_T *linebuf_vcol INIT(= NULL);
// screen grid.
/// Put a ASCII character in a screen cell.
-static inline void schar_from_ascii(char *p, const char c)
-{
- p[0] = c;
- p[1] = 0;
-}
+///
+/// If `x` is a compile time constant, schar_from_ascii(x) will also be.
+/// But the specific value varies per plattform.
+#ifdef ORDER_BIG_ENDIAN
+# define schar_from_ascii(x) ((schar_T)((x) << 24))
+#else
+# define schar_from_ascii(x) ((schar_T)(x))
+#endif
/// Put a unicode character in a screen cell.
-static inline int schar_from_char(char *p, int c)
-{
- int len = utf_char2bytes(c, p);
- p[len] = NUL;
- return len;
-}
-
-/// compare the contents of two screen cells.
-static inline int schar_cmp(char *sc1, char *sc2)
-{
- return strncmp(sc1, sc2, sizeof(schar_T));
-}
-
-/// copy the contents of screen cell `sc2` into cell `sc1`
-static inline void schar_copy(char *sc1, char *sc2)
+static inline schar_T schar_from_char(int c)
{
- xstrlcpy(sc1, sc2, sizeof(schar_T));
+ schar_T sc = 0;
+ if (c >= 0x200000) {
+ // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences
+ c = 0xFFFD;
+ }
+ utf_char2bytes(c, (char *)&sc);
+ return sc;
}
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 4ad7d4cdb4..b0d1dafd29 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -9,9 +9,13 @@
#include "nvim/types.h"
#define MAX_MCO 6 // fixed value for 'maxcombine'
+// Includes final NUL. at least 4*(MAX_MCO+1)+1
+#define MAX_SCHAR_SIZE 32
-// The characters and attributes drawn on grids.
-typedef char schar_T[(MAX_MCO + 1) * 4 + 1];
+// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianess)
+// otherwise it must be a UTF-8 string of length maximum 4 (no NUL when n=4)
+
+typedef uint32_t schar_T;
typedef int sattr_T;
enum {
@@ -41,6 +45,9 @@ enum {
///
/// vcols[] contains the virtual columns in the line. -1 means not available
/// (below last line), MAXCOL means after the end of the line.
+/// -2 or -3 means in fold column and a mouse click should:
+/// -2: open a fold
+/// -3: close a fold
///
/// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start
/// of line 'n'. These offsets are in general not linear, as full screen scrolling
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index df4bffdac3..3728db31d8 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -86,7 +86,7 @@ static int get_attr_entry(HlEntry entry)
}
retry: {}
- MhPutStatus status;
+ MHPutStatus status;
uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status);
if (status == kMHExisting) {
return (int)k;
diff --git a/src/nvim/map.c b/src/nvim/map.c
index e1d0646083..54f1969df8 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -47,25 +47,6 @@ static inline uint32_t hash_cstr_t(const char *s)
#define equal_cstr_t strequal
-// when used as a key, String doesn't need to be NUL terminated,
-// and can also contain embedded NUL:s as part of the data.
-static inline uint32_t hash_String(String s)
-{
- uint32_t h = 0;
- for (size_t i = 0; i < s.size; i++) {
- h = (h << 5) - h + (uint8_t)s.data[i];
- }
- return h;
-}
-
-static inline bool equal_String(String a, String b)
-{
- if (a.size != b.size) {
- return false;
- }
- return memcmp(a.data, b.data, a.size) == 0;
-}
-
static inline uint32_t hash_HlEntry(HlEntry ae)
{
const uint8_t *data = (const uint8_t *)&ae;
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 23a5ea36a3..2d5517c552 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -18,6 +18,25 @@
typedef const char *cstr_t;
typedef void *ptr_t;
+// when used as a key, String doesn't need to be NUL terminated,
+// and can also contain embedded NUL:s as part of the data.
+static inline uint32_t hash_String(String s)
+{
+ uint32_t h = 0;
+ for (size_t i = 0; i < s.size; i++) {
+ h = (h << 5) - h + (uint8_t)s.data[i];
+ }
+ return h;
+}
+
+static inline bool equal_String(String a, String b)
+{
+ if (a.size != b.size) {
+ return false;
+ }
+ return memcmp(a.data, b.data, a.size) == 0;
+}
+
#define Set(type) Set_##type
#define Map(T, U) Map_##T##U
#define PMap(T) Map(T, ptr_t)
@@ -57,7 +76,7 @@ typedef enum {
kMHExisting = 0,
kMHNewKeyDidFit,
kMHNewKeyRealloc,
-} MhPutStatus;
+} MHPutStatus;
void mh_clear(MapHash *h);
void mh_realloc(MapHash *h, uint32_t n_min_buckets);
@@ -65,20 +84,22 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets);
// layer 1: key type specific defs
// This is all need for sets.
-#define KEY_DECLS(T) \
+#define MH_DECLS(T, K, K_query) \
typedef struct { \
MapHash h; \
- T *keys; \
+ K *keys; \
} Set(T); \
\
- uint32_t mh_find_bucket_##T(Set(T) *set, T key, bool put); \
- uint32_t mh_get_##T(Set(T) *set, T key); \
+ uint32_t mh_find_bucket_##T(Set(T) *set, K_query key, bool put); \
+ uint32_t mh_get_##T(Set(T) *set, K_query key); \
void mh_rehash_##T(Set(T) *set); \
- uint32_t mh_put_##T(Set(T) *set, T key, MhPutStatus *new); \
+ uint32_t mh_put_##T(Set(T) *set, K_query key, MHPutStatus *new); \
+
+#define KEY_DECLS(T) \
+ MH_DECLS(T, T, T) \
uint32_t mh_delete_##T(Set(T) *set, T *key); \
- \
static inline bool set_put_##T(Set(T) *set, T key, T **key_alloc) { \
- MhPutStatus status; \
+ MHPutStatus status; \
uint32_t k = mh_put_##T(set, key, &status); \
if (key_alloc) { \
*key_alloc = &set->keys[k]; \
@@ -120,6 +141,7 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets);
#define quasiquote(x, y) x##y
+MH_DECLS(glyph, char, String)
KEY_DECLS(int)
KEY_DECLS(cstr_t)
KEY_DECLS(ptr_t)
diff --git a/src/nvim/map_glyph_cache.c b/src/nvim/map_glyph_cache.c
new file mode 100644
index 0000000000..6dcbfe0532
--- /dev/null
+++ b/src/nvim/map_glyph_cache.c
@@ -0,0 +1,102 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// Specialized version of Set() where interned strings is stored in a compact,
+// NUL-separated char array.
+// `String key` lookup keys don't need to be NULL terminated, but they
+// must not contain embedded NUL:s. When reading a key from set->keys, they
+// are always NUL terminated, though. Thus, it is enough to store an index into
+// this array, and use strlen(), to retrive an interned key.
+
+#include "nvim/api/private/helpers.h"
+#include "nvim/map.h"
+
+uint32_t mh_find_bucket_glyph(Set(glyph) *set, String key, bool put)
+{
+ MapHash *h = &set->h;
+ uint32_t step = 0;
+ uint32_t mask = h->n_buckets - 1;
+ uint32_t k = hash_String(key);
+ uint32_t i = k & mask;
+ uint32_t last = i;
+ uint32_t site = put ? last : MH_TOMBSTONE;
+ while (!mh_is_empty(h, i)) {
+ if (mh_is_del(h, i)) {
+ if (site == last) {
+ site = i;
+ }
+ } else if (equal_String(cstr_as_string(&set->keys[h->hash[i] - 1]), key)) {
+ return i;
+ }
+ i = (i + (++step)) & mask;
+ if (i == last) {
+ abort();
+ }
+ }
+ if (site == last) {
+ site = i;
+ }
+ return site;
+}
+
+/// @return index into set->keys if found, MH_TOMBSTONE otherwise
+uint32_t mh_get_glyph(Set(glyph) *set, String key)
+{
+ if (set->h.n_buckets == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = mh_find_bucket_glyph(set, key, false);
+ return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE;
+}
+
+void mh_rehash_glyph(Set(glyph) *set)
+{
+ // assume the format of set->keys, i e NUL terminated strings
+ for (uint32_t k = 0; k < set->h.n_keys; k += (uint32_t)strlen(&set->keys[k]) + 1) {
+ uint32_t idx = mh_find_bucket_glyph(set, cstr_as_string(&set->keys[k]), true);
+ // there must be tombstones when we do a rehash
+ if (!mh_is_empty((&set->h), idx)) {
+ abort();
+ }
+ set->h.hash[idx] = k + 1;
+ }
+ set->h.n_occupied = set->h.size = set->h.n_keys;
+}
+
+uint32_t mh_put_glyph(Set(glyph) *set, String key, MHPutStatus *new)
+{
+ MapHash *h = &set->h;
+ // Might rehash ahead of time if "key" already existed. But it was
+ // going to happen soon anyway.
+ if (h->n_occupied >= h->upper_bound) {
+ mh_realloc(h, h->n_buckets + 1);
+ mh_rehash_glyph(set);
+ }
+
+ uint32_t idx = mh_find_bucket_glyph(set, key, true);
+
+ if (mh_is_either(h, idx)) {
+ h->size++;
+ h->n_occupied++;
+
+ uint32_t size = (uint32_t)key.size + 1; // NUL takes space
+ uint32_t pos = h->n_keys;
+ h->n_keys += size;
+ if (h->n_keys > h->keys_capacity) {
+ h->keys_capacity = MAX(h->keys_capacity * 2, 64);
+ set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(char));
+ *new = kMHNewKeyRealloc;
+ } else {
+ *new = kMHNewKeyDidFit;
+ }
+ memcpy(&set->keys[pos], key.data, key.size);
+ set->keys[pos + key.size] = NUL;
+ h->hash[idx] = pos + 1;
+ return pos;
+ } else {
+ *new = kMHExisting;
+ uint32_t pos = h->hash[idx] - 1;
+ assert(equal_String(cstr_as_string(&set->keys[pos]), key));
+ return pos;
+ }
+}
diff --git a/src/nvim/map_key_impl.c.h b/src/nvim/map_key_impl.c.h
index 7e7b2f74fe..4d060f5fb8 100644
--- a/src/nvim/map_key_impl.c.h
+++ b/src/nvim/map_key_impl.c.h
@@ -80,7 +80,7 @@ void KEY_NAME(mh_rehash_)(SET_TYPE *set)
/// if new item, indicates if keys[] was resized.
///
/// @return keys index
-uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MhPutStatus *new)
+uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MHPutStatus *new)
{
MapHash *h = &set->h;
// Might rehash ahead of time if "key" already existed. But it was
diff --git a/src/nvim/map_value_impl.c.h b/src/nvim/map_value_impl.c.h
index fffda280f0..8f07bd8fa7 100644
--- a/src/nvim/map_value_impl.c.h
+++ b/src/nvim/map_value_impl.c.h
@@ -28,7 +28,7 @@ VALUE_TYPE *MAP_NAME(map_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc
VALUE_TYPE *MAP_NAME(map_put_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc,
bool *new_item)
{
- MhPutStatus status;
+ MHPutStatus status;
uint32_t k = KEY_NAME(mh_put_)(&map->set, key, &status);
if (status != kMHExisting) {
if (status == kMHNewKeyRealloc) {
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index b8c80cadf5..e35385dd43 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -1849,7 +1849,6 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
int click_grid = mouse_grid;
int click_row = mouse_row;
int click_col = mouse_col;
- int mouse_char = ' ';
int max_row = Rows;
int max_col = Columns;
bool multigrid = ui_has(kUIMultigrid);
@@ -1864,7 +1863,6 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
if (wp && mouse_row >= 0 && mouse_row < max_row
&& mouse_col >= 0 && mouse_col < max_col) {
ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
- int fdc = win_fdccol_count(wp);
int use_row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
int use_col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
@@ -1901,22 +1899,12 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
// concealed characters.
*vcolp = col_from_screen;
}
-
- // Remember the character under the mouse, might be one of foldclose or
- // foldopen fillchars in the fold column.
- mouse_char = utf_ptr2char((char *)gp->chars[off]);
- }
-
- // Check for position outside of the fold column.
- if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc :
- click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
- mouse_char = ' ';
}
}
- if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
+ if (col_from_screen == -2) {
*flagsp |= MOUSE_FOLD_OPEN;
- } else if (mouse_char != ' ') {
+ } else if (col_from_screen == -3) {
*flagsp |= MOUSE_FOLD_CLOSE;
}
}
diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c
index c3b1022db2..37e32729cc 100644
--- a/src/nvim/msgpack_rpc/unpacker.c
+++ b/src/nvim/msgpack_rpc/unpacker.c
@@ -9,6 +9,7 @@
#include "mpack/conv.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
+#include "nvim/grid.h"
#include "nvim/macros.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel_defs.h"
@@ -497,13 +498,13 @@ redo:
if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) {
g->clear_width = repeat;
} else {
+ schar_T sc = schar_from_buf(cellbuf, cellsize);
for (int r = 0; r < repeat; r++) {
if (g->coloff >= (int)grid_line_buf_size) {
p->state = -1;
return false;
}
- memcpy(grid_line_buf_char[g->coloff], cellbuf, cellsize);
- grid_line_buf_char[g->coloff][cellsize] = NUL;
+ grid_line_buf_char[g->coloff] = sc;
grid_line_buf_attr[g->coloff++] = g->cur_attr;
}
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 4280d62a95..d730f247a9 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -4513,7 +4513,7 @@ static void nv_replace(cmdarg_T *cap)
// Visual mode "r"
if (VIsual_active) {
if (got_int) {
- reset_VIsual();
+ got_int = false;
}
if (had_ctrl_v) {
// Use a special (negative) number to make a difference between a
diff --git a/src/nvim/option.c b/src/nvim/option.c
index ae4e7c1fda..c34999ed32 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1915,6 +1915,9 @@ bool parse_winhl_opt(win_T *wp)
char *commap = xstrchrnul(hi, ',');
size_t len = (size_t)(commap - hi);
int hl_id = len ? syn_check_group(hi, len) : -1;
+ if (hl_id == 0) {
+ return false;
+ }
int hl_id_link = nlen ? syn_check_group(p, nlen) : 0;
HlAttrs attrs = HLATTRS_INIT;
@@ -1967,6 +1970,10 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
curbuf->b_p_script_ctx[indir & PV_MASK] = last_set;
} else if (indir & PV_WIN) {
curwin->w_p_script_ctx[indir & PV_MASK] = last_set;
+ if (both) {
+ // also setting the "all buffers" value
+ curwin->w_allbuf_opt.wo_script_ctx[indir & PV_MASK] = last_set;
+ }
}
}
}
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index abeb020645..763d30d4a2 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -256,9 +256,9 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
QUEUE_FOREACH(q, &args_q, {
ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
+ QUEUE_REMOVE(q);
xfree(arg_node->arg);
xfree(arg_node);
- QUEUE_REMOVE(q);
if (!QUEUE_EMPTY(&args_q)) {
xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 1d0a987780..14c7e56e97 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -59,11 +59,7 @@
#define un_Magic(x) ((x) + 256)
#define is_Magic(x) ((x) < 0)
-// We should define ftpr as a pointer to a function returning a pointer to
-// a function returning a pointer to a function ...
-// This is impossible, so we declare a pointer to a function returning a
-// pointer to a function returning void. This should work for all compilers.
-typedef void (*(*fptr_T)(int *, int))(void);
+typedef void (*fptr_T)(int *, int);
static int no_Magic(int x)
{
@@ -1494,34 +1490,14 @@ static inline char *cstrchr(const char *const s, const int c)
// regsub stuff //
////////////////////////////////////////////////////////////////
-// This stuff below really confuses cc on an SGI -- webb
-
-static fptr_T do_upper(int *d, int c)
+static void do_upper(int *d, int c)
{
*d = mb_toupper(c);
-
- return (fptr_T)NULL;
-}
-
-static fptr_T do_Upper(int *d, int c)
-{
- *d = mb_toupper(c);
-
- return (fptr_T)do_Upper;
}
-static fptr_T do_lower(int *d, int c)
+static void do_lower(int *d, int c)
{
*d = mb_tolower(c);
-
- return (fptr_T)NULL;
-}
-
-static fptr_T do_Lower(int *d, int c)
-{
- *d = mb_tolower(c);
-
- return (fptr_T)do_Lower;
}
/// regtilde(): Replace tildes in the pattern by the old pattern.
@@ -1886,16 +1862,16 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
} else if (vim_strchr("uUlLeE", (uint8_t)(*src))) {
switch (*src++) {
case 'u':
- func_one = (fptr_T)do_upper;
+ func_one = do_upper;
continue;
case 'U':
- func_all = (fptr_T)do_Upper;
+ func_all = do_upper;
continue;
case 'l':
- func_one = (fptr_T)do_lower;
+ func_one = do_lower;
continue;
case 'L':
- func_all = (fptr_T)do_Lower;
+ func_all = do_lower;
continue;
case 'e':
case 'E':
@@ -1954,11 +1930,13 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
} else {
c = utf_ptr2char(src - 1);
}
+
// Write to buffer, if copy is set.
if (func_one != NULL) {
- func_one = (fptr_T)(func_one(&cc, c));
+ func_one(&cc, c);
+ func_one = NULL;
} else if (func_all != NULL) {
- func_all = (fptr_T)(func_all(&cc, c));
+ func_all(&cc, c);
} else {
// just copy
cc = c;
@@ -2061,11 +2039,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
c = utf_ptr2char(s);
if (func_one != (fptr_T)NULL) {
- // Turbo C complains without the typecast
- func_one = (fptr_T)(func_one(&cc, c));
+ func_one(&cc, c);
+ func_one = NULL;
} else if (func_all != (fptr_T)NULL) {
- // Turbo C complains without the typecast
- func_all = (fptr_T)(func_all(&cc, c));
+ func_all(&cc, c);
} else { // just copy
cc = c;
}
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index 30c98a2f1e..4e000b8d29 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -1663,7 +1663,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
char *p = NULL;
if (fold) {
- size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM));
+ size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo,
+ (linenr_T)get_vim_var_nr(VV_LNUM), NULL);
stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1);
p = out_p;
p[n] = NUL;
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 0983667695..9fea6442a9 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -24,6 +24,7 @@
#include "nvim/event/signal.h"
#include "nvim/event/stream.h"
#include "nvim/globals.h"
+#include "nvim/grid.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
@@ -675,15 +676,15 @@ static void final_column_wrap(TUIData *tui)
/// It is undocumented, but in the majority of terminals and terminal emulators
/// printing at the right margin does not cause an automatic wrap until the
/// next character is printed, holding the cursor in place until then.
-static void print_cell(TUIData *tui, UCell *ptr)
+static void print_cell(TUIData *tui, char *buf, sattr_T attr)
{
UGrid *grid = &tui->grid;
if (!tui->immediate_wrap_after_last_column) {
// Printing the next character finally advances the cursor.
final_column_wrap(tui);
}
- update_attrs(tui, ptr->attr);
- out(tui, ptr->data, strlen(ptr->data));
+ update_attrs(tui, attr);
+ out(tui, buf, strlen(buf));
grid->col++;
if (tui->immediate_wrap_after_last_column) {
// Printing at the right margin immediately advances the cursor.
@@ -703,8 +704,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next)
return false;
}
}
- if (strlen(cell->data) > 1) {
- return false;
+ if (schar_get_ascii(cell->data) == 0) {
+ return false; // not ascii
}
cell++;
}
@@ -831,14 +832,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
{
UGrid *grid = &tui->grid;
- if (grid->row == -1 && cell->data[0] == NUL) {
+ if (grid->row == -1 && cell->data == NUL) {
// If cursor needs to repositioned and there is nothing to print, don't move cursor.
return;
}
cursor_goto(tui, row, col);
- bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data));
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, cell->data);
+ bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf));
if (is_ambiwidth && is_doublewidth) {
// Clear the two screen cells.
// If the character is single-width in the host terminal it won't change the second cell.
@@ -847,7 +850,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
cursor_goto(tui, row, col);
}
- print_cell(tui, cell);
+ print_cell(tui, buf, cell->attr);
if (is_ambiwidth) {
// Force repositioning cursor after printing an ambiguous-width character.
@@ -976,6 +979,8 @@ void tui_grid_clear(TUIData *tui, Integer g)
{
UGrid *grid = &tui->grid;
ugrid_clear(grid);
+ // safe to clear cache at this point
+ schar_cache_clear_if_full();
kv_size(tui->invalid_regions) = 0;
clear_region(tui, 0, tui->height, 0, tui->width, 0);
}
@@ -1273,7 +1278,7 @@ void tui_flush(TUIData *tui)
int clear_col;
for (clear_col = r.right; clear_col > 0; clear_col--) {
UCell *cell = &grid->cells[row][clear_col - 1];
- if (!(cell->data[0] == ' ' && cell->data[1] == NUL
+ if (!(cell->data == schar_from_ascii(' ')
&& cell->attr == clear_attr)) {
break;
}
@@ -1281,7 +1286,7 @@ void tui_flush(TUIData *tui)
UGRID_FOREACH_CELL(grid, row, r.left, clear_col, {
print_cell_at_pos(tui, row, curcol, cell,
- curcol < clear_col - 1 && (cell + 1)->data[0] == NUL);
+ curcol < clear_col - 1 && (cell + 1)->data == NUL);
});
if (clear_col < r.right) {
clear_region(tui, row, row + 1, clear_col, r.right, clear_attr);
@@ -1399,7 +1404,10 @@ void tui_screenshot(TUIData *tui, String path)
for (int i = 0; i < grid->height; i++) {
cursor_goto(tui, i, 0);
for (int j = 0; j < grid->width; j++) {
- print_cell(tui, &grid->cells[i][j]);
+ UCell cell = grid->cells[i][j];
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, cell.data);
+ print_cell(tui, buf, cell.attr);
}
}
flush_buf(tui);
@@ -1446,13 +1454,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
{
UGrid *grid = &tui->grid;
for (Integer c = startcol; c < endcol; c++) {
- memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T));
+ grid->cells[linerow][c].data = chunk[c - startcol];
assert((size_t)attrs[c - startcol] < kv_size(tui->attrs));
grid->cells[linerow][c].attr = attrs[c - startcol];
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, {
print_cell_at_pos(tui, (int)linerow, curcol, cell,
- curcol < endcol - 1 && (cell + 1)->data[0] == NUL);
+ curcol < endcol - 1 && (cell + 1)->data == NUL);
});
if (clearcol > endcol) {
@@ -1469,7 +1477,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
if (endcol != grid->width) {
// Print the last char of the row, if we haven't already done so.
- int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1;
+ int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1;
print_cell_at_pos(tui, (int)linerow, grid->width - size,
&grid->cells[linerow][grid->width - size], size == 2);
}
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 3a05223023..afc1b3f5fe 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -46,4 +46,10 @@ typedef enum {
typedef struct Decoration Decoration;
+#ifndef ORDER_BIG_ENDIAN
+# if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define ORDER_BIG_ENDIAN
+# endif
+#endif
+
#endif // NVIM_TYPES_H
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
index be1746983c..d3a1097aba 100644
--- a/src/nvim/ugrid.c
+++ b/src/nvim/ugrid.c
@@ -4,6 +4,7 @@
#include <assert.h>
#include <string.h>
+#include "nvim/grid.h"
#include "nvim/memory.h"
#include "nvim/ugrid.h"
@@ -79,8 +80,7 @@ static void clear_region(UGrid *grid, int top, int bot, int left, int right, sat
{
for (int row = top; row <= bot; row++) {
UGRID_FOREACH_CELL(grid, row, left, right + 1, {
- cell->data[0] = ' ';
- cell->data[1] = 0;
+ cell->data = schar_from_ascii(' ');
cell->attr = attr;
});
}
diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h
index a85a6fb4a8..1c73c43867 100644
--- a/src/nvim/ugrid.h
+++ b/src/nvim/ugrid.h
@@ -11,10 +11,8 @@ struct ugrid;
typedef struct ucell UCell;
typedef struct ugrid UGrid;
-#define CELLBYTES (sizeof(schar_T))
-
struct ucell {
- char data[CELLBYTES + 1];
+ schar_T data;
sattr_T attr;
};
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index e9b23d1298..fcb63801e5 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -55,7 +55,7 @@ static int msg_current_row = INT_MAX;
static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
-static schar_T msg_sep_char = { ' ', NUL };
+static schar_T msg_sep_char = schar_from_ascii(' ');
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
@@ -354,7 +354,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
grid = &msg_grid;
sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
for (int i = col; i < until; i++) {
- memcpy(linebuf[i - startcol], msg_sep_char, sizeof(*linebuf));
+ linebuf[i - startcol] = msg_sep_char;
attrbuf[i - startcol] = msg_sep_attr;
}
} else {
@@ -363,9 +363,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf));
memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf));
if (grid->comp_col + grid->cols > until
- && grid->chars[off + n][0] == NUL) {
- linebuf[until - 1 - startcol][0] = ' ';
- linebuf[until - 1 - startcol][1] = '\0';
+ && grid->chars[off + n] == NUL) {
+ linebuf[until - 1 - startcol] = schar_from_ascii(' ');
if (col == startcol && n == 1) {
skipstart = 0;
}
@@ -378,10 +377,10 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
for (int i = col - (int)startcol; i < until - startcol; i += width) {
width = 1;
// negative space
- bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL;
- if (i + 1 < endcol - startcol && bg_line[i + 1][0] == NUL) {
+ bool thru = linebuf[i] == schar_from_ascii(' ') && bg_line[i] != NUL;
+ if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) {
width = 2;
- thru &= strequal((char *)linebuf[i + 1], " ");
+ thru &= linebuf[i + 1] == schar_from_ascii(' ');
}
attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
if (width == 2) {
@@ -396,19 +395,18 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
// Tricky: if overlap caused a doublewidth char to get cut-off, must
// replace the visible half with a space.
- if (linebuf[col - startcol][0] == NUL) {
- linebuf[col - startcol][0] = ' ';
- linebuf[col - startcol][1] = NUL;
+ if (linebuf[col - startcol] == NUL) {
+ linebuf[col - startcol] = schar_from_ascii(' ');
if (col == endcol - 1) {
skipend = 0;
}
- } else if (col == startcol && n > 1 && linebuf[1][0] == NUL) {
+ } else if (col == startcol && n > 1 && linebuf[1] == NUL) {
skipstart = 0;
}
col = until;
}
- if (linebuf[endcol - startcol - 1][0] == NUL) {
+ if (linebuf[endcol - startcol - 1] == NUL) {
skipend = 0;
}
@@ -568,7 +566,7 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
if (scrolled && row > 0) {
msg_sep_row = (int)row - 1;
if (sep_char.data) {
- xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char));
+ msg_sep_char = schar_from_buf(sep_char.data, sep_char.size);
}
} else {
msg_sep_row = -1;
diff --git a/src/nvim/window.c b/src/nvim/window.c
index c0d399c4c4..1208494eaf 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1009,7 +1009,9 @@ void ui_ext_win_position(win_T *wp, bool validate)
comp_row += grid->comp_row;
comp_col += grid->comp_col;
comp_row = MAX(MIN(comp_row, Rows - wp->w_height_outer - (p_ch > 0 ? 1 : 0)), 0);
- comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
+ if (!c.fixed || east) {
+ comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
+ }
wp->w_winrow = comp_row;
wp->w_wincol = comp_col;
ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
diff --git a/test/README.md b/test/README.md
index 42d0ec0323..3aafe1273e 100644
--- a/test/README.md
+++ b/test/README.md
@@ -102,9 +102,25 @@ Debugging tests
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0
```
-- You can set `$GDB` to [run tests under gdbserver](https://github.com/neovim/neovim/pull/1527).
- And if `$VALGRIND` is set it will pass `--vgdb=yes` to valgrind instead of
+- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527):
+
+ ```sh
+ GDB=1 TEST_FILE=test/functional/api/buffer_spec.lua TEST_FILTER='nvim_buf_set_text works$' make functionaltest
+ ```
+
+ Read more about [filtering tests](#filtering-tests).
+
+ Then, in another terminal:
+
+ ```sh
+ gdb -ex 'target remote localhost:7777' build/bin/nvim
+ ```
+
+ If `$VALGRIND` is also set it will pass `--vgdb=yes` to valgrind instead of
starting gdbserver directly.
+
+ See [test/functional/helpers.lua](https://github.com/neovim/neovim/blob/9cadbf1d36b63f53f0de48c8c5ff6c752ff05d70/test/functional/helpers.lua#L52-L69) for details.
+
- Hanging tests can happen due to unexpected "press-enter" prompts. The
default screen width is 50 columns. Commands that try to print lines longer
than 50 columns in the command-line, e.g. `:edit very...long...path`, will
@@ -124,7 +140,7 @@ Filtering Tests
### Filter by name
-Another filter method is by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`.
+Tests can be filtered by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`.
``` lua
it('foo api',function()
diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua
index f544255d81..12763cfef5 100644
--- a/test/functional/plugin/lsp/utils_spec.lua
+++ b/test/functional/plugin/lsp/utils_spec.lua
@@ -88,6 +88,40 @@ describe('vim.lsp.util', function()
end)
end)
+ describe('normalize_markdown', function ()
+ it('collapses consecutive blank lines', function ()
+ local result = exec_lua [[
+ local lines = {
+ 'foo',
+ '',
+ '',
+ '',
+ 'bar',
+ '',
+ 'baz'
+ }
+ return vim.lsp.util._normalize_markdown(lines)
+ ]]
+ local expected = {'foo', '', 'bar', '', 'baz'}
+ eq(expected, result)
+ end)
+
+ it('removes preceding and trailing empty lines', function ()
+ local result = exec_lua [[
+ local lines = {
+ '',
+ 'foo',
+ 'bar',
+ '',
+ ''
+ }
+ return vim.lsp.util._normalize_markdown(lines)
+ ]]
+ local expected = {'foo', 'bar'}
+ eq(expected, result)
+ end)
+ end)
+
describe("make_floating_popup_options", function ()
local function assert_anchor(anchor_bias, expected_anchor)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index e0a8badb67..7e30af5058 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -3032,7 +3032,7 @@ describe('LSP', function()
}
return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','})
]]
- local expected = {'```cs', 'TestEntity.TestEntity()', '```', '<text>', 'some doc', '</text>'}
+ local expected = {'```cs', 'TestEntity.TestEntity()', '```', 'some doc'}
eq(expected, result)
end)
end)
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 57dcb14cf8..2d4613dda4 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -1663,21 +1663,18 @@ describe('TUI', function()
it('draws correctly when cursor_address overflows #21643', function()
helpers.skip(helpers.is_os('mac'), 'FIXME: crashes/errors on macOS')
- screen:try_resize(77, 834)
+ screen:try_resize(77, 855)
retry(nil, nil, function()
- eq({true, 831}, {child_session:request('nvim_win_get_height', 0)})
+ eq({true, 852}, {child_session:request('nvim_win_get_height', 0)})
end)
-- Use full screen message so that redrawing afterwards is more deterministic.
child_session:notify('nvim_command', 'intro')
screen:expect({any = 'Nvim'})
-- Going to top-left corner needs 3 bytes.
-- Setting underline attribute needs 9 bytes.
- -- With screen width 77, 63857 characters need 829 full screen lines.
- -- Drawing each full screen line needs 77 + 2 = 79 bytes (2 bytes for CR LF).
- -- The incomplete screen line needs 24 + 3 = 27 bytes.
- -- The whole line needs 3 + 9 + 79 * 829 + 27 = 65530 bytes.
+ -- The whole line needs 3 + 9 + 65515 + 3 = 65530 bytes.
-- The cursor_address that comes after will overflow the 65535-byte buffer.
- local line = ('a'):rep(63857) .. '℃'
+ local line = ('a'):rep(65515) .. '℃'
child_session:notify('nvim_exec_lua', [[
vim.api.nvim_buf_set_lines(0, 0, -1, true, {...})
vim.o.cursorline = true
@@ -1686,8 +1683,8 @@ describe('TUI', function()
feed_data('\n')
screen:expect(
'{13:a}{12:' .. ('a'):rep(76) .. '}|\n'
- .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(828)
- .. '{12:' .. ('a'):rep(24) .. '℃' .. (' '):rep(52) .. '}|\n' .. dedent([[
+ .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(849)
+ .. '{12:' .. ('a'):rep(65) .. '℃' .. (' '):rep(11) .. '}|\n' .. dedent([[
b |
{5:[No Name] [+] }|
|
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 556859478e..f75fb52108 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -962,6 +962,71 @@ describe('float window', function()
end
end)
+ it('window position fixed', function()
+ command('rightbelow 20vsplit')
+ local buf = meths.create_buf(false,false)
+ local win = meths.open_win(buf, false, {
+ relative='win', width=15, height=2, row=2, col=10, anchor='NW', fixed=true})
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ {5:[No Name] }{4:[No Name] }|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 5
+ {1: }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 4, 2, 10, true, 50};
+ }}
+ else
+ screen:expect([[
+ {5:│}^ |
+ {0:~ }{5:│}{0:~ }|
+ {0:~ }{5:│}{0:~ }{1: }|
+ {0:~ }{5:│}{0:~ }{2:~ }|
+ {0:~ }{5:│}{0:~ }|
+ {5:[No Name] }{4:[No Name] }|
+ |
+ ]])
+ end
+
+ meths.win_set_config(win, {fixed=false})
+
+ if multigrid then
+ screen:expect_unchanged()
+ else
+ screen:expect([[
+ {5:│}^ |
+ {0:~ }{5:│}{0:~ }|
+ {0:~ }{5:│}{0:~ }{1: }|
+ {0:~ }{5:│}{0:~ }{2:~ }|
+ {0:~ }{5:│}{0:~ }|
+ {5:[No Name] }{4:[No Name] }|
+ |
+ ]])
+ end
+ end)
+
it('draws correctly with redrawdebug=compositor', function()
-- NB: we do not test that it produces the "correct" debug info
-- (as it is intermediate only, and is allowed to change by internal
diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua
index 46de6b114e..c8ca5be282 100644
--- a/test/functional/ui/fold_spec.lua
+++ b/test/functional/ui/fold_spec.lua
@@ -423,6 +423,119 @@ describe("folded lines", function()
:set norightleft |
]])
end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 0, 0)
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {7:▸ }{5:^+-- 6 lines: aa···························}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :set norightleft |
+ ]])
+ else
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ screen:expect([[
+ {7:▸ }{5:^+-- 6 lines: aa···························}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set norightleft |
+ ]])
+ end
+
+ -- Add a winbar to avoid double-clicks
+ command('setlocal winbar=!!!!!!')
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 1, 0)
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {11:!!!!!! }|
+ {7:▾▸}{5:^+--- 5 lines: aa··························}|
+ {7:│ }ff |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :set norightleft |
+ ]])
+ else
+ meths.input_mouse('left', 'press', '', 0, 1, 0)
+ screen:expect([[
+ {11:!!!!!! }|
+ {7:▾▸}{5:^+--- 5 lines: aa··························}|
+ {7:│ }ff |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set norightleft |
+ ]])
+ end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 1, 1)
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {11:!!!!!! }|
+ {7:▾▾}^aa |
+ {7:││}bb |
+ {7:││}cc |
+ {7:││}dd |
+ {7:││}ee |
+ {7:│ }ff |
+ ## grid 3
+ :set norightleft |
+ ]])
+ else
+ meths.input_mouse('left', 'press', '', 0, 1, 1)
+ screen:expect([[
+ {11:!!!!!! }|
+ {7:▾▾}^aa |
+ {7:││}bb |
+ {7:││}cc |
+ {7:││}dd |
+ {7:││}ee |
+ {7:│ }ff |
+ :set norightleft |
+ ]])
+ end
end)
it("works with split", function()
@@ -2834,7 +2947,7 @@ describe("folded lines", function()
command('5,6fold')
command('2,6fold')
if multigrid then
- screen:expect{grid=[[
+ screen:expect([[
## grid 1
[2:------------------------------]|
[2:------------------------------]|
@@ -2852,7 +2965,7 @@ describe("folded lines", function()
{1:~ }|
## grid 3
|
- ]]}
+ ]])
else
screen:expect([[
{7: }This is a |
@@ -2868,7 +2981,7 @@ describe("folded lines", function()
feed('zo')
if multigrid then
- screen:expect{grid=[[
+ screen:expect([[
## grid 1
[2:------------------------------]|
[2:------------------------------]|
@@ -2886,7 +2999,7 @@ describe("folded lines", function()
{1:~ }|
## grid 3
|
- ]]}
+ ]])
else
screen:expect([[
{7: }This is a |
@@ -2904,7 +3017,7 @@ describe("folded lines", function()
command('hi! Visual guibg=Red')
feed('V2k')
if multigrid then
- screen:expect{grid=[[
+ screen:expect([[
## grid 1
[2:------------------------------]|
[2:------------------------------]|
@@ -2922,7 +3035,7 @@ describe("folded lines", function()
{1:~ }|
## grid 3
{11:-- VISUAL LINE --} |
- ]]}
+ ]])
else
screen:expect([[
{7: }This is a |
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index c68f4cf34c..931e1f9985 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -8,6 +8,7 @@ local feed_command, eq = helpers.feed_command, helpers.eq
local curbufmeths = helpers.curbufmeths
local funcs = helpers.funcs
local meths = helpers.meths
+local exec_lua = helpers.exec_lua
describe('colorscheme compatibility', function()
before_each(function()
@@ -2641,4 +2642,17 @@ describe('highlight namespaces', function()
|
]]}
end)
+
+ it('winhl does not accept invalid value #24586', function()
+ local res = exec_lua([[
+ local curwin = vim.api.nvim_get_current_win()
+ vim.api.nvim_command("set winhl=Normal:Visual")
+ local _, msg = pcall(vim.api.nvim_command,"set winhl='Normal:Wrong'")
+ return { msg, vim.wo[curwin].winhl }
+ ]])
+ eq({
+ "Vim(set):E5248: Invalid character in group name",
+ "Normal:Visual",
+ },res)
+ end)
end)
diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua
index 5cecd423d7..417c7b797c 100644
--- a/test/functional/ui/multibyte_spec.lua
+++ b/test/functional/ui/multibyte_spec.lua
@@ -160,6 +160,22 @@ describe("multibyte rendering", function()
{1:~ }|
|
]]}
+
+ -- nvim will reset the zalgo text^W^W glyph cache if it gets too full.
+ -- this should be exceedingly rare, but fake it to make sure it works
+ meths._invalidate_glyph_cache()
+ screen:expect{grid=[[
+ ^L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚ ̛̓̑̆̇̍i̗̟̞̜̅̐p̗̞̜̉̆̕s̟̜̘̍̑̏ū̟̞̎̃̉ḿ̘̙́́̐ ̖̍̌̇̉̚d̞̄̃̒̉̎ò́̌̌̂̐l̞̀̄̆̌̚ȯ̖̞̋̀̐r̓̇̌̃̃̚ ̗̘̀̏̍́s̜̀̎̎̑̕i̟̗̐̄̄̚t̝̎̆̓̐̒ ̘̇̔̓̊̚ȃ̛̟̗̏̅m̜̟̙̞̈̓é̘̞̟̔̆t̝̂̂̈̑̔,̜̜̖̅̄̍ ̛̗̊̓̆̚c̟̍̆̍̈̔ȯ̖̖̝̑̀n̜̟̎̊̃̚s̟̏̇̎̒̚e̙̐̈̓̌̚c̙̍̈̏̅̕ť̇̄̇̆̓e̛̓̌̈̓̈t̟̍̀̉̆̅u̝̞̎̂̄̚r̘̀̅̈̅̐ ̝̞̓́̇̉ã̏̀̆̅̕d̛̆̐̉̆̋ȉ̞̟̍̃̚p̛̜̊̍̂̓ȋ̏̅̃̋̚ṥ̛̏̃̕č̛̞̝̀̂í̗̘̌́̎n̔̎́̒̂̕ǧ̗̜̋̇̂ ̛̜̔̄̎̃ê̛̔̆̇̕l̘̝̏̐̊̏ĩ̛̍̏̏̄t̟̐́̀̐̎,̙̘̍̆̉̐ ̋̂̏̄̌̅s̙̓̌̈́̇e̛̗̋̒̎̏d̜̗̊̍̊̚ |
+ ď̘̋̌̌̕ǒ̝̗̔̇̕ ̙̍́̄̄̉è̛̛̞̌̌i̜̖̐̈̆̚ȕ̇̈̓̃̓ŝ̛̞̙̉̋m̜̐̂̄̋̂ȯ̈̎̎̅̕d̜̙̓̔̋̑ ̞̗̄̂̂̚t̝̊́̃́̄e̛̘̜̞̓̑m̊̅̏̉̌̕p̛̈̂̇̀̐ỏ̙̘̈̉̔r̘̞̋̍̃̚ ̝̄̀̇̅̇ỉ̛̖̍̓̈n̛̛̝̎̕̕c̛̛̊̅́̐ĭ̗̓̀̍̐d̞̜̋̐̅̚i̟̙̇̄̊̄d̞̊̂̀̇̚ủ̝̉̑̃̕n̜̏̇̄̐̋ť̗̜̞̋̉ ̝̒̓̌̓̚ȕ̖̙̀̚̕t̖̘̎̉̂̌ ̛̝̄̍̌̂l̛̟̝̃̑̋á̛̝̝̔̅b̝̙̜̗̅̒ơ̖̌̒̄̆r̒̇̓̎̈̄e̛̛̖̅̏̇ ̖̗̜̝̃́e̛̛̘̅̔̌ẗ̛̙̗̐̕ ̖̟̇̋̌̈d̞̙̀̉̑̕ŏ̝̂́̐̑l̞̟̗̓̓̀ơ̘̎̃̄̂r̗̗̖̔̆̍ẻ̖̝̞̋̅ ̜̌̇̍̈̊m̈̉̇̄̒̀a̜̞̘̔̅̆g̗̖̈̃̈̉n̙̖̄̈̉̄â̛̝̜̄̃ ̛́̎̕̕̚ā̊́́̆̌l̟̙̞̃̒́i̖̇̎̃̀̋q̟̇̒̆́̊ủ́̌̇̑̚ã̛̘̉̐̚.̛́̏̐̍̊ |
+ U̝̙̎̈̐̆t̜̍̌̀̔̏ ̞̉̍̇̈̃e̟̟̊̄̕̕n̝̜̒̓̆̕i̖̒̌̅̇̚m̞̊̃̔̊̂ ̛̜̊̎̄̂a̘̜̋̒̚̚d̟̊̎̇̂̍ ̜̖̏̑̉̕m̜̒̎̅̄̚i̝̖̓̂̍̕n̙̉̒̑̀̔ỉ̖̝̌̒́m̛̖̘̅̆̎ ̖̉̎̒̌̕v̖̞̀̔́̎e̖̙̗̒̎̉n̛̗̝̎̀̂ȉ̞̗̒̕̚ȧ̟̜̝̅̚m̆̉̐̐̇̈,̏̐̎́̍́ ̜̞̙̘̏̆q̙̖̙̅̓̂ủ̇́̀̔̚í̙̟̟̏̐s̖̝̍̏̂̇ ̛̘̋̈̕̕ń̛̞̜̜̎o̗̜̔̔̈̆s̞̘̘̄̒̋t̛̅̋́̔̈ȓ̓̒́̇̅ủ̜̄̃̒̍d̙̝̘̊̏̚ ̛̟̞̄́̔e̛̗̝̍̃̀x̞̖̃̄̂̅e̖̅̇̐̔̃r̗̞̖̔̎̚c̘̜̖̆̊̏ï̙̝̙̂̕t̖̏́̓̋̂ă̖̄̆̑̒t̜̟̍̉̑̏i̛̞̞̘̒̑ǒ̜̆̅̃̉ṅ̖̜̒̎̚ |
+ u̗̞̓̔̈̏ĺ̟̝́̎̚l̛̜̅̌̎̆a̒̑̆̔̇̃m̜̗̈̊̎̚ċ̘̋̇̂̚ơ̟̖̊́̕ ̖̟̍̉̏̚l̙̔̓̀̅̏ä̞̗̘̙̅ḃ̟̎̄̃̕o̞̎̓̓̓̚r̗̜̊̓̈̒ï̗̜̃̃̅s̀̒̌̂̎̂ ̖̗̗̋̎̐n̝̟̝̘̄̚i̜̒̀̒̐̕s̘̘̄̊̃̀ī̘̜̏̌̕ ̗̖̞̐̈̒ư̙̞̄́̌t̟̘̖̙̊̚ ̌̅̋̆̚̚ä̇̊̇̕̕l̝̞̘̋̔̅i̍̋́̆̑̈q̛̆̐̈̐̚ư̏̆̊́̚î̜̝̑́̊p̗̓̅̑̆̏ ̆́̓̔̋̋e̟̊̋̏̓̚x̗̍̑̊̎̈ ̟̞̆̄̂̍ë̄̎̄̃̅a̛̜̅́̃̈ ̔̋̀̎̐̀c̖̖̍̀̒̂ơ̛̙̖̄̒m̘̔̍̏̆̕ḿ̖̙̝̏̂ȍ̓̋̈̀̕d̆̂̊̅̓̚o̖̔̌̑̚̕ ̙̆́̔̊̒c̖̘̖̀̄̍o̓̄̑̐̓̒ñ̞̒̎̈̚s̞̜̘̈̄̄e̙̊̀̇̌̋q̐̒̓́̔̃ư̗̟̔̔̚å̖̙̞̄̏t̛̙̟̒̇̏.̙̗̓̃̓̎ |
+ D̜̖̆̏̌̌ư̑̃̌̍̕i̝̊̊̊̊̄s̛̙̒́̌̇ ̛̃̔̄̆̌ă̘̔̅̅̀ú̟̟̟̃̃t̟̂̄̈̈̃e̘̅̌̒̂̆ ̖̟̐̉̉̌î̟̟̙̜̇r̛̙̞̗̄̌ú̗̗̃̌̎r̛̙̘̉̊̕e̒̐̔̃̓̋ ̊̊̍̋̑̉d̛̝̙̉̀̓o̘̜̐̐̓̐l̞̋̌̆̍́o̊̊̐̃̃̚ṙ̛̖̘̃̕ ̞̊̀̍̒̕ȉ́̑̐̇̅ǹ̜̗̜̞̏ ̛̜̐̄̄̚r̜̖̈̇̅̋ĕ̗̉̃̔̚p̟̝̀̓̔̆r̜̈̆̇̃̃e̘̔̔̏̎̓h̗̒̉̑̆̚ė̛̘̘̈̐n̘̂̀̒̕̕d̗̅̂̋̅́ê̗̜̜̜̕r̟̋̄̐̅̂i̛̔̌̒̂̕t̛̗̓̎̀̎ ̙̗̀̉̂̚ȉ̟̗̐̓̚n̙̂̍̏̓̉ ̙̘̊̋̍̕v̜̖̀̎̆̐ő̜̆̉̃̎l̑̋̒̉̔̆ư̙̓̓́̚p̝̘̖̎̏̒t̛̘̝̞̂̓ȁ̘̆̔́̊t̖̝̉̒̐̎e̞̟̋̀̅̄ ̆̌̃̀̑̔v̝̘̝̍̀̇ȅ̝̊̄̓̕l̞̝̑̔̂̋ĭ̝̄̅̆̍t̝̜̉̂̈̇ |
+ ē̟̊̇̕̚s̖̘̘̒̄̑s̛̘̀̊̆̇e̛̝̘̒̏̚ ̉̅̑̂̐̎c̛̟̙̎̋̓i̜̇̒̏̆̆l̟̄́̆̊̌l̍̊̋̃̆̌ủ̗̙̒̔̚m̛̘̘̖̅̍ ̖̙̈̎̂̕d̞̟̏̋̈̔ơ̟̝̌̃̄l̗̙̝̂̉̒õ̒̃̄̄̚ŕ̗̏̏̊̍ê̞̝̞̋̈ ̜̔̒̎̃̚e̞̟̞̒̃̄ư̖̏̄̑̃ ̛̗̜̄̓̎f̛̖̞̅̓̃ü̞̏̆̋̕g̜̝̞̑̑̆i̛̘̐̐̅̚à̜̖̌̆̎t̙̙̎̉̂̍ ̋̔̈̎̎̉n̞̓́̔̊̕ư̘̅̋̔̚l̗̍̒̄̀̚l̞̗̘̙̓̍â̘̔̒̎̚ ̖̓̋̉̃̆p̛̛̘̋̌̀ä̙̔́̒̕r̟̟̖̋̐̋ì̗̙̎̓̓ȃ̔̋̑̚̕t̄́̎̓̂̋ư̏̈̂̑̃r̖̓̋̊̚̚.̒̆̑̆̊̎ ̘̜̍̐̂̚E̞̅̐̇́̂x̄́̈̌̉̕ć̘̃̉̃̕è̘̂̑̏̑p̝̘̑̂̌̆t̔̐̅̍̌̂ȇ̞̈̐̚̕ű̝̞̜́̚ŕ̗̝̉̆́ |
+ š̟́̔̏̀ȉ̝̟̝̏̅n̑̆̇̒̆̚t̝̒́̅̋̏ ̗̑̌̋̇̚ơ̙̗̟̆̅c̙̞̙̎̊̎c̘̟̍̔̊̊a̛̒̓̉́̐e̜̘̙̒̅̇ć̝̝̂̇̕ả̓̍̎̂̚t̗̗̗̟̒̃ ̘̒̓̐̇́c̟̞̉̐̓̄ȕ̙̗̅́̏p̛̍̋̈́̅i̖̓̒̍̈̄d̞̃̈̌̆̐a̛̗̝̎̋̉t̞̙̀̊̆̇a̛̙̒̆̉̚t̜̟̘̉̓̚ ̝̘̗̐̇̕n̛̘̑̏̂́ō̑̋̉̏́ň̞̊̆̄̃ ̙̙̙̜̄̏p̒̆̋̋̓̏r̖̖̅̉́̚ơ̜̆̑̈̚i̟̒̀̃̂̌d̛̏̃̍̋̚ë̖̞̙̗̓n̛̘̓̒̅̎t̟̗̙̊̆̚,̘̙̔̊̚̕ ̟̗̘̜̑̔s̜̝̍̀̓̌û̞̙̅̇́n̘̗̝̒̃̎t̗̅̀̅̊̈ ̗̖̅̅̀̄i̛̖̍̅̋̂n̙̝̓̓̎̚ ̞̋̅̋̃̚c̗̒̀̆̌̎ū̞̂̑̌̓ĺ̛̐̍̑́p̝̆̌̎̈̚a̖̙̒̅̈̌ ̝̝̜̂̈̀q̝̖̔̍̒̚ư̔̐̂̎̊ǐ̛̟̖̘̕ |
+ o̖̜̔̋̅̚f̛̊̀̉́̕f̏̉̀̔̃̃i̘̍̎̐̔̎c̙̅̑̂̐̅ȋ̛̜̀̒̚a̋̍̇̏̀̋ ̖̘̒̅̃̒d̗̘̓̈̇̋é̝́̎̒̄š̙̒̊̉̋e̖̓̐̀̍̕r̗̞̂̅̇̄ù̘̇̐̉̀n̐̑̀̄̍̐t̟̀̂̊̄̚ ̟̝̂̍̏́m̜̗̈̂̏̚ő̞̊̑̇̒l̘̑̏́̔̄l̛̛̇̃̋̊i̓̋̒̃̉̌t̛̗̜̏̀̋ ̙̟̒̂̌̐a̙̝̔̆̏̅n̝̙̙̗̆̅i̍̔́̊̃̕m̖̝̟̒̍̚ ̛̃̃̑̌́ǐ̘̉̔̅̚d̝̗̀̌̏̒ ̖̝̓̑̊̚ȇ̞̟̖̌̕š̙̙̈̔̀t̂̉̒̍̄̄ ̝̗̊̋̌̄l̛̞̜̙̘̔å̝̍̂̍̅b̜̆̇̈̉̌ǒ̜̙̎̃̆r̝̀̄̍́̕ư̋̊́̊̕m̜̗̒̐̕̚.̟̘̀̒̌̚ |
+ {1:~ }|
+ |
+ ]], reset=true}
end)
end)
diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index b2b6ad80bb..e2b977c2b8 100644
--- a/test/old/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -362,6 +362,7 @@ func s:GetFilenameChecks() abort
\ 'lilo': ['lilo.conf', 'lilo.conf-file'],
\ 'lilypond': ['file.ly', 'file.ily'],
\ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
+ \ 'liquidsoap': ['file.liq'],
\ 'liquid': ['file.liquid'],
\ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
\ 'lite': ['file.lite', 'file.lt'],
diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim
index a12ecefc73..5d70492451 100644
--- a/test/old/testdir/test_visual.vim
+++ b/test/old/testdir/test_visual.vim
@@ -1577,4 +1577,18 @@ func Test_visual_hl_with_showbreak()
call StopVimInTerminal(buf)
endfunc
+func Test_Visual_r_CTRL_C()
+ new
+ " visual r_cmd
+ call setline(1, [' '])
+ call feedkeys("\<c-v>$r\<c-c>", 'tx')
+ call assert_equal([''], getline(1, 1))
+
+ " visual gr_cmd
+ call setline(1, [' '])
+ call feedkeys("\<c-v>$gr\<c-c>", 'tx')
+ call assert_equal([''], getline(1, 1))
+ bw!
+endfu
+
" vim: shiftwidth=2 sts=2 expandtab