nvim-lspconfig icon indicating copy to clipboard operation
nvim-lspconfig copied to clipboard

Support for distant & distant.nvim

Open chipsenkbeil opened this issue 2 years ago • 6 comments

Language server

No response

Requested feature

Per conversation with @mjlbach, opening this issue to serve as the tracker to add support for language servers run on remote machines using distant.nvim with configurations provided by nvim-lspconfig.

State today

At the moment, distant.nvim requires users to provide LSP configurations directly through plugin settings:

use {
  'chipsenkbeil/distant.nvim',
  config = function()
    require('distant').setup {
      -- Apply these settings when connecting to example.com
      ['example.com'] = {
        -- Specify an LSP to run for a specific project
        lsp = {
          ['My Project'] = {
            cmd = '/path/to/rust-analyzer',
            root_dir = '/path/to/project/root',

            -- Do your on_attach with keybindings like you would with nvim-lspconfig
            on_attach = function() 
              -- Apply some general bindings for every buffer supporting lsp
            end,
          },
        },
      },
    }
  end)
}

Blockers

nvim-lspconfig enables configurations to specify a function to determine the root directory of a project for use with a language server. This is incompatible with distant as the function typically performs local operations such as reading files, navigating directories, and running processes.

See rust-analyzer.lua for calls to vim.fn.jobstart, util.find_git_ancestor, and util.root_pattern. The nvim-lspconfig util functions in turn call uv.fs_stat via exists(...) and perform other local operations.

My first thought is that distant.nvim could provide a compatibility function that, when executed, will attempt to wrap globals like vim.fn.jobstart and uv.fs_stat to be executed in a remote context. Even if that existed, we'd need some way to plug into nvim-lspconfig to detect whether a buffer was local or remote - distant.nvim can already do this - and then perform the injection accordingly.

Of course, if there's another way that nvim-lspconfig could be redesigned to support alternative root_dir lookups without overwriting a bunch of global functions, I'd be more than happy to pursue that direction.

Other clients which have this feature

No response

chipsenkbeil avatar Oct 16 '21 22:10 chipsenkbeil

I'm assuming this would also benefit from https://github.com/neovim/neovim/issues/15847 to make everything more streamlined.

chipsenkbeil avatar Oct 16 '21 23:10 chipsenkbeil

Options (to be updated):

  • We provide a more abstract API over the minimum subset of features we need that can be overridden by distant, to avoid distant overriding core neovim functions like jobstart. These can then be overridden by distant.
  • We don't provide root patterns to distant, and users have to implement this themselves with distant functionality
  • Distant only works by reading a project file, a pure text (no lua) project markers/configuration that can hardcode the root path

mjlbach avatar Oct 17 '21 00:10 mjlbach

Alternatively the util.root_pattern could be implemented with some shell/regex/rg/tree/grep (not all together) in the distant server and the information sent back to the client.

muniter avatar Oct 19 '21 01:10 muniter

@chipsenkbeil since I opened this issue I directly exposed the configurations to users, I also have a draft PR (in core) allowing for non stdio servers for RPC

:lua print(vim.inspect(require('lspconfig.server_configurations.pyright').default_config))

I think we should not use the lua functions that resolve roots for remote servers. I am in favor of a declarative only approach (you need to declare workspace folders for remote servers explicitly). Otherwise I think it will be difficult, there are too many cases that require explicit handling.

I'm also thinking of removing the root directory patterns from lspconfig entirely into their own repository. These really aren't server specific and are more "language project" markers that could be used by other projects, including core.

What remains to be done then?

mjlbach avatar Jan 07 '22 05:01 mjlbach

@mjlbach I have no problem with that approach based on my understanding as it's what I do today.

From lspconfig, the only feature that distant needs is to be able to wrap around the logic that does the language server spawning. We need to be able to detect if the buffer represents a local or remote file, and if remote then distant will spawn the language server otherwise we leave it up to the default neovim libuv logic. This is currently done via the distant.nvim plugin using this logic.

I could also imagine that it would be desirable to separate out configs for local versus remote servers. A workspace on a remote machine might not be what you want locally. In fact, you might want different workspace locations for different servers.

On server A.A.A.A:
    Open `rls` remotely for '/path/to/rust/project1' and /path/to/rust/project2' for `*.rs`
On server B.B.B.B:
    Open `rls` remotely for '/path/to/rust/project1' and /other/project_abc' for `*.rs`
On local machine:
    Open `rls` locally for any `*.rs` with `Cargo.toml` marking the root directory

distant.nvim already supports distinct configurations based on the server, but I'm not sure how that would work with lspconfig as it has a singular config that's generic (using your root patterns function).

Core neovim requirements

The main requirements for distant to work with language servers (described in https://github.com/neovim/neovim/issues/15847) are

  1. Take control of launching and managing the language server (e.g. spawn and kill handler functions)
  2. Act as the proxy for pipes (e.g. stdin, stdout, and stderr for LSP servers)

For the above, I hack this as seen in distant.nvim/lsp.lua.

chipsenkbeil avatar Jan 09 '22 00:01 chipsenkbeil

I have nothing interesting to add :), but just will mention that as the maintainer of lean.nvim (which is similar to rust-analyzer in that it wants to provide a nice experience for the Lean language instead of Rust) that I'm keeping a far eye on any thoughts here and am happy to try out anything if/when there's ideas on how to make this work!

Julian avatar Nov 02 '23 22:11 Julian