Skip to content

Commit

Permalink
Add multiple language server association with syntax elements
Browse files Browse the repository at this point in the history
  • Loading branch information
Indelog committed Apr 10, 2024
1 parent 8255965 commit 1b8a084
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 9 deletions.
17 changes: 10 additions & 7 deletions .github/workflows/unitests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,26 @@ jobs:
steps:
- name: Install packages
run: |
sudo apt update
sudo apt-get update
# install clangd language server
sudo apt install -y clangd-15
sudo apt-get install -y clangd-15
# install nodejs
sudo apt install -y curl
sudo apt-get install -y curl
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo bash -
sudo apt install -y nodejs
sudo apt-get install -y nodejs
# install the typescript language server
sudo npm install -g typescript-language-server typescript
# install the golang language server
sudo apt install -y golang
sudo apt install -y gopls
sudo apt-get install -y golang
sudo apt-get install -y gopls
# install the rust language server
sudo apt install -y cargo rust-src
sudo apt-get install -y cargo rust-src
mkdir -p ~/.local/bin
curl -L https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-x86_64-unknown-linux-gnu.gz | gunzip -c - > ~/.local/bin/rust-analyzer
chmod +x ~/.local/bin/rust-analyzer
# install markdown language server
curl -L https://github.com/artempyanykh/marksman/releases/latest/download/marksman-linux-x64 -o ~/.local/bin/marksman
chmod +x ~/.local/bin/marksman
- name: Setup Vim
uses: rhysd/action-setup-vim@v1
id: vim
Expand Down
49 changes: 49 additions & 0 deletions autoload/lsp/buffer.vim
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ export def BufLspServerGet(bnr: number, feature: string = null_string): dict<any
endif
endfor

# The LSP is explicitly associated to a specific syntax name within the syntax stack
var selectedLsp = SelectLSPBySyntaxNames(possibleLSPs)
if (!selectedLsp->empty())
return selectedLsp
endif

# Return the first LSP server that supports "feature" and doesn't have it
# disabled
for lspserver in possibleLSPs
Expand Down Expand Up @@ -191,4 +197,47 @@ export def CurbufGetServerChecked(feature: string = null_string): dict<any>
return lspserver
enddef

# Returns the selected LSP based on the syntax names stacked under the
# current cursor position
def SelectLSPBySyntaxNames(possibleLSPs: list<dict<any>>): dict<any>
var synnameStack = util.ListSynstackNamesAtPoint(line('.'), col('.'))->reverse()

if synnameStack->empty()
return {}
endif

# Initialize variables for tracking the selected LSP and the index of the
# matched word
# The syntax word statck is revesed so the word at a lower index is deeper
# in the syntax stack : use it in priority
var synWordIdx = 1000
var selected = {}

for server in possibleLSPs
if server.syntaxAssociatedLSP->empty()
continue
endif

# Loop through each syntax name in the stack
for idx in range(len(synnameStack))
# Skip this syntax name if it's not in the list of associated syntax
# names for this LSP
if server.syntaxAssociatedLSP->index(synnameStack[idx]) < 0
continue
endif

# Update the selected LSP and the index of the matched word if the
# syntax name has higher priority
if idx < synWordIdx
selected = server
synWordIdx = idx
# Break out of the loop once the LSP has been selected
break
endif
endfor
endfor

return selected
enddef

# vim: tabstop=8 shiftwidth=2 softtabstop=2
5 changes: 5 additions & 0 deletions autoload/lsp/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,11 @@ export def AddServer(serverList: list<dict<any>>)
server.runUnlessSearch = []
endif

if !server->has_key('syntaxAssociatedLSP') ||
server.syntaxAssociatedLSP->type() != v:t_list
server.syntaxAssociatedLSP = []
endif

var lspserver: dict<any> = lserver.NewLspServer(server)

var ftypes = server.filetype
Expand Down
3 changes: 2 additions & 1 deletion autoload/lsp/lspserver.vim
Original file line number Diff line number Diff line change
Expand Up @@ -1892,7 +1892,8 @@ export def NewLspServer(serverParams: dict<any>): dict<any>
typeHierPopup: -1,
workspaceConfig: serverParams.workspaceConfig->deepcopy(),
workspaceSymbolPopup: -1,
workspaceSymbolQuery: ''
workspaceSymbolQuery: '',
syntaxAssociatedLSP: serverParams.syntaxAssociatedLSP->deepcopy(),
}
lspserver.logfile = $'lsp-{lspserver.name}.log'
lspserver.errfile = $'lsp-{lspserver.name}.err'
Expand Down
4 changes: 4 additions & 0 deletions autoload/lsp/util.vim
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,8 @@ export def FindNearestRootDir(startDir: string, files: list<any>): string
return sortedList[0]
enddef

export def ListSynstackNamesAtPoint(line: number, col: number): list<string>
return synstack(line, col)->map((_, v) => v->synIDattr('name'))
enddef

# vim: tabstop=8 shiftwidth=2 softtabstop=2
38 changes: 38 additions & 0 deletions doc/lsp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,44 @@ everything else: >
},
])
<
Language servers can also be configured to associate themselves with specific
syntax elements. If a syntax element is detected at the current location, the
corresponding language server will take precedence over the standard ordering.

To specify a language server for a particular syntax element, use the
`syntaxAssociatedLSP` property in the configuration object passed to
`LspAddServer`. The value of `syntaxAssociatedLSP` should be a string or list
of strings representing the desired syntax elements.
>
vim9script
# Add two LSP configurations for handling JavaScript, TypeScript, and
# GraphQL files.
g:LspAddServer([
{
# Configuration for Typescript-language-server:
# Used for general purposes when working with '.js'
# and '.ts' files.
filetype: ['javascript', ''typescript'],
path: 'typescript-language-server',
args: ['--stdio']
},
{
# Configuration for GraphQL-language-server:
# Specifically bound to 'graphqlTemplateString' syntax
# element. Will handle requests regarding GraphQL
# template literals.
filetype: ['javascript', ''typescript', 'graphql'],
path: 'graphql-lsp',
args: ['server', '-m', 'stream'],
syntaxAssociatedLSP: ['graphqlTemplateString'],
}
])
<
To discover the current syntax stack and determine the appropriate value for
`syntaxAssociatedLSP`, you can employ `LspUtilGetCurrentSynStack` command.

==============================================================================
17. Language Server Features *lsp-features*

Expand Down
2 changes: 2 additions & 0 deletions plugin/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ endif
g:loaded_lsp = true

import '../autoload/lsp/options.vim'
import '../autoload/lsp/util.vim'
import autoload '../autoload/lsp/lsp.vim'

# Set LSP plugin options from 'opts'.
Expand Down Expand Up @@ -105,6 +106,7 @@ command! -nargs=? -bar LspSymbolSearch lsp.SymbolSearch(<q-args>, <q-mods>)
command! -nargs=1 -bar -complete=dir LspWorkspaceAddFolder lsp.AddWorkspaceFolder(<q-args>)
command! -nargs=0 -bar LspWorkspaceListFolders lsp.ListWorkspaceFolders()
command! -nargs=1 -bar -complete=dir LspWorkspaceRemoveFolder lsp.RemoveWorkspaceFolder(<q-args>)
command! -nargs=0 -bar LspUtilGetCurrentSynStack echo util.ListSynstackNamesAtPoint(line('.'), col('.'))

# Add the GUI menu entries
if has('gui_running')
Expand Down
2 changes: 1 addition & 1 deletion test/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fi

VIM_CMD="$VIMPRG -u NONE -U NONE -i NONE --noplugin -N --not-a-term"

TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim markdown_tests.vim rust_tests.vim"
TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim markdown_tests.vim rust_tests.vim syntax_stack_lsp_chooser_test.vim"

RunTestsInFile() {
testfile=$1
Expand Down
68 changes: 68 additions & 0 deletions test/syntax_stack_lsp_chooser_test.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
vim9script

import '../autoload/lsp/buffer.vim' as buf
import '../autoload/lsp/util.vim' as util

source common.vim

g:markdown_fenced_languages = ['c']

var lspServers = [
{
name: 'marksman',
filetype: 'markdown',
path: (exepath('marksman') ?? expand('~') .. '/.local/bin/marksman'),
args: ['server'],
},
{
name: 'clangd',
filetype: 'markdown',
path: (exepath('clangd-15') ?? exepath('clangd')),
args: ['--background-index', '--clang-tidy'],
syntaxAssociatedLSP: ['markdownHighlight_c', 'markdownHighlightc'],

},
]
call LspAddServer(lspServers)

def FillDummyFile()
:silent edit dummy.md
sleep 200m
var lines: list<string> =<< trim END
# Title

```c
int f1() {
int x;
int y;
x = 1;
y = 2;
return x + y;
}
```
END
setline(1, lines)
enddef

def g:Test_ChoseDefaultLspIfNoSyntaxMatch()
FillDummyFile()
search('Title')
var selected_lsp = buf.BufLspServerGet(bufnr(), 'hover')
assert_true(selected_lsp->has_key('name'))
assert_equal(selected_lsp.name, 'marksman')
enddef

def g:Test_ChooseCorrectLspIfSyntaxMatch()
FillDummyFile()
search('int')
var selected_lsp = buf.BufLspServerGet(bufnr(), 'hover')
assert_true(selected_lsp->has_key('name'))
assert_equal(selected_lsp.name, 'clangd')
enddef

# Only here to because the test runner needs it
def g:StartLangServer(): bool
return true
enddef

# vim: shiftwidth=2 softtabstop=2 noexpandtab

0 comments on commit 1b8a084

Please sign in to comment.