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:
20
CHANGELOG.md
20
CHANGELOG.md
@@ -36,19 +36,21 @@
|
||||
`CursorHold`. This is also configurable now using
|
||||
`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.
|
||||
- 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
|
||||
node, rather than the same line in the outline.
|
||||
- Optionally not automatically update cursor position in outline to reflect
|
||||
cursor location in code. The auto-update is triggered by events from
|
||||
`outline_items.auto_update_events.follow`, and disabling of follow-cursor can
|
||||
be done through `outline_items.auto_follow_cursor`. Regardless, manual
|
||||
follow-cursor can still be done using `:FollowCursor[!]` or lua API
|
||||
`follow_cursor({ focus_outline = true/false })`.
|
||||
`outline_items.auto_update_events.follow` which controls both highlighting of
|
||||
hovered node and also setting of cursor in outline. To disable the latter,
|
||||
use `outline_items.auto_set_cursor`. Disabling the former can still be done using
|
||||
`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
|
||||
|
||||
|
||||
@@ -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 = '',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user