aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorEric Stern2026-05-31 09:59:23 -0700
committerGitHub2026-05-31 17:59:23 +0100
commit36d541facbcaa3c947bd19ce85a7b854f9257598 (patch)
treeab7ad218a956c4a2f717254eca0deca8aa7fd298
parent7a7fc85e519ebe3eae0246445592e9636a0791ad (diff)
downloadale-36d541facbcaa3c947bd19ce85a7b854f9257598.tar.gz

Read trigger characters from LSP initialize responses (#5121)

  • Add ale#lsp#GetCompletionTriggerCharacters getter
  • Add s:GetTriggerCharacters helper with LSP support
  • Pass connection ID to GetTriggerCharacter in s:OnReady
  • Use LSP trigger characters in Filter function
  • Add tests for LSP completion trigger characters
  • Check LSP trigger characters in GetPrefix
  • Add tests for GetAllCompletionTriggerCharactersForBuffer

GetTriggerCharacter now accepts optional conn_id parameter to prefer LSP-provided trigger characters over the hardcoded map.

GetPrefix now checks if the line ends with any trigger character from LSPs active for the current buffer, enabling automatic completion for LSP-provided triggers like > for PHP.

-rw-r--r--autoload/ale/completion.vim42
-rw-r--r--autoload/ale/lsp.vim30
-rw-r--r--test/completion/test_completion_filtering.vader58
-rw-r--r--test/lsp/test_other_initialize_message_handling.vader48
4 files changed, 173 insertions, 5 deletions
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index a435eb13f..bd49f96eb 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -145,6 +145,7 @@ let s:omni_start_map = {
" A map of exact characters for triggering LSP completions. Do not forget to
" update self.input_patterns in ale.py in updating entries in this map.
+" These are used as a fallback when LSP servers don't provide trigger chars.
let s:trigger_character_map = {
\ '<default>': ['.'],
\ 'typescript': ['.', '''', '"'],
@@ -153,6 +154,19 @@ let s:trigger_character_map = {
\ 'c': ['.', '->'],
\}
+" Get trigger characters, preferring LSP-provided ones over hardcoded.
+function! s:GetTriggerCharacters(filetype, conn_id) abort
+ if !empty(a:conn_id)
+ let l:lsp_triggers = ale#lsp#GetCompletionTriggerCharacters(a:conn_id)
+
+ if !empty(l:lsp_triggers)
+ return l:lsp_triggers
+ endif
+ endif
+
+ return s:GetFiletypeValue(s:trigger_character_map, a:filetype)
+endfunction
+
function! s:GetFiletypeValue(map, filetype) abort
for l:part in reverse(split(a:filetype, '\.'))
let l:regex = get(a:map, l:part, [])
@@ -175,15 +189,32 @@ function! ale#completion#GetPrefix(filetype, line, column) abort
" abc
" ^
" So we need check the text in the column before that position.
- return matchstr(getline(a:line)[: a:column - 2], l:regex)
+ let l:line_text = getline(a:line)[: a:column - 2]
+ let l:prefix = matchstr(l:line_text, l:regex)
+
+ if !empty(l:prefix)
+ return l:prefix
+ endif
+
+ " Check LSP trigger characters for active connections on this buffer.
+ let l:triggers = ale#lsp#GetAllCompletionTriggerCharactersForBuffer(bufnr(''))
+
+ for l:char in l:triggers
+ if l:line_text[-len(l:char):] is# l:char
+ return l:char
+ endif
+ endfor
+
+ return ''
endfunction
-function! ale#completion#GetTriggerCharacter(filetype, prefix) abort
+function! ale#completion#GetTriggerCharacter(filetype, prefix, ...) abort
if empty(a:prefix)
return ''
endif
- let l:char_list = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
+ let l:conn_id = get(a:, 1, '')
+ let l:char_list = s:GetTriggerCharacters(a:filetype, l:conn_id)
if index(l:char_list, a:prefix) >= 0
return a:prefix
@@ -204,7 +235,8 @@ function! ale#completion#Filter(
if empty(a:prefix)
let l:filtered_suggestions = a:suggestions
else
- let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
+ let l:conn_id = get(get(b:, 'ale_completion_info', {}), 'conn_id', '')
+ let l:triggers = s:GetTriggerCharacters(a:filetype, l:conn_id)
" For completing...
" foo.
@@ -805,7 +837,7 @@ function! s:OnReady(linter, lsp_details) abort
\ l:buffer,
\ b:ale_completion_info.line,
\ b:ale_completion_info.column,
- \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
+ \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix, l:id),
\)
endif
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 7103879ba..5b961d30a 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -1010,3 +1010,33 @@ function! ale#lsp#HasCapability(conn_id, capability) abort
return l:conn.capabilities[a:capability]
endfunction
+
+" Get the completion trigger characters for a connection.
+function! ale#lsp#GetCompletionTriggerCharacters(conn_id) abort
+ let l:conn = get(s:connections, a:conn_id, {})
+
+ if empty(l:conn)
+ return []
+ endif
+
+ return get(l:conn.capabilities, 'completion_trigger_characters', [])
+endfunction
+
+" Get all completion trigger characters from LSPs active for a buffer.
+function! ale#lsp#GetAllCompletionTriggerCharactersForBuffer(buffer) abort
+ let l:all_triggers = []
+
+ for l:conn in values(s:connections)
+ if has_key(l:conn.open_documents, a:buffer)
+ let l:triggers = get(l:conn.capabilities, 'completion_trigger_characters', [])
+
+ for l:char in l:triggers
+ if index(l:all_triggers, l:char) < 0
+ call add(l:all_triggers, l:char)
+ endif
+ endfor
+ endif
+ endfor
+
+ return l:all_triggers
+endfunction
diff --git a/test/completion/test_completion_filtering.vader b/test/completion/test_completion_filtering.vader
index 172203a48..de73787d4 100644
--- a/test/completion/test_completion_filtering.vader
+++ b/test/completion/test_completion_filtering.vader
@@ -140,3 +140,61 @@ Execute(Filtering should respect filetype triggers):
AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0)
AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '.', 0)
AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '::', 0)
+
+Execute(GetTriggerCharacter should return trigger characters from hardcoded map):
+ AssertEqual '.', ale#completion#GetTriggerCharacter('python', '.')
+ AssertEqual '::', ale#completion#GetTriggerCharacter('rust', '::')
+ AssertEqual '->', ale#completion#GetTriggerCharacter('c', '->')
+ AssertEqual '', ale#completion#GetTriggerCharacter('python', '@')
+
+Execute(GetTriggerCharacter should return empty for empty prefix):
+ AssertEqual '', ale#completion#GetTriggerCharacter('python', '')
+
+Execute(GetTriggerCharacter should use LSP triggers when conn_id provided):
+ call ale#lsp#Register('test-lsp', '/project', '', {})
+ let g:conn_id = 'test-lsp:/project'
+ call ale#lsp#UpdateCapabilities(g:conn_id, {
+ \ 'completionProvider': {'triggerCharacters': ['@', '#']},
+ \})
+
+ " '@' is in LSP triggers
+ AssertEqual '@', ale#completion#GetTriggerCharacter('python', '@', g:conn_id)
+ " '.' is NOT in LSP triggers (should not match even though it's in hardcoded)
+ AssertEqual '', ale#completion#GetTriggerCharacter('python', '.', g:conn_id)
+ " '#' is in LSP triggers
+ AssertEqual '#', ale#completion#GetTriggerCharacter('python', '#', g:conn_id)
+
+ call ale#lsp#RemoveConnectionWithID(g:conn_id)
+ unlet g:conn_id
+
+Execute(GetTriggerCharacter should fall back to hardcoded when no LSP triggers):
+ call ale#lsp#Register('test-lsp-empty', '/project', '', {})
+ let g:conn_id = 'test-lsp-empty:/project'
+ call ale#lsp#UpdateCapabilities(g:conn_id, {})
+
+ " Falls back to hardcoded map
+ AssertEqual '.', ale#completion#GetTriggerCharacter('python', '.', g:conn_id)
+ AssertEqual '', ale#completion#GetTriggerCharacter('python', '@', g:conn_id)
+
+ call ale#lsp#RemoveConnectionWithID(g:conn_id)
+ unlet g:conn_id
+
+Execute(Filtering should use LSP trigger characters):
+ call ale#lsp#Register('test-lsp-filter', '/project', '', {})
+ let g:conn_id = 'test-lsp-filter:/project'
+ call ale#lsp#UpdateCapabilities(g:conn_id, {
+ \ 'completionProvider': {'triggerCharacters': ['@']},
+ \})
+
+ " Set up completion info with conn_id
+ let b:ale_completion_info = {'conn_id': g:conn_id}
+ let b:suggestions = [{'word': 'foo'}, {'word': 'bar'}]
+
+ " '@' is LSP trigger - should return all suggestions
+ AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'python', b:suggestions, '@', 0)
+ " '.' is NOT LSP trigger - should filter
+ AssertEqual [], ale#completion#Filter(bufnr(''), 'python', b:suggestions, '.', 0)
+
+ unlet b:ale_completion_info
+ call ale#lsp#RemoveConnectionWithID(g:conn_id)
+ unlet g:conn_id
diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader
index 2d1ffbe67..05392275b 100644
--- a/test/lsp/test_other_initialize_message_handling.vader
+++ b/test/lsp/test_other_initialize_message_handling.vader
@@ -343,3 +343,51 @@ Execute(Results that are not dictionaries should be handled correctly):
\ 'result': v:null,
\})
AssertEqual [], g:message_list
+
+Execute(GetCompletionTriggerCharacters should return stored characters):
+ call ale#lsp#HandleInitResponse(b:conn, {
+ \ 'jsonrpc': '2.0',
+ \ 'id': 1,
+ \ 'result': {
+ \ 'capabilities': {
+ \ 'completionProvider': {
+ \ 'triggerCharacters': ['@', '#', '.'],
+ \ },
+ \ },
+ \ },
+ \})
+
+ AssertEqual ['@', '#', '.'], ale#lsp#GetCompletionTriggerCharacters(b:conn.id)
+
+Execute(GetCompletionTriggerCharacters should return empty for missing connection):
+ AssertEqual [], ale#lsp#GetCompletionTriggerCharacters('nonexistent-connection')
+
+Execute(GetCompletionTriggerCharacters should return empty when no triggers):
+ call ale#lsp#HandleInitResponse(b:conn, {
+ \ 'jsonrpc': '2.0',
+ \ 'id': 1,
+ \ 'result': {
+ \ 'capabilities': {},
+ \ },
+ \})
+
+ AssertEqual [], ale#lsp#GetCompletionTriggerCharacters(b:conn.id)
+
+Execute(GetAllCompletionTriggerCharactersForBuffer should return triggers for open buffers):
+ call ale#lsp#HandleInitResponse(b:conn, {
+ \ 'jsonrpc': '2.0',
+ \ 'id': 1,
+ \ 'result': {
+ \ 'capabilities': {
+ \ 'completionProvider': {
+ \ 'triggerCharacters': ['>', '$'],
+ \ },
+ \ },
+ \ },
+ \})
+
+ call ale#lsp#MarkDocumentAsOpen(b:conn.id, 1)
+ AssertEqual sort(['>', '$']), sort(ale#lsp#GetAllCompletionTriggerCharactersForBuffer(1))
+
+Execute(GetAllCompletionTriggerCharactersForBuffer should return empty for unknown buffer):
+ AssertEqual [], ale#lsp#GetAllCompletionTriggerCharactersForBuffer(99999)