diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/autoload/health/provider.vim | 2 | ||||
-rw-r--r-- | runtime/autoload/provider.vim | 5 | ||||
-rw-r--r-- | runtime/autoload/provider/pythonx.vim | 4 | ||||
-rw-r--r-- | runtime/autoload/remote/define.vim | 2 | ||||
-rw-r--r-- | runtime/doc/autocmd.txt | 1 | ||||
-rw-r--r-- | runtime/doc/editing.txt | 4 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 13 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 25 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 70 | ||||
-rw-r--r-- | runtime/filetype.vim | 10 | ||||
-rw-r--r-- | runtime/lua/vim/highlight.lua | 29 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 69 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/callbacks.lua | 17 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 28 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 311 | ||||
-rw-r--r-- | runtime/lua/vim/shared.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 5 | ||||
-rw-r--r-- | runtime/lua/vim/uri.lua | 10 | ||||
-rw-r--r-- | runtime/pack/dist/opt/termdebug/plugin/termdebug.vim | 21 |
20 files changed, 462 insertions, 168 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 6d481e9f49..4975dc66b8 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -581,7 +581,7 @@ function! s:check_ruby() abort endif call health#report_info('Host: '. host) - let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra ^^neovim$' : 'gem list -ra ^neovim$' + let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra "^^neovim$"' : 'gem list -ra ^neovim$' let latest_gem = s:system(split(latest_gem_cmd)) if s:shell_error || empty(latest_gem) call health#report_error('Failed to run: '. latest_gem_cmd, diff --git a/runtime/autoload/provider.vim b/runtime/autoload/provider.vim index dc24e801d0..803c1a0b1c 100644 --- a/runtime/autoload/provider.vim +++ b/runtime/autoload/provider.vim @@ -3,8 +3,11 @@ " Start the provider and perform a 'poll' request " " Returns a valid channel on success -function! provider#Poll(argv, orig_name, log_env) abort +function! provider#Poll(argv, orig_name, log_env, ...) abort let job = {'rpc': v:true, 'stderr_buffered': v:true} + if a:0 + let job = extend(job, a:1) + endif try let channel_id = jobstart(a:argv, job) if channel_id > 0 && rpcrequest(channel_id, 'poll') ==# 'ok' diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 23e7ff8f64..e89d519790 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -19,7 +19,7 @@ function! provider#pythonx#Require(host) abort call add(args, plugin.path) endfor - return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE') + return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE', {'overlapped': v:true}) endfunction function! s:get_python_executable_from_host_var(major_version) abort @@ -29,7 +29,7 @@ endfunction function! s:get_python_candidates(major_version) abort return { \ 2: ['python2', 'python2.7', 'python2.6', 'python'], - \ 3: ['python3', 'python3.8', 'python3.7', 'python3.6', 'python3.5', + \ 3: ['python3', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python3.5', \ 'python3.4', 'python3.3', 'python'] \ }[a:major_version] endfunction diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim index 2688a62a82..2aec96e365 100644 --- a/runtime/autoload/remote/define.vim +++ b/runtime/autoload/remote/define.vim @@ -24,7 +24,7 @@ function! remote#define#CommandOnHost(host, method, sync, name, opts) endif if has_key(a:opts, 'nargs') - call add(forward_args, ' <args>') + call add(forward_args, ' " . <q-args> . "') endif exe s:GetCommandPrefix(a:name, a:opts) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 64ca7b6a45..f1753b75cc 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -844,6 +844,7 @@ TextYankPost Just after a |yank| or |deleting| command, but not regcontents regname regtype + visual The `inclusive` flag combined with the |'[| and |']| marks can be used to calculate the precise region of the operation. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 23a65f16e4..ac398ec494 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1265,7 +1265,7 @@ exist, the next-higher scope in the hierarchy applies. other tabs and windows is not changed. *:tcd-* -:tcd[!] - Change to the previous current directory (before the +:tc[d][!] - Change to the previous current directory (before the previous ":tcd {path}" command). *:tch* *:tchdir* @@ -1280,7 +1280,7 @@ exist, the next-higher scope in the hierarchy applies. :lch[dir][!] Same as |:lcd|. *:lcd-* -:lcd[!] - Change to the previous current directory (before the +:lc[d][!] - Change to the previous current directory (before the previous ":lcd {path}" command). *:pw* *:pwd* *E187* diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 1992c34102..92f703a8a3 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1423,6 +1423,10 @@ PREDEFINED VIM VARIABLES *vim-variable* *v:var* *v:* *E963* Some variables can be set by the user, but the type cannot be changed. + *v:argv* *argv-variable* +v:argv The command line arguments Vim was invoked with. This is a + list of strings. The first item is the Vim command. + *v:beval_col* *beval_col-variable* v:beval_col The number of the column, over which the mouse pointer is. This is the byte index in the |v:beval_lnum| line. @@ -1587,6 +1591,8 @@ v:event Dictionary of event data for the current |autocommand|. Valid operation. regtype Type of register as returned by |getregtype()|. + visual Selection is visual (as opposed to, + e.g., via motion). completed_item Current selected complete item on |CompleteChanged|, Is `{}` when no complete item selected. @@ -2600,6 +2606,7 @@ argv([{nr} [, {winid}]) the whole |arglist| is returned. The {winid} argument specifies the window ID, see |argc()|. + For the Vim command line arguments see |v:argv|. assert_beeps({cmd}) *assert_beeps()* Run {cmd} and add an error message to |v:errors| if it does @@ -5499,6 +5506,11 @@ jobstart({cmd}[, {opts}]) *jobstart()* stdout data. |on_stderr|: (function) Callback invoked when the job emits stderr data. + overlapped: (boolean) Set FILE_FLAG_OVERLAPPED for the + standard input/output passed to the child process. + Normally you do not need to set this. + (Only available on MS-Windows, On other + platforms, this option is silently ignored.) pty: (boolean) Connect the job to a new pseudo terminal, and its streams to the master file descriptor. Then `on_stderr` is ignored, @@ -5828,6 +5840,7 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* "rhs" The {rhs} of the mapping as typed. "silent" 1 for a |:map-silent| mapping, else 0. "noremap" 1 if the {rhs} of the mapping is not remappable. + "script" 1 if mapping was defined with <script>. "expr" 1 for an expression mapping (|:map-<expr>|). "buffer" 1 for a buffer local mapping (|:map-local|). "mode" Modes for which the mapping is defined. In diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 9460e600e3..3a93390210 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -164,21 +164,21 @@ name: > LSP HIGHLIGHT *lsp-highlight* *hl-LspDiagnosticsError* -LspDiagnosticsError used for "Error" diagnostic virtual text +LspDiagnosticsError used for "Error" diagnostic virtual text *hl-LspDiagnosticsErrorSign* -LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column +LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column *hl-LspDiagnosticsWarning* -LspDiagnosticsWarning used for "Warning" diagnostic virtual text +LspDiagnosticsWarning used for "Warning" diagnostic virtual text *hl-LspDiagnosticsWarningSign* -LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column +LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column *hl-LspDiagnosticsInformation* -LspDiagnosticInformation used for "Information" diagnostic virtual text +LspDiagnosticsInformation used for "Information" diagnostic virtual text *hl-LspDiagnosticsInformationSign* -LspDiagnosticInformationSign used for "Information" signs in sign column +LspDiagnosticsInformationSign used for "Information" signs in sign column *hl-LspDiagnosticsHint* -LspDiagnosticHint used for "Hint" diagnostic virtual text +LspDiagnosticsHint used for "Hint" diagnostic virtual text *hl-LspDiagnosticsHintSign* -LspDiagnosticHintSign used for "Hint" diagnostic signs in sign column +LspDiagnosticsHintSign used for "Hint" diagnostic signs in sign column *hl-LspReferenceText* LspReferenceText used for highlighting "text" references *hl-LspReferenceRead* @@ -1042,15 +1042,10 @@ get_current_line_to_cursor() get_severity_highlight_name({severity}) TODO: Documentation - *vim.lsp.util.highlight_range()* -highlight_range({bufnr}, {ns}, {hiname}, {start}, {finish}) - TODO: Documentation - - *vim.lsp.util.highlight_region()* -highlight_region({ft}, {start}, {finish}) +jump_to_location({location}) *vim.lsp.util.jump_to_location()* TODO: Documentation -jump_to_location({location}) *vim.lsp.util.jump_to_location()* +preview_location({location}) *vim.lsp.util.preview_location()* TODO: Documentation locations_to_items({locations}) *vim.lsp.util.locations_to_items()* diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 2b83c35c90..5a49d36503 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -705,6 +705,10 @@ the highlight via > au TextYankPost * silent! lua require'vim.highlight'.on_yank("IncSearch", 500) < +If you want to exclude visual selections from highlighting on yank, use +> +au TextYankPost * silent! lua return (not vim.v.event.visual) and require'vim.highlight'.on_yank() +< vim.highlight.on_yank([{higroup}, {timeout}, {event}]) *vim.highlight.on_yank()* @@ -713,6 +717,16 @@ vim.highlight.on_yank([{higroup}, {timeout}, {event}]) in milliseconds ({timeout}, default `500`), and the event structure that is fired ({event}, default `vim.v.event`). + +vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive}) + *vim.highlight.range()* + Highlights the range between {start} and {finish} (tuples of {line,col}) + in buffer {bufnr} with the highlight group {higroup} using the namespace + {ns}. Optional arguments are the type of range (characterwise, linewise, + or blockwise, see |setreg|; default to characterwise) and whether the + range is inclusive (default false). + + ------------------------------------------------------------------------------ VIM.REGEX *lua-regex* @@ -827,6 +841,62 @@ vim.schedule({callback}) *vim.schedule()* Schedules {callback} to be invoked soon by the main event-loop. Useful to avoid |textlock| or other temporary restrictions. + +vim.defer_fn({fn}, {timeout}) *vim.defer_fn* + Defers calling {fn} until {timeout} ms passes. Use to do a one-shot timer + that calls {fn}. + + Parameters: ~ + {fn} Callback to call once {timeout} expires + {timeout} Time in ms to wait before calling {fn} + + Returns: ~ + |vim.loop|.new_timer() object + +vim.wait({time}, {callback} [, {interval}]) *vim.wait()* + Wait for {time} in milliseconds until {callback} returns `true`. + + Executes {callback} immediately and at approximately {interval} + milliseconds (default 200). Nvim still processes other events during + this time. + + + Returns: ~ + If {callback} returns `true` during the {time}: + `true, nil` + + If {callback} never returns `true` during the {time}: + `false, -1` + + If {callback} is interrupted during the {time}: + `false, -2` + + If {callback} errors, the error is raised. + + Examples: > + + --- + -- Wait for 100 ms, allowing other events to process + vim.wait(100, function() end) + + --- + -- Wait for 100 ms or until global variable set. + vim.wait(100, function() return vim.g.waiting_for_var end) + + --- + -- Wait for 1 second or until global variable set, checking every ~500 ms + vim.wait(1000, function() return vim.g.waiting_for_var end, 500) + + --- + -- Schedule a function to set a value in 100ms + vim.defer_fn(function() vim.g.timer_result = true end, 100) + + -- Would wait ten seconds if results blocked. Actually only waits 100 ms + if vim.wait(10000, function() return vim.g.timer_result end) then + print('Only waiting a little bit of time!') + end +< + vim.fn.{func}({...}) *vim.fn* Invokes |vim-function| or |user-function| {func} with arguments {...}. To call autoload functions, use the syntax: > diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 0b5003dc44..383a45b9d3 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -543,6 +543,9 @@ au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks " ERicsson LANGuage; Yaws is erlang too au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang +" Elm +au BufNewFile,BufRead *.elm setf elm + " Elm Filter Rules file au BufNewFile,BufRead filter-rules setf elmfilt @@ -880,11 +883,12 @@ au BufNewFile,BufRead *.ll setf lifelines " Lilo: Linux loader au BufNewFile,BufRead lilo.conf setf lilo -" Lisp (*.el = ELisp, *.cl = Common Lisp, *.jl = librep Lisp) +" Lisp (*.el = ELisp, *.cl = Common Lisp) +" *.jl was removed, it's also used for Julia, better skip than guess wrong. if has("fname_case") - au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.jl,*.L,.emacs,.sawfishrc setf lisp + au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp else - au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.jl,.emacs,.sawfishrc setf lisp + au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,.emacs,.sawfishrc setf lisp endif " SBCL implementation of Common Lisp diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 5c98c626a4..69c3c8a4dc 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -2,12 +2,34 @@ local api = vim.api local highlight = {} +--- Highlight range between two positions +--- +--@param bufnr number of buffer to apply highlighting to +--@param ns namespace to add highlight to +--@param higroup highlight group to use for highlighting +--@param rtype type of range (:help setreg, default charwise) +--@param inclusive boolean indicating whether the range is end-inclusive (default false) +function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive) + rtype = rtype or 'v' + inclusive = inclusive or false + + -- sanity check + if start[2] < 0 or finish[2] < start[2] then return end + + local region = vim.region(bufnr, start, finish, rtype, inclusive) + for linenr, cols in pairs(region) do + api.nvim_buf_add_highlight(bufnr, ns, higroup, linenr, cols[1], cols[2]) + end + +end + --- Highlight the yanked region --- +--- --- use from init.vim via --- au TextYankPost * lua require'vim.highlight'.on_yank() --- customize highlight group and timeout via --- au TextYankPost * lua require'vim.highlight'.on_yank("IncSearch", 500) +--- -- @param higroup highlight group for yanked region -- @param timeout time in ms before highlight is cleared -- @param event event structure @@ -27,10 +49,7 @@ function highlight.on_yank(higroup, timeout, event) pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]} pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]} - local region = vim.region(bufnr, pos1, pos2, event.regtype, event.inclusive) - for linenr, cols in pairs(region) do - api.nvim_buf_add_highlight(bufnr, yank_ns, higroup, linenr, cols[1], cols[2]) - end + highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive) vim.defer_fn( function() api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1) end, diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 61da2130c8..e759511347 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -46,31 +46,6 @@ local function is_dir(filename) return stat and stat.type == 'directory' or false end --- TODO Use vim.wait when that is available, but provide an alternative for now. -local wait = vim.wait or function(timeout_ms, condition, interval) - validate { - timeout_ms = { timeout_ms, 'n' }; - condition = { condition, 'f' }; - interval = { interval, 'n', true }; - } - assert(timeout_ms > 0, "timeout_ms must be > 0") - local _ = log.debug() and log.debug("wait.fallback", timeout_ms) - interval = interval or 200 - local interval_cmd = "sleep "..interval.."m" - local timeout = timeout_ms + uv.now() - -- TODO is there a better way to sync this? - while true do - uv.update_time() - if condition() then - return 0 - end - if uv.now() >= timeout then - return -1 - end - nvim_command(interval_cmd) - -- vim.loop.sleep(10) - end -end local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" } local valid_encodings = { @@ -122,19 +97,19 @@ local function validate_encoding(encoding) end function lsp._cmd_parts(input) - local cmd, cmd_args - if vim.tbl_islist(input) then - cmd = input[1] - cmd_args = {} - -- Don't mutate our input. - for i, v in ipairs(input) do - assert(type(v) == 'string', "input arguments must be strings") - if i > 1 then - table.insert(cmd_args, v) - end + vim.validate{cmd={ + input, + function() return vim.tbl_islist(input) end, + "list"}} + + local cmd = input[1] + local cmd_args = {} + -- Don't mutate our input. + for i, v in ipairs(input) do + vim.validate{["cmd argument"]={v, "s"}} + if i > 1 then + table.insert(cmd_args, v) end - else - error("cmd type must be list.") end return cmd, cmd_args end @@ -524,7 +499,7 @@ function lsp.start_client(config) function client.request(method, params, callback, bufnr) if not callback then callback = resolve_callback(method) - or error("not found: request callback for client "..client.name) + or error(string.format("not found: %q request callback for client %q.", method, client.name)) end local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr) -- TODO keep these checks or just let it go anyway? @@ -532,6 +507,8 @@ function lsp.start_client(config) or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp') or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition') or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation') + or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration') + or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition') or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') then @@ -609,9 +586,7 @@ do old_utf16_size) local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true)) - if old_byte_size == 0 then - return - end + -- Don't do anything if there are no clients attached. if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then return @@ -810,8 +785,8 @@ function lsp._vim_exit_handler() for _, client in pairs(active_clients) do client.stop() end - local wait_result = wait(500, function() return tbl_isempty(active_clients) end, 50) - if wait_result ~= 0 then + + if not vim.wait(500, function() return tbl_isempty(active_clients) end, 50) then for _, client in pairs(active_clients) do client.stop(true) end @@ -889,12 +864,14 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms) for _ in pairs(client_request_ids) do expected_result_count = expected_result_count + 1 end - local wait_result = wait(timeout_ms or 100, function() + + local wait_result, reason = vim.wait(timeout_ms or 100, function() return result_count >= expected_result_count end, 10) - if wait_result ~= 0 then + + if not wait_result then cancel() - return nil, wait_result_reason[wait_result] + return nil, wait_result_reason[reason] end return request_results end diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 37e9f1e5c1..4b14f0132d 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -72,6 +72,17 @@ M['textDocument/publishDiagnostics'] = function(_, _, result) err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri) return end + + -- Unloaded buffers should not handle diagnostics. + -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen. + -- This should trigger another publish of the diagnostics. + -- + -- In particular, this stops a ton of spam when first starting a server for current + -- unloaded buffers. + if not api.nvim_buf_is_loaded(bufnr) then + return + end + util.buf_clear_diagnostics(bufnr) -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic @@ -242,12 +253,12 @@ end -- Add boilerplate error validation and logging for all of these. for k, fn in pairs(M) do - M[k] = function(err, method, params, client_id) - local _ = log.debug() and log.debug('default_callback', method, { params = params, client_id = client_id, err = err }) + M[k] = function(err, method, params, client_id, bufnr) + log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr }) if err then error(tostring(err)) end - return fn(err, method, params, client_id) + return fn(err, method, params, client_id, bufnr) end end diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 78aabf08ce..696ce43a59 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -24,7 +24,7 @@ do local function path_join(...) return table.concat(vim.tbl_flatten{...}, path_sep) end - local logfilename = path_join(vim.fn.stdpath('data'), 'vim-lsp.log') + local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log') --- Return the log filename. function log.get_filename() diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 877d11411b..4fded1961d 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -633,8 +633,7 @@ function protocol.make_client_capabilities() dynamicRegistration = false; completionItem = { - -- TODO(tjdevries): Is it possible to implement this in plain lua? - snippetSupport = false; + snippetSupport = true; commitCharactersSupport = false; preselectSupport = false; deprecatedSupport = false; @@ -898,7 +897,8 @@ function protocol.resolve_capabilities(server_capabilities) text_document_will_save = ifnil(textDocumentSync.willSave, false); text_document_will_save_wait_until = ifnil(textDocumentSync.willSaveWaitUntil, false); text_document_save = ifnil(textDocumentSync.save, false); - text_document_save_include_text = ifnil(textDocumentSync.save and textDocumentSync.save.includeText, false); + text_document_save_include_text = ifnil(type(textDocumentSync.save) == 'table' + and textDocumentSync.save.includeText, false); } else return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync)) @@ -924,6 +924,28 @@ function protocol.resolve_capabilities(server_capabilities) error("The server sent invalid codeActionProvider") end + if server_capabilities.declarationProvider == nil then + general_properties.declaration = false + elseif type(server_capabilities.declarationProvider) == 'boolean' then + general_properties.declaration = server_capabilities.declarationProvider + elseif type(server_capabilities.declarationProvider) == 'table' then + -- TODO: support more detailed declarationProvider options. + general_properties.declaration = false + else + error("The server sent invalid declarationProvider") + end + + if server_capabilities.typeDefinitionProvider == nil then + general_properties.type_definition = false + elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then + general_properties.type_definition = server_capabilities.typeDefinitionProvider + elseif type(server_capabilities.typeDefinitionProvider) == 'table' then + -- TODO: support more detailed typeDefinitionProvider options. + general_properties.type_definition = false + else + error("The server sent invalid typeDefinitionProvider") + end + if server_capabilities.implementationProvider == nil then general_properties.implementation = false elseif type(server_capabilities.implementationProvider) == 'boolean' then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5c6d183ac1..4c3c4fa6cb 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -3,6 +3,7 @@ local vim = vim local validate = vim.validate local api = vim.api local list_extend = vim.list_extend +local highlight = require 'vim.highlight' local M = {} @@ -91,20 +92,26 @@ local function sort_by_key(fn) end end local edit_sort_key = sort_by_key(function(e) - return {e.A[1], e.A[2], e.i} + return {e.A[1], e.A[2], -e.i} end) -local function get_line_byte_from_line_character(bufnr, lnum, cnum) - -- Skip check when the byte and character position is the same - if cnum > 0 then - local lines = api.nvim_buf_get_lines(bufnr, lnum, lnum+1, false) - +--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position +-- Returns a zero-indexed column, since set_lines() does the conversion to +-- 1-indexed +local function get_line_byte_from_position(bufnr, position) + -- LSP's line and characters are 0-indexed + -- Vim's line and columns are 1-indexed + local col = position.character + -- When on the first character, we can ignore the difference between byte and + -- character + if col > 0 then + local line = position.line + local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false) if #lines > 0 then - return vim.str_byteindex(lines[1], cnum) + return vim.str_byteindex(lines[1], col) end end - - return cnum + return col end function M.apply_text_edits(text_edits, bufnr) @@ -117,15 +124,9 @@ function M.apply_text_edits(text_edits, bufnr) for i, e in ipairs(text_edits) do -- adjust start and end column for UTF-16 encoding of non-ASCII characters local start_row = e.range.start.line - local start_col = get_line_byte_from_line_character( - bufnr, - start_row, - e.range.start.character) + local start_col = get_line_byte_from_position(bufnr, e.range.start) local end_row = e.range["end"].line - local end_col = get_line_byte_from_line_character( - bufnr, - end_row, - e.range["end"].character) + local end_col = get_line_byte_from_position(bufnr, e.range['end']) start_line = math.min(e.range.start.line, start_line) finish_line = math.max(e.range["end"].line, finish_line) -- TODO(ashkan) sanity check ranges for overlap. @@ -199,6 +200,66 @@ function M.get_current_line_to_cursor() return line:sub(pos[2]+1) end +local function parse_snippet_rec(input, inner) + local res = "" + + local close, closeend = nil, nil + if inner then + close, closeend = input:find("}", 1, true) + while close ~= nil and input:sub(close-1,close-1) == "\\" do + close, closeend = input:find("}", closeend+1, true) + end + end + + local didx = input:find('$', 1, true) + if didx == nil and close == nil then + return input, "" + elseif close ~=nil and (didx == nil or close < didx) then + -- No inner placeholders + return input:sub(0, close-1), input:sub(closeend+1) + end + + res = res .. input:sub(0, didx-1) + input = input:sub(didx+1) + + local tabstop, tabstopend = input:find('^%d+') + local placeholder, placeholderend = input:find('^{%d+:') + local choice, choiceend = input:find('^{%d+|') + + if tabstop then + input = input:sub(tabstopend+1) + elseif choice then + input = input:sub(choiceend+1) + close, closeend = input:find("|}", 1, true) + + res = res .. input:sub(0, close-1) + input = input:sub(closeend+1) + elseif placeholder then + -- TODO: add support for variables + input = input:sub(placeholderend+1) + + -- placeholders and variables are recursive + while input ~= "" do + local r, tail = parse_snippet_rec(input, true) + r = r:gsub("\\}", "}") + + res = res .. r + input = tail + end + else + res = res .. "$" + end + + return res, input +end + +-- Parse completion entries, consuming snippet tokens +function M.parse_snippet(input) + local res, _ = parse_snippet_rec(input, false) + + return res +end + -- Sort by CompletionItem.sortText -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function sort_completion_items(items) @@ -213,14 +274,22 @@ end -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) if item.textEdit ~= nil and item.textEdit.newText ~= nil then - return item.textEdit.newText + if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then + return item.textEdit.newText + else + return M.parse_snippet(item.textEdit.newText) + end elseif item.insertText ~= nil then - return item.insertText + if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then + return item.insertText + else + return M.parse_snippet(item.insertText) + end end return item.label end --- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned. +-- Some language servers return complementary candidates whose prefixes do not match are also returned. -- So we exclude completion candidates whose prefix does not match. local function remove_unmatch_completion_items(items, prefix) return vim.tbl_filter(function(item) @@ -474,13 +543,33 @@ function M.jump_to_location(location) api.nvim_buf_set_option(0, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line - local col = range.start.character - local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] - col = vim.str_byteindex(line, col) + local col = get_line_byte_from_position(0, range.start) api.nvim_win_set_cursor(0, {row + 1, col}) return true end +--- Preview a location in a floating windows +--- +--- behavior depends on type of location: +--- - for Location, range is shown (e.g., function definition) +--- - for LocationLink, targetRange is shown (e.g., body of function definition) +--- +--@param location a single Location or LocationLink +--@return bufnr,winnr buffer and window number of floating window or nil +function M.preview_location(location) + -- location may be LocationLink or Location (more useful for the former) + local uri = location.targetUri or location.uri + if uri == nil then return end + local bufnr = vim.uri_to_bufnr(uri) + if not api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + 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) +end + local function find_window_by_var(name, value) for _, win in ipairs(api.nvim_list_wins()) do if npcall(api.nvim_win_get_var, win, name) == value then @@ -525,13 +614,53 @@ function M.focusable_preview(unique_name, fn) end) end --- Convert markdown into syntax highlighted regions by stripping the code --- blocks and converting them into highlighted code. --- This will by default insert a blank line separator after those code block --- regions to improve readability. +--- Trim empty lines from input and pad left and right with spaces +--- +--@param contents table of lines to trim and pad +--@param opts dictionary with optional fields +-- - pad_left amount of columns to pad contents at left (default 1) +-- - pad_right amount of columns to pad contents at right (default 1) +--@return contents table of trimmed and padded lines +function M._trim_and_pad(contents, opts) + validate { + contents = { contents, 't' }; + opts = { opts, 't', true }; + } + opts = opts or {} + local left_padding = (" "):rep(opts.pad_left or 1) + local right_padding = (" "):rep(opts.pad_right or 1) + contents = M.trim_empty_lines(contents) + for i, line in ipairs(contents) do + contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding) + end + return contents +end + + + +--- Convert markdown into syntax highlighted regions by stripping the code +--- blocks and converting them into highlighted code. +--- This will by default insert a blank line separator after those code block +--- regions to improve readability. +--- The result is shown in a floating preview +--- TODO: refactor to separate stripping/converting and make use of open_floating_preview +--- +--@param contents table of lines to show in window +--@param opts dictionary with optional fields +-- - height of floating window +-- - width of floating window +-- - wrap_at character to wrap at for computing height +-- - pad_left amount of columns to pad contents at left +-- - pad_right amount of columns to pad contents at right +-- - separator insert separator after code block +--@return width,height size of float function M.fancy_floating_markdown(contents, opts) - local pad_left = opts and opts.pad_left - local pad_right = opts and opts.pad_right + validate { + contents = { contents, 't' }; + opts = { opts, 't', true }; + } + opts = opts or {} + local stripped = {} local highlights = {} do @@ -565,31 +694,27 @@ function M.fancy_floating_markdown(contents, opts) end end end - local width = 0 - for i, v in ipairs(stripped) do - v = v:gsub("\r", "") - if pad_left then v = (" "):rep(pad_left)..v end - if pad_right then v = v..(" "):rep(pad_right) end - stripped[i] = v - width = math.max(width, #v) - end - if opts and opts.max_width then - width = math.min(opts.max_width, width) - end - -- TODO(ashkan): decide how to make this customizable. - local insert_separator = true + -- Clean up and add padding + stripped = M._trim_and_pad(stripped, opts) + + -- Compute size of float needed to show (wrapped) lines + opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) + local width, height = M._make_floating_popup_size(stripped, opts) + + -- Insert blank line separator after code block + local insert_separator = opts.separator or true if insert_separator then for i, h in ipairs(highlights) do h.start = h.start + i - 1 h.finish = h.finish + i - 1 if h.finish + 1 <= #stripped then table.insert(stripped, h.finish + 1, string.rep("─", width)) + height = height + 1 end end end -- Make the floating window. - local height = #stripped local bufnr = api.nvim_create_buf(false, true) local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts)) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped) @@ -601,7 +726,7 @@ function M.fancy_floating_markdown(contents, opts) vim.cmd("ownsyntax markdown") local idx = 1 - local function highlight_region(ft, start, finish) + local function apply_syntax_to_region(ft, start, finish) if ft == '' then return end local name = ft..idx idx = idx + 1 @@ -617,8 +742,8 @@ function M.fancy_floating_markdown(contents, opts) -- make sure that regions between code blocks are definitely markdown. -- local ph = {start = 0; finish = 1;} for _, h in ipairs(highlights) do - -- highlight_region('markdown', ph.finish, h.start) - highlight_region(h.ft, h.start, h.finish) + -- apply_syntax_to_region('markdown', ph.finish, h.start) + apply_syntax_to_region(h.ft, h.start, h.finish) -- ph = h end @@ -630,33 +755,81 @@ function M.close_preview_autocmd(events, winnr) api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") end -function M.open_floating_preview(contents, filetype, opts) +--- Compute size of float needed to show contents (with optional wrapping) +--- +--@param contents table of lines to show in window +--@param opts dictionary with optional fields +-- - height of floating window +-- - width of floating window +-- - wrap_at character to wrap at for computing height +--@return width,height size of float +function M._make_floating_popup_size(contents, opts) validate { contents = { contents, 't' }; - filetype = { filetype, 's', true }; opts = { opts, 't', true }; } opts = opts or {} - -- Trim empty lines from the end. - contents = M.trim_empty_lines(contents) - local width = opts.width - local height = opts.height or #contents + local height = opts.height + local line_widths = {} + if not width then width = 0 for i, line in ipairs(contents) do - -- Clean up the input and add left pad. - line = " "..line:gsub("\r", "") -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. - local line_width = vim.fn.strdisplaywidth(line) - width = math.max(line_width, width) - contents[i] = line + line_widths[i] = vim.fn.strdisplaywidth(line) + width = math.max(line_widths[i], width) end - -- Add right padding of 1 each. - width = width + 1 end + if not height then + height = #contents + local wrap_at = opts.wrap_at + if wrap_at and width > wrap_at then + height = 0 + if vim.tbl_isempty(line_widths) then + for _, line in ipairs(contents) do + local line_width = vim.fn.strdisplaywidth(line) + height = height + math.ceil(line_width/wrap_at) + end + else + for i = 1, #contents do + height = height + math.ceil(line_widths[i]/wrap_at) + end + end + end + end + + return width, height +end + +--- Show 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 opts dictionary with optional fields +-- - height of floating window +-- - width of floating window +-- - wrap_at character to wrap at for computing height +-- - pad_left amount of columns to pad contents at left +-- - pad_right amount of columns to pad contents at right +--@return bufnr,winnr buffer and window number of floating window or nil +function M.open_floating_preview(contents, filetype, opts) + validate { + contents = { contents, 't' }; + filetype = { filetype, 's', true }; + opts = { opts, 't', true }; + } + opts = opts or {} + + -- Clean up input: trim empty lines from the end, pad + contents = M._trim_and_pad(contents, opts) + + -- Compute size of float needed to show (wrapped) lines + opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) + 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) @@ -668,23 +841,10 @@ function M.open_floating_preview(contents, filetype, opts) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) - M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden"}, floating_winnr) + M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr) return floating_bufnr, floating_winnr end -local function highlight_range(bufnr, ns, hiname, start, finish) - if start[1] == finish[1] then - -- TODO care about encoding here since this is in byte index? - api.nvim_buf_add_highlight(bufnr, ns, hiname, start[1], start[2], finish[2]) - else - api.nvim_buf_add_highlight(bufnr, ns, hiname, start[1], start[2], -1) - for line = start[1] + 1, finish[1] - 1 do - api.nvim_buf_add_highlight(bufnr, ns, hiname, line, 0, -1) - end - api.nvim_buf_add_highlight(bufnr, ns, hiname, finish[1], 0, finish[2]) - end -end - do local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") local reference_ns = api.nvim_create_namespace("vim_lsp_references") @@ -818,8 +978,7 @@ do [protocol.DiagnosticSeverity.Hint]='Hint', } - -- TODO care about encoding here since this is in byte index? - highlight_range(bufnr, diagnostic_ns, + highlight.range(bufnr, diagnostic_ns, underline_highlight_name..hlmap[diagnostic.severity], {start.line, start.character}, {finish.line, finish.character} @@ -843,7 +1002,7 @@ do [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite"; } local kind = reference["kind"] or protocol.DocumentHighlightKind.Text - highlight_range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos) + highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos) end end diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 2135bfc837..384d22cb89 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -79,7 +79,7 @@ function vim.gsplit(s, sep, plain) end return function() - if done then + if done or (s == '' and sep == '') then return end if sep == '' then diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index d3b78a7f73..c502e45bd0 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -162,16 +162,17 @@ function Query:match_preds(match, pattern, bufnr) -- Here we only want to return if a predicate DOES NOT match, and -- continue on the other case. This way unknown predicates will not be considered, -- which allows some testing and easier user extensibility (#12173). + -- Also, tree-sitter strips the leading # from predicates for us. if pred[1] == "eq?" then local node = match[pred[2]] local node_text = get_node_text(node, bufnr) local str if type(pred[3]) == "string" then - -- (eq? @aa "foo") + -- (#eq? @aa "foo") str = pred[3] else - -- (eq? @aa @bb) + -- (#eq? @aa @bb) str = get_node_text(match[pred[3]], bufnr) end diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index e28cc9e20f..9c3535c676 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -65,9 +65,11 @@ local function uri_from_fname(path) return table.concat(uri_parts) end +local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*)://.*' + local function uri_from_bufnr(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) - local scheme = fname:match("^([a-z]+)://.*") + local scheme = fname:match(URI_SCHEME_PATTERN) if scheme then return fname else @@ -76,6 +78,10 @@ local function uri_from_bufnr(bufnr) end local function uri_to_fname(uri) + local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri) + if scheme ~= 'file' then + return uri + end uri = uri_decode(uri) -- TODO improve this. if is_windows_file_uri(uri) then @@ -89,7 +95,7 @@ end -- Return or create a buffer for a uri. local function uri_to_bufnr(uri) - local scheme = assert(uri:match("^([a-z]+)://.*"), 'Uri must contain a scheme: ' .. uri) + local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri) if scheme == 'file' then return vim.fn.bufadd(uri_to_fname(uri)) else diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index aa2b69ad97..28dc3256c7 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -645,7 +645,7 @@ func s:InstallCommands() command Gdb call win_gotoid(s:gdbwin) command Program call win_gotoid(s:ptywin) command Source call s:GotoSourcewinOrCreateIt() - " command Winbar call s:InstallWinbar() + command Winbar call s:InstallWinbar() " TODO: can the K mapping be restored? nnoremap K :Evaluate<CR> @@ -655,6 +655,19 @@ endfunc " let s:winbar_winids = [] +" Install the window toolbar in the current window. +func s:InstallWinbar() + " if has('menu') && &mouse != '' + " nnoremenu WinBar.Step :Step<CR> + " nnoremenu WinBar.Next :Over<CR> + " nnoremenu WinBar.Finish :Finish<CR> + " nnoremenu WinBar.Cont :Continue<CR> + " nnoremenu WinBar.Stop :Stop<CR> + " nnoremenu WinBar.Eval :Evaluate<CR> + " call add(s:winbar_winids, win_getid(winnr())) + " endif +endfunc + " Delete installed debugger commands in the current window. func s:DeleteCommands() delcommand Break @@ -670,7 +683,7 @@ func s:DeleteCommands() delcommand Gdb delcommand Program delcommand Source - " delcommand Winbar + delcommand Winbar nunmap K @@ -940,7 +953,7 @@ func s:GotoSourcewinOrCreateIt() if !win_gotoid(s:sourcewin) new let s:sourcewin = win_getid(winnr()) - " call s:InstallWinbar() + call s:InstallWinbar() endif endfunc @@ -971,7 +984,7 @@ func s:HandleCursor(msg) " TODO: find existing window exe 'split ' . fnameescape(fname) let s:sourcewin = win_getid(winnr()) - " call s:InstallWinbar() + call s:InstallWinbar() else exe 'edit ' . fnameescape(fname) endif |