mirror of
https://github.com/amix/vimrc
synced 2025-06-16 01:25:00 +08:00
Updated plugins
This commit is contained in:
@ -197,6 +197,11 @@ function! ale#Var(buffer, variable_name) abort
|
||||
return get(l:vars, l:full_name, g:[l:full_name])
|
||||
endfunction
|
||||
|
||||
" As above, but curry the arguments so only the buffer number is required.
|
||||
function! ale#VarFunc(variable_name) abort
|
||||
return {buf -> ale#Var(buf, a:variable_name)}
|
||||
endfunction
|
||||
|
||||
" Initialize a variable with a default value, if it isn't already set.
|
||||
"
|
||||
" Every variable name will be prefixed with 'ale_'.
|
||||
|
@ -54,9 +54,14 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
|
||||
endif
|
||||
else
|
||||
let l:command = ale#linter#GetCommand(l:buffer, l:linter)
|
||||
endif
|
||||
|
||||
if type(l:command) is v:t_string
|
||||
" Replace %e with the escaped executable, so tests keep passing after
|
||||
" linters are changed to use %e.
|
||||
let l:command = substitute(l:command, '%e', '\=ale#Escape(l:executable)', 'g')
|
||||
else
|
||||
call map(l:command, 'substitute(v:val, ''%e'', ''\=ale#Escape(l:executable)'', ''g'')')
|
||||
endif
|
||||
|
||||
AssertEqual
|
||||
@ -126,7 +131,9 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
|
||||
|
||||
execute 'runtime ale_linters/' . a:filetype . '/' . a:name . '.vim'
|
||||
|
||||
call ale#test#SetDirectory('/testplugin/test/command_callback')
|
||||
if !exists('g:dir')
|
||||
call ale#test#SetDirectory('/testplugin/test/command_callback')
|
||||
endif
|
||||
|
||||
command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>)
|
||||
command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>)
|
||||
@ -140,14 +147,33 @@ function! ale#assert#TearDownLinterTest() abort
|
||||
unlet! g:ale_create_dummy_temporary_file
|
||||
let s:chain_results = []
|
||||
|
||||
delcommand WithChainResults
|
||||
delcommand AssertLinter
|
||||
delcommand AssertLinterNotExecuted
|
||||
delcommand AssertLSPOptions
|
||||
delcommand AssertLSPLanguage
|
||||
delcommand AssertLSPProject
|
||||
if exists(':WithChainResults')
|
||||
delcommand WithChainResults
|
||||
endif
|
||||
|
||||
call ale#test#RestoreDirectory()
|
||||
if exists(':AssertLinter')
|
||||
delcommand AssertLinter
|
||||
endif
|
||||
|
||||
if exists(':AssertLinterNotExecuted')
|
||||
delcommand AssertLinterNotExecuted
|
||||
endif
|
||||
|
||||
if exists(':AssertLSPOptions')
|
||||
delcommand AssertLSPOptions
|
||||
endif
|
||||
|
||||
if exists(':AssertLSPLanguage')
|
||||
delcommand AssertLSPLanguage
|
||||
endif
|
||||
|
||||
if exists(':AssertLSPProject')
|
||||
delcommand AssertLSPProject
|
||||
endif
|
||||
|
||||
if exists('g:dir')
|
||||
call ale#test#RestoreDirectory()
|
||||
endif
|
||||
|
||||
Restore
|
||||
|
||||
|
@ -8,6 +8,25 @@ let s:sep = has('win32') ? '\' : '/'
|
||||
" Set just so tests can override it.
|
||||
let g:__ale_c_project_filenames = ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt']
|
||||
|
||||
function! ale#c#GetBuildDirectory(buffer) abort
|
||||
" Don't include build directory for header files, as compile_commands.json
|
||||
" files don't consider headers to be translation units, and provide no
|
||||
" commands for compiling header files.
|
||||
if expand('#' . a:buffer) =~# '\v\.(h|hpp)$'
|
||||
return ''
|
||||
endif
|
||||
|
||||
let l:build_dir = ale#Var(a:buffer, 'c_build_dir')
|
||||
|
||||
" c_build_dir has the priority if defined
|
||||
if !empty(l:build_dir)
|
||||
return l:build_dir
|
||||
endif
|
||||
|
||||
return ale#path#Dirname(ale#c#FindCompileCommands(a:buffer))
|
||||
endfunction
|
||||
|
||||
|
||||
function! ale#c#FindProjectRoot(buffer) abort
|
||||
for l:project_filename in g:__ale_c_project_filenames
|
||||
let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename)
|
||||
@ -150,12 +169,22 @@ function! s:GetListFromCompileCommandsFile(compile_commands_file) abort
|
||||
endfunction
|
||||
|
||||
function! ale#c#ParseCompileCommandsFlags(buffer, dir, json_list) abort
|
||||
" Search for an exact file match first.
|
||||
for l:item in a:json_list
|
||||
if bufnr(l:item.file) is a:buffer
|
||||
return ale#c#ParseCFlags(a:dir, l:item.command)
|
||||
endif
|
||||
endfor
|
||||
|
||||
" Look for any file in the same directory if we can't find an exact match.
|
||||
let l:dir = ale#path#Simplify(expand('#' . a:buffer . ':p:h'))
|
||||
|
||||
for l:item in a:json_list
|
||||
if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir
|
||||
return ale#c#ParseCFlags(a:dir, l:item.command)
|
||||
endif
|
||||
endfor
|
||||
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
@ -246,7 +275,7 @@ function! ale#c#IncludeOptions(include_paths) abort
|
||||
return ''
|
||||
endif
|
||||
|
||||
return ' ' . join(l:option_list) . ' '
|
||||
return join(l:option_list)
|
||||
endfunction
|
||||
|
||||
let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [
|
||||
|
@ -424,6 +424,58 @@ function! ale#completion#HandleLSPResponse(conn_id, response) abort
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
" If we have sent a completion request already, don't send another.
|
||||
if b:ale_completion_info.request_id
|
||||
return
|
||||
endif
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#completion#HandleTSServerResponse')
|
||||
\ : function('ale#completion#HandleLSPResponse')
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
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,
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, otherwise
|
||||
" completions won't know what text is nearby.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:buffer)
|
||||
|
||||
" For LSP completions, we need to clamp the column to the length of
|
||||
" the line. python-language-server and perhaps others do not implement
|
||||
" this correctly.
|
||||
let l:message = ale#lsp#message#Completion(
|
||||
\ l:buffer,
|
||||
\ b:ale_completion_info.line,
|
||||
\ min([
|
||||
\ b:ale_completion_info.line_length,
|
||||
\ b:ale_completion_info.column,
|
||||
\ ]),
|
||||
\ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
|
||||
\)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message)
|
||||
|
||||
if l:request_id
|
||||
let b:ale_completion_info.conn_id = l:id
|
||||
let b:ale_completion_info.request_id = l:request_id
|
||||
|
||||
if has_key(a:linter, 'completion_filter')
|
||||
let b:ale_completion_info.completion_filter = a:linter.completion_filter
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:GetLSPCompletions(linter) abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter)
|
||||
@ -433,58 +485,10 @@ function! s:GetLSPCompletions(linter) abort
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:root = l:lsp_details.project_root
|
||||
|
||||
function! OnReady(...) abort closure
|
||||
" If we have sent a completion request already, don't send another.
|
||||
if b:ale_completion_info.request_id
|
||||
return
|
||||
endif
|
||||
let l:OnReady = function('s:OnReady', [a:linter, l:lsp_details])
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#completion#HandleTSServerResponse')
|
||||
\ : function('ale#completion#HandleLSPResponse')
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
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,
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, otherwise
|
||||
" completions won't know what text is nearby.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
|
||||
|
||||
" For LSP completions, we need to clamp the column to the length of
|
||||
" the line. python-language-server and perhaps others do not implement
|
||||
" this correctly.
|
||||
let l:message = ale#lsp#message#Completion(
|
||||
\ l:buffer,
|
||||
\ b:ale_completion_info.line,
|
||||
\ min([
|
||||
\ b:ale_completion_info.line_length,
|
||||
\ b:ale_completion_info.column,
|
||||
\ ]),
|
||||
\ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
|
||||
\)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
|
||||
|
||||
if l:request_id
|
||||
let b:ale_completion_info.conn_id = l:id
|
||||
let b:ale_completion_info.request_id = l:request_id
|
||||
|
||||
if has_key(a:linter, 'completion_filter')
|
||||
let b:ale_completion_info.completion_filter = a:linter.completion_filter
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
call ale#lsp#WaitForCapability(l:id, l:root, 'completion', function('OnReady'))
|
||||
call ale#lsp#WaitForCapability(l:id, 'completion', l:OnReady)
|
||||
endfunction
|
||||
|
||||
function! ale#completion#GetCompletions() abort
|
||||
|
@ -57,6 +57,39 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, line, column, options, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#definition#HandleTSServerResponse')
|
||||
\ : function('ale#definition#HandleLSPResponse')
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:message = ale#lsp#tsserver_message#Definition(
|
||||
\ l:buffer,
|
||||
\ a:line,
|
||||
\ a:column
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, or the
|
||||
" definition position probably won't make sense.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:buffer)
|
||||
|
||||
" For LSP completions, we need to clamp the column to the length of
|
||||
" the line. python-language-server and perhaps others do not implement
|
||||
" this correctly.
|
||||
let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message)
|
||||
|
||||
let s:go_to_definition_map[l:request_id] = {
|
||||
\ 'open_in_tab': get(a:options, 'open_in_tab', 0),
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:GoToLSPDefinition(linter, options) abort
|
||||
let l:buffer = bufnr('')
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
@ -71,39 +104,10 @@ function! s:GoToLSPDefinition(linter, options) abort
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:root = l:lsp_details.project_root
|
||||
|
||||
function! OnReady(...) abort closure
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#definition#HandleTSServerResponse')
|
||||
\ : function('ale#definition#HandleLSPResponse')
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:message = ale#lsp#tsserver_message#Definition(
|
||||
\ l:buffer,
|
||||
\ l:line,
|
||||
\ l:column
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, or the
|
||||
" definition position probably won't make sense.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
|
||||
|
||||
" For LSP completions, we need to clamp the column to the length of
|
||||
" the line. python-language-server and perhaps others do not implement
|
||||
" this correctly.
|
||||
let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
|
||||
|
||||
let s:go_to_definition_map[l:request_id] = {
|
||||
\ 'open_in_tab': get(a:options, 'open_in_tab', 0),
|
||||
\}
|
||||
endfunction
|
||||
|
||||
call ale#lsp#WaitForCapability(l:id, l:root, 'definition', function('OnReady'))
|
||||
call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [
|
||||
\ a:linter, l:lsp_details, l:line, l:column, a:options
|
||||
\]))
|
||||
endfunction
|
||||
|
||||
function! ale#definition#GoTo(options) abort
|
||||
|
@ -157,7 +157,7 @@ let s:default_registry = {
|
||||
\ },
|
||||
\ 'hackfmt': {
|
||||
\ 'function': 'ale#fixers#hackfmt#Fix',
|
||||
\ 'suggested_filetypes': ['php'],
|
||||
\ 'suggested_filetypes': ['hack'],
|
||||
\ 'description': 'Fix Hack files with hackfmt.',
|
||||
\ },
|
||||
\ 'hfmt': {
|
||||
@ -170,6 +170,16 @@ let s:default_registry = {
|
||||
\ 'suggested_filetypes': ['haskell'],
|
||||
\ 'description': 'Fix Haskell files with brittany.',
|
||||
\ },
|
||||
\ 'hlint': {
|
||||
\ 'function': 'ale#fixers#hlint#Fix',
|
||||
\ 'suggested_filetypes': ['haskell'],
|
||||
\ 'description': 'Refactor Haskell files with hlint.',
|
||||
\ },
|
||||
\ 'stylish-haskell': {
|
||||
\ 'function': 'ale#fixers#stylish_haskell#Fix',
|
||||
\ 'suggested_filetypes': ['haskell'],
|
||||
\ 'description': 'Refactor Haskell files with stylish-haskell.',
|
||||
\ },
|
||||
\ 'refmt': {
|
||||
\ 'function': 'ale#fixers#refmt#Fix',
|
||||
\ 'suggested_filetypes': ['reason'],
|
||||
@ -243,7 +253,7 @@ endfunction
|
||||
" (name, func, filetypes, desc, aliases)
|
||||
function! ale#fix#registry#Add(name, func, filetypes, desc, ...) abort
|
||||
" This command will throw from the sandbox.
|
||||
let &equalprg=&equalprg
|
||||
let &l:equalprg=&l:equalprg
|
||||
|
||||
if type(a:name) isnot v:t_string
|
||||
throw '''name'' must be a String'
|
||||
|
@ -1,12 +1,12 @@
|
||||
" Author: Sam Howie <samhowie@gmail.com>
|
||||
" Description: Integration of hackfmt with ALE.
|
||||
|
||||
call ale#Set('php_hackfmt_executable', 'hackfmt')
|
||||
call ale#Set('php_hackfmt_options', '')
|
||||
call ale#Set('hack_hackfmt_executable', 'hackfmt')
|
||||
call ale#Set('hack_hackfmt_options', '')
|
||||
|
||||
function! ale#fixers#hackfmt#Fix(buffer) abort
|
||||
let l:executable = ale#Var(a:buffer, 'php_hackfmt_executable')
|
||||
let l:options = ale#Var(a:buffer, 'php_hackfmt_options')
|
||||
let l:executable = ale#Var(a:buffer, 'hack_hackfmt_executable')
|
||||
let l:options = ale#Var(a:buffer, 'hack_hackfmt_options')
|
||||
|
||||
return {
|
||||
\ 'command': ale#Escape(l:executable)
|
||||
|
16
sources_non_forked/ale/autoload/ale/fixers/hlint.vim
Normal file
16
sources_non_forked/ale/autoload/ale/fixers/hlint.vim
Normal file
@ -0,0 +1,16 @@
|
||||
" Author: eborden <evan@evan-borden.com>
|
||||
" Description: Integration of hlint refactor with ALE.
|
||||
"
|
||||
call ale#Set('haskell_hlint_executable', 'hlint')
|
||||
|
||||
function! ale#fixers#hlint#Fix(buffer) abort
|
||||
let l:executable = ale#Var(a:buffer, 'haskell_hlint_executable')
|
||||
|
||||
return {
|
||||
\ 'command': ale#Escape(l:executable)
|
||||
\ . ' --refactor'
|
||||
\ . ' --refactor-options="--inplace"'
|
||||
\ . ' %t',
|
||||
\ 'read_temporary_file': 1,
|
||||
\}
|
||||
endfunction
|
@ -1,7 +1,7 @@
|
||||
" Author: Jeff Willette <jrwillette88@gmail.com>
|
||||
" Description: Integration of importjs with ALE.
|
||||
|
||||
call ale#Set('js_importjs_executable', 'importjs')
|
||||
call ale#Set('javascript_importjs_executable', 'importjs')
|
||||
|
||||
function! ale#fixers#importjs#ProcessOutput(buffer, output) abort
|
||||
let l:result = ale#util#FuzzyJSONDecode(a:output, [])
|
||||
@ -9,7 +9,7 @@ function! ale#fixers#importjs#ProcessOutput(buffer, output) abort
|
||||
endfunction
|
||||
|
||||
function! ale#fixers#importjs#Fix(buffer) abort
|
||||
let l:executable = ale#Var(a:buffer, 'js_importjs_executable')
|
||||
let l:executable = ale#Var(a:buffer, 'javascript_importjs_executable')
|
||||
|
||||
if !executable(l:executable)
|
||||
return 0
|
||||
|
@ -27,6 +27,17 @@ function! ale#fixers#prettier#Fix(buffer) abort
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort
|
||||
" If the output is an error message, don't use it.
|
||||
for l:line in a:output[:10]
|
||||
if l:line =~# '^\w*Error:'
|
||||
return []
|
||||
endif
|
||||
endfor
|
||||
|
||||
return a:output
|
||||
endfunction
|
||||
|
||||
function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
|
||||
let l:executable = ale#fixers#prettier#GetExecutable(a:buffer)
|
||||
let l:options = ale#Var(a:buffer, 'javascript_prettier_options')
|
||||
@ -36,12 +47,23 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
|
||||
" Append the --parser flag depending on the current filetype (unless it's
|
||||
" already set in g:javascript_prettier_options).
|
||||
if empty(expand('#' . a:buffer . ':e')) && match(l:options, '--parser') == -1
|
||||
let l:prettier_parsers = ['typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue']
|
||||
let l:parser = 'babylon'
|
||||
let l:prettier_parsers = {
|
||||
\ 'typescript': 'typescript',
|
||||
\ 'css': 'css',
|
||||
\ 'less': 'less',
|
||||
\ 'scss': 'scss',
|
||||
\ 'json': 'json',
|
||||
\ 'json5': 'json5',
|
||||
\ 'graphql': 'graphql',
|
||||
\ 'markdown': 'markdown',
|
||||
\ 'vue': 'vue',
|
||||
\ 'yaml': 'yaml',
|
||||
\}
|
||||
let l:parser = ''
|
||||
|
||||
for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.')
|
||||
if index(l:prettier_parsers, l:filetype) > -1
|
||||
let l:parser = l:filetype
|
||||
if has_key(l:prettier_parsers, l:filetype)
|
||||
let l:parser = l:prettier_parsers[l:filetype]
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
@ -51,6 +73,17 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
|
||||
let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--parser ' . l:parser
|
||||
endif
|
||||
|
||||
" Special error handling needed for prettier_d
|
||||
if l:executable =~# 'prettier_d$'
|
||||
return {
|
||||
\ 'command': ale#path#BufferCdString(a:buffer)
|
||||
\ . ale#Escape(l:executable)
|
||||
\ . (!empty(l:options) ? ' ' . l:options : '')
|
||||
\ . ' --stdin-filepath %s --stdin',
|
||||
\ 'process_with': 'ale#fixers#prettier#ProcessPrettierDOutput',
|
||||
\}
|
||||
endif
|
||||
|
||||
" 1.4.0 is the first version with --stdin-filepath
|
||||
if ale#semver#GTE(l:version, [1, 4, 0])
|
||||
return {
|
||||
|
@ -0,0 +1,15 @@
|
||||
" Author: eborden <evan@evan-borden.com>
|
||||
" Description: Integration of stylish-haskell formatting with ALE.
|
||||
"
|
||||
call ale#Set('haskell_stylish_haskell_executable', 'stylish-haskell')
|
||||
|
||||
function! ale#fixers#stylish_haskell#Fix(buffer) abort
|
||||
let l:executable = ale#Var(a:buffer, 'haskell_stylish_haskell_executable')
|
||||
|
||||
return {
|
||||
\ 'command': ale#Escape(l:executable)
|
||||
\ . ' --inplace'
|
||||
\ . ' %t',
|
||||
\ 'read_temporary_file': 1,
|
||||
\}
|
||||
endfunction
|
@ -92,9 +92,12 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
|
||||
" the previous error parsed in output
|
||||
if l:match[4] is# 'note'
|
||||
if !empty(l:output)
|
||||
let l:output[-1]['detail'] =
|
||||
\ get(l:output[-1], 'detail', '')
|
||||
\ . s:RemoveUnicodeQuotes(l:match[0]) . "\n"
|
||||
if !has_key(l:output[-1], 'detail')
|
||||
let l:output[-1].detail = l:output[-1].text
|
||||
endif
|
||||
|
||||
let l:output[-1].detail = l:output[-1].detail . "\n"
|
||||
\ . s:RemoveUnicodeQuotes(l:match[0])
|
||||
endif
|
||||
|
||||
continue
|
||||
|
@ -92,7 +92,44 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:ShowDetails(linter, buffer, line, column, opt) abort
|
||||
function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#hover#HandleTSServerResponse')
|
||||
\ : function('ale#hover#HandleLSPResponse')
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:column = a:column
|
||||
|
||||
let l:message = ale#lsp#tsserver_message#Quickinfo(
|
||||
\ l:buffer,
|
||||
\ a:line,
|
||||
\ l:column
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, or the
|
||||
" hover position probably won't make sense.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:buffer)
|
||||
|
||||
let l:column = min([a:column, len(getbufline(l:buffer, a:line)[0])])
|
||||
|
||||
let l:message = ale#lsp#message#Hover(l:buffer, a:line, l:column)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message)
|
||||
|
||||
let s:hover_map[l:request_id] = {
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'line': a:line,
|
||||
\ 'column': l:column,
|
||||
\ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:ShowDetails(linter, buffer, line, column, opt, ...) abort
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
|
||||
|
||||
if empty(l:lsp_details)
|
||||
@ -100,44 +137,10 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:root = l:lsp_details.project_root
|
||||
let l:language_id = l:lsp_details.language_id
|
||||
|
||||
function! OnReady(...) abort closure
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#hover#HandleTSServerResponse')
|
||||
\ : function('ale#hover#HandleLSPResponse')
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:column = a:column
|
||||
|
||||
let l:message = ale#lsp#tsserver_message#Quickinfo(
|
||||
\ a:buffer,
|
||||
\ a:line,
|
||||
\ l:column
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, or the
|
||||
" hover position probably won't make sense.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:root, a:buffer)
|
||||
|
||||
let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])])
|
||||
|
||||
let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
|
||||
|
||||
let s:hover_map[l:request_id] = {
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'line': a:line,
|
||||
\ 'column': l:column,
|
||||
\ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
|
||||
\}
|
||||
endfunction
|
||||
|
||||
call ale#lsp#WaitForCapability(l:id, l:root, 'hover', function('OnReady'))
|
||||
call ale#lsp#WaitForCapability(l:id, 'hover', function('s:OnReady', [
|
||||
\ a:linter, l:lsp_details, a:line, a:column, a:opt
|
||||
\]))
|
||||
endfunction
|
||||
|
||||
" Obtain Hover information for the specified position
|
||||
|
@ -26,11 +26,13 @@ let s:default_ale_linter_aliases = {
|
||||
"
|
||||
" Only cargo is enabled for Rust by default.
|
||||
" rpmlint is disabled by default because it can result in code execution.
|
||||
" hhast is disabled by default because it executes code in the project root.
|
||||
"
|
||||
" NOTE: Update the g:ale_linters documentation when modifying this.
|
||||
let s:default_ale_linters = {
|
||||
\ 'csh': ['shell'],
|
||||
\ 'go': ['gofmt', 'golint', 'go vet'],
|
||||
\ 'hack': ['hack'],
|
||||
\ 'help': [],
|
||||
\ 'perl': ['perlcritic'],
|
||||
\ 'python': ['flake8', 'mypy', 'pylint'],
|
||||
@ -51,7 +53,7 @@ endfunction
|
||||
" Do not call this function.
|
||||
function! ale#linter#GetLintersLoaded() abort
|
||||
" This command will throw from the sandbox.
|
||||
let &equalprg=&equalprg
|
||||
let &l:equalprg=&l:equalprg
|
||||
|
||||
return s:linters
|
||||
endfunction
|
||||
@ -293,7 +295,7 @@ endfunction
|
||||
|
||||
function! ale#linter#Define(filetype, linter) abort
|
||||
" This command will throw from the sandbox.
|
||||
let &equalprg=&equalprg
|
||||
let &l:equalprg=&l:equalprg
|
||||
|
||||
if !has_key(s:linters, a:filetype)
|
||||
let s:linters[a:filetype] = []
|
||||
|
@ -1,62 +1,66 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: Language Server Protocol client code
|
||||
|
||||
" A List of connections, used for tracking servers which have been connected
|
||||
" to, and programs which are run.
|
||||
let s:connections = get(s:, 'connections', [])
|
||||
" A Dictionary for tracking connections.
|
||||
let s:connections = get(s:, 'connections', {})
|
||||
let g:ale_lsp_next_message_id = 1
|
||||
|
||||
" Exposed only so tests can get at it.
|
||||
" Do not call this function basically anywhere.
|
||||
function! ale#lsp#NewConnection(initialization_options) abort
|
||||
" id: The job ID as a Number, or the server address as a string.
|
||||
" data: The message data received so far.
|
||||
" executable: An executable only set for program connections.
|
||||
" open_documents: A Dictionary mapping buffers to b:changedtick, keeping
|
||||
" track of when documents were opened, and when we last changed them.
|
||||
" callback_list: A list of callbacks for handling LSP responses.
|
||||
" initialization_options: Options to send to the server.
|
||||
" capabilities: Features the server supports.
|
||||
let l:conn = {
|
||||
\ 'is_tsserver': 0,
|
||||
\ 'id': '',
|
||||
\ 'data': '',
|
||||
\ 'projects': {},
|
||||
\ 'open_documents': {},
|
||||
\ 'callback_list': [],
|
||||
\ 'initialization_options': a:initialization_options,
|
||||
\ 'capabilities': {
|
||||
\ 'hover': 0,
|
||||
\ 'references': 0,
|
||||
\ 'completion': 0,
|
||||
\ 'completion_trigger_characters': [],
|
||||
\ 'definition': 0,
|
||||
\ },
|
||||
\}
|
||||
" Given an id, which can be an executable or address, and a project path,
|
||||
" create a new connection if needed. Return a unique ID for the connection.
|
||||
function! ale#lsp#Register(executable_or_address, project, init_options) abort
|
||||
let l:conn_id = a:executable_or_address . ':' . a:project
|
||||
|
||||
call add(s:connections, l:conn)
|
||||
if !has_key(s:connections, l:conn_id)
|
||||
" is_tsserver: 1 if the connection is for tsserver.
|
||||
" data: The message data received so far.
|
||||
" root: The project root.
|
||||
" open_documents: A Dictionary mapping buffers to b:changedtick, keeping
|
||||
" track of when documents were opened, and when we last changed them.
|
||||
" initialized: 0 if the connection is ready, 1 otherwise.
|
||||
" init_request_id: The ID for the init request.
|
||||
" init_options: Options to send to the server.
|
||||
" callback_list: A list of callbacks for handling LSP responses.
|
||||
" message_queue: Messages queued for sending to callbacks.
|
||||
" capabilities_queue: The list of callbacks to call with capabilities.
|
||||
" capabilities: Features the server supports.
|
||||
let s:connections[l:conn_id] = {
|
||||
\ 'is_tsserver': 0,
|
||||
\ 'data': '',
|
||||
\ 'root': a:project,
|
||||
\ 'open_documents': {},
|
||||
\ 'initialized': 0,
|
||||
\ 'init_request_id': 0,
|
||||
\ 'init_options': a:init_options,
|
||||
\ 'callback_list': [],
|
||||
\ 'message_queue': [],
|
||||
\ 'capabilities_queue': [],
|
||||
\ 'capabilities': {
|
||||
\ 'hover': 0,
|
||||
\ 'references': 0,
|
||||
\ 'completion': 0,
|
||||
\ 'completion_trigger_characters': [],
|
||||
\ 'definition': 0,
|
||||
\ },
|
||||
\}
|
||||
endif
|
||||
|
||||
return l:conn
|
||||
return l:conn_id
|
||||
endfunction
|
||||
|
||||
" Remove an LSP connection with a given ID. This is only for tests.
|
||||
function! ale#lsp#RemoveConnectionWithID(id) abort
|
||||
call filter(s:connections, 'v:val.id isnot a:id')
|
||||
if has_key(s:connections, a:id)
|
||||
call remove(s:connections, a:id)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:FindConnection(key, value) abort
|
||||
for l:conn in s:connections
|
||||
if has_key(l:conn, a:key) && get(l:conn, a:key) is# a:value
|
||||
return l:conn
|
||||
endif
|
||||
endfor
|
||||
" This is only needed for tests
|
||||
function! ale#lsp#MarkDocumentAsOpen(id, buffer) abort
|
||||
let l:conn = get(s:connections, a:id, {})
|
||||
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
" Get the capabilities for a connection, or an empty Dictionary.
|
||||
function! ale#lsp#GetConnectionCapabilities(id) abort
|
||||
return get(s:FindConnection('id', a:id), 'capabilities', {})
|
||||
if !empty(l:conn)
|
||||
let l:conn.open_documents[a:buffer] = -1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#GetNextMessageID() abort
|
||||
@ -100,7 +104,7 @@ endfunction
|
||||
" Given a List of one or two items, [method_name] or [method_name, params],
|
||||
" return a List containing [message_id, message_data]
|
||||
function! ale#lsp#CreateMessageData(message) abort
|
||||
if a:message[1] =~# '^ts@'
|
||||
if a:message[1][:2] is# 'ts@'
|
||||
return s:CreateTSServerMessageData(a:message)
|
||||
endif
|
||||
|
||||
@ -167,49 +171,6 @@ 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:MarkProjectAsInitialized(conn, project) abort
|
||||
let a:project.initialized = 1
|
||||
|
||||
" After the server starts, send messages we had queued previously.
|
||||
for l:message_data in a:project.message_queue
|
||||
call s:SendMessageData(a:conn, l:message_data)
|
||||
endfor
|
||||
|
||||
" Remove the messages now.
|
||||
let a:conn.message_queue = []
|
||||
|
||||
" Call capabilities callbacks queued for the project.
|
||||
for [l:capability, l:Callback] in a:project.capabilities_queue
|
||||
if a:conn.is_tsserver || a:conn.capabilities[l:capability]
|
||||
call call(l:Callback, [a:conn.id, a:project.root])
|
||||
endif
|
||||
endfor
|
||||
|
||||
" Clear the queued callbacks now.
|
||||
let a:project.capabilities_queue = []
|
||||
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)
|
||||
call s:MarkProjectAsInitialized(a:conn, l:project)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Update capabilities from the server, so we know which features the server
|
||||
" supports.
|
||||
function! s:UpdateCapabilities(conn, capabilities) abort
|
||||
@ -242,178 +203,138 @@ function! s:UpdateCapabilities(conn, capabilities) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort
|
||||
let l:uninitialized_projects = []
|
||||
function! ale#lsp#HandleInitResponse(conn, response) abort
|
||||
if get(a:response, 'method', '') is# 'initialize'
|
||||
let a:conn.initialized = 1
|
||||
elseif type(get(a:response, 'result')) is v:t_dict
|
||||
\&& has_key(a:response.result, 'capabilities')
|
||||
call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
|
||||
|
||||
for [l:key, l:value] in items(a:conn.projects)
|
||||
if l:value.initialized == 0
|
||||
call add(l:uninitialized_projects, [l:key, l:value])
|
||||
endif
|
||||
endfor
|
||||
let a:conn.initialized = 1
|
||||
endif
|
||||
|
||||
if empty(l:uninitialized_projects)
|
||||
if !a:conn.initialized
|
||||
return
|
||||
endif
|
||||
|
||||
if get(a:response, 'method', '') is# ''
|
||||
if has_key(get(a:response, 'result', {}), 'capabilities')
|
||||
call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
|
||||
" After the server starts, send messages we had queued previously.
|
||||
for l:message_data in a:conn.message_queue
|
||||
call s:SendMessageData(a:conn, l:message_data)
|
||||
endfor
|
||||
|
||||
for [l:dir, l:project] in l:uninitialized_projects
|
||||
call s:MarkProjectAsInitialized(a:conn, l:project)
|
||||
endfor
|
||||
" Remove the messages now.
|
||||
let a:conn.message_queue = []
|
||||
|
||||
" Call capabilities callbacks queued for the project.
|
||||
for [l:capability, l:Callback] in a:conn.capabilities_queue
|
||||
if a:conn.capabilities[l:capability]
|
||||
call call(l:Callback, [a:conn.id])
|
||||
endif
|
||||
elseif get(a:response, 'method', '') is# 'textDocument/publishDiagnostics'
|
||||
let l:filename = ale#path#FromURI(a:response.params.uri)
|
||||
endfor
|
||||
|
||||
for [l:dir, l:project] in l:uninitialized_projects
|
||||
if l:filename[:len(l:dir) - 1] is# l:dir
|
||||
call s:MarkProjectAsInitialized(a:conn, l:project)
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
let a:conn.capabilities_queue = []
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HandleMessage(conn, message) abort
|
||||
function! ale#lsp#HandleMessage(conn_id, message) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
if empty(l:conn)
|
||||
return
|
||||
endif
|
||||
|
||||
if type(a:message) isnot v:t_string
|
||||
" Ignore messages that aren't strings.
|
||||
return
|
||||
endif
|
||||
|
||||
let a:conn.data .= a:message
|
||||
let l:conn.data .= a:message
|
||||
|
||||
" Parse the objects now if we can, and keep the remaining text.
|
||||
let [a:conn.data, l:response_list] = ale#lsp#ReadMessageData(a:conn.data)
|
||||
let [l:conn.data, l:response_list] = ale#lsp#ReadMessageData(l:conn.data)
|
||||
|
||||
" Call our callbacks.
|
||||
for l:response in l:response_list
|
||||
if get(l:response, 'method', '') is# 'initialize'
|
||||
call s:HandleInitializeResponse(a:conn, l:response)
|
||||
else
|
||||
call ale#lsp#HandleOtherInitializeResponses(a:conn, l:response)
|
||||
" Look for initialize responses first.
|
||||
if !l:conn.initialized
|
||||
for l:response in l:response_list
|
||||
call ale#lsp#HandleInitResponse(l:conn, l:response)
|
||||
endfor
|
||||
endif
|
||||
|
||||
" If the connection is marked as initialized, call the callbacks with the
|
||||
" responses.
|
||||
if l:conn.initialized
|
||||
for l:response in l:response_list
|
||||
" Call all of the registered handlers with the response.
|
||||
for l:Callback in a:conn.callback_list
|
||||
call ale#util#GetFunction(l:Callback)(a:conn.id, l:response)
|
||||
for l:Callback in l:conn.callback_list
|
||||
call ale#util#GetFunction(l:Callback)(a:conn_id, l:response)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:HandleChannelMessage(channel_id, message) abort
|
||||
let l:address = ale#socket#GetAddress(a:channel_id)
|
||||
let l:conn = s:FindConnection('id', l:address)
|
||||
|
||||
call ale#lsp#HandleMessage(l:conn, a:message)
|
||||
endfunction
|
||||
|
||||
function! s:HandleCommandMessage(job_id, message) abort
|
||||
let l:conn = s:FindConnection('id', a:job_id)
|
||||
|
||||
call ale#lsp#HandleMessage(l:conn, a:message)
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Given a connection ID, mark it as a tsserver connection, so it will be
|
||||
" handled that way.
|
||||
function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
|
||||
if !empty(l:conn)
|
||||
let l:conn.is_tsserver = 1
|
||||
endif
|
||||
let l:conn = s:connections[a:conn_id]
|
||||
let l:conn.is_tsserver = 1
|
||||
let l:conn.initialized = 1
|
||||
" Set capabilities which are supported by tsserver.
|
||||
let l:conn.capabilities.hover = 1
|
||||
let l:conn.capabilities.references = 1
|
||||
let l:conn.capabilities.completion = 1
|
||||
let l:conn.capabilities.completion_trigger_characters = ['.']
|
||||
let l:conn.capabilities.definition = 1
|
||||
endfunction
|
||||
|
||||
" Register a project for an LSP connection.
|
||||
" Start a program for LSP servers.
|
||||
"
|
||||
" This function will throw if the connection doesn't exist.
|
||||
function! ale#lsp#RegisterProject(conn_id, project_root) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
" 1 will be returned if the program is running, or 0 if the program could
|
||||
" not be started.
|
||||
function! ale#lsp#StartProgram(conn_id, executable, command) abort
|
||||
let l:conn = s:connections[a:conn_id]
|
||||
|
||||
" Empty strings can't be used for Dictionary keys in NeoVim, due to E713.
|
||||
" This appears to be a nonsensical bug in NeoVim.
|
||||
let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
|
||||
|
||||
if !has_key(l:conn.projects, l:key)
|
||||
" Tools without project roots are ready right away, like tsserver.
|
||||
let l:conn.projects[l:key] = {
|
||||
\ 'root': a:project_root,
|
||||
\ 'initialized': empty(a:project_root),
|
||||
\ 'init_request_id': 0,
|
||||
\ 'message_queue': [],
|
||||
\ 'capabilities_queue': [],
|
||||
\}
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#GetProject(conn, project_root) abort
|
||||
if empty(a:conn)
|
||||
return {}
|
||||
endif
|
||||
|
||||
let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
|
||||
|
||||
return get(a:conn.projects, l:key, {})
|
||||
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, init_options) abort
|
||||
if !executable(a:executable)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:conn = s:FindConnection('executable', a:executable)
|
||||
|
||||
" Get the current connection or a new one.
|
||||
let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options)
|
||||
let l:conn.executable = a:executable
|
||||
|
||||
if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id)
|
||||
if !has_key(l:conn, 'job_id') || !ale#job#IsRunning(l:conn.job_id)
|
||||
let l:options = {
|
||||
\ 'mode': 'raw',
|
||||
\ 'out_cb': function('s:HandleCommandMessage'),
|
||||
\ 'out_cb': {_, message -> ale#lsp#HandleMessage(a:conn_id, message)},
|
||||
\}
|
||||
let l:job_id = ale#job#Start(a:command, l:options)
|
||||
else
|
||||
let l:job_id = l:conn.id
|
||||
let l:job_id = l:conn.job_id
|
||||
endif
|
||||
|
||||
if l:job_id <= 0
|
||||
return 0
|
||||
if l:job_id > 0
|
||||
let l:conn.job_id = l:job_id
|
||||
endif
|
||||
|
||||
let l:conn.id = l:job_id
|
||||
|
||||
return l:job_id
|
||||
return l:job_id > 0
|
||||
endfunction
|
||||
|
||||
" Connect to an address and set up a callback for handling responses.
|
||||
function! ale#lsp#ConnectToAddress(address, init_options) 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 : ale#lsp#NewConnection(a:init_options)
|
||||
" Connect to an LSP server via TCP.
|
||||
"
|
||||
" 1 will be returned if the connection is running, or 0 if the connection could
|
||||
" not be opened.
|
||||
function! ale#lsp#ConnectToAddress(conn_id, address) abort
|
||||
let l:conn = s:connections[a:conn_id]
|
||||
|
||||
if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
|
||||
let l:conn.channel_id = ale#socket#Open(a:address, {
|
||||
\ 'callback': function('s:HandleChannelMessage'),
|
||||
let l:channel_id = ale#socket#Open(a:address, {
|
||||
\ 'callback': {_, mess -> ale#lsp#HandleMessage(a:conn_id, mess)},
|
||||
\})
|
||||
else
|
||||
let l:channel_id = l:conn.channel_id
|
||||
endif
|
||||
|
||||
if l:conn.channel_id < 0
|
||||
return ''
|
||||
if l:channel_id >= 0
|
||||
let l:conn.channel_id = l:channel_id
|
||||
endif
|
||||
|
||||
let l:conn.id = a:address
|
||||
|
||||
return a:address
|
||||
return l:channel_id >= 0
|
||||
endfunction
|
||||
|
||||
" Given a connection ID and a callback, register that callback for handling
|
||||
" messages if the connection exists.
|
||||
function! ale#lsp#RegisterCallback(conn_id, callback) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
if !empty(l:conn)
|
||||
" Add the callback to the List if it's not there already.
|
||||
@ -421,23 +342,33 @@ function! ale#lsp#RegisterCallback(conn_id, callback) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Stop a single LSP connection.
|
||||
function! ale#lsp#Stop(conn_id) abort
|
||||
if has_key(s:connections, a:conn_id)
|
||||
let l:conn = remove(s:connections, a:conn_id)
|
||||
|
||||
if has_key(l:conn, 'channel_id')
|
||||
call ale#socket#Close(l:conn.channel_id)
|
||||
elseif has_key(l:conn, 'job_id')
|
||||
call ale#job#Stop(l:conn.job_id)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#CloseDocument(conn_id) abort
|
||||
endfunction
|
||||
|
||||
" Stop all LSP connections, closing all jobs and channels, and removing any
|
||||
" queued messages.
|
||||
function! ale#lsp#StopAll() abort
|
||||
for l:conn in s:connections
|
||||
if has_key(l:conn, 'channel_id')
|
||||
call ale#socket#Close(l:conn.channel_id)
|
||||
else
|
||||
call ale#job#Stop(l:conn.id)
|
||||
endif
|
||||
for l:conn_id in keys(s:connections)
|
||||
call ale#lsp#Stop(l:conn_id)
|
||||
endfor
|
||||
|
||||
let s:connections = []
|
||||
endfunction
|
||||
|
||||
function! s:SendMessageData(conn, data) abort
|
||||
if has_key(a:conn, 'executable')
|
||||
call ale#job#SendRaw(a:conn.id, a:data)
|
||||
if has_key(a:conn, 'job_id')
|
||||
call ale#job#SendRaw(a:conn.job_id, a:data)
|
||||
elseif has_key(a:conn, 'channel_id') && ale#socket#IsOpen(a:conn.channel_id)
|
||||
" Send the message to the server
|
||||
call ale#socket#Send(a:conn.channel_id, a:data)
|
||||
@ -454,38 +385,32 @@ 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
|
||||
let l:project_root = get(a:000, 0, '')
|
||||
function! ale#lsp#Send(conn_id, message) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
let l:project = ale#lsp#GetProject(l:conn, l:project_root)
|
||||
|
||||
if empty(l:project)
|
||||
if empty(l:conn)
|
||||
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:project_root, l:conn.initialization_options),
|
||||
\)
|
||||
if !l:conn.initialized && !l:conn.init_request_id
|
||||
let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
|
||||
\ ale#lsp#message#Initialize(l:conn.root, l:conn.init_options),
|
||||
\)
|
||||
|
||||
let l:project.init_request_id = l:init_id
|
||||
let l:conn.init_request_id = l:init_id
|
||||
|
||||
call s:SendMessageData(l:conn, l:init_data)
|
||||
endif
|
||||
call s:SendMessageData(l:conn, l:init_data)
|
||||
endif
|
||||
|
||||
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
|
||||
|
||||
if l:project.initialized
|
||||
if l:conn.initialized
|
||||
" Send the message now.
|
||||
call s:SendMessageData(l:conn, l:data)
|
||||
else
|
||||
" Add the message we wanted to send to a List to send later.
|
||||
call add(l:project.message_queue, l:data)
|
||||
call add(l:conn.message_queue, l:data)
|
||||
endif
|
||||
|
||||
return l:id == 0 ? -1 : l:id
|
||||
@ -493,11 +418,10 @@ endfunction
|
||||
|
||||
" 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.
|
||||
function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
let l:opened = 0
|
||||
|
||||
" FIXME: Return 1 if the document is already open?
|
||||
if !empty(l:conn) && !has_key(l:conn.open_documents, a:buffer)
|
||||
if l:conn.is_tsserver
|
||||
let l:message = ale#lsp#tsserver_message#Open(a:buffer)
|
||||
@ -505,7 +429,7 @@ function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort
|
||||
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 ale#lsp#Send(a:conn_id, l:message)
|
||||
let l:conn.open_documents[a:buffer] = getbufvar(a:buffer, 'changedtick')
|
||||
let l:opened = 1
|
||||
endif
|
||||
@ -515,8 +439,8 @@ endfunction
|
||||
|
||||
" Notify LSP servers or tsserver that a document has changed, if needed.
|
||||
" If a notification is sent, 1 will be returned, otherwise 0 will be returned.
|
||||
function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
function! ale#lsp#NotifyForChanges(conn_id, buffer) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
let l:notified = 0
|
||||
|
||||
if !empty(l:conn) && has_key(l:conn.open_documents, a:buffer)
|
||||
@ -529,7 +453,7 @@ function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort
|
||||
let l:message = ale#lsp#message#DidChange(a:buffer)
|
||||
endif
|
||||
|
||||
call ale#lsp#Send(a:conn_id, l:message, a:project_root)
|
||||
call ale#lsp#Send(a:conn_id, l:message)
|
||||
let l:conn.open_documents[a:buffer] = l:new_tick
|
||||
let l:notified = 1
|
||||
endif
|
||||
@ -540,25 +464,24 @@ endfunction
|
||||
|
||||
" Given some LSP details that must contain at least `connection_id` and
|
||||
" `project_root` keys,
|
||||
function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort
|
||||
let l:conn = s:FindConnection('id', a:conn_id)
|
||||
let l:project = ale#lsp#GetProject(l:conn, a:project_root)
|
||||
function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
if empty(l:project)
|
||||
return 0
|
||||
if empty(l:conn)
|
||||
return
|
||||
endif
|
||||
|
||||
if type(get(l:conn.capabilities, a:capability, v:null)) isnot v:t_number
|
||||
throw 'Invalid capability ' . a:capability
|
||||
endif
|
||||
|
||||
if l:project.initialized
|
||||
if l:conn.is_tsserver || l:conn.capabilities[a:capability]
|
||||
if l:conn.initialized
|
||||
if l:conn.capabilities[a:capability]
|
||||
" The project has been initialized, so call the callback now.
|
||||
call call(a:callback, [a:conn_id, a:project_root])
|
||||
call call(a:callback, [a:conn_id])
|
||||
endif
|
||||
else
|
||||
" Call the callback later, once we have the information we need.
|
||||
call add(l:project.capabilities_queue, [a:capability, a:callback])
|
||||
call add(l:conn.capabilities_queue, [a:capability, a:callback])
|
||||
endif
|
||||
endfunction
|
||||
|
@ -47,7 +47,23 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
|
||||
endif
|
||||
|
||||
if has_key(l:diagnostic, 'code')
|
||||
let l:loclist_item.nr = l:diagnostic.code
|
||||
if type(l:diagnostic.code) == v:t_string
|
||||
let l:loclist_item.code = l:diagnostic.code
|
||||
elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1
|
||||
let l:loclist_item.code = string(l:diagnostic.code)
|
||||
let l:loclist_item.nr = l:diagnostic.code
|
||||
endif
|
||||
endif
|
||||
|
||||
if has_key(l:diagnostic, 'relatedInformation')
|
||||
let l:related = deepcopy(l:diagnostic.relatedInformation)
|
||||
call map(l:related, {key, val ->
|
||||
\ ale#path#FromURI(val.location.uri) .
|
||||
\ ':' . (val.location.range.start.line + 1) .
|
||||
\ ':' . (val.location.range.start.character + 1) .
|
||||
\ ":\n\t" . val.message
|
||||
\ })
|
||||
let l:loclist_item.detail = l:diagnostic.message . "\n" . join(l:related, "\n")
|
||||
endif
|
||||
|
||||
call add(l:loclist, l:loclist_item)
|
||||
@ -70,7 +86,12 @@ function! ale#lsp#response#ReadTSServerDiagnostics(response) abort
|
||||
\}
|
||||
|
||||
if has_key(l:diagnostic, 'code')
|
||||
let l:loclist_item.nr = l:diagnostic.code
|
||||
if type(l:diagnostic.code) == v:t_string
|
||||
let l:loclist_item.code = l:diagnostic.code
|
||||
elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1
|
||||
let l:loclist_item.code = string(l:diagnostic.code)
|
||||
let l:loclist_item.nr = l:diagnostic.code
|
||||
endif
|
||||
endif
|
||||
|
||||
if get(l:diagnostic, 'category') is# 'warning'
|
||||
@ -110,7 +131,7 @@ function! ale#lsp#response#GetErrorMessage(response) abort
|
||||
|
||||
if type(l:error_data) is v:t_string
|
||||
let l:message .= "\n" . l:error_data
|
||||
else
|
||||
elseif type(l:error_data) is v:t_dict
|
||||
let l:traceback = get(l:error_data, 'traceback', [])
|
||||
|
||||
if type(l:traceback) is v:t_list && !empty(l:traceback)
|
||||
|
@ -143,7 +143,8 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
|
||||
|
||||
if a:linter.lsp is# 'socket'
|
||||
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
|
||||
let l:conn_id = ale#lsp#ConnectToAddress(l:address, l:init_options)
|
||||
let l:conn_id = ale#lsp#Register(l:address, l:root, l:init_options)
|
||||
let l:ready = ale#lsp#ConnectToAddress(l:conn_id, l:address)
|
||||
else
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
@ -151,18 +152,16 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
|
||||
return {}
|
||||
endif
|
||||
|
||||
let l:conn_id = ale#lsp#Register(l:executable, l:root, l:init_options)
|
||||
|
||||
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
|
||||
" Format the command, so %e can be formatted into it.
|
||||
let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0)[1]
|
||||
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
|
||||
let l:conn_id = ale#lsp#StartProgram(
|
||||
\ l:executable,
|
||||
\ l:command,
|
||||
\ l:init_options,
|
||||
\)
|
||||
let l:ready = ale#lsp#StartProgram(l:conn_id, l:executable, l:command)
|
||||
endif
|
||||
|
||||
if empty(l:conn_id)
|
||||
if !l:ready
|
||||
if g:ale_history_enabled && !empty(l:command)
|
||||
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
|
||||
endif
|
||||
@ -175,9 +174,6 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
|
||||
call ale#lsp#MarkConnectionAsTsserver(l:conn_id)
|
||||
endif
|
||||
|
||||
" Register the project now the connection is ready.
|
||||
call ale#lsp#RegisterProject(l:conn_id, l:root)
|
||||
|
||||
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
|
||||
|
||||
let l:details = {
|
||||
@ -188,7 +184,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
|
||||
\ 'language_id': l:language_id,
|
||||
\}
|
||||
|
||||
if ale#lsp#OpenDocument(l:conn_id, l:root, a:buffer, l:language_id)
|
||||
if ale#lsp#OpenDocument(l:conn_id, a:buffer, l:language_id)
|
||||
if g:ale_history_enabled && !empty(l:command)
|
||||
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
|
||||
endif
|
||||
@ -196,7 +192,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
|
||||
|
||||
" The change message needs to be sent for tsserver before doing anything.
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
call ale#lsp#NotifyForChanges(l:conn_id, l:root, a:buffer)
|
||||
call ale#lsp#NotifyForChanges(l:conn_id, a:buffer)
|
||||
endif
|
||||
|
||||
return l:details
|
||||
@ -211,7 +207,6 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:root = l:lsp_details.project_root
|
||||
|
||||
" Register a callback now for handling errors now.
|
||||
let l:Callback = function('ale#lsp_linter#HandleLSPResponse')
|
||||
@ -222,16 +217,16 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:message = ale#lsp#tsserver_message#Geterr(a:buffer)
|
||||
let l:notified = ale#lsp#Send(l:id, l:message, l:root) != 0
|
||||
let l:notified = ale#lsp#Send(l:id, l:message) != 0
|
||||
else
|
||||
let l:notified = ale#lsp#NotifyForChanges(l:id, l:root, a:buffer)
|
||||
let l:notified = ale#lsp#NotifyForChanges(l:id, a:buffer)
|
||||
endif
|
||||
|
||||
" If this was a file save event, also notify the server of that.
|
||||
if a:linter.lsp isnot# 'tsserver'
|
||||
\&& getbufvar(a:buffer, 'ale_save_event_fired', 0)
|
||||
let l:save_message = ale#lsp#message#DidSave(a:buffer)
|
||||
let l:notified = ale#lsp#Send(l:id, l:save_message, l:root) != 0
|
||||
let l:notified = ale#lsp#Send(l:id, l:save_message) != 0
|
||||
endif
|
||||
|
||||
if l:notified
|
||||
|
@ -23,6 +23,11 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
|
||||
return ale#Var(a:buffer, a:base_var_name . '_executable')
|
||||
endfunction
|
||||
|
||||
" As above, but curry the arguments so only the buffer number is required.
|
||||
function! ale#node#FindExecutableFunc(base_var_name, path_list) abort
|
||||
return {buf -> ale#node#FindExecutable(buf, a:base_var_name, a:path_list)}
|
||||
endfunction
|
||||
|
||||
" Create a executable string which executes a Node.js script command with a
|
||||
" Node.js executable if needed.
|
||||
"
|
||||
|
@ -24,6 +24,7 @@ function! ale#python#FindProjectRootIni(buffer) abort
|
||||
\|| filereadable(l:path . '/mypy.ini')
|
||||
\|| filereadable(l:path . '/pycodestyle.cfg')
|
||||
\|| filereadable(l:path . '/flake8.cfg')
|
||||
\|| filereadable(l:path . '/.flake8rc')
|
||||
\|| filereadable(l:path . '/Pipfile')
|
||||
\|| filereadable(l:path . '/Pipfile.lock')
|
||||
return l:path
|
||||
|
@ -64,6 +64,35 @@ function! ale#references#HandleLSPResponse(conn_id, response) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, line, column, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#references#HandleTSServerResponse')
|
||||
\ : function('ale#references#HandleLSPResponse')
|
||||
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:message = ale#lsp#tsserver_message#References(
|
||||
\ l:buffer,
|
||||
\ a:line,
|
||||
\ a:column
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, or the
|
||||
" references position probably won't make sense.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:buffer)
|
||||
|
||||
let l:message = ale#lsp#message#References(l:buffer, a:line, a:column)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message)
|
||||
|
||||
let s:references_map[l:request_id] = {}
|
||||
endfunction
|
||||
|
||||
function! s:FindReferences(linter) abort
|
||||
let l:buffer = bufnr('')
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
@ -79,35 +108,10 @@ function! s:FindReferences(linter) abort
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:root = l:lsp_details.project_root
|
||||
|
||||
function! OnReady(...) abort closure
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#references#HandleTSServerResponse')
|
||||
\ : function('ale#references#HandleLSPResponse')
|
||||
|
||||
call ale#lsp#RegisterCallback(l:id, l:Callback)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:message = ale#lsp#tsserver_message#References(
|
||||
\ l:buffer,
|
||||
\ l:line,
|
||||
\ l:column
|
||||
\)
|
||||
else
|
||||
" Send a message saying the buffer has changed first, or the
|
||||
" references position probably won't make sense.
|
||||
call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
|
||||
|
||||
let l:message = ale#lsp#message#References(l:buffer, l:line, l:column)
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
|
||||
|
||||
let s:references_map[l:request_id] = {}
|
||||
endfunction
|
||||
|
||||
call ale#lsp#WaitForCapability(l:id, l:root, 'references', function('OnReady'))
|
||||
call ale#lsp#WaitForCapability(l:id, 'references', function('s:OnReady', [
|
||||
\ a:linter, l:lsp_details, l:line, l:column
|
||||
\]))
|
||||
endfunction
|
||||
|
||||
function! ale#references#Find() abort
|
||||
|
@ -88,10 +88,10 @@ endfunction
|
||||
|
||||
function! ale#util#Open(filename, line, column, options) abort
|
||||
if get(a:options, 'open_in_tab', 0)
|
||||
call ale#util#Execute('tabedit ' . fnameescape(a:filename))
|
||||
call ale#util#Execute('tabedit +' . a:line . ' ' . fnameescape(a:filename))
|
||||
elseif bufnr(a:filename) isnot bufnr('')
|
||||
" Open another file only if we need to.
|
||||
call ale#util#Execute('edit ' . fnameescape(a:filename))
|
||||
call ale#util#Execute('edit +' . a:line . ' ' . fnameescape(a:filename))
|
||||
else
|
||||
normal! m`
|
||||
endif
|
||||
@ -268,7 +268,7 @@ endfunction
|
||||
" See :help sandbox
|
||||
function! ale#util#InSandbox() abort
|
||||
try
|
||||
let &equalprg=&equalprg
|
||||
let &l:equalprg=&l:equalprg
|
||||
catch /E48/
|
||||
" E48 is the sandbox error.
|
||||
return 1
|
||||
|
Reference in New Issue
Block a user