aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-07-30 13:47:28 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-01-30 18:01:24 +0100
commit4ec576b6d600711160f7e3d10bbe60308c5b232c (patch)
tree07cc234cd2b15a898d5004f03d60e6d49ad84036
parentc12e2a1fc0ff5a4172552196627d8b1d221b66fe (diff)
downloadvis-lspc-4ec576b6d600711160f7e3d10bbe60308c5b232c.tar.gz
vis-lspc-4ec576b6d600711160f7e3d10bbe60308c5b232c.zip
make navigation window configurable and add key bindings
-rw-r--r--README.md10
-rw-r--r--init.lua142
-rw-r--r--lspc.lua17
3 files changed, 137 insertions, 32 deletions
diff --git a/README.md b/README.md
index de1206a..a5ff2d5 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/init.lua b/init.lua
index 8953847..d01969c 100644
--- a/init.lua
+++ b/init.lua
@@ -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
diff --git a/lspc.lua b/lspc.lua
index f34ba4d..b1b5e98 100644
--- a/lspc.lua
+++ b/lspc.lua
@@ -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',