New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(lsp): add vim.lsp.server to create in-process server #24338
base: master
Are you sure you want to change the base?
Conversation
|
We could reduce the boilerplate even more. For example: local server = vim.lsp.server({
---@param params lsp.HoverParams
["on_textDocument/hover"] = function(_, params)
local bufnr = vim.uri_to_bufnr(params.textDocument.uri)
local lnum = params.position.line
local col = params.position.character
-- use bufnr/lnum/col to provide actual text
return {
contents = {
kind = "plaintext",
value = "just some dummy text",
}
}
end,
})
Maybe even with a |
|
Wonderful! I think we definitely want this, can you confirm the following:
|
Yes
Yes. null-ls used the same mechanism. (At least since Neovim 0.8 made it possible, before it hacked into internals))
It goes into that direction in the sense that it lets plugins provide that kind of functionality, yes - but also with a key difference. That makes a lot of sense for vscode since they already share the common structures, but for Neovim we have different core primitives. Following the vscode model closely would mean creating |
|
|
@mfussenegger I'm author of https://github.com/pmizio/typescript-tools.nvim and this is s great addition to have which can really simplify process of spawning this tsserver mess. Tsserver is lsp like but not compatible on "message structure" level. I see how this addition can simplify my code, but to use this I would need few extensions to this:
I know this pr is meant to be for simpler use cases but this three changes can make it more universal even for more complicated projects. Of course I give only my insight from building off-spec in process server plugin and it is ok if this is out of scope of this pr. But anyway I want to put this on the table. |
|
I'm pretty confident we need to pass a callback to the handlers to better support async applications as opposed to them returning data. local api = vim.api
local server = vim.lsp.server({
capabilities = {
hoverProvider = true
},
handlers = {
---@param params lsp.HoverParams
["textDocument/hover"] = function(_, params, callback)
local bufnr = vim.uri_to_bufnr(params.textDocument.uri)
local lnum = params.position.line
local col = params.position.character
-- use bufnr/lnum/col to provide actual text
callback({
contents = {
kind = "plaintext",
value = "just some dummy text",
}
})
end,
}
})
vim.lsp.start({ name = "dummy-ls", cmd = server }) |
It could run in a coroutine to allow yielding. For example, if we extended --- a/runtime/lua/vim/_system.lua
+++ b/runtime/lua/vim/_system.lua
@@ -22,10 +22,11 @@ local uv = vim.uv
--- @field handle uv_process_t
--- @field timer uv_timer_t
--- @field pid integer
--- @field timeout? integer
--- @field done boolean
+--- @field co thread
--- @field stdin uv_stream_t?
--- @field stdout uv_stream_t?
--- @field stderr uv_stream_t?
--- @field cmd string[]
--- @field result? SystemCompleted
@@ -78,10 +79,20 @@ local MAX_TIMEOUT = 2 ^ 31
--- @param timeout? integer
--- @return SystemCompleted
function SystemObj:wait(timeout)
local state = self._state
+ if state.done then
+ return state.result
+ end
+
+ local co, is_main = coroutine.running()
+ if co and not is_main then
+ state.co = co
+ -- TODO: spawn timer for timeout?
+ return coroutine.yield()
+ end
+
vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.done
end)
if not state.done then
@@ -294,14 +305,16 @@ function M.run(cmd, opts, on_exit)
code = code,
signal = signal,
stdout = stdout_data and table.concat(stdout_data) or nil,
stderr = stderr_data and table.concat(stderr_data) or nil,
}
-
if on_exit then
on_exit(state.result)
end
+ if state.co then
+ coroutine.resume(state.co, state.result)
+ end
end)
end, function()
close_handles(state)
end)One could write code as if synchronous without blocking and without the 2-color problem of explicit async/await |
|
I don't think there's any need for that and just over complicates things as there are too many edge cases that need to be considered with coroutines. Plus an async interface is already planned (#19624) so it should just be handled with that. All we need here is the handlers to have access to a callback. |
Early WIP/draft:
There are occasionally requests for dedicated APIs for code actions or codelens, (also formatter, but there's
formatexpr,formatprgand!cmdand afaik so far no explanation of why they are not sufficient). Concrete request for codelens: #20181Or potential use-cases for code-action:
For
vim.diagnostic, a dedicated module made a lot of sense as linters have existed long before LSP and there are lots of standalone tools.For the others (codelens, code actions, hover, inline-hints, etc.) the value proposition is more questionable. What would a dedicated API improve over just using LSP?
One problem with "just use LSP" is that you either need a separate process, or some boilerplate.
This PR would address the boilerplate problem.
E.g. a implementation for hover could look like this:
Advantages:
vim.lsp.buf.*#22852)Disadvantages:
vim.fn.expand("<cexpr>"), but you'd currently have to create a dummy window, and open the buffer in that windowsTBD:
methodparam required for each handler, it's kinda redundant.