diff --git a/README.md b/README.md index 4f5ea3894..3bdbccbf2 100644 --- a/README.md +++ b/README.md @@ -62,22 +62,26 @@ For more information, refer to the readme and documentation of the respective pl | Command | Description| |--|--| |`:LspCodeAction`| Gets a list of possible commands that can be applied to a file so it can be fixed (quick fix) | -|`:LspDeclaration`| Go to declaration | -|`:LspDefinition`| Go to definition | +|`:LspDeclaration`| Go to the declaration of the word under the cursor, and open in the current window | +|`:LspDefinition`| Go to the definition of the word under the cursor, and open in the current window | |`:LspDocumentDiagnostics`| Get current document diagnostics information | |`:LspDocumentFormat`| Format entire document | |`:LspDocumentRangeFormat`| Format document selection | |`:LspDocumentSymbol`| Show document symbols | |`:LspHover`| Show hover information | -|`:LspImplementation` | Show implementation of interface | +|`:LspImplementation` | Show implementation of interface in the current window | |`:LspNextError`| jump to next error | |`:LspNextReference`| jump to next reference to the symbol under cursor | +|`:LspPeekDeclaration`| Go to the declaration of the word under the cursor, but open in preview window | +|`:LspPeekDefinition`| Go to the definition of the word under the cursor, but open in preview window | +|`:LspPeekImplementation`| Go to the implementation of an interface, but open in preview window | +|`:LspPeekTypeDefinition`| Go to the type definition of the word under the cursor, but open in preview window | |`:LspPreviousError`| jump to previous error | |`:LspPreviousReference`| jump to previous reference to the symbol under cursor | |`:LspReferences`| Find references | |`:LspRename`| Rename symbol | |`:LspStatus` | Show the status of the language server | -|`:LspTypeDefinition`| Go to type definition | +|`:LspTypeDefinition`| Go to the type definition of the word under the cursor, and open in the current window | |`:LspWorkspaceSymbol`| Search/Show workspace symbol | ### Diagnostics diff --git a/autoload/lsp/ui/vim.vim b/autoload/lsp/ui/vim.vim index c424ee6e5..9c4fd9918 100644 --- a/autoload/lsp/ui/vim.vim +++ b/autoload/lsp/ui/vim.vim @@ -4,7 +4,7 @@ function! s:not_supported(what) abort return lsp#utils#error(a:what.' not supported for '.&filetype) endfunction -function! lsp#ui#vim#implementation() abort +function! lsp#ui#vim#implementation(in_preview) abort let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_implementation_provider(v:val)') let s:last_req_id = s:last_req_id + 1 call setqflist([]) @@ -13,7 +13,7 @@ function! lsp#ui#vim#implementation() abort call s:not_supported('Retrieving implementation') return endif - let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1 } + let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1, 'in_preview': a:in_preview } for l:server in l:servers call lsp#send_request(l:server, { \ 'method': 'textDocument/implementation', @@ -28,7 +28,7 @@ function! lsp#ui#vim#implementation() abort echo 'Retrieving implementation ...' endfunction -function! lsp#ui#vim#type_definition() abort +function! lsp#ui#vim#type_definition(in_preview) abort let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_type_definition_provider(v:val)') let s:last_req_id = s:last_req_id + 1 call setqflist([]) @@ -37,7 +37,7 @@ function! lsp#ui#vim#type_definition() abort call s:not_supported('Retrieving type definition') return endif - let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1 } + let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1, 'in_preview': a:in_preview } for l:server in l:servers call lsp#send_request(l:server, { \ 'method': 'textDocument/typeDefinition', @@ -52,7 +52,7 @@ function! lsp#ui#vim#type_definition() abort echo 'Retrieving type definition ...' endfunction -function! lsp#ui#vim#declaration() abort +function! lsp#ui#vim#declaration(in_preview) abort let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_declaration_provider(v:val)') let s:last_req_id = s:last_req_id + 1 call setqflist([]) @@ -62,7 +62,7 @@ function! lsp#ui#vim#declaration() abort return endif - let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1 } + let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1, 'in_preview': a:in_preview } for l:server in l:servers call lsp#send_request(l:server, { \ 'method': 'textDocument/declaration', @@ -77,7 +77,7 @@ function! lsp#ui#vim#declaration() abort echo 'Retrieving declaration ...' endfunction -function! lsp#ui#vim#definition() abort +function! lsp#ui#vim#definition(in_preview) abort let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_definition_provider(v:val)') let s:last_req_id = s:last_req_id + 1 call setqflist([]) @@ -87,7 +87,7 @@ function! lsp#ui#vim#definition() abort return endif - let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1 } + let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 1, 'in_preview': a:in_preview } for l:server in l:servers call lsp#send_request(l:server, { \ 'method': 'textDocument/definition', @@ -108,7 +108,7 @@ function! lsp#ui#vim#references() abort call setqflist([]) - let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 0 } + let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_req_id': s:last_req_id, 'jump_if_one': 0, 'in_preview': 0 } if len(l:servers) == 0 call s:not_supported('Retrieving references') return @@ -418,7 +418,7 @@ function! s:handle_symbol(server, last_req_id, type, data) abort endif endfunction -function! s:handle_location(ctx, server, type, data) abort "ctx = {counter, list, jump_if_one, last_req_id} +function! s:handle_location(ctx, server, type, data) abort "ctx = {counter, list, jump_if_one, last_req_id, in_preview} if a:ctx['last_req_id'] != s:last_req_id return endif @@ -443,9 +443,10 @@ function! s:handle_location(ctx, server, type, data) abort "ctx = {counter, list call settagstack(winid, {'curidx': len(gettagstack(winid)['items']) + 1}) endif - if len(a:ctx['list']) == 1 && a:ctx['jump_if_one'] + let l:loc = a:ctx['list'][0] + + if len(a:ctx['list']) == 1 && a:ctx['jump_if_one'] && !a:ctx['in_preview'] normal! m' - let l:loc = a:ctx['list'][0] let l:buffer = bufnr(l:loc['filename']) if &modified && !&hidden let l:cmd = l:buffer !=# -1 ? 'sb ' . l:buffer : 'split ' . fnameescape(l:loc['filename']) @@ -455,10 +456,17 @@ function! s:handle_location(ctx, server, type, data) abort "ctx = {counter, list execute l:cmd . ' | call cursor('.l:loc['lnum'].','.l:loc['col'].')' echo 'Retrieved ' . a:type redraw - else + elseif !a:ctx['in_preview'] call setqflist(a:ctx['list']) echo 'Retrieved ' . a:type botright copen + else + let l:lines = readfile(fnameescape(l:loc['filename'])) + call lsp#ui#vim#output#preview(l:lines, { + \ 'statusline': ' LSP Peek ' . a:type, + \ 'cursor': { 'line': l:loc['lnum'], 'col': l:loc['col'], 'align': g:lsp_peek_alignment }, + \ 'filetype': &filetype + \ }) endif endif endif diff --git a/autoload/lsp/ui/vim/hover.vim b/autoload/lsp/ui/vim/hover.vim index 4166d7783..5454f97e6 100644 --- a/autoload/lsp/ui/vim/hover.vim +++ b/autoload/lsp/ui/vim/hover.vim @@ -35,7 +35,7 @@ function! s:handle_hover(server, data) abort endif if !empty(a:data['response']['result']) && !empty(a:data['response']['result']['contents']) - call lsp#ui#vim#output#preview(a:data['response']['result']['contents']) + call lsp#ui#vim#output#preview(a:data['response']['result']['contents'], {'statusline': ' LSP Hover'}) return else call lsp#utils#error('No hover information found') diff --git a/autoload/lsp/ui/vim/output.vim b/autoload/lsp/ui/vim/output.vim index 38098a3a0..9e4d84bd9 100644 --- a/autoload/lsp/ui/vim/output.vim +++ b/autoload/lsp/ui/vim/output.vim @@ -190,7 +190,91 @@ function! s:open_preview(data) abort return l:winid endfunction -function! lsp#ui#vim#output#preview(data) abort +function! s:set_cursor(current_window_id, options) abort + if !has_key(a:options, 'cursor') + return + endif + + if s:supports_floating && g:lsp_preview_float && has('nvim') + " Neovim floats + " Go back to the preview window to set the cursor + call win_gotoid(s:winid) + let l:old_scrolloff = &scrolloff + let &scrolloff = 0 + + call nvim_win_set_cursor(s:winid, [a:options['cursor']['line'], a:options['cursor']['col']]) + call s:align_preview(a:options) + + " Finally, go back to the original window + call win_gotoid(a:current_window_id) + + let &scrolloff = l:old_scrolloff + elseif s:supports_floating && g:lsp_preview_float && !has('nvim') + " Vim popups + function! AlignVimPopup(timer) closure abort + call s:align_preview(a:options) + endfunction + call timer_start(0, function('AlignVimPopup')) + else + " Preview + " Don't use 'scrolloff', it might mess up the cursor's position + let &l:scrolloff = 0 + call cursor(a:options['cursor']['line'], a:options['cursor']['col']) + call s:align_preview(a:options) + endif +endfunction + +function! s:align_preview(options) abort + if !has_key(a:options, 'cursor') || + \ !has_key(a:options['cursor'], 'align') + return + endif + + let l:align = a:options['cursor']['align'] + + if s:supports_floating && g:lsp_preview_float && !has('nvim') + " Vim popups + let l:pos = popup_getpos(s:winid) + let l:below = winline() < winheight(0) / 2 + if l:below + let l:height = min([l:pos['core_height'], winheight(0) - winline() - 2]) + else + let l:height = min([l:pos['core_height'], winline() - 3]) + endif + let l:width = l:pos['core_width'] + + let l:options = { + \ 'minwidth': l:width, + \ 'maxwidth': l:width, + \ 'minheight': l:height, + \ 'maxheight': l:height, + \ 'pos': l:below ? 'topleft' : 'botleft', + \ 'line': l:below ? 'cursor+1' : 'cursor-1' + \ } + + if l:align ==? 'top' + let l:options['firstline'] = a:options['cursor']['line'] + elseif l:align ==? 'center' + let l:options['firstline'] = a:options['cursor']['line'] - (l:height - 1) / 2 + elseif l:align ==? 'bottom' + let l:options['firstline'] = a:options['cursor']['line'] - l:height + 1 + endif + + call popup_setoptions(s:winid, l:options) + redraw! + else + " Preview and Neovim floats + if l:align ==? 'top' + normal! zt + elseif l:align ==? 'center' + normal! zz + elseif l:align ==? 'bottom' + normal! zb + endif + endif +endfunction + +function! lsp#ui#vim#output#preview(data, options) abort if s:winid && type(s:preview_data) == type(a:data) \ && s:preview_data == a:data \ && type(g:lsp_preview_doubletap) == 3 @@ -209,21 +293,40 @@ function! lsp#ui#vim#output#preview(data) abort let s:preview_data = a:data let l:lines = [] let l:ft = s:append(a:data, l:lines) + + if has_key(a:options, 'filetype') + let l:ft = a:options['filetype'] + endif + call s:setcontent(l:lines, l:ft) " Get size information while still having the buffer active let l:bufferlines = line('$') let l:maxwidth = max(map(getline(1, '$'), 'strdisplaywidth(v:val)')) - " restore focus to the previous window + if !s:supports_floating || !g:lsp_preview_float + " Set statusline + if has_key(a:options, 'statusline') + let &l:statusline = a:options['statusline'] + endif + + call s:set_cursor(l:current_window_id, a:options) + endif + + " Go to the previous window to adjust positioning call win_gotoid(l:current_window_id) echo '' if s:supports_floating && s:winid && g:lsp_preview_float if has('nvim') + " Neovim floats call s:adjust_float_placement(l:bufferlines, l:maxwidth) + call s:set_cursor(l:current_window_id, a:options) call s:add_float_closing_hooks() + else + " Vim popups + call s:set_cursor(l:current_window_id, a:options) endif doautocmd User lsp_float_opened endif @@ -243,7 +346,7 @@ function! s:append(data, lines) abort return 'markdown' elseif type(a:data) == type('') - call extend(a:lines, split(a:data, "\n")) + call extend(a:lines, split(a:data, "\n", v:true)) return 'markdown' elseif type(a:data) == type({}) && has_key(a:data, 'language') @@ -253,7 +356,7 @@ function! s:append(data, lines) abort return 'markdown' elseif type(a:data) == type({}) && has_key(a:data, 'kind') - call extend(a:lines, split(a:data.value, '\n')) + call extend(a:lines, split(a:data.value, '\n', v:true)) return a:data.kind ==? 'plaintext' ? 'text' : a:data.kind endif diff --git a/doc/vim-lsp.txt b/doc/vim-lsp.txt index fc67a1549..61224b156 100644 --- a/doc/vim-lsp.txt +++ b/doc/vim-lsp.txt @@ -27,6 +27,7 @@ CONTENTS *vim-lsp-contents* g:lsp_highlight_references_enabled |g:lsp_highlight_references_enabled| g:lsp_get_vim_completion_item |g:lsp_get_vim_completion_item| g:lsp_get_supported_capabilities |g:lsp_get_supported_capabilities| + g:lsp_peek_alignment |g:lsp_peek_alignment| g:lsp_preview_max_width |g:lsp_preview_max_width| Functions |vim-lsp-functions| enable |vim-lsp-enable| @@ -49,6 +50,10 @@ CONTENTS *vim-lsp-contents* LspHover |LspHover| LspNextError |LspNextError| LspNextReference |LspNextReference| + LspPeekDeclaration |LspPeekDeclaration| + LspPeekDefinition |LspPeekDefinition| + LspPeekImplementation |LspPeekImplementation| + LspPeekTypeDefinition |LspPeekTypeDefinition| LspPreviousError |LspPreviousError| LspPreviousReference |LspPreviousReference| LspImplementation |LspImplementation| @@ -409,6 +414,15 @@ g:lsp_get_supported_capabilities *g:lsp_get_supported_capabilities* calling `lsp#omni#default_get_supported_capabilities` from within your function. +g:lsp_peek_alignment *g:lsp_peek_alignment* + Type: |String| + Default: `"center"` + + Determines how to align the location of interest for e.g. + |LspPeekDefinition|. Three values are possible: `"top"`, `"center"` and + `"bottom"`, which place the location of interest at the first, middle and + last lines of the preview/popup/floating window, respectively. + g:lsp_preview_max_width *g:lsp_preview_max_width* Type: |Number| Default: `-1` @@ -652,10 +666,14 @@ LspDeclaration *LspDeclaration* Go to declaration. Useful for languages such as C/C++ where there is a clear distinction between declaration and definition. +Also see |LspPeekDeclaration|. + LspDefinition *LspDefinition* Go to definition. +Also see |LspPeekDefinition|. + LspDocumentFormat *LspDocumentFormat* Format the entire document. @@ -698,6 +716,34 @@ LspNextReference *LspNextReference* Jump to the next reference of the symbol under cursor. +LspPeekDeclaration *LspPeekDeclaration* + +Like |LspDeclaration|, but opens the declaration in the |preview-window| +instead of the current window. + +Also see |g:lsp_peek_alignment| and |g:lsp_preview_float|. + +LspPeekDefinition *LspPeekDefinition* + +Like |LspDefinition|, but opens the definition in the |preview-window| instead +of the current window. + +Also see |g:lsp_peek_alignment| and |g:lsp_preview_float|. + +LspPeekImplementation *LspPeekImplementation* + +Like |LspImplementation|, but opens the implementation in the |preview-window| +instead of the current window. + +Also see |g:lsp_peek_alignment| and |g:lsp_preview_float|. + +LspPeekTypeDefinition *LspPeekTypeDefinition* + +Like |LspTypeDefinition|, but opens the type definition in the +|preview-window| instead of the current window. + +Also see |g:lsp_peek_alignment| and |g:lsp_preview_float|. + LspPreviousError *LspPreviousError* Jump to Previous err diagnostics @@ -710,6 +756,8 @@ LspImplementation *LspImplementation* Find all implementation of interface. +Also see |LspPeekImplementation|. + LspReferences *LspReferences* Find all references. @@ -722,6 +770,8 @@ LspTypeDefinition *LspTypeDefinition* Go to the type definition. +Also see |LspPeekTypeDefinition|. + LspWorkspaceSymbol *LspWorkspaceSymbol* Search and show workspace symbols. @@ -756,7 +806,9 @@ Available plug mappings are following: (lsp-code-action) (lsp-declaration) + (lsp-peek-declaration) (lsp-definition) + (lsp-peek-definition) (lsp-document-symbol) (lsp-document-diagnostics) (lsp-hover) @@ -772,7 +824,9 @@ Available plug mappings are following: (lsp-document-format) (lsp-document-format) (lsp-implementation) + (lsp-peek-implementation) (lsp-type-definition) + (lsp-peek-type-definition) (lsp-status) See also |vim-lsp-commands| diff --git a/ftplugin/lsp-hover.vim b/ftplugin/lsp-hover.vim index 67557341c..eef089eb0 100644 --- a/ftplugin/lsp-hover.vim +++ b/ftplugin/lsp-hover.vim @@ -6,14 +6,13 @@ if has('patch-8.1.1517') && g:lsp_preview_float && !has('nvim') else setlocal previewwindow buftype=nofile bufhidden=wipe noswapfile nobuflisted endif -setlocal nocursorline nofoldenable +setlocal nocursorline nofoldenable nonumber norelativenumber if has('syntax') setlocal nospell endif -let &l:statusline = ' LSP Hover' - let b:undo_ftplugin = 'setlocal pvw< bt< bh< swf< bl< cul< fen<' . \ (has('syntax') ? ' spell<' : '') . + \ ' number< relativenumber<' . \ ' | unlet! g:markdown_fenced_languages' diff --git a/plugin/lsp.vim b/plugin/lsp.vim index cfa71e53f..c50c96111 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -28,6 +28,7 @@ let g:lsp_highlight_references_enabled = get(g:, 'lsp_highlight_references_enabl let g:lsp_preview_float = get(g:, 'lsp_preview_float', 1) let g:lsp_preview_autoclose = get(g:, 'lsp_preview_autoclose', 1) let g:lsp_preview_doubletap = get(g:, 'lsp_preview_doubletap', [function('lsp#ui#vim#output#focuspreview')]) +let g:lsp_peek_alignment = get(g:, 'lsp_peek_alignment', 'center') let g:lsp_preview_max_width = get(g:, 'lsp_preview_max_width', -1) let g:lsp_get_vim_completion_item = get(g:, 'lsp_get_vim_completion_item', [function('lsp#omni#default_get_vim_completion_item')]) @@ -41,8 +42,10 @@ if g:lsp_auto_enable endif command! -range LspCodeAction call lsp#ui#vim#code_action() -command! LspDeclaration call lsp#ui#vim#declaration() -command! LspDefinition call lsp#ui#vim#definition() +command! LspDeclaration call lsp#ui#vim#declaration(0) +command! LspPeekDeclaration call lsp#ui#vim#declaration(1) +command! LspDefinition call lsp#ui#vim#definition(0) +command! LspPeekDefinition call lsp#ui#vim#definition(1) command! LspDocumentSymbol call lsp#ui#vim#document_symbol() command! LspDocumentDiagnostics call lsp#ui#vim#diagnostics#document_diagnostics() command! -nargs=? -complete=customlist,lsp#utils#empty_complete LspHover call lsp#ui#vim#hover#get_hover_under_cursor() @@ -50,21 +53,25 @@ command! LspNextError call lsp#ui#vim#diagnostics#next_error() command! LspPreviousError call lsp#ui#vim#diagnostics#previous_error() command! LspReferences call lsp#ui#vim#references() command! LspRename call lsp#ui#vim#rename() -command! LspTypeDefinition call lsp#ui#vim#type_definition() +command! LspTypeDefinition call lsp#ui#vim#type_definition(0) +command! LspPeekTypeDefinition call lsp#ui#vim#type_definition(1) command! LspWorkspaceSymbol call lsp#ui#vim#workspace_symbol() command! -range LspDocumentFormat call lsp#ui#vim#document_format() command! -range LspDocumentFormatSync call lsp#ui#vim#document_format_sync() command! -range LspDocumentRangeFormat call lsp#ui#vim#document_range_format() command! -range LspDocumentRangeFormatSync call lsp#ui#vim#document_range_format_sync() -command! LspImplementation call lsp#ui#vim#implementation() +command! LspImplementation call lsp#ui#vim#implementation(0) +command! LspPeekImplementation call lsp#ui#vim#implementation(1) command! LspTypeDefinition call lsp#ui#vim#type_definition() command! -nargs=0 LspStatus echo lsp#get_server_status() command! LspNextReference call lsp#ui#vim#references#jump(+1) command! LspPreviousReference call lsp#ui#vim#references#jump(-1) nnoremap (lsp-code-action) :call lsp#ui#vim#code_action() -nnoremap (lsp-declaration) :call lsp#ui#vim#declaration() -nnoremap (lsp-definition) :call lsp#ui#vim#definition() +nnoremap (lsp-declaration) :call lsp#ui#vim#declaration(0) +nnoremap (lsp-peek-declaration) :call lsp#ui#vim#declaration(1) +nnoremap (lsp-definition) :call lsp#ui#vim#definition(0) +nnoremap (lsp-peek-definition) :call lsp#ui#vim#definition(1) nnoremap (lsp-document-symbol) :call lsp#ui#vim#document_symbol() nnoremap (lsp-document-diagnostics) :call lsp#ui#vim#diagnostics#document_diagnostics() nnoremap (lsp-hover) :call lsp#ui#vim#hover#get_hover_under_cursor() @@ -74,11 +81,13 @@ nnoremap (lsp-next-error) :call lsp#ui#vim#diagnostics#next_error()(lsp-previous-error) :call lsp#ui#vim#diagnostics#previous_error() nnoremap (lsp-references) :call lsp#ui#vim#references() nnoremap (lsp-rename) :call lsp#ui#vim#rename() -nnoremap (lsp-type-definition) :call lsp#ui#vim#type_definition() +nnoremap (lsp-type-definition) :call lsp#ui#vim#type_definition(0) +nnoremap (lsp-peek-type-definition) :call lsp#ui#vim#type_definition(1) nnoremap (lsp-workspace-symbol) :call lsp#ui#vim#workspace_symbol() nnoremap (lsp-document-format) :call lsp#ui#vim#document_format() vnoremap (lsp-document-format) :silent call lsp#ui#vim#document_range_format() -nnoremap (lsp-implementation) :call lsp#ui#vim#implementation() +nnoremap (lsp-implementation) :call lsp#ui#vim#implementation(0) +nnoremap (lsp-peek-implementation) :call lsp#ui#vim#implementation(1) nnoremap (lsp-status) :echo lsp#get_server_status() nnoremap (lsp-next-reference) :call lsp#ui#vim#references#jump(+1) nnoremap (lsp-previous-reference) :call lsp#ui#vim#references#jump(-1)