Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement hover window with floating window for Neovim 0.4.0 or later #767

Merged
merged 19 commits into from
Mar 26, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions autoload/LanguageClient.vim
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let s:TYPE = {
\ 'dict': type({}),
\ 'funcref': type(function('call'))
\ }
let s:FLOAT_WINDOW_AVAILABLE = has('nvim') && exists('*nvim_open_win')

function! s:AddPrefix(message) abort
return '[LC] ' . a:message
Expand Down Expand Up @@ -261,6 +262,154 @@ function! s:GetVar(...) abort
endif
endfunction

function! s:ShouldUseFloatWindow() abort
return s:FLOAT_WINDOW_AVAILABLE && get(g:, 'LanguageClient_useFloatingHover', 1)
rhysd marked this conversation as resolved.
Show resolved Hide resolved
endfunction

function! s:CloseFloatingHoverOnCursorMove(win_id, opened) abort
if getpos('.') == a:opened
" Just after opening floating window, CursorMoved event is run.
" To avoid closing floating window immediately, check the cursor
" was really moved
return
endif
silent! autocmd! plugin-LC-neovim-close-hover
let winnr = win_id2win(a:win_id)
if winnr == 0
return
endif
execute winnr . 'wincmd c'
endfunction

function! s:CloseFloatingHoverOnBufEnter(win_id, bufnr) abort
let winnr = win_id2win(a:win_id)
if winnr == 0
" Float window was already closed
silent! autocmd! plugin-LC-neovim-close-hover
return
endif
if winnr == winnr()
" Cursor is moving into floating window. Do not close it
return
endif
if bufnr('%') == a:bufnr
" When current buffer opened hover window, it's not another buffer. Skipped
return
endif
silent! autocmd! plugin-LC-neovim-close-hover
execute winnr . 'wincmd c'
endfunction

" Open preview window. Window is open in:
" - Floating window on Neovim (0.4.0 or later)
" - Preview window on Neovim (0.3.0 or earlier) or Vim
function! s:OpenHoverPreview(bufname, lines, filetype) abort
" Use local variable since parameter is not modifiable
let lines = a:lines
let bufnr = bufnr('%')

let use_float_win = s:ShouldUseFloatWindow()
if use_float_win
let pos = getpos('.')

" Calculate width and height and give margin to lines
let width = 0
for index in range(len(lines))
let line = lines[index]
if line !=# ''
" Give a left margin
let line = ' ' . line
endif
let lw = strdisplaywidth(line)
if lw > width
let width = lw
endif
let lines[index] = line
endfor

" Give margin
let width += 1
let lines = [''] + lines + ['']
let height = len(lines)

" Calculate anchor
" Prefer North, but if there is no space, fallback into South
let bottom_line = line('w0') + winheight(0) - 1
if pos[1] + height <= bottom_line
let vert = 'N'
let row = 1
else
let vert = 'S'
let row = 0
endif

" Prefer West, but if there is no space, fallback into East
if pos[2] + width <= &columns
let hor = 'W'
let col = 0
else
let hor = 'E'
let col = 1
endif

let float_win_id = nvim_open_win(bufnr, v:true, {
\ 'relative': 'cursor',
\ 'anchor': vert . hor,
\ 'row': row,
\ 'col': col,
\ 'width': width,
\ 'height': height,
\ })

execute 'noswapfile edit!' a:bufname

setlocal winhl=Normal:CursorLine
else
execute 'silent! noswapfile pedit!' a:bufname
wincmd P
endif

setlocal buftype=nofile nobuflisted bufhidden=wipe nonumber norelativenumber signcolumn=no

if a:filetype isnot v:null
let &filetype = a:filetype
endif

call setline(1, lines)
setlocal nomodified nomodifiable

wincmd p

if use_float_win
" Unlike preview window, :pclose does not close window. Instead, close
" hover window automatically when cursor is moved.
let call_after_move = printf('<SID>CloseFloatingHoverOnCursorMove(%d, %s)', float_win_id, string(pos))
let call_on_bufenter = printf('<SID>CloseFloatingHoverOnBufEnter(%d, %d)', float_win_id, bufnr)
augroup plugin-LC-neovim-close-hover
execute 'autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call ' . call_after_move
execute 'autocmd BufEnter * call ' . call_on_bufenter
augroup END
endif
endfunction

function! s:GetHoverPreviewBufnr() abort
for bufnr in range(1, bufnr('$'))
if bufname(bufnr) ==# '__LanguageClient__'
return bufnr
endif
endfor
return -1
endfunction

function! s:MoveIntoHoverPreview() abort
let winnr = bufwinnr(s:GetHoverPreviewBufnr())
if winnr == -1
return v:false
endif
execute winnr . 'wincmd w'
return v:true
endfunction

let s:id = 1
let s:handlers = {}

Expand Down Expand Up @@ -529,6 +678,9 @@ function! LanguageClient#Notify(method, params) abort
endfunction

function! LanguageClient#textDocument_hover(...) abort
if s:ShouldUseFloatWindow() && s:MoveIntoHoverPreview()
return
endif
let l:Callback = get(a:000, 1, v:null)
let l:params = {
\ 'filename': LSP#filename(),
Expand Down Expand Up @@ -1151,4 +1303,30 @@ function! LanguageClient#debugInfo(...) abort
return LanguageClient#Call('languageClient/debugInfo', l:params, l:Callback)
endfunction

function! LanguageClient#reopenHoverInSeparateWindow() abort
let bufnr = s:GetHoverPreviewBufnr()
if bufnr == -1
echo 'No hover found'
return
endif

let lines = nvim_buf_get_lines(bufnr, 1, -1, v:false)
let filetype = nvim_buf_get_option(bufnr, 'filetype')
let name = bufname(bufnr)

silent! autocmd! plugin-LC-neovim-close-hover
let winnr = bufwinnr(bufnr)
if winnr != -1
execute winnr . 'wincmd c'
endif

execute 'silent! noswapfile pedit!' name
wincmd P
setlocal buftype=nofile nobuflisted bufhidden=wipe nonumber norelativenumber signcolumn=no
let &filetype = filetype
call setline(1, lines)
setlocal nomodified nomodifiable
wincmd p
endfunction

let g:LanguageClient_loaded = s:Launch()
11 changes: 10 additions & 1 deletion autoload/health/LanguageClient.vim
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@ function! s:checkBinary() abort
\ l:path)
endif

let output = system([l:path, '--version'])
let output = substitute(system([l:path, '--version']), '\n$', '', '')
call health#report_ok(output)
endfunction

function! s:checkFloatingWindow() abort
if !exists('*nvim_open_win')
call health#report_info('Floating window is not supported. Preview window will be used for hover')
return
endif
call health#report_ok('Floating window is supported and will be used for hover')
endfunction

function! health#LanguageClient#check() abort
call s:checkJobFeature()
call s:checkBinary()
call s:checkFloatingWindow()
endfunction
23 changes: 23 additions & 0 deletions doc/LanguageClient.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ accessed by regular quickfix/location list operations.
To use the language server with Vim's formatting operator |gq|, set 'formatexpr': >
set formatexpr=LanguageClient#textDocument_rangeFormatting_sync()
<
If you're using Neovim 0.4.0 or later, |LanguageClient#textDocument_hover()|
rhysd marked this conversation as resolved.
Show resolved Hide resolved
opens documentation in a floating window. The window is automatically closed
when you move the cursor. Or calling |LanguageClient#textDocument_hover()|
again just after opening the floating window moves the cursor into the window.
It is useful when documentation is longer and you need to scroll down or you
want to yank some text in the documentation.

==============================================================================
2. Configuration *LanguageClientConfiguration*
Expand Down Expand Up @@ -348,6 +354,16 @@ Specify whether to use virtual text to display diagnostics.
Default: 1 whenever virtual text is supported.
Valid Options: 1 | 0

2.26 g:LanguageClient_useFloatingHover *g:LanguageClient_useFloatingHover*

When the value is set to 1, |LanguageClient#textDocument_hover()| opens
documentation in a floating window instead of preview.
This variable is effective only when the floating window feature is
supported.

Default: 1 when a floating window is supported, otherwise 0
Valid Options: 1 | 0

==============================================================================
3. Commands *LanguageClientCommands*

Expand Down Expand Up @@ -617,6 +633,13 @@ Signature: LanguageClient#debugInfo(...)

Print out debug info.

*LanguageClient#reopenHoverInSeparateWindow*
Signature: LanguageClient#reopenHoverInSeparateWindow()

When a hover is open in floating window, calling this function reopens the
content in a separate preview window. This function is useful when you want to
keep it open.

==============================================================================
5. Events *LanguageClientEvents*

Expand Down
25 changes: 5 additions & 20 deletions src/language_server_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,27 +860,12 @@ impl LanguageClient {
D: ToDisplay + ?Sized,
{
let bufname = "__LanguageClient__";

let cmd = "silent! pedit! +setlocal\\ buftype=nofile\\ nobuflisted\\ noswapfile\\ nonumber";
let cmd = if let Some(ref ft) = to_display.vim_filetype() {
format!("{}\\ filetype={} {}", cmd, ft, bufname)
} else {
format!("{} {}", cmd, bufname)
};
self.vim()?.command(cmd)?;

let filetype = &to_display.vim_filetype();
let lines = to_display.to_display();
if self.get(|state| state.is_nvim)? {
let bufnr: u64 = serde_json::from_value(self.vim()?.rpcclient.call("bufnr", bufname)?)?;
self.vim()?
.rpcclient
.notify("nvim_buf_set_lines", json!([bufnr, 0, -1, 0, lines]))?;
} else {
self.vim()?
.rpcclient
.notify("setbufline", json!([bufname, 1, lines]))?;
// TODO: removing existing bottom lines.
}

self.vim()?
.rpcclient
.notify("s:OpenHoverPreview", json!([bufname, lines, filetype]))?;

Ok(())
}
Expand Down
Loading