feat: Add follow_cursor and restore_location

New lua API function: follow_cursor (supports opts.focus_outline).
This sets location in outline to match location in code.

New keymap: restore_location (C-g) by default.
This provides the same functionality as follow_cursor.

I've also refactored other lua API functions for consistency of using
`opts.focus_outline`. If opts is not provided, focus_outline is
defaulted to true. To change this behaviour, set it to false.
This commit is contained in:
hedy
2023-11-06 09:20:52 +08:00
parent ceb9a4841d
commit 9e96b54de9
7 changed files with 225 additions and 90 deletions

View File

@@ -45,7 +45,8 @@ I do not merge PRs from the original repo that I don't personally need.
- Fold all operation too slow: simrat39/symbols-outline.nvim#223 (simrat39/symbols-outline.nvim#224)
- "Invalid buffer id" error simrat39/symbols-outline.nvim#177
- Open handler triggering multiple times ends up in messy state with errors
simrat39/symbols-outline.nvim#235
simrat39/symbols-outline.nvim#235
- Fixed `_highlight_current_item` function checking provider on outline window
## 🛑 Breaking changes
@@ -74,6 +75,7 @@ features:
- **Behaviour**: For `auto_preview=true`, previously preview is only shown after
some delay. Now preview is shown instantly every time the cursor moves.
## Features
[Skip this section](#symbols-outlinenvim)
@@ -107,6 +109,11 @@ Features/Changes:
- Auto jump config option (see config `auto_goto`)
(simrat39/symbols-outline.nvim#229, simrat39/symbols-outline.nvim#228).
- New Follow command, opposite of `goto_location`/`focus_location`
- New restore location keymap option to go back to corresponding outline
location synced with code (see config `restore_location`).
## PRs
[Skip this section](#symbols-outlinenvim)
@@ -193,7 +200,7 @@ Key:
(#101 by druskus20)
### TODO
## TODO
[Skip this section](#symbols-outlinenvim)
@@ -225,9 +232,12 @@ Items will be moved to above list when complete.
(simrat39/symbols-outline#128)
- `[/]` Configurable width and height of preview window (simrat39/symbols-outline#130)
- `[/]` Outline window customizations (simrat39/symbols-outline#137)
- View
- `[/]` Outline window customizations (simrat39/symbols-outline#137)
- `[/]` Option to show line number next to symbols
### Related plugins
## Related plugins
- nvim-navic
- nvim-navbuddy
@@ -403,19 +413,25 @@ local opts = {
-- Jump to symbol under cursor but keep focus on outline window.
-- Renamed in this fork!
peek_location = "o",
-- Only in this fork:
-- Change cursor position of outline window to the current location in code.
-- "Opposite" of goto/peek_location.
restore_location = "<C-g>",
-- Open LSP/provider-dependent symbol hover information
hover_symbol = "<C-space>",
-- Preview symbol under cursor
-- Preview location code of the symbol under cursor
toggle_preview = "K",
-- Symbol actions
rename_symbol = "r",
code_actions = "a",
-- These fold actions are collapsing tree nodes, not code folding
fold = "h",
unfold = "l",
fold_toggle = '<Tab>', -- Only in this fork
-- Toggle folds for all nodes.
-- If at least one node is folded, this action will fold all nodes.
-- If all nodes are folded, this action will unfold all nodes.
fold_toggle_all = '<S-Tab>', -- Only in this fork
unfold = "l",
fold_all = "W",
unfold_all = "E",
fold_reset = "R",
@@ -504,6 +520,20 @@ local opts = {
Display current provider and outline window status in the messages area.
- **:SymbolsOutlineFollow[!]**
Go to corresponding node in outline based on cursor position in code, and
focus on the outline window.
With bang, retain focus on the code window.
This can be understood as the converse of `goto_location` (see keymaps).
`goto_location` sets cursor of code window to the position of outline window,
whereas this command sets position in outline window to the cursor position of
code window.
With bang, it can be understood as the converse of `focus_location`.
### Lua API
@@ -516,13 +546,13 @@ require'symbols-outline'
Toggle opening/closing of outline window.
If `opts.bang` is true, keep focus on previous window.
If `opts.focus_outline=false`, keep focus on previous window.
- **open_outline(opts)**
Open the outline window.
If `opts.bang` is true, keep focus on previous window.
If `opts.focus_outline=false`, keep focus on previous window.
- **close_outline()**
@@ -548,22 +578,36 @@ require'symbols-outline'
Display current provider and outline window status in the messages area.
- **has_provider()**
Returns whether a provider is available for current window.
- **follow_cursor(opts)**
Go to corresponding node in outline based on cursor position in code, and
focus on the outline window.
With `opts.focus_outline=false`, cursor focus will remain on code window.
## Default keymaps
These mappings are active for the outline window.
| Key | Action |
| ---------- | -------------------------------------------------- |
| Escape | Close outline |
| ? | Show help message |
| Enter | Go to symbol location in code |
| o | Go to symbol location in code without losing focus |
| Ctrl+g | Go to code location in outline window |
| Ctrl+Space | Hover current symbol |
| K | Toggles the current symbol preview |
| r | Rename symbol |
| a | Code actions |
| h | fold symbol |
| Tab | toggle fold under cursor |
| Shift+Tab | toggle all folds |
| h | Fold symbol or parent symbol |
| Tab | Toggle fold under cursor |
| Shift+Tab | Toggle all folds |
| l | Unfold symbol |
| W | Fold all symbols |
| E | Unfold all symbols |

View File

@@ -37,6 +37,7 @@ M.defaults = {
close = { '<Esc>', 'q' },
goto_location = '<Cr>',
peek_location = 'o',
restore_location = "<C-g>",
hover_symbol = '<C-space>',
toggle_preview = 'K',
rename_symbol = 'r',

View File

@@ -39,31 +39,6 @@ local function setup_global_autocmd()
})
end
local function setup_buffer_autocmd()
if config.options.auto_preview then
vim.api.nvim_create_autocmd('CursorMoved', {
buffer = 0,
callback = require('symbols-outline.preview').show,
})
else
vim.api.nvim_create_autocmd('CursorMoved', {
buffer = 0,
callback = require('symbols-outline.preview').close,
})
end
end
local function setup_commands()
local cmd = function(n, c, o) vim.api.nvim_create_user_command('SymbolsOutline'..n, c, o) end
cmd('', M.toggle_outline, { nargs = 0, bang = true })
cmd('Open', M.open_outline, { nargs = 0, bang = true })
cmd('Close', M.close_outline, { nargs = 0 })
cmd('FocusOutline', M.focus_outline, { nargs = 0 })
cmd('FocusCode', M.focus_code, { nargs = 0 })
cmd('Focus', M.focus_toggle, { nargs = 0 })
cmd('Status', M.show_status, { nargs = 0 })
end
-------------------------
-- STATE
-------------------------
@@ -71,7 +46,6 @@ M.state = {
outline_items = {},
flattened_outline_items = {},
code_win = 0,
opts = {}
}
local function wipe_state()
@@ -91,7 +65,9 @@ local function _merge_items(items)
end
local function __refresh()
if M.view:is_open() then
local current_buffer_is_outline = M.view.bufnr
== vim.api.nvim_get_current_buf()
if M.view:is_open() and current_buffer_is_outline then
local function refresh_handler(response)
if response == nil or type(response) ~= 'table' then
return
@@ -153,25 +129,6 @@ function M._toggle_fold(move_cursor, node_index)
end
end
local function setup_buffer_autocmd()
if config.options.auto_preview then
vim.api.nvim_create_autocmd('CursorHold', {
buffer = 0,
callback = require('symbols-outline.preview').show,
})
else
vim.api.nvim_create_autocmd('CursorMoved', {
buffer = 0,
callback = function()
require('symbols-outline.preview').close()
if config.options.auto_goto then
M._goto_location(false)
end
end,
})
end
end
local function setup_buffer_autocmd()
if config.options.auto_preview then
vim.api.nvim_create_autocmd('CursorMoved', {
@@ -252,32 +209,25 @@ function M._set_all_folded(folded, nodes)
end
function M._highlight_current_item(winnr)
local has_provider = providers.has_provider()
local is_current_buffer_the_outline = M.view.bufnr
local has_provider = M.has_provider()
local has_outline_open = M.view:is_open()
local current_buffer_is_outline = M.view.bufnr
== vim.api.nvim_get_current_buf()
local doesnt_have_outline_buf = not M.view.bufnr
local should_exit = not has_provider
or doesnt_have_outline_buf
or is_current_buffer_the_outline
-- Make a special case if we have a window number
-- Because we might use this to manually focus so we dont want to quit this
-- function
if winnr then
should_exit = false
if not has_provider then
return
end
if should_exit then
if
not has_outline_open -- Outline not open
-- Not currently on outline window, but no code window is given.
or (not winnr and not current_buffer_is_outline)
then
return
end
local win = winnr or vim.api.nvim_get_current_win()
local hovered_line = vim.api.nvim_win_get_cursor(win)[1] - 1
local leaf_node = nil
local cb = function(value)
@@ -318,6 +268,10 @@ local function setup_keymaps(bufnr)
map(config.options.keymaps.peek_location, function()
M._goto_location(false)
end)
-- Navigate to corresponding outline location for current code location
map(config.options.keymaps.restore_location, function()
M._map_follow_cursor()
end)
-- Move down/up in outline and peek that location in code
map(config.options.keymaps.down_and_goto, function()
M._move_and_goto('down')
@@ -381,7 +335,7 @@ local function setup_keymaps(bufnr)
end)
end
local function handler(response)
local function handler(response, opts)
if response == nil or type(response) ~= 'table' or M.view:is_open() then
return
end
@@ -408,42 +362,122 @@ local function handler(response)
M._highlight_current_item(M.state.code_win)
if not config.options.focus_on_open or (M.state.opts and M.state.opts.bang) then
if not config.options.focus_on_open or (opts and not opts.focus_outline) then
vim.fn.win_gotoid(M.state.code_win)
end
end
---Set position of outline window to match cursor position in code, return
---whether the window is just newly opened (previously not open).
---@param opts table? Field `focus_outline` = `false` or `nil` means don't focus on outline window after following cursor. If opts is not provided, focus will be on outline window after following cursor.
---@return boolean ok Whether it was successful. If ok=false, either the outline window is not open or the code window cannot be found.
function M.follow_cursor(opts)
if not M.view:is_open() then
return false
end
if require('symbols-outline.preview').has_code_win() then
M._highlight_current_item(M.state.code_win)
else
return false
end
if not opts then
opts = { focus_outline = true }
end
if opts.focus_outline then
M.focus_outline()
end
return true
end
local function _cmd_follow_cursor(opts)
local fnopts = { focus_outline = true }
if opts.bang then
fnopts.focus_outline = false
end
M.follow_cursor(fnopts)
end
function M._map_follow_cursor()
if not M.follow_cursor({ focus_outline = true }) then
vim.notify(
"Code window no longer active. Try closing and reopening the outline.",
vim.log.levels.ERROR
)
end
end
---Toggle the outline window, and return whether the outline window is open
---after this operation.
---@param opts table? Table of options, @see open_outline
---@return boolean is_open Whether outline window is open
function M.toggle_outline(opts)
if M.view:is_open() then
M.close_outline()
return false
else
M.open_outline(opts)
return true
end
end
-- Used for SymbolsOutline user command
local function _cmd_toggle_outline(opts)
if opts.bang then
M.toggle_outline({ focus_outline = false })
else
M.toggle_outline({ focus_outline = true })
end
end
---Open the outline window.
---@param opts table? Field focus_outline=false means don't focus on outline window after opening. If opts is not provided, focus will be on outline window after opening.
function M.open_outline(opts)
if not opts then
opts = { focus_outline = true }
end
if not M.view:is_open() then
M.state.opts = opts
providers.request_symbols(handler)
providers.request_symbols(handler, opts)
end
end
local function _cmd_open_outline(opts)
if opts.bang then
M.open_outline({ focus_outline = false })
else
M.open_outline({ focus_outline = true })
end
end
---Close the outline window.
function M.close_outline()
M.view:close()
end
---Set cursor to focus on the outline window, return whether the window is currently open..
---@return boolean is_open Whether the window is open
function M.focus_outline()
if M.view:is_open() then
vim.fn.win_gotoid(M.view.winnr)
return true
end
return false
end
---Set cursor to focus on the code window, return whether this operation was successful.
---@return boolean ok Whether it was successful. If unsuccessful, it might mean that the attached code window has been closed or is no longer valid.
function M.focus_code()
if require('symbols-outline.preview').has_code_win() then
vim.fn.win_gotoid(M.state.code_win)
return true
end
return false
end
---Toggle focus between outline and code window, returns whether it was successful.
---@return boolean ok Whether it was successful. If `ok=false`, either the outline window is not open or the code window is no longer valid.
function M.focus_toggle()
if M.view:is_open() and require('symbols-outline.preview').has_code_win() then
local winid = vim.fn.win_getid()
@@ -452,27 +486,80 @@ function M.focus_toggle()
else
vim.fn.win_gotoid(M.state.code_win)
end
return true
end
return false
end
---Whether the outline window is currently open.
---@return boolean is_open
function M.is_open()
return M.view:is_open()
end
---Display outline window status in the message area.
function M.show_status()
if providers.has_provider() and _G._symbols_outline_current_provider then
if M.has_provider() then
print("Current provider:")
print(_G._symbols_outline_current_provider.name)
print(' ' .. _G._symbols_outline_current_provider.name)
if M.view:is_open() then
print("Outline window is open")
print("Outline window is open.")
else
print("Outline window is not open")
print("Outline window is not open.")
end
if require('symbols-outline.preview').has_code_win() then
print("Code window is active.")
else
print("Warning: code window is either closed or invalid. Please close and reopen the outline window.")
end
else
print("No providers")
end
end
---Whether there is currently an available provider.
---@return boolean has_provider
function M.has_provider()
local winid = vim.fn.win_getid()
if M.view:is_open() and winid == M.view.winnr then
return _G._symbols_outline_current_provider ~= nil
end
return providers.has_provider() and _G._symbols_outline_current_provider
end
local function setup_commands()
local cmd = function(n, c, o)
vim.api.nvim_create_user_command('SymbolsOutline'..n, c, o)
end
cmd('', _cmd_toggle_outline, {
desc = "Toggle the outline window. \
With bang, keep focus on initial window after opening.",
nargs = 0,
bang = true,
})
cmd('Open', _cmd_open_outline, {
desc = "With bang, keep focus on initial window after opening.",
nargs = 0,
bang = true,
})
cmd('Close', M.close_outline, { nargs = 0 })
cmd('FocusOutline', M.focus_outline, { nargs = 0 })
cmd('FocusCode', M.focus_code, { nargs = 0 })
cmd('Focus', M.focus_toggle, { nargs = 0 })
cmd('Status', M.show_status, {
desc = "Show a message about the current status of the outline window.",
nargs = 0,
})
cmd('Follow', _cmd_follow_cursor, {
desc = "Update position of outline with position of cursor. \
With bang, don't switch cursor focus to outline window.",
nargs = 0,
bang = true,
})
end
---Set up configuration options for symbols-outline.
function M.setup(opts)
config.setup(opts)
ui.setup_highlights()

View File

@@ -83,11 +83,12 @@ local function convert_symbols(result)
end
---@param on_symbols function
function M.request_symbols(on_symbols)
---@param opts table
function M.request_symbols(on_symbols, opts)
vim.fn.call('CocActionAsync', {
'documentSymbols',
function(_, symbols)
on_symbols { [1000000] = { result = convert_symbols(symbols) } }
on_symbols({ [1000000] = { result = convert_symbols(symbols) } }, opts)
end,
})
end

View File

@@ -21,13 +21,13 @@ function M.has_provider()
end
---@param on_symbols function
function M.request_symbols(on_symbols)
function M.request_symbols(on_symbols, opts)
for _, value in ipairs(providers) do
local provider = require(value)
if provider.should_use_provider(0) then
_G._symbols_outline_current_provider = provider
_G._symbols_outline_current_provider.name = value
provider.request_symbols(on_symbols)
provider.request_symbols(on_symbols, opts)
break
end
end

View File

@@ -17,8 +17,9 @@ function M.hover_info(_, _, on_info)
end
---@param on_symbols function
function M.request_symbols(on_symbols)
on_symbols(md_parser.handle_markdown())
---@param opts table
function M.request_symbols(on_symbols, opts)
on_symbols(md_parser.handle_markdown(), opts)
end
return M

View File

@@ -69,13 +69,14 @@ function M.postprocess_symbols(response)
end
---@param on_symbols function
function M.request_symbols(on_symbols)
function M.request_symbols(on_symbols, opts)
vim.lsp.buf_request_all(
0,
'textDocument/documentSymbol',
getParams(),
function (response)
on_symbols(M.postprocess_symbols(response))
response = M.postprocess_symbols(response)
on_symbols(response, opts)
end
)
end