yegappan / lsp

Language Server Protocol (LSP) plugin for Vim9
MIT License
473 stars 57 forks source link

Add multiple language server association with syntax elements #496

Open indelog opened 6 months ago

indelog commented 6 months ago

Improve multi-language server support by enabling the selection of the appropriate LSP based on syntax stack elements using a new configuration option for the LSP: syntaxAssociatedLSP, which accepts a list of syntax names obtainable through:

synstack(line, col)->map((_, v) => v->synIDattr('name'))

This enhancement allows by example, the simultaneous use of a PHP and HTML LSP in a PHP file when an LSP function is triggered. If the syntax stack contains an htmlTag pattern, the HTML LSP is called; otherwise, the PHP LSP is utilized. Furthermore, this improvement supports cases like embedding GraphQL blocks in TypeScript files.

Example usages include situations like the following configurations:

    call LspAddServer([
                \{
                \    'name': 'phpactor',
                \    'filetype': ['php'],
                \    'path': expand('~') . '/.local/bin/phpactor',
                \    'args': ['language-server'],
                \},
                \{
                \    'name': 'htmlls',
                \    'filetype': ['html', 'php'],
                \    'path': l:lib_path . 'node_modules/.bin/html-languageserver',
                \    'args': ['--stdio'],
                \    'initializationOptions': {'embeddedLanguages': {'css': v:true, 'javascript': v:true}},
                \    'syntaxAssociatedLSP': ['htmlTag', 'htmlError'],
                \    'runUnlessSearch': [
                \      'angular.json',
                \    ],
                \},
                \{
                \    'name': 'typescript-language-server',
                \    'filetype': ['typescript', 'javascript'],
                \    'path': l:lib_path . '/node_modules/.bin/typescript-language-server',
                \    'args': ['--stdio'],
                \},
                \{
                \    'name': 'graphql-language-server',
                \    'filetype': ['typescript', 'javascript', 'graphql'],
                \    'path': l:lib_path . '/node_modules/.bin/graphql-lsp',
                \    'args': ['server', '-m', 'stream'],
                \    'initializationOptions': {'diagnostics': 'true'},
                \    'syntaxAssociatedLSP': ['graphqlTemplateString'],
                \},
                \])

While acknowledging that this approach is relatively straightforward, I believe it remains effective for conventional use cases as long as the language server is designed to operate within the context of the current file.

Shane-XB-Qian commented 6 months ago

thanks, seems a good try, but not sure how reliable was it, and need to dyn change/config the syntax value? seems a little not easy to use.

BTW: truly there now still some problem so far no good answer, e.g:

  1. like this one, how to easily handle embedded code.
  2. some lsp handle for same ft, e.g yaml and ansible was both to 'yml' ft.
indelog commented 6 months ago

With this proposal, I do not claim to fully address the complex challenge of multi-LSP, but rather aim to enhance existing options with a simple solution built upon Vim's inherent syntax identification capabilities.

This works smoothly in cases where the LSP is designed to operate within the given context. As demonstrated, the proposed solution excels in cases like PHP files with embedded HTML or TypeScript files featuring GraphQL templates. By maintaining the primary LSP as the default and placing it first in the LSP list, complementary LSPs are included only when encountered within a specific syntax group. Notably, the script processes the complete syntax stack of the current word, starting from the highest level.

Admittedly, cases similar to the YAML Ansible scenario cannot be resolved solely through the suggested approach, as decisions concerning the appropriate LSP cannot depend exclusively on file syntax. In such circumstances, tradeoffs must be made, leaning on other configuration options like runIfSearch or features. For instance, choosing the Angular LSP over the HTML LSP requires the following configuration:

    call LspAddServer([
                \{
                \    'name': 'angular',
                \    'filetype': ['html'],
                \    'path': g:lib_path . 'node_modules/.bin/ngserver',
                \    'args': [
                \      '--stdio',
                \      '--tsProbeLocations',
                \      g:lib_path . 'node_modules/typescript/lib/',
                \      '--ngProbeLocations',
                \      g:lib_path . 'node_modules/@angular/language-server/bin/',
                \    ],
                \    'initializationOptions': {'diagnostics': 'true'},
                \    'runIfSearch' : [
                \      'angular.json',
                \    ],
                \},
                \{
                \    'name': 'htmlls',
                \    'filetype': ['html', 'php'],
                \    'path': g:lib_path . 'node_modules/.bin/html-languageserver',
                \    'args': ['--stdio'],
                \    'initializationOptions': {'embeddedLanguages': {'css': v:true, 'javascript': v:true}},
                \    'syntaxAssociatedLSP': ['htmlTag', 'htmlError'],
                \    'runUnlessSearch': [
                \      'angular.json',
                \    ],
                \},
                \])
Shane-XB-Qian commented 6 months ago

With this proposal, I do not claim to fully address the complex challenge of multi-LSP, but rather aim to enhance existing options with a simple solution built upon Vim's inherent syntax identification capabilities.

i know, so i am just discussing, not against it, but just the detection on syntax val seems manually seems was not reliable, or IF it would be auto then would be great.

as for 'ansible', there was no fixed file like 'angular.json', but shared yaml lsp with same ft, that's another knew problem.

indelog commented 5 months ago

Manually configuring the syntax may seem non-optimal. But this can have certain advantages, notably allowing the user to adapt their configuration as they wish without having to worry about whether the LPS plugin supports the scenario or not.

On the other hand, this can in fact sometimes be tricky. I actually got trapped with the new unit tests because on my machine a C language block is recognized with the syntax name markdownHighlight_c whereas with the version of vim used by the CI is markdownHighlightc. This is why I added the small utility command LspUtilGetCurrentSynStack to help find which syntax name used for syntaxAssociatedLSP.

As there is no standard regarding the name of syntax identifiers and each syntax description has its own terminology, I do not see how to automate the process of identifying syntax names depending on the context. Any idea suggested?

Shane-XB-Qian commented 5 months ago

Any idea suggested?

i like your 'try', but so far no good answer, wish there some more convenience or reliable way.

jclsn commented 4 months ago

Would this fix https://github.com/yegappan/lsp/issues/521 ?