Supply language_id values to Neovim LSP API

Change logic so ALE's LSP implementation and the Neovim LSP client
retrieve the language_id for language clients at roughly the same time
via the same means. This makes ALE inform the language server what the
language for the language is for clients.
This commit is contained in:
w0rp
2025-03-18 14:05:58 +00:00
parent ee975196ff
commit 964e0a2e39
19 changed files with 65 additions and 41 deletions

View File

@@ -205,7 +205,10 @@ endfunction
function! ale#assert#LSPLanguage(expected_language) abort function! ale#assert#LSPLanguage(expected_language) abort
let l:buffer = bufnr('') let l:buffer = bufnr('')
let l:linter = s:GetLinter() let l:linter = s:GetLinter()
let l:language = ale#linter#GetLanguage(l:buffer, l:linter) let l:Language = l:linter.language
let l:language = type(l:Language) is v:t_func
\ ? l:Language(l:buffer)
\ : l:Language
AssertEqual a:expected_language, l:language AssertEqual a:expected_language, l:language
endfunction endfunction

View File

@@ -446,9 +446,3 @@ function! ale#linter#GetAddress(buffer, linter) abort
return type(l:Address) is v:t_func ? l:Address(a:buffer) : l:Address return type(l:Address) is v:t_func ? l:Address(a:buffer) : l:Address
endfunction endfunction
function! ale#linter#GetLanguage(buffer, linter) abort
let l:Language = a:linter.language
return type(l:Language) is v:t_func ? l:Language(a:buffer) : l:Language
endfunction

View File

@@ -5,9 +5,10 @@
let s:connections = get(s:, 'connections', {}) let s:connections = get(s:, 'connections', {})
let g:ale_lsp_next_message_id = 1 let g:ale_lsp_next_message_id = 1
" Given an id, which can be an executable or address, and a project path, " Given an id, which can be an executable or address, a project path,
" and a language string or (bufnr) -> string function
" create a new connection if needed. Return a unique ID for the connection. " create a new connection if needed. Return a unique ID for the connection.
function! ale#lsp#Register(executable_or_address, project, init_options) abort function! ale#lsp#Register(executable_or_address, project, language, init_options) abort
let l:conn_id = a:executable_or_address . ':' . a:project let l:conn_id = a:executable_or_address . ':' . a:project
if !has_key(s:connections, l:conn_id) if !has_key(s:connections, l:conn_id)
@@ -28,6 +29,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
\ 'is_tsserver': 0, \ 'is_tsserver': 0,
\ 'data': '', \ 'data': '',
\ 'root': a:project, \ 'root': a:project,
\ 'language': a:language,
\ 'open_documents': {}, \ 'open_documents': {},
\ 'initialized': 0, \ 'initialized': 0,
\ 'init_request_id': 0, \ 'init_request_id': 0,
@@ -677,9 +679,20 @@ function! ale#lsp#Send(conn_id, message) abort
return l:id == 0 ? -1 : l:id return l:id == 0 ? -1 : l:id
endfunction endfunction
function! ale#lsp#GetLanguage(conn_id, buffer) abort
let l:conn = get(s:connections, a:conn_id, {})
let l:Language = get(l:conn, 'language')
if empty(l:Language)
return getbufvar(a:buffer, '&filetype')
endif
return type(l:Language) is v:t_func ? l:Language(a:buffer) : l:Language
endfunction
" Notify LSP servers or tsserver if a document is opened, if needed. " Notify LSP servers or tsserver if a document is opened, if needed.
" If a document is opened, 1 will be returned, otherwise 0 will be returned. " If a document is opened, 1 will be returned, otherwise 0 will be returned.
function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort function! ale#lsp#OpenDocument(conn_id, buffer) abort
let l:conn = get(s:connections, a:conn_id, {}) let l:conn = get(s:connections, a:conn_id, {})
let l:opened = 0 let l:opened = 0
@@ -693,7 +706,8 @@ function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort
\ 'client_id': l:conn.client_id, \ 'client_id': l:conn.client_id,
\}) \})
else else
let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id) let l:language_id = ale#lsp#GetLanguage(a:conn_id, a:buffer)
let l:message = ale#lsp#message#DidOpen(a:buffer, l:language_id)
call ale#lsp#Send(a:conn_id, l:message) call ale#lsp#Send(a:conn_id, l:message)
endif endif

View File

@@ -306,11 +306,10 @@ function! ale#lsp_linter#OnInit(linter, details, Callback) abort
let l:command = a:details.command let l:command = a:details.command
let l:config = ale#lsp_linter#GetConfig(l:buffer, a:linter) let l:config = ale#lsp_linter#GetConfig(l:buffer, a:linter)
let l:language_id = ale#linter#GetLanguage(l:buffer, a:linter)
call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config) call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config)
if ale#lsp#OpenDocument(l:conn_id, l:buffer, l:language_id) if ale#lsp#OpenDocument(l:conn_id, l:buffer)
if g:ale_history_enabled && !empty(l:command) if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(l:buffer, 'started', l:conn_id, l:command) call ale#history#Add(l:buffer, 'started', l:conn_id, l:command)
endif endif
@@ -357,11 +356,21 @@ function! s:StartLSP(options, address, executable, command) abort
let l:init_options = ale#lsp_linter#GetOptions(l:buffer, l:linter) let l:init_options = ale#lsp_linter#GetOptions(l:buffer, l:linter)
if l:linter.lsp is# 'socket' if l:linter.lsp is# 'socket'
let l:conn_id = ale#lsp#Register(a:address, l:root, l:init_options) let l:conn_id = ale#lsp#Register(
\ a:address,
\ l:root,
\ l:linter.language,
\ l:init_options
\)
let l:ready = ale#lsp#ConnectToAddress(l:conn_id, a:address) let l:ready = ale#lsp#ConnectToAddress(l:conn_id, a:address)
let l:command = '' let l:command = ''
else else
let l:conn_id = ale#lsp#Register(a:executable, l:root, l:init_options) let l:conn_id = ale#lsp#Register(
\ a:executable,
\ l:root,
\ l:linter.language,
\ l:init_options
\)
" tsserver behaves differently, so tell the LSP API that it is tsserver. " tsserver behaves differently, so tell the LSP API that it is tsserver.
if l:linter.lsp is# 'tsserver' if l:linter.lsp is# 'tsserver'

View File

@@ -39,6 +39,10 @@ module.start = function(config)
end, 0) end, 0)
end end
config.get_language_id = function(bufnr, _)
return vim.fn["ale#lsp#GetLanguage"](config.name, bufnr)
end
return vim.lsp.start(config, { return vim.lsp.start(config, {
attach = false, attach = false,
silent = true, silent = true,

View File

@@ -19,7 +19,7 @@ Before:
let g:init_callback_list = [] let g:init_callback_list = []
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
let l:details = { let l:details = {

View File

@@ -37,24 +37,24 @@ After:
runtime autoload/ale/lsp.vim runtime autoload/ale/lsp.vim
Execute(No errors should be thrown if the connection is not initialized): Execute(No errors should be thrown if the connection is not initialized):
call ale#lsp#Register('command', '/foo', {}) call ale#lsp#Register('command', '/foo', '', {})
call MarkDocumentOpened() call MarkDocumentOpened()
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
AssertEqual [], g:message_list AssertEqual [], g:message_list
Execute(No messages should be sent if the document wasn't opened): Execute(No messages should be sent if the document wasn't opened):
call ale#lsp#Register('command', '/foo', {}) call ale#lsp#Register('command', '/foo', '', {})
call MarkAllConnectionsInitialized() call MarkAllConnectionsInitialized()
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
AssertEqual [], g:message_list AssertEqual [], g:message_list
Execute(A message should be sent if the document was opened): Execute(A message should be sent if the document was opened):
call ale#lsp#Register('command', '/foo', {}) call ale#lsp#Register('command', '/foo', 'lang', {})
call MarkAllConnectionsInitialized() call MarkAllConnectionsInitialized()
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') call ale#lsp#OpenDocument('command:/foo', bufnr(''))
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
" We should only send the message once. " We should only send the message once.
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
@@ -78,10 +78,10 @@ Execute(A message should be sent if the document was opened):
\ g:message_list \ g:message_list
Execute(A message should be sent if the document was opened for tsserver): Execute(A message should be sent if the document was opened for tsserver):
call ale#lsp#Register('command', '/foo', {}) call ale#lsp#Register('command', '/foo', 'lang', {})
call ale#lsp#MarkConnectionAsTsserver('command:/foo') call ale#lsp#MarkConnectionAsTsserver('command:/foo')
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') call ale#lsp#OpenDocument('command:/foo', bufnr(''))
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
" We should only send the message once. " We should only send the message once.
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
@@ -94,12 +94,12 @@ Execute(A message should be sent if the document was opened for tsserver):
\ g:message_list \ g:message_list
Execute(Re-opening and closing the documents should work): Execute(Re-opening and closing the documents should work):
call ale#lsp#Register('command', '/foo', {}) call ale#lsp#Register('command', '/foo', 'lang', {})
call MarkAllConnectionsInitialized() call MarkAllConnectionsInitialized()
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') call ale#lsp#OpenDocument('command:/foo', bufnr(''))
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') call ale#lsp#OpenDocument('command:/foo', bufnr(''))
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
AssertEqual AssertEqual
@@ -134,12 +134,12 @@ Execute(Re-opening and closing the documents should work):
\ g:message_list \ g:message_list
Execute(Messages for closing documents should be sent to each server): Execute(Messages for closing documents should be sent to each server):
call ale#lsp#Register('command', '/foo', {}) call ale#lsp#Register('command', '/foo', 'lang', {})
call ale#lsp#Register('command', '/bar', {}) call ale#lsp#Register('command', '/bar', 'lang', {})
call MarkAllConnectionsInitialized() call MarkAllConnectionsInitialized()
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang') call ale#lsp#OpenDocument('command:/foo', bufnr(''))
call ale#lsp#OpenDocument('command:/bar', bufnr(''), 'lang') call ale#lsp#OpenDocument('command:/bar', bufnr(''))
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))
" We should only send the message once. " We should only send the message once.
call ale#engine#Cleanup(bufnr('')) call ale#engine#Cleanup(bufnr(''))

View File

@@ -39,7 +39,7 @@ Before:
let g:ale_linters = {'foobar': ['dummy_linter']} let g:ale_linters = {'foobar': ['dummy_linter']}
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', 'foobar', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
let l:details = { let l:details = {
\ 'command': 'foobar', \ 'command': 'foobar',

View File

@@ -39,7 +39,7 @@ Before:
" Replace the StartLSP function to mock an LSP linter " Replace the StartLSP function to mock an LSP linter
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register(g:executable_or_address, g:project_root, {}) let g:conn_id = ale#lsp#Register(g:executable_or_address, g:project_root, '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'})) call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'}))

View File

@@ -441,7 +441,7 @@ Execute(Deferred addresses should be handled correctly):
call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '', bufnr('')) call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '', bufnr(''))
Execute(Servers that have crashed should be restarted): Execute(Servers that have crashed should be restarted):
call ale#lsp#Register('foo', '/foo/bar', {}) call ale#lsp#Register('foo', '/foo/bar', '', {})
call extend(ale#lsp#GetConnections()['foo:/foo/bar'], {'initialized': 1}) call extend(ale#lsp#GetConnections()['foo:/foo/bar'], {'initialized': 1})
" Starting the program again should reset initialized to `0`. " Starting the program again should reset initialized to `0`.

View File

@@ -4,7 +4,7 @@ Before:
let g:message_list = [] let g:message_list = []
" Register a fake connection and get it for tests. " Register a fake connection and get it for tests.
call ale#lsp#Register('ale-fake-lsp-server', '/code', {}) call ale#lsp#Register('ale-fake-lsp-server', '/code', '', {})
let b:conn = ale#lsp#GetConnections()['ale-fake-lsp-server:/code'] let b:conn = ale#lsp#GetConnections()['ale-fake-lsp-server:/code']
function! ale#lsp#Send(conn_id, message) abort function! ale#lsp#Send(conn_id, message) abort

View File

@@ -1,7 +1,7 @@
Before: Before:
runtime autoload/ale/lsp.vim runtime autoload/ale/lsp.vim
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
" Stub out this function, so we test updating configs. " Stub out this function, so we test updating configs.
function! ale#lsp#Send(conn_id, message) abort function! ale#lsp#Send(conn_id, message) abort

View File

@@ -23,7 +23,7 @@ Before:
runtime autoload/ale/code_action.vim runtime autoload/ale/code_action.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'

View File

@@ -20,7 +20,7 @@ Before:
runtime autoload/ale/code_action.vim runtime autoload/ale/code_action.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'

View File

@@ -22,7 +22,7 @@ Before:
runtime autoload/ale/preview.vim runtime autoload/ale/preview.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'

View File

@@ -19,7 +19,7 @@ Before:
runtime autoload/ale/util.vim runtime autoload/ale/util.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'

View File

@@ -20,7 +20,7 @@ Before:
runtime autoload/ale/code_action.vim runtime autoload/ale/code_action.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'

View File

@@ -20,7 +20,7 @@ Before:
runtime autoload/ale/code_action.vim runtime autoload/ale/code_action.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'

View File

@@ -18,7 +18,7 @@ Before:
runtime autoload/ale/preview.vim runtime autoload/ale/preview.vim
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
let l:details = { let l:details = {
\ 'buffer': a:buffer, \ 'buffer': a:buffer,