feat(lsp): add a start function (#18631)

A alternative/subset of https://github.com/neovim/neovim/pull/18506  that should be forward compatible with a potential project system.

Configuration of LSP clients (without lspconfig) now looks like this:

    vim.lsp.start({
       name = 'my-server-name',
       cmd = {'name-of-language-server-executable'},
       root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]),
    })
This commit is contained in:
Mathias Fußenegger 2022-06-03 14:59:19 +02:00 committed by GitHub
parent 61e33f312e
commit 69774e3179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 203 additions and 74 deletions

View File

@ -24,88 +24,82 @@ QUICKSTART *lsp-quickstart*
Nvim provides an LSP client, but the servers are provided by third parties.
Follow these steps to get LSP features:
1. Install the nvim-lspconfig plugin. It provides common configuration for
various servers so you can get started quickly.
https://github.com/neovim/nvim-lspconfig
2. Install a language server. A list of language servers can be found here:
https://microsoft.github.io/language-server-protocol/implementors/servers/
See individual server documentation for installation instructions.
3. Add `lua require('lspconfig').xx.setup{…}` to your init.vim, where "xx" is
the name of the relevant config. See the nvim-lspconfig README for details.
NOTE: Make sure to restart nvim after installing and configuring.
4. Check that an LSP client has attached to the current buffer: >
1. Install language servers using your package manager or by
following the upstream installation instruction.
:lua print(vim.inspect(vim.lsp.buf_get_clients()))
A list of language servers is available at:
https://microsoft.github.io/language-server-protocol/implementors/servers/
2. Configure the LSP client per language server.
A minimal example:
>
vim.lsp.start({
name = 'my-server-name',
cmd = {'name-of-language-server-executable'},
root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]),
})
<
See |vim.lsp.start| for details.
3. Configure keymaps and autocmds to utilize LSP features.
See |lsp-config|.
<
*lsp-config*
Inline diagnostics are enabled automatically, e.g. syntax errors will be
annotated in the buffer. But you probably also want to use other features
like go-to-definition, hover, etc.
While Nvim does not provide an "auto-completion" framework by default, it is
still possible to get completions from the LSP server. To incorporate these
completions, it is recommended to use |vim.lsp.omnifunc|, which is an 'omnifunc'
handler. When 'omnifunc' is set to `v:lua.vim.lsp.omnifunc`, |i_CTRL-X_CTRL-O|
will provide completions from the language server.
Starting a LSP client will automatically report diagnostics via
|vim.diagnostic|. Read |vim.diagnostic.config| to learn how to customize the
display.
Example config (in init.vim): >
To get completion from the LSP server you can enable the |vim.lsp.omnifunc|:
>
vim.bo.omnifunc = 'v:lua.vim.lsp.omnifunc'
<
To trigger completion, use |i_CTRL-X_CTRL-O|
lua << EOF
local custom_lsp_attach = function(client)
-- See `:help nvim_buf_set_keymap()` for more information
vim.api.nvim_buf_set_keymap(0, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', {noremap = true})
vim.api.nvim_buf_set_keymap(0, 'n', '<c-]>', '<cmd>lua vim.lsp.buf.definition()<CR>', {noremap = true})
-- ... and other keymappings for LSP
To get features like go-to-definition you can enable the |vim.lsp.tagfunc|
which changes commands like |:tjump| to utilize the language server and also
enables keymaps like |CTLR-]|, |CTRL-W_]|, |CTRL-W_}| and many more.
-- Use LSP as the handler for omnifunc.
-- See `:help omnifunc` and `:help ins-completion` for more information.
vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
-- Use LSP as the handler for formatexpr.
-- See `:help formatexpr` for more information.
vim.api.nvim_buf_set_option(0, 'formatexpr', 'v:lua.vim.lsp.formatexpr()')
-- For plugins with an `on_attach` callback, call them here. For example:
-- require('completion').on_attach()
end
-- An example of configuring for `sumneko_lua`,
-- a language server for Lua.
-- set the path to the sumneko installation
local system_name = "Linux" -- (Linux, macOS, or Windows)
local sumneko_root_path = '/path/to/lua-language-server'
local sumneko_binary = sumneko_root_path.."/bin/"..system_name.."/lua-language-server"
require('lspconfig').sumneko_lua.setup({
cmd = {sumneko_binary, "-E", sumneko_root_path .. "/main.lua"};
-- An example of settings for an LSP server.
-- For more options, see nvim-lspconfig
settings = {
Lua = {
runtime = {
-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
version = 'LuaJIT',
-- Setup your lua path
path = vim.split(package.path, ';'),
},
diagnostics = {
-- Get the language server to recognize the `vim` global
globals = {'vim'},
},
workspace = {
-- Make the server aware of Neovim runtime files
library = {
[vim.fn.expand('$VIMRUNTIME/lua')] = true,
[vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true,
},
},
}
},
on_attach = custom_lsp_attach
To use other LSP features like hover, rename, etc. you can setup some
additional keymaps. It's recommended to setup them in a |LspAttach| autocmd to
ensure they're only active if there is a LSP client running. An example:
>
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
end,
})
EOF
<
The most used 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:
>
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client.server_capabilities.hoverProvider then
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
end
end,
})
<
To learn what capabilities are available you can run the following command in
a buffer with a started LSP client:
>
:lua =vim.lsp.get_active_clients()[1].server_capabilties
<
Full list of features provided by default can be found in |lsp-buf|.
@ -800,6 +794,66 @@ set_log_level({level}) *vim.lsp.set_log_level()*
See also: ~
|vim.lsp.log_levels|
start({config}, {opts}) *vim.lsp.start()*
Create a new LSP client and start a language server or reuses
an already running client if one is found matching `name` and
`root_dir`. Attaches the current buffer to the client.
Example:
>
vim.lsp.start({
name = 'my-server-name',
cmd = {'name-of-language-server-executable'},
root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
})
<
See |lsp.start_client| for all available options. The most
important are:
`name` is an arbitrary name for the LSP client. It should be
unique per language server.
`cmd` the command as list - used to start the language server. The
command must be present in the `$PATH` environment variable or an absolute path to the executable.
Shell constructs like `~` are NOT expanded.
`root_dir` path to the project root. By default this is used
to decide if an existing client should be re-used. The example
above uses |vim.fs.find| and |vim.fs.dirname| to detect the
root by traversing the file system upwards starting from the
current directory until either a `pyproject.toml` or
`setup.py` file is found.
`workspace_folders` a list of { uri:string, name: string }
tables. The project root folders used by the language server.
If `nil` the property is derived from the `root_dir` for
convenience.
Language servers use this information to discover metadata
like the dependencies of your project and they tend to index
the contents within the project folder.
To ensure a language server is only started for languages it
can handle, make sure to call |vim.lsp.start| within a
|FileType| autocmd. Either use |:au|, |nvim_create_autocmd()|
or put the call in a `ftplugin/<filetype_name>.lua` (See
|ftplugin-name|)
Parameters: ~
{config} (table) Same configuration as documented in
|lsp.start_client()|
{opts} nil|table Optional keyword arguments:
• reuse_client (fun(client: client, config:
table): boolean) Predicate used to decide if a
client should be re-used. Used on all running
clients. The default implementation re-uses a
client if name and root_dir matches.
Return: ~
(number) client_id
start_client({config}) *vim.lsp.start_client()*
Starts and initializes a client with the given configuration.

View File

@ -37,6 +37,9 @@ end
---@param file (string) File or directory
---@return (string) Parent directory of {file}
function M.dirname(file)
if file == nil then
return nil
end
return vim.fn.fnamemodify(file, ':h')
end

View File

@ -4,6 +4,7 @@ local lsp_rpc = require('vim.lsp.rpc')
local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync')
local api = vim.api
local vim = vim
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
@ -662,6 +663,77 @@ function lsp.client()
error()
end
--- Create a new LSP client and start a language server or reuses an already
--- running client if one is found matching `name` and `root_dir`.
--- Attaches the current buffer to the client.
---
--- Example:
---
--- <pre>
--- vim.lsp.start({
--- name = 'my-server-name',
--- cmd = {'name-of-language-server-executable'},
--- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
--- })
--- </pre>
---
--- See |lsp.start_client| for all available options. The most important are:
---
--- `name` is an arbitrary name for the LSP client. It should be unique per
--- language server.
---
--- `cmd` the command as list - used to start the language server.
--- The command must be present in the `$PATH` environment variable or an
--- absolute path to the executable. Shell constructs like `~` are *NOT* expanded.
---
--- `root_dir` path to the project root.
--- By default this is used to decide if an existing client should be re-used.
--- The example above uses |vim.fs.find| and |vim.fs.dirname| to detect the
--- root by traversing the file system upwards starting
--- from the current directory until either a `pyproject.toml` or `setup.py`
--- file is found.
---
--- `workspace_folders` a list of { uri:string, name: string } tables.
--- The project root folders used by the language server.
--- If `nil` the property is derived from the `root_dir` for convenience.
---
--- Language servers use this information to discover metadata like the
--- dependencies of your project and they tend to index the contents within the
--- project folder.
---
---
--- To ensure a language server is only started for languages it can handle,
--- make sure to call |vim.lsp.start| within a |FileType| autocmd.
--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
---@param config table Same configuration as documented in |lsp.start_client()|
---@param opts nil|table Optional keyword arguments:
--- - reuse_client (fun(client: client, config: table): boolean)
--- Predicate used to decide if a client should be re-used.
--- Used on all running clients.
--- The default implementation re-uses a client if name
--- and root_dir matches.
---@return number client_id
function lsp.start(config, opts)
opts = opts or {}
local reuse_client = opts.reuse_client
or function(client, conf)
return client.config.root_dir == conf.root_dir and client.name == conf.name
end
config.name = config.name or (config.cmd[1] and vim.fs.basename(config.cmd[1])) or nil
local bufnr = api.nvim_get_current_buf()
for _, client in pairs(lsp.get_active_clients()) do
if reuse_client(client, config) then
lsp.buf_attach_client(bufnr, client.id)
return client.id
end
end
local client_id = lsp.start_client(config)
lsp.buf_attach_client(bufnr, client_id)
return client_id
end
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g.
-- `client.request()`). This is a workaround for the vimdoc generator script