diff options
66 files changed, 1672 insertions, 334 deletions
diff --git a/contrib/flake.nix b/contrib/flake.nix index 08126a48e9..c86bba6809 100644 --- a/contrib/flake.nix +++ b/contrib/flake.nix @@ -42,12 +42,16 @@ disallowedReferences = []; })); - # for neovim developers, very slow + # for neovim developers, builds a slow binary + # huge closure size but aims at covering all scripts # brings development tools as well neovim-developer = let lib = nixpkgs.lib; - pythonEnv = pkgs.python3; + pythonEnv = pkgs.python3.withPackages(ps: [ + ps.msgpack + ps.flake8 # for 'make pylint' + ]); luacheck = pkgs.luaPackages.luacheck; in (neovim-debug.override ({ doCheck = pkgs.stdenv.isLinux; })).overrideAttrs (oa: { @@ -56,7 +60,7 @@ "-DMIN_LOG_LEVEL=0" "-DENABLE_LTO=OFF" "-DUSE_BUNDLED=OFF" - ] ++ pkgs.stdenv.lib.optionals pkgs.stdenv.isLinux [ + ] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags # https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports "-DCLANG_ASAN_UBSAN=ON" @@ -66,7 +70,8 @@ pythonEnv include-what-you-use # for scripts/check-includes.py jq # jq for scripts/vim-patch.sh -r - doxygen + shellcheck # for `make shlint` + doxygen # for script/gen_vimdoc.py ]); shellHook = oa.shellHook + '' @@ -102,6 +107,5 @@ defaultApp = apps.nvim; devShell = pkgs.neovim-developer; - } - ); + }); } diff --git a/runtime/autoload/haskellcomplete.vim b/runtime/autoload/haskellcomplete.vim index 520ab93700..48fbac7f9f 100644 --- a/runtime/autoload/haskellcomplete.vim +++ b/runtime/autoload/haskellcomplete.vim @@ -2,7 +2,7 @@ " Language: Haskell " Maintainer: Daniel Campoverde <alx@sillybytes.net> " URL: https://github.com/alx741/haskellcomplete.vim -" Last Change: 2018 Aug 26 +" Last Change: 2019 May 14 " Usage: setlocal omnifunc=haskellcomplete#Complete @@ -63,6 +63,7 @@ function! haskellcomplete#Complete(findstart, base) call add(l:matches, extension) endif endfor + let b:completingLangExtension = 0 return l:matches endif @@ -78,6 +79,7 @@ function! haskellcomplete#Complete(findstart, base) call add(l:matches, flag) endif endfor + let b:completingOptionsGHC = 0 return l:matches endif @@ -93,6 +95,7 @@ function! haskellcomplete#Complete(findstart, base) call add(l:matches, module) endif endfor + let b:completingModule = 0 return l:matches endif diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index fdc41af1d5..be4197f5ea 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1415,9 +1415,9 @@ nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()* • "c" |charwise| mode • "l" |linewise| mode • "" guess by contents, see |setreg()| - {after} Insert after cursor (like |p|), or before (like - |P|). - {follow} Place cursor at end of inserted text. + {after} If true insert after cursor (like |p|), or + before (like |P|). + {follow} If true place cursor at end of inserted text. *nvim_replace_termcodes()* nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) @@ -1737,7 +1737,7 @@ nvim__buf_stats({buffer}) *nvim__buf_stats()* TODO: Documentation *nvim_buf_add_highlight()* -nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, {col_start}, +nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, {col_end}) Adds a highlight to buffer. @@ -2202,6 +2202,19 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) column, without shifting the underlying text. + • virt_text_hide : hide the virtual text when + the background text is selected or hidden due + to horizontal scroll 'nowrap' + • hl_mode : control how highlights are combined + with the highlights of the text. Currently + only affects virt_text highlights, but might + affect`hl_group`in later versions. + • "replace": only show the virt_text color. + This is the default + • "combine": combine with background text + color + • "blend": blend with background text color. + • ephemeral : for use with |nvim_set_decoration_provider| callbacks. The mark will only be used for the current redraw diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index d2d88fb9ba..061a851157 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -749,15 +749,6 @@ start_client({config}) *vim.lsp.start_client()* The following parameters describe fields in the {config} table. -> - - -- In attach function for the client, you can do: - local custom_attach = function(client) - if client.config.flags then - client.config.flags.allow_incremental_sync = true - end - end -< Parameters: ~ {root_dir} (required, string) Directory where the @@ -799,6 +790,8 @@ start_client({config}) *vim.lsp.start_client()* See `initialize` in the LSP spec. {name} (string, default=client-id) Name in log messages. + {get_language_id} function(bufnr, filetype) -> language + ID as string. Defaults to the filetype. {offset_encoding} (default="utf-16") One of "utf-8", "utf-16", or "utf-32" which is the encoding that the LSP server expects. @@ -854,8 +847,8 @@ start_client({config}) *vim.lsp.start_client()* {flags} A table with flags for the client. The current (experimental) flags are: • allow_incremental_sync (bool, default - false): Allow using on_line callbacks - for lsp + true): Allow using incremental sync + for buffer edits Return: ~ Client id. |vim.lsp.get_client_by_id()| Note: client may @@ -1469,8 +1462,8 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) ============================================================================== Lua module: vim.lsp.handlers *lsp-handlers* - *vim.lsp.handlers.progress_callback()* -progress_callback({_}, {_}, {params}, {client_id}) + *vim.lsp.handlers.progress_handler()* +progress_handler({_}, {_}, {params}, {client_id}) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand @@ -1547,6 +1540,18 @@ close_preview_autocmd({events}, {winnr}) See also: ~ |autocmd-events| + *vim.lsp.util.compute_diff()* +compute_diff({old_lines}, {new_lines}, {start_line_idx}, {end_line_idx}) + Returns the range table for the difference between old and new + lines + + Parameters: ~ + {old_lines} table list of lines + {new_lines} table list of lines + + Return: ~ + table start_line_idx and start_col_idx of range + *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) Converts any of `MarkedString` | `MarkedString[]` | diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 0bbed56662..c2fc25431c 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1055,6 +1055,18 @@ list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* See also: ~ |vim.tbl_extend()| +list_slice({list}, {start}, {finish}) *vim.list_slice()* + Creates a copy of a table containing only elements from start + to end (inclusive) + + Parameters: ~ + {list} table table + {start} integer Start range of slice + {finish} integer End range of slice + + Return: ~ + Copy of table sliced from start to finish (inclusive) + pesc({s}) *vim.pesc()* Escapes magic chars in a Lua pattern. diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index bec2b362ea..5885b20ab7 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -312,6 +312,8 @@ Other commands ~ *:Program* jump to the window with the running program *:Source* jump to the window with the source code, create it if there isn't one + *:Asm* jump to the window with the disassembly, create it if there + isn't one Prompt mode ~ @@ -330,6 +332,12 @@ This works slightly differently: Prompt mode can be used even when the |+terminal| feature is present with: > let g:termdebug_use_prompt = 1 +< + *termdebug_disasm_window* +If you want the Asm window shown by default, set this to 1. Setting to +any value greater than 1 will set the Asm window height to that value: > + let g:termdebug_disasm_window = 15 +< Communication ~ *termdebug-communication* diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 6a13e67ac5..4130db2534 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1330,6 +1330,9 @@ au BufNewFile,BufRead *.pml setf promela au BufNewFile,BufRead *.proto setf proto au BufNewFile,BufRead *.pbtxt setf pbtxt +" Poke +au BufNewFile,BufRead *.pk setf poke + " Protocols au BufNewFile,BufRead */etc/protocols setf protocols diff --git a/runtime/ftplugin/lsp_markdown.vim b/runtime/ftplugin/lsp_markdown.vim new file mode 100644 index 0000000000..d3244fc955 --- /dev/null +++ b/runtime/ftplugin/lsp_markdown.vim @@ -0,0 +1,11 @@ +" Vim filetype plugin +" Language: lsp_markdown +" Maintainer: Michael Lingelbach <m.j.lbach@gmail.com> +" Last Change: 2021 Mar 09 + +if exists("b:did_ftplugin") + finish +endif + +runtime! ftplugin/markdown.vim +" vim:set sw=2: diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fdff8e3661..f0845bfc80 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -42,6 +42,8 @@ lsp._request_name_to_capability = { ['textDocument/prepareCallHierarchy'] = 'call_hierarchy'; ['textDocument/rename'] = 'rename'; ['textDocument/codeAction'] = 'code_action'; + ['textDocument/codeLens'] = 'code_lens'; + ['codeLens/resolve'] = 'code_lens_resolve'; ['workspace/executeCommand'] = 'execute_command'; ['textDocument/references'] = 'find_references'; ['textDocument/rangeFormatting'] = 'document_range_formatting'; @@ -228,6 +230,7 @@ local function validate_client_config(config) before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; flags = { config.flags, "t", true }; + get_language_id = { config.get_language_id, "f", true }; } local cmd, cmd_args = lsp._cmd_parts(config.cmd) @@ -262,18 +265,29 @@ end --@param bufnr (Number) Number of the buffer, or 0 for current --@param client Client object local function text_document_did_open_handler(bufnr, client) + local use_incremental_sync = ( + if_nil(client.config.flags.allow_incremental_sync, true) + and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental + ) + if use_incremental_sync then + if not client._cached_buffers then + client._cached_buffers = {} + end + client._cached_buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true) + end if not client.resolved_capabilities.text_document_open_close then return end if not vim.api.nvim_buf_is_loaded(bufnr) then return end + local filetype = nvim_buf_get_option(bufnr, 'filetype') + local params = { textDocument = { version = 0; uri = vim.uri_from_bufnr(bufnr); - -- TODO make sure our filetypes are compatible with languageId names. - languageId = nvim_buf_get_option(bufnr, 'filetype'); + languageId = client.config.get_language_id(bufnr, filetype); text = buf_get_full_text(bufnr); } } @@ -400,6 +414,9 @@ end --- --@param name (string, default=client-id) Name in log messages. --- +--@param get_language_id function(bufnr, filetype) -> language ID as string. +--- Defaults to the filetype. +--- --@param offset_encoding (default="utf-16") One of "utf-8", "utf-16", --- or "utf-32" which is the encoding that the LSP server expects. Client does --- not verify this is correct. @@ -438,16 +455,7 @@ end --@param trace: "off" | "messages" | "verbose" | nil passed directly to the language --- server in the initialize request. Invalid/empty values will default to "off" --@param flags: A table with flags for the client. The current (experimental) flags are: ---- - allow_incremental_sync (bool, default false): Allow using on_line callbacks for lsp ---- ---- <pre> ---- -- In attach function for the client, you can do: ---- local custom_attach = function(client) ---- if client.config.flags then ---- client.config.flags.allow_incremental_sync = true ---- end ---- end ---- </pre> +--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits --- --@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be --- fully initialized. Use `on_init` to do any actions once @@ -459,6 +467,11 @@ function lsp.start_client(config) config.flags = config.flags or {} config.settings = config.settings or {} + -- By default, get_language_id just returns the exact filetype it is passed. + -- It is possible to pass in something that will calculate a different filetype, + -- to be sent by the client. + config.get_language_id = config.get_language_id or function(_, filetype) return filetype end + local client_id = next_client_id() local handlers = config.handlers or {} @@ -542,7 +555,7 @@ function lsp.start_client(config) client_ids[client_id] = nil end - if code ~= 0 or signal ~= 0 then + if code ~= 0 or (signal ~= 0 and signal ~= 15) then local msg = string.format("Client %s quit with exit code %s and signal %s", client_id, code, signal) vim.schedule(function() vim.notify(msg, vim.log.levels.WARN) @@ -808,7 +821,6 @@ end --- Notify all attached clients that a buffer has changed. local text_document_did_change_handler do - local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; } text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) @@ -827,23 +839,12 @@ do util.buf_versions[bufnr] = changedtick -- Lazy initialize these because clients may not even need them. local incremental_changes = once(function(client) - local size_index = encoding_index[client.offset_encoding] - local length = select(size_index, old_byte_size, old_utf16_size, old_utf32_size) - local lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true) - - -- This is necessary because we are specifying the full line including the - -- newline in range. Therefore, we must replace the newline as well. - if #lines > 0 then - table.insert(lines, '') - end - return { - range = { - start = { line = firstline, character = 0 }; - ["end"] = { line = lastline, character = 0 }; - }; - rangeLength = length; - text = table.concat(lines, '\n'); - }; + local lines = nvim_buf_get_lines(bufnr, 0, -1, true) + local startline = math.min(firstline + 1, math.min(#client._cached_buffers[bufnr], #lines)) + local endline = math.min(-(#lines - new_lastline), -1) + local incremental_change = vim.lsp.util.compute_diff(client._cached_buffers[bufnr], lines, startline, endline) + client._cached_buffers[bufnr] = lines + return incremental_change end) local full_changes = once(function() return { @@ -851,19 +852,12 @@ do }; end) local uri = vim.uri_from_bufnr(bufnr) - for_each_buffer_client(bufnr, function(client, _client_id) - local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, false) - + for_each_buffer_client(bufnr, function(client) + local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, true) local text_document_did_change = client.resolved_capabilities.text_document_did_change local changes if text_document_did_change == protocol.TextDocumentSyncKind.None then return - --[=[ TODO(ashkan) there seem to be problems with the byte_sizes sent by - -- neovim right now so only send the full content for now. In general, we - -- can assume that servers *will* support both versions anyway, as there - -- is no way to specify the sync capability by the client. - -- See https://github.com/palantir/python-language-server/commit/cfd6675bc10d5e8dbc50fc50f90e4a37b7178821#diff-f68667852a14e9f761f6ebf07ba02fc8 for an example of pyls handling both. - --]=] elseif not allow_incremental_sync or text_document_did_change == protocol.TextDocumentSyncKind.Full then changes = full_changes(client) elseif text_document_did_change == protocol.TextDocumentSyncKind.Incremental then @@ -931,6 +925,9 @@ function lsp.buf_attach_client(bufnr, client_id) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end + if client._cached_buffers then + client._cached_buffers[bufnr] = nil + end end) util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index df25943ecd..a1f24706c0 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -264,10 +264,14 @@ local function set_diagnostic_cache(diagnostics, bufnr, client_id) -- The diagnostic's severity. Can be omitted. If omitted it is up to the -- client to interpret diagnostics as error, warning, info or hint. -- TODO: Replace this with server-specific heuristics to infer severity. + local buf_line_count = vim.api.nvim_buf_line_count(bufnr) for _, diagnostic in ipairs(diagnostics) do if diagnostic.severity == nil then diagnostic.severity = DiagnosticSeverity.Error end + -- Account for servers that place diagnostics on terminating newline + local start = diagnostic.range.start + start.line = math.min(start.line, buf_line_count - 1) end diagnostic_cache[bufnr][client_id] = diagnostics diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 7819bddcb9..0cf80e1443 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -26,7 +26,7 @@ end -- @msg of type ProgressParams -- Basically a token of type number/string -local function progress_callback(_, _, params, client_id) +local function progress_handler(_, _, params, client_id) local client = vim.lsp.get_client_by_id(client_id) local client_name = client and client.name or string.format("id=%d", client_id) if not client then @@ -62,7 +62,7 @@ local function progress_callback(_, _, params, client_id) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress -M['$/progress'] = progress_callback +M['$/progress'] = progress_handler --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create M['window/workDoneProgress/create'] = function(_, _, params, client_id) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 4e8d2d4202..331e980e67 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -32,6 +32,8 @@ do vim.fn.mkdir(vim.fn.stdpath('cache'), "p") local logfile = assert(io.open(logfilename, "a+")) + -- Start message for logging + logfile:write(string.format("[ START ] %s ] LSP logging initiated\n", os.date(log_date_format))) for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. log[level] = levelnr @@ -68,8 +70,6 @@ do logfile:flush() end end - -- Add some space to make it easier to distinguish different neovim runs. - logfile:write("\n") end -- This is put here on purpose after the loop above so that it doesn't diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 388f65c180..0f440d6d70 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -975,6 +975,16 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.rename = true end + if server_capabilities.codeLensProvider == nil then + general_properties.code_lens = false + general_properties.code_lens_resolve = false + elseif type(server_capabilities.codeLensProvider) == 'table' then + general_properties.code_lens = true + general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false + else + error("The server sent invalid codeLensProvider") + end + if server_capabilities.codeActionProvider == nil then general_properties.code_action = false elseif type(server_capabilities.codeActionProvider) == 'boolean' diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 6fb9f09c99..918b77e9f9 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -226,6 +226,165 @@ end -- function M.glob_to_regex(glob) -- end +--@private +--- Finds the first line and column of the difference between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@returns (int, int) start_line_idx and start_col_idx of range +local function first_difference(old_lines, new_lines, start_line_idx) + local line_count = math.min(#old_lines, #new_lines) + if line_count == 0 then return 1, 1 end + if not start_line_idx then + for i = 1, line_count do + start_line_idx = i + if old_lines[start_line_idx] ~= new_lines[start_line_idx] then + break + end + end + end + local old_line = old_lines[start_line_idx] + local new_line = new_lines[start_line_idx] + local length = math.min(#old_line, #new_line) + local start_col_idx = 1 + while start_col_idx <= length do + if string.sub(old_line, start_col_idx, start_col_idx) ~= string.sub(new_line, start_col_idx, start_col_idx) then + break + end + start_col_idx = start_col_idx + 1 + end + return start_line_idx, start_col_idx +end + + +--@private +--- Finds the last line and column of the differences between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@param start_char integer First different character idx of range +--@returns (int, int) end_line_idx and end_col_idx of range +local function last_difference(old_lines, new_lines, start_char, end_line_idx) + local line_count = math.min(#old_lines, #new_lines) + if line_count == 0 then return 0,0 end + if not end_line_idx then + end_line_idx = -1 + end + for i = end_line_idx, -line_count, -1 do + if old_lines[#old_lines + i + 1] ~= new_lines[#new_lines + i + 1] then + end_line_idx = i + break + end + end + local old_line + local new_line + if end_line_idx <= -line_count then + end_line_idx = -line_count + old_line = string.sub(old_lines[#old_lines + end_line_idx + 1], start_char) + new_line = string.sub(new_lines[#new_lines + end_line_idx + 1], start_char) + else + old_line = old_lines[#old_lines + end_line_idx + 1] + new_line = new_lines[#new_lines + end_line_idx + 1] + end + local old_line_length = #old_line + local new_line_length = #new_line + local length = math.min(old_line_length, new_line_length) + local end_col_idx = -1 + while end_col_idx >= -length do + local old_char = string.sub(old_line, old_line_length + end_col_idx + 1, old_line_length + end_col_idx + 1) + local new_char = string.sub(new_line, new_line_length + end_col_idx + 1, new_line_length + end_col_idx + 1) + if old_char ~= new_char then + break + end + end_col_idx = end_col_idx - 1 + end + return end_line_idx, end_col_idx + +end + +--@private +--- Get the text of the range defined by start and end line/column +--@param lines table list of lines +--@param start_char integer First different character idx of range +--@param end_char integer Last different character idx of range +--@param start_line integer First different line idx of range +--@param end_line integer Last different line idx of range +--@returns string text extracted from defined region +local function extract_text(lines, start_line, start_char, end_line, end_char) + if start_line == #lines + end_line + 1 then + if end_line == 0 then return '' end + local line = lines[start_line] + local length = #line + end_char - start_char + return string.sub(line, start_char, start_char + length + 1) + end + local result = string.sub(lines[start_line], start_char) .. '\n' + for line_idx = start_line + 1, #lines + end_line do + result = result .. lines[line_idx] .. '\n' + end + if end_line ~= 0 then + local line = lines[#lines + end_line + 1] + local length = #line + end_char + 1 + result = result .. string.sub(line, 1, length) + end + return result +end + +--@private +--- Compute the length of the substituted range +--@param lines table list of lines +--@param start_char integer First different character idx of range +--@param end_char integer Last different character idx of range +--@param start_line integer First different line idx of range +--@param end_line integer Last different line idx of range +--@returns (int, int) end_line_idx and end_col_idx of range +local function compute_length(lines, start_line, start_char, end_line, end_char) + local adj_end_line = #lines + end_line + 1 + local adj_end_char + if adj_end_line > #lines then + adj_end_char = end_char - 1 + else + adj_end_char = #lines[adj_end_line] + end_char + end + if start_line == adj_end_line then + return adj_end_char - start_char + 1 + end + local result = #lines[start_line] - start_char + 1 + for line = start_line + 1, adj_end_line -1 do + result = result + #lines[line] + 1 + end + result = result + adj_end_char + 1 + return result +end + +--- Returns the range table for the difference between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@returns table start_line_idx and start_col_idx of range +function M.compute_diff(old_lines, new_lines, start_line_idx, end_line_idx) + local start_line, start_char = first_difference(old_lines, new_lines, start_line_idx) + local end_line, end_char = last_difference(vim.list_slice(old_lines, start_line, #old_lines), + vim.list_slice(new_lines, start_line, #new_lines), start_char, end_line_idx) + local text = extract_text(new_lines, start_line, start_char, end_line, end_char) + local length = compute_length(old_lines, start_line, start_char, end_line, end_char) + + local adj_end_line = #old_lines + end_line + local adj_end_char + if end_line == 0 then + adj_end_char = 0 + else + adj_end_char = #old_lines[#old_lines + end_line + 1] + end_char + 1 + end + + local result = { + range = { + start = { line = start_line - 1, character = start_char - 1}, + ["end"] = { line = adj_end_line, character = adj_end_char} + }, + text = text, + rangeLength = length + 1, + } + + return result +end + --- Can be used to extract the completion items from a --- `textDocument/completion` request, which may return one of --- `CompletionItem[]`, `CompletionList` or null. @@ -873,8 +1032,11 @@ function M.fancy_floating_markdown(contents, opts) -- This is because the syntax command doesn't accept a target. local cwin = vim.api.nvim_get_current_win() vim.api.nvim_set_current_win(winnr) + api.nvim_win_set_option(winnr, 'conceallevel', 2) + api.nvim_win_set_option(winnr, 'concealcursor', 'n') - vim.cmd("ownsyntax markdown") + vim.cmd("ownsyntax lsp_markdown") + vim.cmd("set filetype=lsp_markdown") local idx = 1 --@private local function apply_syntax_to_region(ft, start, finish) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 998e04f568..0a663628a5 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -400,6 +400,20 @@ function vim.tbl_count(t) return count end +--- Creates a copy of a table containing only elements from start to end (inclusive) +--- +--@param list table table +--@param start integer Start range of slice +--@param finish integer End range of slice +--@returns Copy of table sliced from start to finish (inclusive) +function vim.list_slice(list, start, finish) + local new_list = {} + for i = start or 1, finish or #list do + new_list[#new_list+1] = list[i] + end + return new_list +end + --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- --@see https://www.lua.org/pil/20.2.html diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 6870bcec75..fa5d064048 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -43,7 +43,7 @@ " balloon -> nvim floating window " " The code for opening the floating window was taken from the beautiful -" implementation of LanguageClient-Neovim: +" implementation of LanguageClient-Neovim: " https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304 " " Neovim terminal also works seamlessly on windows, which is why the ability @@ -76,9 +76,14 @@ if !exists('g:termdebugger') endif let s:pc_id = 12 -let s:break_id = 13 " breakpoint number is added to this +let s:asm_id = 13 +let s:break_id = 14 " breakpoint number is added to this let s:stopped = 1 +let s:parsing_disasm_msg = 0 +let s:asm_lines = [] +let s:asm_addr = '' + " Take a breakpoint number as used by GDB and turn it into an integer. " The breakpoint may contain a dot: 123.4 -> 123004 " The main breakpoint has a zero subid. @@ -120,6 +125,7 @@ func s:StartDebug_internal(dict) let s:ptywin = 0 let s:pid = 0 + let s:asmwin = 0 " Uncomment this line to write logging in "debuglog". " call ch_logfile('debuglog', 'w') @@ -155,6 +161,14 @@ func s:StartDebug_internal(dict) else call s:StartDebug_term(a:dict) endif + + if exists('g:termdebug_disasm_window') + if g:termdebug_disasm_window + let curwinid = win_getid(winnr()) + call s:GotoAsmwinOrCreateIt() + call win_gotoid(curwinid) + endif + endif endfunc " Use when debugger didn't start or ended. @@ -321,9 +335,9 @@ func s:StartDebug_prompt(dict) "call ch_log('executing "' . join(cmd) . '"') let s:gdbjob = jobstart(cmd, { - \ 'on_exit': function('s:EndPromptDebug'), - \ 'on_stdout': function('s:GdbOutCallback'), - \ }) + \ 'on_exit': function('s:EndPromptDebug'), + \ 'on_stdout': function('s:GdbOutCallback'), + \ }) if s:gdbjob == 0 echoerr 'invalid argument (or job table is full) while starting gdb job' exe 'bwipe! ' . s:ptybuf @@ -562,6 +576,15 @@ func s:GetFullname(msg) return name endfunc +" Extract the "addr" value from a gdb message with addr="0x0001234". +func s:GetAsmAddr(msg) + if a:msg !~ 'addr=' + return '' + endif + let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', '')) + return addr +endfunc + function s:EndTermDebug(job_id, exit_code, event) unlet s:gdbwin @@ -601,6 +624,66 @@ func s:EndPromptDebug(job_id, exit_code, event) "call ch_log("Returning from EndPromptDebug()") endfunc +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n" +" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n" +" ... +" - CommOutput: ~" 0x0000555556467cd0:\tpop rbp\n" +" - CommOutput: ~" 0x0000555556467cd1:\tret \n" +" - CommOutput: ~"End of assembler dump.\n" +" - CommOutput: ^done + +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: &"No function contains specified address.\n" +" - CommOutput: ^error,msg="No function contains specified address." +func s:HandleDisasmMsg(msg) + if a:msg =~ '^\^done' + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + silent normal! gg0"_dG + call setline(1, s:asm_lines) + set nomodified + set filetype=asm + + let lnum = search('^' . s:asm_addr) + if lnum != 0 + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + + call win_gotoid(curwinid) + endif + + let s:parsing_disasm_msg = 0 + let s:asm_lines = [] + elseif a:msg =~ '^\^error,msg=' + if s:parsing_disasm_msg == 1 + " Disassemble call ran into an error. This can happen when gdb can't + " find the function frame address, so let's try to disassemble starting + " at current PC + call s:SendCommand('disassemble $pc,+100') + endif + let s:parsing_disasm_msg = 0 + elseif a:msg =~ '\&\"disassemble \$pc' + if a:msg =~ '+100' + " This is our second disasm attempt + let s:parsing_disasm_msg = 2 + endif + else + let value = substitute(a:msg, '^\~\"[ ]*', '', '') + let value = substitute(value, '^=>[ ]*', '', '') + let value = substitute(value, '\\n\"
$', '', '') + let value = substitute(value, '
', '', '') + let value = substitute(value, '\\t', ' ', 'g') + + if value != '' || !empty(s:asm_lines) + call add(s:asm_lines, value) + endif + endif +endfunc + func s:CommOutput(job_id, msgs, event) for msg in a:msgs @@ -608,7 +691,10 @@ func s:CommOutput(job_id, msgs, event) if msg[0] == "\n" let msg = msg[1:] endif - if msg != '' + + if s:parsing_disasm_msg + call s:HandleDisasmMsg(msg) + elseif msg != '' if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' call s:HandleCursor(msg) elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' @@ -621,6 +707,9 @@ func s:CommOutput(job_id, msgs, event) call s:HandleEvaluate(msg) elseif msg =~ '^\^error,msg=' call s:HandleError(msg) + elseif msg =~ '^disassemble' + let s:parsing_disasm_msg = 1 + let s:asm_lines = [] endif endif endfor @@ -651,6 +740,7 @@ func s:InstallCommands() command Gdb call win_gotoid(s:gdbwin) command Program call win_gotoid(s:ptywin) command Source call s:GotoSourcewinOrCreateIt() + command Asm call s:GotoAsmwinOrCreateIt() command Winbar call s:InstallWinbar() " TODO: can the K mapping be restored? @@ -689,6 +779,7 @@ func s:DeleteCommands() delcommand Gdb delcommand Program delcommand Source + delcommand Asm delcommand Winbar nunmap K @@ -963,6 +1054,48 @@ func s:GotoSourcewinOrCreateIt() endif endfunc +func s:GotoAsmwinOrCreateIt() + if !win_gotoid(s:asmwin) + if win_gotoid(s:sourcewin) + exe 'rightbelow new' + else + exe 'new' + endif + + let s:asmwin = win_getid(winnr()) + + setlocal nowrap + setlocal number + setlocal noswapfile + setlocal buftype=nofile + + let asmbuf = bufnr('Termdebug-asm-listing') + if asmbuf > 0 + exe 'buffer' . asmbuf + else + exe 'file Termdebug-asm-listing' + endif + + if exists('g:termdebug_disasm_window') + if g:termdebug_disasm_window > 1 + exe 'resize ' . g:termdebug_disasm_window + endif + endif + endif + + if s:asm_addr != '' + let lnum = search('^' . s:asm_addr) + if lnum == 0 + if s:stopped + call s:SendCommand('disassemble $pc') + endif + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + endif +endfunc + " Handle stopping and running message from gdb. " Will update the sign that shows the current position. func s:HandleCursor(msg) @@ -981,10 +1114,31 @@ func s:HandleCursor(msg) else let fname = '' endif + + if a:msg =~ 'addr=' + let asm_addr = s:GetAsmAddr(a:msg) + if asm_addr != '' + let s:asm_addr = asm_addr + + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + let lnum = search('^' . s:asm_addr) + if lnum == 0 + call s:SendCommand('disassemble $pc') + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + + call win_gotoid(curwinid) + endif + endif + endif + if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') if lnum =~ '^[0-9]*$' - call s:GotoSourcewinOrCreateIt() + call s:GotoSourcewinOrCreateIt() if expand('%:p') != fnamemodify(fname, ':p') if &modified " TODO: find existing window diff --git a/runtime/syntax/cabal.vim b/runtime/syntax/cabal.vim index 8af47d4042..92e6b8331e 100644 --- a/runtime/syntax/cabal.vim +++ b/runtime/syntax/cabal.vim @@ -4,7 +4,7 @@ " Maintainer: Marcin Szamotulski <profunctor@pm.me> " Previous Maintainer: Vincent Berthoux <twinside@gmail.com> " File Types: .cabal -" Last Change: 15 May 2018 +" Last Change: 21 Nov 2020 " v1.5: Incorporated changes from " https://github.com/sdiehl/haskell-vim-proto/blob/master/vim/syntax/cabal.vim " Use `syn keyword` instead of `syn match`. @@ -62,11 +62,12 @@ syn keyword cabalCategory contained \ source-repository \ flag \ custom-setup + \ common syn match cabalCategoryTitle contained /[^{]*\ze{\?/ syn match cabalCategoryRegion \ contains=cabalCategory,cabalCategoryTitle \ nextgroup=cabalCategory skipwhite - \ /^\c\s*\(contained\|executable\|library\|benchmark\|test-suite\|source-repository\|flag\|custom-setup\)\+\s*\%(.*$\|$\)/ + \ /^\c\s*\(contained\|executable\|library\|benchmark\|test-suite\|source-repository\|flag\|custom-setup\|common\)\+\s*\%(.*$\|$\)/ syn keyword cabalTruth true false " cabalStatementRegion which limits the scope of cabalStatement keywords, this @@ -76,10 +77,14 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ default-language \ default-extensions \ author + \ autogen-modules + \ asm-sources + \ asm-options \ branch \ bug-reports \ build-depends \ build-tools + \ build-tools-depends \ build-type \ buildable \ c-sources @@ -87,32 +92,46 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ category \ cc-options \ copyright + \ cmm-sources + \ cmm-options \ cpp-options + \ cxx-sources \ data-dir \ data-files \ default + \ default-extensions \ description \ executable \ exposed-modules \ exposed \ extensions - \ extra-tmp-files + \ extra-bundled-libraries \ extra-doc-files + \ extra-dynamic-library-flavours + \ extra-framework-dirs + \ extra-ghci-libraries \ extra-lib-dirs \ extra-libraries + \ extra-library-flavours \ extra-source-files - \ exta-tmp-files + \ extra-tmp-files \ for example \ frameworks \ ghc-options \ ghc-prof-options \ ghc-shared-options + \ ghcjs-options + \ ghcjs-prof-options + \ ghcjs-shared-options \ homepage + \ hs-source-dir \ hs-source-dirs \ hugs-options + \ import \ include-dirs \ includes \ install-includes + \ js-sources \ ld-options \ license \ license-file @@ -120,10 +139,13 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ main-is \ maintainer \ manual + \ mixins \ module \ name \ nhc98-options \ other-extensions + \ other-language + \ other-languages \ other-modules \ package-url \ pkgconfig-depends diff --git a/runtime/syntax/cabalconfig.vim b/runtime/syntax/cabalconfig.vim new file mode 100644 index 0000000000..0165725c06 --- /dev/null +++ b/runtime/syntax/cabalconfig.vim @@ -0,0 +1,30 @@ +" Vim syntax file +" Language: Cabal Config +" Maintainer: profunctor@pm.me +" Last Change: Marcin Szamotulski +" Original Author: Marcin Szamotulski + +if exists("b:current_syntax") + finish +endif + +syn match CabalConfigSection /^\S[[:alpha:]]\+\%(-[[:alpha:]]\+\)*[^:]*$/ +syn region CabalConfigRegion matchgroup=CabalConfigKey start=/^\s*[[:alpha:]]\+\%(-[[:alpha:]]\+\)*:/ matchgroup=NONE end=/$/ contains=CabalConfigSeparator,CabalConfigKeyword,CabalConfigPath keepend +syn match CabalConfigComment /^\s*--.*$/ +syn match CabalConfigValue /.*$/ contained +syn match CabalConfigKey /[[:alpha:]]\+\%(-[[:alpha:]]\+\)*\ze:/ +syn keyword CabalConfigSeparator : contained +syn match CabalConfigVariable /\$[[:alpha:]]\+/ +syn keyword CabalConfigKeyword True False ghc +syn match CabalConfigPath /\%([[:alpha:]]\+:\)\?\%(\/[[:print:]]\+\)\+/ + +hi def link CabalConfigComment Comment +hi def link CabalConfigSection Title +hi def link CabalConfigKey Statement +hi def link CabalConfigSeparator NonText +hi def link CabalConfigValue Normal +hi def link CabalConfigVariable Identifier +hi def link CabalConfigKeyword Keyword +hi def link CabalConfigPath Directory + +let b:current_syntax = "cabal.config" diff --git a/runtime/syntax/cabalproject.vim b/runtime/syntax/cabalproject.vim new file mode 100644 index 0000000000..12143b9ee9 --- /dev/null +++ b/runtime/syntax/cabalproject.vim @@ -0,0 +1,28 @@ +" Vim syntax file +" Language: Cabal Project +" Maintainer: profunctor@pm.me +" Last Change: Marcin Szamotulski +" Original Author: Marcin Szamotulski + +if exists("b:current_syntax") + finish +endif + +syn match CabalProjectComment /^\s*--.*/ contains=@Spell +syn match CabalProjectField /^\w\%(\w\|-\)\+/ contains=@NoSpell + +syn keyword CabalProjectBoolean true false True False +syn keyword CabalProjectCompiler ghc ghcjs jhc lhc uhc haskell-suite +syn match CabalProjectNat /\<\d\+\>/ +syn keyword CabalProjectJobs $ncpus +syn keyword CabalProjectProfilingLevel default none exported-functions toplevel-functions all-functions + +hi def link CabalProjectComment Comment +hi def link CabalProjectField Statement +hi def link CabalProjectBoolean Boolean +hi def link CabalProjectCompiler Identifier +hi def link CabalProjectNat Number +hi def link CabalProjectJobs Number +hi def link CabalProjectProfilingLevel Statement + +let b:current_syntax = "cabal.project" diff --git a/runtime/syntax/haskell.vim b/runtime/syntax/haskell.vim index e5128a12ab..1b70b9344a 100644 --- a/runtime/syntax/haskell.vim +++ b/runtime/syntax/haskell.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Haskell " Maintainer: Haskell Cafe mailinglist <haskell-cafe@haskell.org> -" Last Change: 2018 Mar 29 by Marcin Szamotulski +" Last Change: 2020 Oct 4 by Marcin Szamotulski <profunctor@pm.me> " Original Author: John Williams <jrw@pobox.com> " " Thanks to Ryan Crumley for suggestions and John Meacham for @@ -38,8 +38,8 @@ if exists("b:current_syntax") endif " (Qualified) identifiers (no default highlighting) -syn match ConId "\(\<[A-Z][a-zA-Z0-9_']*\.\)\=\<[A-Z][a-zA-Z0-9_']*\>" contains=@NoSpell -syn match VarId "\(\<[A-Z][a-zA-Z0-9_']*\.\)\=\<[a-z][a-zA-Z0-9_']*\>" contains=@NoSpell +syn match ConId "\(\<[A-Z][a-zA-Z0-9_']*\.\)*\<[A-Z][a-zA-Z0-9_']*\>" contains=@NoSpell +syn match VarId "\(\<[A-Z][a-zA-Z0-9_']*\.\)*\<[a-z][a-zA-Z0-9_']*\>" contains=@NoSpell " Infix operators--most punctuation characters and any (qualified) identifier " enclosed in `backquotes`. An operator starting with : is a constructor, @@ -49,8 +49,11 @@ syn match hsConSym "\(\<[A-Z][a-zA-Z0-9_']*\.\)\=:[-!#$%&\*\+./<=>\?@\\^|~:]*" syn match hsVarSym "`\(\<[A-Z][a-zA-Z0-9_']*\.\)\=[a-z][a-zA-Z0-9_']*`" syn match hsConSym "`\(\<[A-Z][a-zA-Z0-9_']*\.\)\=[A-Z][a-zA-Z0-9_']*`" +" (Non-qualified) identifiers which start with # are labels +syn match hsLabel "#[a-z][a-zA-Z0-9_']*\>" + " Reserved symbols--cannot be overloaded. -syn match hsDelimiter "(\|)\|\[\|\]\|,\|;\|_\|{\|}" +syn match hsDelimiter "(\|)\|\[\|\]\|,\|;\|{\|}" " Strings and constants syn match hsSpecialChar contained "\\\([0-9]\+\|o[0-7]\+\|x[0-9a-fA-F]\+\|[\"\\'&\\abfnrtv]\|^[A-Z^_\[\\\]]\)" @@ -62,37 +65,41 @@ syn match hsCharacter "^'\([^\\]\|\\[^']\+\|\\'\)'" contains=hsSpecialChar,hs syn match hsNumber "\v<[0-9]%(_*[0-9])*>|<0[xX]_*[0-9a-fA-F]%(_*[0-9a-fA-F])*>|<0[oO]_*%(_*[0-7])*>|<0[bB]_*[01]%(_*[01])*>" syn match hsFloat "\v<[0-9]%(_*[0-9])*\.[0-9]%(_*[0-9])*%(_*[eE][-+]?[0-9]%(_*[0-9])*)?>|<[0-9]%(_*[0-9])*_*[eE][-+]?[0-9]%(_*[0-9])*>|<0[xX]_*[0-9a-fA-F]%(_*[0-9a-fA-F])*\.[0-9a-fA-F]%(_*[0-9a-fA-F])*%(_*[pP][-+]?[0-9]%(_*[0-9])*)?>|<0[xX]_*[0-9a-fA-F]%(_*[0-9a-fA-F])*_*[pP][-+]?[0-9]%(_*[0-9])*>" -" Keyword definitions. These must be patterns instead of keywords -" because otherwise they would match as keywords at the start of a -" "literate" comment (see lhs.vim). -syn match hsModule "\<module\>" -syn match hsImport "\<import\>.*"he=s+6 contains=hsImportMod,hsLineComment,hsBlockComment,@NoSpell -syn match hsImportMod contained "\<\(as\|qualified\|hiding\)\>" contains=@NoSpell -syn match hsInfix "\<\(infix\|infixl\|infixr\)\>" -syn match hsStructure "\<\(class\|data\|deriving\|instance\|default\|where\)\>" -syn match hsTypedef "\<\(type\|newtype\)\>" -syn match hsStatement "\<\(do\|case\|of\|let\|in\)\>" -syn match hsConditional "\<\(if\|then\|else\)\>" +" Keyword definitions. +syn keyword hsModule module +syn match hsImportGroup "\<import\>.*" contains=hsImport,hsImportModuleName,hsImportMod,hsLineComment,hsBlockComment,hsImportList,@NoSpell nextgroup=hsImport +syn keyword hsImport import contained nextgroup=hsImportModuleName +syn match hsImportModuleName '\<[A-Z][A-Za-z.]*' contained +syn region hsImportList start='(' skip='([^)]\{-})' end=')' keepend contained contains=ConId,VarId,hsDelimiter,hsBlockComment,hsTypedef,@NoSpell + +syn keyword hsImportMod contained as qualified hiding +syn keyword hsInfix infix infixl infixr +syn keyword hsStructure class data deriving instance default where +syn keyword hsTypedef type +syn keyword hsNewtypedef newtype +syn keyword hsTypeFam family +syn keyword hsStatement mdo do case of let in +syn keyword hsConditional if then else " Not real keywords, but close. if exists("hs_highlight_boolean") " Boolean constants from the standard prelude. - syn match hsBoolean "\<\(True\|False\)\>" + syn keyword hsBoolean True False endif if exists("hs_highlight_types") " Primitive types from the standard prelude and libraries. - syn match hsType "\<\(Int\|Integer\|Char\|Bool\|Float\|Double\|IO\|Void\|Addr\|Array\|String\)\>" + syn keyword hsType Int Integer Char Bool Float Double IO Void Addr Array String endif if exists("hs_highlight_more_types") " Types from the standard prelude libraries. - syn match hsType "\<\(Maybe\|Either\|Ratio\|Complex\|Ordering\|IOError\|IOResult\|ExitCode\)\>" - syn match hsMaybe "\<Nothing\>" - syn match hsExitCode "\<\(ExitSuccess\)\>" - syn match hsOrdering "\<\(GT\|LT\|EQ\)\>" + syn keyword hsType Maybe Either Ratio Complex Ordering IOError IOResult ExitCode + syn keyword hsMaybe Nothing + syn keyword hsExitCode ExitSuccess + syn keyword hsOrdering GT LT EQ endif if exists("hs_highlight_debug") " Debugging functions from the standard prelude. - syn match hsDebug "\<\(undefined\|error\|trace\)\>" + syn keyword hsDebug undefined error trace endif @@ -133,12 +140,14 @@ hi def link hsImportMod hsImport hi def link hsInfix PreProc hi def link hsStructure Structure hi def link hsStatement Statement -hi def link hsConditional Conditional -hi def link hsSpecialChar SpecialChar +hi def link hsConditional Conditional +hi def link hsSpecialChar SpecialChar hi def link hsTypedef Typedef +hi def link hsNewtypedef Typedef hi def link hsVarSym hsOperator hi def link hsConSym hsOperator hi def link hsOperator Operator +hi def link hsTypeFam Structure if exists("hs_highlight_delimiters") " Some people find this highlighting distracting. hi def link hsDelimiter Delimiter @@ -160,22 +169,22 @@ hi def link hsMaybe hsEnumConst hi def link hsOrdering hsEnumConst hi def link hsEnumConst Constant hi def link hsDebug Debug - -hi def link cCppString hsString -hi def link cCommentStart hsComment -hi def link cCommentError hsError -hi def link cCommentStartError hsError -hi def link cInclude Include -hi def link cPreProc PreProc -hi def link cDefine Macro -hi def link cIncluded hsString -hi def link cError Error -hi def link cPreCondit PreCondit -hi def link cComment Comment -hi def link cCppSkip cCppOut -hi def link cCppOut2 cCppOut -hi def link cCppOut Comment - +hi def link hsLabel Special + +hi def link cCppString hsString +hi def link cCommentStart hsComment +hi def link cCommentError hsError +hi def link cCommentStartError hsError +hi def link cInclude Include +hi def link cPreProc PreProc +hi def link cDefine Macro +hi def link cIncluded hsString +hi def link cError Error +hi def link cPreCondit PreCondit +hi def link cComment Comment +hi def link cCppSkip cCppOut +hi def link cCppOut2 cCppOut +hi def link cCppOut Comment let b:current_syntax = "haskell" diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim new file mode 100644 index 0000000000..23658a9db6 --- /dev/null +++ b/runtime/syntax/lsp_markdown.vim @@ -0,0 +1,16 @@ +" Vim syntax file +" Language: lsp_markdown +" Maintainer: Michael Lingelbach <m.j.lbach@gmail.com +" URL: http://neovim.io +" Remark: Uses markdown syntax file + +runtime! syntax/markdown.vim + +syn cluster mkdNonListItem add=mkdEscape,mkdNbsp +syntax region mkdNonListItemBlock start=/\(\%^\(\s*\([-*+]\|\d\+\.\)\s\+\)\@!\|\n\(\_^\_$\|\s\{4,}[^]\|\t+[^\t]\)\@!\)/ end=/^\(\s*\([-*+]\|\d\+\.\)\s\+\)\@=/ contains=@mkdNonListItem + +syntax region mkdEscape matchgroup=mkdEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/.\zs/ keepend contains=mkdEscapeCh contained oneline concealends +syntax match mkdEscapeCh /./ contained +syntax match mkdNbsp / / conceal cchar= + +hi def link mkdEscape special diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 0507e4b7b6..b4d896fecc 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -48,6 +48,7 @@ import textwrap import subprocess import collections import msgpack +import logging from xml.dom import minidom @@ -57,10 +58,18 @@ if sys.version_info < MIN_PYTHON_VERSION: print("requires Python {}.{}+".format(*MIN_PYTHON_VERSION)) sys.exit(1) -DEBUG = ('DEBUG' in os.environ) +# DEBUG = ('DEBUG' in os.environ) INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ) INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ) +log = logging.getLogger(__name__) + +LOG_LEVELS = { + logging.getLevelName(level): level for level in [ + logging.DEBUG, logging.INFO, logging.ERROR + ] +} + fmt_vimhelp = False # HACK text_width = 78 script_path = os.path.abspath(__file__) @@ -157,7 +166,7 @@ CONFIG = { ]), 'file_patterns': '*.lua', 'fn_name_prefix': '', - 'section_name': {}, + 'section_name': {'lsp.lua': 'lsp'}, 'section_fmt': lambda name: ( 'Lua module: vim.lsp' if name.lower() == 'lsp' @@ -726,8 +735,8 @@ def extract_from_xml(filename, target, width): if desc: for child in desc.childNodes: paras.append(para_as_map(child)) - if DEBUG: - print(textwrap.indent( + log.debug( + textwrap.indent( re.sub(r'\n\s*\n+', '\n', desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16)) @@ -885,12 +894,13 @@ def main(config, args): os.remove(mpack_file) output_dir = out_dir.format(target=target) + debug = args.log_level >= logging.DEBUG p = subprocess.Popen( ['doxygen', '-'], stdin=subprocess.PIPE, # silence warnings # runtime/lua/vim/lsp.lua:209: warning: argument 'foo' not found - stderr=(subprocess.STDOUT if DEBUG else subprocess.DEVNULL)) + stderr=(subprocess.STDOUT if debug else subprocess.DEVNULL)) p.communicate( config.format( input=CONFIG[target]['files'], @@ -1039,6 +1049,10 @@ def filter_source(filename): def parse_args(): targets = ', '.join(CONFIG.keys()) ap = argparse.ArgumentParser() + ap.add_argument( + "--log-level", "-l", choices=LOG_LEVELS.keys(), + default=logging.getLevelName(logging.ERROR), help="Set log verbosity" + ) ap.add_argument('source_filter', nargs='*', help="Filter source file(s)") ap.add_argument('-k', '--keep-tmpfiles', action='store_true', @@ -1085,6 +1099,10 @@ Doxyfile = textwrap.dedent(''' if __name__ == "__main__": args = parse_args() + print("Setting log level to %s" % args.log_level) + args.log_level = LOG_LEVELS[args.log_level] + log.setLevel(args.log_level) + if len(args.source_filter) > 0: filter_source(args.source_filter[0]) else: diff --git a/scripts/lua2dox_filter b/scripts/lua2dox_filter index 61577527c4..8760f12176 100755 --- a/scripts/lua2dox_filter +++ b/scripts/lua2dox_filter @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ########################################################################### # Copyright (C) 2012 by Simon Dales # diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 1a1a178620..2c9d655a15 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -513,8 +513,44 @@ if(WIN32) tidy.exe win32yank.exe winpty-agent.exe + winpty.dll xxd.exe + # Dependencies for neovim-qt + bearer/qgenericbearer.dll + iconengines/qsvgicon.dll + imageformats/qgif.dll + imageformats/qicns.dll + imageformats/qico.dll + imageformats/qjpeg.dll + imageformats/qsvg.dll + imageformats/qtga.dll + imageformats/qtiff.dll + imageformats/qwbmp.dll + imageformats/qwebp.dll + platforms/qwindows.dll + styles/qwindowsvistastyle.dll + translations/qt_ar.qm + translations/qt_bg.qm + translations/qt_ca.qm + translations/qt_cs.qm + translations/qt_da.qm + translations/qt_de.qm + translations/qt_en.qm + translations/qt_es.qm + translations/qt_fi.qm + translations/qt_fr.qm + translations/qt_gd.qm + translations/qt_he.qm + translations/qt_hu.qm + translations/qt_it.qm + translations/qt_ja.qm + translations/qt_ko.qm + translations/qt_lv.qm + translations/qt_pl.qm + translations/qt_ru.qm + translations/qt_sk.qm + translations/qt_uk.qm D3Dcompiler_47.dll libEGL.dll libgcc_s_dw2-1.dll @@ -522,14 +558,13 @@ if(WIN32) libstdc++-6.dll libwinpthread-1.dll nvim-qt.exe + opengl32sw.dll Qt5Core.dll Qt5Gui.dll Qt5Network.dll Qt5Svg.dll Qt5Widgets.dll - winpty.dll - platforms/qwindows.dll ) get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY) set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n" diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index c1f2842b52..66c4454f7b 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1430,6 +1430,18 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - "eol": right after eol character (default) /// - "overlay": display over the specified column, without /// shifting the underlying text. +/// - virt_text_hide : hide the virtual text when the background +/// text is selected or hidden due to +/// horizontal scroll 'nowrap' +/// - hl_mode : control how highlights are combined with the +/// highlights of the text. Currently only affects +/// virt_text highlights, but might affect `hl_group` +/// in later versions. +/// - "replace": only show the virt_text color. This is the +/// default +/// - "combine": combine with background text color +/// - "blend": blend with background text color. +/// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the @@ -1477,11 +1489,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, bool ephemeral = false; uint64_t id = 0; - int line2 = -1, hl_id = 0; - DecorPriority priority = DECOR_PRIORITY_BASE; + int line2 = -1; + Decoration decor = DECORATION_INIT; colnr_T col2 = -1; - VirtText virt_text = KV_INITIAL_VALUE; - VirtTextPos virt_text_pos = kVTEndOfLine; + bool right_gravity = true; bool end_right_gravity = false; bool end_gravity_set = false; @@ -1528,12 +1539,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, switch (v->type) { case kObjectTypeString: hl_group = v->data.string; - hl_id = syn_check_group( + decor.hl_id = syn_check_group( (char_u *)(hl_group.data), (int)hl_group.size); break; case kObjectTypeInteger: - hl_id = (int)v->data.integer; + decor.hl_id = (int)v->data.integer; break; default: api_set_error(err, kErrorTypeValidation, @@ -1546,7 +1557,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, "virt_text is not an Array"); goto error; } - virt_text = parse_virt_text(v->data.array, err); + decor.virt_text = parse_virt_text(v->data.array, err); if (ERROR_SET(err)) { goto error; } @@ -1558,9 +1569,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } String str = v->data.string; if (strequal("eol", str.data)) { - virt_text_pos = kVTEndOfLine; + decor.virt_text_pos = kVTEndOfLine; } else if (strequal("overlay", str.data)) { - virt_text_pos = kVTOverlay; + decor.virt_text_pos = kVTOverlay; + } else { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos: invalid value"); + goto error; + } + } else if (strequal("virt_text_hide", k.data)) { + decor.virt_text_hide = api_object_to_bool(*v, + "virt_text_hide", false, err); + if (ERROR_SET(err)) { + goto error; + } + } else if (strequal("hl_mode", k.data)) { + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "hl_mode is not a String"); + goto error; + } + String str = v->data.string; + if (strequal("replace", str.data)) { + decor.hl_mode = kHlModeReplace; + } else if (strequal("combine", str.data)) { + decor.hl_mode = kHlModeCombine; + } else if (strequal("blend", str.data)) { + decor.hl_mode = kHlModeBlend; } else { api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value"); @@ -1583,7 +1618,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, "priority is not a valid value"); goto error; } - priority = (DecorPriority)v->data.integer; + decor.priority = (DecorPriority)v->data.integer; } else if (strequal("right_gravity", k.data)) { if (v->type != kObjectTypeBoolean) { api_set_error(err, kErrorTypeValidation, @@ -1631,23 +1666,23 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, col2 = 0; } - Decoration *decor = NULL, tmp = { 0 }; + Decoration *d = NULL; - if (kv_size(virt_text) || priority != DECOR_PRIORITY_BASE) { + if (ephemeral) { + d = &decor; + } else if (kv_size(decor.virt_text) + || decor.priority != DECOR_PRIORITY_BASE) { // TODO(bfredl): this is a bit sketchy. eventually we should // have predefined decorations for both marks/ephemerals - decor = ephemeral ? &tmp : xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->virt_text = virt_text; - decor->priority = priority; - decor->virt_text_pos = virt_text_pos; - } else if (hl_id) { - decor = decor_hl(hl_id); + d = xcalloc(1, sizeof(*d)); + *d = decor; + } else if (decor.hl_id) { + d = decor_hl(decor.hl_id); } // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - decor_add_ephemeral((int)line, (int)col, line2, col2, decor, 0); + decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, 0); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); @@ -1655,14 +1690,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, - line2, col2, decor, right_gravity, + line2, col2, d, right_gravity, end_right_gravity, kExtmarkNoUndo); } return (Integer)id; error: - clear_virttext(&virt_text); + clear_virttext(&decor.virt_text); return 0; } @@ -1723,7 +1758,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, /// @param[out] err Error details, if any /// @return The ns_id that was used Integer nvim_buf_add_highlight(Buffer buffer, - Integer src_id, + Integer ns_id, String hl_group, Integer line, Integer col_start, @@ -1748,18 +1783,18 @@ Integer nvim_buf_add_highlight(Buffer buffer, col_end = MAXCOL; } - uint64_t ns_id = src2ns(&src_id); + uint64_t ns = src2ns(&ns_id); if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range - return src_id; + return ns_id; } int hl_id = 0; if (hl_group.size > 0) { hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); } else { - return src_id; + return ns_id; } int end_line = (int)line; @@ -1768,11 +1803,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, end_line++; } - extmark_set(buf, ns_id, 0, + extmark_set(buf, ns, 0, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, decor_hl(hl_id), true, false, kExtmarkNoUndo); - return src_id; + return ns_id; } /// Clears namespaced objects (highlights, extmarks, virtual text) from diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b94c99dc5e..1b428718b5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -39,6 +39,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/fileio.h" +#include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" @@ -1246,6 +1247,99 @@ fail: return 0; } +/// Open a terminal instance in a buffer +/// +/// By default (and currently the only option) the terminal will not be +/// connected to an external process. Instead, input send on the channel +/// will be echoed directly by the terminal. This is useful to disply +/// ANSI terminal sequences returned as part of a rpc message, or similar. +/// +/// Note: to directly initiate the terminal using the right size, display the +/// buffer in a configured window before calling this. For instance, for a +/// floating display, first create an empty buffer using |nvim_create_buf()|, +/// then display it using |nvim_open_win()|, and then call this function. +/// Then |nvim_chan_send()| cal be called immediately to process sequences +/// in a virtual terminal having the intended size. +/// +/// @param buffer the buffer to use (expected to be empty) +/// @param opts Optional parameters. Reserved for future use. +/// @param[out] err Error details, if any +Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return 0; + } + + TerminalOptions topts; + Channel *chan = channel_alloc(kChannelStreamInternal); + topts.data = chan; + // NB: overriden in terminal_check_size if a window is already + // displaying the buffer + topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0); + topts.height = (uint16_t)curwin->w_height_inner; + topts.write_cb = term_write; + topts.resize_cb = term_resize; + topts.close_cb = term_close; + Terminal *term = terminal_open(buf, topts); + terminal_check_size(term); + chan->term = term; + channel_incref(chan); + return (Integer)chan->id; +} + +static void term_write(char *buf, size_t size, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_resize(uint16_t width, uint16_t height, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_close(void *data) +{ + Channel *chan = data; + terminal_destroy(chan->term); + chan->term = NULL; + channel_decref(chan); +} + + +/// Send data to channel `id`. For a job, it writes it to the +/// stdin of the process. For the stdio channel |channel-stdio|, +/// it writes to Nvim's stdout. For an internal terminal instance +/// (|nvim_open_term()|) it writes directly to terimal output. +/// See |channel-bytes| for more information. +/// +/// This function writes raw data, not RPC messages. If the channel +/// was created with `rpc=true` then the channel expects RPC +/// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead. +/// +/// @param chan id of the channel +/// @param data data to write. 8-bit clean: can contain NUL bytes. +/// @param[out] err Error details, if any +void nvim_chan_send(Integer chan, String data, Error *err) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY +{ + const char *error = NULL; + if (!data.size) { + return; + } + + channel_send((uint64_t)chan, data.data, data.size, &error); + if (error) { + api_set_error(err, kErrorTypeValidation, "%s", error); + } +} + /// Open a new window. /// /// Currently this is used to open floating and external windows. @@ -1546,8 +1640,8 @@ theend: /// - "c" |charwise| mode /// - "l" |linewise| mode /// - "" guess by contents, see |setreg()| -/// @param after Insert after cursor (like |p|), or before (like |P|). -/// @param follow Place cursor at end of inserted text. +/// @param after If true insert after cursor (like |p|), or before (like |P|). +/// @param follow If true place cursor at end of inserted text. /// @param[out] err Error details, if any void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 09a34ca9fe..7a08ba58d0 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -161,7 +161,7 @@ void channel_init(void) /// /// Channel is allocated with refcount 1, which should be decreased /// when the underlying stream closes. -static Channel *channel_alloc(ChannelStreamType type) +Channel *channel_alloc(ChannelStreamType type) { Channel *chan = xcalloc(1, sizeof(*chan)); if (type == kChannelStreamStdio) { @@ -503,7 +503,7 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error) { Channel *chan = find_channel(id); if (!chan) { - EMSG(_(e_invchan)); + *error = _(e_invchan); goto err; } @@ -518,6 +518,11 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error) return len * written; } + if (chan->streamtype == kChannelStreamInternal && chan->term) { + terminal_receive(chan->term, data, len); + return len; + } + Stream *in = channel_instream(chan); if (in->closed) { @@ -724,8 +729,8 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader) /// Open terminal for channel /// /// Channel `chan` is assumed to be an open pty channel, -/// and curbuf is assumed to be a new, unmodified buffer. -void channel_terminal_open(Channel *chan) +/// and `buf` is assumed to be a new, unmodified buffer. +void channel_terminal_open(buf_T *buf, Channel *chan) { TerminalOptions topts; topts.data = chan; @@ -734,8 +739,8 @@ void channel_terminal_open(Channel *chan) topts.write_cb = term_write; topts.resize_cb = term_resize; topts.close_cb = term_close; - curbuf->b_p_channel = (long)chan->id; // 'channel' option - Terminal *term = terminal_open(topts); + buf->b_p_channel = (long)chan->id; // 'channel' option + Terminal *term = terminal_open(buf, topts); chan->term = term; channel_incref(chan); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index a1289f202a..9a20b06660 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -230,7 +230,7 @@ static void decor_add(DecorState *state, int start_row, int start_col, HlRange range = { start_row, start_col, end_row, end_col, attr_id, MAX(priority, decor->priority), kv_size(decor->virt_text) ? &decor->virt_text : NULL, - decor->virt_text_pos, + decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode, kv_size(decor->virt_text) && owned, -1 }; kv_pushp(state->active); @@ -245,7 +245,8 @@ static void decor_add(DecorState *state, int start_row, int start_col, kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, int virt_col, DecorState *state) +int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, + DecorState *state) { if (col <= state->col_until) { return state->current; @@ -324,7 +325,7 @@ next_mark: } if ((item.start_row == state->row && item.start_col <= col) && item.virt_text && item.virt_col == -1) { - item.virt_col = virt_col; + item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col; } if (keep) { kv_A(state->active, j++) = item; @@ -345,7 +346,7 @@ void decor_redraw_end(DecorState *state) VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) { - decor_redraw_col(buf, MAXCOL, MAXCOL, state); + decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); if (item.start_row == state->row && item.virt_text diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 47bd9abbc3..264e8a4a82 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -23,15 +23,26 @@ typedef enum { kVTOverlay, } VirtTextPos; +typedef enum { + kHlModeUnknown, + kHlModeReplace, + kHlModeCombine, + kHlModeBlend, +} HlMode; + struct Decoration { int hl_id; // highlight group VirtText virt_text; VirtTextPos virt_text_pos; + bool virt_text_hide; + HlMode hl_mode; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free }; +#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \ + kHlModeUnknown, DECOR_PRIORITY_BASE, false } typedef struct { int start_row; @@ -39,9 +50,13 @@ typedef struct { int end_row; int end_col; int attr_id; + // TODO(bfredl): embed decoration instead, perhaps using an arena + // for ephemerals? DecorPriority priority; VirtText *virt_text; VirtTextPos virt_text_pos; + bool virt_text_hide; + HlMode hl_mode; bool virt_text_owned; int virt_col; } HlRange; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 53717229f6..68c7438ea3 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1024,7 +1024,7 @@ static int insert_handle_key(InsertState *s) break; case K_EVENT: // some event - multiqueue_process_events(main_loop.events); + state_handle_k_event(); goto check_pum; case K_COMMAND: // some command diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6d97310c1c..f190ef14c4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3007,7 +3007,8 @@ static size_t varnamebuflen = 0; /* * Function to concatenate a prefix and a variable name. */ -static char_u *cat_prefix_varname(int prefix, char_u *name) +char_u *cat_prefix_varname(int prefix, const char_u *name) + FUNC_ATTR_NONNULL_ALL { size_t len = STRLEN(name) + 3; @@ -5280,14 +5281,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, if (ht_stack == NULL) { abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); } else { - ht_stack_T *newitem = try_malloc(sizeof(ht_stack_T)); - if (newitem == NULL) { - abort = true; - } else { - newitem->ht = &dd->dv_hashtab; - newitem->prev = *ht_stack; - *ht_stack = newitem; - } + ht_stack_T *const newitem = xmalloc(sizeof(ht_stack_T)); + newitem->ht = &dd->dv_hashtab; + newitem->prev = *ht_stack; + *ht_stack = newitem; } QUEUE *w = NULL; @@ -5308,14 +5305,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, if (list_stack == NULL) { abort = set_ref_in_list(ll, copyID, ht_stack); } else { - list_stack_T *newitem = try_malloc(sizeof(list_stack_T)); - if (newitem == NULL) { - abort = true; - } else { - newitem->list = ll; - newitem->prev = *list_stack; - *list_stack = newitem; - } + list_stack_T *const newitem = xmalloc(sizeof(list_stack_T)); + newitem->list = ll; + newitem->prev = *list_stack; + *list_stack = newitem; } } break; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8c8e0d568b..2b04469af7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -117,8 +117,12 @@ char_u *get_function_name(expand_T *xp, int idx) intidx = -1; if (intidx < 0) { name = get_user_func_name(xp, idx); - if (name != NULL) + if (name != NULL) { + if (*name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', name); + } return name; + } } while ((size_t)++intidx < ARRAY_SIZE(functions) && functions[intidx].name[0] == '\0') { @@ -3029,10 +3033,11 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. + // TODO(bfredl): deduplicate shared logic with state_enter ? if (!(char_avail() || using_script() || input_available())) { (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { - multiqueue_process_events(main_loop.events); + state_handle_k_event(); continue; } } @@ -10730,7 +10735,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); - channel_terminal_open(chan); + channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 6fcb01aace..531b17cb59 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -341,8 +341,9 @@ struct ufunc { ///< used for s: variables int uf_refcount; ///< reference count, see func_name_refcount() funccall_T *uf_scoped; ///< l: local variables for closure - char_u uf_name[]; ///< Name of function; can start with <SNR>123_ - ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) + char_u uf_name[]; ///< Name of function (actual size equals name); + ///< can start with <SNR>123_ + ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) }; struct partial_S { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 103c081143..854faf5377 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1407,19 +1407,20 @@ do_shell( * For autocommands we want to get the output on the current screen, to * avoid having to type return below. */ - msg_putchar('\r'); /* put cursor at start of line */ - msg_putchar('\n'); /* may shift screen one line up */ + msg_putchar('\r'); // put cursor at start of line + msg_putchar('\n'); // may shift screen one line up - /* warning message before calling the shell */ + // warning message before calling the shell if (p_warn && !autocmd_busy - && msg_silent == 0) + && msg_silent == 0) { FOR_ALL_BUFFERS(buf) { if (bufIsChanged(buf)) { MSG_PUTS(_("[No write since last change]\n")); break; } } + } // This ui_cursor_goto is required for when the '\n' resulted in a "delete line // 1" command to the terminal. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index d470bfb418..9977be56ca 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -935,7 +935,7 @@ static int command_line_execute(VimState *state, int key) if (s->c == K_EVENT || s->c == K_COMMAND) { if (s->c == K_EVENT) { - multiqueue_process_events(main_loop.events); + state_handle_k_event(); } else { do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); } @@ -5117,6 +5117,18 @@ ExpandFromContext ( if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, num_file, file); } + + // When expanding a function name starting with s:, match the <SNR>nr_ + // prefix. + char_u *tofree = NULL; + if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { + const size_t len = STRLEN(pat) + 20; + + tofree = xmalloc(len); + snprintf((char *)tofree, len, "^<SNR>\\d\\+_%s", pat + 3); + pat = tofree; + } + if (xp->xp_context == EXPAND_LUA) { ILOG("PAT %s", pat); return nlua_expand_pat(xp, pat, num_file, file); @@ -5195,6 +5207,7 @@ ExpandFromContext ( } vim_regfree(regmatch.regprog); + xfree(tofree); return ret; } diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 63789b3981..09453e100d 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -387,9 +387,10 @@ static int put_view( if (wp->w_alt_fnum) { buf_T *const alt = buflist_findnr(wp->w_alt_fnum); - // Set the alternate file. + // Set the alternate file if the buffer is listed. if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL && *alt->b_fname != NUL + && alt->b_p_bl && (fputs("balt ", fd) < 0 || ses_fname(fd, alt, flagp, true) == FAIL)) { return FAIL; diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 7e78b9e9d6..d2a7c16186 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -104,8 +104,11 @@ for _,f in ipairs(shallowcopy(functions)) do elseif startswith(f.name, "nvim_tabpage_") then ismethod = true end + f.remote = f.remote_only or not f.lua_only + f.lua = f.lua_only or not f.remote_only + f.eval = (not f.lua_only) and (not f.remote_only) else - f.remote_only = true + f.remote = true f.since = 0 f.deprecated_since = 1 end @@ -127,7 +130,8 @@ for _,f in ipairs(shallowcopy(functions)) do newf.return_type = "Object" end newf.impl_name = f.name - newf.remote_only = true + newf.lua = false + newf.eval = false newf.since = 0 newf.deprecated_since = 1 functions[#functions+1] = newf @@ -192,7 +196,7 @@ end -- the real API. for i = 1, #functions do local fn = functions[i] - if fn.impl_name == nil and not fn.lua_only then + if fn.impl_name == nil and fn.remote then local args = {} output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') @@ -323,7 +327,7 @@ void msgpack_rpc_init_method_table(void) for i = 1, #functions do local fn = functions[i] - if not fn.lua_only then + if fn.remote then output:write(' msgpack_rpc_add_method_handler('.. '(String) {.data = "'..fn.name..'", '.. '.size = sizeof("'..fn.name..'") - 1}, '.. @@ -492,7 +496,7 @@ local function process_function(fn) end for _, fn in ipairs(functions) do - if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then + if fn.lua or fn.name:sub(1, 4) == '_vim' then process_function(fn) end end diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index d16453530f..679895421a 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do - if not (fun.remote_only or fun.lua_only) then + if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 2dad8fb781..31615e744a 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1865,7 +1865,7 @@ static void cs_release_csp(size_t i, bool freefnpp) alive = false; // cscope process no longer exists break; } - os_delay(50L, false); // sleep 50ms + os_delay(50L, false); // sleep 50 ms } } if (alive) diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index e13b9745a8..eb54ff28ee 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -263,8 +263,15 @@ end -- vim.fn.{func}(...) vim.fn = setmetatable({}, { __index = function(t, key) - local function _fn(...) - return vim.call(key, ...) + local _fn + if vim.api[key] ~= nil then + _fn = function() + error(string.format("Tried to call API function with vim.fn: use vim.api.%s instead", key)) + end + else + _fn = function(...) + return vim.call(key, ...) + end end t[key] = _fn return _fn diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 293a4d01db..34d8eb0ffe 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3859,8 +3859,8 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) /* May resize here so we don't have to do it in both cases below */ if (buf->b_ml.ml_usedchunks + 1 >= buf->b_ml.ml_numchunks) { buf->b_ml.ml_numchunks = buf->b_ml.ml_numchunks * 3 / 2; - buf->b_ml.ml_chunksize = (chunksize_T *) - xrealloc(buf->b_ml.ml_chunksize, + buf->b_ml.ml_chunksize = xrealloc( + buf->b_ml.ml_chunksize, sizeof(chunksize_T) * buf->b_ml.ml_numchunks); } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index f28658aa29..9dc85797b4 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -717,23 +717,22 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // Check clicked cell is foldcolumn int mouse_check_fold(void) { - int grid = mouse_grid; - int row = mouse_row; - int col = mouse_col; + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; int mouse_char = ' '; win_T *wp; - wp = mouse_find_win(&grid, &row, &col); + wp = mouse_find_win(&click_grid, &click_row, &click_col); if (wp && mouse_row >= 0 && mouse_row < Rows && mouse_col >= 0 && mouse_col <= Columns) { int multigrid = ui_has(kUIMultigrid); ScreenGrid *gp = multigrid ? &wp->w_grid : &default_grid; int fdc = win_fdccol_count(wp); - - row = multigrid && mouse_grid == 0 ? row : mouse_row; - col = multigrid && mouse_grid == 0 ? col : mouse_col; + int row = multigrid && mouse_grid == 0 ? click_row : mouse_row; + int col = multigrid && mouse_grid == 0 ? click_col : mouse_col; // Remember the character under the mouse, might be one of foldclose or // foldopen fillchars in the fold column. @@ -743,8 +742,8 @@ int mouse_check_fold(void) } // Check for position outside of the fold column. - if (wp->w_p_rl ? col < wp->w_width_inner - fdc : - col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { + if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc : + click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { mouse_char = ' '; } } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 4d8b11f832..0b4e2e1f23 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -8103,7 +8103,7 @@ static void nv_event(cmdarg_T *cap) // lists or dicts being used. may_garbage_collect = false; bool may_restart = (restart_edit != 0); - multiqueue_process_events(main_loop.events); + state_handle_k_event(); finish_op = false; if (may_restart) { // Tricky: if restart_edit was set before the handler we are in ctrl-o mode, diff --git a/src/nvim/option.c b/src/nvim/option.c index ac25c86b5f..dbd8ceb55c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2086,9 +2086,12 @@ int was_set_insecurely(win_T *const wp, char_u *opt, int opt_flags) /// Get a pointer to the flags used for the P_INSECURE flag of option /// "opt_idx". For some local options a local flags field is used. +/// NOTE: Caller must make sure that "wp" is set to the window from which +/// the option is used. static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) { - if (opt_flags & OPT_LOCAL) + if (opt_flags & OPT_LOCAL) { + assert(wp != NULL); switch ((int)options[opt_idx].indir) { case PV_STL: return &wp->w_p_stl_flags; case PV_FDE: return &wp->w_p_fde_flags; @@ -2097,6 +2100,7 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) case PV_FEX: return &wp->w_buffer->b_p_fex_flags; case PV_INEX: return &wp->w_buffer->b_p_inex_flags; } + } // Nothing special, return global flags field. return &options[opt_idx].flags; @@ -6178,6 +6182,8 @@ set_context_in_set_cmd( xp->xp_backslash = XP_BS_THREE; else xp->xp_backslash = XP_BS_ONE; + } else if (p == (char_u *)&p_ft) { + xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fe108ef1cc..f4c1ac9131 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -900,6 +900,7 @@ return { normal_fname_chars=true, vi_def=true, alloced=true, + expand=true, varname='p_ft', defaults={if_true={vi=""}} }, diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 9d6518841a..eca245650a 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -159,16 +159,28 @@ bool os_char_avail(void) return inbuf_poll(0, NULL) == kInputAvail; } -// Check for CTRL-C typed by reading all available characters. +/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed. +/// +/// This invokes a full libuv loop iteration which can be quite costly. +/// Prefer `line_breakcheck()` if called in a busy inner loop. +/// +/// Caller must at least check `got_int` before calling this function again. +/// checking for other low-level input state like `input_available()` might +/// also be relevant (i e to throttle idle processing when user input is +/// available) void os_breakcheck(void) { + if (got_int) { + return; + } + int save_us = updating_screen; // We do not want screen_resize() to redraw here. + // TODO(bfredl): we are already special casing redraw events, is this + // hack still needed? updating_screen++; - if (!got_int) { - loop_poll_events(&main_loop, 0); - } + loop_poll_events(&main_loop, 0); updating_screen = save_us; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2d67436aa6..467cac4f27 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2096,6 +2096,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext + bool area_active = false; + /* draw_state: items that are drawn in sequence: */ #define WL_START 0 /* nothing done yet */ # define WL_CMDLINE WL_START + 1 /* cmdline window column */ @@ -2656,7 +2658,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // already be in use. xfree(p_extra_free); p_extra_free = xmalloc(MAX_MCO * fdc + 1); - n_extra = fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); + n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); p_extra_free[n_extra] = NUL; p_extra = p_extra_free; c_extra = NUL; @@ -2850,6 +2852,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, if (draw_state == WL_LINE - 1 && n_extra == 0) { sign_idx = 0; draw_state = WL_LINE; + + if (has_decor && row == startrow + filler_lines) { + // hide virt_text on text hidden by 'nowrap' + decor_redraw_col(wp->w_buffer, vcol, off, true, &decor_state); + } + if (saved_n_extra) { /* Continue item from end of wrapped line. */ n_extra = saved_n_extra; @@ -2934,10 +2942,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vcol_prev < vcol // not at margin && vcol < tocol)) { area_attr = attr; // start highlighting + if (area_highlighting) { + area_active = true; + } } else if (area_attr != 0 && (vcol == tocol || (noinvcur && (colnr_T)vcol == wp->w_virtcol))) { area_attr = 0; // stop highlighting + area_active = false; } if (!n_extra) { @@ -3397,9 +3409,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_attr = hl_combine_attr(spell_attr, char_attr); } + if (wp->w_buffer->terminal) { + char_attr = hl_combine_attr(term_attrs[vcol], char_attr); + } + if (has_decor && v > 0) { + bool selected = (area_active || (area_highlighting && noinvcur + && (colnr_T)vcol == wp->w_virtcol)); int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, off, - &decor_state); + selected, &decor_state); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3409,10 +3427,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (wp->w_buffer->terminal) { - char_attr = hl_combine_attr(term_attrs[vcol], char_attr); - } - // Found last space before word: check for line break. if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak((int)(*ptr))) { @@ -4355,10 +4369,22 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) virt_pos++; continue; } - int cells = line_putchar(&s, &linebuf_char[col], 2, false); - linebuf_attr[col++] = virt_attr; + int attr; + bool through = false; + if (item->hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (item->hl_mode == kHlModeBlend) { + through = (*s.p == ' '); + attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); + } else { + attr = virt_attr; + } + schar_T dummy[2]; + int cells = line_putchar(&s, through ? dummy : &linebuf_char[col], + max_col-col, false); + linebuf_attr[col++] = attr; if (cells > 1) { - linebuf_attr[col++] = virt_attr; + linebuf_attr[col++] = attr; } } *end_col = MAX(*end_col, col); diff --git a/src/nvim/state.c b/src/nvim/state.c index b195c1d96b..a3c74789d1 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -75,6 +75,34 @@ getkey: } } +/// process events on main_loop, but interrupt if input is available +/// +/// This should be used to handle K_EVENT in states accepting input +/// otherwise bursts of events can block break checking indefinitely. +void state_handle_k_event(void) +{ + while (true) { + Event event = multiqueue_get(main_loop.events); + if (event.handler) { + event.handler(event.argv); + } + + if (multiqueue_empty(main_loop.events)) { + // don't breakcheck before return, caller should return to main-loop + // and handle input already. + return; + } + + // TODO(bfredl): as an further micro-optimization, we could check whether + // event.handler already checked input. + os_breakcheck(); + if (input_available() || got_int) { + return; + } + } +} + + /// Return true if in the current mode we need to use virtual. bool virtual_active(void) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 642c443318..913ef3baed 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -169,19 +169,20 @@ void terminal_teardown(void) multiqueue_free(refresh_timer.events); time_watcher_close(&refresh_timer, NULL); pmap_free(ptr_t)(invalidated_terminals); + invalidated_terminals = NULL; } // public API {{{ -Terminal *terminal_open(TerminalOptions opts) +Terminal *terminal_open(buf_T *buf, TerminalOptions opts) { // Create a new terminal instance and configure it Terminal *rv = xcalloc(1, sizeof(Terminal)); rv->opts = opts; rv->cursor.visible = true; // Associate the terminal instance with the new buffer - rv->buf_handle = curbuf->handle; - curbuf->terminal = rv; + rv->buf_handle = buf->handle; + buf->terminal = rv; // Create VTerm rv->vt = vterm_new(opts.height, opts.width); vterm_set_utf8(rv->vt, 1); @@ -198,28 +199,36 @@ Terminal *terminal_open(TerminalOptions opts) // have as many lines as screen rows when refresh_scrollback is called rv->invalid_start = 0; rv->invalid_end = opts.height; - refresh_screen(rv, curbuf); + + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + + refresh_screen(rv, buf); set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666 // Default settings for terminal buffers - curbuf->b_p_ma = false; // 'nomodifiable' - curbuf->b_p_ul = -1; // 'undolevels' - curbuf->b_p_scbk = // 'scrollback' (initialize local from global) + buf->b_p_ma = false; // 'nomodifiable' + buf->b_p_ul = -1; // 'undolevels' + buf->b_p_scbk = // 'scrollback' (initialize local from global) (p_scbk < 0) ? 10000 : MAX(1, p_scbk); - curbuf->b_p_tw = 0; // 'textwidth' + buf->b_p_tw = 0; // 'textwidth' set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); - buf_set_term_title(curbuf, (char *)curbuf->b_ffname); + if (buf->b_ffname != NULL) { + buf_set_term_title(buf, (char *)buf->b_ffname); + } RESET_BINDING(curwin); // Reset cursor in current window. curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; // Apply TermOpen autocmds _before_ configuring the scrollback buffer. - apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf); // Local 'scrollback' _after_ autocmds. - curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk; + buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk; + + aucmd_restbuf(&aco); // Configure the scrollback buffer. - rv->sb_size = (size_t)curbuf->b_p_scbk; + rv->sb_size = (size_t)buf->b_p_scbk; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); // Configure the color palette. Try to get the color from: @@ -457,7 +466,7 @@ static int terminal_execute(VimState *state, int key) case K_EVENT: // We cannot let an event free the terminal yet. It is still needed. s->term->refcount++; - multiqueue_process_events(main_loop.events); + state_handle_k_event(); s->term->refcount--; if (s->term->buf_handle == 0) { s->close = true; @@ -511,7 +520,9 @@ void terminal_destroy(Terminal *term) } if (!term->refcount) { - if (pmap_has(ptr_t)(invalidated_terminals, term)) { + // might be destroyed after terminal_teardown is invoked + if (invalidated_terminals + && pmap_has(ptr_t)(invalidated_terminals, term)) { // flush any pending changes to the buffer block_autocmds(); refresh_terminal(term); @@ -1324,6 +1335,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) // focused) of a invalidated terminal static void refresh_screen(Terminal *term, buf_T *buf) { + assert(buf == curbuf); // TODO(bfredl): remove this condition int changed = 0; int added = 0; int height; diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index daf3c9c110..a47d20a265 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -33,7 +33,6 @@ source test_move.vim source test_partial.vim source test_popup.vim source test_put.vim -source test_recover.vim source test_scroll_opt.vim source test_sort.vim source test_sha256.vim diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index a66aee5e02..489b2477e6 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -569,6 +569,21 @@ func Test_cmdline_complete_user_cmd() delcommand Foo endfunc +func s:ScriptLocalFunction() + echo 'yes' +endfunc + +func Test_cmdline_complete_user_func() + call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func Test_cmdline_complete_user', @:) + call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:) + + " g: prefix also works + call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"echo g:Test_cmdline_complete_user_func', @:) +endfunc + func Test_cmdline_complete_user_names() if has('unix') && executable('whoami') let whoami = systemlist('whoami')[0] diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 2e190911b2..53b7da517e 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -92,6 +92,11 @@ func Test_screenpos() \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) close bwipe! + + call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + nmenu WinBar.TEST : + call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + nunmenu WinBar.TEST endfunc func Test_screenpos_number() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 6bc5fba5db..76e5de7df9 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -358,6 +358,7 @@ let s:filename_checks = { \ 'po': ['file.po', 'file.pot'], \ 'pod': ['file.pod'], \ 'pod6': ['file.pod6'], + \ 'poke': ['file.pk'], \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], \ 'pov': ['file.pov'], \ 'povini': ['.povrayrc'], diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 7c7804212b..8486f3ff68 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -331,6 +331,20 @@ func Test_mkview_no_balt() %bwipe endfunc +func Test_mksession_no_balt() + edit Xtestfile1 + edit Xtestfile2 + + bdelete Xtestfile1 + mksession! Xtestview + + source Xtestview + call assert_equal(0, buflisted('Xtestfile1')) + + call delete('Xtestview') + %bwipe +endfunc + " Test :mkview with a file argument. func Test_mkview_file() " Create a view with line number and a fold. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 1202b842fd..79481097ec 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -230,6 +230,13 @@ func Test_set_completion() call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) set tags& + + " Expand values for 'filetype' + call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set filetype=sshdconfig', @:) + call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) + call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:) endfunc func Test_set_errors() @@ -627,6 +634,28 @@ func Test_opt_winminheight_term() call delete('Xwinminheight') endfunc +func Test_opt_winminheight_term_tabs() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + + " The tabline should be taken into account. + let lines =<< trim END + set wmh=0 stal=2 + split + split + split + split + tabnew + END + call writefile(lines, 'Xwinminheight') + let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11}) + call term_sendkeys(buf, ":set wmh=1\n") + call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))}) + + call StopVimInTerminal(buf) + call delete('Xwinminheight') +endfunc + " Test for setting option value containing spaces with isfname+=32 func Test_isfname_with_options() set isfname+=32 diff --git a/src/nvim/window.c b/src/nvim/window.c index 7b53077293..aa8d8727e7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5501,8 +5501,8 @@ void win_setminheight(void) // loop until there is a 'winminheight' that is possible while (p_wmh > 0) { - const int room = Rows - p_ch - tabline_height(); - const int needed = frame_minheight(topframe, NULL); + const int room = Rows - p_ch; + const int needed = min_rows() - 1; // 1 was added for the cmdline if (room >= needed) { break; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 437a1858f3..3db44f3f11 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -2087,4 +2087,67 @@ describe('API', function() eq("", meths.exec("messages", true)) end) end) + + + describe('nvim_open_term', function() + local screen + + before_each(function() + clear() + screen = Screen.new(100, 35) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background = Screen.colors.Plum1}; + [2] = {background = tonumber('0xffff40'), bg_indexed = true}; + [3] = {background = Screen.colors.Plum1, fg_indexed = true, foreground = tonumber('0x00e000')}; + [4] = {bold = true, reverse = true, background = Screen.colors.Plum1}; + }) + end) + + it('can batch process sequences', function() + local b = meths.create_buf(true,true) + meths.open_win(b, false, {width=79, height=31, row=1, col=1, relative='editor'}) + local t = meths.open_term(b, {}) + + meths.chan_send(t, io.open("test/functional/fixtures/smile2.cat", "r"):read("*a")) + screen:expect{grid=[[ + ^ | + {0:~}{1::smile }{0: }| + {0:~}{1: }{2:oooo$$$$$$$$$$$$oooo}{1: }{0: }| + {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{0: }| + {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:o$}{1: }{2:$$}{1: }{2:o$}{1: }{0: }| + {0:~}{1: }{2:o}{1: }{2:$}{1: }{2:oo}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:$$}{1: }{2:$$}{1: }{2:$$o$}{1: }{0: }| + {0:~}{1: }{2:oo}{1: }{2:$}{1: }{2:$}{1: "}{2:$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$o}{1: }{2:$$$o$$o$}{1: }{0: }| + {0:~}{1: "}{2:$$$$$$o$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$o}{1: }{2:$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$}{1: """}{2:$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$}{1:""""}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$}{1: }{0: }| + {0:~}{1: }{2:$$$}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$o}{1: }{0: }| + {0:~}{1: }{2:o$$}{1:" }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$o}{1: }{0: }| + {0:~}{1: }{2:$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" "}{2:$$$$$$ooooo$$$$o}{1: }{0: }| + {0:~}{1: }{2:o$$$oooo$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:o$$$$$$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$}{1:"}{2:$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$}{1:"""""""" }{0: }| + {0:~}{1: """" }{2:$$$$}{1: "}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" }{2:o$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$o}{1: """}{2:$$$$$$$$$$$$$$$$$$}{1:"}{2:$$}{1:" }{2:$$$}{1: }{0: }| + {0:~}{1: }{2:$$$o}{1: "}{2:$$}{1:""}{2:$$$$$$}{1:"""" }{2:o$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$o}{1: }{2:o$$$}{1:" }{0: }| + {0:~}{1: "}{2:$$$$o}{1: }{2:o$$$$$$o}{1:"}{2:$$$$o}{1: }{2:o$$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$$$oo}{1: ""}{2:$$$$o$$$$$o}{1: }{2:o$$$$}{1:"" }{0: }| + {0:~}{1: ""}{2:$$$$$oooo}{1: "}{2:$$$o$$$$$$$$$}{1:""" }{0: }| + {0:~}{1: ""}{2:$$$$$$$oo}{1: }{2:$$$$$$$$$$}{1: }{0: }| + {0:~}{1: """"}{2:$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$}{1:" }{0: }| + {0:~}{1: "}{2:$$$}{1:"""" }{0: }| + {0:~}{1: }{0: }| + {0:~}{3:Press ENTER or type command to continue}{1: }{0: }| + {0:~}{4:term://~/config2/docs/pres//32693:vim --clean +smile 29,39 All}{0: }| + {0:~}{1::call nvim__screenshot("smile2.cat") }{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end) + end) end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 3bbb4c4517..bcd5e22492 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -154,6 +154,7 @@ function tests.capabilities_for_client_supports_method() hoverProvider = true; definitionProvider = false; referencesProvider = false; + codeLensProvider = { resolveProvider = true; }; } } end; @@ -402,11 +403,11 @@ function tests.basic_check_buffer_open_and_change_incremental() contentChanges = { { range = { - start = { line = 1; character = 0; }; - ["end"] = { line = 2; character = 0; }; + start = { line = 1; character = 3; }; + ["end"] = { line = 1; character = 3; }; }; - rangeLength = 4; - text = "boop\n"; + rangeLength = 0; + text = "boop"; }; } }) diff --git a/test/functional/fixtures/smile2.cat b/test/functional/fixtures/smile2.cat new file mode 100644 index 0000000000..0feb32f293 --- /dev/null +++ b/test/functional/fixtures/smile2.cat @@ -0,0 +1,32 @@ +31,79 +[?25l[H[2J:smile
+ [103moooo$$$$$$$$$$$$oooo(B[m
+ [103moo$$$$$$$$$$$$$$$$$$$$$$$$o(B[m
+ [103moo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o(B[m [103mo$(B[m [103m$$(B[m [103mo$(B[m
+ [103mo(B[m [103m$(B[m [103moo(B[m [103mo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o(B[m [103m$$(B[m [103m$$(B[m [103m$$o$(B[m
+ [103moo(B[m [103m$(B[m [103m$(B[m "[103m$(B[m [103mo$$$$$$$$$(B[m [103m$$$$$$$$$$$$$(B[m [103m$$$$$$$$$o(B[m [103m$$$o$$o$(B[m
+ "[103m$$$$$$o$(B[m [103mo$$$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$o(B[m [103m$$$$$$$$(B[m
+ [103m$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$$(B[m """[103m$$$(B[m
+ "[103m$$$(B[m""""[103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m "[103m$$$(B[m
+ [103m$$$(B[m [103mo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m "[103m$$$o(B[m
+ [103mo$$(B[m" [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$o(B[m
+ [103m$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m" "[103m$$$$$$ooooo$$$$o(B[m
+ [103mo$$$oooo$$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103mo$$$$$$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$(B[m"[103m$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$$(B[m""""""""
+ """" [103m$$$$(B[m "[103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m" [103mo$$$(B[m
+ "[103m$$$o(B[m """[103m$$$$$$$$$$$$$$$$$$(B[m"[103m$$(B[m" [103m$$$(B[m
+ [103m$$$o(B[m "[103m$$(B[m""[103m$$$$$$(B[m"""" [103mo$$$(B[m
+ [103m$$$$o(B[m [103mo$$$(B[m"
+ "[103m$$$$o(B[m [103mo$$$$$$o(B[m"[103m$$$$o(B[m [103mo$$$$(B[m
+ "[103m$$$$$oo(B[m ""[103m$$$$o$$$$$o(B[m [103mo$$$$(B[m""
+ ""[103m$$$$$oooo(B[m "[103m$$$o$$$$$$$$$(B[m"""
+ ""[103m$$$$$$$oo(B[m [103m$$$$$$$$$$(B[m
+ """"[103m$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$(B[m"
+ "[103m$$$(B[m""""
+
+[32mPress ENTER or type command to continue(B[m
+(B[0;1;7mterm://~/config2/docs/pres//32693:vim --clean +smile 29,39 All
+(B[m:call nvim__screenshot("smile2.cat") [?25h
\ No newline at end of file diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua index d7f5df3a1e..023cdd4ae1 100644 --- a/test/functional/legacy/options_spec.lua +++ b/test/functional/legacy/options_spec.lua @@ -42,6 +42,20 @@ describe('set', function() matches('E36: Not enough room', exc_exec('set wmh=1')) end) + it('winminheight works with tabline', function() + local screen = Screen.new(20, 11) + screen:attach() + source([[ + set wmh=0 stal=2 + split + split + split + split + tabnew + ]]) + matches('E36: Not enough room', exc_exec('set wmh=1')) + end) + it('scroll works', function() local screen = Screen.new(42, 16) screen:attach() diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index e253db5297..9bf00b594b 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -715,6 +715,11 @@ describe('lua stdlib', function() eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) end) + it('vim.fn should error when calling API function', function() + eq('Error executing lua: vim.lua:0: Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', + pcall_err(exec_lua, "vim.fn.nvim_get_current_line()")) + end) + it('vim.rpcrequest and vim.rpcnotify', function() exec_lua([[ chan = vim.fn.jobstart({'cat'}, {rpc=true}) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 8ac81daeef..c62d91cb6d 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -27,10 +27,10 @@ teardown(function() os.remove(fake_lsp_logfile) end) -local function fake_lsp_server_setup(test_name, timeout_ms) +local function fake_lsp_server_setup(test_name, timeout_ms, options) exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout = ... + local test_name, fixture_filename, logfile, timeout, options = ... TEST_RPC_CLIENT_ID = lsp.start_client { cmd_env = { NVIM_LOG_FILE = logfile; @@ -52,18 +52,19 @@ local function fake_lsp_server_setup(test_name, timeout_ms) on_init = function(client, result) TEST_RPC_CLIENT = client vim.rpcrequest(1, "init", result) + client.config.flags.allow_incremental_sync = options.allow_incremental_sync or false end; on_exit = function(...) vim.rpcnotify(1, "exit", ...) end; } - ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3) + ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}) end local function test_rpc_server(config) if config.test_name then clear() - fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3) + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options) end local client = setmetatable({}, { __index = function(_, name) @@ -257,6 +258,7 @@ describe('LSP', function() eq(0, client.resolved_capabilities().text_document_did_change) client.request('shutdown') client.notify('exit') + client.stop() end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -334,6 +336,8 @@ describe('LSP', function() local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") eq(full_kind, client.resolved_capabilities().text_document_did_change) eq(true, client.resolved_capabilities().text_document_save) + eq(false, client.resolved_capabilities().code_lens) + eq(false, client.resolved_capabilities().code_lens_resolve) end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -359,6 +363,8 @@ describe('LSP', function() eq(true, client.resolved_capabilities().hover) eq(false, client.resolved_capabilities().goto_definition) eq(false, client.resolved_capabilities().rename) + eq(true, client.resolved_capabilities().code_lens) + eq(true, client.resolved_capabilities().code_lens_resolve) -- known methods for resolved capabilities eq(true, client.supports_method("textDocument/hover")) @@ -680,8 +686,7 @@ describe('LSP', function() } end) - -- TODO(askhan) we don't support full for now, so we can disable these tests. - pending('should check the body and didChange incremental', function() + it('should check the body and didChange incremental', function() local expected_callbacks = { {NIL, "shutdown", {}, 1}; {NIL, "finish", {}, 1}; @@ -690,6 +695,7 @@ describe('LSP', function() local client test_rpc_server { test_name = "basic_check_buffer_open_and_change_incremental"; + options = { allow_incremental_sync = true }; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -716,7 +722,7 @@ describe('LSP', function() if method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + "123boop"; }) ]] client.notify('finish') diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 7a87521a6b..6cf549909a 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -8,6 +8,7 @@ local exec_lua = helpers.exec_lua local exec = helpers.exec local expect_events = helpers.expect_events local meths = helpers.meths +local command = helpers.command describe('decorations providers', function() local screen @@ -314,11 +315,30 @@ describe('extmark decorations', function() [2] = {foreground = Screen.colors.Brown}; [3] = {bold = true, foreground = Screen.colors.SeaGreen}; [4] = {background = Screen.colors.Red1, foreground = Screen.colors.Gray100}; + [5] = {foreground = Screen.colors.Brown, bold = true}; + [6] = {foreground = Screen.colors.DarkCyan}; + [7] = {foreground = Screen.colors.Grey0, background = tonumber('0xff4c4c')}; + [8] = {foreground = tonumber('0x180606'), background = tonumber('0xff4c4c')}; + [9] = {foreground = tonumber('0xe40c0c'), background = tonumber('0xff4c4c'), bold = true}; + [10] = {foreground = tonumber('0xb20000'), background = tonumber('0xff4c4c')}; + [11] = {blend = 30, background = Screen.colors.Red1}; + [12] = {foreground = Screen.colors.Brown, blend = 30, background = Screen.colors.Red1, bold = true}; + [13] = {foreground = Screen.colors.Fuchsia}; + [14] = {background = Screen.colors.Red1, foreground = Screen.colors.Black}; + [15] = {background = Screen.colors.Red1, foreground = tonumber('0xb20000')}; + [16] = {blend = 30, background = Screen.colors.Red1, foreground = Screen.colors.Magenta1}; + [17] = {bold = true, foreground = Screen.colors.Brown, background = Screen.colors.LightGrey}; + [18] = {background = Screen.colors.LightGrey}; + [19] = {foreground = Screen.colors.Cyan4, background = Screen.colors.LightGrey}; + [20] = {foreground = tonumber('0x180606'), background = tonumber('0xf13f3f')}; + [21] = {foreground = Screen.colors.Gray0, background = tonumber('0xf13f3f')}; + [22] = {foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f')}; + [23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey}; + [24] = {bold = true}; } end) - it('can have virtual text of overlay style', function() - insert [[ + local example_text = [[ for _,item in ipairs(items) do local text, hl_id_cell, count = unpack(item) if hl_id_cell ~= nil then @@ -331,69 +351,164 @@ for _,item in ipairs(items) do colpos = colpos+1 end end]] - feed 'gg' - local ns = meths.create_namespace 'test' - for i = 1,9 do - meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'}) - if i == 3 or (i >= 6 and i <= 9) then - meths.buf_set_extmark(0, ns, i, 4, { virt_text={{'|', 'NonText'}}, virt_text_pos='overlay'}) + it('can have virtual text of overlay position', function() + insert(example_text) + feed 'gg' + + local ns = meths.create_namespace 'test' + for i = 1,9 do + meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'}) + if i == 3 or (i >= 6 and i <= 9) then + meths.buf_set_extmark(0, ns, i, 4, { virt_text={{'|', 'NonText'}}, virt_text_pos='overlay'}) + end end - end - meths.buf_set_extmark(0, ns, 9, 10, { virt_text={{'foo'}, {'bar', 'MoreMsg'}, {'!!', 'ErrorMsg'}}, virt_text_pos='overlay'}) - - -- can "float" beyond end of line - meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) - -- bound check: right edge of window - meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) - - screen:expect{grid=[[ - ^for _,item in ipairs(items) do | - {2:|} local text, hl_id_cell, count = unpack(item) | - {2:|} if hl_id_cell ~= nil tbork bork bork {4:bork bork}| - {2:|} {1:|} hl_id = hl_id_cell | - {2:|} end | - {2:|} for _ = 1, (count or 1) {4:loopy} | - {2:|} {1:|} local cell = line[colpos] | - {2:|} {1:|} cell.text = text | - {2:|} {1:|} cell.hl_id = hl_id | - {2:|} {1:|} cofoo{3:bar}{4:!!}olpos+1 | - end | - end | - {1:~ }| - {1:~ }| - | - ]]} - - - -- handles broken lines - screen:try_resize(22, 25) - screen:expect{grid=[[ - ^for _,item in ipairs(i| - tems) do | - {2:|} local text, hl_id_| - cell, count = unpack(i| - tem) | - {2:|} if hl_id_cell ~= n| - il tbork bork bork {4:bor}| - {2:|} {1:|} hl_id = hl_id_| - cell | - {2:|} end | - {2:|} for _ = 1, (count | - or 1) {4:loopy} | - {2:|} {1:|} local cell = l| - ine[colpos] | - {2:|} {1:|} cell.text = te| - xt | - {2:|} {1:|} cell.hl_id = h| - l_id | - {2:|} {1:|} cofoo{3:bar}{4:!!}olpo| - s+1 | - end | - end | - {1:~ }| - {1:~ }| - | - ]]} + meths.buf_set_extmark(0, ns, 9, 10, { virt_text={{'foo'}, {'bar', 'MoreMsg'}, {'!!', 'ErrorMsg'}}, virt_text_pos='overlay'}) + + -- can "float" beyond end of line + meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) + -- bound check: right edge of window + meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + + screen:expect{grid=[[ + ^for _,item in ipairs(items) do | + {2:|} local text, hl_id_cell, count = unpack(item) | + {2:|} if hl_id_cell ~= nil tbork bork bork {4:bork bork}| + {2:|} {1:|} hl_id = hl_id_cell | + {2:|} end | + {2:|} for _ = 1, (count or 1) {4:loopy} | + {2:|} {1:|} local cell = line[colpos] | + {2:|} {1:|} cell.text = text | + {2:|} {1:|} cell.hl_id = hl_id | + {2:|} {1:|} cofoo{3:bar}{4:!!}olpos+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + + + -- handles broken lines + screen:try_resize(22, 25) + screen:expect{grid=[[ + ^for _,item in ipairs(i| + tems) do | + {2:|} local text, hl_id_| + cell, count = unpack(i| + tem) | + {2:|} if hl_id_cell ~= n| + il tbork bork bork {4:bor}| + {2:|} {1:|} hl_id = hl_id_| + cell | + {2:|} end | + {2:|} for _ = 1, (count | + or 1) {4:loopy} | + {2:|} {1:|} local cell = l| + ine[colpos] | + {2:|} {1:|} cell.text = te| + xt | + {2:|} {1:|} cell.hl_id = h| + l_id | + {2:|} {1:|} cofoo{3:bar}{4:!!}olpo| + s+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + end) + + it('can have virtual text of overlay position and styling', function() + insert(example_text) + feed 'gg' + local ns = meths.create_namespace 'test' + + command 'set ft=lua' + command 'syntax on' + + screen:expect{grid=[[ + {5:^for} _,item {5:in} {6:ipairs}(items) {5:do} | + {5:local} text, hl_id_cell, count = unpack(item) | + {5:if} hl_id_cell ~= {13:nil} {5:then} | + hl_id = hl_id_cell | + {5:end} | + {5:for} _ = {13:1}, (count {5:or} {13:1}) {5:do} | + {5:local} cell = line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + | + ]]} + + command 'hi Blendy guibg=Red blend=30' + meths.buf_set_extmark(0, ns, 1, 5, { virt_text={{'blendy text - here', 'Blendy'}}, virt_text_pos='overlay', hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 2, 5, { virt_text={{'combining color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='combine'}) + meths.buf_set_extmark(0, ns, 3, 5, { virt_text={{'replacing color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='replace'}) + + meths.buf_set_extmark(0, ns, 4, 5, { virt_text={{'blendy text - here', 'Blendy'}}, virt_text_pos='overlay', hl_mode='blend', virt_text_hide=true}) + meths.buf_set_extmark(0, ns, 5, 5, { virt_text={{'combining color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='combine', virt_text_hide=true}) + meths.buf_set_extmark(0, ns, 6, 5, { virt_text={{'replacing color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='replace', virt_text_hide=true}) + + screen:expect{grid=[[ + {5:^for} _,item {5:in} {6:ipairs}(items) {5:do} | + {5:l}{8:blen}{7:dy}{10:e}{7:text}{10:h}{7:-}{10:_}{7:here}ell, count = unpack(item) | + {5:i}{12:c}{11:ombining color} {13:nil} {5:then} | + {11:replacing color}d_cell | + {5:e}{8:bl}{14:endy}{15:i}{14:text}{15:o}{14:-}{15:o}{14:h}{7:ere} | + {5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} | + {11:replacing color} line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + | + ]]} + + feed 'V5G' + screen:expect{grid=[[ + {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do} | + {18: }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count = unpack(item)} | + {18: }{17:i}{12:c}{11:ombining color}{18: }{23:nil}{18: }{17:then} | + {18: }{11:replacing color}{18:d_cell} | + {18: }{5:^e}{17:nd} | + {5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} | + {11:replacing color} line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + {24:-- VISUAL LINE --} | + ]]} + + feed 'jj' + screen:expect{grid=[[ + {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do} | + {18: }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count = unpack(item)} | + {18: }{17:i}{12:c}{11:ombining color}{18: }{23:nil}{18: }{17:then} | + {18: }{11:replacing color}{18:d_cell} | + {18: }{17:end} | + {18: }{17:for}{18: _ = }{23:1}{18:, (count }{17:or}{18: }{23:1}{18:) }{17:do} | + {18: }^ {18: }{17:local}{18: cell = line[colpos]} | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + {24:-- VISUAL LINE --} | + ]]} end) end) diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index d3b1d33956..8883ad8270 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -464,6 +464,206 @@ describe("folded lines", function() end end) + it("works with vsplit", function() + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + feed('zO') + feed_command("rightbelow vnew") + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + if multigrid then + meths.input_mouse('left', 'press', '', 4, 0, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {2:[No Name] [+] }{3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:+}{5:+--- 4 lines: bb····}| + {7:│}ff | + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 23) + screen:expect([[ + {7:-}aa {2:│}{7:-}^aa | + {7:-}bb {2:│}{7:+}{5:+--- 4 lines: bb····}| + {7:2}cc {2:│}{7:│}ff | + {7:2}dd {2:│}{1:~ }| + {7:2}ee {2:│}{1:~ }| + {7:│}ff {2:│}{1:~ }| + {2:[No Name] [+] }{3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 1, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {2:[No Name] [+] }{3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 23) + screen:expect([[ + {7:-}aa {2:│}{7:-}^aa | + {7:-}bb {2:│}{7:-}bb | + {7:2}cc {2:│}{7:2}cc | + {7:2}dd {2:│}{7:2}dd | + {7:2}ee {2:│}{7:2}ee | + {7:│}ff {2:│}{7:│}ff | + {2:[No Name] [+] }{3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {3:[No Name] [+] }{2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:+}{5:^+--- 4 lines: bb····}| + {7:│}ff | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {7:-}aa {2:│}{7:-}aa | + {7:+}{5:^+--- 4 lines: bb····}{2:│}{7:-}bb | + {7:│}ff {2:│}{7:2}cc | + {1:~ }{2:│}{7:2}dd | + {1:~ }{2:│}{7:2}ee | + {1:~ }{2:│}{7:│}ff | + {3:[No Name] [+] }{2:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {3:[No Name] [+] }{2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 6 lines: aa·····}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + {7:+}{5:^+-- 6 lines: aa·····}{2:│}{7:-}aa | + {1:~ }{2:│}{7:-}bb | + {1:~ }{2:│}{7:2}cc | + {1:~ }{2:│}{7:2}dd | + {1:~ }{2:│}{7:2}ee | + {1:~ }{2:│}{7:│}ff | + {3:[No Name] [+] }{2:[No Name] [+] }| + :1 | + ]]) + end + end) + it("works with tab", function() insert([[ aa diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 9313a35708..ea8968a653 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -1,16 +1,18 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim +local clear, feed_command = helpers.clear, helpers.feed_command local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq local command = helpers.command local expect = helpers.expect +local meths = helpers.meths +local exec_lua = helpers.exec_lua local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') -describe('mappings', function() - local cid +before_each(clear) +describe('mappings', function() local add_mapping = function(mapping, send) - local cmd = "nnoremap "..mapping.." :call rpcnotify("..cid..", 'mapped', '" + local cmd = "nnoremap "..mapping.." :call rpcnotify(1, 'mapped', '" ..send:gsub('<', '<lt>').."')<cr>" feed_command(cmd) end @@ -21,8 +23,6 @@ describe('mappings', function() end before_each(function() - clear() - cid = nvim('get_api_info')[1] add_mapping('<C-L>', '<C-L>') add_mapping('<C-S-L>', '<C-S-L>') add_mapping('<s-up>', '<s-up>') @@ -115,7 +115,6 @@ describe('mappings', function() end) describe('input utf sequences that contain CSI/K_SPECIAL', function() - before_each(clear) it('ok', function() feed('i…<esc>') expect('…') @@ -129,7 +128,6 @@ describe('input non-printable chars', function() it("doesn't crash when echoing them back", function() write_file("Xtest-overwrite", [[foobar]]) - clear() local screen = Screen.new(60,8) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, @@ -215,3 +213,27 @@ describe('input non-printable chars', function() ]]) end) end) + +describe("event processing and input", function() + it('not blocked by event bursts', function() + meths.set_keymap('', '<f2>', "<cmd>lua vim.rpcnotify(1, 'stop') winning = true <cr>", {noremap=true}) + + exec_lua [[ + winning = false + burst = vim.schedule_wrap(function(tell) + if tell then + vim.rpcnotify(1, 'start') + end + -- Are we winning, son? + if not winning then + burst(false) + end + end) + burst(true) + ]] + + eq({'notification', 'start', {}}, next_msg()) + feed '<f2>' + eq({'notification', 'stop', {}}, next_msg()) + end) +end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index aab8eb4464..5f381175f1 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -147,8 +147,8 @@ set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0 set(MSGPACK_SHA256 bfbb71b7c02f806393bc3cbc491b40523b89e64f83860c58e3e54af47de176e4) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/1d8b747c161db457e032a023ebbff511f5de5ec2.tar.gz) -set(LUAJIT_SHA256 20a159c38a98ecdb6368e8d655343b6036622a29a1621da9dc303f7ed9bf37f3) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/787736990ac3b7d5ceaba2697c7d0f58f77bb782.tar.gz) +set(LUAJIT_SHA256 2e3f74bc279f46cc463abfc67b36e69faaf0366237004771f4cac4bf2a9f5efb) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) |