3 # Functions for dealing with symbols.
4 # - LSP symbol menu and for searching symbols across the workspace.
6 # - jump to a symbol definition, declaration, type definition or
9 import './options.vim' as opt
11 import './outline.vim'
13 # Initialize the highlight group and the text property type used for
14 # document symbol search
16 # Use a high priority value to override other highlights in the line
18 {name: 'LspSymbolName', default: true, linksto: 'Search'},
19 {name: 'LspSymbolRange', default: true, linksto: 'Visual'}
21 prop_type_add('LspSymbolNameProp', {highlight: 'LspSymbolName',
25 prop_type_add('LspSymbolRangeProp', {highlight: 'LspSymbolRange',
31 # Handle keys pressed when the workspace symbol popup menu is displayed
32 def FilterSymbols(lspserver: dict<any>, popupID: number, key: string): bool
33 var key_handled: bool = false
34 var update_popup: bool = false
35 var query: string = lspserver.workspaceSymbolQuery
37 if key == "\<BS>" || key == "\<C-H>"
38 # Erase one character from the filter text
44 elseif key == "\<C-U>"
45 # clear the filter text
49 elseif key == "\<C-F>"
52 || key == "\<PageDown>"
57 # scroll the popup window
58 var cmd: string = 'normal! ' .. (key == "\<C-N>" ? 'j' : key == "\<C-P>" ? 'k' : key)
59 win_execute(popupID, cmd)
61 elseif key == "\<Up>" || key == "\<Down>"
62 # Use native Vim handling for these keys
64 elseif key =~ '^\f$' || key == "\<Space>"
65 # Filter the names based on the typed key and keys typed before
72 # Update the popup with the new list of symbol names
73 popupID->popup_settext('')
75 lspserver.workspaceQuery(query, false)
77 []->setwinvar(popupID, 'LspSymbolTable')
79 :echo $'Symbol: {query}'
82 # Update the workspace symbol query string
83 lspserver.workspaceSymbolQuery = query
89 return popupID->popup_filter_menu(key)
92 # Jump to the location of a symbol selected in the popup menu
93 def JumpToWorkspaceSymbol(cmdmods: string, popupID: number, result: number): void
94 # clear the message displayed at the command-line
102 var symTbl: list<dict<any>> = popupID->getwinvar('LspSymbolTable', [])
107 # Save the current location in the tag stack
108 util.PushCursorToTagStack()
110 # if the selected file is already present in a window, then jump to it
111 var fname: string = symTbl[result - 1].file
112 var bnr = fname->bufnr()
114 var winList: list<number> = bnr->win_findbuf()
116 # Not present in any window
117 if &modified || &buftype != ''
118 # the current buffer is modified or is not a normal buffer, then
119 # open the file in a new window
120 exe $'split {symTbl[result - 1].file}'
122 exe $'confirm edit {symTbl[result - 1].file}'
125 # If the target buffer is opened in the current window, then don't
128 # If the target buffer is opened in a window in the current tab
130 var winID = fname->bufwinid()
132 # not present in the current tab page. Use the first window.
139 exe $'{cmdmods} split {symTbl[result - 1].file}'
141 # Set the previous cursor location mark. Instead of using setpos(), m' is
142 # used so that the current location is added to the jump list.
144 setcursorcharpos(symTbl[result - 1].pos.line + 1,
145 util.GetCharIdxWithoutCompChar(bufnr(),
146 symTbl[result - 1].pos) + 1)
153 # display a list of symbols from the workspace
154 def ShowSymbolMenu(lspserver: dict<any>, query: string, cmdmods: string)
155 # Create the popup menu
156 var lnum = &lines - &cmdheight - 2 - 10
158 title: 'Workspace Symbol Search',
170 filter: function(FilterSymbols, [lspserver]),
171 callback: function('JumpToWorkspaceSymbol', [cmdmods])
173 lspserver.workspaceSymbolPopup = popup_menu([], popupAttr)
174 lspserver.workspaceSymbolQuery = query
175 prop_type_add('lspworkspacesymbol',
176 {bufnr: lspserver.workspaceSymbolPopup->winbufnr(),
178 :echo $'Symbol: {query}'
181 # Convert a file name to <filename> (<dirname>) format.
182 # Make sure the popup doesn't occupy the entire screen by reducing the width.
183 def MakeMenuName(popupWidth: number, fname: string): string
184 var filename: string = fname->fnamemodify(':t')
185 var flen: number = filename->len()
186 var dirname: string = fname->fnamemodify(':h')
188 if fname->len() > popupWidth && flen < popupWidth
189 # keep the full file name and reduce directory name length
190 # keep some characters at the beginning and end (equally).
191 # 6 spaces are used for "..." and " ()"
192 var dirsz = (popupWidth - flen - 6) / 2
193 dirname = dirname[: dirsz] .. '...' .. dirname[-dirsz : ]
195 var str: string = filename
197 str ..= $' ({dirname}/)'
202 # process the 'workspace/symbol' reply from the LSP server
203 # Result: SymbolInformation[] | null
204 export def WorkspaceSymbolPopup(lspserver: dict<any>, query: string,
205 symInfo: list<dict<any>>, cmdmods: string)
206 var symbols: list<dict<any>> = []
207 var symbolType: string
211 # Create a symbol popup menu if it is not present
212 if lspserver.workspaceSymbolPopup->winbufnr() == -1
213 ShowSymbolMenu(lspserver, query, cmdmods)
216 for symbol in symInfo
217 if !symbol->has_key('location')
218 # ignore entries without location information
222 # interface SymbolInformation
223 fileName = util.LspUriToFile(symbol.location.uri)
225 symName = symbol.name
226 if symbol->has_key('containerName') && symbol.containerName != ''
227 symName = $'{symbol.containerName}::{symName}'
229 symName ..= $' [{SymbolKindToName(symbol.kind)}]'
230 symName ..= ' ' .. MakeMenuName(
231 lspserver.workspaceSymbolPopup->popup_getpos().core_width,
234 symbols->add({name: symName,
236 pos: symbol.location.range.start})
238 symbols->setwinvar(lspserver.workspaceSymbolPopup, 'LspSymbolTable')
239 lspserver.workspaceSymbolPopup->popup_settext(
240 symbols->copy()->mapnew('v:val.name'))
243 # map the LSP symbol kind number to string
244 export def SymbolKindToName(symkind: number): string
245 var symbolMap: list<string> = [
277 return symbolMap[symkind]
280 def UpdatePeekFilePopup(lspserver: dict<any>, locations: list<dict<any>>)
281 if lspserver.peekSymbolPopup->winbufnr() == -1
285 lspserver.peekSymbolFilePopup->popup_close()
287 var n = line('.', lspserver.peekSymbolPopup) - 1
288 var [uri, range] = util.LspLocationParse(locations[n])
289 var fname: string = util.LspUriToFile(uri)
291 var bnr: number = fname->bufnr()
293 bnr = fname->bufadd()
297 title: $"{fname->fnamemodify(':t')} ({fname->fnamemodify(':h')})",
302 minwidth: winwidth(0) - 38,
303 maxwidth: winwidth(0) - 38,
311 lspserver.peekSymbolFilePopup = popup_create(bnr, popupAttrs)
312 var rstart = range.start
313 var cmds =<< trim eval END
315 [{rstart.line + 1}, 1]->cursor()
318 win_execute(lspserver.peekSymbolFilePopup, cmds)
320 lspserver.peekSymbolFilePopup->clearmatches()
321 var start_col = util.GetLineByteFromPos(bnr, rstart) + 1
322 var end_col = util.GetLineByteFromPos(bnr, range.end)
323 var pos = [[rstart.line + 1,
324 start_col, end_col - start_col + 1]]
325 matchaddpos('Search', pos, 10, -1, {window: lspserver.peekSymbolFilePopup})
328 def LocPopupFilter(lspserver: dict<any>, locations: list<dict<any>>,
329 popup_id: number, key: string): bool
330 popup_filter_menu(popup_id, key)
331 if lspserver.peekSymbolPopup->winbufnr() == -1
332 if lspserver.peekSymbolFilePopup->winbufnr() != -1
333 lspserver.peekSymbolFilePopup->popup_close()
335 lspserver.peekSymbolPopup = -1
336 lspserver.peekSymbolFilePopup = -1
338 UpdatePeekFilePopup(lspserver, locations)
343 def LocPopupCallback(lspserver: dict<any>, locations: list<dict<any>>,
344 popup_id: number, selIdx: number)
345 if lspserver.peekSymbolFilePopup->winbufnr() != -1
346 lspserver.peekSymbolFilePopup->popup_close()
348 lspserver.peekSymbolPopup = -1
350 util.PushCursorToTagStack()
351 util.JumpToLspLocation(locations[selIdx - 1], '')
355 # Display the locations in a popup menu. Display the corresponding file in
356 # an another popup window.
357 def PeekLocations(lspserver: dict<any>, locations: list<dict<any>>,
359 if lspserver.peekSymbolPopup->winbufnr() != -1
360 # If the symbol popup window is already present, close it.
361 lspserver.peekSymbolPopup->popup_close()
364 var w: number = &columns
365 var fnamelen = float2nr(w * 0.4)
367 var curlnum = line('.')
370 var menuItems: list<string> = []
372 var [uri, range] = util.LspLocationParse(loc)
373 var fname: string = util.LspUriToFile(uri)
374 var bnr: number = fname->bufnr()
376 bnr = fname->bufadd()
378 :silent! bnr->bufload()
380 var lnum = range.start.line + 1
381 var text: string = bnr->getbufline(lnum)->get(0, '')
382 menuItems->add($'{lnum}: {text}')
395 col: winwidth(0) - 34,
402 filter: function(LocPopupFilter, [lspserver, locations]),
403 callback: function(LocPopupCallback, [lspserver, locations])
405 lspserver.peekSymbolPopup = popup_menu(menuItems, popupAttrs)
406 # Select the current symbol in the menu
407 var cmds =<< trim eval END
408 [{curSymIdx}, 1]->cursor()
410 win_execute(lspserver.peekSymbolPopup, cmds, 'silent!')
411 UpdatePeekFilePopup(lspserver, locations)
414 export def ShowLocations(lspserver: dict<any>, locations: list<dict<any>>,
415 peekSymbol: bool, title: string)
417 PeekLocations(lspserver, locations, title)
421 # create a loclist the location of the locations
422 var qflist: list<dict<any>> = []
424 var [uri, range] = util.LspLocationParse(loc)
425 var fname: string = util.LspUriToFile(uri)
426 var bnr: number = fname->bufnr()
428 bnr = fname->bufadd()
430 :silent! bnr->bufload()
431 var rstart = range.start
432 var text: string = bnr->getbufline(rstart.line + 1)->get(0, '')->trim("\t ", 1)
433 qflist->add({filename: fname,
434 lnum: rstart.line + 1,
435 col: util.GetLineByteFromPos(bnr, rstart) + 1,
439 var save_winid = win_getid()
441 if opt.lspOptions.useQuickfixForLocations
442 setqflist([], ' ', {title: title, items: qflist})
443 var mods: string = ''
446 setloclist(0, [], ' ', {title: title, items: qflist})
447 var mods: string = ''
451 if !opt.lspOptions.keepFocusInReferences
452 save_winid->win_gotoid()
456 # Key filter callback function used for the symbol popup window.
457 # Vim doesn't close the popup window when the escape key is pressed.
458 # This is function supports that.
459 def SymbolFilterCB(lspserver: dict<any>, id: number, key: string): bool
461 lspserver.peekSymbolPopup->popup_close()
468 # Display the file specified by LSP "LocationLink" in a popup window and
469 # highlight the range in "location".
470 def PeekSymbolLocation(lspserver: dict<any>, location: dict<any>)
471 var [uri, range] = util.LspLocationParse(location)
472 var fname = util.LspUriToFile(uri)
473 var bnum = fname->bufadd()
475 # Failed to create or find a buffer
478 :silent! bnum->bufload()
480 if lspserver.peekSymbolPopup->winbufnr() != -1
481 # If the symbol popup window is already present, close it.
482 lspserver.peekSymbolPopup->popup_close()
484 var CbFunc = function(SymbolFilterCB, [lspserver])
486 title: $"{fnamemodify(fname, ':t')} ({fnamemodify(fname, ':h')})",
498 lspserver.peekSymbolPopup = popup_atcursor(bnum, popupAttrs)
500 # Highlight the symbol name and center the line in the popup
501 var pwid = lspserver.peekSymbolPopup
502 var pwbuf = pwid->winbufnr()
503 var pos: list<number> = []
504 var start_col: number
506 var rstart = range.start
507 start_col = util.GetLineByteFromPos(pwbuf, rstart) + 1
508 end_col = util.GetLineByteFromPos(pwbuf, range.end) + 1
509 pos->add(rstart.line + 1)
510 pos->extend([start_col, end_col - start_col])
511 matchaddpos('Search', [pos], 10, 101, {window: pwid})
512 var cmds =<< trim eval END
514 [{rstart.line + 1}, 1]->cursor()
517 win_execute(pwid, cmds, 'silent!')
520 # Jump to the definition, declaration or implementation of a symbol.
521 # Also, used to peek at the definition, declaration or implementation of a
523 export def GotoSymbol(lspserver: dict<any>, location: dict<any>,
524 peekSymbol: bool, cmdmods: string)
526 PeekSymbolLocation(lspserver, location)
528 # Save the current cursor location in the tag stack.
529 util.PushCursorToTagStack()
530 util.JumpToLspLocation(location, cmdmods)
534 # Process the LSP server reply message for a 'textDocument/definition' request
535 # and return a list of Dicts in a format accepted by the 'tagfunc' option.
536 export def TagFunc(lspserver: dict<any>,
537 taglocations: list<dict<any>>,
538 pat: string): list<dict<any>>
539 var retval: list<dict<any>>
541 for tagloc in taglocations
545 var [uri, range] = util.LspLocationParse(tagloc)
546 tagitem.filename = util.LspUriToFile(uri)
547 var bnr = util.LspUriToBufnr(uri)
548 var rstart = range.start
549 var startByteIdx = util.GetLineByteFromPos(bnr, rstart)
550 tagitem.cmd = $"/\\%{rstart.line + 1}l\\%{startByteIdx + 1}c"
558 # process SymbolInformation[]
559 def ProcessSymbolInfoTable(lspserver: dict<any>,
561 symbolInfoTable: list<dict<any>>,
562 symbolTypeTable: dict<list<dict<any>>>,
563 symbolLineTable: list<dict<any>>)
565 var symbolType: string
567 var r: dict<dict<number>>
568 var symInfo: dict<any>
570 for syminfo in symbolInfoTable
571 fname = util.LspUriToFile(syminfo.location.uri)
572 symbolType = SymbolKindToName(syminfo.kind)
574 if syminfo->has_key('containerName')
575 if syminfo.containerName != ''
576 name ..= $' [{syminfo.containerName}]'
579 r = syminfo.location.range
580 lspserver.decodeRange(bnr, r)
582 if !symbolTypeTable->has_key(symbolType)
583 symbolTypeTable[symbolType] = []
585 symInfo = {name: name, range: r}
586 symbolTypeTable[symbolType]->add(symInfo)
587 symbolLineTable->add(symInfo)
591 # process DocumentSymbol[]
592 def ProcessDocSymbolTable(lspserver: dict<any>,
594 docSymbolTable: list<dict<any>>,
595 symbolTypeTable: dict<list<dict<any>>>,
596 symbolLineTable: list<dict<any>>)
597 var symbolType: string
599 var r: dict<dict<number>>
600 var symInfo: dict<any>
601 var symbolDetail: string
602 var childSymbols: dict<list<dict<any>>>
604 for syminfo in docSymbolTable
606 symbolType = SymbolKindToName(syminfo.kind)
607 r = syminfo.selectionRange
608 lspserver.decodeRange(bnr, r)
609 if syminfo->has_key('detail')
610 symbolDetail = syminfo.detail
612 if !symbolTypeTable->has_key(symbolType)
613 symbolTypeTable[symbolType] = []
616 if syminfo->has_key('children')
617 ProcessDocSymbolTable(lspserver, bnr, syminfo.children, childSymbols,
620 symInfo = {name: name, range: r, detail: symbolDetail,
621 children: childSymbols}
622 symbolTypeTable[symbolType]->add(symInfo)
623 symbolLineTable->add(symInfo)
627 # process the 'textDocument/documentSymbol' reply from the LSP server
628 # Open a symbols window and display the symbols as a tree
629 # Result: DocumentSymbol[] | SymbolInformation[] | null
630 export def DocSymbolOutline(lspserver: dict<any>, docSymbol: any, fname: string)
631 var bnr = fname->bufnr()
632 var symbolTypeTable: dict<list<dict<any>>> = {}
633 var symbolLineTable: list<dict<any>> = []
635 if docSymbol->empty()
636 # No symbols defined for this file. Clear the outline window.
637 outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
641 if docSymbol[0]->has_key('location')
642 # SymbolInformation[]
643 ProcessSymbolInfoTable(lspserver, bnr, docSymbol, symbolTypeTable,
647 ProcessDocSymbolTable(lspserver, bnr, docSymbol, symbolTypeTable,
651 # sort the symbols by line number
652 symbolLineTable->sort((a, b) => a.range.start.line - b.range.start.line)
653 outline.UpdateOutlineWindow(fname, symbolTypeTable, symbolLineTable)
656 # Process the list of symbols (LSP interface "SymbolInformation") in
657 # "symbolInfoTable". For each symbol, create the name to display in the popup
658 # menu along with the symbol range and return the List.
659 def GetSymbolsInfoTable(lspserver: dict<any>,
661 symbolInfoTable: list<dict<any>>): list<dict<any>>
662 var symbolTable: list<dict<any>> = []
663 var symbolType: string
665 var containerName: string
666 var r: dict<dict<number>>
668 for syminfo in symbolInfoTable
669 symbolType = SymbolKindToName(syminfo.kind)
670 name = $'{symbolType} : {syminfo.name}'
671 if syminfo->has_key('containerName') && !syminfo.containerName->empty()
672 name ..= $' [{syminfo.containerName}]'
674 r = syminfo.location.range
675 lspserver.decodeRange(bnr, r)
677 symbolTable->add({name: name, range: r, selectionRange: {}})
683 # Process the list of symbols (LSP interface "DocumentSymbol") in
684 # "docSymbolTable". For each symbol, create the name to display in the popup
685 # menu along with the symbol range and return the List in "symbolTable"
686 def GetSymbolsDocSymbol(lspserver: dict<any>,
688 docSymbolTable: list<dict<any>>,
689 symbolTable: list<dict<any>>,
690 parentName: string = '')
691 var symbolType: string
693 var r: dict<dict<number>>
694 var sr: dict<dict<number>>
695 var symInfo: dict<any>
697 for syminfo in docSymbolTable
698 var symName = syminfo.name
699 symbolType = SymbolKindToName(syminfo.kind)->tolower()
700 sr = syminfo.selectionRange
701 lspserver.decodeRange(bnr, sr)
703 lspserver.decodeRange(bnr, r)
704 name = $'{symbolType} : {symName}'
706 name ..= $' [{parentName}]'
708 # TODO: Should include syminfo.detail? Will it clutter the menu?
709 symInfo = {name: name, range: r, selectionRange: sr}
710 symbolTable->add(symInfo)
712 if syminfo->has_key('children')
713 # Process all the child symbols
714 GetSymbolsDocSymbol(lspserver, bnr, syminfo.children, symbolTable,
720 # Highlight the name and the range of lines for the symbol at symTbl[symIdx]
721 def SymbolHighlight(symTbl: list<dict<any>>, symIdx: number)
722 prop_remove({type: 'LspSymbolNameProp', all: true})
723 prop_remove({type: 'LspSymbolRangeProp', all: true})
728 var r = symTbl[symIdx].range
732 var rangeStart = r.start
734 var start_lnum = rangeStart.line + 1
735 var start_col = rangeStart.character + 1
736 var end_lnum = rangeEnd.line + 1
738 var last_lnum = line('$')
739 if end_lnum > line('$')
741 end_col = col([last_lnum, '$'])
743 end_col = rangeEnd.character + 1
745 prop_add(start_lnum, start_col,
746 {type: 'LspSymbolRangeProp',
749 cursor(start_lnum, 1)
752 var sr = symTbl[symIdx].selectionRange
756 rangeStart = sr.start
758 prop_add(rangeStart.line + 1, 1,
759 {type: 'LspSymbolNameProp',
760 start_col: rangeStart.character + 1,
761 end_lnum: rangeEnd.line + 1,
762 end_col: rangeEnd.character + 1})
765 # Callback invoked when an item is selected in the symbol popup menu
766 # "symTbl" - list of symbols
767 # "symInputPopup" - Symbol search input popup window ID
768 # "save_curpos" - Cursor position before invoking the symbol search. If the
769 # symbol search is canceled, restore the cursor to this
771 def SymbolMenuItemSelected(symPopupMenu: number,
773 var symTblFiltered = symPopupMenu->getwinvar('symbolTableFiltered', [])
774 var symInputPopup = symPopupMenu->getwinvar('symbolInputPopup', 0)
775 var save_curpos = symPopupMenu->getwinvar('saveCurPos', [])
777 # Restore the cursor to the location where the command was invoked
778 setpos('.', save_curpos)
781 # A symbol is selected in the popup menu
783 # Set the previous cursor location mark. Instead of using setpos(), m' is
784 # used so that the current location is added to the jump list.
787 # Jump to the selected symbol location
788 var r = symTblFiltered[result - 1].selectionRange
790 # SymbolInformation doesn't have the selectionRange field
791 r = symTblFiltered[result - 1].range
793 setcursorcharpos(r.start.line + 1,
794 util.GetCharIdxWithoutCompChar(bufnr(), r.start) + 1)
797 symInputPopup->popup_close()
798 prop_remove({type: 'LspSymbolNameProp', all: true})
799 prop_remove({type: 'LspSymbolRangeProp', all: true})
802 # Key filter function for the symbol popup menu.
803 def SymbolMenuFilterKey(symPopupMenu: number,
805 var keyHandled = false
806 var updateInputPopup = false
807 var inputText = symPopupMenu->getwinvar('inputText', '')
808 var symInputPopup = symPopupMenu->getwinvar('symbolInputPopup', 0)
810 if key == "\<BS>" || key == "\<C-H>"
811 # Erase a character in the input popup
812 if inputText->len() >= 1
813 inputText = inputText[: -2]
815 updateInputPopup = true
817 elseif key == "\<C-U>"
818 # Erase all the characters in the input popup
821 updateInputPopup = true
822 elseif key == "\<C-F>"
824 || key == "\<PageUp>"
825 || key == "\<PageDown>"
826 || key == "\<C-Home>"
830 # scroll the symbol popup window
831 var cmd: string = 'normal! ' .. (key == "\<C-N>" ? 'j' :
832 key == "\<C-P>" ? 'k' : key)
833 win_execute(symPopupMenu, cmd)
836 # A keyword character is typed. Add to the input text and update the
840 updateInputPopup = true
843 var symTblFiltered: list<dict<any>> = []
844 symTblFiltered = symPopupMenu->getwinvar('symbolTableFiltered', [])
847 # Update the input popup with the new text and update the symbol popup
848 # window with the matching symbol names.
849 symInputPopup->popup_settext(inputText)
851 var symbolTable = symPopupMenu->getwinvar('symbolTable')
852 symTblFiltered = symbolTable->deepcopy()
853 var symbolMatchPos: list<list<number>> = []
855 # Get the list of symbols fuzzy matching the entered text
857 var t = symTblFiltered->matchfuzzypos(inputText, {key: 'name'})
858 symTblFiltered = t[0]
859 symbolMatchPos = t[1]
862 var popupText: list<dict<any>>
863 var text: list<dict<any>>
864 if !symbolMatchPos->empty()
865 # Generate a list of symbol names and the corresponding text properties
866 # to highlight the matching characters.
867 popupText = symTblFiltered->mapnew((idx, val): dict<any> => ({
869 props: symbolMatchPos[idx]->mapnew((_, w: number): dict<any> => ({
872 type: 'LspSymbolMatch'}
876 popupText = symTblFiltered->mapnew((idx, val): dict<string> => {
877 return {text: val.name}
880 symPopupMenu->popup_settext(popupText)
882 # Select the first symbol and highlight the corresponding text range
883 win_execute(symPopupMenu, 'cursor(1, 1)')
884 SymbolHighlight(symTblFiltered, 0)
887 # Save the filtered symbol table and the search text in popup window
889 setwinvar(symPopupMenu, 'inputText', inputText)
890 setwinvar(symPopupMenu, 'symbolTableFiltered', symTblFiltered)
893 # Use the default handler for the key
894 symPopupMenu->popup_filter_menu(key)
897 # Highlight the name and range of the selected symbol
898 var lnum = line('.', symPopupMenu) - 1
900 SymbolHighlight(symTblFiltered, lnum)
906 # Display the symbols popup menu
907 def SymbolPopupMenu(symbolTable: list<dict<any>>)
908 var curLine = line('.')
911 # Get the names of all the symbols. Also get the index of the symbol under
913 var symNames = symbolTable->mapnew((idx, val): string => {
915 if !r->empty() && curSymIdx == 0
916 if curLine >= r.start.line + 1 && curLine <= r.end.line + 1
923 var symInputPopupAttr = {
924 title: 'Select Symbol',
937 var symInputPopup = popup_create('', symInputPopupAttr)
939 var symNamesPopupattr = {
949 border: [0, 0, 0, 0],
950 callback: SymbolMenuItemSelected,
951 filter: SymbolMenuFilterKey,
953 var symPopupMenu = popup_menu(symNames, symNamesPopupattr)
955 # Save the state in the popup menu window variables
956 setwinvar(symPopupMenu, 'symbolTable', symbolTable)
957 setwinvar(symPopupMenu, 'symbolTableFiltered', symbolTable->deepcopy())
958 setwinvar(symPopupMenu, 'symbolInputPopup', symInputPopup)
959 setwinvar(symPopupMenu, 'saveCurPos', getcurpos())
960 prop_type_add('LspSymbolMatch', {bufnr: symPopupMenu->winbufnr(),
964 # Start with the symbol under the cursor
965 var cmds =<< trim eval END
966 [{curSymIdx + 1}, 1]->cursor()
969 win_execute(symPopupMenu, cmds, 'silent!')
971 # Highlight the name and range of the first symbol
972 SymbolHighlight(symbolTable, curSymIdx)
975 # process the 'textDocument/documentSymbol' reply from the LSP server
976 # Result: DocumentSymbol[] | SymbolInformation[] | null
977 # Display the symbols in a popup window and jump to the selected symbol
978 export def DocSymbolPopup(lspserver: dict<any>, docSymbol: any, fname: string)
979 var symList: list<dict<any>> = []
981 if docSymbol->empty()
985 var bnr = fname->bufnr()
987 if docSymbol[0]->has_key('location')
988 # SymbolInformation[]
989 symList = GetSymbolsInfoTable(lspserver, bnr, docSymbol)
992 GetSymbolsDocSymbol(lspserver, bnr, docSymbol, symList)
996 SymbolPopupMenu(symList)
999 # vim: tabstop=8 shiftwidth=2 softtabstop=2