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. Nvim provides an LSP client, but the servers are provided by third parties.
Follow these steps to get LSP features: Follow these steps to get LSP features:
1. Install the nvim-lspconfig plugin. It provides common configuration for 1. Install language servers using your package manager or by
various servers so you can get started quickly. following the upstream installation instruction.
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: >
: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* *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 Starting a LSP client will automatically report diagnostics via
still possible to get completions from the LSP server. To incorporate these |vim.diagnostic|. Read |vim.diagnostic.config| to learn how to customize the
completions, it is recommended to use |vim.lsp.omnifunc|, which is an 'omnifunc' display.
handler. When 'omnifunc' is set to `v:lua.vim.lsp.omnifunc`, |i_CTRL-X_CTRL-O|
will provide completions from the language server.
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 To get features like go-to-definition you can enable the |vim.lsp.tagfunc|
local custom_lsp_attach = function(client) which changes commands like |:tjump| to utilize the language server and also
-- See `:help nvim_buf_set_keymap()` for more information enables keymaps like |CTLR-]|, |CTRL-W_]|, |CTRL-W_}| and many more.
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
-- Use LSP as the handler for omnifunc. To use other LSP features like hover, rename, etc. you can setup some
-- See `:help omnifunc` and `:help ins-completion` for more information. additional keymaps. It's recommended to setup them in a |LspAttach| autocmd to
vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.vim.lsp.omnifunc') ensure they're only active if there is a LSP client running. An example:
>
-- Use LSP as the handler for formatexpr. vim.api.nvim_create_autocmd('LspAttach', {
-- See `:help formatexpr` for more information. callback = function(args)
vim.api.nvim_buf_set_option(0, 'formatexpr', 'v:lua.vim.lsp.formatexpr()') vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
end,
-- 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
}) })
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|. 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: ~ See also: ~
|vim.lsp.log_levels| |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()* start_client({config}) *vim.lsp.start_client()*
Starts and initializes a client with the given configuration. Starts and initializes a client with the given configuration.

View File

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

View File

@ -4,6 +4,7 @@ local lsp_rpc = require('vim.lsp.rpc')
local protocol = require('vim.lsp.protocol') local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util') local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync') local sync = require('vim.lsp.sync')
local api = vim.api
local vim = vim local vim = vim
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds = 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() error()
end 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 -- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g. -- documented twice: Here, and on the methods themselves (e.g.
-- `client.request()`). This is a workaround for the vimdoc generator script -- `client.request()`). This is a workaround for the vimdoc generator script