TUI / GUI tooltip with content from ALEHover (#1556)

* Guard the ballooneval settings

* Mark main objectives to do to get nice Hover

* Make tweaks to make the tooltip work - See " XXX: comments

* Guard balloon_show call

* Use return instead of finish for functions

* ale#hover#show : Add optional arguments to specify arbtirary position

This change is requested to be able to call the function with mouse
position to enable hover information in vim's balloon

* ale#ballon#Disable : Remove feature guards

* ale#balloon : Show 'ALEHover' output on balloon if no diagnostic found

* ale#hover#HandleLSPResponse : remove the check for cursor position

This check prevented the 'ALEHover in balloon' feature, since mouse
position is almost never cursor position.

* ale#balloon#MessageForPos : Change the return of balloonexpr

balloonexpr evaluation now works even without balloon_show for basic
diagnostics, leaving the balloon_show call to ale#hover#Show, which can
then feature guard the call to avoid errors

* ale#hover#Response : Feature guard balloon_show calls

* ale#hover : always display 'Hover' information in messages

Also add a small comment to warn readers the different outputs the
ale#hover#Show will write to

* {LSP,TS}Response : use only variables from the Response

It is clearer that we only rely on l:options to get the relevant data to
build the LSP Response string

* hover#ShowDetails : fix an issue where not having focus broke balloons

The issue was caused by not using a buffer-specific version of getline()
to cap the value of the column sent in the message to LSP. Therefore a
cursor on column 10 in an inactive window could send a message with
column=0, if the active window had a buffer with too few lines

* {LSP,TS}Response : Remove redundant checks for balloon_show call

With the upcoming change in ale_set_balloons default value (see Pull
Request w0rp/ale#1565), this check will be useless

* balloonexpr? : Add a flag to separate hover#Show() calls

The goal of this flag is to make `:ALEHover` calls not pop a balloon
under the cursor, since the user has probably no interest in their
cursor while typing the command

The flag is a default argument which is overridden only in ballonexpr
call of ale#hover#Show, and stays set in the hover_map until the
callback for the LSP handles it.

There are no automated tests for this feature right now, and the nature
of the addition (one optional argument in the API) should make it
transparent to existing tests.

Since the differentiation is now possible, the check for moved cursor
has been put back in ale#hover#HandleLSPResponse

* ale#hover#hover_map : Protect accesses to hover_map

Using get() is safer than trying to access directly with ., as the tests
show.

* Raise timeout to try to get Appveyor happy

* Review : Fix comments

* Review : pass the optional argument 'called_from_balloonexpr' in a Dict

This optional dictionary has documentation just before the function
using it, ale#hover#Show, and allows easier extension in the future.
This commit is contained in:
Gerry Agbobada 2018-05-16 22:23:48 +02:00 committed by w0rp
parent c1da7866d0
commit 3a3c244723
4 changed files with 74 additions and 30 deletions

View File

@ -12,7 +12,13 @@ function! ale#balloon#MessageForPos(bufnr, lnum, col) abort
let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist
let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col) let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col)
return l:index >= 0 ? l:loclist[l:index].text : '' " Show the diagnostics message if found, 'Hover' output otherwise
if l:index >= 0
return l:loclist[l:index].text
else
call ale#hover#Show(a:bufnr, a:lnum, a:col, {'called_from_balloonexpr': 1})
return ''
endif
endfunction endfunction
function! ale#balloon#Expr() abort function! ale#balloon#Expr() abort
@ -20,9 +26,22 @@ function! ale#balloon#Expr() abort
endfunction endfunction
function! ale#balloon#Disable() abort function! ale#balloon#Disable() abort
set noballooneval balloonexpr= set noballooneval noballoonevalterm
set balloonexpr=
endfunction endfunction
function! ale#balloon#Enable() abort function! ale#balloon#Enable() abort
set ballooneval balloonexpr=ale#balloon#Expr() if !has('balloon_eval') && !has('balloon_eval_term')
return
endif
if has('balloon_eval')
set ballooneval
endif
if has('balloon_eval_term')
set balloonevalterm
endif
set balloonexpr=ale#balloon#Expr()
endfunction endfunction

View File

@ -24,7 +24,13 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
if get(a:response, 'success', v:false) is v:true if get(a:response, 'success', v:false) is v:true
\&& get(a:response, 'body', v:null) isnot v:null \&& get(a:response, 'body', v:null) isnot v:null
call ale#util#ShowMessage(a:response.body.displayString) if get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show')
\&& ale#Var(l:options.buffer, 'set_balloons')
call balloon_show(a:response.body.displayString)
else
call ale#util#ShowMessage(a:response.body.displayString)
endif
endif endif
endif endif
endfunction endfunction
@ -34,15 +40,18 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
\&& has_key(s:hover_map, a:response.id) \&& has_key(s:hover_map, a:response.id)
let l:options = remove(s:hover_map, a:response.id) let l:options = remove(s:hover_map, a:response.id)
let l:buffer = bufnr('') " If the call did __not__ come from balloonexpr...
let [l:line, l:column] = getcurpos()[1:2] if !get(l:options, 'hover_from_balloonexpr', 0)
let l:end = len(getline(l:line)) let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2]
let l:end = len(getline(l:line))
if l:buffer isnot l:options.buffer if l:buffer isnot l:options.buffer
\|| l:line isnot l:options.line \|| l:line isnot l:options.line
\|| min([l:column, l:end]) isnot min([l:options.column, l:end]) \|| min([l:column, l:end]) isnot min([l:options.column, l:end])
" Cancel display the message if the cursor has moved. " ... Cancel display the message if the cursor has moved.
return return
endif
endif endif
" The result can be a Dictionary item, a List of the same, or null. " The result can be a Dictionary item, a List of the same, or null.
@ -71,21 +80,24 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
let l:str = substitute(l:str, '^\s*\(.\{-}\)\s*$', '\1', '') let l:str = substitute(l:str, '^\s*\(.\{-}\)\s*$', '\1', '')
if !empty(l:str) if !empty(l:str)
call ale#util#ShowMessage(l:str) if get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show')
\&& ale#Var(l:options.buffer, 'set_balloons')
call balloon_show(l:str)
else
call ale#util#ShowMessage(l:str)
endif
endif endif
endif endif
endif endif
endfunction endfunction
function! s:ShowDetails(linter) abort function! s:ShowDetails(linter, buffer, line, column, opt) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2]
let l:Callback = a:linter.lsp is# 'tsserver' let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#hover#HandleTSServerResponse') \ ? function('ale#hover#HandleTSServerResponse')
\ : function('ale#hover#HandleLSPResponse') \ : function('ale#hover#HandleLSPResponse')
let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) let l:lsp_details = ale#linter#StartLSP(a:buffer, a:linter, l:Callback)
if empty(l:lsp_details) if empty(l:lsp_details)
return 0 return 0
@ -95,34 +107,46 @@ function! s:ShowDetails(linter) abort
let l:root = l:lsp_details.project_root let l:root = l:lsp_details.project_root
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'
let l:column = a:column
let l:message = ale#lsp#tsserver_message#Quickinfo( let l:message = ale#lsp#tsserver_message#Quickinfo(
\ l:buffer, \ a:buffer,
\ l:line, \ a:line,
\ l:column \ l:column
\) \)
else else
" Send a message saying the buffer has changed first, or the " Send a message saying the buffer has changed first, or the
" hover position probably won't make sense. " hover position probably won't make sense.
call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) call ale#lsp#Send(l:id, ale#lsp#message#DidChange(a:buffer), l:root)
let l:column = min([l:column, len(getline(l:line))]) let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])])
let l:message = ale#lsp#message#Hover(l:buffer, l:line, l:column) let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column)
endif endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:root) let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
let s:hover_map[l:request_id] = { let s:hover_map[l:request_id] = {
\ 'buffer': l:buffer, \ 'buffer': a:buffer,
\ 'line': l:line, \ 'line': a:line,
\ 'column': l:column, \ 'column': l:column,
\ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
\} \}
endfunction endfunction
function! ale#hover#Show() abort " Obtain Hover information for the specified position
for l:linter in ale#linter#Get(&filetype) " Pass optional arguments in the dictionary opt.
" Currently, only one key/value is useful:
" - called_from_balloonexpr, this flag marks if we want the result from this
" ale#hover#Show to display in a balloon if possible
"
" Currently, the callbacks displays the info from hover :
" - in the balloon if opt.called_from_balloonexpr and balloon_show is detected
" - as status message otherwise
function! ale#hover#Show(buffer, line, col, opt) abort
for l:linter in ale#linter#Get(getbufvar(a:buffer, '&filetype'))
if !empty(l:linter.lsp) if !empty(l:linter.lsp)
call s:ShowDetails(l:linter) call s:ShowDetails(l:linter, a:buffer, a:line, a:col, a:opt)
endif endif
endfor endfor
endfunction endfunction

View File

@ -278,7 +278,8 @@ command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in_tab': 1
command! -bar ALEFindReferences :call ale#references#Find() command! -bar ALEFindReferences :call ale#references#Find()
" Get information for the cursor. " Get information for the cursor.
command! -bar ALEHover :call ale#hover#Show() command! -bar ALEHover :call ale#hover#Show(bufnr(''), getcurpos()[1],
\ getcurpos()[2], {})
" <Plug> mappings for commands " <Plug> mappings for commands
nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return>

View File

@ -93,7 +93,7 @@ Execute(Linters should run in PowerShell too):
\}) \})
call ale#Lint() call ale#Lint()
call ale#engine#WaitForJobs(2000) call ale#engine#WaitForJobs(4000)
AssertEqual [ AssertEqual [
\ { \ {