feat: Live previews (peek editor)
Disabled by default, but if this feature turns out to be useful and stable, it will replace the old preview window.
This commit is contained in:
@@ -79,6 +79,10 @@
|
||||
([#37](https://github.com/hedyhli/outline.nvim/issues/37))
|
||||
- Added `get_symbol` and `get_breadcrumb` functions (useful in
|
||||
statusline/winbar) ([#24](https://github.com/hedyhli/outline.nvim/issues/24))
|
||||
- New "Live Preview" feature which allows editing in the preview buffer. This
|
||||
allows navigating some symbol away from cursor location and make quick edits in
|
||||
the other position using the preview window. This feature is currently
|
||||
experimental and opt-in. Enable with `preview_window.live = true`
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -116,6 +120,9 @@
|
||||
- Follow cursor algorithm significantly improved
|
||||
- Highlight hovered item and initial opening of outline has been rewritten and
|
||||
performance improved
|
||||
- Revamped various provider-related modules such as rename/code-actions/hover
|
||||
to delegate the task to specific providers
|
||||
- Revamped the preview module for better per-tab outline support and more features
|
||||
|
||||
### Others
|
||||
|
||||
|
||||
24
README.md
24
README.md
@@ -309,6 +309,9 @@ Pass a table to the setup call with your configuration options.
|
||||
winhl = 'NormalFloat:',
|
||||
-- Pseudo-transparency of the preview window, see ':h winblend'
|
||||
winblend = 0
|
||||
-- Experimental feature that let's you edit the source content live
|
||||
-- in the preview window. Like VS Code's "peek editor".
|
||||
live = false
|
||||
},
|
||||
|
||||
-- These keymaps can be a string or a table for multiple keys.
|
||||
@@ -1036,6 +1039,27 @@ symbols = {
|
||||
|
||||
<div align=center><img width="500" alt="outline with disabled icon for String" src="https://github.com/hedyhli/outline.nvim/assets/50042066/26d258c6-9530-43d4-b88b-963304e3bf2d"></div>
|
||||
|
||||
### Live, editable previews
|
||||
|
||||
Press `K` to open the preview, press `K` again to focus on the preview window
|
||||
to make any quick edits, similar to VS Code's reference window "peek editor".
|
||||
|
||||
Then use `:q` to close the window, and continue browsing the outline.
|
||||
|
||||
```lua
|
||||
preview_window = {
|
||||
live = true,
|
||||
}
|
||||
```
|
||||
|
||||
Note that this feature is experimental and may be unstable.
|
||||
|
||||
https://github.com/hedyhli/outline.nvim/assets/50042066/183fc5f9-b369-41e2-a831-a4185704d76d
|
||||
|
||||
Auto-preview with the feature is also supported, set `auto_preview = true` and
|
||||
press `K` to focus on the auto-opened preview window. `:q` to quit the window.
|
||||
|
||||
|
||||
<!-- panvimdoc-ignore-start -->
|
||||
|
||||
---
|
||||
|
||||
@@ -48,6 +48,7 @@ M.defaults = {
|
||||
center_on_jump = true,
|
||||
},
|
||||
preview_window = {
|
||||
live = false,
|
||||
auto_preview = false,
|
||||
width = 50,
|
||||
min_width = 50,
|
||||
|
||||
@@ -24,9 +24,13 @@ local function setup_global_autocmd()
|
||||
pattern = '*',
|
||||
callback = function()
|
||||
local s = M._get_sidebar()
|
||||
local w = vim.api.nvim_get_current_win()
|
||||
if s and s.preview then
|
||||
-- Don't close preview when entering preview!
|
||||
if not s.preview.win or s.preview.win ~= w then
|
||||
s.preview:close()
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_create_autocmd('TabClosed', {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local cfg = require('outline.config')
|
||||
local providers = require('outline.providers')
|
||||
|
||||
local conf
|
||||
-- A floating window to preview the location of a symbol from the outline.
|
||||
-- Classical preview reads entire lines into a new buffer for preview. Live
|
||||
-- preview sets the buffer of floating window to the code buffer, which allows
|
||||
-- focusing by pressing the preview keymap again, to edit the buffer at that
|
||||
-- position.
|
||||
|
||||
---@class outline.Preview
|
||||
local Preview = {}
|
||||
@@ -9,77 +10,53 @@ local Preview = {}
|
||||
---@class outline.Preview
|
||||
---@field buf integer
|
||||
---@field win integer
|
||||
---@field width integer
|
||||
---@field height integer
|
||||
---@field width integer
|
||||
---@field outline_height integer
|
||||
---@field s outline.Sidebar
|
||||
---@field conf table
|
||||
|
||||
---@param s outline.Sidebar
|
||||
function Preview:new(s)
|
||||
-- Config must have been setup when calling Preview:new
|
||||
conf = cfg.o.preview_window
|
||||
---@class outline.LivePreview
|
||||
local LivePreview = {}
|
||||
|
||||
---@class outline.LivePreview
|
||||
---@field win integer
|
||||
---@field codewin integer
|
||||
---@field codebuf integer
|
||||
---@field height integer
|
||||
---@field width integer
|
||||
---@field outline_height integer
|
||||
---@field s outline.Sidebar
|
||||
---@field last_node outline.FlatSymbol
|
||||
---@field initial_cursorline boolean
|
||||
---@field conf table
|
||||
|
||||
---@param conf table
|
||||
function Preview:new(conf)
|
||||
if conf.live == true then
|
||||
return setmetatable({
|
||||
conf = conf,
|
||||
win = nil,
|
||||
width = nil,
|
||||
height = nil,
|
||||
}, { __index = LivePreview })
|
||||
else
|
||||
return setmetatable({
|
||||
conf = conf,
|
||||
buf = nil,
|
||||
win = nil,
|
||||
s = s,
|
||||
width = nil,
|
||||
height = nil,
|
||||
}, { __index = Preview })
|
||||
end
|
||||
|
||||
---Creates new preview window and sets the content. Calls setup and set_lines.
|
||||
function Preview:create()
|
||||
self.buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_attach(self.buf, false, {
|
||||
on_detach = function()
|
||||
self.buf = nil
|
||||
self.win = nil
|
||||
end,
|
||||
})
|
||||
-- FIXME: Re-calculate dimensions on update-preview, in case outline window
|
||||
-- was resized between last preview and next preview?
|
||||
self.outline_height = vim.api.nvim_win_get_height(self.s.view.win)
|
||||
self.width = conf.width
|
||||
self.height = math.max(math.ceil(self.outline_height / 2), conf.min_height)
|
||||
self.win = vim.api.nvim_open_win(self.buf, false, {
|
||||
relative = 'editor',
|
||||
height = self.height,
|
||||
width = self.width,
|
||||
bufpos = { 0, 0 },
|
||||
col = self:calc_col(),
|
||||
row = self:calc_row(),
|
||||
border = conf.border,
|
||||
})
|
||||
self:setup()
|
||||
self:set_lines()
|
||||
end
|
||||
|
||||
---Set up highlights, window, and buffer options
|
||||
function Preview:setup()
|
||||
vim.api.nvim_win_set_option(self.win, 'winhl', conf.winhl)
|
||||
vim.api.nvim_win_set_option(self.win, 'winblend', conf.winblend)
|
||||
|
||||
local code_buf = self.s.code.buf
|
||||
local ft = vim.api.nvim_buf_get_option(code_buf, 'filetype')
|
||||
vim.api.nvim_buf_set_option(self.buf, 'syntax', ft)
|
||||
|
||||
local ts_highlight_fn = vim.treesitter.start
|
||||
if not _G._outline_nvim_has[8] then
|
||||
local ok, ts_highlight = pcall(require, 'nvim-treesitter.highlight')
|
||||
if ok then
|
||||
ts_highlight_fn = ts_highlight.attach
|
||||
end
|
||||
end
|
||||
pcall(ts_highlight_fn, self.buf, ft)
|
||||
|
||||
vim.api.nvim_buf_set_option(self.buf, 'bufhidden', 'delete')
|
||||
vim.api.nvim_win_set_option(self.win, 'cursorline', true)
|
||||
vim.api.nvim_buf_set_option(self.buf, 'modifiable', false)
|
||||
end
|
||||
|
||||
---Get the correct column to place the floating window based on relative
|
||||
---positions of the outline and the code window.
|
||||
function Preview:calc_col()
|
||||
---@param self outline.Preview|outline.LivePreview
|
||||
local function calc_col(self)
|
||||
-- TODO: Re-calculate dimensions on update-preview, in case outline window
|
||||
-- was resized between last preview and next preview?
|
||||
---@type integer
|
||||
local outline_winnr = self.s.view.win
|
||||
local outline_col = vim.api.nvim_win_get_position(outline_winnr)[2]
|
||||
@@ -99,20 +76,63 @@ function Preview:calc_col()
|
||||
end
|
||||
|
||||
---Get the vertically center-aligned row for preview window
|
||||
function Preview:calc_row()
|
||||
---@param self outline.Preview|outline.LivePreview
|
||||
local function calc_row(self)
|
||||
local offset = math.floor((self.outline_height - self.height) / 2) - 1
|
||||
return vim.api.nvim_win_get_position(self.s.view.win)[1] + offset
|
||||
end
|
||||
|
||||
---Set and update preview buffer content
|
||||
function Preview:set_lines()
|
||||
-- TODO: Editable, savable buffer in the preview like VS Code for quick
|
||||
-- edits? It can be like LSP. Trigger preview to open, trigger again to focus
|
||||
-- (so buffer can be edited).
|
||||
-- This can be achieved by simply opening the buffer from inside the preview
|
||||
-- window.
|
||||
-- This also removes the need of manually setting highlights, treesitter etc.
|
||||
-- The preview window will look exactly the same as in the code window.
|
||||
function Preview:create()
|
||||
self.buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_attach(self.buf, false, {
|
||||
on_detach = function()
|
||||
self.buf = nil
|
||||
self.win = nil
|
||||
end,
|
||||
})
|
||||
self.outline_height = vim.api.nvim_win_get_height(self.s.view.win)
|
||||
self.width = self.conf.width
|
||||
self.height = math.max(math.ceil(self.outline_height / 2), self.conf.min_height)
|
||||
self.win = vim.api.nvim_open_win(self.buf, false, {
|
||||
relative = 'editor',
|
||||
height = self.height,
|
||||
width = self.width,
|
||||
bufpos = { 0, 0 },
|
||||
col = calc_col(self),
|
||||
row = calc_row(self),
|
||||
border = self.conf.border,
|
||||
focusable = false,
|
||||
})
|
||||
self:setup()
|
||||
self:update()
|
||||
end
|
||||
|
||||
|
||||
---Set buf & win options, and setup highlight
|
||||
function Preview:setup()
|
||||
vim.api.nvim_win_set_option(self.win, 'winhl', self.conf.winhl)
|
||||
vim.api.nvim_win_set_option(self.win, 'winblend', self.conf.winblend)
|
||||
|
||||
local code_buf = self.s.code.buf
|
||||
local ft = vim.api.nvim_buf_get_option(code_buf, 'filetype')
|
||||
vim.api.nvim_buf_set_option(self.buf, 'syntax', ft)
|
||||
|
||||
local ts_highlight_fn = vim.treesitter.start
|
||||
if not _G._outline_nvim_has[8] then
|
||||
local ok, ts_highlight = pcall(require, 'nvim-treesitter.highlight')
|
||||
if ok then
|
||||
ts_highlight_fn = ts_highlight.attach
|
||||
end
|
||||
end
|
||||
pcall(ts_highlight_fn, self.buf, ft)
|
||||
|
||||
vim.api.nvim_buf_set_option(self.buf, 'bufhidden', 'delete')
|
||||
vim.api.nvim_buf_set_option(self.buf, 'modifiable', false)
|
||||
vim.api.nvim_win_set_option(self.win, 'cursorline', true)
|
||||
end
|
||||
|
||||
|
||||
function Preview:update()
|
||||
local node = self.s:_current_node()
|
||||
if not node then
|
||||
return
|
||||
@@ -133,24 +153,17 @@ function Preview:show()
|
||||
return
|
||||
end
|
||||
|
||||
if self.buf and self.win then
|
||||
self:set_lines()
|
||||
else
|
||||
if not self.buf or not self.win then
|
||||
self:create()
|
||||
end
|
||||
|
||||
if conf.open_hover_on_preview then
|
||||
providers.action(self.s, 'show_hover', { self.s })
|
||||
else
|
||||
self:update()
|
||||
end
|
||||
end
|
||||
|
||||
function Preview:close()
|
||||
-- TODO: Why was this in symbols-outline.nvim?
|
||||
-- if self.s:has_code_win() then
|
||||
if self.win ~= nil and vim.api.nvim_win_is_valid(self.win) then
|
||||
vim.api.nvim_win_close(self.win, true)
|
||||
end
|
||||
-- end
|
||||
end
|
||||
|
||||
function Preview:toggle()
|
||||
@@ -161,4 +174,102 @@ function Preview:toggle()
|
||||
end
|
||||
end
|
||||
|
||||
---Creates new preview window and sets the content. Calls setup and set_lines.
|
||||
function LivePreview:create()
|
||||
self.codewin = self.s.code.win
|
||||
self.initial_cursorline = vim.api.nvim_win_get_option(self.s.code.win, 'cursorline')
|
||||
self.outline_height = vim.api.nvim_win_get_height(self.s.view.win)
|
||||
self.width = self.conf.width
|
||||
self.height = math.max(math.ceil(self.outline_height / 2), self.conf.min_height)
|
||||
self.win = vim.api.nvim_open_win(self.s.code.buf, false, {
|
||||
relative = 'editor',
|
||||
height = self.height,
|
||||
width = self.width,
|
||||
bufpos = { 0, 0 },
|
||||
col = calc_col(self),
|
||||
row = calc_row(self),
|
||||
border = self.conf.border,
|
||||
-- Setting this to disallow using other methods to focus on this window,
|
||||
-- because currently the autocmds from setup() isn't triggering if user did
|
||||
-- not use close() and focus().
|
||||
focusable = false,
|
||||
})
|
||||
self:setup()
|
||||
end
|
||||
|
||||
---Set buf & win options, and autocmds
|
||||
function LivePreview:setup()
|
||||
vim.api.nvim_win_set_option(self.win, 'winhl', self.conf.winhl)
|
||||
vim.api.nvim_win_set_option(self.win, 'winblend', self.conf.winblend)
|
||||
vim.api.nvim_win_set_option(self.win, 'cursorline', true)
|
||||
|
||||
vim.api.nvim_create_autocmd('WinClosed', {
|
||||
pattern = tostring(self.win),
|
||||
once = true,
|
||||
callback = function()
|
||||
self.s.code.win = self.codewin
|
||||
self.win = nil
|
||||
end
|
||||
})
|
||||
vim.api.nvim_create_autocmd('WinEnter', {
|
||||
pattern = tostring(self.win),
|
||||
once = true,
|
||||
callback = function()
|
||||
-- This doesn't work at all?
|
||||
vim.api.nvim_win_set_option(self.win, 'cursorline', self.initial_cursorline)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function LivePreview:update(node)
|
||||
vim.api.nvim_win_set_buf(self.win, self.s.code.buf)
|
||||
vim.api.nvim_win_set_cursor(self.win, { node.line + 1, node.character })
|
||||
end
|
||||
|
||||
function LivePreview:focus()
|
||||
vim.api.nvim_set_current_win(self.win)
|
||||
-- Remove this when the autocmd for WinEnter works above
|
||||
vim.api.nvim_win_set_option(self.win, 'cursorline', self.initial_cursorline)
|
||||
end
|
||||
|
||||
---Create, focus, or update preview
|
||||
function LivePreview:show()
|
||||
if not self.s:has_focus() or #vim.api.nvim_list_wins() < 2 then
|
||||
return
|
||||
end
|
||||
|
||||
local node = self.s:_current_node()
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.win then
|
||||
self:create()
|
||||
vim.api.nvim_win_set_cursor(self.win, { node.line + 1, node.character })
|
||||
self.last_node = node
|
||||
return
|
||||
end
|
||||
|
||||
if node == self.last_node then
|
||||
self:focus()
|
||||
else
|
||||
self:update(node)
|
||||
end
|
||||
|
||||
self.last_node = node
|
||||
end
|
||||
|
||||
function LivePreview:close()
|
||||
if self.win ~= nil then
|
||||
vim.api.nvim_win_close(self.win, true)
|
||||
-- autocmd from setup is not triggered here?
|
||||
self.win = nil
|
||||
self.s.code.win = self.codewin
|
||||
end
|
||||
end
|
||||
|
||||
function LivePreview:toggle()
|
||||
self:show()
|
||||
end
|
||||
|
||||
return Preview
|
||||
|
||||
@@ -26,13 +26,13 @@ local Sidebar = {}
|
||||
---@field code outline.SidebarCodeState
|
||||
---@field augroup integer
|
||||
---@field provider outline.Provider?
|
||||
---@field preview outline.Preview
|
||||
---@field preview outline.Preview|outline.LivePreview
|
||||
|
||||
function Sidebar:new(id)
|
||||
return setmetatable({
|
||||
id = id,
|
||||
view = View:new(),
|
||||
preview = Preview:new(),
|
||||
preview = Preview:new(cfg.o.preview_window),
|
||||
code = { buf = 0, win = 0 },
|
||||
items = {},
|
||||
flats = {},
|
||||
@@ -138,7 +138,6 @@ function Sidebar:setup_keymaps()
|
||||
goto_and_close = { '_goto_and_close', {} },
|
||||
down_and_jump = { '_move_and_jump', { 'down' } },
|
||||
up_and_jump = { '_move_and_jump', { 'up' } },
|
||||
toggle_preview = { function() self.preview:toggle() end, {} },
|
||||
fold_toggle = { '_toggle_fold', {} },
|
||||
fold = { '_set_folded', { true } },
|
||||
unfold = { '_set_folded', { false } },
|
||||
@@ -159,6 +158,14 @@ function Sidebar:setup_keymaps()
|
||||
---@diagnostic disable-next-line param-type-mismatch
|
||||
self:nmap(name, meth[1], meth[2])
|
||||
end
|
||||
|
||||
local toggle_preview
|
||||
if cfg.o.preview_window.auto_preview and cfg.o.preview_window.live then
|
||||
toggle_preview = { function() self.preview:focus() end, {} }
|
||||
else
|
||||
toggle_preview = { function() self.preview:toggle() end, {} }
|
||||
end
|
||||
self:nmap('toggle_preview', toggle_preview[1], toggle_preview[2])
|
||||
end
|
||||
-- stylua: ignore end
|
||||
|
||||
@@ -240,7 +247,6 @@ function Sidebar:update_cursor_style()
|
||||
|
||||
-- Set cursor color to CursorLine in normal mode
|
||||
if hide_cursor then
|
||||
self.original_cursor = vim.o.guicursor
|
||||
local cur = vim.o.guicursor:match('n.-:(.-)[-,]')
|
||||
vim.opt.guicursor:append('n:' .. cur .. '-Cursorline')
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user