diff options
38 files changed, 1904 insertions, 277 deletions
diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 0ffc8aa786..422fa366b6 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -1,17 +1,17 @@ # sourcehut CI: https://builds.sr.ht/~jmk/neovim -image: openbsd/6.7 +image: openbsd/6.9 packages: -- autoconf-2.69p2 -- automake-1.15.1 +- autoconf-2.71 +- automake-1.16.3 - cmake -- gettext-runtime-0.20.1p1 -- gettext-tools-0.20.1p3 +- gettext-runtime-0.21p1 +- gettext-tools-0.21p1 - gmake - libtool -- ninja-1.10.0 -- unzip-6.0p13 +- ninja-1.10.2p0 +- unzip-6.0p14 sources: - https://github.com/neovim/neovim @@ -23,8 +23,8 @@ environment: tasks: - build-deps: | - export AUTOCONF_VERSION=2.69 - export AUTOMAKE_VERSION=1.15 + export AUTOCONF_VERSION=2.71 + export AUTOMAKE_VERSION=1.16 mkdir neovim/.deps cd neovim/.deps cmake -G Ninja ../third-party/ diff --git a/CMakeLists.txt b/CMakeLists.txt index c22ab8dbae..346600740e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,9 +23,23 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Prefer our bundled versions of dependencies. if(DEFINED ENV{DEPS_BUILD_DIR}) - set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies") + if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + # pkg-config 29.2 has a bug on OpenBSD which causes it to drop any paths that + # *contain* system include paths. To avoid this, we prefix what would be + # "/usr/include" as "/_usr/include". + # This check is also performed in the third-party/CMakeLists.txt and in the + # else clause following here. + # https://github.com/neovim/neovim/pull/14745#issuecomment-860201794 + set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/_usr" CACHE PATH "Path prefix for finding dependencies") + else() + set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies") + endif() else() - set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies") + if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/_usr" CACHE PATH "Path prefix for finding dependencies") + else() + set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies") + endif() # When running from within CLion or Visual Studio, # build bundled dependencies automatically. if(NOT EXISTS ${DEPS_PREFIX} diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 6b3b0f7762..919417f3a0 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1325,33 +1325,37 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* and clearing the |EndOfBuffer| region in 'winhighlight'. - • `border`: style of (optional) window border. This can - either be a string or an array. the string - values are: - • "none" No border. This is the default - • "single" a single line box - • "double" a double line box - • "shadow" a drop shadow effect by blending - with the background. If it is an array it - should be an array of eight items or any - divisor of eight. The array will specifify - the eight chars building up the border in a - clockwise fashion starting with the top-left - corner. As, an example, the double box style - could be specified as: [ "╔", "═" ,"╗", "║", - "╝", "═", "╚", "║" ] if the number of chars - are less than eight, they will be repeated. - Thus an ASCII border could be specified as: - [ "/", "-", "\\", "|" ] or all chars the - same as: [ "x" ] An empty string can be used - to turn off a specific border, for instance: - [ "", "", "", ">", "", "", "", "<" ] will - only make vertical borders but not - horizontal ones. By default `FloatBorder` - highlight is used which links to `VertSplit` - when not defined. It could also be specified - by character: [ {"+", "MyCorner"}, {"x", - "MyBorder"} ] + • `border`: Style of (optional) window border. This can + either be a string or an array. The string + values are + • "none": No border (default). + • "single": A single line box. + • "double": A double line box. + • "rounded": Like "single", but with rounded + corners ("╭" etc.). + • "solid": Adds padding by a single whitespace + cell. + • "shadow": A drop shadow effect by blending + with the background. + • If it is an array, it should have a length + of eight or any divisor of eight. The array + will specifify the eight chars building up + the border in a clockwise fashion starting + with the top-left corner. As an example, the + double box style could be specified as [ + "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. If + the number of chars are less than eight, + they will be repeated. Thus an ASCII border + could be specified as [ "/", "-", "\\", "|" + ], or all chars the same as [ "x" ]. An + empty string can be used to turn off a + specific border, for instance, [ "", "", "", + ">", "", "", "", "<" ] will only make + vertical borders but not horizontal ones. By + default, `FloatBorder` highlight is used, + which links to `VertSplit` when not defined. + It could also be specified by character: [ + {"+", "MyCorner"}, {"x", "MyBorder"} ]. • `noautocmd` : If true then no buffer-related autocommand events such as |BufEnter|, diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index bf94383ec4..9ee1954514 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -831,16 +831,16 @@ ShellFilterPost After executing a shell command with ":{range}!cmd", ":w !cmd" or ":r !cmd". Can be used to check for any changed files. *SourcePre* -SourcePre Before sourcing a Vim script. |:source| +SourcePre Before sourcing a vim/lua file. |:source| <afile> is the name of the file being sourced. *SourcePost* -SourcePost After sourcing a Vim script. |:source| +SourcePost After sourcing a vim/lua file. |:source| <afile> is the name of the file being sourced. Not triggered when sourcing was interrupted. Also triggered after a SourceCmd autocommand was triggered. *SourceCmd* -SourceCmd When sourcing a Vim script. |:source| +SourceCmd When sourcing a vim/lua file. |:source| <afile> is the name of the file being sourced. The autocommand must source this file. |Cmd-event| diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index b7214d1390..9f44c016f0 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -3693,6 +3693,8 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()* line number <sflnum> script file line number, also when in a function + <SID> "<SNR>123_" where "123" is the + current script ID |<SID>| <cword> word under the cursor <cWORD> WORD under the cursor <client> the {clientid} of the last received @@ -8230,9 +8232,8 @@ spellbadword([{sentence}]) echo spellbadword("the quik brown fox") < ['quik', 'bad'] ~ - The spelling information for the current window is used. The - 'spell' option must be set and the value of 'spelllang' is - used. + The spelling information for the current window and the value + of 'spelllang' are used. *spellsuggest()* spellsuggest({word} [, {max} [, {capital}]]) @@ -8254,8 +8255,7 @@ spellsuggest({word} [, {max} [, {capital}]]) although it may appear capitalized. The spelling information for the current window is used. The - 'spell' option must be set and the values of 'spelllang' and - 'spellsuggest' are used. + values of 'spelllang' and 'spellsuggest' are used. split({expr} [, {pattern} [, {keepempty}]]) *split()* @@ -10195,7 +10195,9 @@ text... {endmarker} Set internal variable {var-name} to a |List| containing the lines of text bounded by the string - {endmarker}. + {endmarker}. The lines of text is used as a + |literal-string|. + {endmarker} must not contain white space. {endmarker} cannot start with a lower case character. The last line should end only with the {endmarker} string without any other character. Watch out for diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 531374620a..075912d4e5 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1559,6 +1559,50 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) ============================================================================== +Lua module: vim.lsp.codelens *lsp-codelens* + +display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()* + Display the lenses using virtual text + + Parameters: ~ + {lenses} table of lenses to display ( `CodeLens[] | + null` ) + {bufnr} number + {client_id} number + +get({bufnr}) *vim.lsp.codelens.get()* + Return all lenses for the given buffer + + Return: ~ + table ( `CodeLens[]` ) + + *vim.lsp.codelens.on_codelens()* +on_codelens({err}, {_}, {result}, {client_id}, {bufnr}) + |lsp-handler| for the method `textDocument/codeLens` + +refresh() *vim.lsp.codelens.refresh()* + Refresh the codelens for the current buffer + + It is recommended to trigger this using an autocmd or via + keymap. +> + autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() +< + +run() *vim.lsp.codelens.run()* + Run the code lens in the current line + +save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* + Store lenses for a specific buffer and client + + Parameters: ~ + {lenses} table of lenses to store ( `CodeLens[] | + null` ) + {bufnr} number + {client_id} number + + +============================================================================== Lua module: vim.lsp.handlers *lsp-handlers* *vim.lsp.handlers.progress_handler()* diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index f722747ce9..22d7cdf491 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -110,6 +110,23 @@ zuG Undo |zW| and |zG|, remove the word from the internal :spellw[rong]! {word} Add {word} as a wrong (bad) word to the internal word list, like with |zW|. + *:spellra* *:spellrare* +:[count]spellr[are] {word} + Add {word} as a rare word to 'spellfile', similar to + |zw|. Without count the first name is used, with + a count of two the second entry, etc. + + There are no normal mode commands to mark words as + rare as this is a fairly uncommon command and all + intuitive commands for this are already taken. If you + want you can add mappings with e.g.: > + nnoremap z? :exe ':spellrare ' . expand('<cWORD>')<CR> + nnoremap z/ :exe ':spellrare! ' . expand('<cWORD>')<CR> +< |:spellundo|, |zuw|, or |zuW| can be used to undo this. + +:spellr[rare]! {word} Add {word} as a rare word to the internal word + list, similar to |zW|. + :[count]spellu[ndo] {word} *:spellu* *:spellundo* Like |zuw|. [count] used as with |:spellgood|. diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 89cc1a8fc5..09a1d1d0e6 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -851,6 +851,9 @@ au BufNewFile,BufRead *.jov,*.j73,*.jovial setf jovial " JSON au BufNewFile,BufRead *.json,*.jsonp,*.webmanifest setf json +" Jupyter Notebook is also json +au BufNewFile,BufRead *.ipynb setf json + " Kixtart au BufNewFile,BufRead *.kix setf kix diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5a606188dd..75faf9bcc7 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -20,6 +20,7 @@ local lsp = { buf = require'vim.lsp.buf'; diagnostic = require'vim.lsp.diagnostic'; + codelens = require'vim.lsp.codelens'; util = util; -- Allow raw RPC access. diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua new file mode 100644 index 0000000000..fbd37e3830 --- /dev/null +++ b/runtime/lua/vim/lsp/codelens.lua @@ -0,0 +1,231 @@ +local util = require('vim.lsp.util') +local api = vim.api +local M = {} + +--- bufnr → true|nil +--- to throttle refreshes to at most one at a time +local active_refreshes = {} + +--- bufnr -> client_id -> lenses +local lens_cache_by_buf = setmetatable({}, { + __index = function(t, b) + local key = b > 0 and b or api.nvim_get_current_buf() + return rawget(t, key) + end +}) + +local namespaces = setmetatable({}, { + __index = function(t, key) + local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key) + rawset(t, key, value) + return value + end; +}) + +--@private +M.__namespaces = namespaces + + +--@private +local function execute_lens(lens, bufnr, client_id) + local line = lens.range.start.line + api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1) + + -- Need to use the client that returned the lens → must not use buf_request + local client = vim.lsp.get_client_by_id(client_id) + assert(client, 'Client is required to execute lens, client_id=' .. client_id) + client.request('workspace/executeCommand', lens.command, function(...) + local result = vim.lsp.handlers['workspace/executeCommand'](...) + M.refresh() + return result + end, bufnr) +end + + +--- Return all lenses for the given buffer +--- +---@return table (`CodeLens[]`) +function M.get(bufnr) + local lenses_by_client = lens_cache_by_buf[bufnr] + if not lenses_by_client then return {} end + local lenses = {} + for _, client_lenses in pairs(lenses_by_client) do + vim.list_extend(lenses, client_lenses) + end + return lenses +end + + +--- Run the code lens in the current line +--- +function M.run() + local line = api.nvim_win_get_cursor(0)[1] + local bufnr = api.nvim_get_current_buf() + local options = {} + local lenses_by_client = lens_cache_by_buf[bufnr] or {} + for client, lenses in pairs(lenses_by_client) do + for _, lens in pairs(lenses) do + if lens.range.start.line == (line - 1) then + table.insert(options, {client=client, lens=lens}) + end + end + end + if #options == 0 then + vim.notify('No executable codelens found at current line') + elseif #options == 1 then + local option = options[1] + execute_lens(option.lens, bufnr, option.client) + else + local options_strings = {"Code lenses:"} + for i, option in ipairs(options) do + table.insert(options_strings, string.format('%d. %s', i, option.lens.command.title)) + end + local choice = vim.fn.inputlist(options_strings) + if choice < 1 or choice > #options then + return + end + local option = options[choice] + execute_lens(option.lens, bufnr, option.client) + end +end + + +--- Display the lenses using virtual text +--- +---@param lenses table of lenses to display (`CodeLens[] | null`) +---@param bufnr number +---@param client_id number +function M.display(lenses, bufnr, client_id) + if not lenses or not next(lenses) then + return + end + local lenses_by_lnum = {} + for _, lens in pairs(lenses) do + local line_lenses = lenses_by_lnum[lens.range.start.line] + if not line_lenses then + line_lenses = {} + lenses_by_lnum[lens.range.start.line] = line_lenses + end + table.insert(line_lenses, lens) + end + local ns = namespaces[client_id] + local num_lines = api.nvim_buf_line_count(bufnr) + for i = 0, num_lines do + local line_lenses = lenses_by_lnum[i] + api.nvim_buf_clear_namespace(bufnr, ns, i, i + 1) + local chunks = {} + for _, lens in pairs(line_lenses or {}) do + local text = lens.command and lens.command.title or 'Unresolved lens ...' + table.insert(chunks, {text, 'LspCodeLens' }) + end + if #chunks > 0 then + api.nvim_buf_set_virtual_text(bufnr, ns, i, chunks, {}) + end + end +end + + +--- Store lenses for a specific buffer and client +--- +---@param lenses table of lenses to store (`CodeLens[] | null`) +---@param bufnr number +---@param client_id number +function M.save(lenses, bufnr, client_id) + local lenses_by_client = lens_cache_by_buf[bufnr] + if not lenses_by_client then + lenses_by_client = {} + lens_cache_by_buf[bufnr] = lenses_by_client + local ns = namespaces[client_id] + api.nvim_buf_attach(bufnr, false, { + on_detach = function(b) lens_cache_by_buf[b] = nil end, + on_lines = function(_, b, _, first_lnum, last_lnum) + api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum) + end + }) + end + lenses_by_client[client_id] = lenses +end + + +--@private +local function resolve_lenses(lenses, bufnr, client_id, callback) + lenses = lenses or {} + local num_lens = vim.tbl_count(lenses) + if num_lens == 0 then + callback() + return + end + + --@private + local function countdown() + num_lens = num_lens - 1 + if num_lens == 0 then + callback() + end + end + local ns = namespaces[client_id] + local client = vim.lsp.get_client_by_id(client_id) + for _, lens in pairs(lenses or {}) do + if lens.command then + countdown() + else + client.request('codeLens/resolve', lens, function(_, _, result) + if result and result.command then + lens.command = result.command + -- Eager display to have some sort of incremental feedback + -- Once all lenses got resolved there will be a full redraw for all lenses + -- So that multiple lens per line are properly displayed + api.nvim_buf_set_virtual_text( + bufnr, + ns, + lens.range.start.line, + {{ lens.command.title, 'LspCodeLens' },}, + {} + ) + end + countdown() + end, bufnr) + end + end +end + + +--- |lsp-handler| for the method `textDocument/codeLens` +--- +function M.on_codelens(err, _, result, client_id, bufnr) + assert(not err, vim.inspect(err)) + + M.save(result, bufnr, client_id) + + -- Eager display for any resolved (and unresolved) lenses and refresh them + -- once resolved. + M.display(result, bufnr, client_id) + resolve_lenses(result, bufnr, client_id, function() + M.display(result, bufnr, client_id) + active_refreshes[bufnr] = nil + end) +end + + +--- Refresh the codelens for the current buffer +--- +--- It is recommended to trigger this using an autocmd or via keymap. +--- +--- <pre> +--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() +--- </pre> +--- +function M.refresh() + local params = { + textDocument = util.make_text_document_params() + } + local bufnr = api.nvim_get_current_buf() + if active_refreshes[bufnr] then + return + end + active_refreshes[bufnr] = true + vim.lsp.buf_request(0, 'textDocument/codeLens', params) +end + + +return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 6ae54ea253..5d38150fc0 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -187,6 +187,10 @@ M['textDocument/publishDiagnostics'] = function(...) return require('vim.lsp.diagnostic').on_publish_diagnostics(...) end +M['textDocument/codeLens'] = function(...) + return require('vim.lsp.codelens').on_codelens(...) +end + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references M['textDocument/references'] = function(_, _, result) if not result then return end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index cb9a7cbed5..afe1ead9ee 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -50,7 +50,7 @@ local function get_border_size(opts) local width = 0 if type(border) == 'string' then - local border_size = {none = {0, 0}, single = {2, 2}, double = {2, 2}, shadow = {1, 1}} + local border_size = {none = {0, 0}, single = {2, 2}, double = {2, 2}, rounded = {2, 2}, solid = {2, 2}, shadow = {1, 1}} if border_size[border] == nil then error("floating preview border is not correct. Please refer to the docs |vim.api.nvim_open_win()|" .. vim.inspect(border)) @@ -806,14 +806,20 @@ function M.convert_input_to_markdown_lines(input, contents) assert(type(input) == 'table', "Expected a table for Hover.contents") -- MarkupContent if input.kind then - -- The kind can be either plaintext or markdown. However, either way we - -- will just be rendering markdown, so we handle them both the same way. - -- TODO these can have escaped/sanitized html codes in markdown. We - -- should make sure we handle this correctly. + -- 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 :( + input.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 + input.value = string.format("<text>\n%s\n</text>", input.value or "") + end + -- assert(type(input.value) == 'string') - list_extend(contents, split_lines(input.value or '')) + list_extend(contents, split_lines(input.value)) -- MarkupString variation 2 elseif input.language then -- Some servers send input.value as empty, so let's ignore this :( @@ -1134,26 +1140,45 @@ function M.stylize_markdown(bufnr, contents, opts) } opts = opts or {} + -- table of fence types to {ft, begin, end} + -- when ft is nil, we get the ft from the regex match + local matchers = { + block = {nil, "```+([a-zA-Z0-9_]*)", "```+"}, + pre = {"", "<pre>", "</pre>"}, + code = {"", "<code>", "</code>"}, + text = {"plaintex", "<text>", "</text>"}, + } + + local match_begin = function(line) + for type, pattern in pairs(matchers) do + local ret = line:match(string.format("^%%s*%s%%s*$", pattern[2])) + if ret then + return { + type = type, + ft = pattern[1] or ret + } + end + end + end + + local match_end = function(line, match) + local pattern = matchers[match.type] + return line:match(string.format("^%%s*%s%%s*$", pattern[3])) + end + local stripped = {} local highlights = {} do local i = 1 while i <= #contents do local line = contents[i] - -- TODO(ashkan): use a more strict regex for filetype? - local ft = line:match("^```([a-zA-Z0-9_]*)$") - -- local ft = line:match("^```(.*)$") - -- TODO(ashkan): validate the filetype here. - local is_pre = line:match("^%s*<pre>%s*$") - if is_pre then - ft = "" - end - if ft then + local match = match_begin(line) + if match then local start = #stripped i = i + 1 while i <= #contents do line = contents[i] - if line == "```" or (is_pre and line:match("^%s*</pre>%s*$")) then + if match_end(line, match) then i = i + 1 break end @@ -1161,7 +1186,7 @@ function M.stylize_markdown(bufnr, contents, opts) i = i + 1 end table.insert(highlights, { - ft = ft; + ft = match.ft; start = start + 1; finish = #stripped + 1 - 1; }) diff --git a/runtime/spell/en.utf-8.spl b/runtime/spell/en.utf-8.spl Binary files differindex 83b9b8f7c2..e4b1e1cce7 100644 --- a/runtime/spell/en.utf-8.spl +++ b/runtime/spell/en.utf-8.spl diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index d46306d41a..18a2839702 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -154,6 +154,7 @@ CONFIG = { 'lsp.lua', 'buf.lua', 'diagnostic.lua', + 'codelens.lua', 'handlers.lua', 'util.lua', 'log.lua', diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4b1c2d4baa..c7d261ba18 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1770,6 +1770,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, + { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false }, { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, { NULL, { { NUL } } , false }, }; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 60535b13b3..349cc0e7da 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1437,28 +1437,30 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// end-of-buffer region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |EndOfBuffer| region in 'winhighlight'. -/// - `border`: style of (optional) window border. This can either be a string -/// or an array. the string values are: -/// - "none" No border. This is the default -/// - "single" a single line box -/// - "double" a double line box -/// - "shadow" a drop shadow effect by blending with the background. -/// If it is an array it should be an array of eight items or any divisor of +/// - `border`: Style of (optional) window border. This can either be a string +/// or an array. The string values are +/// - "none": No border (default). +/// - "single": A single line box. +/// - "double": A double line box. +/// - "rounded": Like "single", but with rounded corners ("╭" etc.). +/// - "solid": Adds padding by a single whitespace cell. +/// - "shadow": A drop shadow effect by blending with the background. +/// - If it is an array, it should have a length of eight or any divisor of /// eight. The array will specifify the eight chars building up the border -/// in a clockwise fashion starting with the top-left corner. As, an -/// example, the double box style could be specified as: -/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ] -/// if the number of chars are less than eight, they will be repeated. Thus -/// an ASCII border could be specified as: -/// [ "/", "-", "\\", "|" ] -/// or all chars the same as: -/// [ "x" ] -/// An empty string can be used to turn off a specific border, for instance: +/// in a clockwise fashion starting with the top-left corner. As an +/// example, the double box style could be specified as +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. +/// If the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as +/// [ "/", "-", "\\", "|" ], +/// or all chars the same as +/// [ "x" ]. +/// An empty string can be used to turn off a specific border, for instance, /// [ "", "", "", ">", "", "", "", "<" ] /// will only make vertical borders but not horizontal ones. -/// By default `FloatBorder` highlight is used which links to `VertSplit` +/// By default, `FloatBorder` highlight is used, which links to `VertSplit` /// when not defined. It could also be specified by character: -/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. /// - `noautocmd`: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ce09d268ea..4f9a9fcd68 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9661,6 +9661,18 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *word = ""; hlf_T attr = HLF_COUNT; size_t len = 0; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + EMSG(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return; + } if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. @@ -9669,7 +9681,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) word = (char *)get_cursor_pos_ptr(); curwin->w_set_curswant = true; } - } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { + } else if (*curbuf->b_s.b_p_spl != NUL) { const char *str = tv_get_string_chk(&argvars[0]); int capcol = -1; @@ -9687,6 +9699,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } + curwin->w_p_spell = wo_spell_save; assert(len <= INT_MAX); tv_list_alloc_ret(rettv, 2); @@ -9708,8 +9721,20 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) int maxcount; garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + EMSG(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return; + } - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { + if (*curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); @@ -9736,6 +9761,7 @@ f_spellsuggest_return: tv_list_append_allocated_string(rettv->vval.v_list, p); } ga_clear(&ga); + curwin->w_p_spell = wo_spell_save; } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index d99383303b..7b971f464f 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2568,6 +2568,12 @@ module.cmds = { func='ex_spellrepall', }, { + command='spellrare', + flags=bit.bor(BANG, RANGE, NEEDARG, EXTRA, TRLBAR), + addr_type='ADDR_OTHER', + func='ex_spell', + }, + { command='spellundo', flags=bit.bor(BANG, RANGE, NEEDARG, EXTRA, TRLBAR), addr_type='ADDR_OTHER', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 4798e93b91..9abeee47f4 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2809,10 +2809,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc) proftime_T wait_start; bool trigger_source_post = false; - if (path_with_extension((const char *)fname, "lua")) { - return (int)nlua_exec_file((const char *)fname); - } - p = expand_env_save(fname); if (p == NULL) { return retval; @@ -3005,10 +3001,15 @@ int do_source(char_u *fname, int check_other, int is_vimrc) firstline = p; } - // Call do_cmdline, which will call getsourceline() to get the lines. - do_cmdline(firstline, getsourceline, (void *)&cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); - retval = OK; + if (path_with_extension((const char *)fname, "lua")) { + // Source the file as lua + retval = (int)nlua_exec_file((const char *)fname); + } else { + // Call do_cmdline, which will call getsourceline() to get the lines. + do_cmdline(firstline, getsourceline, (void *)&cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + retval = OK; + } if (l_do_profiling == PROF_YES) { // Get "si" again, "script_items" may have been reallocated. diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0ce2b586e3..7c7ce5e65f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -878,6 +878,7 @@ EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s")); EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range")); EXTERN char_u e_invcmd[] INIT(= N_("E476: Invalid command")); EXTERN char_u e_isadir2[] INIT(= N_("E17: \"%s\" is a directory")); +EXTERN char_u e_no_spell[] INIT(= N_("E756: Spell checking is not possible")); EXTERN char_u e_invchan[] INIT(= N_("E900: Invalid channel id")); EXTERN char_u e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job")); EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full")); diff --git a/src/nvim/main.c b/src/nvim/main.c index 53043d293e..7d7eba2105 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1811,7 +1811,7 @@ static bool do_user_initialization(void) char_u *init_lua_path = (char_u *)stdpaths_user_conf_subpath("init.lua"); if (os_path_exists(init_lua_path) - && nlua_exec_file((const char *)init_lua_path)) { + && do_source(init_lua_path, true, DOSO_VIMRC)) { os_setenv("MYVIMRC", (const char *)init_lua_path, 1); char_u *vimrc_path = (char_u *)stdpaths_user_conf_subpath("init.vim"); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e8ab8da744..44cdc09c0b 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4604,7 +4604,9 @@ dozet: if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) return; assert(len <= INT_MAX); - spell_add_word(ptr, (int)len, nchar == 'w' || nchar == 'W', + spell_add_word(ptr, (int)len, + nchar == 'w' || nchar == 'W' + ? SPELL_ADD_BAD : SPELL_ADD_GOOD, (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, undo); } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 10cc410e81..771c2106db 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1343,7 +1343,7 @@ static bool no_spell_checking(win_T *wp) { if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) { - EMSG(_("E756: Spell checking is not enabled")); + EMSG(_(e_no_spell)); return true; } return false; @@ -2771,9 +2771,17 @@ void spell_suggest(int count) int selected = count; int badlen = 0; int msg_scroll_save = msg_scroll; + const int wo_spell_save = curwin->w_p_spell; - if (no_spell_checking(curwin)) + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + EMSG(_(e_no_spell)); return; + } if (VIsual_active) { // Use the Visually selected text as the bad word. But reject @@ -2966,6 +2974,7 @@ void spell_suggest(int count) spell_find_cleanup(&sug); xfree(line); + curwin->w_p_spell = wo_spell_save; } // Check if the word at line "lnum" column "col" is required to start with a @@ -5761,7 +5770,9 @@ cleanup_suggestions ( xfree(stp[i].st_word); } gap->ga_len = keep; - return stp[keep - 1].st_score; + if (keep >= 1) { + return stp[keep - 1].st_score; + } } } return maxscore; diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index e2c9ab7ae8..f07f5673f9 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -284,4 +284,11 @@ extern int did_set_spelltab; extern char *e_format; +// Values for "what" argument of spell_add_word() +typedef enum { + SPELL_ADD_GOOD = 0, + SPELL_ADD_BAD = 1, + SPELL_ADD_RARE = 2, +} SpellAddType; + #endif // NVIM_SPELL_DEFS_H diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 145cfe4fc6..0597f392e7 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -5290,13 +5290,16 @@ static void spell_message(const spellinfo_T *spin, char_u *str) } // ":[count]spellgood {word}" -// ":[count]spellwrong {word}" +// ":[count]spellwrong {word}" // ":[count]spellundo {word}" +// ":[count]spellrare {word}" void ex_spell(exarg_T *eap) { - spell_add_word(eap->arg, (int)STRLEN(eap->arg), eap->cmdidx == CMD_spellwrong, - eap->forceit ? 0 : (int)eap->line2, - eap->cmdidx == CMD_spellundo); + spell_add_word(eap->arg, (int)STRLEN(eap->arg), + eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD : + eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD, + eap->forceit ? 0 : (int)eap->line2, + eap->cmdidx == CMD_spellundo); } // Add "word[len]" to 'spellfile' as a good or bad word. @@ -5304,10 +5307,10 @@ void spell_add_word ( char_u *word, int len, - int bad, - int idx, // "zG" and "zW": zero, otherwise index in - // 'spellfile' - bool undo // true for "zug", "zuG", "zuw" and "zuW" + SpellAddType what, // SPELL_ADD_ values + int idx, // "zG" and "zW": zero, otherwise index in + // 'spellfile' + bool undo // true for "zug", "zuG", "zuw" and "zuW" ) { FILE *fd = NULL; @@ -5364,7 +5367,7 @@ spell_add_word ( fname = fnamebuf; } - if (bad || undo) { + if (what == SPELL_ADD_BAD || undo) { // When the word appears as good word we need to remove that one, // since its flags sort before the one with WF_BANNED. fd = os_fopen((char *)fname, "r"); @@ -5422,13 +5425,16 @@ spell_add_word ( } } - if (fd == NULL) + if (fd == NULL) { EMSG2(_(e_notopen), fname); - else { - if (bad) + } else { + if (what == SPELL_ADD_BAD) { fprintf(fd, "%.*s/!\n", len, word); - else + } else if (what == SPELL_ADD_RARE) { + fprintf(fd, "%.*s/?\n", len, word); + } else { fprintf(fd, "%.*s\n", len, word); + } fclose(fd); home_replace(NULL, fname, NameBuff, MAXPATHL, TRUE); diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 09fdbf4e20..71a7a2cce5 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -257,7 +257,7 @@ let s:filename_checks = { \ 'jgraph': ['file.jgr'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], - \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest', 'Pipfile.lock'], + \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb'], \ 'jsp': ['file.jsp'], \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'], \ 'kivy': ['file.kv'], diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4a00999c45..5c413d1e16 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1111,161 +1111,6 @@ func Test_normal18_z_fold() bw! endfunc -func Test_normal19_z_spell() - if !has("spell") || !has('syntax') - return - endif - new - call append(0, ['1 good', '2 goood', '3 goood']) - set spell spellfile=./Xspellfile.add spelllang=en - let oldlang=v:lang - lang C - - " Test for zg - 1 - norm! ]s - call assert_equal('2 goood', getline('.')) - norm! zg - 1 - let a=execute('unsilent :norm! ]s') - call assert_equal('1 good', getline('.')) - call assert_equal('search hit BOTTOM, continuing at TOP', a[1:]) - let cnt=readfile('./Xspellfile.add') - call assert_equal('goood', cnt[0]) - - " Test for zw - 2 - norm! $zw - 1 - norm! ]s - call assert_equal('2 goood', getline('.')) - let cnt=readfile('./Xspellfile.add') - call assert_equal('#oood', cnt[0]) - call assert_equal('goood/!', cnt[1]) - - " Test for zg in visual mode - let a=execute('unsilent :norm! V$zg') - call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) - 1 - norm! ]s - call assert_equal('3 goood', getline('.')) - let cnt=readfile('./Xspellfile.add') - call assert_equal('2 goood', cnt[2]) - " Remove "2 good" from spellfile - 2 - let a=execute('unsilent norm! V$zw') - call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) - let cnt=readfile('./Xspellfile.add') - call assert_equal('2 goood/!', cnt[3]) - - " Test for zG - let a=execute('unsilent norm! V$zG') - call assert_match("Word '2 goood' added to .*", a) - let fname=matchstr(a, 'to\s\+\zs\f\+$') - let fname=Fix_truncated_tmpfile(fname) - let cnt=readfile(fname) - call assert_equal('2 goood', cnt[0]) - - " Test for zW - let a=execute('unsilent norm! V$zW') - call assert_match("Word '2 goood' added to .*", a) - let cnt=readfile(fname) - call assert_equal('# goood', cnt[0]) - call assert_equal('2 goood/!', cnt[1]) - - " Test for zuW - let a=execute('unsilent norm! V$zuW') - call assert_match("Word '2 goood' removed from .*", a) - let cnt=readfile(fname) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - - " Test for zuG - let a=execute('unsilent norm! $zG') - call assert_match("Word 'goood' added to .*", a) - let cnt=readfile(fname) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - call assert_equal('goood', cnt[2]) - let a=execute('unsilent norm! $zuG') - let cnt=readfile(fname) - call assert_match("Word 'goood' removed from .*", a) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - call assert_equal('#oood', cnt[2]) - " word not found in wordlist - let a=execute('unsilent norm! V$zuG') - let cnt=readfile(fname) - call assert_match("", a) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - call assert_equal('#oood', cnt[2]) - - " Test for zug - call delete('./Xspellfile.add') - 2 - let a=execute('unsilent norm! $zg') - let cnt=readfile('./Xspellfile.add') - call assert_equal('goood', cnt[0]) - let a=execute('unsilent norm! $zug') - call assert_match("Word 'goood' removed from \./Xspellfile.add", a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('#oood', cnt[0]) - " word not in wordlist - let a=execute('unsilent norm! V$zug') - call assert_match('', a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('#oood', cnt[0]) - - " Test for zuw - call delete('./Xspellfile.add') - 2 - let a=execute('unsilent norm! Vzw') - let cnt=readfile('./Xspellfile.add') - call assert_equal('2 goood/!', cnt[0]) - let a=execute('unsilent norm! Vzuw') - call assert_match("Word '2 goood' removed from \./Xspellfile.add", a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('# goood/!', cnt[0]) - " word not in wordlist - let a=execute('unsilent norm! $zug') - call assert_match('', a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('# goood/!', cnt[0]) - - " add second entry to spellfile setting - set spellfile=./Xspellfile.add,./Xspellfile2.add - call delete('./Xspellfile.add') - 2 - let a=execute('unsilent norm! $2zg') - let cnt=readfile('./Xspellfile2.add') - call assert_match("Word 'goood' added to ./Xspellfile2.add", a) - call assert_equal('goood', cnt[0]) - - " Test for :spellgood! - let temp = execute(':spe!0/0') - call assert_match('Invalid region', temp) - let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0') - call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile)) - call delete(spellfile) - - " clean up - exe "lang" oldlang - call delete("./Xspellfile.add") - call delete("./Xspellfile2.add") - call delete("./Xspellfile.add.spl") - call delete("./Xspellfile2.add.spl") - - " zux -> no-op - 2 - norm! $zux - call assert_equal([], glob('Xspellfile.add',0,1)) - call assert_equal([], glob('Xspellfile2.add',0,1)) - - set spellfile= - bw! -endfunc - func Test_normal20_exmode() if !has("unix") " Reading from redirected file doesn't work on MS-Windows diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index bf15f7f52b..14240f0d5f 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2692,6 +2692,28 @@ func Test_cwindow_jump() set efm&vim endfunc +func Test_cwindow_highlight() + CheckScreendump + + let lines =<< trim END + call setline(1, ['some', 'text', 'with', 'matches']) + write XCwindow + vimgrep e XCwindow + redraw + cwindow 4 + END + call writefile(lines, 'XtestCwindow') + let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12}) + call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {}) + call term_sendkeys(buf, ":cnext\<CR>") + call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestCwindow') + call delete('XCwindow') +endfunc + func XvimgrepTests(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index ab8a998bb8..e525d06ea2 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -106,11 +106,14 @@ foobar/? set spelllang=Xwords.spl call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) - " Typo should not be detected without the 'spell' option. + " Typo should be detected even without the 'spell' option. set spelllang=en_gb nospell call assert_equal(['', ''], spellbadword('centre')) - call assert_equal(['', ''], spellbadword('My bycycle.')) - call assert_equal(['', ''], spellbadword('A sentence. another sentence')) + call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.')) + call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence')) + + set spelllang= + call assert_fails("call spellbadword('maxch')", 'E756:') call delete('Xwords.spl') call delete('Xwords') @@ -172,6 +175,183 @@ func Test_spellreall() bwipe! endfunc +" Test spellsuggest({word} [, {max} [, {capital}]]) +func Test_spellsuggest() + " Verify suggestions are given even when spell checking is not enabled. + set nospell + call assert_equal(['march', 'March'], spellsuggest('marrch', 2)) + + set spell + + " With 1 argument. + call assert_equal(['march', 'March'], spellsuggest('marrch')[0:1]) + + " With 2 arguments. + call assert_equal(['march', 'March'], spellsuggest('marrch', 2)) + + " With 3 arguments. + call assert_equal(['march'], spellsuggest('marrch', 1, 0)) + call assert_equal(['March'], spellsuggest('marrch', 1, 1)) + + " Test with digits and hyphen. + call assert_equal('Carbon-14', spellsuggest('Carbon-15')[0]) + + " Comment taken from spellsuggest.c explains the following test cases: + " + " If there are more UPPER than lower case letters suggest an + " ALLCAP word. Otherwise, if the first letter is UPPER then + " suggest ONECAP. Exception: "ALl" most likely should be "All", + " require three upper case letters. + call assert_equal(['THIRD', 'third'], spellsuggest('thIRD', 2)) + call assert_equal(['third', 'THIRD'], spellsuggest('tHIrd', 2)) + call assert_equal(['Third'], spellsuggest('THird', 1)) + call assert_equal(['All'], spellsuggest('ALl', 1)) + + call assert_fails("call spellsuggest('maxch', [])", 'E745:') + call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:') + + set spelllang= + call assert_fails("call spellsuggest('maxch')", 'E756:') + set spelllang& + + set spell& +endfunc + +" Test 'spellsuggest' option with methods fast, best and double. +func Test_spellsuggest_option_methods() + set spell + + for e in ['utf-8'] + exe 'set encoding=' .. e + + set spellsuggest=fast + call assert_equal(['Stick', 'Stitch'], spellsuggest('Stich', 2), e) + + " With best or double option, "Stitch" should become the top suggestion + " because of better phonetic matching. + set spellsuggest=best + call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e) + + set spellsuggest=double + call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e) + endfor + + set spell& spellsuggest& encoding& +endfunc + +" Test 'spellsuggest' option with value file:{filename} +func Test_spellsuggest_option_file() + set spell spellsuggest=file:Xspellsuggest + call writefile(['emacs/vim', + \ 'theribal/terrible', + \ 'teribal/terrrible', + \ 'terribal'], + \ 'Xspellsuggest') + + call assert_equal(['vim'], spellsuggest('emacs', 2)) + call assert_equal(['terrible'], spellsuggest('theribal',2)) + + " If the suggestion is misspelled (*terrrible* with 3 r), + " it should not be proposed. + " The entry for "terribal" should be ignored because of missing slash. + call assert_equal([], spellsuggest('teribal', 2)) + call assert_equal([], spellsuggest('terribal', 2)) + + set spell spellsuggest=best,file:Xspellsuggest + call assert_equal(['vim', 'Emacs'], spellsuggest('emacs', 2)) + call assert_equal(['terrible', 'tribal'], spellsuggest('theribal', 2)) + call assert_equal(['tribal'], spellsuggest('teribal', 1)) + call assert_equal(['tribal'], spellsuggest('terribal', 1)) + + call delete('Xspellsuggest') + call assert_fails("call spellsuggest('vim')", "E484: Can't open file Xspellsuggest") + + set spellsuggest& spell& +endfunc + +" Test 'spellsuggest' option with value {number} +" to limit the number of suggestions +func Test_spellsuggest_option_number() + set spell spellsuggest=2,best + new + + " We limited the number of suggestions to 2, so selecting + " the 1st and 2nd suggestion should correct the word, but + " selecting a 3rd suggestion should do nothing. + call setline(1, 'A baord') + norm $1z= + call assert_equal('A board', getline(1)) + + call setline(1, 'A baord') + norm $2z= + call assert_equal('A bard', getline(1)) + + call setline(1, 'A baord') + norm $3z= + call assert_equal('A baord', getline(1)) + + let a = execute('norm $z=') + call assert_equal( + \ "\n" + \ .. "Change \"baord\" to:\n" + \ .. " 1 \"board\"\n" + \ .. " 2 \"bard\"\n" + \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + + set spell spellsuggest=0 + call assert_equal("\nSorry, no suggestions", execute('norm $z=')) + + " Unlike z=, function spellsuggest(...) should not be affected by the + " max number of suggestions (2) set by the 'spellsuggest' option. + call assert_equal(['board', 'bard', 'broad'], spellsuggest('baord', 3)) + + set spellsuggest& spell& + bwipe! +endfunc + +" Test 'spellsuggest' option with value expr:{expr} +func Test_spellsuggest_option_expr() + " A silly 'spellsuggest' function which makes suggestions all uppercase + " and makes the score of each suggestion the length of the suggested word. + " So shorter suggestions are preferred. + func MySuggest() + let spellsuggest_save = &spellsuggest + set spellsuggest=3,best + let result = map(spellsuggest(v:val, 3), "[toupper(v:val), len(v:val)]") + let &spellsuggest = spellsuggest_save + return result + endfunc + + set spell spellsuggest=expr:MySuggest() + call assert_equal(['BARD', 'BOARD', 'BROAD'], spellsuggest('baord', 3)) + + new + call setline(1, 'baord') + let a = execute('norm z=') + call assert_equal( + \ "\n" + \ .. "Change \"baord\" to:\n" + \ .. " 1 \"BARD\"\n" + \ .. " 2 \"BOARD\"\n" + \ .. " 3 \"BROAD\"\n" + \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + + " With verbose, z= should show the score i.e. word length with + " our SpellSuggest() function. + set verbose=1 + let a = execute('norm z=') + call assert_equal( + \ "\n" + \ .. "Change \"baord\" to:\n" + \ .. " 1 \"BARD\" (4 - 0)\n" + \ .. " 2 \"BOARD\" (5 - 0)\n" + \ .. " 3 \"BROAD\" (5 - 0)\n" + \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + + set spell& spellsuggest& verbose& + bwipe! +endfunc + func Test_spellinfo() throw 'skipped: Nvim does not support enc=latin1' new @@ -227,7 +407,7 @@ func Test_zz_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - call assert_equal("kepereneven", soundfold('koprnven')) + call assert_equal("kepereneven", soundfold('kóopërÿnôven')) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc @@ -408,7 +588,7 @@ func Test_zz_sal_and_addition() mkspell! Xtest Xtest set spl=Xtest.latin1.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) - call assert_equal('kprnfn', soundfold('koprnven')) + call assert_equal('kprnfn', soundfold('kóopërÿnôven')) call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file @@ -461,6 +641,34 @@ func Test_zeq_crash() bwipe! endfunc +" Check that z= works even when 'nospell' is set. This test uses one of the +" tests in Test_spellsuggest_option_number() just to verify that z= basically +" works and that "E756: Spell checking is not enabled" is not generated. +func Test_zeq_nospell() + new + set nospell spellsuggest=1,best + call setline(1, 'A baord') + try + norm $1z= + call assert_equal('A board', getline(1)) + catch + call assert_report("Caught exception: " . v:exception) + endtry + set spell& spellsuggest& + bwipe! +endfunc + +" Check that "E756: Spell checking is not possible" is reported when z= is +" executed and 'spelllang' is empty. +func Test_zeq_no_spelllang() + new + set spelllang= spellsuggest=1,best + call setline(1, 'A baord') + call assert_fails('normal $1z=', 'E756:') + set spelllang& spellsuggest& + bwipe! +endfunc + " Check handling a word longer than MAXWLEN. func Test_spell_long_word() set enc=utf-8 diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim new file mode 100644 index 0000000000..cafdb97f28 --- /dev/null +++ b/src/nvim/testdir/test_spell_utf8.vim @@ -0,0 +1,773 @@ +" Test for spell checking with 'encoding' set to utf-8 + +source check.vim +CheckFeature spell + +scriptencoding utf-8 + +func TearDown() + set nospell + call delete('Xtest.aff') + call delete('Xtest.dic') + call delete('Xtest.utf-8.add') + call delete('Xtest.utf-8.add.spl') + call delete('Xtest.utf-8.spl') + call delete('Xtest.utf-8.sug') +endfunc + +let g:test_data_aff1 = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ", + \"LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ", + \"UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ", + \"", + \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF", + \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF" + \ ] +let g:test_data_dic1 = [ + \"123456", + \"test/NO", + \"# comment", + \"wrong", + \"Comment", + \"OK", + \"uk", + \"put/ISO", + \"the end", + \"deol", + \"d\xE9\xF4r", + \ ] +let g:test_data_aff2 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out [a-z]", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_aff3 = [ + \"SET ISO8859-1", + \"", + \"COMPOUNDMIN 3", + \"COMPOUNDRULE m*", + \"NEEDCOMPOUND x", + \ ] +let g:test_data_dic3 = [ + \"1234", + \"foo/m", + \"bar/mx", + \"m\xEF/m", + \"la/mx", + \ ] +let g:test_data_aff4 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"COMPOUNDRULE m+", + \"COMPOUNDRULE sm*e", + \"COMPOUNDRULE sm+", + \"COMPOUNDMIN 3", + \"COMPOUNDWORDMAX 3", + \"COMPOUNDFORBIDFLAG t", + \"", + \"COMPOUNDSYLMAX 5", + \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"NEEDAFFIX x", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD '-", + \"", + \"SFX q N 1", + \"SFX q 0 -ok .", + \"", + \"SFX a Y 2", + \"SFX a 0 s .", + \"SFX a 0 ize/t .", + \"", + \"PFX p N 1", + \"PFX p 0 pre .", + \"", + \"PFX P N 1", + \"PFX P 0 nou .", + \ ] +let g:test_data_dic4 = [ + \"1234", + \"word/mP", + \"util/am", + \"pro/xq", + \"tomato/m", + \"bork/mp", + \"start/s", + \"end/e", + \ ] +let g:test_data_aff5 = [ + \"SET ISO8859-1", + \"", + \"FLAG long", + \"", + \"NEEDAFFIX !!", + \"", + \"COMPOUNDRULE ssmm*ee", + \"", + \"NEEDCOMPOUND xx", + \"COMPOUNDPERMITFLAG pp", + \"", + \"SFX 13 Y 1", + \"SFX 13 0 bork .", + \"", + \"SFX a1 Y 1", + \"SFX a1 0 a1 .", + \"", + \"SFX a\xE9 Y 1", + \"SFX a\xE9 0 a\xE9 .", + \"", + \"PFX zz Y 1", + \"PFX zz 0 pre/pp .", + \"", + \"PFX yy Y 1", + \"PFX yy 0 nou .", + \ ] +let g:test_data_dic5 = [ + \"1234", + \"foo/a1a\xE9!!", + \"bar/zz13ee", + \"start/ss", + \"end/eeyy", + \"middle/mmxx", + \ ] +let g:test_data_aff6 = [ + \"SET ISO8859-1", + \"", + \"FLAG caplong", + \"", + \"NEEDAFFIX A!", + \"", + \"COMPOUNDRULE sMm*Ee", + \"", + \"NEEDCOMPOUND Xx", + \"", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX N3 Y 1", + \"SFX N3 0 bork .", + \"", + \"SFX A1 Y 1", + \"SFX A1 0 a1 .", + \"", + \"SFX A\xE9 Y 1", + \"SFX A\xE9 0 a\xE9 .", + \"", + \"PFX Zz Y 1", + \"PFX Zz 0 pre/p .", + \ ] +let g:test_data_dic6 = [ + \"1234", + \"mee/A1A\xE9A!", + \"bar/ZzN3Ee", + \"lead/s", + \"end/Ee", + \"middle/MmXx", + \ ] +let g:test_data_aff7 = [ + \"SET ISO8859-1", + \"", + \"FLAG num", + \"", + \"NEEDAFFIX 9999", + \"", + \"COMPOUNDRULE 2,77*123", + \"", + \"NEEDCOMPOUND 1", + \"COMPOUNDPERMITFLAG 432", + \"", + \"SFX 61003 Y 1", + \"SFX 61003 0 meat .", + \"", + \"SFX 0 Y 1", + \"SFX 0 0 zero .", + \"", + \"SFX 391 Y 1", + \"SFX 391 0 a1 .", + \"", + \"SFX 111 Y 1", + \"SFX 111 0 a\xE9 .", + \"", + \"PFX 17 Y 1", + \"PFX 17 0 pre/432 .", + \ ] +let g:test_data_dic7 = [ + \"1234", + \"mee/0,391,111,9999", + \"bar/17,61003,123", + \"lead/2", + \"tail/123", + \"middle/77,1", + \ ] +let g:test_data_aff8 = [ + \"SET ISO8859-1", + \"", + \"NOSPLITSUGS", + \ ] +let g:test_data_dic8 = [ + \"1234", + \"foo", + \"bar", + \"faabar", + \ ] +let g:test_data_aff9 = [ + \ ] +let g:test_data_dic9 = [ + \"1234", + \"foo", + \"bar", + \ ] +let g:test_data_aff10 = [ + \"COMPOUNDRULE se", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX A Y 1", + \"SFX A 0 able/Mp .", + \"", + \"SFX M Y 1", + \"SFX M 0 s .", + \ ] +let g:test_data_dic10 = [ + \"1234", + \"drink/As", + \"table/e", + \ ] +let g:test_data_aff_sal = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"SAL AH(AEIOUY)-^ *H", + \"SAL AR(AEIOUY)-^ *R", + \"SAL A(HR)^ *", + \"SAL A^ *", + \"SAL AH(AEIOUY)- H", + \"SAL AR(AEIOUY)- R", + \"SAL A(HR) _", + \"SAL \xC0^ *", + \"SAL \xC5^ *", + \"SAL BB- _", + \"SAL B B", + \"SAL CQ- _", + \"SAL CIA X", + \"SAL CH X", + \"SAL C(EIY)- S", + \"SAL CK K", + \"SAL COUGH^ KF", + \"SAL CC< C", + \"SAL C K", + \"SAL DG(EIY) K", + \"SAL DD- _", + \"SAL D T", + \"SAL \xC9< E", + \"SAL EH(AEIOUY)-^ *H", + \"SAL ER(AEIOUY)-^ *R", + \"SAL E(HR)^ *", + \"SAL ENOUGH^$ *NF", + \"SAL E^ *", + \"SAL EH(AEIOUY)- H", + \"SAL ER(AEIOUY)- R", + \"SAL E(HR) _", + \"SAL FF- _", + \"SAL F F", + \"SAL GN^ N", + \"SAL GN$ N", + \"SAL GNS$ NS", + \"SAL GNED$ N", + \"SAL GH(AEIOUY)- K", + \"SAL GH _", + \"SAL GG9 K", + \"SAL G K", + \"SAL H H", + \"SAL IH(AEIOUY)-^ *H", + \"SAL IR(AEIOUY)-^ *R", + \"SAL I(HR)^ *", + \"SAL I^ *", + \"SAL ING6 N", + \"SAL IH(AEIOUY)- H", + \"SAL IR(AEIOUY)- R", + \"SAL I(HR) _", + \"SAL J K", + \"SAL KN^ N", + \"SAL KK- _", + \"SAL K K", + \"SAL LAUGH^ LF", + \"SAL LL- _", + \"SAL L L", + \"SAL MB$ M", + \"SAL MM M", + \"SAL M M", + \"SAL NN- _", + \"SAL N N", + \"SAL OH(AEIOUY)-^ *H", + \"SAL OR(AEIOUY)-^ *R", + \"SAL O(HR)^ *", + \"SAL O^ *", + \"SAL OH(AEIOUY)- H", + \"SAL OR(AEIOUY)- R", + \"SAL O(HR) _", + \"SAL PH F", + \"SAL PN^ N", + \"SAL PP- _", + \"SAL P P", + \"SAL Q K", + \"SAL RH^ R", + \"SAL ROUGH^ RF", + \"SAL RR- _", + \"SAL R R", + \"SAL SCH(EOU)- SK", + \"SAL SC(IEY)- S", + \"SAL SH X", + \"SAL SI(AO)- X", + \"SAL SS- _", + \"SAL S S", + \"SAL TI(AO)- X", + \"SAL TH @", + \"SAL TCH-- _", + \"SAL TOUGH^ TF", + \"SAL TT- _", + \"SAL T T", + \"SAL UH(AEIOUY)-^ *H", + \"SAL UR(AEIOUY)-^ *R", + \"SAL U(HR)^ *", + \"SAL U^ *", + \"SAL UH(AEIOUY)- H", + \"SAL UR(AEIOUY)- R", + \"SAL U(HR) _", + \"SAL V^ W", + \"SAL V F", + \"SAL WR^ R", + \"SAL WH^ W", + \"SAL W(AEIOU)- W", + \"SAL X^ S", + \"SAL X KS", + \"SAL Y(AEIOU)- Y", + \"SAL ZZ- _", + \"SAL Z S", + \ ] + +func LoadAffAndDic(aff_contents, dic_contents) + set enc=utf-8 + set spellfile= + call writefile(a:aff_contents, "Xtest.aff") + call writefile(a:dic_contents, "Xtest.dic") + " Generate a .spl file from a .dic and .aff file. + mkspell! Xtest Xtest + " use that spell file + set spl=Xtest.utf-8.spl spell +endfunc + +func ListWords() + spelldump + %yank + quit + return split(@", "\n") +endfunc + +func TestGoodBadBase() + exe '1;/^good:' + normal 0f:]s + let prevbad = '' + let result = [] + while 1 + let [bad, a] = spellbadword() + if bad == '' || bad == prevbad || bad == 'badend' + break + endif + let prevbad = bad + " let lst = bad->spellsuggest(3) + let lst = spellsuggest(bad, 3) + normal mm + + call add(result, [bad, lst]) + normal `m]s + endwhile + return result +endfunc + +func RunGoodBad(good, bad, expected_words, expected_bad_words) + %bwipe! + call setline(1, ['', "good: ", a:good, a:bad, " badend "]) + let words = ListWords() + call assert_equal(a:expected_words, words[1:-1]) + let bad_words = TestGoodBadBase() + call assert_equal(a:expected_bad_words, bad_words) + %bwipe! +endfunc + +func Test_spell_basic() + call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1) + call RunGoodBad("wrong OK puts. Test the end", + \ "bad: inputs comment ok Ok. test d\u00E9\u00F4l end the", + \["Comment", "deol", "d\u00E9\u00F4r", "input", "OK", "output", "outputs", "outtest", "put", "puts", + \ "test", "testen", "testn", "the end", "uk", "wrong"], + \[ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment", "outtest", "the end"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]], + \ ["end", ["put", "uk", "test"]], + \ ["the", ["put", "uk", "test"]], + \ ] + \ ) + + call assert_equal("gebletegek", soundfold('goobledygoook')) + " call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) + call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) +endfunc + +" Postponed prefixes +func Test_spell_prefixes() + call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1) + call RunGoodBad("puts", + \ "bad: inputs comment ok Ok end the. test d\u00E9\u00F4l", + \ ["Comment", "deol", "d\u00E9\u00F4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"], + \ [ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["end", ["put", "uk", "deol"]], + \ ["the", ["put", "uk", "test"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]], + \ ]) +endfunc + +"Compound words +func Test_spell_compound() + throw 'skipped: TODO: ' + call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3) + call RunGoodBad("foo m\u00EF foobar foofoobar barfoo barbarfoo", + \ "bad: bar la foom\u00EF barm\u00EF m\u00EFfoo m\u00EFbar m\u00EFm\u00EF lala m\u00EFla lam\u00EF foola labar", + \ ["foo", "m\u00EF"], + \ [ + \ ["bad", ["foo", "m\u00EF"]], + \ ["bar", ["barfoo", "foobar", "foo"]], + \ ["la", ["m\u00EF", "foo"]], + \ ["foom\u00EF", ["foo m\u00EF", "foo", "foofoo"]], + \ ["barm\u00EF", ["barfoo", "m\u00EF", "barbar"]], + \ ["m\u00EFfoo", ["m\u00EF foo", "foo", "foofoo"]], + \ ["m\u00EFbar", ["foobar", "barbar", "m\u00EF"]], + \ ["m\u00EFm\u00EF", ["m\u00EF m\u00EF", "m\u00EF"]], + \ ["lala", []], + \ ["m\u00EFla", ["m\u00EF", "m\u00EF m\u00EF"]], + \ ["lam\u00EF", ["m\u00EF", "m\u00EF m\u00EF"]], + \ ["foola", ["foo", "foobar", "foofoo"]], + \ ["labar", ["barbar", "foobar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4) + call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword", + \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword", + \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"], + \ [ + \ ["bad", ["end", "bork", "word"]], + \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]], + \ ["pro", ["bork", "word", "end"]], + \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]], + \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]], + \ ["endstart", ["end start", "start"]], + \ ["endend", ["end end", "end"]], + \ ["startstart", ["start start"]], + \ ["wordend", ["word end", "word", "wordword"]], + \ ["wordstart", ["word start", "bork start"]], + \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]], + \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]], + \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]], + \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]], + \ ["utilsbork", ["utilbork", "utils bork", "util bork"]], + \ ["startnouword", ["start nouword", "startword", "startborkword"]], + \ ]) + +endfunc + +" Test affix flags with two characters +func Test_spell_affix() + throw 'skipped: TODO: ' + call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5) + call RunGoodBad("fooa1 fooa\u00E9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend", + \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend", + \ ["bar", "barbork", "end", "fooa1", "fooa\u00E9", "nouend", "prebar", "prebarbork", "start"], + \ [ + \ ["bad", ["bar", "end", "fooa1"]], + \ ["foo", ["fooa1", "fooa\u00E9", "bar"]], + \ ["fooa2", ["fooa1", "fooa\u00E9", "bar"]], + \ ["prabar", ["prebar", "bar", "bar bar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["startmiddle", ["startmiddleend", "startmiddlebar"]], + \ ["middleend", []], + \ ["endstart", ["end start", "start"]], + \ ["startprobar", ["startprebar", "start prebar", "startbar"]], + \ ["startnouend", ["start nouend", "startend"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6) + call RunGoodBad("meea1 meea\u00E9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend", + \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar", + \ ["bar", "barbork", "end", "lead", "meea1", "meea\u00E9", "prebar", "prebarbork"], + \ [ + \ ["bad", ["bar", "end", "lead"]], + \ ["mee", ["meea1", "meea\u00E9", "bar"]], + \ ["meea2", ["meea1", "meea\u00E9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]], + \ ["middleend", []], + \ ["endlead", ["end lead", "lead", "end end"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) + call RunGoodBad("meea1 meezero meea\u00E9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", + \ ["bar", "barmeat", "lead", "meea1", "meea\u00E9", "meezero", "prebar", "prebarmeat", "tail"], + \ [ + \ ["bad", ["bar", "lead", "tail"]], + \ ["mee", ["meea1", "meea\u00E9", "bar"]], + \ ["meea2", ["meea1", "meea\u00E9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarmaat", ["prebarmeat"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddlebar"]], + \ ["middletail", []], + \ ["taillead", ["tail lead", "tail"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) +endfunc + +func Test_spell_NOSLITSUGS() + call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8) + call RunGoodBad("foo bar faabar", "bad: foobar barfoo", + \ ["bar", "faabar", "foo"], + \ [ + \ ["bad", ["bar", "foo"]], + \ ["foobar", ["faabar", "foo bar", "bar"]], + \ ["barfoo", ["bar foo", "bar", "foo"]], + \ ]) +endfunc + +" Numbers +func Test_spell_Numbers() + call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9) + call RunGoodBad("0b1011 0777 1234 0x01ff", "", + \ ["bar", "foo"], + \ [ + \ ]) +endfunc + +" Affix flags +func Test_spell_affix_flags() + throw 'skipped: TODO: ' + call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10) + call RunGoodBad("drink drinkable drinkables drinktable drinkabletable", + \ "bad: drinks drinkstable drinkablestable", + \ ["drink", "drinkable", "drinkables", "table"], + \ [['bad', []], + \ ['drinks', ['drink']], + \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']], + \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']], + \ ]) +endfunc + +function FirstSpellWord() + call feedkeys("/^start:\n", 'tx') + normal ]smm + let [str, a] = spellbadword() + return str +endfunc + +function SecondSpellWord() + normal `m]s + let [str, a] = spellbadword() + return str +endfunc + +" Test with SAL instead of SOFO items; test automatic reloading +func Test_spell_sal_and_addition() + set spellfile= + call writefile(g:test_data_dic1, "Xtest.dic") + call writefile(g:test_data_aff_sal, "Xtest.aff") + mkspell! Xtest Xtest + set spl=Xtest.utf-8.spl spell + call assert_equal('kbltykk', soundfold('goobledygoook')) + call assert_equal('kprnfn', soundfold('kóopërÿnôven')) + call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) + + "also use an addition file + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add") + mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add + + bwipe! + call setline(1, ["start: elequint test elekwint test elekwent asdf"]) + + set spellfile=Xtest.utf-8.add + call assert_equal("elekwent", FirstSpellWord()) + + set spl=Xtest_us.utf-8.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) + + set spl=Xtest_gb.utf-8.spl + call assert_equal("elekwint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_nz.utf-8.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_ca.utf-8.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) +endfunc + +func Test_spellfile_value() + set spellfile=Xdir/Xtest.utf-8.add + set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim new file mode 100644 index 0000000000..729467b556 --- /dev/null +++ b/src/nvim/testdir/test_spellfile.vim @@ -0,0 +1,240 @@ +" Test for commands that operate on the spellfile. + +source shared.vim +source check.vim + +CheckFeature spell +CheckFeature syntax + +func Test_spell_normal() + new + call append(0, ['1 good', '2 goood', '3 goood']) + set spell spellfile=./Xspellfile.add spelllang=en + let oldlang=v:lang + lang C + + " Test for zg + 1 + norm! ]s + call assert_equal('2 goood', getline('.')) + norm! zg + 1 + let a=execute('unsilent :norm! ]s') + call assert_equal('1 good', getline('.')) + call assert_equal('search hit BOTTOM, continuing at TOP', a[1:]) + let cnt=readfile('./Xspellfile.add') + call assert_equal('goood', cnt[0]) + + " Test for zw + 2 + norm! $zw + 1 + norm! ]s + call assert_equal('2 goood', getline('.')) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + call assert_equal('goood/!', cnt[1]) + + " Test for :spellrare + spellrare rare + let cnt=readfile('./Xspellfile.add') + call assert_equal(['#oood', 'goood/!', 'rare/?'], cnt) + + " Make sure :spellundo works for rare words. + spellundo rare + let cnt=readfile('./Xspellfile.add') + call assert_equal(['#oood', 'goood/!', '#are/?'], cnt) + + " Test for zg in visual mode + let a=execute('unsilent :norm! V$zg') + call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) + 1 + norm! ]s + call assert_equal('3 goood', getline('.')) + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood', cnt[3]) + " Remove "2 good" from spellfile + 2 + let a=execute('unsilent norm! V$zw') + call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood/!', cnt[4]) + + " Test for zG + let a=execute('unsilent norm! V$zG') + call assert_match("Word '2 goood' added to .*", a) + let fname=matchstr(a, 'to\s\+\zs\f\+$') + let cnt=readfile(fname) + call assert_equal('2 goood', cnt[0]) + + " Test for zW + let a=execute('unsilent norm! V$zW') + call assert_match("Word '2 goood' added to .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('2 goood/!', cnt[1]) + + " Test for zuW + let a=execute('unsilent norm! V$zuW') + call assert_match("Word '2 goood' removed from .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + + " Test for zuG + let a=execute('unsilent norm! $zG') + call assert_match("Word 'goood' added to .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('goood', cnt[2]) + let a=execute('unsilent norm! $zuG') + let cnt=readfile(fname) + call assert_match("Word 'goood' removed from .*", a) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('#oood', cnt[2]) + " word not found in wordlist + let a=execute('unsilent norm! V$zuG') + let cnt=readfile(fname) + call assert_match("", a) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('#oood', cnt[2]) + + " Test for zug + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! $zg') + let cnt=readfile('./Xspellfile.add') + call assert_equal('goood', cnt[0]) + let a=execute('unsilent norm! $zug') + call assert_match("Word 'goood' removed from \./Xspellfile.add", a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + " word not in wordlist + let a=execute('unsilent norm! V$zug') + call assert_match('', a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + + " Test for zuw + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! Vzw') + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood/!', cnt[0]) + let a=execute('unsilent norm! Vzuw') + call assert_match("Word '2 goood' removed from \./Xspellfile.add", a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('# goood/!', cnt[0]) + " word not in wordlist + let a=execute('unsilent norm! $zug') + call assert_match('', a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('# goood/!', cnt[0]) + + " add second entry to spellfile setting + set spellfile=./Xspellfile.add,./Xspellfile2.add + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! $2zg') + let cnt=readfile('./Xspellfile2.add') + call assert_match("Word 'goood' added to ./Xspellfile2.add", a) + call assert_equal('goood', cnt[0]) + + " Test for :spellgood! + let temp = execute(':spe!0/0') + call assert_match('Invalid region', temp) + let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0') + call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile)) + + " Test for :spellrare! + :spellrare! raare + call assert_equal(['# goood', '# goood/!', '#oood', '0/0', 'raare/?'], readfile(spellfile)) + call delete(spellfile) + + " clean up + exe "lang" oldlang + call delete("./Xspellfile.add") + call delete("./Xspellfile2.add") + call delete("./Xspellfile.add.spl") + call delete("./Xspellfile2.add.spl") + + " zux -> no-op + 2 + norm! $zux + call assert_equal([], glob('Xspellfile.add',0,1)) + call assert_equal([], glob('Xspellfile2.add',0,1)) + + set spellfile= + bw! +endfunc + +" Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN) +func Test_spellfile_CHECKCOMPOUNDPATTERN() + call writefile(['4', + \ 'one/c', + \ 'two/c', + \ 'three/c', + \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic') + " Forbid compound words where first word ends with 'wo' and second starts with 'on'. + call writefile(['CHECKCOMPOUNDPATTERN 1', + \ 'CHECKCOMPOUNDPATTERN wo on', + \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff') + + let output = execute('mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN') + set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl + + " Check valid words with and without valid compounds. + for goodword in ['one', 'two', 'three', 'four', + \ 'oneone', 'onetwo', 'onethree', + \ 'twotwo', 'twothree', + \ 'threeone', 'threetwo', 'threethree', + \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone'] + call assert_equal(['', ''], spellbadword(goodword), goodword) + endfor + + " Compounds 'twoone' or 'threetwoone' should be forbidden by CHECKCOMPOUNPATTERN. + " 'four' does not have the 'c' flag in *.aff file so no compound. + " 'five' is not in the *.dic file. + for badword in ['five', 'onetwox', + \ 'twoone', 'threetwoone', + \ 'fourone', 'onefour'] + call assert_equal([badword, 'bad'], spellbadword(badword)) + endfor + + set spell& spelllang& + call delete('XtestCHECKCOMPOUNDPATTERN.dic') + call delete('XtestCHECKCOMPOUNDPATTERN.aff') + call delete('XtestCHECKCOMPOUNDPATTERN-utf8.spl') +endfunc + +" Test COMMON (better suggestions with common words, see :help spell-COMMON) +func Test_spellfile_COMMON() + call writefile(['7', + \ 'and', + \ 'ant', + \ 'end', + \ 'any', + \ 'tee', + \ 'the', + \ 'ted'], 'XtestCOMMON.dic') + call writefile(['COMMON the and'], 'XtestCOMMON.aff') + + let output = execute('mkspell! XtestCOMMON-utf8.spl XtestCOMMON') + set spell spelllang=XtestCOMMON-utf8.spl + + " COMMON words 'and' and 'the' should be the top suggestions. + call assert_equal(['and', 'ant'], spellsuggest('anr', 2)) + call assert_equal(['and', 'end'], spellsuggest('ond', 2)) + call assert_equal(['the', 'ted'], spellsuggest('tha', 2)) + call assert_equal(['the', 'tee'], spellsuggest('dhe', 2)) + + set spell& spelllang& + call delete('XtestCOMMON.dic') + call delete('XtestCOMMON.aff') + call delete('XtestCOMMON-utf8.spl') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim index 1b3d2184a0..bb4304396e 100644 --- a/src/nvim/testdir/test_startup_utf8.vim +++ b/src/nvim/testdir/test_startup_utf8.vim @@ -1,5 +1,6 @@ " Tests for startup using utf-8. +source check.vim source shared.vim source screendump.vim @@ -73,7 +74,7 @@ func Test_detect_ambiwidth() \ 'call test_option_not_set("ambiwidth")', \ 'redraw', \ ], 'Xscript') - let buf = RunVimInTerminal('-S Xscript', {}) + let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1}) call term_wait(buf) call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>") call WaitForAssert({-> assert_match('single', term_getline(buf, 1))}) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 30c0758446..658dfbda60 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -526,12 +526,25 @@ describe('runtime:', function() local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep) local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep) + local profiler_file = 'test_startuptime.log' + mkdir_p(plugin_folder_path) write_file(plugin_file_path, [[vim.g.lua_plugin = 2]]) - clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig }} + clear{ args_rm={'-u'}, args={'--startuptime', profiler_file}, env={ XDG_CONFIG_HOME=xconfig }} eq(2, eval('g:lua_plugin')) + -- Check if plugin_file_path is listed in :scriptname + local scripts = meths.exec(':scriptnames', true) + assert.Truthy(scripts:find(plugin_file_path)) + + -- Check if plugin_file_path is listed in startup profile + local profile_reader = io.open(profiler_file, 'r') + local profile_log = profile_reader:read('*a') + profile_reader:close() + assert.Truthy(profile_log :find(plugin_file_path)) + + os.remove(profiler_file) rmdir(plugin_path) end) diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua new file mode 100644 index 0000000000..e09d93f7cc --- /dev/null +++ b/test/functional/plugin/lsp/codelens_spec.lua @@ -0,0 +1,62 @@ +local helpers = require('test.functional.helpers')(after_each) + +local exec_lua = helpers.exec_lua +local eq = helpers.eq + +describe('vim.lsp.codelens', function() + before_each(function() + helpers.clear() + exec_lua('require("vim.lsp")') + end) + after_each(helpers.clear) + + it('on_codelens_stores_and_displays_lenses', function() + local fake_uri = "file://fake/uri" + local bufnr = exec_lua([[ + fake_uri = ... + local bufnr = vim.uri_to_bufnr(fake_uri) + local lines = {'So', 'many', 'lines'} + vim.fn.bufload(bufnr) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + return bufnr + ]], fake_uri) + + exec_lua([[ + local bufnr = ... + local lenses = { + { + range = { + start = { line = 0, character = 0, }, + ['end'] = { line = 0, character = 0 } + }, + command = { title = 'Lens1', command = 'Dummy' } + }, + } + vim.lsp.codelens.on_codelens(nil, 'textDocument/codeLens', lenses, 1, bufnr) + ]], bufnr) + + local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + local expected = { + { + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 } + }, + command = { + title = 'Lens1', + command = 'Dummy', + }, + }, + } + eq(expected, stored_lenses) + + local virtual_text_chunks = exec_lua([[ + local bufnr = ... + local ns = vim.lsp.codelens.__namespaces[1] + local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {}) + return vim.api.nvim_buf_get_extmark_by_id(bufnr, ns, extmarks[1][1], { details = true })[3].virt_text + ]], bufnr) + + eq({[1] = {'Lens1', 'LspCodeLens'}}, virtual_text_chunks) + end) +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 3a9dcb8b01..9fa0ad08f1 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -711,6 +711,49 @@ describe('float window', function() ]]} end + meths.win_set_config(win, {border="rounded"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╭─────────╮}| + {5:│}{1: halloj! }{5:│}| + {5:│}{1: BORDAA }{5:│}| + {5:╰─────────╯}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╭─────────╮}{0: }| + {0:~ }{5:│}{1: halloj! }{5:│}{0: }| + {0:~ }{5:│}{1: BORDAA }{5:│}{0: }| + {0:~ }{5:╰─────────╯}{0: }| + | + ]]} + end + meths.win_set_config(win, {border="solid"}) if multigrid then screen:expect{grid=[[ diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 72468392ee..c238898244 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -811,7 +811,7 @@ describe('ui/ext_messages', function() {1:~ }| {1:^~ }| ]], messages={ - {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""} + {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""} }} feed('1') @@ -822,7 +822,7 @@ describe('ui/ext_messages', function() {1:~ }| {1:^~ }| ]], messages={ - {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""}, + {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""}, { content = { { "1" } }, kind = "" } }} diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua index 2c6e586665..de77100cc0 100644 --- a/test/functional/ui/spell_spec.lua +++ b/test/functional/ui/spell_spec.lua @@ -36,7 +36,7 @@ describe("'spell'", function() feed('ggJJJJJJ0') screen:expect([[ {1:^Lorem} {1:ipsum} dolor sit {1:amet}, {1:consectetur} {1:adipiscing} {1:elit}, {1:sed} do {1:eiusmod} {1:tempor} {1:i}| - {1:ncididunt} {1:ut} {1:labore} {1:et} {1:dolore} {1:magna} {1:aliqua}. {1:Ut} {1:enim} ad minim {1:veniam}, {1:quis} {1:nostru}| + {1:ncididunt} {1:ut} {1:labore} et {1:dolore} {1:magna} {1:aliqua}. {1:Ut} {1:enim} ad minim {1:veniam}, {1:quis} {1:nostru}| {1:d} {1:exercitation} {1:ullamco} {1:laboris} {1:nisi} {1:ut} {1:aliquip} ex ea {1:commodo} {1:consequat}. {1:Duis} {1:aut}| {1:e} {1:irure} dolor in {1:reprehenderit} in {1:voluptate} {1:velit} {1:esse} {1:cillum} {1:dolore} {1:eu} {1:fugiat} {1:n}| {1:ulla} {1:pariatur}. {1:Excepteur} {1:sint} {1:occaecat} {1:cupidatat} non {1:proident}, {1:sunt} in culpa {1:qui}| @@ -44,6 +44,7 @@ describe("'spell'", function() {0:~ }| | ]]) + end) it('has correct highlight at start of line', function() diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index df7f1a0ac2..f9da7c497a 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -21,7 +21,17 @@ if(HAS_OG_FLAG) set(DEFAULT_MAKE_CFLAGS CFLAGS+=-Og ${DEFAULT_MAKE_CFLAGS}) endif() -set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.") +if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + # pkg-config 29.2 has a bug on OpenBSD which causes it to drop any paths that + # *contain* system include paths. To avoid this, we prefix what would be + # "/usr/include" as "/_usr/include". + # This check is also performed in the root CMakeLists.txt + # https://github.com/neovim/neovim/pull/14745#issuecomment-860201794 + set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/_usr" CACHE PATH "Dependencies install directory.") +else() + set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.") +endif() + set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin" CACHE PATH "Dependencies binary install directory.") set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib" CACHE PATH "Dependencies library install directory.") set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build" CACHE PATH "Dependencies build directory.") |