mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 12:50:03 +08:00
420 lines
12 KiB
Lua
420 lines
12 KiB
Lua
local vim = vim
|
|
local utils = require("neo-tree.utils")
|
|
local highlights = require("neo-tree.ui.highlights")
|
|
local events = require("neo-tree.events")
|
|
local manager = require("neo-tree.sources.manager")
|
|
local log = require("neo-tree.log")
|
|
local renderer = require("neo-tree.ui.renderer")
|
|
|
|
local neo_tree_preview_namespace = vim.api.nvim_create_namespace("neo_tree_preview")
|
|
|
|
local function create_floating_preview_window(state)
|
|
local default_position = utils.resolve_config_option(state, "window.position", "left")
|
|
state.current_position = state.current_position or default_position
|
|
|
|
local winwidth = vim.api.nvim_win_get_width(state.winid)
|
|
local winheight = vim.api.nvim_win_get_height(state.winid)
|
|
local height = vim.o.lines - 4
|
|
local width = 120
|
|
local row, col = 0, 0
|
|
|
|
if state.current_position == "left" then
|
|
col = winwidth + 1
|
|
width = math.min(vim.o.columns - col, 120)
|
|
elseif state.current_position == "top" or state.current_position == "bottom" then
|
|
height = height - winheight
|
|
width = winwidth - 2
|
|
if state.current_position == "top" then
|
|
row = vim.api.nvim_win_get_height(state.winid) + 1
|
|
end
|
|
elseif state.current_position == "right" then
|
|
width = math.min(vim.o.columns - winwidth - 4, 120)
|
|
col = vim.o.columns - winwidth - width - 3
|
|
elseif state.current_position == "float" then
|
|
local pos = vim.api.nvim_win_get_position(state.winid)
|
|
-- preview will be same height and top as tree
|
|
row = pos[1] - 1
|
|
height = winheight
|
|
|
|
-- tree and preview window will be side by side and centered in the editor
|
|
width = math.min(vim.o.columns - winwidth - 4, 120)
|
|
local total_width = winwidth + width + 4
|
|
local margin = math.floor((vim.o.columns - total_width) / 2)
|
|
col = margin + winwidth + 2
|
|
|
|
-- move the tree window to make the combined layout centered
|
|
local popup = renderer.get_nui_popup(state.winid)
|
|
popup:update_layout({
|
|
relative = "editor",
|
|
position = {
|
|
row = row,
|
|
col = margin,
|
|
},
|
|
})
|
|
else
|
|
local cur_pos = state.current_position or "unknown"
|
|
log.error('Preview cannot be used when position = "' .. cur_pos .. '"')
|
|
return
|
|
end
|
|
|
|
local popups = require("neo-tree.ui.popups")
|
|
local options = popups.popup_options("Neo-tree Preview", width, {
|
|
ns_id = highlights.ns_id,
|
|
size = { height = height, width = width },
|
|
relative = "editor",
|
|
position = {
|
|
row = row,
|
|
col = col,
|
|
},
|
|
win_options = {
|
|
number = true,
|
|
winhighlight = "Normal:"
|
|
.. highlights.FLOAT_NORMAL
|
|
.. ",FloatBorder:"
|
|
.. highlights.FLOAT_BORDER,
|
|
},
|
|
})
|
|
options.zindex = 40
|
|
options.buf_options.filetype = "neo-tree-preview"
|
|
|
|
local NuiPopup = require("nui.popup")
|
|
local win = NuiPopup(options)
|
|
win:mount()
|
|
return win
|
|
end
|
|
|
|
local Preview = {}
|
|
local instance = nil
|
|
|
|
---Creates a new preview.
|
|
---@param state table The state of the source.
|
|
---@return table preview A new preview. A preview is a table consisting of the following keys:
|
|
-- active = boolean Whether the preview is active.
|
|
-- winid = number The id of the window being used to preview.
|
|
-- is_neo_tree_window boolean Whether the preview window belongs to neo-tree.
|
|
-- bufnr = number The buffer that is currently in the preview window.
|
|
-- start_pos = array or nil An array-like table specifying the (0-indexed) starting position of the previewed text.
|
|
-- end_pos = array or nil An array-like table specifying the (0-indexed) ending position of the preview text.
|
|
-- truth = table A table containing information to be restored when the preview ends.
|
|
-- events = array A list of events the preview is subscribed to.
|
|
--These keys should not be altered directly. Note that the keys `start_pos`, `end_pos` and `truth`
|
|
--may be inaccurate if `active` is false.
|
|
function Preview:new(state)
|
|
local preview = {}
|
|
preview.active = false
|
|
preview.config = vim.deepcopy(state.config)
|
|
setmetatable(preview, { __index = self })
|
|
preview:findWindow(state)
|
|
return preview
|
|
end
|
|
|
|
---Preview a buffer in the preview window and optionally reveal and highlight the previewed text.
|
|
---@param bufnr number? The number of the buffer to be previewed.
|
|
---@param start_pos table? The (0-indexed) starting position of the previewed text. May be absent.
|
|
---@param end_pos table? The (0-indexed) ending position of the previewed text. May be absent
|
|
function Preview:preview(bufnr, start_pos, end_pos)
|
|
if self.is_neo_tree_window then
|
|
log.warn("Could not find appropriate window for preview")
|
|
return
|
|
end
|
|
|
|
bufnr = bufnr or self.bufnr
|
|
if not self.active then
|
|
self:activate()
|
|
end
|
|
|
|
if not self.active then
|
|
return
|
|
end
|
|
|
|
if bufnr ~= self.bufnr then
|
|
self:setBuffer(bufnr)
|
|
end
|
|
|
|
self:clearHighlight()
|
|
|
|
self.bufnr = bufnr
|
|
self.start_pos = start_pos
|
|
self.end_pos = end_pos
|
|
|
|
self:reveal()
|
|
self:highlight()
|
|
end
|
|
|
|
---Reverts the preview and inactivates it, restoring the preview window to its previous state.
|
|
function Preview:revert()
|
|
self.active = false
|
|
self:unsubscribe()
|
|
self:clearHighlight()
|
|
|
|
if not renderer.is_window_valid(self.winid) then
|
|
self.winid = nil
|
|
return
|
|
end
|
|
|
|
if self.config.use_float then
|
|
vim.api.nvim_win_close(self.winid, true)
|
|
self.winid = nil
|
|
return
|
|
else
|
|
local foldenable = utils.get_value(self.truth, "options.foldenable", nil, false)
|
|
if foldenable ~= nil then
|
|
vim.api.nvim_win_set_option(self.winid, "foldenable", self.truth.options.foldenable)
|
|
end
|
|
vim.api.nvim_win_set_var(self.winid, "neo_tree_preview", 0)
|
|
end
|
|
|
|
local bufnr = self.truth.bufnr
|
|
if type(bufnr) ~= "number" then
|
|
return
|
|
end
|
|
if not vim.api.nvim_buf_is_valid(bufnr) then
|
|
return
|
|
end
|
|
self:setBuffer(bufnr)
|
|
self.bufnr = bufnr
|
|
if vim.api.nvim_win_is_valid(self.winid) then
|
|
vim.api.nvim_win_call(self.winid, function()
|
|
vim.fn.winrestview(self.truth.view)
|
|
end)
|
|
end
|
|
vim.api.nvim_buf_set_option(self.bufnr, "bufhidden", self.truth.options.bufhidden)
|
|
end
|
|
|
|
---Subscribe to event and add it to the preview event list.
|
|
--@param source string? Name of the source to add the event to. Will use `events.subscribe` if nil.
|
|
--@param event table Event to subscribe to.
|
|
function Preview:subscribe(source, event)
|
|
if source == nil then
|
|
events.subscribe(event)
|
|
else
|
|
manager.subscribe(source, event)
|
|
end
|
|
self.events = self.events or {}
|
|
table.insert(self.events, { source = source, event = event })
|
|
end
|
|
|
|
---Unsubscribe to all events in the preview event list.
|
|
function Preview:unsubscribe()
|
|
if self.events == nil then
|
|
return
|
|
end
|
|
for _, event in ipairs(self.events) do
|
|
if event.source == nil then
|
|
events.unsubscribe(event.event)
|
|
else
|
|
manager.unsubscribe(event.source, event.event)
|
|
end
|
|
end
|
|
self.events = {}
|
|
end
|
|
|
|
---Finds the appropriate window and updates the preview accordingly.
|
|
---@param state table The state of the source.
|
|
function Preview:findWindow(state)
|
|
local winid, is_neo_tree_window
|
|
if self.config.use_float then
|
|
if
|
|
type(self.winid) == "number"
|
|
and vim.api.nvim_win_is_valid(self.winid)
|
|
and utils.is_floating(self.winid)
|
|
then
|
|
return
|
|
end
|
|
local win = create_floating_preview_window(state)
|
|
if not win then
|
|
self.active = false
|
|
return
|
|
end
|
|
winid = win.winid
|
|
is_neo_tree_window = false
|
|
else
|
|
winid, is_neo_tree_window = utils.get_appropriate_window(state)
|
|
self.bufnr = vim.api.nvim_win_get_buf(winid)
|
|
end
|
|
|
|
if winid == self.winid then
|
|
return
|
|
end
|
|
self.winid, self.is_neo_tree_window = winid, is_neo_tree_window
|
|
|
|
if self.active then
|
|
self:revert()
|
|
self:preview()
|
|
end
|
|
end
|
|
|
|
---Activates the preview, but does not populate the preview window,
|
|
function Preview:activate()
|
|
if self.active then
|
|
return
|
|
end
|
|
if not renderer.is_window_valid(self.winid) then
|
|
return
|
|
end
|
|
if self.config.use_float then
|
|
self.truth = {}
|
|
else
|
|
self.truth = {
|
|
bufnr = self.bufnr,
|
|
view = vim.api.nvim_win_call(self.winid, vim.fn.winsaveview),
|
|
options = {
|
|
bufhidden = vim.api.nvim_buf_get_option(self.bufnr, "bufhidden"),
|
|
foldenable = vim.api.nvim_win_get_option(self.winid, "foldenable"),
|
|
},
|
|
}
|
|
vim.api.nvim_buf_set_option(self.bufnr, "bufhidden", "hide")
|
|
vim.api.nvim_win_set_option(self.winid, "foldenable", false)
|
|
end
|
|
self.active = true
|
|
vim.api.nvim_win_set_var(self.winid, "neo_tree_preview", 1)
|
|
end
|
|
|
|
---Set the buffer in the preview window without executing BufEnter or BufWinEnter autocommands.
|
|
--@param bufnr number The buffer number of the buffer to set.
|
|
function Preview:setBuffer(bufnr)
|
|
local eventignore = vim.opt.eventignore
|
|
vim.opt.eventignore:append("BufEnter,BufWinEnter")
|
|
vim.api.nvim_win_set_buf(self.winid, bufnr)
|
|
if self.config.use_float then
|
|
-- I'm not sufe why float windows won;t show numbers without this
|
|
vim.api.nvim_win_set_option(self.winid, "number", true)
|
|
end
|
|
vim.opt.eventignore = eventignore
|
|
end
|
|
|
|
---Move the cursor to the previewed position and center the screen.
|
|
function Preview:reveal()
|
|
local pos = self.start_pos or self.end_pos
|
|
if not self.active or not self.winid or not pos then
|
|
return
|
|
end
|
|
vim.api.nvim_win_set_cursor(self.winid, { (pos[1] or 0) + 1, pos[2] or 0 })
|
|
vim.api.nvim_win_call(self.winid, function()
|
|
vim.cmd("normal! zz")
|
|
end)
|
|
end
|
|
|
|
---Highlight the previewed range
|
|
function Preview:highlight()
|
|
if not self.active or not self.bufnr then
|
|
return
|
|
end
|
|
local start_pos, end_pos = self.start_pos, self.end_pos
|
|
if not start_pos and not end_pos then
|
|
return
|
|
elseif not start_pos then
|
|
start_pos = end_pos
|
|
elseif not end_pos then
|
|
end_pos = start_pos
|
|
end
|
|
|
|
local highlight = function(line, col_start, col_end)
|
|
vim.api.nvim_buf_add_highlight(
|
|
self.bufnr,
|
|
neo_tree_preview_namespace,
|
|
highlights.PREVIEW,
|
|
line,
|
|
col_start,
|
|
col_end
|
|
)
|
|
end
|
|
|
|
local start_line, end_line = start_pos[1], end_pos[1]
|
|
local start_col, end_col = start_pos[2], end_pos[2]
|
|
if start_line == end_line then
|
|
highlight(start_line, start_col, end_col)
|
|
else
|
|
highlight(start_line, start_col, -1)
|
|
for line = start_line + 1, end_line - 1 do
|
|
highlight(line, 0, -1)
|
|
end
|
|
highlight(end_line, 0, end_col)
|
|
end
|
|
end
|
|
|
|
---Clear the preview highlight in the buffer currently in the preview window.
|
|
function Preview:clearHighlight()
|
|
if type(self.bufnr) == "number" and vim.api.nvim_buf_is_valid(self.bufnr) then
|
|
vim.api.nvim_buf_clear_namespace(self.bufnr, neo_tree_preview_namespace, 0, -1)
|
|
end
|
|
end
|
|
|
|
local toggle_state = false
|
|
|
|
Preview.hide = function()
|
|
toggle_state = false
|
|
if instance then
|
|
instance:revert()
|
|
end
|
|
instance = nil
|
|
end
|
|
|
|
Preview.is_active = function()
|
|
return instance and instance.active
|
|
end
|
|
|
|
Preview.show = function(state)
|
|
local node = state.tree:get_node()
|
|
if node.type == "directory" then
|
|
return
|
|
end
|
|
|
|
if instance then
|
|
instance:findWindow(state)
|
|
else
|
|
instance = Preview:new(state)
|
|
end
|
|
|
|
local extra = node.extra or {}
|
|
local position = extra.position
|
|
local end_position = extra.end_position
|
|
local path = node.path or node:get_id()
|
|
local bufnr = extra.bufnr or vim.fn.bufadd(path)
|
|
|
|
if bufnr and bufnr > 0 and instance then
|
|
instance:preview(bufnr, position, end_position)
|
|
end
|
|
end
|
|
|
|
Preview.toggle = function(state)
|
|
if toggle_state then
|
|
Preview.hide()
|
|
else
|
|
Preview.show(state)
|
|
if instance and instance.active then
|
|
toggle_state = true
|
|
else
|
|
Preview.hide()
|
|
return
|
|
end
|
|
local winid = state.winid
|
|
local source_name = state.name
|
|
local preview_event = {
|
|
event = events.VIM_CURSOR_MOVED,
|
|
handler = function()
|
|
if not toggle_state or vim.api.nvim_get_current_win() == instance.winid then
|
|
return
|
|
end
|
|
if vim.api.nvim_get_current_win() == winid then
|
|
log.debug("Cursor moved in tree window, updating preview")
|
|
Preview.show(state)
|
|
else
|
|
log.debug("Neo-tree window lost focus, disposing preview")
|
|
Preview.hide()
|
|
end
|
|
end,
|
|
id = "preview-event",
|
|
}
|
|
instance:subscribe(source_name, preview_event)
|
|
end
|
|
end
|
|
|
|
Preview.focus = function()
|
|
if Preview.is_active() then
|
|
vim.fn.win_gotoid(instance.winid)
|
|
end
|
|
end
|
|
|
|
return Preview
|