diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/doc/api.txt | 88 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 21 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 45 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 12 | ||||
-rw-r--r-- | runtime/doc/nvim_terminal_emulator.txt | 8 | ||||
-rw-r--r-- | runtime/filetype.vim | 3 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 75 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 13 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 248 | ||||
-rw-r--r-- | runtime/lua/vim/shared.lua | 14 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 6 | ||||
-rw-r--r-- | runtime/pack/dist/opt/termdebug/plugin/termdebug.vim | 168 | ||||
-rw-r--r-- | runtime/queries/c/highlights.scm | 17 | ||||
-rw-r--r-- | runtime/syntax/lsp_markdown.vim | 15 |
17 files changed, 642 insertions, 103 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index fdc41af1d5..c72381fd06 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -538,6 +538,21 @@ nvim__screenshot({path}) *nvim__screenshot()* Attributes: ~ {fast} +nvim__set_hl_ns({ns_id}) *nvim__set_hl_ns()* + Set active namespace for highlights. + + NB: this function can be called from async contexts, but the + semantics are not yet well-defined. To start with + |nvim_set_decoration_provider| on_win and on_line callbacks + are explicitly allowed to change the namespace during a redraw + cycle. + + Attributes: ~ + {fast} + + Parameters: ~ + {ns_id} the namespace to activate + nvim__stats() *nvim__stats()* Gets internal stats. @@ -599,6 +614,22 @@ nvim_call_function({fn}, {args}) *nvim_call_function()* Return: ~ Result of the function call +nvim_chan_send({chan}, {data}) *nvim_chan_send()* + 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. + + Parameters: ~ + {chan} id of the channel + {data} data to write. 8-bit clean: can contain NUL bytes. + nvim_command({command}) *nvim_command()* Executes an ex-command. @@ -1160,6 +1191,27 @@ nvim_notify({msg}, {log_level}, {opts}) *nvim_notify()* {log_level} The log level {opts} Reserved for future use. +nvim_open_term({buffer}, {opts}) *nvim_open_term()* + 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. + + Parameters: ~ + {buffer} the buffer to use (expected to be empty) + {opts} Optional parameters. Reserved for future use. + nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* Open a new window. @@ -1415,9 +1467,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}) @@ -1623,21 +1675,6 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()* keys are also recognized: `default` : don't override existing definition, like `hi default` -nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()* - Set active namespace for highlights. - - NB: this function can be called from async contexts, but the - semantics are not yet well-defined. To start with - |nvim_set_decoration_provider| on_win and on_line callbacks - are explicitly allowed to change the namespace during a redraw - cycle. - - Attributes: ~ - {fast} - - Parameters: ~ - {ns_id} the namespace to activate - nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()* Sets a global |mapping| for the given mode. @@ -1737,7 +1774,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 +2239,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/eval.txt b/runtime/doc/eval.txt index be7c026f5a..1832e2443f 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2498,6 +2498,8 @@ win_gotoid({expr}) Number go to |window-ID| {expr} win_id2tabwin({expr}) List get tab and window nr from |window-ID| win_id2win({expr}) Number get window nr from |window-ID| win_screenpos({nr}) List get screen position of window {nr} +win_splitmove({nr}, {target} [, {options}]) + none move window {nr} to split of {target} winbufnr({nr}) Number buffer number of window {nr} wincol() Number window column of the cursor winheight({nr}) Number height of window {nr} @@ -9370,6 +9372,25 @@ win_screenpos({nr}) *win_screenpos()* Return [0, 0] if the window cannot be found in the current tabpage. +win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* + Move the window {nr} to a new split of the window {target}. + This is similar to moving to {target}, creating a new window + using |:split| but having the same contents as window {nr}, and + then closing {nr}. + + Both {nr} and {target} can be window numbers or |window-ID|s. + + Returns zero for success, non-zero for failure. + + {options} is a Dictionary with the following optional entries: + "vertical" When TRUE, the split is created vertically, + like with |:vsplit|. + "rightbelow" When TRUE, the split is made below or to the + right (if vertical). When FALSE, it is done + above or to the left (if vertical). When not + present, the values of 'splitbelow' and + 'splitright' are used. + *winbufnr()* winbufnr({nr}) The result is a Number, which is the number of the buffer associated with window {nr}. {nr} can be the window number or diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index d2d88fb9ba..67a10c7efb 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[]` | @@ -1581,6 +1586,12 @@ convert_signature_help_to_markdown_lines({signature_help}) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp +create_file({change}) *vim.lsp.util.create_file()* + TODO: Documentation + +delete_file({change}) *vim.lsp.util.delete_file()* + TODO: Documentation + *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. @@ -1788,12 +1799,12 @@ make_workspace_params({added}, {removed}) {removed} *vim.lsp.util.open_floating_preview()* -open_floating_preview({contents}, {filetype}, {opts}) +open_floating_preview({contents}, {syntax}, {opts}) Shows contents in a floating window. Parameters: ~ {contents} table of lines to show in window - {filetype} string of filetype to set for opened buffer + {syntax} string of syntax to set for opened buffer {opts} dictionary with optional fields Return: ~ @@ -1824,6 +1835,10 @@ preview_location({location}) *vim.lsp.util.preview_location()* (bufnr,winnr) buffer and window number of floating window or nil +rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()* + Parameters: ~ + {opts} (table) + set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* Replaces text in a range with new text. 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/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 841c365cbe..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 {} @@ -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..7e43eb84de 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -749,6 +749,9 @@ function protocol.make_client_capabilities() }; workspaceFolders = true; applyEdit = true; + workspaceEdit = { + resourceOperations = {'rename', 'create', 'delete',}, + }; }; callHierarchy = { dynamicRegistration = false; @@ -975,6 +978,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..5c139253ee 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. @@ -453,6 +612,62 @@ function M.text_document_completion_list_to_complete_items(result, prefix) return matches end + +--- Rename old_fname to new_fname +-- +--@param opts (table) +-- overwrite? bool +-- ignoreIfExists? bool +function M.rename(old_fname, new_fname, opts) + opts = opts or {} + local bufnr = vim.fn.bufadd(old_fname) + vim.fn.bufload(bufnr) + local target_exists = vim.loop.fs_stat(new_fname) ~= nil + if target_exists and not opts.overwrite or opts.ignoreIfExists then + vim.notify('Rename target already exists. Skipping rename.') + return + end + local ok, err = os.rename(old_fname, new_fname) + assert(ok, err) + api.nvim_buf_call(bufnr, function() + vim.cmd('saveas! ' .. vim.fn.fnameescape(new_fname)) + end) +end + + +local function create_file(change) + local opts = change.options or {} + -- from spec: Overwrite wins over `ignoreIfExists` + local fname = vim.uri_to_fname(change.uri) + if not opts.ignoreIfExists or opts.overwrite then + local file = io.open(fname, 'w') + file:close() + end + vim.fn.bufadd(fname) +end + + +local function delete_file(change) + local opts = change.options or {} + local fname = vim.uri_to_fname(change.uri) + local stat = vim.loop.fs_stat(fname) + if opts.ignoreIfNotExists and not stat then + return + end + assert(stat, "Cannot delete not existing file or folder " .. fname) + local flags + if stat and stat.type == 'directory' then + flags = opts.recursive and 'rf' or 'd' + else + flags = '' + end + local bufnr = vim.fn.bufadd(fname) + local result = tonumber(vim.fn.delete(fname, flags)) + assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat)) + api.nvim_buf_delete(bufnr, { force = true }) +end + + --- Applies a `WorkspaceEdit`. --- --@param workspace_edit (table) `WorkspaceEdit` @@ -460,8 +675,17 @@ end function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for idx, change in ipairs(workspace_edit.documentChanges) do - if change.kind then - -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile + if change.kind == "rename" then + M.rename( + vim.uri_to_fname(change.oldUri), + vim.uri_to_fname(change.newUri), + change.options + ) + elseif change.kind == 'create' then + create_file(change) + elseif change.kind == 'delete' then + delete_file(change) + elseif change.kind then error(string.format("Unsupported change: %q", vim.inspect(change))) else M.apply_text_document_edit(change, idx) @@ -687,8 +911,8 @@ function M.preview_location(location) end local range = location.targetRange or location.range local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false) - local filetype = api.nvim_buf_get_option(bufnr, 'filetype') - return M.open_floating_preview(contents, filetype) + local syntax = api.nvim_buf_get_option(bufnr, 'syntax') + return M.open_floating_preview(contents, syntax) end --@private @@ -873,8 +1097,10 @@ 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") local idx = 1 --@private local function apply_syntax_to_region(ft, start, finish) @@ -975,7 +1201,7 @@ end --- Shows contents in a floating window. --- --@param contents table of lines to show in window ---@param filetype string of filetype to set for opened buffer +--@param syntax string of syntax to set for opened buffer --@param opts dictionary with optional fields -- - height of floating window -- - width of floating window @@ -988,10 +1214,10 @@ end -- - pad_bottom number of lines to pad contents at bottom --@returns bufnr,winnr buffer and window number of the newly created floating ---preview window -function M.open_floating_preview(contents, filetype, opts) +function M.open_floating_preview(contents, syntax, opts) validate { contents = { contents, 't' }; - filetype = { filetype, 's', true }; + syntax = { syntax, 's', true }; opts = { opts, 't', true }; } opts = opts or {} @@ -1004,12 +1230,12 @@ function M.open_floating_preview(contents, filetype, opts) local width, height = M._make_floating_popup_size(contents, opts) local floating_bufnr = api.nvim_create_buf(false, true) - if filetype then - api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype) + if syntax then + api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax) end local float_option = M.make_floating_popup_options(width, height, opts) local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option) - if filetype == 'markdown' then + if syntax == 'markdown' then api.nvim_win_set_option(floating_winnr, 'conceallevel', 2) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) 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/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 3af66b134c..64a5ba1fd8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -12,11 +12,7 @@ local M = vim.tbl_extend("error", query, language) setmetatable(M, { __index = function (t, k) - if k == "TSHighlighter" then - a.nvim_err_writeln("vim.TSHighlighter is deprecated, please use vim.treesitter.highlighter") - t[k] = require'vim.treesitter.highlighter' - return t[k] - elseif k == "highlighter" then + if k == "highlighter" then t[k] = require'vim.treesitter.highlighter' return t[k] end 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/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 96b43cf0d0..260750a85b 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -14,6 +14,7 @@ "union" "volatile" "goto" + "register" ] @keyword [ @@ -81,6 +82,8 @@ "|=" "&=" "^=" + ">>=" + "<<=" "--" "++" ] @operator @@ -117,7 +120,6 @@ (preproc_arg) (preproc_defined) ] @function.macro -; TODO (preproc_arg) @embedded (field_identifier) @property (statement_identifier) @label @@ -129,13 +131,22 @@ (type_descriptor) ] @type -(declaration type: [(identifier) (type_identifier)] @type) -(cast_expression type: [(identifier) (type_identifier)] @type) +(declaration (type_qualifier) @type) +(cast_expression type: (type_descriptor) @type) (sizeof_expression value: (parenthesized_expression (identifier) @type)) ((identifier) @constant (#match? @constant "^[A-Z][A-Z0-9_]+$")) +;; Preproc def / undef +(preproc_def + name: (_) @constant) +(preproc_call + directive: (preproc_directive) @_u + argument: (_) @constant + (#eq? @_u "#undef")) + + (comment) @comment ;; Parameters diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim new file mode 100644 index 0000000000..d5c1414f01 --- /dev/null +++ b/runtime/syntax/lsp_markdown.vim @@ -0,0 +1,15 @@ +" 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 mkdEscape matchgroup=mkdEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/.\zs/ keepend contains=mkdEscapeCh oneline concealends +syntax match mkdEscapeCh /./ contained +syntax match mkdNbsp / / conceal cchar= + +hi def link mkdEscape special |