feat(lsp): add more LSP defaults (#28500)

- crn for rename
- crr for code actions
- gr for references
- <C-S> (in Insert mode) for signature help
This commit is contained in:
Gregory Anders 2024-04-26 11:12:49 -05:00 committed by GitHub
parent 9b028bd64f
commit 6888607415
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 274 additions and 239 deletions

View File

@ -281,6 +281,10 @@ gr{char} Replace the virtual characters under the cursor with
that have a special meaning in Insert mode, such as that have a special meaning in Insert mode, such as
most CTRL-keys, cannot be used. most CTRL-keys, cannot be used.
*gr-default*
Mapped to |vim.lsp.buf.references()| by default.
|default-mappings|
*digraph-arg* *digraph-arg*
The argument for Normal mode commands like |r| and |t| is a single character. The argument for Normal mode commands like |r| and |t| is a single character.
When 'cpo' doesn't contain the 'D' flag, this character can also be entered When 'cpo' doesn't contain the 'D' flag, this character can also be entered

View File

@ -61,47 +61,41 @@ options are not restored when the LSP client is stopped or detached.
- |K| is mapped to |vim.lsp.buf.hover()| unless |'keywordprg'| is customized or - |K| is mapped to |vim.lsp.buf.hover()| unless |'keywordprg'| is customized or
a custom keymap for `K` exists. a custom keymap for `K` exists.
*crr* *v_crr* *crn* *i_CTRL-S*
Some keymaps are created unconditionally when Nvim starts:
- "crn" is mapped in Normal mode to |vim.lsp.buf.rename()|
- "crr" is mapped in Normal and Visual mode to |vim.lsp.buf.code_action()|
- "gr" is mapped in Normal mode to |vim.lsp.buf.references()| |gr-default|
- CTRL-S is mapped in Insert mode to |vim.lsp.buf.signature_help()|
If not wanted, these keymaps can be removed at any time using
|vim.keymap.del()| or |:unmap|.
*lsp-defaults-disable* *lsp-defaults-disable*
To override the above defaults, set or unset the options on |LspAttach|: >lua To override the above defaults, set or unset the options on |LspAttach|: >lua
vim.api.nvim_create_autocmd('LspAttach', { vim.api.nvim_create_autocmd('LspAttach', {
callback = function(ev) callback = function(ev)
vim.bo[ev.buf].formatexpr = nil vim.bo[ev.buf].formatexpr = nil
vim.bo[ev.buf].omnifunc = nil vim.bo[ev.buf].omnifunc = nil
vim.keymap.del("n", "K", { buffer = ev.buf }) vim.keymap.del('n', 'K', { buffer = ev.buf })
end, end,
}) })
To use other LSP features like hover, rename, etc. you can set other keymaps To use other LSP features, set keymaps on |LspAttach|. Not all language
on |LspAttach|. Example: >lua servers provide the same capabilities. To ensure you only set keymaps if the
vim.api.nvim_create_autocmd('LspAttach', { language server supports a feature, guard keymaps behind capability checks.
callback = function(args) Example: >lua
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
end,
})
The most common functions are:
- |vim.lsp.buf.hover()|
- |vim.lsp.buf.format()|
- |vim.lsp.buf.references()|
- |vim.lsp.buf.implementation()|
- |vim.lsp.buf.code_action()|
Not all language servers provide the same capabilities. To ensure you only set
keymaps if the language server supports a feature, you can guard the keymap
calls behind capability checks:
>lua
vim.api.nvim_create_autocmd('LspAttach', { vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args) callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id) local client = vim.lsp.get_client_by_id(args.data.client_id)
if client.server_capabilities.hoverProvider then if client.supports_method('textDocument/implementation') then
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf }) vim.keymap.set('n', 'g<C-I>', vim.lsp.buf.implementation, { buffer = args.buf })
end end
end, end,
}) })
< <
To learn what capabilities are available you can run the following command in To learn what capabilities are available you can run the following command in
a buffer with a started LSP client: >vim a buffer with a started LSP client: >vim

View File

@ -408,6 +408,10 @@ The following changes to existing APIs or features add new behavior.
• 'comments' includes "fb:•". • 'comments' includes "fb:•".
• 'shortmess' includes the "C" flag. • 'shortmess' includes the "C" flag.
• 'grepprg' defaults to using ripgrep if available. • 'grepprg' defaults to using ripgrep if available.
• |crn| in Normal mode maps to |vim.lsp.buf.rename()|.
• |crr| in Normal and Visual mode maps to |vim.lsp.buf.code_action()|.
• "gr" in Normal mode maps to |vim.lsp.buf.references()| |gr-default|
• |i_CTRL-S| in Insert mode maps to |vim.lsp.buf.signature_help()|
• Automatic linting of treesitter query files (see |ft-query-plugin|). • Automatic linting of treesitter query files (see |ft-query-plugin|).
Can be disabled via: >lua Can be disabled via: >lua
vim.g.query_lint_on = {} vim.g.query_lint_on = {}
@ -438,9 +442,10 @@ The following changes to existing APIs or features add new behavior.
:call netrw#BrowseX(expand(exists("g:netrw_gx")? g:netrw_gx : '<cfile>'), netrw#CheckIfRemote())<CR> :call netrw#BrowseX(expand(exists("g:netrw_gx")? g:netrw_gx : '<cfile>'), netrw#CheckIfRemote())<CR>
• |vim.lsp.start()| now maps |K| to use |vim.lsp.buf.hover()| if the server • |vim.lsp.start()| now creates the following default keymaps (assuming the
supports it, unless |'keywordprg'| was customized before calling server supports the feature):
|vim.lsp.start()|. - |K| in Normal mode maps to |vim.lsp.buf.hover()|, unless |'keywordprg'|
was customized before calling |vim.lsp.start()|.
• Terminal buffers started with no arguments (and use 'shell') close • Terminal buffers started with no arguments (and use 'shell') close
automatically if the job exited without error, eliminating the (often automatically if the job exited without error, eliminating the (often

View File

@ -137,6 +137,10 @@ of these in your config by simply removing the mapping, e.g. ":unmap Y".
- * |v_star-default| - * |v_star-default|
- gc |gc-default| |v_gc-default| |o_gc-default| - gc |gc-default| |v_gc-default| |o_gc-default|
- gcc |gcc-default| - gcc |gcc-default|
- |crn|
- |crr|
- gr |gr-default|
- <C-S> |i_CTRL-S|
- Nvim LSP client defaults |lsp-defaults| - Nvim LSP client defaults |lsp-defaults|
- K |K-lsp-default| - K |K-lsp-default|

View File

@ -144,6 +144,31 @@ do
end end
vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' }) vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' })
end end
--- Default maps for LSP functions.
---
--- These are mapped unconditionally to avoid confusion. If no server is attached, or if a server
--- does not support a capability, an error message is displayed rather than exhibiting different
--- behavior.
---
--- See |gr-default|, |crn|, |crr|, |i_CTRL-S|.
do
vim.keymap.set('n', 'crn', function()
vim.lsp.buf.rename()
end, { desc = 'vim.lsp.buf.rename()' })
vim.keymap.set({ 'n', 'v' }, 'crr', function()
vim.lsp.buf.code_action()
end, { desc = 'vim.lsp.buf.code_action()' })
vim.keymap.set('n', 'gr', function()
vim.lsp.buf.references()
end, { desc = 'vim.lsp.buf.references()' })
vim.keymap.set('i', '<C-S>', function()
vim.lsp.buf.signature_help()
end, { desc = 'vim.lsp.buf.signature_help()' })
end
end end
--- Default menus --- Default menus
@ -243,230 +268,140 @@ do
vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid)) vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid))
end, end,
}) })
end
-- Only do the following when the TUI is attached -- Only do the following when the TUI is attached
local tty = nil local tty = nil
for _, ui in ipairs(vim.api.nvim_list_uis()) do for _, ui in ipairs(vim.api.nvim_list_uis()) do
if ui.chan == 1 and ui.stdout_tty then if ui.chan == 1 and ui.stdout_tty then
tty = ui tty = ui
break break
end
end
if tty then
local group = vim.api.nvim_create_augroup('nvim_tty', {})
--- Set an option after startup (so that OptionSet is fired), but only if not
--- already set by the user.
---
--- @param option string Option name
--- @param value any Option value
local function setoption(option, value)
if vim.api.nvim_get_option_info2(option, {}).was_set then
-- Don't do anything if option is already set
return
end
-- Wait until Nvim is finished starting to set the option to ensure the
-- OptionSet event fires.
if vim.v.vim_did_enter == 1 then
--- @diagnostic disable-next-line:no-unknown
vim.o[option] = value
else
vim.api.nvim_create_autocmd('VimEnter', {
group = group,
once = true,
nested = true,
callback = function()
setoption(option, value)
end,
})
end end
end end
--- Guess value of 'background' based on terminal color. if tty then
--- local group = vim.api.nvim_create_augroup('nvim_tty', {})
--- We write Operating System Command (OSC) 11 to the terminal to request the
--- terminal's background color. We then wait for a response. If the response --- Set an option after startup (so that OptionSet is fired), but only if not
--- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then --- already set by the user.
--- compute the luminance[1] of the RGB color and classify it as light/dark
--- accordingly. Note that the color components may have anywhere from one to
--- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
--- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
--- ignored in the calculations.
---
--- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
do
--- Parse a string of hex characters as a color.
--- ---
--- The string can contain 1 to 4 hex characters. The returned value is --- @param option string Option name
--- between 0.0 and 1.0 (inclusive) representing the intensity of the color. --- @param value any Option value
--- local function setoption(option, value)
--- For instance, if only a single hex char "a" is used, then this function if vim.api.nvim_get_option_info2(option, {}).was_set then
--- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / -- Don't do anything if option is already set
--- 256). return
---
--- @param c string Color as a string of hex chars
--- @return number? Intensity of the color
local function parsecolor(c)
if #c == 0 or #c > 4 then
return nil
end end
local val = tonumber(c, 16) -- Wait until Nvim is finished starting to set the option to ensure the
if not val then -- OptionSet event fires.
return nil if vim.v.vim_did_enter == 1 then
--- @diagnostic disable-next-line:no-unknown
vim.o[option] = value
else
vim.api.nvim_create_autocmd('VimEnter', {
group = group,
once = true,
nested = true,
callback = function()
setoption(option, value)
end,
})
end end
local max = tonumber(string.rep('f', #c), 16)
return val / max
end end
--- Parse an OSC 11 response --- Guess value of 'background' based on terminal color.
--- ---
--- Either of the two formats below are accepted: --- We write Operating System Command (OSC) 11 to the terminal to request the
--- terminal's background color. We then wait for a response. If the response
--- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
--- compute the luminance[1] of the RGB color and classify it as light/dark
--- accordingly. Note that the color components may have anywhere from one to
--- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
--- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
--- ignored in the calculations.
--- ---
--- OSC 11 ; rgb:<red>/<green>/<blue> --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
--- do
--- or --- Parse a string of hex characters as a color.
--- ---
--- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha> --- The string can contain 1 to 4 hex characters. The returned value is
--- --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
--- where ---
--- --- For instance, if only a single hex char "a" is used, then this function
--- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
--- --- 256).
--- The alpha component is ignored, if present. ---
--- --- @param c string Color as a string of hex chars
--- @param resp string OSC 11 response --- @return number? Intensity of the color
--- @return string? Red component local function parsecolor(c)
--- @return string? Green component if #c == 0 or #c > 4 then
--- @return string? Blue component return nil
local function parseosc11(resp)
local r, g, b
r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
if not r and not g and not b then
local a
r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
if not a or #a > 4 then
return nil, nil, nil
end end
local val = tonumber(c, 16)
if not val then
return nil
end
local max = tonumber(string.rep('f', #c), 16)
return val / max
end end
if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then --- Parse an OSC 11 response
return r, g, b ---
end --- Either of the two formats below are accepted:
---
return nil, nil, nil --- OSC 11 ; rgb:<red>/<green>/<blue>
end ---
--- or
local timer = assert(vim.uv.new_timer()) ---
--- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
local id = vim.api.nvim_create_autocmd('TermResponse', { ---
group = group, --- where
nested = true, ---
callback = function(args) --- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
local resp = args.data ---@type string ---
local r, g, b = parseosc11(resp) --- The alpha component is ignored, if present.
if r and g and b then ---
local rr = parsecolor(r) --- @param resp string OSC 11 response
local gg = parsecolor(g) --- @return string? Red component
local bb = parsecolor(b) --- @return string? Green component
--- @return string? Blue component
if rr and gg and bb then local function parseosc11(resp)
local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) local r, g, b
local bg = luminance < 0.5 and 'dark' or 'light' r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
setoption('background', bg) if not r and not g and not b then
local a
r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
if not a or #a > 4 then
return nil, nil, nil
end end
return true
end end
end,
})
io.stdout:write('\027]11;?\007') if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
return r, g, b
end
timer:start(1000, 0, function() return nil, nil, nil
-- Delete the autocommand if no response was received
vim.schedule(function()
-- Suppress error if autocommand has already been deleted
pcall(vim.api.nvim_del_autocmd, id)
end)
if not timer:is_closing() then
timer:close()
end end
end)
end
--- If the TUI (term_has_truecolor) was able to determine that the host
--- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the
--- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's
--- response indicates that it does support truecolor enable 'termguicolors',
--- but only if the user has not already disabled it.
do
if tty.rgb then
-- The TUI was able to determine truecolor support
setoption('termguicolors', true)
else
local caps = {} ---@type table<string, boolean>
require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found)
if not found then
return
end
caps[cap] = true
if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then
setoption('termguicolors', true)
end
end)
local timer = assert(vim.uv.new_timer()) local timer = assert(vim.uv.new_timer())
-- Arbitrary colors to set in the SGR sequence
local r = 1
local g = 2
local b = 3
local id = vim.api.nvim_create_autocmd('TermResponse', { local id = vim.api.nvim_create_autocmd('TermResponse', {
group = group, group = group,
nested = true, nested = true,
callback = function(args) callback = function(args)
local resp = args.data ---@type string local resp = args.data ---@type string
local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') local r, g, b = parseosc11(resp)
if r and g and b then
local rr = parsecolor(r)
local gg = parsecolor(g)
local bb = parsecolor(b)
if decrqss then if rr and gg and bb then
-- The DECRQSS SGR response first contains attributes separated by local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
-- semicolons, followed by the SGR itself with parameters separated local bg = luminance < 0.5 and 'dark' or 'light'
-- by colons. Some terminals include "0" in the attribute list setoption('background', bg)
-- unconditionally; others do not. Our SGR sequence did not set any
-- attributes, so there should be no attributes in the list.
local attrs = vim.split(decrqss, ';')
if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then
return false
end
-- The returned SGR sequence should begin with 48:2
local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$')
if not sgr then
return false
end
-- The remaining elements of the SGR sequence should be the 3 colors
-- we set. Some terminals also include an additional parameter
-- (which can even be empty!), so handle those cases as well
local params = vim.split(sgr, ':')
if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then
return true
end
if
tonumber(params[#params - 2]) == r
and tonumber(params[#params - 1]) == g
and tonumber(params[#params]) == b
then
setoption('termguicolors', true)
end end
return true return true
@ -474,16 +409,7 @@ if tty then
end, end,
}) })
-- Write SGR followed by DECRQSS. This sets the background color then io.stdout:write('\027]11;?\007')
-- immediately asks the terminal what the background color is. If the
-- terminal responds to the DECRQSS with the same SGR sequence that we
-- sent then the terminal supports truecolor.
local decrqss = '\027P$qm\027\\'
if os.getenv('TMUX') then
decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027'))
end
-- Reset attributes first, as other code may have set attributes.
io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss))
timer:start(1000, 0, function() timer:start(1000, 0, function()
-- Delete the autocommand if no response was received -- Delete the autocommand if no response was received
@ -497,13 +423,115 @@ if tty then
end end
end) end)
end end
--- If the TUI (term_has_truecolor) was able to determine that the host
--- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the
--- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's
--- response indicates that it does support truecolor enable 'termguicolors',
--- but only if the user has not already disabled it.
do
if tty.rgb then
-- The TUI was able to determine truecolor support
setoption('termguicolors', true)
else
local caps = {} ---@type table<string, boolean>
require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found)
if not found then
return
end
caps[cap] = true
if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then
setoption('termguicolors', true)
end
end)
local timer = assert(vim.uv.new_timer())
-- Arbitrary colors to set in the SGR sequence
local r = 1
local g = 2
local b = 3
local id = vim.api.nvim_create_autocmd('TermResponse', {
group = group,
nested = true,
callback = function(args)
local resp = args.data ---@type string
local decrqss = resp:match('^\027P1%$r([%d;:]+)m$')
if decrqss then
-- The DECRQSS SGR response first contains attributes separated by
-- semicolons, followed by the SGR itself with parameters separated
-- by colons. Some terminals include "0" in the attribute list
-- unconditionally; others do not. Our SGR sequence did not set any
-- attributes, so there should be no attributes in the list.
local attrs = vim.split(decrqss, ';')
if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then
return false
end
-- The returned SGR sequence should begin with 48:2
local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$')
if not sgr then
return false
end
-- The remaining elements of the SGR sequence should be the 3 colors
-- we set. Some terminals also include an additional parameter
-- (which can even be empty!), so handle those cases as well
local params = vim.split(sgr, ':')
if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then
return true
end
if
tonumber(params[#params - 2]) == r
and tonumber(params[#params - 1]) == g
and tonumber(params[#params]) == b
then
setoption('termguicolors', true)
end
return true
end
end,
})
-- Write SGR followed by DECRQSS. This sets the background color then
-- immediately asks the terminal what the background color is. If the
-- terminal responds to the DECRQSS with the same SGR sequence that we
-- sent then the terminal supports truecolor.
local decrqss = '\027P$qm\027\\'
if os.getenv('TMUX') then
decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027'))
end
-- Reset attributes first, as other code may have set attributes.
io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss))
timer:start(1000, 0, function()
-- Delete the autocommand if no response was received
vim.schedule(function()
-- Suppress error if autocommand has already been deleted
pcall(vim.api.nvim_del_autocmd, id)
end)
if not timer:is_closing() then
timer:close()
end
end)
end
end
end end
end end
--- Default 'grepprg' to ripgrep if available. --- Default options
if vim.fn.executable('rg') == 1 then do
-- Match :grep default, otherwise rg searches cwd by default --- Default 'grepprg' to ripgrep if available.
-- Use -uuu to make ripgrep not do its default filtering if vim.fn.executable('rg') == 1 then
vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul') -- Match :grep default, otherwise rg searches cwd by default
vim.o.grepformat = '%f:%l:%c:%m' -- Use -uuu to make ripgrep not do its default filtering
vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul')
vim.o.grepformat = '%f:%l:%c:%m'
end
end end

View File

@ -348,7 +348,7 @@ function lsp._set_defaults(client, bufnr)
and is_empty_or_default(bufnr, 'keywordprg') and is_empty_or_default(bufnr, 'keywordprg')
and vim.fn.maparg('K', 'n', false, false) == '' and vim.fn.maparg('K', 'n', false, false) == ''
then then
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr }) vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr, desc = 'vim.lsp.buf.hover()' })
end end
end) end)
if client.supports_method(ms.textDocument_diagnostic) then if client.supports_method(ms.textDocument_diagnostic) then