diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-07-29 13:36:54 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2026-01-30 18:01:20 +0100 |
| commit | 8ea9f55529e20bff95657372bed85ef3dfa836e2 (patch) | |
| tree | 26ec94c3392e4aa0c90841da96c83e90a2890077 /init.lua | |
| parent | 6293900d15239c22f589a29c8aecfc09b78822b5 (diff) | |
| download | vis-lspc-8ea9f55529e20bff95657372bed85ef3dfa836e2.tar.gz vis-lspc-8ea9f55529e20bff95657372bed85ef3dfa836e2.zip | |
support navigating a file by its symbols in a seperate window
Diffstat (limited to 'init.lua')
| -rw-r--r-- | init.lua | 147 |
1 files changed, 147 insertions, 0 deletions
@@ -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 |
