Skip to content

Commit

Permalink
Add multiple language server association with syntax elements
Browse files Browse the repository at this point in the history
Fix redundant blank line
  • Loading branch information
Indelog committed Apr 9, 2024
1 parent 8255965 commit 33e3b3f
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 9 deletions.
20 changes: 13 additions & 7 deletions .github/workflows/unitests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ 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
Expand All @@ -36,6 +36,12 @@ jobs:
version: ${{ matrix.vim }}
- name: Checkout LSP plugin Code
uses: actions/checkout@v4
- name: Prepare Tests
run: |
# install deps for the dummy lsp
cd ./test/dummy-lsp/
npm install
cd -
- name: Run Tests
run: |
uname -a
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ test/X*.cpp
# test/Xtest.c
# test/Xtest.cpp
test/results.txt
test/dummy-lsp/node_modules/
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
54 changes: 54 additions & 0 deletions test/dummy-lsp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions test/dummy-lsp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "dummy-lsp",
"version": "0.0.0",
"description": "Mock for LSP server",
"main": "server.js",
"dependencies": {
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.11"
}
}
43 changes: 43 additions & 0 deletions test/dummy-lsp/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env node

(async () => {
const {
createConnection,
TextDocuments,
ProposedFeatures,
} = await import('vscode-languageserver');
const {
TextDocument,
} = await import('vscode-languageserver-textdocument');

async function main() {
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);

const settings = {};

connection.onInitialize((params) => {

settings.hoverReply = params.initializationOptions?.hoverReply || 'DEFAULT';

const result = {
capabilities: {
hoverProvider: true,
}
};

return result;
});

connection.onHover(param => {
return {
contents: settings.hoverReply,
}
})

documents.listen(connection);
connection.listen();
}

await main();
})()
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
Loading

0 comments on commit 33e3b3f

Please sign in to comment.