diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-07-30 13:47:28 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2026-01-30 18:01:24 +0100 |
| commit | 4ec576b6d600711160f7e3d10bbe60308c5b232c (patch) | |
| tree | 07cc234cd2b15a898d5004f03d60e6d49ad84036 | |
| parent | c12e2a1fc0ff5a4172552196627d8b1d221b66fe (diff) | |
| download | vis-lspc-4ec576b6d600711160f7e3d10bbe60308c5b232c.tar.gz vis-lspc-4ec576b6d600711160f7e3d10bbe60308c5b232c.zip | |
make navigation window configurable and add key bindings
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | init.lua | 142 | ||||
| -rw-r--r-- | lspc.lua | 17 |
3 files changed, 137 insertions, 32 deletions
@@ -76,7 +76,14 @@ Alternatively, a plugin manager like [vis-plug](https://github.com/erf/vis-plug) <K> - hover over current position Normal and Insert mode: <C- > - get completions + < o> - open the symbol navigation window +#### Non-Auto-Jump Navigation Window + + Normal mode: + < s> - scroll to the selected symbol + < j> - jump to the selected symbol (switch to the code window) + < x> - close the navigaion window ### Available commands @@ -133,6 +140,9 @@ Available options are: * `show_message = 'message'` - how to present information. `'message'`: use `vis:message`; `'open'`: use a new window supporting syntax highlighting. * `universal_root_globs = {}` - Globs to consider as workspace root for any language server (e.g. `*.git` or `*.hg`). * `fallback_dirname_as_root = false` - If set to true a file's directory is used as workspace root if no explicit root was found. +* `navwin_symbols_format = '%s[%.1s] %s\n'` - The format string used to display a symbol in the symbol navigation window. +* `navwin_auto_scroll = true` - If set to true the code window will scroll to the currently selected symbol in the navigation window without user interaction. +* `navwin_layout = 'vr'` - Layout where the navigation window will be placed (`v[r|l]|h[t|b]`: vertical right or left; horizontal top or bottom). #### Configure your own Language Server @@ -1610,9 +1610,8 @@ local function recursive_bfs_symbols_flatten(symbols, indent) 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) + str = str .. string.format(lspc.navwin_symbol_format, 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 .. ' ') @@ -1625,60 +1624,115 @@ local function recursive_bfs_symbols_flatten(symbols, indent) return str, ranges end +--- Scroll the code window to the definition of the symbol selected in the navigation window. +local function lspc_navwin_scroll_codewin(switch_win) + local line = lspc.navctx.navwin.selection.line + -- No valid selection in the navigation window + if line > #lspc.navctx.ranges then + return + end + local sel = lsp_pos_to_vis_sel(lspc.navctx.ranges[line].start) + + -- Set the selection in the code window to the selected symbol + lspc.navctx.codewin.selection:to(sel.line, sel.col) + + if switch_win then + vis.win = lspc.navctx.codewin + end +end + +--- Instantiate a new navigation context from the received symbols. 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 + if lspc.navwin_layout:sub(1, 1) == 'v' then + vis.ui.layout = vis.ui.layouts.VERTICAL + elseif lspc.navwin_layout:sub(1, 1) == 'h' then + vis.ui.layout = vis.ui.layouts.HORIZONTAL + end + + local navwin + local codewin + -- The right or bottom (old) window is our navigation window + if lspc.navwin_layout:sub(2) == 'r' or lspc.navwin_layout:sub(2) == 'b' then + vis:command('o ' .. ctx.win.file.path) + navwin = ctx.win + navwin.file = '' + navwin.file:delete(0, navwin.file.size) + codewin = vis.win + -- The new (old) window is our navigation window + else + vis:command('o') + navwin = vis.win + codewin = ctx.win + end + + assert(navwin) + assert(codewin) + lspc.navctx = { + navwin = navwin, + codewin = codewin, + ranges = ranges, + old_layout = old_layout, + } -- 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.file: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 + -- Follow the position in the navigation window + local nav_win_win_highlight_handler + if lspc.navwin_auto_scroll then + -- Create event handlers to connect the position in the navigation window to + -- the original window. + nav_win_win_highlight_handler = function(win) + -- This is not the navigation window + if win ~= lspc.navctx.navwin then + return true + end - -- No valid selection in the navigation window - local line = navwin.selection.line - if line > #ranges then + -- scroll the code window without switching to it. + lspc_navwin_scroll_codewin(false) 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 + vis.events.subscribe(vis.events.WIN_HIGHLIGHT, nav_win_win_highlight_handler) + else + navwin:map(vis.modes.NORMAL, ' j', function() + lspc_navwin_scroll_codewin(true) + end, 'jump to the selected symbol') + navwin:map(vis.modes.NORMAL, ' s', function() + lspc_navwin_scroll_codewin(false) + end, 'scroll to the selected symbol') + navwin:map(vis.modes.NORMAL, ' x', function() + lspc.navctx.navwin:close(true) + end, 'close the navgation window') 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 + if win ~= lspc.navctx.navwin and win ~= lspc.navctx.codewin then return true -- The code windows was closed, therefore we can also close the navigation window - elseif win == codewin then - navwin:close(true) + elseif win == lspc.navctx.codewin then + lspc:log('Code window closed. Force close the navigation window.') + lspc.navctx.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 + vis.ui.layout = lspc.navctx.old_layout -- remove the navigation window event handlers - vis.events.unsubscribe(vis.events.UI_DRAW, nav_win_win_highlight_handler) + if lspc.navwin_auto_jump then + vis.events.unsubscribe(vis.events.UI_DRAW, nav_win_win_highlight_handler) + end + + -- remove the navigation context + lspc.navctx = nil vis.events.unsubscribe(vis.events.WIN_CLOSE, nav_win_close_handler) end return true @@ -1928,9 +1982,33 @@ vis:command_register('lspc-navigate-symbols', function(argv, _, win) if not ls:is_file_opened(win.file.path) then lspc_open(ls, win, win.file) end + if lspc.navctx then + lspc:err('navigation window already active') + return + end lspc_nav_symbols(ls, win) end, 'navigate the file by its symbols') +vis:command_register('lspc-navigate-close', function() + if not lspc.navctx then + lspc:err('no navigation window active') + return + end + lspc.navctx.navwin:close(true) +end, 'close the navigation window') + +vis:command_register('lspc-navigate-jump', function(_, _, win) + if not lspc.navctx then + lspc:err('no navigation window active') + return + end + if win ~= lspc.navctx.navwin then + lspc:err('not in navigation window') + return + end + +end, 'navigate to the selected symbol') + -- vis-lspc event hooks vis.events.subscribe(vis.events.WIN_OPEN, function(win) if lspc.autostart and win.syntax then @@ -57,6 +57,23 @@ local lspc = { -- Should a file's directory be used as workspace root if no explicit root was found. fallback_dirname_as_root = false, + -- Format string how the symbols are presented in the navigation window. + -- The following arguments are passed to the string.format function: + -- indentation, symbol kind, name, line, column + navwin_symbol_format = '%s[%.1s] %s\n', -- short version + -- navwin_symbol_format = '%s[%s] %s %d:%d\n', -- verbose version + + -- Automaticaly scroll the code window to the symbol selected in the navigation window. + navwin_auto_scroll = true, + + -- The layout where the navigation window will be placed. + -- The following layout identifiers are supported: + -- 'vr' -- DEFAULT: vertical right + -- 'vl' -- vertical left + -- 'ht' -- horizontal top + -- 'hb' -- horizontak bottom + navwin_layout = 'vr', + -- events events = { LS_INITIALIZED = 'LspcEvent::LS_INITIALIZED', |
