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

@@ -36,19 +36,21 @@
`CursorHold`. This is also configurable now using `CursorHold`. This is also configurable now using
`outline_items.auto_update_events` with key `follow`. The key `items` controls `outline_items.auto_update_events` with key `follow`. The key `items` controls
the events that should trigger a re-request of symbols from the provider. the events that should trigger a re-request of symbols from the provider.
- Config option for cursorline now supports 2 other string values,
`focus_in_outline` and `focus_in_code`. These make the cursorline only show up
depending on cursor focus. The first option, `focus_in_outline` makes it so
cursorline is enabled only when focus is in outline. This lessens the visual
changes due to auto follow_cursor.
- On fold all or unfold all operations, the cursor will now stay on the same - On fold all or unfold all operations, the cursor will now stay on the same
node, rather than the same line in the outline. node, rather than the same line in the outline.
- Optionally not automatically update cursor position in outline to reflect - Optionally not automatically update cursor position in outline to reflect
cursor location in code. The auto-update is triggered by events from cursor location in code. The auto-update is triggered by events from
`outline_items.auto_update_events.follow`, and disabling of follow-cursor can `outline_items.auto_update_events.follow` which controls both highlighting of
be done through `outline_items.auto_follow_cursor`. Regardless, manual hovered node and also setting of cursor in outline. To disable the latter,
follow-cursor can still be done using `:FollowCursor[!]` or lua API use `outline_items.auto_set_cursor`. Disabling the former can still be done using
`follow_cursor({ focus_outline = true/false })`. `outline_items.highlight_hovered_item`. Regardless, manual follow-cursor can
still be done using `:FollowCursor[!]` or lua API `follow_cursor({
focus_outline = true/false })`.
- Config option for cursorline now supports 2 other string values,
`focus_in_outline` and `focus_in_code`. These make the cursorline only show up
depending on cursor focus. The first option, `focus_in_outline` makes it so
cursorline is enabled only when focus is in outline. This lessens the visual
changes due to `auto_set_cursor`, when focus is in code.
### Fixes ### Fixes

View File

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

View File

@@ -50,6 +50,9 @@ local function wipe_state()
} }
end 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 function _update_lines(update_cursor, set_cursor_to_node)
local current local current
M.state.flattened_outline_items, 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 }) utils.merge_items_rec({ children = items }, { children = M.state.outline_items })
end 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 function __refresh()
local current_buffer_is_outline = M.view.bufnr == vim.api.nvim_get_current_buf() 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 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 M.state.autocmds[M.state.code_win] = nil
end end
end end
M.state.code_win = curwin M.state.code_win = curwin
M.state.code_buf = curbuf M.state.code_buf = curbuf
if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then setup_attached_buffer_autocmd(curwin, curbuf)
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
local items = parser.parse(response, vim.api.nvim_get_current_buf()) local items = parser.parse(response, vim.api.nvim_get_current_buf())
_merge_items(items) _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) _update_lines(update_cursor)
end end
@@ -326,13 +345,22 @@ function M._highlight_current_item(winnr, update_cursor)
return return
end end
-- TODO: Find an efficient way to: local valid_code_win = vim.api.nvim_win_is_valid(M.state.code_win)
-- 1) Set highlight for all nodes in range (regardless of visibility) local valid_winnr = winnr and vim.api.nvim_win_is_valid(winnr)
-- 2) Find the line number of the deepest node in range, that is visible (no
-- parents folded)
-- In one go
-- 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) _update_lines(update_cursor)
end end
@@ -421,35 +449,16 @@ local function handler(response, opts)
return return
end end
if not opts then
opts = {}
end
M.state.code_win = vim.api.nvim_get_current_win() M.state.code_win = vim.api.nvim_get_current_win()
M.state.code_buf = vim.api.nvim_get_current_buf() M.state.code_buf = vim.api.nvim_get_current_buf()
M.state.opened_first_outline = true M.state.opened_first_outline = true
if opts and opts.on_symbols then local sc = opts.split_command or cfg.get_split_command()
opts.on_symbols() M.view:setup_view(sc)
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
-- clear state when buffer is closed -- clear state when buffer is closed
vim.api.nvim_buf_attach(M.view.bufnr, false, { vim.api.nvim_buf_attach(M.view.bufnr, false, {
@@ -460,6 +469,7 @@ local function handler(response, opts)
setup_keymaps(M.view.bufnr) setup_keymaps(M.view.bufnr)
setup_buffer_autocmd() setup_buffer_autocmd()
setup_attached_buffer_autocmd(M.state.code_win, M.state.code_buf)
local items = parser.parse(response, 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) 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) vim.fn.win_gotoid(M.state.code_win)
end end
end end
@@ -531,26 +541,13 @@ end
local function _cmd_open_with_mods(fn) local function _cmd_open_with_mods(fn)
return function(opts) return function(opts)
local old_sc, use_old_sc local fnopts = { focus_outline = not opts.bang }
local split = opts.smods.split local sc = opts.smods.split
if split ~= '' then if sc ~= '' then
old_sc = cfg.o.outline_window.split_command fnopts.split_command = sc .. ' vsplit'
use_old_sc = true
cfg.o.outline_window.split_command = split .. ' vsplit'
end end
local function on_outline_setup() fn(fnopts)
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
end end
end end

View File

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

View File

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

View File

@@ -6,15 +6,17 @@ function View:new()
return setmetatable({ bufnr = nil, winnr = nil }, { __index = View }) return setmetatable({ bufnr = nil, winnr = nil }, { __index = View })
end end
---creates the outline window and sets it up ---Creates the outline window and sets it up
function View:setup_view() ---@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 -- create a scratch unlisted buffer
self.bufnr = vim.api.nvim_create_buf(false, true) self.bufnr = vim.api.nvim_create_buf(false, true)
-- delete buffer when window is closed / buffer is hidden -- delete buffer when window is closed / buffer is hidden
vim.api.nvim_buf_set_option(self.bufnr, 'bufhidden', 'delete') vim.api.nvim_buf_set_option(self.bufnr, 'bufhidden', 'delete')
-- create a split -- create a split
vim.cmd(cfg.get_split_command()) vim.cmd(split_command)
-- resize to a % of the current window size -- resize to a % of the current window size
vim.cmd('vertical resize ' .. cfg.get_window_width()) vim.cmd('vertical resize ' .. cfg.get_window_width())