added builtin previewer
parent
233062d7e6
commit
eefed45175
@ -0,0 +1,374 @@
|
||||
local path = require "fzf-lua.path"
|
||||
local utils = require "fzf-lua.utils"
|
||||
local previewer_base = require "fzf-lua.previewer".base
|
||||
local raw_action = require("fzf.actions").raw_action
|
||||
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
local cmd = vim.cmd
|
||||
|
||||
local Previewer = {}
|
||||
|
||||
-- signgleton instance used for our keymappings
|
||||
local _self = nil
|
||||
|
||||
setmetatable(Previewer, {
|
||||
__call = function (cls, ...)
|
||||
return cls:new(...)
|
||||
end,
|
||||
})
|
||||
|
||||
function Previewer:setup_keybinds()
|
||||
if not self.win or not self.win.fzf_bufnr then return end
|
||||
local keymap_tbl = {
|
||||
toggle_full = { module = 'previewer.builtin', fnc = 'toggle_full()' },
|
||||
toggle_wrap = { module = 'previewer.builtin', fnc = 'toggle_wrap()' },
|
||||
toggle_hide = { module = 'previewer.builtin', fnc = 'toggle_hide()' },
|
||||
page_up = { module = 'previewer.builtin', fnc = 'scroll(-1)' },
|
||||
page_down = { module = 'previewer.builtin', fnc = 'scroll(1)' },
|
||||
page_reset = { module = 'previewer.builtin', fnc = 'scroll(0)' },
|
||||
}
|
||||
local function funcref_str(keymap)
|
||||
return ([[<Cmd>lua require('fzf-lua.%s').%s<CR>]]):format(keymap.module, keymap.fnc)
|
||||
end
|
||||
for action, key in pairs(self.keymap) do
|
||||
local keymap = keymap_tbl[action]
|
||||
if keymap and not vim.tbl_isempty(keymap) and key ~= false then
|
||||
api.nvim_buf_set_keymap(self.win.fzf_bufnr, 't', key,
|
||||
funcref_str(keymap), {nowait = true, noremap = true})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:new(o, opts, fzf_win)
|
||||
self = setmetatable(previewer_base(o, opts), {
|
||||
__index = vim.tbl_deep_extend("keep",
|
||||
self, previewer_base
|
||||
)})
|
||||
self.type = "builtin"
|
||||
self.win = fzf_win
|
||||
self.wrap = o.wrap
|
||||
self.title = o.title
|
||||
self.scrollbar = o.scrollbar
|
||||
if o.scrollchar then
|
||||
self.win.winopts.scrollchar = o.scrollchar
|
||||
end
|
||||
self.fullscreen = o.fullscreen
|
||||
self.syntax = o.syntax
|
||||
self.syntax_delay = o.syntax_delay
|
||||
self.hl_cursor = o.hl_cursor
|
||||
self.hl_range = o.hl_range
|
||||
self.keymap = o.keymap
|
||||
self.backups = {}
|
||||
_self = self
|
||||
return self
|
||||
end
|
||||
|
||||
function Previewer:close()
|
||||
-- restore winopts backup for those that weren't restored
|
||||
-- (usually the last previewed loaded buffer)
|
||||
for bufnr, _ in pairs(self.backups) do
|
||||
self:restore_winopts(bufnr, self.win.preview_winid)
|
||||
end
|
||||
self:clear_preview_buf()
|
||||
self.backups = {}
|
||||
_self = nil
|
||||
end
|
||||
|
||||
function Previewer:update_border(entry)
|
||||
if self.title then
|
||||
local title = (' %s '):format(entry.path)
|
||||
if entry.bufnr then
|
||||
-- local border_width = api.nvim_win_get_width(self.win.preview_winid)
|
||||
local buf_str = ('buf %d:'):format(entry.bufnr)
|
||||
title = (' %s %s '):format(buf_str, entry.path)
|
||||
end
|
||||
self.win:update_title(title)
|
||||
end
|
||||
if self.scrollbar then
|
||||
self.win:update_scrollbar()
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:gen_winopts()
|
||||
return {
|
||||
wrap = self.wrap,
|
||||
number = true,
|
||||
relativenumber = false,
|
||||
cursorline = true,
|
||||
cursorcolumn = false,
|
||||
signcolumn = 'no',
|
||||
foldenable = false,
|
||||
foldmethod = 'manual',
|
||||
}
|
||||
end
|
||||
|
||||
function Previewer:backup_winopts(key, win)
|
||||
if not key then return end
|
||||
if not win or not api.nvim_win_is_valid(win) then return end
|
||||
self.backups[key] = {}
|
||||
for opt, _ in pairs(self:gen_winopts()) do
|
||||
self.backups[key][opt] = api.nvim_win_get_option(win, opt)
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:restore_winopts(key, win)
|
||||
if not self.backups[key] then return end
|
||||
if not win or not api.nvim_win_is_valid(win) then return end
|
||||
for opt, v in pairs(self.backups[key]) do
|
||||
api.nvim_win_set_option(win, opt, v)
|
||||
end
|
||||
self.backups[key] = nil
|
||||
end
|
||||
|
||||
function Previewer:set_winopts(win)
|
||||
if not win or not api.nvim_win_is_valid(win) then return end
|
||||
for opt, v in pairs(self:gen_winopts()) do
|
||||
api.nvim_win_set_option(win, opt, v)
|
||||
--[[ api.nvim_win_call(win, function()
|
||||
api.nvim_win_set_option(0, opt, v)
|
||||
end) ]]
|
||||
end
|
||||
end
|
||||
|
||||
local function set_cursor_hl(self, entry)
|
||||
local lnum, col = tonumber(entry.line), tonumber(entry.col)
|
||||
local pattern = entry.pattern or entry.text
|
||||
|
||||
if not lnum or lnum < 1 then
|
||||
api.nvim_win_set_cursor(0, {1, 0})
|
||||
if pattern ~= '' then
|
||||
fn.search(pattern, 'c')
|
||||
end
|
||||
else
|
||||
if not pcall(api.nvim_win_set_cursor, 0, {lnum, math.max(0, col - 1)}) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
utils.zz()
|
||||
|
||||
self.orig_pos = api.nvim_win_get_cursor(0)
|
||||
|
||||
fn.clearmatches()
|
||||
|
||||
if lnum and lnum > 0 and col and col > 1 then
|
||||
fn.matchaddpos(self.hl_cursor, {{lnum, math.max(1, col)}}, 11)
|
||||
end
|
||||
|
||||
cmd(('noa call nvim_set_current_win(%d)'):format(self.win.preview_winid))
|
||||
end
|
||||
|
||||
function Previewer:do_syntax(entry)
|
||||
if not self.preview_bufnr then return end
|
||||
local bufnr = self.preview_bufnr
|
||||
local preview_winid = self.win.preview_winid
|
||||
if self.preview_bufloaded and vim.bo[bufnr].filetype == '' then
|
||||
if fn.bufwinid(bufnr) == preview_winid then
|
||||
-- do not enable for lage files, treesitter still has perf issues:
|
||||
-- https://github.com/nvim-treesitter/nvim-treesitter/issues/556
|
||||
-- https://github.com/nvim-treesitter/nvim-treesitter/issues/898
|
||||
local lcount = api.nvim_buf_line_count(bufnr)
|
||||
local bytes = api.nvim_buf_get_offset(bufnr, lcount)
|
||||
if bytes / lcount < 1000 then
|
||||
-- nvim_buf_call is less side-effects than changing window
|
||||
-- make sure that buffer in preview window must not in normal window
|
||||
local ext = path.extension(entry.path)
|
||||
api.nvim_buf_call(bufnr, function()
|
||||
if ext then vim.cmd('set ft=' .. ext) end
|
||||
vim.cmd('filetype detect')
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:set_tmp_buffer()
|
||||
if not self.win or not self.win:validate_preview() then return end
|
||||
local tmp_buf = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_option(tmp_buf, 'bufhidden', 'wipe')
|
||||
api.nvim_win_set_buf(self.win.preview_winid, tmp_buf)
|
||||
return tmp_buf
|
||||
end
|
||||
|
||||
function Previewer:clear_preview_buf()
|
||||
local retbuf = nil
|
||||
if self.win and self.win:validate_preview() then
|
||||
-- attach a temp buffer to the window
|
||||
-- so we can safely delete the buffer
|
||||
-- ('nvim_buf_delete' removes the attached win)
|
||||
retbuf = self:set_tmp_buffer()
|
||||
end
|
||||
if self.preview_bufloaded then
|
||||
local bufnr = self.preview_bufnr
|
||||
if vim.api.nvim_buf_is_valid(bufnr) then
|
||||
api.nvim_buf_call(bufnr, function()
|
||||
vim.cmd('delm \\"')
|
||||
end)
|
||||
vim.api.nvim_buf_delete(bufnr, {force=true})
|
||||
end
|
||||
end
|
||||
self.preview_bufnr = nil
|
||||
self.preview_bufloaded = nil
|
||||
return retbuf
|
||||
end
|
||||
|
||||
function Previewer:display_last_entry()
|
||||
self:display_entry(self.last_entry)
|
||||
end
|
||||
|
||||
function Previewer:preview_buf_post(entry)
|
||||
-- backup window options
|
||||
local bufnr = self.preview_bufnr
|
||||
local preview_winid = self.win.preview_winid
|
||||
self:backup_winopts(bufnr, preview_winid)
|
||||
|
||||
-- set preview win options or load the file
|
||||
-- if not already loaded from buffer
|
||||
utils.win_execute(preview_winid, function()
|
||||
set_cursor_hl(self, entry)
|
||||
end)
|
||||
|
||||
-- set preview window options
|
||||
self:set_winopts(preview_winid)
|
||||
|
||||
-- reset the preview window highlights
|
||||
self.win:reset_win_highlights(preview_winid)
|
||||
|
||||
-- local ml = vim.bo[entry.bufnr].ml
|
||||
-- vim.bo[entry.bufnr].ml = false
|
||||
|
||||
if self.syntax then
|
||||
vim.defer_fn(function()
|
||||
self:do_syntax(entry)
|
||||
-- vim.bo[entry.bufnr].ml = ml
|
||||
end, self.syntax_delay)
|
||||
end
|
||||
|
||||
self:update_border(entry)
|
||||
end
|
||||
|
||||
function Previewer:display_entry(entry)
|
||||
if not entry then return
|
||||
else
|
||||
-- save last entry even if we don't display
|
||||
self.last_entry = entry
|
||||
end
|
||||
if not self.win or not self.win:validate_preview() then return end
|
||||
local preview_winid = self.win.preview_winid
|
||||
local previous_bufnr = api.nvim_win_get_buf(preview_winid)
|
||||
assert(not self.preview_bufnr or previous_bufnr == self.preview_bufnr)
|
||||
-- restore settings for the buffer we were previously viewing
|
||||
self:restore_winopts(previous_bufnr, preview_winid)
|
||||
-- clear the current preview buffer
|
||||
local bufnr = self:clear_preview_buf()
|
||||
-- store the preview buffer
|
||||
self.preview_bufnr = bufnr
|
||||
|
||||
if entry.bufnr and api.nvim_buf_is_loaded(entry.bufnr) then
|
||||
-- must convert to number or our backup will have conflicting keys
|
||||
bufnr = tonumber(entry.bufnr)
|
||||
-- display the buffer in the preview
|
||||
api.nvim_win_set_buf(preview_winid, bufnr)
|
||||
-- store current preview buffer
|
||||
self.preview_bufnr = bufnr
|
||||
self:preview_buf_post(entry)
|
||||
else
|
||||
-- mark the buffer for unloading the next call
|
||||
self.preview_bufloaded = true
|
||||
-- read the file into the buffer
|
||||
utils.read_file_async(entry.path, vim.schedule_wrap(function(data)
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, vim.split(data, "[\r]?\n"))
|
||||
if not ok then
|
||||
return
|
||||
end
|
||||
self:preview_buf_post(entry)
|
||||
end))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function Previewer:action(_)
|
||||
local act = raw_action(function (items, _, _)
|
||||
local entry = path.entry_to_file(items[1], self.opts.cwd)
|
||||
self:display_entry(entry)
|
||||
return ""
|
||||
end)
|
||||
return act
|
||||
end
|
||||
|
||||
function Previewer:cmdline(_)
|
||||
return vim.fn.shellescape(self:action())
|
||||
-- return 'true'
|
||||
end
|
||||
|
||||
function Previewer:preview_window(_)
|
||||
return 'nohidden:right:0'
|
||||
end
|
||||
|
||||
function Previewer:override_fzf_preview_window()
|
||||
return self.win and not self.win.winopts.split
|
||||
end
|
||||
|
||||
function Previewer.scroll(direction)
|
||||
if not _self then return end
|
||||
local self = _self
|
||||
local preview_winid = self.win.preview_winid
|
||||
if preview_winid < 0 or not direction then
|
||||
return
|
||||
end
|
||||
local fzf_winid = self.win.fzf_winid
|
||||
utils.win_execute(preview_winid, function()
|
||||
if direction == 0 then
|
||||
api.nvim_win_set_cursor(preview_winid, self.orig_pos)
|
||||
else
|
||||
-- ^D = 0x04, ^U = 0x15
|
||||
fn.execute(('norm! %c'):format(direction > 0 and 0x04 or 0x15))
|
||||
end
|
||||
utils.zz()
|
||||
cmd(('noa call nvim_set_current_win(%d)'):format(fzf_winid))
|
||||
end)
|
||||
if self.scrollbar then
|
||||
self.win:update_scrollbar()
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer.toggle_wrap()
|
||||
if not _self then return end
|
||||
local self = _self
|
||||
self.wrap = not self.wrap
|
||||
if self.win and self.win:validate_preview() then
|
||||
api.nvim_win_set_option(self.win.preview_winid, 'wrap', self.wrap)
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer.toggle_full()
|
||||
if not _self then return end
|
||||
local self = _self
|
||||
self.fullscreen = not self.fullscreen
|
||||
if self.win and self.win:validate_preview() then
|
||||
self.win:redraw_preview()
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer.toggle_hide()
|
||||
if not _self then return end
|
||||
local self = _self
|
||||
if self.win then
|
||||
if self.win:validate_preview() then
|
||||
self.win:close_preview()
|
||||
else
|
||||
self.win:redraw_preview()
|
||||
self:display_last_entry()
|
||||
end
|
||||
end
|
||||
-- close_preview() calls Previewer:close()
|
||||
-- which will clear out our singleton so
|
||||
-- we must save it again to call redraw
|
||||
_self = self
|
||||
end
|
||||
|
||||
return Previewer
|
@ -0,0 +1,475 @@
|
||||
local utils = require "fzf-lua.utils"
|
||||
local config = require "fzf-lua.config"
|
||||
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
|
||||
local FzfWin = {}
|
||||
|
||||
setmetatable(FzfWin, {
|
||||
__call = function (cls, ...)
|
||||
return cls:new(...)
|
||||
end,
|
||||
})
|
||||
|
||||
local generate_layout = function(winopts)
|
||||
local row, col = winopts.row, winopts.col
|
||||
local height, width = winopts.height, winopts.width
|
||||
local preview_pos = winopts.preview_pos
|
||||
local preview_size = winopts.preview_size
|
||||
local prev_row, prev_col = row, col
|
||||
local prev_height, prev_width
|
||||
local padding = 2
|
||||
local anchor
|
||||
local vert_split = winopts.split and winopts.split:match("vnew") ~= nil
|
||||
if preview_pos == 'down' or preview_pos == 'up' then
|
||||
height = height - padding
|
||||
prev_width = width
|
||||
prev_height = utils.round(height * preview_size/100, 0.6)
|
||||
height = height - prev_height
|
||||
if preview_pos == 'up' then
|
||||
row = row + prev_height + padding
|
||||
if winopts.split then
|
||||
anchor = 'NW'
|
||||
prev_row = 1
|
||||
prev_height = prev_height - 1
|
||||
if vert_split then
|
||||
prev_col = 1
|
||||
end
|
||||
else
|
||||
anchor = 'SW'
|
||||
prev_row = row - padding
|
||||
end
|
||||
else
|
||||
anchor = 'NW'
|
||||
if winopts.split then
|
||||
prev_col = 1
|
||||
prev_row = height + padding
|
||||
prev_height = prev_height - 1
|
||||
else
|
||||
prev_row = row + height + padding
|
||||
end
|
||||
end
|
||||
elseif preview_pos == 'left' or preview_pos == 'right' then
|
||||
prev_height = height
|
||||
prev_width = utils.round(width * preview_size/100)
|
||||
width = width - prev_width
|
||||
if preview_pos == 'left' then
|
||||
anchor = 'NE'
|
||||
col = col + prev_width
|
||||
prev_col = col - padding
|
||||
if winopts.split then
|
||||
prev_row = 1
|
||||
prev_height = prev_height - padding
|
||||
if vert_split then
|
||||
anchor = 'NW'
|
||||
prev_col = 1
|
||||
prev_width = prev_width + 1
|
||||
else
|
||||
prev_width = prev_width - 1
|
||||
end
|
||||
end
|
||||
else
|
||||
anchor = 'NW'
|
||||
if winopts.split then
|
||||
prev_row = 1
|
||||
prev_height = prev_height - padding
|
||||
if vert_split then
|
||||
prev_col = width + 2
|
||||
prev_width = prev_width - 1
|
||||
else
|
||||
prev_col = width + 3
|
||||
prev_width = prev_width - padding
|
||||
end
|
||||
else
|
||||
prev_col = col + width + padding
|
||||
end
|
||||
end
|
||||
end
|
||||
return {
|
||||
fzf = {
|
||||
row = row, col = col,
|
||||
height = height, width = width,
|
||||
},
|
||||
preview = {
|
||||
anchor = anchor,
|
||||
row = prev_row, col = prev_col,
|
||||
height = prev_height, width = prev_width,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local normalize_winopts = function(opts)
|
||||
if not opts then opts = {} end
|
||||
if not opts.winopts then opts.winopts = {} end
|
||||
opts = vim.tbl_deep_extend("keep", opts, config.globals)
|
||||
opts.winopts = vim.tbl_deep_extend("keep", opts.winopts, config.globals.winopts)
|
||||
opts.winopts_raw = opts.winopts_raw or config.globals.winopts_raw
|
||||
|
||||
local raw = {}
|
||||
if opts.winopts_raw and type(opts.winopts_raw) == "function" then
|
||||
raw = opts.winopts_raw()
|
||||
end
|
||||
|
||||
local winopts = opts.winopts
|
||||
local height = raw.height or math.floor(vim.o.lines * winopts.win_height)
|
||||
local width = raw.width or math.floor(vim.o.columns * winopts.win_width)
|
||||
local row = raw.row or math.floor((vim.o.lines - height) * winopts.win_row)
|
||||
local col = raw.col or math.floor((vim.o.columns - width) * winopts.win_col)
|
||||
local border = raw.border or winopts.win_border
|
||||
local scrollchar = raw.scrollchar or winopts.scrollchar
|
||||
local hl_normal = raw.hl_normal or winopts.hl_normal
|
||||
local hl_border = raw.hl_border or winopts.hl_border
|
||||
|
||||
-- normalize border option for nvim_open_win()
|
||||
if border == false then
|
||||
border = "none"
|
||||
elseif border == true or border == nil then
|
||||
border = config.globals.winopts.borderchars
|
||||
end
|
||||
|
||||
-- did user supply the scroll char at 9th slot?
|
||||
if type(border) == 'table' and #border == 9 then
|
||||
if not scrollchar then scrollchar = border[9] end
|
||||
border[9] = nil
|
||||
end
|
||||
|
||||
-- parse preview options
|
||||
local preview = opts.preview_horizontal
|
||||
if opts.preview_layout == "vertical" then
|
||||
preview = opts.preview_vertical
|
||||
elseif opts.preview_layout == "flex" then
|
||||
preview = utils._if(vim.o.columns>opts.flip_columns, opts.preview_horizontal, opts.preview_vertical)
|
||||
end
|
||||
|
||||
-- builtin previewer params
|
||||
local prev_pos = preview:match("[^:]+") or 'right'
|
||||
local prev_size = tonumber(preview:match(":(%d+)%%")) or 50
|
||||
|
||||
return {
|
||||
height = height, width = width, row = row, col = col, border = border,
|
||||
window_on_create = raw.window_on_create or winopts.window_on_create,
|
||||
split = raw.split or winopts.split,
|
||||
hl_normal = hl_normal, hl_border = hl_border,
|
||||
-- builtin previewer params
|
||||
scrollchar = scrollchar or '█',
|
||||
preview_pos = prev_pos, preview_size = prev_size,
|
||||
}
|
||||
end
|
||||
|
||||
function FzfWin:reset_win_highlights(win, is_border)
|
||||
local hl = ("Normal:%s,FloatBorder:%s"):format(
|
||||
self.winopts.hl_normal, self.winopts.hl_border)
|
||||
if is_border then
|
||||
-- our border is manuually drawn so we need
|
||||
-- to replace Normal with the border color
|
||||
hl = ("Normal:%s"):format(self.winopts.hl_border)
|
||||
end
|
||||
vim.api.nvim_win_set_option(win, 'winhighlight', hl)
|
||||
--[[ if self.winopts.window_on_create then
|
||||
utils.win_execute(win, function()
|
||||
self.winopts.window_on_create()
|
||||
end)
|
||||
end ]]
|
||||
end
|
||||
|
||||
function FzfWin:new(o)
|
||||
o = o or {}
|
||||
self = setmetatable({}, { __index = self })
|
||||
self.winopts = normalize_winopts(o)
|
||||
self.previewer = o.previewer
|
||||
self.previewer_type = o.previewer_type
|
||||
if self.previewer_type == 'builtin' or self.previewer == 'builtin' then
|
||||
self.previewer_is_builtin = true
|
||||
end
|
||||
if not self.winopts.split and self.previewer_is_builtin then
|
||||
self.layout = generate_layout(self.winopts)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function FzfWin:attach_previewer(previewer)
|
||||
self._previewer = previewer
|
||||
end
|
||||
|
||||
function FzfWin:fs_preview_layout(fs)
|
||||
local prev_winopts = self.prev_winopts
|
||||
local border_winopts = self.border_winopts
|
||||
if not fs or self.winopts.split
|
||||
or not prev_winopts or not border_winopts
|
||||
or vim.tbl_isempty(prev_winopts)
|
||||
or vim.tbl_isempty(border_winopts) then
|
||||
return prev_winopts, border_winopts
|
||||
end
|
||||
|
||||
local preview_pos = self.winopts.preview_pos
|
||||
local height_diff = 0
|
||||
local width_diff = 0
|
||||
if preview_pos == 'down' or preview_pos == 'up'then
|
||||
width_diff = vim.o.columns - border_winopts.width
|
||||
if preview_pos == 'down' then
|
||||
height_diff = vim.o.lines - border_winopts.row - border_winopts.height - 3
|
||||
elseif preview_pos == 'up' then
|
||||
height_diff = border_winopts.row - border_winopts.height + 1
|
||||
end
|
||||
prev_winopts.col = prev_winopts.col - width_diff/2
|
||||
border_winopts.col = border_winopts.col - width_diff/2
|
||||
elseif preview_pos == 'left' or preview_pos == 'right'then
|
||||
height_diff = vim.o.lines - border_winopts.height - 2
|
||||
if preview_pos == 'left' then
|
||||
width_diff = border_winopts.col - border_winopts.width + 1
|
||||
elseif preview_pos == 'right' then
|
||||
width_diff = vim.o.columns - border_winopts.col - border_winopts.width - 1
|
||||
end
|
||||
prev_winopts.row = prev_winopts.row - height_diff/2 - 1
|
||||
border_winopts.row = border_winopts.row - height_diff/2 - 1
|
||||
end
|
||||
|
||||
prev_winopts.height = prev_winopts.height + height_diff
|
||||
border_winopts.height = border_winopts.height + height_diff
|
||||
prev_winopts.width = prev_winopts.width + width_diff
|
||||
border_winopts.width = border_winopts.width + width_diff
|
||||
|
||||
return prev_winopts, border_winopts
|
||||
end
|
||||
|
||||
function FzfWin:preview_layout()
|
||||
if self.winopts.split and self.previewer_is_builtin then
|
||||
local wininfo = fn.getwininfo(self.fzf_winid)[1]
|
||||
self.layout = generate_layout({
|
||||
row = wininfo.winrow,
|
||||
col = wininfo.wincol,
|
||||
height = wininfo.height,
|
||||
width = api.nvim_win_get_width(self.fzf_winid),
|
||||
preview_pos = self.winopts.preview_pos,
|
||||
preview_size = self.winopts.preview_size,
|
||||
split = self.winopts.split,
|
||||
})
|
||||
end
|
||||
if not self.layout then return {}, {} end
|
||||
|
||||
local anchor = self.layout.preview.anchor
|
||||
local row, col = self.layout.preview.row, self.layout.preview.col
|
||||
local width, height = self.layout.preview.width, self.layout.preview.height
|
||||
if not anchor or not width or width < 1 or not height or height < 1 then
|
||||
return {}, {}
|
||||
end
|
||||
|
||||
local winopts = {relative = 'win', win = self.fzf_winid, focusable = false, style = 'minimal'}
|
||||
if self.winopts.split then
|
||||
width = width - 2
|
||||
end
|
||||
local preview_opts = vim.tbl_extend('force', winopts, {
|
||||
anchor = anchor,
|
||||
width = width,
|
||||
height = height,
|
||||
col = col,
|
||||
row = row
|
||||
})
|
||||
local border_winopts = vim.tbl_extend('force', winopts, {
|
||||
anchor = anchor,
|
||||
width = width + 2,
|
||||
height = height + 2,
|
||||
col = anchor:match('W') and col - 1 or col + 1,
|
||||
row = anchor:match('N') and row - 1 or row + 1
|
||||
})
|
||||
return preview_opts, border_winopts
|
||||
end
|
||||
|
||||
function FzfWin:validate_preview()
|
||||
return self.preview_winid and self.preview_winid > 0
|
||||
and api.nvim_win_is_valid(self.preview_winid)
|
||||
and self.border_winid and self.border_winid > 0
|
||||
and api.nvim_win_is_valid(self.border_winid)
|
||||
end
|
||||
|
||||
function FzfWin:preview_winids()
|
||||
return self.preview_winid, self.border_winid
|
||||
end
|
||||
|
||||
|
||||
function FzfWin:update_border_buf()
|
||||
local border_buf = self.border_buf
|
||||
local border_winopts = self.border_winopts
|
||||
local border_chars = self.winopts.border
|
||||
local width, height = border_winopts.width, border_winopts.height
|
||||
local top = border_chars[1] .. border_chars[2]:rep(width - 2) .. border_chars[3]
|
||||
local mid = border_chars[8] .. (' '):rep(width - 2) .. border_chars[4]
|
||||
local bot = border_chars[7] .. border_chars[6]:rep(width - 2) .. border_chars[5]
|
||||
local lines = {top}
|
||||
for _ = 1, height - 2 do
|
||||
table.insert(lines, mid)
|
||||
end
|
||||
table.insert(lines, bot)
|
||||
if not border_buf then
|
||||
border_buf = api.nvim_create_buf(false, true)
|
||||
-- run nvim with `-M` will reset modifiable's default value to false
|
||||
vim.bo[border_buf].modifiable = true
|
||||
vim.bo[border_buf].bufhidden = 'wipe'
|
||||
end
|
||||
api.nvim_buf_set_lines(border_buf, 0, -1, 1, lines)
|
||||
return border_buf
|
||||
end
|
||||
|
||||
function FzfWin:redraw_preview()
|
||||
self.prev_winopts, self.border_winopts = self:preview_layout()
|
||||
if vim.tbl_isempty(self.prev_winopts) or vim.tbl_isempty(self.border_winopts) then
|
||||
return -1, -1
|
||||
end
|
||||
|
||||
-- fullscreen preview only if set by the previewer
|
||||
if self._previewer and self._previewer.fullscreen then
|
||||
self.prev_winopts, self.border_winopts =
|
||||
self:fs_preview_layout(self._previewer.fullscreen)
|
||||
end
|
||||
|
||||
if self:validate_preview() then
|
||||
self.border_buf = api.nvim_win_get_buf(self.border_winid)
|
||||
self:update_border_buf()
|
||||
api.nvim_win_set_config(self.border_winid, self.border_winopts)
|
||||
api.nvim_win_set_config(self.preview_winid, self.prev_winopts)
|
||||
if self._previewer and self._previewer.set_winopts then
|
||||
self._previewer:set_winopts(self.preview_winid)
|
||||
self._previewer:display_last_entry()
|
||||
end
|
||||
else
|
||||
local tmp_buf = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_option(tmp_buf, 'bufhidden', 'wipe')
|
||||
self.border_buf = self:update_border_buf()
|
||||
self.preview_winid = api.nvim_open_win(tmp_buf, false, self.prev_winopts)
|
||||
self.border_winid = api.nvim_open_win(self.border_buf, false, self.border_winopts)
|
||||
end
|
||||
self:reset_win_highlights(self.border_winid, true)
|
||||
self:reset_win_highlights(self.preview_winid)
|
||||
return self.preview_winid, self.border_winid
|
||||
end
|
||||
|
||||
function FzfWin:create()
|
||||
-- save sending bufnr/winid
|
||||
self.src_bufnr = vim.api.nvim_get_current_buf()
|
||||
self.src_winid = vim.api.nvim_get_current_win()
|
||||
if self.winopts.split then
|
||||
vim.cmd(self.winopts.split)
|
||||
self.fzf_bufnr = vim.api.nvim_get_current_buf()
|
||||
self.fzf_winid = vim.api.nvim_get_current_win()
|
||||
else
|
||||
local relative = self.winopts.relative or 'editor'
|
||||
local columns, lines = vim.o.columns, vim.o.lines
|
||||
if relative == 'win' then
|
||||
columns, lines = vim.api.nvim_win_get_width(0), vim.api.nvim_win_get_height(0)
|
||||
end
|
||||
|
||||
local winopts = self.winopts
|
||||
if self.layout then winopts = self.layout.fzf end
|
||||
local win_opts = {
|
||||
width = winopts.width or math.min(columns - 4, math.max(80, columns - 20)),
|
||||
height = winopts.height or math.min(lines - 4, math.max(20, lines - 10)),
|
||||
style = 'minimal',
|
||||
relative = relative,
|
||||
border = self.winopts.border
|
||||
}
|
||||
win_opts.row = winopts.row or math.floor(((lines - win_opts.height) / 2) - 1)
|
||||
win_opts.col = winopts.col or math.floor((columns - win_opts.width) / 2)
|
||||
|
||||
self.fzf_bufnr = vim.api.nvim_create_buf(false, true)
|
||||
self.fzf_winid = vim.api.nvim_open_win(self.fzf_bufnr, true, win_opts)
|
||||
end
|
||||
|
||||
self:reset_win_highlights(self.fzf_winid)
|
||||
|
||||
if self.winopts.window_on_create then
|
||||
self.winopts.window_on_create()
|
||||
end
|
||||
|
||||
-- create or redraw the preview win
|
||||
self:redraw_preview()
|
||||
|
||||
-- setup the keybinds for the builtin previewer
|
||||
if self._previewer and self._previewer.setup_keybinds then
|
||||
self._previewer:setup_keybinds()
|
||||
end
|
||||
|
||||
return {
|
||||
src_bufnr = self.src_bufnr,
|
||||
src_winid = self.src_winid,
|
||||
fzf_bufnr = self.fzf_bufnr,
|
||||
fzf_winid = self.fzf_winid,
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
function FzfWin:close_preview()
|
||||
if self._previewer and self._previewer.close then
|
||||
self._previewer:close()
|
||||
end
|
||||
if not self:validate_preview() then return end
|
||||
if vim.api.nvim_win_is_valid(self.border_winid) then
|
||||
api.nvim_win_close(self.border_winid, true)
|
||||
end
|
||||
if vim.api.nvim_buf_is_valid(self.border_buf) then
|
||||
vim.api.nvim_buf_delete(self.border_buf, {force=true})
|
||||
end
|
||||
if vim.api.nvim_win_is_valid(self.preview_winid) then
|
||||
api.nvim_win_close(self.preview_winid, true)
|
||||
end
|
||||
self.border_buf = nil
|
||||
self.border_winid = nil
|
||||
self.preview_winid = nil
|
||||
end
|
||||
|
||||
function FzfWin:close()
|
||||
self:close_preview()
|
||||
if vim.api.nvim_win_is_valid(self.fzf_winid) then
|
||||
vim.api.nvim_win_close(self.fzf_winid, {force=true})
|
||||
end
|
||||
if vim.api.nvim_buf_is_valid(self.fzf_bufnr) then
|
||||
vim.api.nvim_buf_delete(self.fzf_bufnr, {force=true})
|
||||
end
|
||||
if vim.api.nvim_win_is_valid(self.src_winid) then
|
||||
vim.api.nvim_set_current_win(self.src_winid)
|
||||
end
|
||||
end
|
||||
|
||||
function FzfWin:update_scrollbar()
|
||||
local border_winid = self.border_winid
|
||||
local preview_winid = self.preview_winid
|
||||
local border_chars = self.winopts.border
|
||||
local buf = api.nvim_win_get_buf(preview_winid)
|
||||
local border_buf = api.nvim_win_get_buf(border_winid)
|
||||
local line_count = api.nvim_buf_line_count(buf)
|
||||
|
||||
local win_info = fn.getwininfo(preview_winid)[1]
|
||||
local topline, height = win_info.topline, win_info.height
|
||||
|
||||
local bar_size = math.min(height, math.ceil(height * height / line_count))
|
||||
|
||||
local bar_pos = math.ceil(height * topline / line_count)
|
||||
if bar_pos + bar_size > height then
|
||||
bar_pos = height - bar_size + 1
|
||||
end
|
||||
|
||||
local lines = api.nvim_buf_get_lines(border_buf, 1, -2, true)
|
||||
for i = 1, #lines do
|
||||
local bar_char
|
||||
if i >= bar_pos and i < bar_pos + bar_size then
|
||||
bar_char = self.winopts.scrollchar
|
||||
else
|
||||
bar_char = border_chars[4]
|
||||
end
|
||||
local line = lines[i]
|
||||
lines[i] = fn.strcharpart(line, 0, fn.strwidth(line) - 1) .. bar_char
|
||||
end
|
||||
api.nvim_buf_set_lines(border_buf, 1, -2, 0, lines)
|
||||
end
|
||||
|
||||
function FzfWin:update_title(title)
|
||||
self:update_border_buf()
|
||||
local border_buf = api.nvim_win_get_buf(self.border_winid)
|
||||
local top = api.nvim_buf_get_lines(border_buf, 0, 1, 0)[1]
|
||||
local prefix = fn.strcharpart(top, 0, 3)
|
||||
local suffix = fn.strcharpart(top, fn.strwidth(title) + 3, fn.strwidth(top))
|
||||
title = ('%s%s%s'):format(prefix, title, suffix)
|
||||
api.nvim_buf_set_lines(border_buf, 0, 1, 1, {title})
|
||||
end
|
||||
|
||||
return FzfWin
|
Loading…
Reference in New Issue