SmiteshP/nvim-navic

How to handle buffer which has multiple LSP clients available?

lougreenwood opened this issue · 9 comments

I'm noticing some weird behaviour for buffers which have multiple clients with the documentSymbolProvider capability.

Specifically, I'm using tsserver and ember language servers.

When I open any .ts or .js file with these servers I get the error nvim-navic: Failed to attach to tsserver for current buffer. Already attached to tsserver.

What is the correct way to handle this? I'm assuming that a quick and dirty way to do this would be could be to hard code in the on-attach, but I'm trying to think of a more general config which doesn't require hard coding.

Alternatively, do you see any heuristic which nvim-navic could employ to discern which client of many is the most suitable one to use? (Maybe the one with the most provided symbols or something similar?)

Thankyou!

Alternatively... considering that multiple clients might provide symbols for different parts of a file (In this case, tsserver for the "typescript" code and ember for the embedded template code), is it possible that nvim-navic might support multiple clients in future?

Proper solution for this is really to just using the attach function on only one of the lsp servers. nvim-navic can make use of only one of the lsp server right now. Or you can use navic_silence global variable to silence the error messages thrown by navic, but it will still use only one of the servers.

considering that multiple clients might provide symbols for different parts of a file (In this case, tsserver for the "typescript" code and ember for the embedded template code)

Not sure I follow, one file has code from multiple languages?

is it possible that nvim-navic might support multiple clients in future?

No plans for this right now, but I would need to understand the use case for this. Because it is difficult to understand which information to use and which to discard when multiple servers are giving info, so I trust the user would make the attachments as they see fit.

Not sure I follow, one file has code from multiple languages?

Correct, for example, this can be seen here in the way tests are written in Ember.js:

test("should allow disabling the button", async function (assert) {
  await render(hbs`
    <SimpleButton
      @label="Hello world!"
      @isDisabled={{true}}
    />
  `);
  
  let button = this.element.querySelector("button");
  assert.strictEqual(button.disabled, true);
});

https://guides.emberjs.com/release/testing/testing-tools/#toc_qunit-qunit-dom

You can see here that the file is a javascript file, but in the template literal (hbs`...` ) we have handlebars (aka glimmer) template code.

So for this code, I need 2 LSP clients, typescript & ember. The ember LSP client knows to only deal with document symbols in the hbs tagged code.

You can also see more info here: https://github.com/ember-template-imports/ember-template-imports - this is for the .gjs file format which also allows combining js with these glimmer templates.

Treesitter also supports this concept of files having multiple formats / languages: http://tree-sitter.github.io/tree-sitter/using-parsers#multi-language-documents

Also, here's some info about how VSCode handles this: https://code.visualstudio.com/api/language-extensions/embedded-languages

No plans for this right now, but I would need to understand the use case for this. Because it is difficult to understand which information to use and which to discard when multiple servers are giving info, so I trust the user would make the attachments as they see fit.

For the above use case, I would guess that something like the following would be how to handle this (bear in mind I know very little about LSP inner workings / api / implementation, so maybe some of my assumptions might be naive or incorrect):

  • for a given file, multiple lip clients can be attached
  • for a given position in the file, only one lsp client should provide symbols
    • some heuristineg / rule needed if lsp's misbehave and provide symbols?
  • nvim-navic displays the current symbol for the code currently under the cursor

Hmm.. seems complicated 🤔

Hello

I had a similar error over the weekend:

Javascript Compatibility

The issue seems to have been resolved by removing redundant Javascript LSPs. Removing vtsls has me running smoothly again.

@unfirthman But it seems that in your use case, your other LSP client was redundant. In the usecase I'm describing both LSPs are useful and needed to get full symbol "discovery" for a given file.

@kola-web What's the point of providing a wrong snippet without explanation? You still attach only one navic instance for only one of the LSPs of the current buffer. The problem they were talking about here is being able to handle multiple language servers attached to the same buffer.

I have a similar problem recently. There is an error message saying that navic for tsserver cannot be attached because graphql lsp has occupied the only position. This is an error for me because tsserver outweighs graphql one in this case.

It will be really helpful if navic can be applied to multiple LSPs.

For some real examples I have encountered:

  1. I have a private ChatGPT program in neovim, and it's unavoidable to write multiple embedded scripts in different programming languages inside the prompt.
  2. Sometimes I need to write JS inside HTML.
  3. There are JSX syntax, CSS-in-JS, etc.

Also relevant to this discussion is tsserver and volar 2.0 (vue-language-server).

Recommendation from the team is to attach both lsp clients to buffer: https://github.com/vuejs/language-tools?tab=readme-ov-file#community-integration

For now in my "breadcrumbs" method I just don't attach navic to the buffer for vue files.

-- allows for context aware breadcrumbs
local breadcrumbs = function(client, bufnr)
	if client.name == "volar" then
		return
	end