aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-07-29 13:36:54 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-01-30 18:01:20 +0100
commit8ea9f55529e20bff95657372bed85ef3dfa836e2 (patch)
tree26ec94c3392e4aa0c90841da96c83e90a2890077
parent6293900d15239c22f589a29c8aecfc09b78822b5 (diff)
downloadvis-lspc-8ea9f55529e20bff95657372bed85ef3dfa836e2.tar.gz
vis-lspc-8ea9f55529e20bff95657372bed85ef3dfa836e2.zip
support navigating a file by its symbols in a seperate window
-rw-r--r--README.md1
-rw-r--r--init.lua147
2 files changed, 148 insertions, 0 deletions
diff --git a/README.md b/README.md
index 806af12..de1206a 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,7 @@ Alternatively, a plugin manager like [vis-plug](https://github.com/erf/vis-plug)
lspc-show-diagnostics - show the available diagnostics of the current line
lspc-next-diagnostic - jump to the next available diagnostic
lspc-prev-diagnostic - jump to the previous available diagnostic
+ lspc-navigate-symbols - navigate a file by its symbols in a seperate window
### Available configuration options
diff --git a/init.lua b/init.lua
index 9a40bf9..8953847 100644
--- a/init.lua
+++ b/init.lua
@@ -728,6 +728,14 @@ function LanguageServer:call_text_document_method(method, params, win, ctx)
self:request_diagnostics(win)
end
+--- Call the textDocument/symbol method of the server if provided
+function LanguageServer:request_symbols(win, ctx)
+ if self.capabilities['documentSymbolProvider'] then
+ local params = {textDocument = {uri = path_to_uri(win.file.path)}}
+ self:call_text_document_method('documentSymbol', params, win, ctx)
+ end
+end
+
local function lspc_handle_goto_method_response(req, result)
if not result or type(result) ~= 'table' or next(result) == nil then
lspc:warn(req.method .. ' found no results')
@@ -962,6 +970,12 @@ local function lspc_handle_initialize_response(ls, result)
vis.events.emit(lspc.events.LS_INITIALIZED, ls)
end
+local function lspc_handle_symbol_method_response(result, ctx)
+ if ctx.callback and type(ctx.callback) == 'function' then
+ ctx.callback(result, ctx)
+ end
+end
+
--- Send didChangeConfiguration notification with the default settings
function LanguageServer:send_default_settings()
-- Use the rootUri or the open file's path as scope for the initial settings
@@ -1023,6 +1037,9 @@ function LanguageServer:handle_method_response(method_response, req)
elseif method == 'textDocument/diagnostic' then
lspc_handle_diagnostic_method_response(self, result, req.ctx)
+ elseif method == 'textDocument/documentSymbol' then
+ lspc_handle_symbol_method_response(result, req.ctx)
+
elseif method == 'shutdown' then
self:send_notification('exit')
self.fd:close()
@@ -1558,6 +1575,123 @@ local function lspc_show_diagnostic(win, line)
end
end
+local lsp_symbol_kinds = {
+ 'File',
+ 'Module',
+ 'Namespace',
+ 'Package',
+ 'Class',
+ 'Method',
+ 'Property',
+ 'Field',
+ 'Constructor',
+ 'Enum',
+ 'Interface',
+ 'Function',
+ 'Variable',
+ 'Constant',
+ 'String',
+ 'Number',
+ 'Boolean',
+ 'Array',
+ 'Object',
+ 'Key',
+ 'Null',
+ 'EnumMember',
+ 'Struct',
+ 'Event',
+ 'Operator',
+ 'TypeParameter',
+}
+
+local function recursive_bfs_symbols_flatten(symbols, indent)
+ local ranges = {}
+ local str = ''
+ for _, s in ipairs(symbols) do
+ local range = s.selectionRange or s.location.range
+ table.insert(ranges, range)
+ str = str ..
+ string.format('%s%s %s %d:%d\n', indent, lsp_symbol_kinds[s.kind], s.name,
+ range.start.line + 1, range.start.character + 1)
+ -- Remember DocumentSymbol.selectionRange or SymbolInformation.location.range
+ if s.children then
+ local cstr, cranges = recursive_bfs_symbols_flatten(s.children, indent .. ' ')
+ str = str .. cstr
+ for _, v in ipairs(cranges) do
+ table.insert(ranges, v)
+ end
+ end
+ end
+ return str, ranges
+end
+
+local function lspc_navwin_from_symbols(symbols, ctx)
+ local symbols_outline, ranges = recursive_bfs_symbols_flatten(symbols, '')
+
+ local old_layout = vis.ui.layout
+ vis:command('vsplit')
+ local navwin = ctx.win
+ local codewin = vis.win
+
+ -- Prepare window to be our symbol navigation window
+ navwin.file = 'symbols'
+ local navfile = navwin.file
+ navfile:delete(0, navfile.size)
+ navfile:insert(0, symbols_outline)
+ navwin:draw()
+ -- Jump to navigation window
+ vis.win = navwin
+
+ -- Create event handlers to connect the position in the navigation window to
+ -- the original window.
+ local function nav_win_win_highlight_handler(win)
+ -- This is not the navigation window
+ if win ~= navwin then
+ return true
+ end
+
+ -- No valid selection in the navigation window
+ local line = navwin.selection.line
+ if line > #ranges then
+ return true
+ end
+ local sel = lsp_pos_to_vis_sel(ranges[navwin.selection.line].start)
+
+ -- Set the selection in the code window to the selected symbol
+ codewin.selection:to(sel.line, sel.col)
+ return true
+ end
+ vis.events.subscribe(vis.events.WIN_HIGHLIGHT, nav_win_win_highlight_handler)
+
+ -- Remove the event handlers.
+ local nav_win_close_handler
+ nav_win_close_handler = function(win)
+ if win ~= navwin and win ~= codewin then
+ return true
+ -- The code windows was closed, therefore we can also close the navigation window
+ elseif win == codewin then
+ navwin:close(true)
+ -- Reset everything after closing the navigation window
+ else
+ -- reset vis' layout
+ vis.ui.layout = old_layout
+ vis.ui.layout = vis.ui.layouts.HORIZONTAL
+
+ -- remove the navigation window event handlers
+ vis.events.unsubscribe(vis.events.UI_DRAW, nav_win_win_highlight_handler)
+ vis.events.unsubscribe(vis.events.WIN_CLOSE, nav_win_close_handler)
+ end
+ return true
+ end
+
+ vis.events.subscribe(vis.events.WIN_CLOSE, nav_win_close_handler)
+end
+
+local function lspc_nav_symbols(ls, win)
+ local ctx = {win = win, callback = lspc_navwin_from_symbols}
+ ls:request_symbols(win, ctx)
+end
+
-- vis-lspc commands
vis:command_register('lspc-back', function()
@@ -1784,6 +1918,19 @@ vis:command_register('lspc-show-diagnostics', function(argv, _, win)
end
end)
+vis:command_register('lspc-navigate-symbols', function(argv, _, win)
+ local ls, err = lspc_get_usable_ls(win, argv[1])
+ if err then
+ lspc:err(err)
+ return
+ end
+ assert(ls)
+ if not ls:is_file_opened(win.file.path) then
+ lspc_open(ls, win, win.file)
+ end
+ lspc_nav_symbols(ls, win)
+end, 'navigate the file by its symbols')
+
-- vis-lspc event hooks
vis.events.subscribe(vis.events.WIN_OPEN, function(win)
if lspc.autostart and win.syntax then