jose-elias-alvarez / null-ls.nvim

Use Neovim as a language server to inject LSP diagnostics, code actions, and more via Lua.
Other
3.64k stars 791 forks source link

When there are multiple projects opend in one nvim instance, null-ls will only start one instance at the first project root, and other project's local configuration files cannot be respected #1134

Open milanglacier opened 2 years ago

milanglacier commented 2 years ago

FAQ

Issues

Neovim Version

NVIM v0.8.0-dev-1092-g89b9eab63-dirty

Operating System

macOS

Minimal config

It's not related to config files.

Steps to reproduce

  1. Open files in one project.
  2. Open another file in another project.

We can simply assume the two projects are two git projects. Suppose the first project is a lua project, and the second is a python project, I have some project local configuration files (for example .flake8), then open files in the second project will result in, say the following diagnostic errors:

Screen Shot 2022-09-24 at 04 16 27

Because flake8 spawned by null-ls does not start at the correct project root.

Expected behavior

Like what lspconfig did when there are multiple projects are detected, spawn multiple lsps under different root directory.

Actual behavior

Null-ls will only spawn one lsp in the first root directory. Or if there is only one project in this nvim instance, the null-ls works fine, as the project root of null-ls is correct.

Debug log

Not related

Help

Yes, but I don't know how to start. I would need guidance

Implementation help

No response

Requirements

milanglacier commented 2 years ago

https://github.com/jose-elias-alvarez/null-ls.nvim/blob/8af89c5fa2b732aaa9c3bf8aed95bccc9c4ce295/lua/null-ls/config.lua#L98

Is it possible to maintain an additional lua table which records the working directory null-ls launches? And in an BufEnter autocmd to detect if there is a new project (new root), if true, add a new entry into this table and launch a new null-ls instance.

jose-elias-alvarez commented 2 years ago

Yep, this is currently not supported - see #1120 for discussion on why this is and what can be done about it. This isn't a workflow I personally use, but I'm happy to review / provide support with PRs improving behavior here. I think the approach you suggest is the right one, though there's a few other areas that assume only a single null-ls client will ever exist that also have to be updated.

Edit: deleted non-functional suggestion.

milanglacier commented 2 years ago

Hi! Thanks for your suggestion. I would definitely will to make the contribution.

But I am thinking about that: there are some sources which don't need to respect project local settings, for example: proselint, codespell, shellcheck, then if we spawn multiple null-ls instances, then those cli program will be also spawned multiple times. Do we need to take care of it? Just leave it as it is?

jose-elias-alvarez commented 2 years ago

But I am thinking about that: there are some sources which don't need to respect project local settings, for example: proselint, codespell, shellcheck, then if we spawn multiple null-ls instances, then those cli program will be also spawned multiple times. Do we need to take care of it? Just leave it as it is?

I don't think the behavior will change for built-in sources without local configuration (and I think the maintenance burden of tracking which sources do and don't need multiple clients is too high).

milanglacier commented 2 years ago

Yep, this is currently not supported - see #1120 for discussion on why this is and what can be done about it. This isn't a workflow I personally use, but I'm happy to review / provide support with PRs improving behavior here. I think the approach you suggest is the right one, though there's a few other areas that assume only a single null-ls client will ever exist that also have to be updated.

Also, for anyone who stumbles on this issue before it's solved, here is a workaround:

local flake8 = require("null-ls").builtins.diagnostics.flake8.with({
    cwd = function()
        return vim.loop.cwd()
    end,
})

I have tried this in my config and it does not work. It seems that the only way to fix it is to allow null-ls to spawn multiple instances under different project root.

Minimal config example:

```lua -- this template is borrowed from nvim-lspconfig local on_windows = vim.loop.os_uname().version:match("Windows") local function join_paths(...) local path_sep = on_windows and "\\" or "/" local result = table.concat({ ... }, path_sep) return result end vim.g.loaded_remote_plugins = "" vim.cmd([[set runtimepath=$VIMRUNTIME]]) local temp_dir = vim.loop.os_getenv("TEMP") or "/tmp" vim.cmd("set packpath=" .. join_paths(temp_dir, "nvim", "site")) local package_root = join_paths(temp_dir, "nvim", "site", "pack") local install_path = join_paths(package_root, "packer", "start", "packer.nvim") local compile_path = join_paths(install_path, "plugin", "packer_compiled.lua") local null_ls_config = function() local null_ls = require("null-ls") -- add only what you need to reproduce your issue null_ls.setup({ sources = { null_ls.builtins.diagnostics.flake8.with { cwd = function() return vim.loop.cwd() end, }, }, debug = true, }) end local function load_plugins() -- only add other plugins if they are necessary to reproduce the issue require("packer").startup({ { "wbthomason/packer.nvim", { "jose-elias-alvarez/null-ls.nvim", requires = { "nvim-lua/plenary.nvim" }, config = null_ls_config, }, }, config = { package_root = package_root, compile_path = compile_path, }, }) end if vim.fn.isdirectory(install_path) == 0 then vim.fn.system({ "git", "clone", "https://github.com/wbthomason/packer.nvim", install_path }) load_plugins() require("packer").sync() else load_plugins() require("packer").sync() end ```

I think the reason is that I use project.nvim to automatically setup cwd in different project. And null-ls launches flake8 before project.nvim switches the project root for the new opened buffer. So the cwd doesn't change when flake8 is launched.

project.nvim launches its autocmd to switch project root at BufEnter, while null-ls launches its autocmd on FileType, and by using the lua way to detect filetype : i.e vim.filetype, the filetype is set at the events BufRead and BufNewFile, both of which which preceed the BufEnter, resulting in the conflict.

milanglacier commented 2 years ago

A temporary workaround would be:

    local null_ls = require 'null-ls'
    local util = require 'null-ls.utils'
            null_ls.builtins.diagnostics.flake8.with {
                command = mypath.flake8,
                cwd = function()
                    return util.root_pattern('.git')(vim.fn.expand '%:p')
                end,
            },

this works for me if you are using project.nvim as your project manager.

jose-elias-alvarez commented 2 years ago

I see, thanks for mentioning the workaround for the plugin. We should make sure that whatever solution we come up with also handles the interaction, since I imagine a good percentage of users who want this feature are using the plugin (or a similar one).

jose-elias-alvarez commented 2 years ago

FYI: while the original issue still exists and will only be fixed by creating multiple null-ls clients, #1172 should fix the specific issue for flake8.