#517 Add more code LSP support which makes the tssserver linter behave more like the LSP linters
This commit is contained in:
@@ -203,41 +203,29 @@ function! s:HandleTSServerLSPResponse(response) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:GetCompletionsForTSServer(linter) abort
|
||||
function! s:GetLSPCompletions(linter) abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:executable = ale#linter#GetExecutable(l:buffer, a:linter)
|
||||
let l:command = ale#job#PrepareCommand(
|
||||
\ ale#linter#GetCommand(l:buffer, a:linter),
|
||||
\)
|
||||
let l:id = ale#lsp#StartProgram(
|
||||
\ l:executable,
|
||||
\ l:command,
|
||||
let l:lsp_details = ale#linter#StartLSP(
|
||||
\ l:buffer,
|
||||
\ a:linter,
|
||||
\ function('s:HandleTSServerLSPResponse'),
|
||||
\)
|
||||
|
||||
if !l:id
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(l:buffer, 'failed', l:id, l:command)
|
||||
endif
|
||||
if empty(l:lsp_details)
|
||||
return 0
|
||||
endif
|
||||
|
||||
if ale#lsp#OpenTSServerDocumentIfNeeded(l:id, l:buffer)
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(l:buffer, 'started', l:id, l:command)
|
||||
endif
|
||||
endif
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:command = l:lsp_details.command
|
||||
let l:root = l:lsp_details.project_root
|
||||
|
||||
call ale#lsp#Send(l:id, ale#lsp#tsserver_message#Change(l:buffer))
|
||||
|
||||
let l:request_id = ale#lsp#Send(
|
||||
\ l:id,
|
||||
\ ale#lsp#tsserver_message#Completions(
|
||||
\ l:buffer,
|
||||
\ b:ale_completion_info.line,
|
||||
\ b:ale_completion_info.column,
|
||||
\ b:ale_completion_info.prefix,
|
||||
\ ),
|
||||
let l:message = ale#lsp#tsserver_message#Completions(
|
||||
\ l:buffer,
|
||||
\ b:ale_completion_info.line,
|
||||
\ b:ale_completion_info.column,
|
||||
\ b:ale_completion_info.prefix,
|
||||
\)
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
|
||||
|
||||
if l:request_id
|
||||
let b:ale_completion_info.conn_id = l:id
|
||||
@@ -268,7 +256,7 @@ function! ale#completion#GetCompletions() abort
|
||||
|
||||
for l:linter in ale#linter#Get(&filetype)
|
||||
if l:linter.lsp ==# 'tsserver'
|
||||
call s:GetCompletionsForTSServer(l:linter)
|
||||
call s:GetLSPCompletions(l:linter)
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
@@ -31,13 +31,15 @@ endfunction
|
||||
|
||||
function! ale#engine#InitBufferInfo(buffer) abort
|
||||
if !has_key(g:ale_buffer_info, a:buffer)
|
||||
" job_list will hold the list of jobs
|
||||
" job_list will hold the list of job IDs
|
||||
" active_linter_list will hold the list of active linter names
|
||||
" loclist holds the loclist items after all jobs have completed.
|
||||
" temporary_file_list holds temporary files to be cleaned up
|
||||
" temporary_directory_list holds temporary directories to be cleaned up
|
||||
" history holds a list of previously run commands for this buffer
|
||||
let g:ale_buffer_info[a:buffer] = {
|
||||
\ 'job_list': [],
|
||||
\ 'active_linter_list': [],
|
||||
\ 'loclist': [],
|
||||
\ 'temporary_file_list': [],
|
||||
\ 'temporary_directory_list': [],
|
||||
@@ -114,6 +116,16 @@ function! s:GatherOutput(job_id, line) abort
|
||||
endfunction
|
||||
|
||||
function! s:HandleLoclist(linter_name, buffer, loclist) abort
|
||||
let l:buffer_info = get(g:ale_buffer_info, a:buffer, {})
|
||||
|
||||
if empty(l:buffer_info)
|
||||
return
|
||||
endif
|
||||
|
||||
" Remove this linter from the list of active linters.
|
||||
" This may have already been done when the job exits.
|
||||
call filter(l:buffer_info.active_linter_list, 'v:val !=# a:linter_name')
|
||||
|
||||
" Make some adjustments to the loclists to fix common problems, and also
|
||||
" to set default values for loclist items.
|
||||
let l:linter_loclist = ale#engine#FixLocList(a:buffer, a:linter_name, a:loclist)
|
||||
@@ -154,6 +166,7 @@ function! s:HandleExit(job_id, exit_code) abort
|
||||
call ale#job#Stop(a:job_id)
|
||||
call remove(s:job_info_map, a:job_id)
|
||||
call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val !=# a:job_id')
|
||||
call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val !=# l:linter.name')
|
||||
|
||||
" Stop here if we land in the handle for a job completing if we're in
|
||||
" a sandbox.
|
||||
@@ -180,29 +193,32 @@ function! s:HandleExit(job_id, exit_code) abort
|
||||
call s:HandleLoclist(l:linter.name, l:buffer, l:loclist)
|
||||
endfunction
|
||||
|
||||
function! s:HandleLSPResponse(response) abort
|
||||
let l:is_diag_response = get(a:response, 'type', '') ==# 'event'
|
||||
\ && get(a:response, 'event', '') ==# 'semanticDiag'
|
||||
function! s:HandleLSPDiagnostics(response) abort
|
||||
let l:filename = ale#path#FromURI(a:response.params.uri)
|
||||
let l:buffer = bufnr(l:filename)
|
||||
let l:loclist = ale#lsp#response#ReadDiagnostics(a:response)
|
||||
|
||||
if !l:is_diag_response
|
||||
return
|
||||
endif
|
||||
call s:HandleLoclist('langserver', l:buffer, l:loclist)
|
||||
endfunction
|
||||
|
||||
function! s:HandleTSServerDiagnostics(response) abort
|
||||
let l:buffer = bufnr(a:response.body.file)
|
||||
|
||||
let l:info = get(g:ale_buffer_info, l:buffer, {})
|
||||
|
||||
if empty(l:info)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:info.waiting_for_tsserver = 0
|
||||
|
||||
let l:loclist = ale#lsp#response#ReadTSServerDiagnostics(a:response)
|
||||
|
||||
call s:HandleLoclist('tsserver', l:buffer, l:loclist)
|
||||
endfunction
|
||||
|
||||
function! s:HandleLSPResponse(response) abort
|
||||
let l:method = get(a:response, 'method', '')
|
||||
|
||||
if l:method ==# 'textDocument/publishDiagnostics'
|
||||
call s:HandleLSPDiagnostics(a:response)
|
||||
elseif get(a:response, 'type', '') ==# 'event'
|
||||
\&& get(a:response, 'event', '') ==# 'semanticDiag'
|
||||
call s:HandleTSServerDiagnostics(a:response)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#engine#SetResults(buffer, loclist) abort
|
||||
let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer)
|
||||
|
||||
@@ -430,6 +446,7 @@ function! s:RunJob(options) abort
|
||||
if l:job_id
|
||||
" Add the job to the list of jobs, so we can track them.
|
||||
call add(g:ale_buffer_info[l:buffer].job_list, l:job_id)
|
||||
call add(g:ale_buffer_info[l:buffer].active_linter_list, l:linter.name)
|
||||
|
||||
let l:status = 'started'
|
||||
" Store the ID for the job in the map to read back again.
|
||||
@@ -555,41 +572,27 @@ function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort
|
||||
let l:info.job_list = l:new_job_list
|
||||
endfunction
|
||||
|
||||
function! s:CheckWithTSServer(buffer, linter, executable) abort
|
||||
let l:info = g:ale_buffer_info[a:buffer]
|
||||
|
||||
let l:command = ale#job#PrepareCommand(
|
||||
\ ale#linter#GetCommand(a:buffer, a:linter),
|
||||
\)
|
||||
let l:id = ale#lsp#StartProgram(
|
||||
\ a:executable,
|
||||
\ l:command,
|
||||
function! s:CheckWithLSP(buffer, linter) abort
|
||||
let l:lsp_details = ale#linter#StartLSP(
|
||||
\ a:buffer,
|
||||
\ a:linter,
|
||||
\ function('s:HandleLSPResponse'),
|
||||
\)
|
||||
|
||||
if !l:id
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(a:buffer, 'failed', l:id, l:command)
|
||||
endif
|
||||
|
||||
if empty(l:lsp_details)
|
||||
return 0
|
||||
endif
|
||||
|
||||
if ale#lsp#OpenTSServerDocumentIfNeeded(l:id, a:buffer)
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(a:buffer, 'started', l:id, l:command)
|
||||
endif
|
||||
endif
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:root = l:lsp_details.project_root
|
||||
|
||||
call ale#lsp#Send(l:id, ale#lsp#tsserver_message#Change(a:buffer))
|
||||
|
||||
let l:request_id = ale#lsp#Send(
|
||||
\ l:id,
|
||||
\ ale#lsp#tsserver_message#Geterr(a:buffer),
|
||||
\)
|
||||
let l:change_message = a:linter.lsp ==# 'tsserver'
|
||||
\ ? ale#lsp#tsserver_message#Geterr(a:buffer)
|
||||
\ : ale#lsp#message#DidChange(a:buffer)
|
||||
let l:request_id = ale#lsp#Send(l:id, l:change_message, l:root)
|
||||
|
||||
if l:request_id != 0
|
||||
let l:info.waiting_for_tsserver = 1
|
||||
call add(g:ale_buffer_info[a:buffer].active_linter_list, a:linter.name)
|
||||
endif
|
||||
|
||||
return l:request_id != 0
|
||||
@@ -614,15 +617,12 @@ endfunction
|
||||
"
|
||||
" Returns 1 if the linter was successfully run.
|
||||
function! s:RunLinter(buffer, linter) abort
|
||||
if empty(a:linter.lsp) || a:linter.lsp ==# 'tsserver'
|
||||
if !empty(a:linter.lsp) || a:linter.lsp ==# 'tsserver'
|
||||
return s:CheckWithLSP(a:buffer, a:linter)
|
||||
else
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
" Run this program if it can be executed.
|
||||
if s:IsExecutable(l:executable)
|
||||
if a:linter.lsp ==# 'tsserver'
|
||||
return s:CheckWithTSServer(a:buffer, a:linter, l:executable)
|
||||
endif
|
||||
|
||||
return s:InvokeChain(a:buffer, a:linter, 0, [])
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -199,6 +199,10 @@ function! ale#job#Start(command, options) abort
|
||||
let l:job_info = copy(a:options)
|
||||
let l:job_options = {}
|
||||
|
||||
if exists('*ch_logfile')
|
||||
call ch_logfile(expand('~/channel.log'), 'a')
|
||||
endif
|
||||
|
||||
if has('nvim')
|
||||
if has_key(a:options, 'out_cb')
|
||||
let l:job_options.on_stdout = function('s:NeoVimCallback')
|
||||
|
||||
@@ -62,6 +62,7 @@ function! ale#linter#PreProcess(linter) abort
|
||||
let l:needs_address = l:obj.lsp ==# 'socket'
|
||||
let l:needs_executable = l:obj.lsp !=# 'socket'
|
||||
let l:needs_command = l:obj.lsp !=# 'socket'
|
||||
let l:needs_lsp_details = !empty(l:obj.lsp)
|
||||
|
||||
if empty(l:obj.lsp)
|
||||
let l:obj.callback = get(a:linter, 'callback')
|
||||
@@ -176,6 +177,20 @@ function! ale#linter#PreProcess(linter) abort
|
||||
throw '`address_callback` must be defined for getting the LSP address'
|
||||
endif
|
||||
|
||||
if l:needs_lsp_details
|
||||
let l:obj.language_callback = get(a:linter, 'language_callback')
|
||||
|
||||
if !s:IsCallback(l:obj.language_callback)
|
||||
throw '`language_callback` must be a callback for LSP linters'
|
||||
endif
|
||||
|
||||
let l:obj.project_root_callback = get(a:linter, 'project_root_callback')
|
||||
|
||||
if !s:IsCallback(l:obj.project_root_callback)
|
||||
throw '`project_root_callback` must be a callback for LSP linters'
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout')
|
||||
|
||||
if type(l:obj.output_stream) != type('')
|
||||
@@ -346,3 +361,72 @@ function! ale#linter#GetCommand(buffer, linter) abort
|
||||
\ ? ale#util#GetFunction(a:linter.command_callback)(a:buffer)
|
||||
\ : a:linter.command
|
||||
endfunction
|
||||
|
||||
" Given a buffer and linter, get the address for connecting to the server.
|
||||
function! ale#linter#GetAddress(buffer, linter) abort
|
||||
return has_key(a:linter, 'address_callback')
|
||||
\ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer)
|
||||
\ : a:linter.address
|
||||
endfunction
|
||||
|
||||
" Given a buffer, an LSP linter, and a callback to register for handling
|
||||
" messages, start up an LSP linter and get ready to receive errors or
|
||||
" completions.
|
||||
function! ale#linter#StartLSP(buffer, linter, callback) abort
|
||||
let l:command = ''
|
||||
let l:address = ''
|
||||
let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
|
||||
|
||||
if a:linter.lsp ==# 'socket'
|
||||
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
|
||||
let l:conn_id = ale#lsp#ConnectToAddress(
|
||||
\ l:address,
|
||||
\ l:root,
|
||||
\ a:callback,
|
||||
\)
|
||||
else
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
if !executable(l:executable)
|
||||
return {}
|
||||
endif
|
||||
|
||||
let l:command = ale#job#PrepareCommand(
|
||||
\ ale#linter#GetCommand(a:buffer, a:linter),
|
||||
\)
|
||||
let l:conn_id = ale#lsp#StartProgram(
|
||||
\ l:executable,
|
||||
\ l:command,
|
||||
\ l:root,
|
||||
\ a:callback,
|
||||
\)
|
||||
endif
|
||||
|
||||
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
|
||||
|
||||
if !l:conn_id
|
||||
if g:ale_history_enabled && !empty(l:command)
|
||||
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
|
||||
endif
|
||||
|
||||
return {}
|
||||
endif
|
||||
|
||||
if ale#lsp#OpenDocumentIfNeeded(l:conn_id, a:buffer, l:root, l:language_id)
|
||||
if g:ale_history_enabled && !empty(l:command)
|
||||
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
|
||||
endif
|
||||
endif
|
||||
|
||||
" The change message needs to be sent for tsserver before doing anything.
|
||||
if a:linter.lsp ==# 'tsserver'
|
||||
call ale#lsp#Send(l:conn_id, ale#lsp#tsserver_message#Change(a:buffer))
|
||||
endif
|
||||
|
||||
return {
|
||||
\ 'connection_id': l:conn_id,
|
||||
\ 'command': l:command,
|
||||
\ 'project_root': l:root,
|
||||
\ 'language_id': l:language_id,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
@@ -11,10 +11,13 @@ function! s:NewConnection() abort
|
||||
" data: The message data received so far.
|
||||
" executable: An executable only set for program connections.
|
||||
" open_documents: A list of buffers we told the server we opened.
|
||||
" callback_list: A list of callbacks for handling LSP responses.
|
||||
let l:conn = {
|
||||
\ 'id': '',
|
||||
\ 'data': '',
|
||||
\ 'projects': {},
|
||||
\ 'open_documents': [],
|
||||
\ 'callback_list': [],
|
||||
\}
|
||||
|
||||
call add(s:connections, l:conn)
|
||||
@@ -141,6 +144,35 @@ function! ale#lsp#ReadMessageData(data) abort
|
||||
return [l:remainder, l:response_list]
|
||||
endfunction
|
||||
|
||||
function! s:FindProjectWithInitRequestID(conn, init_request_id) abort
|
||||
for l:project_root in keys(a:conn.projects)
|
||||
let l:project = a:conn.projects[l:project_root]
|
||||
|
||||
if l:project.init_request_id == a:init_request_id
|
||||
return l:project
|
||||
endif
|
||||
endfor
|
||||
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
function! s:HandleInitializeResponse(conn, response) abort
|
||||
let l:request_id = a:response.request_id
|
||||
let l:project = s:FindProjectWithInitRequestID(a:conn, l:request_id)
|
||||
|
||||
if empty(l:project)
|
||||
return
|
||||
endif
|
||||
|
||||
" After the server starts, send messages we had queued previously.
|
||||
for l:message_data in l:project.message_queue
|
||||
call s:SendMessageData(a:conn, l:message_data)
|
||||
endfor
|
||||
|
||||
" Remove the messages now.
|
||||
let a:conn.message_queue = []
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HandleMessage(conn, message) abort
|
||||
let a:conn.data .= a:message
|
||||
|
||||
@@ -149,8 +181,13 @@ function! ale#lsp#HandleMessage(conn, message) abort
|
||||
|
||||
" Call our callbacks.
|
||||
for l:response in l:response_list
|
||||
if has_key(a:conn, 'callback')
|
||||
call ale#util#GetFunction(a:conn.callback)(l:response)
|
||||
if get(l:response, 'method', '') ==# 'initialize'
|
||||
call s:HandleInitializeResponse(a:conn, l:response)
|
||||
else
|
||||
" Call all of the registered handlers with the response.
|
||||
for l:Callback in a:conn.callback_list
|
||||
call ale#util#GetFunction(l:Callback)(l:response)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
@@ -169,11 +206,22 @@ function! s:HandleCommandMessage(job_id, message) abort
|
||||
call ale#lsp#HandleMessage(l:conn, a:message)
|
||||
endfunction
|
||||
|
||||
function! s:RegisterProject(conn, project_root) abort
|
||||
if !has_key(a:conn, a:project_root)
|
||||
" Tools without project roots are ready right away, like tsserver.
|
||||
let a:conn.projects[a:project_root] = {
|
||||
\ 'initialized': empty(a:project_root),
|
||||
\ 'init_messsage_id': 0,
|
||||
\ 'message_queue': [],
|
||||
\}
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Start a program for LSP servers which run with executables.
|
||||
"
|
||||
" The job ID will be returned for for the program if it ran, otherwise
|
||||
" 0 will be returned.
|
||||
function! ale#lsp#StartProgram(executable, command, callback) abort
|
||||
function! ale#lsp#StartProgram(executable, command, project_root, callback) abort
|
||||
if !executable(a:executable)
|
||||
return 0
|
||||
endif
|
||||
@@ -199,13 +247,15 @@ function! ale#lsp#StartProgram(executable, command, callback) abort
|
||||
endif
|
||||
|
||||
let l:conn.id = l:job_id
|
||||
let l:conn.callback = a:callback
|
||||
" Add the callback to the List if it's not there already.
|
||||
call uniq(sort(add(l:conn.callback_list, a:callback)))
|
||||
call s:RegisterProject(l:conn, a:project_root)
|
||||
|
||||
return l:job_id
|
||||
endfunction
|
||||
|
||||
" Connect to an address and set up a callback for handling responses.
|
||||
function! ale#lsp#ConnectToAddress(address, callback) abort
|
||||
function! ale#lsp#ConnectToAddress(address, project_root, callback) abort
|
||||
let l:conn = s:FindConnection('id', a:address)
|
||||
" Get the current connection or a new one.
|
||||
let l:conn = !empty(l:conn) ? l:conn : s:NewConnection()
|
||||
@@ -223,7 +273,22 @@ function! ale#lsp#ConnectToAddress(address, callback) abort
|
||||
endif
|
||||
|
||||
let l:conn.id = a:address
|
||||
let l:conn.callback = a:callback
|
||||
" Add the callback to the List if it's not there already.
|
||||
call uniq(sort(add(l:conn.callback_list, a:callback)))
|
||||
call s:RegisterProject(l:conn, a:project_root)
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:SendMessageData(conn, data) abort
|
||||
if has_key(a:conn, 'executable')
|
||||
call ale#job#SendRaw(a:conn.id, a:data)
|
||||
elseif has_key(a:conn, 'channel') && ch_status(a:conn.channnel) ==# 'open'
|
||||
" Send the message to the server
|
||||
call ch_sendraw(a:conn.channel, a:data)
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
@@ -234,28 +299,60 @@ endfunction
|
||||
" Returns -1 when a message is sent, but no response is expected
|
||||
" 0 when the message is not sent and
|
||||
" >= 1 with the message ID when a response is expected.
|
||||
function! ale#lsp#Send(conn_id, message) abort
|
||||
function! ale#lsp#Send(conn_id, message, ...) abort
|
||||
let l:project_root = get(a:000, 0, '')
|
||||
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
|
||||
if empty(l:conn)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:project = get(l:conn.projects, l:project_root, {})
|
||||
|
||||
if empty(l:project)
|
||||
return 0
|
||||
endif
|
||||
|
||||
" If we haven't initialized the server yet, then send the message for it.
|
||||
if !l:project.initialized
|
||||
" Only send the init message once.
|
||||
if !l:project.init_request_id
|
||||
let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
|
||||
\ ale#lsp#message#Initialize(l:conn.project_root),
|
||||
\)
|
||||
|
||||
let l:project.init_request_id = l:init_id
|
||||
|
||||
call s:SendMessageData(l:conn, l:init_data)
|
||||
endif
|
||||
endif
|
||||
|
||||
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
|
||||
|
||||
if has_key(l:conn, 'executable')
|
||||
call ale#job#SendRaw(l:conn.id, l:data)
|
||||
elseif has_key(l:conn, 'channel') && ch_status(l:conn.channnel) ==# 'open'
|
||||
" Send the message to the server
|
||||
call ch_sendraw(l:conn.channel, l:data)
|
||||
if l:project.initialized
|
||||
" Send the message now.
|
||||
call s:SendMessageData(l:conn, l:data)
|
||||
else
|
||||
return 0
|
||||
" Add the message we wanted to send to a List to send later.
|
||||
call add(l:project.message_queue, l:data)
|
||||
endif
|
||||
|
||||
return l:id == 0 ? -1 : l:id
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#OpenTSServerDocumentIfNeeded(conn_id, buffer) abort
|
||||
function! ale#lsp#OpenDocumentIfNeeded(conn_id, buffer, project_root, language_id) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
let l:opened = 0
|
||||
|
||||
if !empty(l:conn) && index(l:conn.open_documents, a:buffer) < 0
|
||||
call ale#lsp#Send(a:conn_id, ale#lsp#tsserver_message#Open(a:buffer))
|
||||
if empty(a:language_id)
|
||||
let l:message = ale#lsp#tsserver_message#Open(a:buffer)
|
||||
else
|
||||
let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id)
|
||||
endif
|
||||
|
||||
call ale#lsp#Send(a:conn_id, l:message, a:project_root)
|
||||
call add(l:conn.open_documents, a:buffer)
|
||||
let l:opened = 1
|
||||
endif
|
||||
|
||||
@@ -3,12 +3,32 @@
|
||||
"
|
||||
" Messages in this movie will be returned in the format
|
||||
" [is_notification, method_name, params?]
|
||||
let g:ale_lsp_next_version_id = 1
|
||||
|
||||
function! ale#lsp#message#Initialize(root_uri) abort
|
||||
" The LSP protocols demands that we send every change to a document, including
|
||||
" undo, with incrementing version numbers, so we'll just use one incrementing
|
||||
" ID for everything.
|
||||
function! ale#lsp#message#GetNextVersionID() abort
|
||||
" Use the current ID
|
||||
let l:id = g:ale_lsp_next_version_id
|
||||
|
||||
" Increment the ID variable.
|
||||
let g:ale_lsp_next_version_id += 1
|
||||
|
||||
" When the ID overflows, reset it to 1. By the time we hit the initial ID
|
||||
" again, the messages will be long gone.
|
||||
if g:ale_lsp_next_version_id < 1
|
||||
let g:ale_lsp_next_version_id = 1
|
||||
endif
|
||||
|
||||
return l:id
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#Initialize(root_path) abort
|
||||
" TODO: Define needed capabilities.
|
||||
return [0, 'initialize', {
|
||||
\ 'processId': getpid(),
|
||||
\ 'rootUri': a:root_uri,
|
||||
\ 'rootPath': a:root_path,
|
||||
\ 'capabilities': {},
|
||||
\}]
|
||||
endfunction
|
||||
@@ -25,40 +45,44 @@ function! ale#lsp#message#Exit() abort
|
||||
return [1, 'exit']
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#DidOpen(uri, language_id, version, text) abort
|
||||
function! ale#lsp#message#DidOpen(buffer, language_id) abort
|
||||
let l:lines = getbufline(a:buffer, 1, '$')
|
||||
|
||||
return [1, 'textDocument/didOpen', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': a:uri,
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ 'languageId': a:language_id,
|
||||
\ 'version': a:version,
|
||||
\ 'text': a:text,
|
||||
\ 'version': ale#lsp#message#GetNextVersionID(),
|
||||
\ 'text': join(l:lines, "\n"),
|
||||
\ },
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#DidChange(uri, version, text) abort
|
||||
function! ale#lsp#message#DidChange(buffer) abort
|
||||
let l:lines = getbufline(a:buffer, 1, '$')
|
||||
|
||||
" For changes, we simply send the full text of the document to the server.
|
||||
return [1, 'textDocument/didChange', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': a:uri,
|
||||
\ 'version': a:version,
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ 'version': ale#lsp#message#GetNextVersionID(),
|
||||
\ },
|
||||
\ 'contentChanges': [{'text': a:text}]
|
||||
\ 'contentChanges': [{'text': join(l:lines, "\n")}]
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#DidSave(uri) abort
|
||||
function! ale#lsp#message#DidSave(buffer) abort
|
||||
return [1, 'textDocument/didSave', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': a:uri,
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#DidClose(uri) abort
|
||||
function! ale#lsp#message#DidClose(buffer) abort
|
||||
return [1, 'textDocument/didClose', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': a:uri,
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
@@ -8,11 +8,10 @@ let s:SEVERITY_INFORMATION = 3
|
||||
let s:SEVERITY_HINT = 4
|
||||
|
||||
" Parse the message for textDocument/publishDiagnostics
|
||||
function! ale#lsp#response#ReadDiagnostics(params) abort
|
||||
let l:filename = a:params.uri
|
||||
function! ale#lsp#response#ReadDiagnostics(response) abort
|
||||
let l:loclist = []
|
||||
|
||||
for l:diagnostic in a:params.diagnostics
|
||||
for l:diagnostic in a:response.params.diagnostics
|
||||
let l:severity = get(l:diagnostic, 'severity', 0)
|
||||
let l:loclist_item = {
|
||||
\ 'text': l:diagnostic.message,
|
||||
@@ -40,7 +39,7 @@ function! ale#lsp#response#ReadDiagnostics(params) abort
|
||||
call add(l:loclist, l:loclist_item)
|
||||
endfor
|
||||
|
||||
return [l:filename, l:loclist]
|
||||
return l:loclist
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#response#ReadTSServerDiagnostics(response) abort
|
||||
|
||||
@@ -141,3 +141,26 @@ function! ale#path#Upwards(path) abort
|
||||
|
||||
return l:path_list
|
||||
endfunction
|
||||
|
||||
" Convert a filesystem path to a file:// URI
|
||||
" relatives paths will not be prefixed with the protocol.
|
||||
" For Windows paths, the `:` in C:\ etc. will not be percent-encoded.
|
||||
function! ale#path#ToURI(path) abort
|
||||
let l:has_drive_letter = a:path[1:2] ==# ':\'
|
||||
|
||||
return substitute(
|
||||
\ ((l:has_drive_letter || a:path[:0] ==# '/') ? 'file://' : '')
|
||||
\ . (l:has_drive_letter ? '/' . a:path[:2] : '')
|
||||
\ . ale#uri#Encode(l:has_drive_letter ? a:path[3:] : a:path),
|
||||
\ '\\',
|
||||
\ '/',
|
||||
\ 'g',
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! ale#path#FromURI(uri) abort
|
||||
let l:i = len('file://')
|
||||
let l:encoded_path = a:uri[: l:i - 1] ==# 'file://' ? a:uri[l:i :] : a:uri
|
||||
|
||||
return ale#uri#Decode(l:encoded_path)
|
||||
endfunction
|
||||
|
||||
18
autoload/ale/uri.vim
Normal file
18
autoload/ale/uri.vim
Normal file
@@ -0,0 +1,18 @@
|
||||
" This probably doesn't handle Unicode characters well.
|
||||
function! ale#uri#Encode(value) abort
|
||||
return substitute(
|
||||
\ a:value,
|
||||
\ '\([^a-zA-Z0-9\\/$\-_.!*''(),]\)',
|
||||
\ '\=printf(''%%%02x'', char2nr(submatch(1)))',
|
||||
\ 'g'
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! ale#uri#Decode(value) abort
|
||||
return substitute(
|
||||
\ a:value,
|
||||
\ '%\(\x\x\)',
|
||||
\ '\=nr2char(''0x'' . submatch(1))',
|
||||
\ 'g'
|
||||
\)
|
||||
endfunction
|
||||
Reference in New Issue
Block a user