From 3a3c24472361f4a7567500b63e9122bfcd5eb252 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada <10496163+gagbo@users.noreply.github.com> Date: Wed, 16 May 2018 22:23:48 +0200 Subject: [PATCH] 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. --- autoload/ale/balloon.vim | 25 ++++++++++++-- autoload/ale/hover.vim | 74 ++++++++++++++++++++++++++-------------- plugin/ale.vim | 3 +- test/smoke_test.vader | 2 +- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim index 552ced82..d36dd4f5 100644 --- a/autoload/ale/balloon.vim +++ b/autoload/ale/balloon.vim @@ -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: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 function! ale#balloon#Expr() abort @@ -20,9 +26,22 @@ function! ale#balloon#Expr() abort endfunction function! ale#balloon#Disable() abort - set noballooneval balloonexpr= + set noballooneval noballoonevalterm + set balloonexpr= endfunction 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 diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim index 12af877d..3bf92488 100644 --- a/autoload/ale/hover.vim +++ b/autoload/ale/hover.vim @@ -24,7 +24,13 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort if get(a:response, 'success', v:false) is v:true \&& 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 endfunction @@ -34,15 +40,18 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort \&& has_key(s:hover_map, a:response.id) let l:options = remove(s:hover_map, a:response.id) - let l:buffer = bufnr('') - let [l:line, l:column] = getcurpos()[1:2] - let l:end = len(getline(l:line)) + " If the call did __not__ come from balloonexpr... + if !get(l:options, 'hover_from_balloonexpr', 0) + 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 - \|| l:line isnot l:options.line - \|| min([l:column, l:end]) isnot min([l:options.column, l:end]) - " Cancel display the message if the cursor has moved. - return + if l:buffer isnot l:options.buffer + \|| l:line isnot l:options.line + \|| min([l:column, l:end]) isnot min([l:options.column, l:end]) + " ... Cancel display the message if the cursor has moved. + return + endif endif " 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', '') 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 endfunction -function! s:ShowDetails(linter) abort - let l:buffer = bufnr('') - let [l:line, l:column] = getcurpos()[1:2] - +function! s:ShowDetails(linter, buffer, line, column, opt) abort let l:Callback = a:linter.lsp is# 'tsserver' \ ? function('ale#hover#HandleTSServerResponse') \ : 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) return 0 @@ -95,34 +107,46 @@ function! s:ShowDetails(linter) abort let l:root = l:lsp_details.project_root if a:linter.lsp is# 'tsserver' + let l:column = a:column + let l:message = ale#lsp#tsserver_message#Quickinfo( - \ l:buffer, - \ l:line, + \ 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#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 let l:request_id = ale#lsp#Send(l:id, l:message, l:root) let s:hover_map[l:request_id] = { - \ 'buffer': l:buffer, - \ 'line': l:line, + \ 'buffer': a:buffer, + \ 'line': a:line, \ 'column': l:column, + \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), \} endfunction -function! ale#hover#Show() abort - for l:linter in ale#linter#Get(&filetype) +" Obtain Hover information for the specified position +" 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) - call s:ShowDetails(l:linter) + call s:ShowDetails(l:linter, a:buffer, a:line, a:col, a:opt) endif endfor endfunction diff --git a/plugin/ale.vim b/plugin/ale.vim index a49bf68c..48ff531c 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -278,7 +278,8 @@ command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in_tab': 1 command! -bar ALEFindReferences :call ale#references#Find() " Get information for the cursor. -command! -bar ALEHover :call ale#hover#Show() +command! -bar ALEHover :call ale#hover#Show(bufnr(''), getcurpos()[1], + \ getcurpos()[2], {}) " mappings for commands nnoremap (ale_previous) :ALEPrevious diff --git a/test/smoke_test.vader b/test/smoke_test.vader index f6d0be56..843bddab 100644 --- a/test/smoke_test.vader +++ b/test/smoke_test.vader @@ -93,7 +93,7 @@ Execute(Linters should run in PowerShell too): \}) call ale#Lint() - call ale#engine#WaitForJobs(2000) + call ale#engine#WaitForJobs(4000) AssertEqual [ \ {