neovim / nvim-lspconfig

Quickstart configs for Nvim LSP
Apache License 2.0
10.46k stars 2.06k forks source link

Project local settings #383

Closed Sh3Rm4n closed 3 years ago

Sh3Rm4n commented 3 years ago

How to reproduce the problem from neovim startup

I'm trying to setup up project local settings for my lsp, specifically rust_analyser. Project local settings are easy for other lsp's. Coc for example allows to create a vim/coc-settings.json similar to vscode's vscode/settings.json, which overwrites the global lsp configuration.

I am using localvimrc to load a directory specific vim files automatically. But I'm stuck on that point.

My latest approach looks something like this:

lua << EOF
require('nvim_lsp').rust_analyzer.setup {
    on_init = client.workspace_did_change_configuration({
        settings = {
            ["rust-analyzer"] = {
                ["checkOnSave"] = {
                    ["enabled"] = true,
                    ["command"] = "clippy",
                },
                ["cargo"] = {
                    ["target"] = "thumbv7em-none-eabihf",
                },
            },
        }
    })
}
EOF

But this is obviously not working.

I tried the client.workspace_did_change_configuration({setup}) approach. But I don't really understand, how to apply it.

https://github.com/neovim/nvim-lspconfig/blame/6ebd50abb40e8849ae0717328f478ee75cb62568/README.md#L169

Actual behavior

lua print(vim.inspect(vim.lsp.buf_get_clients())) show the configuration from above under config.settings

Expected behavior

Local settings are not getting applied.

localvimrc: error when sourcing script: "Vim(lua):E5108: Error executing lua [string ":lua"]:2: attempt to index global 'client' (a nil value)" (<...>/.lvimrc, line 20)
teto commented 3 years ago

on_init = client.workspace_did_change_configuration({ it doesn't know client. That recommandation is for a dynamic setup but in your case you can setup the server before starting neovim so just try to configure rust_analyzer.setup without any trick.

NB: in case you dont know there is :h exrc that can potentially replace your plugin.

Sh3Rm4n commented 3 years ago

Thanks for your help.

My try above was just a shot in the blue. But your solution is pretty obvious in hindsight :sweat_smile:

Thanks for the suggestions about :h exrc. Seems like the better solution for my use case, and it does not need any plugins :)

I'll try out your suggestions and will close this issue, when it is working.

Maybe I expected some configuration like CoC, where you can configure your local coc-settings.json, which will apply all settings to the default configuration.

In this case, if I understand it correctly, I'll have to repeat myself, if I want to configure the lsp with extra options. But this isn't too annoying and it should be easy to do some scripting to improve this situation on my side.

Anyway thanks for your help! :)

mjlbach commented 3 years ago

NB: in case you dont know there is :h exrc that can potentially replace your plugin.

Unfortunately I think this is currently broken with init.lua

Sh3Rm4n commented 3 years ago

After getting a bit more comfortable with lua, I've found a solution to the problem that I described in https://github.com/neovim/nvim-lspconfig/issues/383#issuecomment-727573659.

Converting the global lsp config to a module, like so

local M = {} 

M.rust_analyzer.settings = {
    ["rust-analyzer"] = {
        ["checkOnSave"] = {
            ["enabled"] = true,
            ["command"] = "clippy",
        },
    },
}

require('lspconfig').rust_analyzer.setup {
    settings = M.rust_analyzer.settings,
    on_attach = -- ...
}

return M

and then acquire that table in the local config file (e.g. .nvimrc)

lua << EOF
-- get the global config
settings = require('mycustom_lspconfig')
-- applying changes, which are unique to the local project
settings["rust-analyzer"]["cargo"] = {
      ["target"] = "thumbv7em-none-eabihf",
}
-- and overwrite the global configuration for the server
-- with the new configuration
require('lspconfig').rust_analyzer.setup {
    settings = settings,
    on_attach = -- ...
}
EOF

This is a solution, sufficient for my usecase. After knowing lua a little better, this solution is quite simple and obvious in hindsight. But this is not as convenient and obvious as the solution provided by CoC. However, lspconfig's / builtin lsp's goal isn't to be as convenient as CoC I assume, it does focus more on flexibility.

Maybe the documentation could be improved for beginners? Or is it already documented, and I've just overlooked that part? I'm willing to contribute to the documentation, but I am not sure about the correct place for this tip and if my solution is the best one.

mjlbach commented 3 years ago

I think we should add a section on project local settings and include an example similar to the above, however, ~unless I'm mistaken~ this is currently broken when using an init.lua (and there is a PR to fix). I personally prefer this approach to coc, since it mimics emacs in where you can define arbitrary editor settings (default formatter, project specific commands) in a way that is significantly more flexible that whatever json options coc affords you.

mjlbach commented 3 years ago

Related: https://github.com/neovim/neovim/pull/13503

lithammer commented 3 years ago

You could also do something like this in Vimscript:

let settings = findfile('.settings.lua', '.;')
if !empty(settings)
  luafile settings
endif

Same but in Lua:

local settings = vim.fn.findfile('.settings.lua', '.;')
if settings ~= '' then
  dofile(settings)
end

Ideally it should be called as part of on_attach, or perhaps even better in before_init. Note that I haven't actually tested the above, so there might be issues with loading order etc. But the general idea should work I think.

mjlbach commented 3 years ago

I added a note to the wiki on using .exrc for project local settings.

baco commented 3 years ago

The problem with that approach is that if Neovim is launched from a desktop launcher the working directory is the user's home directory.

Even changing the working directory from inside Neovim with the :cd command won't take effect on loading the custom local config.

ibraheemdev commented 3 years ago

If require('lspconfig').foo.setup is put in both the global and local configs, won't the language server be started twice? Or is the suggestion to only call setup in local configs?

ibraheemdev commented 3 years ago

I just ended up having an optional .lspconfig.lua file with local settings:

local f = loadfile(vim.fn.getcwd() .. '/.lspconfig.lua')

if f ~= nil then
    local cfg = f()

    if cfg ~= nil then
        if cfg.rust_analyzer ~= nil then
            print("merging local rust-analyzer config")

            for opt, val in pairs(cfg.rust_analyzer) do
                rust_analyzer[opt] = val
            end
        end
    end
end

exrc is deprecated.

LunarLambda commented 8 months ago

If require('lspconfig').foo.setup is put in both the global and local configs, won't the language server be started twice? Or is the suggestion to only call setup in local configs?

I was also wondering about this. From what I understand, the LS is only launched once, but the second call to setup() replaces its settings?

But I think with the wiki section suggesting .exrc/.nvim.lua it would be good to clarify the interaction. From a brief test it seems to work alright for simple use cases.

My understanding is that the second setup() call overrides the settings, which are only used once the Lsp actually starts/attaches to a buffer. So it's fine to call setup() more than once at startup, but it wouldn't work while neovim is already running.

Update: I've ended up reading the setup() function and as far as I can tell, it does just replace settings. However, it can register duplicate autocmds if autostart is set to true. Unsure how big of a problem that is.