Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems unnecessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor fix. Using the apt command in scripts is discouraged, and it is recommended to use apt-get instead. According to the apt manual:

The apt(8) commandline is designed as an end-user tool and its behavior might change between versions. Although it aims to maintain backward compatibility, this is not guaranteed if a modification appears advantageous for interactive use.

That's why a warning message is issued when apt is employed in a script.

However, this fix can be safely ignored; nevertheless, there isn't a reason to ignore it.

@Shane-XB-Qian Shane-XB-Qian Apr 8, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it has no pipeline here, maybe it's ok.

# 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'],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how reliable of this? and need to dyn change/config the syntax value?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't foresee a situation where we need to dynamically change the syntax. Can you provide an example to help me understand what you mean?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant the syntax value was not reliable, or dynamic, perhaps if there was a way to auto detect the syntax value (which it belonging) would be great, otherwise e.g markdown file, there perhaps embedded some unconcern code block.

}
])

<
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