mirror of
https://github.com/stevearc/aerial.nvim
synced 2024-09-16 14:34:08 +02:00
feat: new navigation view (#235)
This commit is contained in:
parent
8d0915b1f8
commit
4b725dc8e5
15 changed files with 653 additions and 12 deletions
32
README.md
32
README.md
|
@ -168,6 +168,9 @@ it](https://github.com/stevearc/aerial.nvim/issues/new?assignees=stevearc&labels
|
|||
| `[count]AerialPrev` | | Jump backwards [count] symbols (default 1). |
|
||||
| `[count]AerialGo[!]` | | Jump to the [count] symbol (default 1). |
|
||||
| `AerialInfo` | | Print out debug info related to aerial. |
|
||||
| `AerialNavToggle` | | Open or close the aerial nav window. |
|
||||
| `AerialNavOpen` | | Open the aerial nav window. |
|
||||
| `AerialNavClose` | | Close the aerial nav window. |
|
||||
|
||||
|
||||
## Options
|
||||
|
@ -455,6 +458,31 @@ require("aerial").setup({
|
|||
end,
|
||||
},
|
||||
|
||||
-- Options for the floating nav windows
|
||||
nav = {
|
||||
border = "rounded",
|
||||
max_height = 0.9,
|
||||
min_height = { 10, 0.1 },
|
||||
max_width = 0.5,
|
||||
min_width = { 0.2, 20 },
|
||||
win_opts = {
|
||||
cursorline = true,
|
||||
winblend = 10,
|
||||
},
|
||||
-- Jump to symbol in source window when the cursor moves
|
||||
autojump = false,
|
||||
-- Keymaps in the nav window
|
||||
keymaps = {
|
||||
["<CR>"] = "actions.jump",
|
||||
["<2-LeftMouse>"] = "actions.jump",
|
||||
["<C-v>"] = "actions.jump_vsplit",
|
||||
["<C-s>"] = "actions.jump_split",
|
||||
["h"] = "actions.left",
|
||||
["l"] = "actions.right",
|
||||
["<C-c>"] = "actions.close",
|
||||
},
|
||||
},
|
||||
|
||||
lsp = {
|
||||
-- Fetch document symbols when LSP diagnostics update.
|
||||
-- If false, will update on buffer changes.
|
||||
|
@ -646,6 +674,10 @@ hi AerialGuide2 guifg=Blue
|
|||
- [tree_open(opts)](doc/api.md#tree_openopts)
|
||||
- [tree_close(opts)](doc/api.md#tree_closeopts)
|
||||
- [tree_toggle(opts)](doc/api.md#tree_toggleopts)
|
||||
- [nav_is_open()](doc/api.md#nav_is_open)
|
||||
- [nav_open()](doc/api.md#nav_open)
|
||||
- [nav_close()](doc/api.md#nav_close)
|
||||
- [nav_toggle()](doc/api.md#nav_toggle)
|
||||
- [sync_folds(bufnr)](doc/api.md#sync_foldsbufnr)
|
||||
- [info()](doc/api.md#info)
|
||||
- [num_symbols(bufnr)](doc/api.md#num_symbolsbufnr)
|
||||
|
|
|
@ -293,6 +293,31 @@ OPTIONS *aerial-option
|
|||
end,
|
||||
},
|
||||
|
||||
-- Options for the floating nav windows
|
||||
nav = {
|
||||
border = "rounded",
|
||||
max_height = 0.9,
|
||||
min_height = { 10, 0.1 },
|
||||
max_width = 0.5,
|
||||
min_width = { 0.2, 20 },
|
||||
win_opts = {
|
||||
cursorline = true,
|
||||
winblend = 10,
|
||||
},
|
||||
-- Jump to symbol in source window when the cursor moves
|
||||
autojump = false,
|
||||
-- Keymaps in the nav window
|
||||
keymaps = {
|
||||
["<CR>"] = "actions.jump",
|
||||
["<2-LeftMouse>"] = "actions.jump",
|
||||
["<C-v>"] = "actions.jump_vsplit",
|
||||
["<C-s>"] = "actions.jump_split",
|
||||
["h"] = "actions.left",
|
||||
["l"] = "actions.right",
|
||||
["<C-c>"] = "actions.close",
|
||||
},
|
||||
},
|
||||
|
||||
lsp = {
|
||||
-- Fetch document symbols when LSP diagnostics update.
|
||||
-- If false, will update on buffer changes.
|
||||
|
@ -364,6 +389,15 @@ AerialCloseAll *:AerialCloseAl
|
|||
AerialInfo *:AerialInfo*
|
||||
Print out debug info related to aerial.
|
||||
|
||||
AerialNavToggle *:AerialNavToggle*
|
||||
Open or close the aerial nav window.
|
||||
|
||||
AerialNavOpen *:AerialNavOpen*
|
||||
Open the aerial nav window.
|
||||
|
||||
AerialNavClose *:AerialNavClose*
|
||||
Close the aerial nav window.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
API *aerial-api*
|
||||
|
||||
|
@ -551,6 +585,22 @@ tree_toggle({opts}) *aerial.tree_toggl
|
|||
{bubble} `nil|boolean` If true and current symbol has no children,
|
||||
perform the action on the nearest parent (default true)
|
||||
|
||||
nav_is_open(): boolean *aerial.nav_is_open*
|
||||
Check if the nav windows are open
|
||||
|
||||
|
||||
nav_open() *aerial.nav_open*
|
||||
Open the nav windows
|
||||
|
||||
|
||||
nav_close() *aerial.nav_close*
|
||||
Close the nav windows
|
||||
|
||||
|
||||
nav_toggle() *aerial.nav_toggle*
|
||||
Toggle the nav windows open/closed
|
||||
|
||||
|
||||
sync_folds({bufnr}) *aerial.sync_folds*
|
||||
Sync code folding with the current tree state.
|
||||
|
||||
|
|
28
doc/api.md
28
doc/api.md
|
@ -26,6 +26,10 @@
|
|||
- [tree_open(opts)](#tree_openopts)
|
||||
- [tree_close(opts)](#tree_closeopts)
|
||||
- [tree_toggle(opts)](#tree_toggleopts)
|
||||
- [nav_is_open()](#nav_is_open)
|
||||
- [nav_open()](#nav_open)
|
||||
- [nav_close()](#nav_close)
|
||||
- [nav_toggle()](#nav_toggle)
|
||||
- [sync_folds(bufnr)](#sync_foldsbufnr)
|
||||
- [info()](#info)
|
||||
- [num_symbols(bufnr)](#num_symbolsbufnr)
|
||||
|
@ -266,6 +270,30 @@ Toggle the collapsed state at the selected location
|
|||
| | recurse | `nil\|boolean` | If true, perform the action recursively on all children (default false) |
|
||||
| | bubble | `nil\|boolean` | If true and current symbol has no children, perform the action on the nearest parent (default true) |
|
||||
|
||||
## nav_is_open()
|
||||
|
||||
`nav_is_open(): boolean` \
|
||||
Check if the nav windows are open
|
||||
|
||||
|
||||
## nav_open()
|
||||
|
||||
`nav_open()` \
|
||||
Open the nav windows
|
||||
|
||||
|
||||
## nav_close()
|
||||
|
||||
`nav_close()` \
|
||||
Close the nav windows
|
||||
|
||||
|
||||
## nav_toggle()
|
||||
|
||||
`nav_toggle()` \
|
||||
Toggle the nav windows open/closed
|
||||
|
||||
|
||||
## sync_folds(bufnr)
|
||||
|
||||
`sync_folds(bufnr)` \
|
||||
|
|
7
doc/tags
7
doc/tags
|
@ -2,6 +2,9 @@
|
|||
:AerialCloseAll aerial.txt /*:AerialCloseAll*
|
||||
:AerialGo aerial.txt /*:AerialGo*
|
||||
:AerialInfo aerial.txt /*:AerialInfo*
|
||||
:AerialNavClose aerial.txt /*:AerialNavClose*
|
||||
:AerialNavOpen aerial.txt /*:AerialNavOpen*
|
||||
:AerialNavToggle aerial.txt /*:AerialNavToggle*
|
||||
:AerialNext aerial.txt /*:AerialNext*
|
||||
:AerialOpen aerial.txt /*:AerialOpen*
|
||||
:AerialOpenAll aerial.txt /*:AerialOpenAll*
|
||||
|
@ -26,6 +29,10 @@ aerial.focus aerial.txt /*aerial.focus*
|
|||
aerial.get_location aerial.txt /*aerial.get_location*
|
||||
aerial.info aerial.txt /*aerial.info*
|
||||
aerial.is_open aerial.txt /*aerial.is_open*
|
||||
aerial.nav_close aerial.txt /*aerial.nav_close*
|
||||
aerial.nav_is_open aerial.txt /*aerial.nav_is_open*
|
||||
aerial.nav_open aerial.txt /*aerial.nav_open*
|
||||
aerial.nav_toggle aerial.txt /*aerial.nav_toggle*
|
||||
aerial.next aerial.txt /*aerial.next*
|
||||
aerial.next_up aerial.txt /*aerial.next_up*
|
||||
aerial.num_symbols aerial.txt /*aerial.num_symbols*
|
||||
|
|
|
@ -6,7 +6,7 @@ M.show_help = {
|
|||
desc = "Show default keymaps",
|
||||
callback = function()
|
||||
local config = require("aerial.config")
|
||||
require("aerial.keymap_util").show_help(config.keymaps)
|
||||
require("aerial.keymap_util").show_help("aerial.actions", config.keymaps)
|
||||
end,
|
||||
}
|
||||
|
||||
|
|
|
@ -120,4 +120,16 @@ M.info = function(params)
|
|||
print(string.format("Show symbols: %s", data.filter_kind_map))
|
||||
end
|
||||
|
||||
M.nav_toggle = function()
|
||||
require("aerial.nav_view").toggle()
|
||||
end
|
||||
|
||||
M.nav_open = function()
|
||||
require("aerial.nav_view").open()
|
||||
end
|
||||
|
||||
M.nav_close = function()
|
||||
require("aerial.nav_view").close()
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -279,6 +279,31 @@ local default_options = {
|
|||
end,
|
||||
},
|
||||
|
||||
-- Options for the floating nav windows
|
||||
nav = {
|
||||
border = "rounded",
|
||||
max_height = 0.9,
|
||||
min_height = { 10, 0.1 },
|
||||
max_width = 0.5,
|
||||
min_width = { 0.2, 20 },
|
||||
win_opts = {
|
||||
cursorline = true,
|
||||
winblend = 10,
|
||||
},
|
||||
-- Jump to symbol in source window when the cursor moves
|
||||
autojump = false,
|
||||
-- Keymaps in the nav window
|
||||
keymaps = {
|
||||
["<CR>"] = "actions.jump",
|
||||
["<2-LeftMouse>"] = "actions.jump",
|
||||
["<C-v>"] = "actions.jump_vsplit",
|
||||
["<C-s>"] = "actions.jump_split",
|
||||
["h"] = "actions.left",
|
||||
["l"] = "actions.right",
|
||||
["<C-c>"] = "actions.close",
|
||||
},
|
||||
},
|
||||
|
||||
lsp = {
|
||||
-- Fetch document symbols when LSP diagnostics update.
|
||||
-- If false, will update on buffer changes.
|
||||
|
|
|
@ -208,6 +208,7 @@ function M.set_symbols(buf, items)
|
|||
local prev = prev_by_level[item.level]
|
||||
if prev then
|
||||
prev.next_sibling = item
|
||||
item.prev_sibling = prev
|
||||
end
|
||||
for j = item.level + 1, max_level do
|
||||
prev_by_level[j] = nil
|
||||
|
|
|
@ -87,6 +87,27 @@ local commands = {
|
|||
desc = "Print out debug info related to aerial.",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd = "AerialNavToggle",
|
||||
func = "nav_toggle",
|
||||
defn = {
|
||||
desc = "Open or close the aerial nav window.",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd = "AerialNavOpen",
|
||||
func = "nav_open",
|
||||
defn = {
|
||||
desc = "Open the aerial nav window.",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd = "AerialNavClose",
|
||||
func = "nav_close",
|
||||
defn = {
|
||||
desc = "Close the aerial nav window.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local do_setup
|
||||
|
@ -367,6 +388,19 @@ M.tree_close = lazy("tree", "close")
|
|||
--- bubble nil|boolean If true and current symbol has no children, perform the action on the nearest parent (default true)
|
||||
M.tree_toggle = lazy("tree", "toggle")
|
||||
|
||||
---Check if the nav windows are open
|
||||
---@return boolean
|
||||
M.nav_is_open = lazy("nav_view", "is_open")
|
||||
|
||||
---Open the nav windows
|
||||
M.nav_open = lazy("nav_view", "open")
|
||||
|
||||
---Close the nav windows
|
||||
M.nav_close = lazy("nav_view", "close")
|
||||
|
||||
---Toggle the nav windows open/closed
|
||||
M.nav_toggle = lazy("nav_view", "toggle")
|
||||
|
||||
---Sync code folding with the current tree state.
|
||||
---@param bufnr nil|integer
|
||||
---@note
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
local actions = require("aerial.actions")
|
||||
local util = require("aerial.util")
|
||||
local M = {}
|
||||
|
||||
local function resolve(rhs)
|
||||
local function resolve(action_module, rhs)
|
||||
if type(rhs) == "string" and vim.startswith(rhs, "actions.") then
|
||||
return resolve(actions[vim.split(rhs, ".", true)[2]])
|
||||
local mod = require(action_module)
|
||||
return resolve(action_module, mod[vim.split(rhs, ".", true)[2]])
|
||||
elseif type(rhs) == "table" then
|
||||
local opts = vim.deepcopy(rhs)
|
||||
opts.callback = nil
|
||||
|
@ -13,16 +13,23 @@ local function resolve(rhs)
|
|||
return rhs, {}
|
||||
end
|
||||
|
||||
M.set_keymaps = function(mode, keymaps, bufnr)
|
||||
M.set_keymaps = function(mode, action_module, keymaps, bufnr, ...)
|
||||
local args = vim.F.pack_len(...)
|
||||
for k, v in pairs(keymaps) do
|
||||
local rhs, opts = resolve(v)
|
||||
local rhs, opts = resolve(action_module, v)
|
||||
if rhs then
|
||||
if type(rhs) == "function" and args.n > 0 then
|
||||
local _rhs = rhs
|
||||
rhs = function()
|
||||
_rhs(vim.F.unpack_len(args))
|
||||
end
|
||||
end
|
||||
vim.keymap.set(mode, k, rhs, vim.tbl_extend("keep", { buffer = bufnr }, opts))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.show_help = function(keymaps)
|
||||
M.show_help = function(action_module, keymaps)
|
||||
local rhs_to_lhs = {}
|
||||
local lhs_to_all_lhs = {}
|
||||
for k, rhs in pairs(keymaps) do
|
||||
|
@ -43,7 +50,7 @@ M.show_help = function(keymaps)
|
|||
for k, rhs in pairs(keymaps) do
|
||||
local all_lhs = lhs_to_all_lhs[k]
|
||||
if all_lhs then
|
||||
local _, opts = resolve(rhs)
|
||||
local _, opts = resolve(action_module, rhs)
|
||||
local keystr = table.concat(all_lhs, "/")
|
||||
max_lhs = math.max(max_lhs, vim.api.nvim_strwidth(keystr))
|
||||
table.insert(col_left, { str = keystr, all_lhs = all_lhs })
|
||||
|
|
|
@ -13,6 +13,27 @@ local function calc_float(value, max_value)
|
|||
end
|
||||
end
|
||||
|
||||
---@return integer
|
||||
M.get_editor_width = function()
|
||||
return vim.o.columns
|
||||
end
|
||||
|
||||
---@return integer
|
||||
M.get_editor_height = function()
|
||||
local editor_height = vim.o.lines - vim.o.cmdheight
|
||||
-- Subtract 1 if tabline is visible
|
||||
if vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1) then
|
||||
editor_height = editor_height - 1
|
||||
end
|
||||
-- Subtract 1 if statusline is visible
|
||||
if
|
||||
vim.o.laststatus >= 2 or (vim.o.laststatus == 1 and #vim.api.nvim_tabpage_list_wins(0) > 1)
|
||||
then
|
||||
editor_height = editor_height - 1
|
||||
end
|
||||
return editor_height
|
||||
end
|
||||
|
||||
local function calc_list(values, max_value, aggregator, limit)
|
||||
local ret = limit
|
||||
if type(values) == "table" then
|
||||
|
@ -44,7 +65,7 @@ end
|
|||
|
||||
local function get_max_width(relative, winid)
|
||||
if relative == "editor" then
|
||||
return vim.o.columns
|
||||
return M.get_editor_width()
|
||||
else
|
||||
return vim.api.nvim_win_get_width(winid or 0)
|
||||
end
|
||||
|
@ -52,7 +73,7 @@ end
|
|||
|
||||
local function get_max_height(relative, winid)
|
||||
if relative == "editor" then
|
||||
return vim.o.lines - vim.o.cmdheight
|
||||
return M.get_editor_height()
|
||||
else
|
||||
return vim.api.nvim_win_get_height(winid or 0)
|
||||
end
|
||||
|
|
75
lua/aerial/nav_actions.lua
Normal file
75
lua/aerial/nav_actions.lua
Normal file
|
@ -0,0 +1,75 @@
|
|||
local aerial = require("aerial")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.jump = {
|
||||
desc = "Jump to the symbol under the cursor",
|
||||
callback = function(nav)
|
||||
local symbol = nav:get_current_symbol()
|
||||
nav:close()
|
||||
if symbol then
|
||||
require("aerial.navigation").select_symbol(symbol, nav.winid, nav.bufnr, { jump = true })
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.jump_vsplit = {
|
||||
desc = "Jump to the symbol in a vertical split",
|
||||
callback = function(nav)
|
||||
local symbol = nav:get_current_symbol()
|
||||
nav:close()
|
||||
if symbol then
|
||||
require("aerial.navigation").select_symbol(
|
||||
symbol,
|
||||
nav.winid,
|
||||
nav.bufnr,
|
||||
{ jump = true, split = "vertical" }
|
||||
)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.jump_split = {
|
||||
desc = "Jump to the symbol in a horizontal split",
|
||||
callback = function(nav)
|
||||
local symbol = nav:get_current_symbol()
|
||||
nav:close()
|
||||
if symbol then
|
||||
require("aerial.navigation").select_symbol(
|
||||
symbol,
|
||||
nav.winid,
|
||||
nav.bufnr,
|
||||
{ jump = true, split = "horizontal" }
|
||||
)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.left = {
|
||||
desc = "Navigate to parent symbol",
|
||||
callback = function(nav)
|
||||
local symbol = nav:get_current_symbol()
|
||||
if symbol and symbol.parent then
|
||||
nav:focus_symbol(symbol.parent)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.right = {
|
||||
desc = "Navigate to child symbol",
|
||||
callback = function(nav)
|
||||
local symbol = nav:get_current_symbol()
|
||||
if symbol and symbol.children and not vim.tbl_isempty(symbol.children) then
|
||||
nav:focus_symbol(symbol.children[1])
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.close = {
|
||||
desc = "Close the nav windows",
|
||||
callback = function()
|
||||
aerial.nav_close()
|
||||
end,
|
||||
}
|
||||
|
||||
return M
|
342
lua/aerial/nav_view.lua
Normal file
342
lua/aerial/nav_view.lua
Normal file
|
@ -0,0 +1,342 @@
|
|||
local backends = require("aerial.backends")
|
||||
local config = require("aerial.config")
|
||||
local data = require("aerial.data")
|
||||
local keymap_util = require("aerial.keymap_util")
|
||||
local layout = require("aerial.layout")
|
||||
local navigation = require("aerial.navigation")
|
||||
local util = require("aerial.util")
|
||||
local window = require("aerial.window")
|
||||
local M = {}
|
||||
|
||||
---@class aerial.NavPanel
|
||||
---@field winid integer
|
||||
---@field bufnr integer
|
||||
---@field width integer
|
||||
---@field height integer
|
||||
---@field symbols aerial.Symbol[]
|
||||
|
||||
---@class aerial.Nav
|
||||
---@field left aerial.NavPanel
|
||||
---@field main aerial.NavPanel
|
||||
---@field right aerial.NavPanel
|
||||
---@field bufnr integer
|
||||
---@field autocmds integer[]
|
||||
local AerialNav = {}
|
||||
|
||||
local _active_nav = nil
|
||||
|
||||
---@return integer
|
||||
local function create_buf()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[bufnr].buftype = "nofile"
|
||||
vim.bo[bufnr].bufhidden = "wipe"
|
||||
vim.bo[bufnr].swapfile = false
|
||||
vim.bo[bufnr].modifiable = false
|
||||
vim.bo[bufnr].filetype = "aerial-nav"
|
||||
return bufnr
|
||||
end
|
||||
|
||||
function AerialNav.new(bufnr, winid)
|
||||
local left_buf = create_buf()
|
||||
local width = math.floor((layout.get_editor_width() - 6) / 3)
|
||||
local left_win = vim.api.nvim_open_win(left_buf, false, {
|
||||
relative = "editor",
|
||||
row = 1,
|
||||
col = 1,
|
||||
width = width,
|
||||
height = 20,
|
||||
border = config.nav.border,
|
||||
style = "minimal",
|
||||
})
|
||||
local main_buf = create_buf()
|
||||
local main_win = vim.api.nvim_open_win(main_buf, true, {
|
||||
relative = "editor",
|
||||
row = 1,
|
||||
col = width + 2,
|
||||
width = width,
|
||||
height = 20,
|
||||
-- If you want a rounded border, convert the main window border to 'single' so the center joints
|
||||
-- have a cleaner look
|
||||
border = config.nav.border == "rounded" and "single" or config.nav.border,
|
||||
style = "minimal",
|
||||
zindex = 51,
|
||||
})
|
||||
local right_buf = create_buf()
|
||||
local right_win = vim.api.nvim_open_win(right_buf, false, {
|
||||
relative = "editor",
|
||||
row = 1,
|
||||
col = 2 * (width + 2),
|
||||
width = width,
|
||||
height = 20,
|
||||
border = config.nav.border,
|
||||
style = "minimal",
|
||||
})
|
||||
local nav = setmetatable({
|
||||
winid = winid,
|
||||
bufnr = bufnr,
|
||||
left = {
|
||||
bufnr = left_buf,
|
||||
winid = left_win,
|
||||
width = 80,
|
||||
height = 20,
|
||||
symbols = {},
|
||||
},
|
||||
main = {
|
||||
bufnr = main_buf,
|
||||
winid = main_win,
|
||||
width = 80,
|
||||
height = 20,
|
||||
symbols = {},
|
||||
},
|
||||
right = {
|
||||
bufnr = right_buf,
|
||||
winid = right_win,
|
||||
width = 80,
|
||||
height = 20,
|
||||
symbols = {},
|
||||
},
|
||||
autocmds = {},
|
||||
}, {
|
||||
__index = AerialNav,
|
||||
})
|
||||
keymap_util.set_keymaps("", "aerial.nav_actions", config.nav.keymaps, main_buf, nav)
|
||||
vim.api.nvim_create_autocmd("WinLeave", {
|
||||
desc = "Close Aerial nav window on leave",
|
||||
nested = true,
|
||||
once = true,
|
||||
callback = function()
|
||||
M.close()
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_create_autocmd("BufLeave", {
|
||||
desc = "Close Aerial nav window on leave",
|
||||
nested = true,
|
||||
once = true,
|
||||
buffer = main_buf,
|
||||
callback = function()
|
||||
M.close()
|
||||
end,
|
||||
})
|
||||
-- Defer the CursorMoved autocmd so it doesn't fire immediately
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_create_autocmd("CursorMoved", {
|
||||
desc = "Update symbols on cursor move",
|
||||
buffer = main_buf,
|
||||
callback = function()
|
||||
local symbol = nav:get_current_symbol()
|
||||
if symbol then
|
||||
if config.nav.autojump then
|
||||
navigation.select_symbol(symbol, winid, bufnr, { jump = false })
|
||||
end
|
||||
nav:focus_symbol(symbol)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end)
|
||||
table.insert(
|
||||
nav.autocmds,
|
||||
vim.api.nvim_create_autocmd("VimResized", {
|
||||
desc = "Update aerial nav view",
|
||||
callback = function()
|
||||
nav:relayout()
|
||||
end,
|
||||
})
|
||||
)
|
||||
return nav
|
||||
end
|
||||
|
||||
---@return nil|aerial.Symbol
|
||||
function AerialNav:get_current_symbol()
|
||||
local lnum = vim.api.nvim_win_get_cursor(self.main.winid)[1]
|
||||
return self.main.symbols[lnum]
|
||||
end
|
||||
|
||||
---@param symbol nil|aerial.Symbol
|
||||
---@return aerial.Symbol[]
|
||||
---@return integer
|
||||
local function get_all_siblings(symbol)
|
||||
local ret = {}
|
||||
if not symbol then
|
||||
return ret, 1
|
||||
end
|
||||
table.insert(ret, symbol)
|
||||
local i = 1
|
||||
local iter = symbol
|
||||
while iter.prev_sibling do
|
||||
iter = iter.prev_sibling
|
||||
i = i + 1
|
||||
table.insert(ret, 1, iter)
|
||||
end
|
||||
iter = symbol
|
||||
while iter.next_sibling do
|
||||
iter = iter.next_sibling
|
||||
table.insert(ret, iter)
|
||||
end
|
||||
return ret, i
|
||||
end
|
||||
|
||||
---@param panel aerial.NavPanel
|
||||
local function render_symbols(panel)
|
||||
local bufnr = panel.bufnr
|
||||
local lines = {}
|
||||
local highlights = {}
|
||||
local max_len = 1
|
||||
for i, item in ipairs(panel.symbols) do
|
||||
local kind = config.get_icon(bufnr, item.kind)
|
||||
local text = util.remove_newlines(string.format("%s %s", kind, item.name))
|
||||
table.insert(lines, text)
|
||||
local text_cols = vim.api.nvim_strwidth(text)
|
||||
table.insert(highlights, { "Aerial" .. item.kind .. "Icon", i - 1, 0, kind:len() })
|
||||
table.insert(highlights, { "Aerial" .. item.kind, i - 1, kind:len(), -1 })
|
||||
max_len = math.max(max_len, text_cols)
|
||||
end
|
||||
|
||||
-- If there are no symbols in this section, add some indicator of that
|
||||
if #lines == 0 then
|
||||
table.insert(lines, "<none>")
|
||||
table.insert(highlights, { "Comment", 0, 0, -1 })
|
||||
end
|
||||
|
||||
vim.bo[bufnr].modifiable = true
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
vim.bo[bufnr].modifiable = false
|
||||
vim.bo[bufnr].modified = false
|
||||
|
||||
local ns = vim.api.nvim_create_namespace("aerial")
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
||||
for _, hl in ipairs(highlights) do
|
||||
vim.api.nvim_buf_add_highlight(bufnr, ns, unpack(hl))
|
||||
end
|
||||
panel.width = max_len
|
||||
panel.height = #lines
|
||||
end
|
||||
|
||||
---@param symbol aerial.Symbol
|
||||
function AerialNav:focus_symbol(symbol)
|
||||
local siblings, lnum = get_all_siblings(symbol)
|
||||
self.main.symbols = siblings
|
||||
self.left.symbols = get_all_siblings(symbol.parent)
|
||||
self.right.symbols = symbol.children or {}
|
||||
|
||||
render_symbols(self.left)
|
||||
render_symbols(self.main)
|
||||
render_symbols(self.right)
|
||||
|
||||
if vim.api.nvim_win_is_valid(self.main.winid) then
|
||||
vim.api.nvim_win_set_cursor(self.main.winid, { lnum, 0 })
|
||||
end
|
||||
self:relayout()
|
||||
end
|
||||
|
||||
function AerialNav:relayout()
|
||||
local total_width = layout.get_editor_width()
|
||||
local total_height = layout.get_editor_height()
|
||||
local main_width = layout.calculate_width("editor", self.main.width, config.nav, 0)
|
||||
local desired_height = math.max(self.left.height, math.max(self.main.height, self.right.height))
|
||||
local height = layout.calculate_height("editor", desired_height, config.nav, 0)
|
||||
local main_col = math.floor((total_width - main_width) / 2)
|
||||
local main_row = math.floor((total_height - height) / 2)
|
||||
vim.api.nvim_win_set_config(self.main.winid, {
|
||||
relative = "editor",
|
||||
width = main_width,
|
||||
height = height,
|
||||
row = main_row,
|
||||
col = main_col,
|
||||
})
|
||||
|
||||
local border_width = config.nav.border == "none" and 0 or 1
|
||||
local width_remaining = math.floor((total_width - main_width) / 2)
|
||||
if config.nav.border ~= "none" then
|
||||
width_remaining = width_remaining - (2 * border_width)
|
||||
end
|
||||
|
||||
local left_width = layout.calculate_width("editor", self.left.width, config.nav, 0)
|
||||
if left_width > width_remaining then
|
||||
left_width = width_remaining
|
||||
end
|
||||
vim.api.nvim_win_set_config(self.left.winid, {
|
||||
relative = "editor",
|
||||
width = left_width,
|
||||
height = height,
|
||||
row = main_row,
|
||||
col = main_col - left_width - border_width,
|
||||
})
|
||||
local right_width = layout.calculate_width("editor", self.right.width, config.nav, 0)
|
||||
if right_width > width_remaining then
|
||||
right_width = width_remaining
|
||||
end
|
||||
vim.api.nvim_win_set_config(self.right.winid, {
|
||||
relative = "editor",
|
||||
width = right_width,
|
||||
height = height,
|
||||
row = main_row,
|
||||
col = main_col + main_width + border_width,
|
||||
})
|
||||
for k, v in pairs(config.nav.win_opts) do
|
||||
vim.wo[self.main.winid][k] = v
|
||||
-- Hack: we generally don't want the left/right to have cursorline enabled
|
||||
if k ~= "cursorline" then
|
||||
vim.wo[self.left.winid][k] = v
|
||||
vim.wo[self.right.winid][k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AerialNav:close()
|
||||
for _, winid in ipairs({ self.left.winid, self.main.winid, self.right.winid }) do
|
||||
if vim.api.nvim_win_is_valid(winid) then
|
||||
vim.api.nvim_win_close(winid, true)
|
||||
end
|
||||
end
|
||||
for _, id in ipairs(self.autocmds) do
|
||||
vim.api.nvim_del_autocmd(id)
|
||||
end
|
||||
self.autocmds = {}
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
M.is_open = function()
|
||||
return _active_nav ~= nil
|
||||
end
|
||||
|
||||
M.open = function()
|
||||
if M.is_open() then
|
||||
return
|
||||
end
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local backend = backends.get(bufnr)
|
||||
if not backend then
|
||||
backends.log_support_err()
|
||||
return
|
||||
end
|
||||
if not data.has_symbols(bufnr) then
|
||||
backend.fetch_symbols(bufnr)
|
||||
end
|
||||
_active_nav = AerialNav.new(bufnr, winid)
|
||||
local bufdata = data.get(bufnr)
|
||||
if bufdata then
|
||||
local pos = window.get_symbol_position(bufdata, cursor[1], cursor[2], true)
|
||||
_active_nav:focus_symbol(pos.closest_symbol)
|
||||
end
|
||||
end
|
||||
|
||||
M.toggle = function()
|
||||
if M.is_open() then
|
||||
M.close()
|
||||
else
|
||||
M.open()
|
||||
end
|
||||
end
|
||||
|
||||
M.close = function()
|
||||
if not M.is_open() then
|
||||
return
|
||||
end
|
||||
local nav = _active_nav
|
||||
_active_nav = nil
|
||||
nav:close()
|
||||
end
|
||||
|
||||
return M
|
|
@ -196,8 +196,15 @@ M.select = function(opts)
|
|||
error(string.format("Symbol %s is outside the bounds", opts.index))
|
||||
return
|
||||
end
|
||||
|
||||
local bufnr, _ = util.get_buffers()
|
||||
M.select_symbol(item, winid, bufnr, opts)
|
||||
end
|
||||
|
||||
---@param item aerial.Symbol
|
||||
---@param winid integer
|
||||
---@param bufnr integer
|
||||
---@param opts table
|
||||
M.select_symbol = function(item, winid, bufnr, opts)
|
||||
if opts.jump and config.close_on_select then
|
||||
window.close()
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ local function create_aerial_buffer(bufnr)
|
|||
end
|
||||
local aer_bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
keymap_util.set_keymaps("", config.keymaps, aer_bufnr)
|
||||
keymap_util.set_keymaps("", "aerial.actions", config.keymaps, aer_bufnr)
|
||||
vim.api.nvim_buf_set_var(bufnr, "aerial_buffer", aer_bufnr)
|
||||
-- Set buffer options
|
||||
vim.api.nvim_buf_set_var(aer_bufnr, "source_buffer", bufnr)
|
||||
|
|
Loading…
Reference in a new issue