fix: Preview window position based on outline split position

- preview window can adapt based on position of outline window, and not
  based on config value of `position` left/right
- it can also properly vertically center-align, even when there are
  horizontal splits below the outline
- fixed a few bugs associated with previous rewrite commits in init.lua

config:
- Added min_height for preview window
This commit is contained in:
hedy
2023-11-18 15:04:26 +08:00
parent 62183f9d51
commit 090da7633b
6 changed files with 112 additions and 102 deletions

View File

@@ -23,7 +23,7 @@ M.defaults = {
-- On open, always followed. This is for auto_update_events.follow, whether
-- to auto update cursor position to reflect code location. If false, can
-- manually trigger with follow_cursor (API, command, keymap action).
auto_follow_cursor = true,
auto_set_cursor = true,
auto_update_events = {
follow = { 'CursorMoved' },
items = { 'InsertLeave', 'WinEnter', 'BufEnter', 'BufWinEnter', 'TabEnter', 'BufWritePost' },
@@ -40,7 +40,7 @@ M.defaults = {
auto_jump = false,
show_numbers = false,
show_relative_numbers = false,
---@type boolean?|string?
---@type boolean|string?
show_cursorline = true,
hide_cursor = false,
winhl = 'OutlineDetails:Comment,OutlineLineno:LineNr',
@@ -52,6 +52,7 @@ M.defaults = {
width = 50,
min_width = 50,
relative_width = true,
min_height = 10,
border = 'single',
open_hover_on_preview = false,
winhl = '',

View File

@@ -50,6 +50,9 @@ local function wipe_state()
}
end
---Calls writer.make_outline and then calls M.update_cursor_pos if update_cursor is not false
---@param update_cursor boolean?
---@param set_cursor_to_node outline.SymbolNode|outline.FlatSymbolNode?
local function _update_lines(update_cursor, set_cursor_to_node)
local current
M.state.flattened_outline_items, current =
@@ -64,6 +67,35 @@ local function _merge_items(items)
utils.merge_items_rec({ children = items }, { children = M.state.outline_items })
end
---Setup autocmds for the buffer that the outline attached to
---@param code_win integer Must be valid
---@param code_buf integer Must be valid
local function setup_attached_buffer_autocmd(code_win, code_buf)
local events = cfg.o.outline_items.auto_update_events
if
cfg.o.outline_items.highlight_hovered_item
or cfg.o.symbol_folding.auto_unfold_hover
then
if M.state.autocmds[code_win] then
vim.api.nvim_del_autocmd(M.state.autocmds[code_win])
M.state.autocmds[code_win] = nil
end
if utils.str_or_nonempty_table(events.follow) then
M.state.autocmds[code_win] =
vim.api.nvim_create_autocmd(events.follow, {
buffer = code_buf,
callback = function()
M._highlight_current_item(
code_win,
cfg.o.outline_items.auto_set_cursor
)
end,
})
end
end
end
local function __refresh()
local current_buffer_is_outline = M.view.bufnr == vim.api.nvim_get_current_buf()
if M.view:is_open() and not current_buffer_is_outline then
@@ -82,29 +114,16 @@ local function __refresh()
M.state.autocmds[M.state.code_win] = nil
end
end
M.state.code_win = curwin
M.state.code_buf = curbuf
if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then
if M.state.autocmds[curwin] then
vim.api.nvim_del_autocmd(M.state.autocmds[curwin])
M.state.autocmds[curwin] = nil
end
if utils.str_or_nonempty_table(cfg.o.outline_items.auto_update_events.follow) then
M.state.autocmds[curwin] =
vim.api.nvim_create_autocmd(cfg.o.outline_items.auto_update_events.follow, {
buffer = curbuf,
callback = function()
M._highlight_current_item(nil, cfg.o.outline_items.auto_follow_cursor)
end,
})
end
end
setup_attached_buffer_autocmd(curwin, curbuf)
local items = parser.parse(response, vim.api.nvim_get_current_buf())
_merge_items(items)
local update_cursor = newbuf or cfg.o.outline_items.auto_follow_cursor
local update_cursor = newbuf or cfg.o.outline_items.auto_set_cursor
_update_lines(update_cursor)
end
@@ -326,13 +345,22 @@ function M._highlight_current_item(winnr, update_cursor)
return
end
-- TODO: Find an efficient way to:
-- 1) Set highlight for all nodes in range (regardless of visibility)
-- 2) Find the line number of the deepest node in range, that is visible (no
-- parents folded)
-- In one go
local valid_code_win = vim.api.nvim_win_is_valid(M.state.code_win)
local valid_winnr = winnr and vim.api.nvim_win_is_valid(winnr)
-- XXX: Could current win ~= M.state.code_win here?
if not valid_code_win then
-- Definetely don't attempt to update anything if code win is no longer valid
return
end
if not valid_winnr then
return
elseif winnr ~= M.state.code_win then
-- Both valid, but given winnr ~= known code_win.
-- Best not to handle this situation at all to prevent any unwanted side
-- effects
return
end
_update_lines(update_cursor)
end
@@ -421,35 +449,16 @@ local function handler(response, opts)
return
end
if not opts then
opts = {}
end
M.state.code_win = vim.api.nvim_get_current_win()
M.state.code_buf = vim.api.nvim_get_current_buf()
M.state.opened_first_outline = true
if opts and opts.on_symbols then
opts.on_symbols()
end
M.view:setup_view()
if opts and opts.on_outline_setup then
opts.on_outline_setup()
end
if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then
if M.state.autocmds[M.state.code_win] then
vim.api.nvim_del_autocmd(M.state.autocmds[M.state.code_win])
M.state.autocmds[M.state.code_win] = nil
end
if utils.str_or_nonempty_table(cfg.o.outline_items.auto_update_events.follow) then
M.state.autocmds[M.state.code_win] =
vim.api.nvim_create_autocmd(cfg.o.outline_items.auto_update_events.follow, {
buffer = M.state.code_buf,
callback = function()
M._highlight_current_item(nil, cfg.o.outline_items.auto_follow_cursor)
end,
})
end
end
local sc = opts.split_command or cfg.get_split_command()
M.view:setup_view(sc)
-- clear state when buffer is closed
vim.api.nvim_buf_attach(M.view.bufnr, false, {
@@ -460,6 +469,7 @@ local function handler(response, opts)
setup_keymaps(M.view.bufnr)
setup_buffer_autocmd()
setup_attached_buffer_autocmd(M.state.code_win, M.state.code_buf)
local items = parser.parse(response, M.state.code_buf)
@@ -470,7 +480,7 @@ local function handler(response, opts)
M.update_cursor_pos(current)
if not cfg.o.outline_window.focus_on_open or (opts and not opts.focus_outline) then
if not cfg.o.outline_window.focus_on_open or not opts.focus_outline then
vim.fn.win_gotoid(M.state.code_win)
end
end
@@ -531,26 +541,13 @@ end
local function _cmd_open_with_mods(fn)
return function(opts)
local old_sc, use_old_sc
local split = opts.smods.split
if split ~= '' then
old_sc = cfg.o.outline_window.split_command
use_old_sc = true
cfg.o.outline_window.split_command = split .. ' vsplit'
local fnopts = { focus_outline = not opts.bang }
local sc = opts.smods.split
if sc ~= '' then
fnopts.split_command = sc .. ' vsplit'
end
local function on_outline_setup()
if use_old_sc then
cfg.o.outline_window.split_command = old_sc
-- the old option should already have been resolved during set up
end
end
if opts.bang then
fn({ focus_outline = false, on_outline_setup = on_outline_setup })
else
fn({ focus_outline = true, on_outline_setup = on_outline_setup })
end
fn(fnopts)
end
end

View File

@@ -26,29 +26,37 @@ end
M.has_code_win = has_code_win
local function get_width_offset()
---Get the correct column to place the floating window based on
-- Relative positions of the outline and the code window.
---@param preview_width integer
local function get_col(preview_width)
---@type integer
local outline_winnr = outline.view.winnr
local width = cfg.get_preview_width() + 3
local has_numbers = vim.api.nvim_win_get_option(outline_winnr, 'number')
has_numbers = has_numbers or vim.api.nvim_win_get_option(outline_winnr, 'relativenumber')
local outline_col = vim.api.nvim_win_get_position(outline_winnr)[2]
local outline_width = vim.api.nvim_win_get_width(outline_winnr)
local code_col = vim.api.nvim_win_get_position(outline.state.code_win)[2]
if has_numbers then
width = width + 4
end
-- TODO: What if code win is below/above outline instead?
-- FIXME: use actual window position based on view rather than config
if cfg.o.outline_window.position == 'right' then
width = 0 - width
local col = outline_col
if outline_col > code_col then
col = col - preview_width - 3
else
width = vim.api.nvim_win_get_width(outline_winnr) + 1
col = col + outline_width + 1
end
return width
return col
end
---@param preview_height integer
---@param outline_height integer
local function get_row(preview_height, outline_height)
local offset = math.floor((outline_height - preview_height) / 2) - 1
return vim.api.nvim_win_get_position(outline.view.winnr)[1] + offset
end
local function get_height()
return vim.api.nvim_list_uis()[1].height
return vim.api.nvim_win_get_height(outline.view.winnr)
end
local function get_hovered_node()
@@ -106,15 +114,16 @@ local function show_preview()
end,
})
local height = get_height()
local winheight = math.ceil(height / 2)
local width = cfg.get_preview_width()
local winheight = math.max(math.ceil(height / 2), cfg.o.preview_window.min_height)
state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, {
relative = 'win',
relative = 'editor',
height = winheight,
width = cfg.get_preview_width(),
width = width,
bufpos = { 0, 0 },
col = get_width_offset(),
col = get_col(width),
-- Position preview window middle-aligned vertically
row = math.floor((height - winheight) / 2) - 1,
row = get_row(winheight, height),
border = cfg.o.preview_window.border,
})
setup_preview_buf()

View File

@@ -42,7 +42,7 @@
---@class outline.FlatSymbolNode
---@field name string
---@field depth integer
---@field parent outline.SymbolNode
---@field parent outline.FlatSymbolNode
---@field deprecated boolean
---@field kind integer|string
---@field icon string
@@ -53,7 +53,7 @@
---@field range_end integer
---@field isLast boolean
---@field hierarchy boolean
---@field children? outline.SymbolNode[]
---@field children? outline.FlatSymbolNode[]
---@field traversal_child integer
---@field line_in_outline integer
---@field prefix_length integer
@@ -64,5 +64,4 @@
---@class outline.OutlineOpts
---@field focus_outline boolean? Whether to focus on outline of after some operation. If nil, defaults to true
---@field on_symbols function? After symbols have been received, before sidebar window is setup
---@field on_outline_setup function? After sidebar window is setup
---@field split_command string?

View File

@@ -6,15 +6,17 @@ function View:new()
return setmetatable({ bufnr = nil, winnr = nil }, { __index = View })
end
---creates the outline window and sets it up
function View:setup_view()
---Creates the outline window and sets it up
---@param split_command string A valid split command that is to be executed in order to create the view.
function View:setup_view(split_command)
-- create a scratch unlisted buffer
self.bufnr = vim.api.nvim_create_buf(false, true)
-- delete buffer when window is closed / buffer is hidden
vim.api.nvim_buf_set_option(self.bufnr, 'bufhidden', 'delete')
-- create a split
vim.cmd(cfg.get_split_command())
vim.cmd(split_command)
-- resize to a % of the current window size
vim.cmd('vertical resize ' .. cfg.get_window_width())