]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Add support for highlighting current symbol in the outline window
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 3 Jan 2021 20:00:49 +0000 (12:00 -0800)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 3 Jan 2021 20:00:49 +0000 (12:00 -0800)
autoload/handlers.vim
autoload/lsp.vim
autoload/lspserver.vim
plugin/lsp.vim

index c5a97a504ae2b914ca19914d0bda730ebc321d76..5bc7f803f975494ec0bf3229c33a68392eee72ee 100644 (file)
@@ -289,57 +289,29 @@ def LspSymbolKindToName(symkind: number): string
   return symbolMap[symkind]
 enddef
 
-# jump to a symbol selected in the symbols window
-def handlers#jumpToSymbol()
-  var lnum: number = line('.') - 1
-  if w:lsp_info.data[lnum]->empty()
+# process the 'textDocument/documentSymbol' reply from the LSP server
+# Open a symbols window and display the symbols as a tree
+def s:processDocSymbolReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
+  if reply.result->empty()
+    WarnMsg('No symbols are found')
     return
   endif
 
-  var slnum: number = w:lsp_info.data[lnum].lnum
-  var scol: number = w:lsp_info.data[lnum].col
-  var fname: string = w:lsp_info.filename
-
-  # If the file is already opened in a window, jump to it. Otherwise open it
-  # in another window
-  var wid: number = fname->bufwinid()
-  if wid == -1
-    # Find a window showing a normal buffer and use it
-    for w in getwininfo()
-      if w.winid->getwinvar('&buftype') == ''
-       wid = w.winid
-       wid->win_gotoid()
-       break
-      endif
-    endfor
-    if wid == -1
-      var symWinid: number = win_getid()
-      :rightbelow vnew
-      # retain the fixed symbol window width
-      win_execute(symWinid, 'vertical resize 20')
-    endif
-
-    exe 'edit ' .. fname
-  else
-    wid->win_gotoid()
-  endif
-  [slnum, scol]->cursor()
-enddef
-
-# display the list of document symbols from the LSP server in a window as a
-# tree
-def s:showSymbols(symTable: list<dict<any>>, uri: string)
-  var symbols: dict<list<dict<any>>>
-  var symbolType: string
   var fname: string
-  var r: dict<dict<number>>
+  var symbolTypeTable: dict<list<dict<any>>>
+  var symbolLineTable: list<dict<any>> = []
   var name: string
+  var symbolType: string
+  var r: dict<dict<number>>
+  var symbolDetail: string
+  var symInfo: dict<any>
 
-  if uri != ''
-    fname = LspUriToFile(uri)
+  if req.params.textDocument.uri != ''
+    fname = LspUriToFile(req.params.textDocument.uri)
   endif
 
-  for symbol in symTable
+  for symbol in reply.result
+    symbolDetail = ''
     if symbol->has_key('location')
       # interface SymbolInformation
       fname = LspUriToFile(symbol.location.uri)
@@ -356,56 +328,20 @@ def s:showSymbols(symTable: list<dict<any>>, uri: string)
       name = symbol.name
       symbolType = LspSymbolKindToName(symbol.kind)
       r = symbol.range
+      if symbol->has_key('detail')
+       symbolDetail = symbol.detail
+      endif
     endif
-    if !symbols->has_key(symbolType)
-      symbols[symbolType] = []
+    if !symbolTypeTable->has_key(symbolType)
+      symbolTypeTable[symbolType] = []
     endif
-    symbols[symbolType]->add({name: name,
-                     lnum: r.start.line + 1, col: r.start.character + 1})
+    symInfo = {name: name, range: r, detail: symbolDetail}
+    symbolTypeTable[symbolType]->add(symInfo)
+    symbolLineTable->add(symInfo)
   endfor
-
-  var wid: number = bufwinid('LSP-Symbols')
-  if wid == -1
-    :20vnew LSP-Symbols
-  else
-    win_gotoid(wid)
-  endif
-
-  :setlocal modifiable
-  :setlocal noreadonly
-  :silent! :%d _
-  :setlocal buftype=nofile
-  :setlocal bufhidden=delete
-  :setlocal noswapfile nobuflisted
-  :setlocal nonumber norelativenumber fdc=0 nowrap winfixheight winfixwidth
-  setline(1, ['# Language Server Symbols', '# ' .. fname])
-  # First two lines in the buffer display comment information
-  var lnumMap: list<dict<number>> = [{}, {}]
-  var text: list<string> = []
-  for [symType, syms] in items(symbols)
-    text->extend(['', symType])
-    lnumMap->extend([{}, {}])
-    for s in syms
-      text->add('  ' .. s.name)
-      lnumMap->add({lnum: s.lnum, col: s.col})
-    endfor
-  endfor
-  append(line('$'), text)
-  w:lsp_info = {filename: fname, data: lnumMap}
-  :nnoremap <silent> <buffer> q :quit<CR>
-  :nnoremap <silent> <buffer> <CR> :call handlers#jumpToSymbol()<CR>
-  :setlocal nomodifiable
-enddef
-
-# process the 'textDocument/documentSymbol' reply from the LSP server
-# Open a symbols window and display the symbols as a tree
-def s:processDocSymbolReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  if reply.result->empty()
-    WarnMsg('No symbols are found')
-    return
-  endif
-
-  s:showSymbols(reply.result, req.params.textDocument.uri)
+  # sort the symbols by line number
+  symbolLineTable->sort({a, b -> a.range.start.line - b.range.start.line})
+  lsp#updateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
 enddef
 
 # Returns the byte number of the specified line/col position.  Returns a
index a738bcd442bec9241af596b391ddd16be9299bce..83d11726fdebf66ed93ec55955d737fbf9a94a92 100644 (file)
@@ -508,8 +508,223 @@ def lsp#docHighlightClear()
   prop_remove({'type': 'LspWriteRef', 'all': v:true}, 1, line('$'))
 enddef
 
-# open a window and display all the symbols in a file
-def lsp#showDocSymbols()
+# jump to a symbol selected in the outline window
+def s:outlineJumpToSymbol()
+  var lnum: number = line('.') - 1
+  if w:lspSymbols.lnumTable[lnum]->empty()
+    return
+  endif
+
+  var slnum: number = w:lspSymbols.lnumTable[lnum].lnum
+  var scol: number = w:lspSymbols.lnumTable[lnum].col
+  var fname: string = w:lspSymbols.filename
+
+  # Highlight the selected symbol
+  prop_remove({type: 'LspOutlineHighlight'})
+  prop_add(line('.'), 3, {type: 'LspOutlineHighlight',
+                       length: w:lspSymbols.lnumTable[lnum].name->len()})
+
+  # disable the outline window refresh
+  skipOutlineRefresh = true
+
+  # If the file is already opened in a window, jump to it. Otherwise open it
+  # in another window
+  var wid: number = fname->bufwinid()
+  if wid == -1
+    # Find a window showing a normal buffer and use it
+    for w in getwininfo()
+      if w.winid->getwinvar('&buftype') == ''
+       wid = w.winid
+       wid->win_gotoid()
+       break
+      endif
+    endfor
+    if wid == -1
+      var symWinid: number = win_getid()
+      :rightbelow vnew
+      # retain the fixed symbol window width
+      win_execute(symWinid, 'vertical resize 20')
+    endif
+
+    exe 'edit ' .. fname
+  else
+    wid->win_gotoid()
+  endif
+  [slnum, scol]->cursor()
+  skipOutlineRefresh = false
+enddef
+
+var skipOutlineRefresh: bool = false
+
+# update the symbols displayed in the outline window
+def lsp#updateOutlineWindow(fname: string,
+                               symbolTypeTable: dict<list<dict<any>>>,
+                               symbolLineTable: list<dict<any>>)
+  var wid: number = bufwinid('LSP-Outline')
+  if wid == -1
+    return
+  endif
+
+  # stop refreshing the outline window recursively
+  skipOutlineRefresh = true
+
+  var prevWinID: number = win_getid()
+  win_gotoid(wid)
+
+  # if the file displayed in the outline window is same as the new file, then
+  # save and restore the cursor position
+  var symbols = getwinvar(wid, 'lspSymbols', {})
+  var saveCursor: list<number> = []
+  if !symbols->empty() && symbols.filename == fname
+    saveCursor = getcurpos()
+  endif
+
+  :setlocal modifiable
+  :silent! :%d _
+  setline(1, ['# File Outline', '# ' .. fname])
+
+  # First two lines in the buffer display comment information
+  var lnumMap: list<dict<any>> = [{}, {}]
+  var text: list<string> = []
+  for [symType, syms] in items(symbolTypeTable)
+    text->extend(['', symType])
+    lnumMap->extend([{}, {}])
+    for s in syms
+      text->add('  ' .. s.name)
+      # remember the line number for the symbol
+      lnumMap->add({name: s.name, lnum: s.range.start.line + 1,
+                                       col: s.range.start.character + 1})
+      s.outlineLine = lnumMap->len()
+    endfor
+  endfor
+  append('$', text)
+  w:lspSymbols = {filename: fname, lnumTable: lnumMap,
+                               symbolsByLine: symbolLineTable}
+  :setlocal nomodifiable
+
+  if !saveCursor->empty()
+    setpos('.', saveCursor)
+  endif
+  win_gotoid(prevWinID)
+
+  # Highlight the current symbol
+  s:outlineHighlightCurrentSymbol()
+
+  # re-enable refreshing the outline window
+  skipOutlineRefresh = false
+enddef
+
+def s:outlineHighlightCurrentSymbol()
+  var fname: string = fnamemodify(expand('%'), ':p')
+  if fname == '' || &filetype == ''
+    return
+  endif
+
+  var wid: number = bufwinid('LSP-Outline')
+  if wid == -1
+    return
+  endif
+
+  # Check whether the symbols for this file are displayed in the outline
+  # window
+  var lspSymbols = getwinvar(wid, 'lspSymbols', {})
+  if lspSymbols->empty() || lspSymbols.filename != fname
+    return
+  endif
+
+  var symbolTable: list<dict<any>> = lspSymbols.symbolsByLine
+
+  # line number to locate the symbol
+  var lnum: number = line('.')
+
+  # Find the symbol for the current line number (binary search)
+  var left: number = 0
+  var right: number = symbolTable->len() - 1
+  var mid: number
+  while left <= right
+    mid = (left + right) / 2
+    if lnum >= (symbolTable[mid].range.start.line + 1) &&
+               lnum <= (symbolTable[mid].range.end.line + 1)
+      break
+    endif
+    if lnum > (symbolTable[mid].range.start.line + 1)
+      left = mid + 1
+    else
+      right = mid - 1
+    endif
+  endwhile
+
+  # clear the highlighting in the outline window
+  var bnr: number = wid->winbufnr()
+  prop_remove({bufnr: bnr, type: 'LspOutlineHighlight'})
+
+  if left > right
+    # symbol not found
+    return
+  endif
+
+  # Highlight the selected symbol
+  prop_add(symbolTable[mid].outlineLine, 3,
+                       {bufnr: bnr, type: 'LspOutlineHighlight',
+                       length: symbolTable[mid].name->len()})
+
+  # if the line is not visible, then scroll the outline window to make the
+  # line visible
+  var wininfo = wid->getwininfo()
+  if symbolTable[mid].outlineLine < wininfo[0].topline
+                       || symbolTable[mid].outlineLine > wininfo[0].botline
+    var cmd: string = 'call cursor(' ..
+                       symbolTable[mid].outlineLine .. ', 1) | normal z.'
+    win_execute(wid, cmd)
+  endif
+enddef
+
+# when the outline window is closed, do the cleanup
+def s:outlineCleanup()
+  # Remove the outline autocommands
+  :silent! autocmd! LSPOutline
+enddef
+
+# open the symbol outline window
+def s:openOutlineWindow()
+  var wid: number = bufwinid('LSP-Outline')
+  if wid != -1
+    return
+  endif
+
+  var prevWinID: number = win_getid()
+
+  :topleft :20vnew LSP-Outline
+  :setlocal modifiable
+  :setlocal noreadonly
+  :silent! :%d _
+  :setlocal buftype=nofile
+  :setlocal bufhidden=delete
+  :setlocal noswapfile nobuflisted
+  :setlocal nonumber norelativenumber fdc=0 nowrap winfixheight winfixwidth
+  setline(1, ['# File Outline'])
+  :nnoremap <silent> <buffer> q :quit<CR>
+  :nnoremap <silent> <buffer> <CR> :call <SID>outlineJumpToSymbol()<CR>
+  :setlocal nomodifiable
+
+  prop_type_add('LspOutlineHighlight', {bufnr: bufnr(), highlight: 'Search'})
+
+  augroup LSPOutline
+    au!
+    autocmd BufEnter * call s:requestDocSymbols()
+    # when the outline window is closed, do the cleanup
+    autocmd BufUnload LSP-Outline call s:outlineCleanup()
+    autocmd CursorHold * call s:outlineHighlightCurrentSymbol()
+  augroup END
+
+  win_gotoid(prevWinID)
+enddef
+
+def s:requestDocSymbols()
+  if skipOutlineRefresh
+    return
+  endif
+
   var ftype = &filetype
   if ftype == ''
     return
@@ -517,11 +732,9 @@ def lsp#showDocSymbols()
 
   var lspserver: dict<any> = s:lspGetServer(ftype)
   if lspserver->empty()
-    ErrMsg('Error: LSP server for "' .. ftype .. '" filetype is not found')
     return
   endif
   if !lspserver.running
-    ErrMsg('Error: LSP server for "' .. ftype .. '" filetype is not running')
     return
   endif
 
@@ -530,7 +743,13 @@ def lsp#showDocSymbols()
     return
   endif
 
-  lspserver.showDocSymbols(fname)
+  lspserver.getDocSymbols(fname)
+enddef
+
+# open a window and display all the symbols in a file (outline)
+def lsp#outline()
+  s:openOutlineWindow()
+  s:requestDocSymbols()
 enddef
 
 # Format the entire file
@@ -699,7 +918,7 @@ def s:filterSymbols(lspserver: dict<any>, popupID: number, key: string): bool
 enddef
 
 # Jump to the location of a symbol selected in the popup menu
-def s:jumpToSymbol(popupID: number, result: number): void
+def s:jumpToWorkspaceSymbol(popupID: number, result: number): void
   # clear the message displayed at the command-line
   echo ''
 
@@ -750,7 +969,7 @@ def s:showSymbolMenu(lspserver: dict<any>, query: string)
       fixed: 1,
       close: "button",
       filter: function('s:filterSymbols', [lspserver]),
-      callback: function('s:jumpToSymbol')
+      callback: function('s:jumpToWorkspaceSymbol')
   }
   lspserver.workspaceSymbolPopup = popup_menu([], popupAttr)
   lspserver.workspaceSymbolQuery = query
index 571779aca24b43c166b98c207f2c79ff313e2686..c82f73150fa55d5f62d10abcd5ef04ba13ab81f0 100644 (file)
@@ -513,7 +513,7 @@ def s:docHighlight(lspserver: dict<any>): void
   lspserver.sendMessage(req)
 enddef
 
-def s:showDocSymbols(lspserver: dict<any>, fname: string): void
+def s:getDocSymbols(lspserver: dict<any>, fname: string): void
   # Check whether LSP server supports getting document symbol information
   if !lspserver.caps->has_key('documentSymbolProvider')
                        || !lspserver.caps.documentSymbolProvider
@@ -758,7 +758,7 @@ export def NewLspServer(path: string, args: list<string>): dict<any>
     hover: function('s:hover', [lspserver]),
     showReferences: function('s:showReferences', [lspserver]),
     docHighlight: function('s:docHighlight', [lspserver]),
-    showDocSymbols: function('s:showDocSymbols', [lspserver]),
+    getDocSymbols: function('s:getDocSymbols', [lspserver]),
     textDocFormat: function('s:textDocFormat', [lspserver]),
     renameSymbol: function('s:renameSymbol', [lspserver]),
     codeAction: function('s:codeAction', [lspserver]),
index f2e396f290f6529bc97ff377ffe8084e2712086d..7f68c823c12d23245b2719a221d55ebd39a6c8ed 100644 (file)
@@ -29,7 +29,7 @@ command! -nargs=0 -bar LspShowDiagnostics call lsp#showDiagnostics()
 command! -nargs=0 -bar LspShowReferences call lsp#showReferences()
 command! -nargs=0 -bar LspHighlight call lsp#docHighlight()
 command! -nargs=0 -bar LspHighlightClear call lsp#docHighlightClear()
-command! -nargs=0 -bar LspShowDocSymbols call lsp#showDocSymbols()
+command! -nargs=0 -bar LspOutline call lsp#outline()
 command! -nargs=0 -bar -range=% LspFormat call lsp#textDocFormat(<range>, <line1>, <line2>)
 command! -nargs=0 -bar LspCalledBy call lsp#incomingCalls()
 command! -nargs=0 -bar LspCalling call lsp#outgoingCalls()