Feature: open/close nested symbols like a tree

This commit is contained in:
Steven Arcangeli 2021-06-16 18:55:09 -07:00
parent 0d27e70caa
commit d0e3b8be6e
12 changed files with 274 additions and 119 deletions

View file

@ -59,3 +59,6 @@ require'lspconfig'.vimls.setup{
A full list of commands and options can be found [in the
docs](https://github.com/stevearc/aerial.nvim/blob/master/doc/aerial.txt)
The default keybindings in the aerial window are [in the
ftplugin](https://github.com/stevearc/aerial.nvim/blob/master/ftplugin/aerial.vim)

View file

@ -39,19 +39,7 @@ This is a minimal configuration to get you started:
}
The aerial window itself has some sane default bindings, however you can easily
override them. The easiest way to is to use a ftplugin. For example, you can
create a file `.vim/ftplugin/aerial.vim`:
>
" These are the default bindings.
nnoremap <buffer> <CR> <cmd>lua require'aerial'.jump_to_loc()<CR>zvzz
nnoremap <buffer> <C-v> <cmd>lua require'aerial'.jump_to_loc(2)<CR>zvzz
nnoremap <buffer> <C-s> <cmd>lua require'aerial'.jump_to_loc(2, 'belowright split')<CR>zvzz
nnoremap <buffer> <C-j> j<cmd>lua require'aerial'.scroll_to_loc()<CR>
nnoremap <buffer> <C-k> k<cmd>lua require'aerial'.scroll_to_loc()<CR>
nnoremap <buffer> [[ <cmd>lua require'aerial'.prev_item()<CR>
nnoremap <buffer> ]] <cmd>lua require'aerial'.next_item()<CR>
nnoremap <buffer> p <cmd>lua require'aerial'.scroll_to_loc()<CR>
nnoremap <buffer> q <cmd>lua require"aerial".close()<CR>
override them. The easiest way to is to use a ftplugin (e.g. `.vim/ftplugin/aerial.vim`). You can find the default bindings in this plugin's `ftplugin` dir.
By default, the symbols information in the buffer should stay updated, but if
you'd like to tweak the behavior see |g:aerial_diagnostics_trigger_update| and
@ -114,6 +102,8 @@ aerial.skip_item({delta}) *aerial.skip_item()
===============================================================================
OPTIONS *aerial-options*
g:aerial_default_bindings *g:aerial_default_bindings*
If false, don't set up the default keybindings in the aerial buffer.
g:aerial_min_width *g:aerial_min_width*
The minimum width of the aerial window. Default 10.
@ -177,17 +167,26 @@ g:aerial_use_icons *g:aerial_use_icons
https://www.nerdfonts.com/). Will try to smart default by looking for
`nvim-web-devicons` (https://github.com/kyazdani42/nvim-web-devicons).
aerial.set_kind_abbr({kind}, {abbr}) *aerial.set_kind_abbr()*
aerial.set_kind_abbr({mapping})
Use these abbreviations for the SymbolKind of the items.
aerial.set_icon({kind}, {icon}) *aerial.set_icon()*
aerial.set_icon({mapping})
Configures the icons for each of the symbols.
See https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol
for a complete list of the possible SymbolKind values.
>
aerial.set_kind_abbr('Function', '[F]')
aerial.set_kind_abbr{
Function = '[F]',
Method = '[M]',
}
aerial.set_icon('Function', '[F]')
aerial.set_icon{
Function = '[F]',
Method = '[M]',
}
You can set a different value to display when the line is collapsed
aerial.set_icon('FunctionCollapsed', '-F-')
The special value of `Collapsed` will be used as the default for any
collapsed symbols that have not been defined
aerial.set_icon('Collapsed', '---')
aerial.set_filter_kind({kinds}) *aerial.set_filter_kind()*
Only display these SymbolKind symbols in the aerial buffer. {kinds} is a

View file

@ -16,12 +16,13 @@ aerial.open() aerial.txt /*aerial.open()*
aerial.prev_item() aerial.txt /*aerial.prev_item()*
aerial.scroll_to_loc() aerial.txt /*aerial.scroll_to_loc()*
aerial.set_filter_kind() aerial.txt /*aerial.set_filter_kind()*
aerial.set_kind_abbr() aerial.txt /*aerial.set_kind_abbr()*
aerial.set_icon() aerial.txt /*aerial.set_icon()*
aerial.set_open_automatic() aerial.txt /*aerial.set_open_automatic()*
aerial.skip_item() aerial.txt /*aerial.skip_item()*
aerial.toggle() aerial.txt /*aerial.toggle()*
aerial.txt aerial.txt /*aerial.txt*
g:aerial_automatic_direction aerial.txt /*g:aerial_automatic_direction*
g:aerial_default_bindings aerial.txt /*g:aerial_default_bindings*
g:aerial_diagnostics_trigger_update aerial.txt /*g:aerial_diagnostics_trigger_update*
g:aerial_highlight_mode aerial.txt /*g:aerial_highlight_mode*
g:aerial_highlight_on_jump aerial.txt /*g:aerial_highlight_on_jump*

28
ftplugin/aerial.vim Normal file
View file

@ -0,0 +1,28 @@
if !get(g:, 'aerial_default_bindings', 1)
finish
endif
" Use <CR> to jump to the location, just like with the quickfix
nnoremap <buffer> <CR> <cmd>lua require'aerial'.jump_to_loc()<CR>zvzz
" Jump to location in a vertical split
nnoremap <buffer> <C-v> <cmd>lua require'aerial'.jump_to_loc(2)<CR>zvzz
" Jump to location in a horizontal split
nnoremap <buffer> <C-s> <cmd>lua require'aerial'.jump_to_loc(2, 'belowright split')<CR>zvzz
" Use p to scroll to the location under cursor but stay in the aerial window
nnoremap <buffer> p <cmd>lua require'aerial'.scroll_to_loc()<CR>
" Hold ctrl + j/k to go up and down while scrolling to the location under cursor
nnoremap <buffer> <C-j> j<cmd>lua require'aerial'.scroll_to_loc()<CR>
nnoremap <buffer> <C-k> k<cmd>lua require'aerial'.scroll_to_loc()<CR>
" Use [[]] to jump to the prev/next item
nnoremap <buffer> [[ <cmd>lua require'aerial'.prev_item()<CR>
nnoremap <buffer> ]] <cmd>lua require'aerial'.next_item()<CR>
" q closes
nnoremap <buffer> q <cmd>lua require"aerial".close()<CR>
"""" Tree commands
" o toggles the tree
nnoremap <buffer> o <cmd>lua require"aerial".tree_cmd('toggle')<CR>
" O toggles the tree recursively
nnoremap <buffer> O <cmd>lua require"aerial".tree_cmd('toggle', {recurse=true})<CR>
" l opens the tree and moves the cursor
nnoremap <buffer> l l<cmd>lua require"aerial".tree_cmd('open', {bubble=false})<CR>

View file

@ -1,6 +1,8 @@
local callbacks = require 'aerial.callbacks'
local config = require 'aerial.config'
local data = require 'aerial.data'
local nav = require 'aerial.navigation'
local render = require 'aerial.render'
local window = require 'aerial.window'
local M = {}
@ -73,16 +75,29 @@ M.on_attach = function(client, opts)
end
end
M.set_open_automatic = function(ft_or_mapping, bool)
if type(ft_or_mapping) == 'table' then
config.open_automatic = ft_or_mapping
else
config.open_automatic[ft_or_mapping] = bool
M.tree_cmd = function(action, opts)
local did_update, row = data[0]:action(action, opts)
if did_update then
render.update_aerial_buffer()
nav.update_all_positions()
print(row)
if row then
vim.api.nvim_win_set_cursor(0, {row, 0})
end
end
end
M.set_open_automatic = function(ft_or_mapping, bool)
config.set_open_automatic(ft_or_mapping, bool)
end
-- @deprecated. use set_icon() instead
M.set_kind_abbr = function(kind_or_mapping, abbr)
config.set_kind_abbr(kind_or_mapping, abbr)
config.set_icon(kind_or_mapping, abbr)
end
M.set_icon = function(kind_or_mapping, icon)
config.set_icon(kind_or_mapping, icon)
end
M.set_filter_kind = function(list)

View file

@ -12,7 +12,7 @@ local function get_symbol_kind_name(kind_number)
end
local function process_symbols(symbols)
local function _process_symbols(_symbols, list, level)
local function _process_symbols(_symbols, parent, list, level)
for _, symbol in ipairs(_symbols) do
local kind = get_symbol_kind_name(symbol.kind)
local range
@ -28,15 +28,16 @@ local function process_symbols(symbols)
kind = kind,
name = symbol.name,
level = level,
parent = parent,
lnum = range.start.line + 1,
col = range.start.character + 1,
}
if symbol.children then
item.children = _process_symbols(symbol.children, {}, level + 1)
item.children = _process_symbols(symbol.children, item, {}, level + 1)
end
table.insert(list, item)
elseif symbol.children then
_process_symbols(symbol.children, list, level)
_process_symbols(symbol.children, parent, list, level)
end
end
table.sort(list, function(a, b)
@ -49,7 +50,7 @@ local function process_symbols(symbols)
return list
end
return _process_symbols(symbols, {}, 0)
return _process_symbols(symbols, nil, {}, 0)
end
M.symbol_callback = function(_, _, result, _, bufnr)

View file

@ -1,7 +1,7 @@
local M = {}
local has_devicons = pcall(require, 'nvim-web-devicons')
M.open_automatic = {
local open_automatic = {
['_'] = false,
}
@ -15,9 +15,9 @@ M.filter_kind = {
Struct = true,
}
local _kind_abbr = nil
local icons = nil
local default_abbr = {
local plain_icons = {
Array = '[arr]';
Boolean = '[bool]';
Class = '[C]';
@ -44,9 +44,10 @@ local default_abbr = {
Struct = '[S]';
TypeParameter = '[T]';
Variable = '[V]';
Collapsed = '';
}
local icons_abbr = {
local nerd_icons = {
Class = '';
Constructor = '';
Enum = '';
@ -54,6 +55,7 @@ local icons_abbr = {
Interface = '';
Method = '';
Struct = '';
Collapsed = '';
}
M.get_highlight_on_jump = function()
@ -66,13 +68,18 @@ M.get_update_when_errors = function()
if val == nil then return true else return val end
end
M.set_open_automatic = function(ft_or_mapping, bool)
if type(ft_or_mapping) == 'table' then
open_automatic = ft_or_mapping
else
open_automatic[ft_or_mapping] = bool
end
end
M.get_open_automatic = function(bufnr)
local ft = vim.api.nvim_buf_get_option(bufnr or 0, 'filetype')
local ret = M.open_automatic[ft]
if ret == nil then
return M.open_automatic['_']
end
return ret
local ret = open_automatic[ft]
return ret == nil and open_automatic['_'] or ret
end
M.get_open_automatic_min_lines = function()
@ -120,29 +127,33 @@ M.get_use_icons = function()
if use_icons == nil then return has_devicons else return use_icons end
end
local function get_kind_abbr()
if not _kind_abbr then
local function get_icons()
if not icons then
if M.get_use_icons() then
_kind_abbr = vim.tbl_extend('keep', icons_abbr, default_abbr)
icons = vim.tbl_extend('keep', nerd_icons, plain_icons)
else
_kind_abbr = default_abbr
icons = plain_icons
end
end
return _kind_abbr
return icons
end
M.set_kind_abbr = function(kind_or_mapping, abbr)
M.set_icon = function(kind_or_mapping, icon)
if type(kind_or_mapping) == 'table' then
_kind_abbr = vim.tbl_extend('keep', kind_or_mapping, get_kind_abbr())
icons = vim.tbl_extend('keep', kind_or_mapping, get_icons())
else
local kind_abbr = get_kind_abbr()
kind_abbr[kind_or_mapping] = abbr
_kind_abbr = kind_abbr
get_icons()[kind_or_mapping] = icon
end
end
M.get_kind_abbr = function(kind)
local abbr = get_kind_abbr()[kind]
M.get_icon = function(kind, collapsed)
local abbrs = get_icons()
local abbr
if collapsed then
abbr = abbrs[kind .. 'Collapsed'] or abbrs['Collapsed']
else
abbr = abbrs[kind]
end
return abbr and abbr or kind
end

View file

@ -1,9 +1,12 @@
local util = require 'aerial.util'
local BufData = {
new = function(t)
local new = {
items = {},
positions = {},
last_position = {},
last_position = 1,
collapsed = {},
}
setmetatable(new, {__index = t})
return new
@ -19,11 +22,97 @@ local BufData = {
end)
end,
visit = function(self, callback)
indexof = function(self, target)
if target == nil then
return nil
end
local i = 1
return self:visit(function(item)
if item == target then
return i
end
i = i + 1
end)
end,
_get_item_key = function(_, item)
local key = string.format("%s:%s", item.kind, item.name)
while item.parent do
item = item.parent
key = string.format("%s:%s", item.name, key)
end
return key
end,
_is_collapsed = function(self, item)
local key = self:_get_item_key(item)
return self.collapsed[key]
end,
_is_collapsable = function(_, item)
return item.children and not vim.tbl_isempty(item.children)
end,
_get_config = function(self, item)
return {
collapsed = self:_is_collapsed(item),
has_children = self:_is_collapsable(item),
}
end,
action = function(self, action, opts)
opts = vim.tbl_extend('keep', opts or {}, {
recurse = false,
bubble = true,
})
local did_update = false
local function do_action(item, bubble)
if bubble then
while item and not self:_is_collapsable(item) do
item = item.parent
end
end
if not item or not self:_is_collapsable(item) then
return
end
local key = self:_get_item_key(item)
if action == 'toggle' then
action = self.collapsed[key] and 'open' or 'close'
end
if action == 'open' then
did_update = did_update or self.collapsed[key]
self.collapsed[key] = nil
if opts.recurse then
for _,child in ipairs(item.children) do
do_action(child, false)
end
end
elseif action == 'close' then
did_update = did_update or not self.collapsed[key]
self.collapsed[key] = true
if opts.recurse and item.parent then
return do_action(item.parent, false)
end
return item
else
error(string.format("Unknown action '%s'", action))
end
end
local cursor = vim.api.nvim_win_get_cursor(0)
local current_item = self:item(cursor[1])
local item = do_action(current_item, opts.bubble)
return did_update, self:indexof(item)
end,
visit = function(self, callback, opts)
opts = vim.tbl_extend('keep', opts or {}, {
incl_hidden = false,
})
local function visit_item(item)
local ret = callback(item)
local ret = callback(item, self:_get_config(item))
if ret then return ret end
if item.children then
if item.children
and (opts.incl_hidden or not self:_is_collapsed(item)) then
for _,child in ipairs(item.children) do
ret = visit_item(child)
if ret then return ret end
@ -36,20 +125,18 @@ local BufData = {
end
end,
count = function(self)
count = function(self, incl_hidden)
local count = 0
self:visit(function(_)
count = count + 1
end)
end, {incl_hidden = incl_hidden})
return count
end,
}
local Data = setmetatable({}, {
__index = function(t, bufnr)
if bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
__index = function(t, buf)
local bufnr, _ = util.get_buffers(buf)
local bufdata = rawget(t, bufnr)
if not bufdata then
bufdata = BufData:new()

View file

@ -5,7 +5,7 @@ local render = require 'aerial.render'
local M = {}
M._get_virt_winid = function(bufnr, virt_winnr)
local function _get_virt_winid(bufnr, virt_winnr)
local vwin = 1
for i=1,vim.fn.winnr('$'),1 do
if vim.fn.winbufnr(i) == bufnr then
@ -19,25 +19,8 @@ M._get_virt_winid = function(bufnr, virt_winnr)
return -1
end
M._get_current_lnum = function()
local bufnr = vim.api.nvim_get_current_buf()
local lnum = vim.fn.getcurpos()[2]
if util.is_aerial_buffer(bufnr) then
bufnr = util.get_source_buffer()
local winid = M._get_virt_winid(bufnr, 1)
if winid == -1 then
return nil
end
local bufdata = data[bufnr]
local cached_lnum = bufdata.positions[winid]
return cached_lnum == nil and nil or {
['lnum'] = cached_lnum,
['relative'] = 'exact',
}
end
if not data:has_symbols(bufnr) then
return nil
end
local function _get_pos_in_win(bufnr, winid)
local lnum = vim.api.nvim_win_get_cursor(winid or 0)[1]
local bufdata = data[bufnr]
local selected = 0
local relative = 'above'
@ -57,13 +40,51 @@ M._get_current_lnum = function()
}
end
local function _get_current_lnum()
local bufnr = vim.api.nvim_get_current_buf()
if util.is_aerial_buffer(bufnr) then
bufnr = util.get_source_buffer()
local winid = _get_virt_winid(bufnr, 1)
if winid == -1 then
return nil
end
local bufdata = data[bufnr]
local cached_lnum = bufdata.positions[winid]
return cached_lnum == nil and nil or {
['lnum'] = cached_lnum,
['relative'] = 'exact',
}
end
if data:has_symbols(bufnr) then
return _get_pos_in_win(bufnr)
else
return nil
end
end
M.update_all_positions = function()
local bufnr, _ = util.get_buffers()
local bufdata = data[bufnr]
local tabwins = {}
for _,winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
tabwins[winid] = true
end
for _,winid in ipairs(vim.fn.win_findbuf(bufnr)) do
if tabwins[winid] then
local pos = _get_pos_in_win(bufnr, winid)
bufdata.positions[winid] = pos.lnum
bufdata.last_position = pos.lnum
end
end
render.update_highlights(bufnr)
end
M._update_position = function()
local pos = M._get_current_lnum()
local pos = _get_current_lnum()
if pos == nil then
return
end
local bufnr = vim.api.nvim_get_current_buf()
local aer_bufnr = util.get_aerial_buffer(bufnr)
local bufnr, aer_bufnr = util.get_buffers()
local mywin = vim.api.nvim_get_current_win()
local bufdata = data[bufnr]
bufdata.positions[mywin] = pos.lnum
@ -88,7 +109,7 @@ M._jump_to_loc = function(item_no, virt_winnr, split_cmd)
return
end
bufnr = util.get_source_buffer()
local winid = M._get_virt_winid(bufnr, virt_winnr)
local winid = _get_virt_winid(bufnr, virt_winnr)
if winid == -1 then
-- Create a new split for the source window
winid = vim.fn.bufwinid(bufnr)
@ -123,16 +144,11 @@ M.scroll_to_loc = function(virt_winnr, split_cmd)
end
M.skip_item = function(delta)
local pos = M._get_current_lnum()
local pos = _get_current_lnum()
if pos == nil then
return
end
local bufnr
if util.is_aerial_buffer(bufnr) then
bufnr = util.get_source_buffer()
else
bufnr = vim.api.nvim_get_current_buf()
end
local bufnr, _ = util.get_buffers()
local count = data[bufnr]:count()
local new_num = pos.lnum + delta

View file

@ -4,8 +4,8 @@ local config = require 'aerial.config'
local M = {}
-- Update the aerial buffer from cached symbols
M.update_aerial_buffer = function(bufnr)
local aer_bufnr = util.get_aerial_buffer(bufnr)
M.update_aerial_buffer = function(buf)
local bufnr, aer_bufnr = util.get_buffers(buf)
if aer_bufnr == -1 then
return
end
@ -16,8 +16,8 @@ M.update_aerial_buffer = function(bufnr)
local max_len = 1
local lines = {}
local highlights = {}
data[bufnr]:visit(function(item)
local kind = config.get_kind_abbr(item.kind)
data[bufnr]:visit(function(item, conf)
local kind = config.get_icon(item.kind, conf.collapsed)
local spacing = string.rep(' ', item.level)
local text = string.format("%s%s %s", spacing, kind, item.name)
local strlen = string.len(text)
@ -64,8 +64,9 @@ M.update_aerial_buffer = function(bufnr)
end
-- Update the highlighted lines in the aerial buffer
M.update_highlights = function(bufnr)
if not data:has_symbols(bufnr) then
M.update_highlights = function(buf)
local bufnr, aer_bufnr = util.get_buffers(buf)
if not data:has_symbols(bufnr) or aer_bufnr == -1 then
return
end
local winids = {}
@ -82,10 +83,6 @@ M.update_highlights = function(bufnr)
return vim.fn.win_id2win(a) < vim.fn.win_id2win(b)
end)
local ns = vim.api.nvim_create_namespace('aerial-line')
local aer_bufnr = util.get_aerial_buffer(bufnr)
if aer_bufnr == -1 then
return
end
vim.api.nvim_buf_clear_namespace(aer_bufnr, ns, 0, -1)
local hl_width = math.floor(util.get_width(aer_bufnr) / win_count)
local hl_mode = config.get_highlight_mode()

View file

@ -51,6 +51,17 @@ M.get_aerial_buffer = function(bufnr)
return M.get_buffer_from_var(bufnr or 0, 'aerial_buffer')
end
M.get_buffers = function(bufnr)
if bufnr == nil or bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
end
if M.is_aerial_buffer(bufnr) then
return M.get_source_buffer(bufnr), bufnr
else
return bufnr, M.get_aerial_buffer(bufnr)
end
end
M.get_source_buffer = function(bufnr)
return M.get_buffer_from_var(bufnr or 0, 'source_buffer')
end

View file

@ -46,20 +46,6 @@ end
M._create_aerial_buffer = function(bufnr)
local aer_bufnr = vim.api.nvim_create_buf(false, true)
-- Set up default mappings
local mapper = function(mode, key, result)
vim.api.nvim_buf_set_keymap(aer_bufnr, mode, key, result, {noremap = true, silent = true})
end
mapper('n', '<CR>', "<cmd>lua require'aerial'.jump_to_loc()<CR>zvzz")
mapper('n', '<C-v>', "<cmd>lua require'aerial'.jump_to_loc(2)<CR>zvzz")
mapper('n', '<C-s>', "<cmd>lua require'aerial'.jump_to_loc(2, 'belowright split')<CR>zvzz")
mapper('n', '<C-j>', "j<cmd>lua require'aerial'.scroll_to_loc()<CR>")
mapper('n', '<C-k>', "k<cmd>lua require'aerial'.scroll_to_loc()<CR>")
mapper('n', ']]', "<cmd>lua require'aerial'.next_item()<CR>")
mapper('n', '[[', "<cmd>lua require'aerial'.prev_item()<CR>")
mapper('n', 'p', "<cmd>lua require'aerial'.scroll_to_loc()<CR>")
mapper('n', 'q', '<cmd>lua require"aerial".close()<CR>')
vim.api.nvim_set_current_buf(aer_bufnr)
-- Set buffer options
vim.api.nvim_buf_set_lines(aer_bufnr, 0, -1, false, {"Loading..."})