feat(previewer): show unwritten buffer previewers for some pickers

Use actual buffer contents for buffer previewers for some pickers
(buffers, treesitter, current_buffer_fuzzy_find, for starters). Not
to be confused with reusing buffers for preview buffers.

This lets users view previews of buffers with unwritten changes. For
treesitter and current_buffer_fuzzy_find, not showing actual buffer
content is arguably a bug considering the finder operates over the
buffer (written or not). So there are instances where you can search
over unwritten content in the buffer only to see an outdated, written
only content in the preview.
This commit is contained in:
James Trew 2024-02-24 19:55:41 -05:00
parent bc4e7e4503
commit 2768d5e921
2 changed files with 131 additions and 47 deletions

View file

@ -708,6 +708,7 @@ function make_entry.gen_from_treesitter(opts)
node_text = node_text,
filename = get_filename(bufnr),
bufnr = opts.bufnr,
-- need to add one since the previewer substacts one
lnum = start_row + 1,
col = start_col,
@ -971,6 +972,7 @@ function make_entry.gen_from_buffer_lines(opts)
ordinal = entry.text,
display = make_display,
filename = entry.filename,
bufnr = opts.bufnr,
lnum = entry.lnum,
text = entry.text,
}, opts)

View file

@ -56,11 +56,15 @@ local function defaulter(f, default_opts)
}
end
-- modified vim.split to incorporate a timer
local function split(s, sep, plain, opts)
--- modified vim.gsplit to incorporate a timer
---@param s string
---@param sep string
---@param opts table
---@return string[]?
local function split(s, sep, opts)
opts = opts or {}
local t = {}
for c in vim.gsplit(s, sep, plain) do
for c in vim.gsplit(s, sep) do
local line = opts.file_encoding and vim.iconv(c, opts.file_encoding, "utf8") or c
table.insert(t, line)
if opts.preview.timeout then
@ -72,7 +76,10 @@ local function split(s, sep, plain, opts)
end
return t
end
local bytes_to_megabytes = math.pow(1024, 2)
local bytes_to_megabytes = function(bytes)
return bytes / math.pow(1024, 2)
end
local color_hash = {
["p"] = "TelescopePreviewPipe",
@ -157,22 +164,43 @@ local handle_directory_preview = function(filepath, bufnr, opts)
})
end
local function ft_known(ft)
return ft ~= nil and ft ~= ""
end
---@param mb_filesize number
---@param filepath string
---@param bufnr number preview bufnr
---@param opts table
---@return boolean # whether to abort preview generation
local function filesize_ok(mb_filesize, filepath, bufnr, opts)
if opts.preview.filesize_limit then
if mb_filesize > opts.preview.filesize_limit then
if type(opts.preview.filesize_hook) == "function" then
opts.preview.filesize_hook(filepath, bufnr, opts)
else
putils.set_preview_message(bufnr, opts.winid, "File exceeds preview size limit", opts.preview.msg_bg_fillchar)
end
return false
end
end
return true
end
local handle_file_preview = function(filepath, bufnr, stat, opts)
vim.schedule(function()
opts.ft = opts.use_ft_detect and putils.filetype_detect(filepath)
local possible_binary = false
if type(opts.preview.filetype_hook) == "function" and opts.ft ~= nil and opts.ft ~= "" then
if not opts.preview.filetype_hook(filepath, bufnr, opts) then
return
end
if not opts.preview.filetype_hook(filepath, bufnr, opts) then
return
end
if opts.preview.check_mime_type == true and has_file and (opts.ft == nil or opts.ft == "") then
local possible_binary = false
if opts.preview.check_mime_type == true and has_file and not ft_known(opts.ft) then
-- avoid SIGABRT in buffer previewer happening with utils.get_os_command_output
local mime_type = capture(string.format([[file --mime-type -b "%s"]], filepath))
if putils.binary_mime_type(mime_type) then
if type(opts.preview.mime_hook) == "function" then
opts.preview.mime_hook(filepath, bufnr, opts)
return
else
possible_binary = true
end
@ -182,16 +210,9 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
end
end
local mb_filesize = stat.size / bytes_to_megabytes
if opts.preview.filesize_limit then
if mb_filesize > opts.preview.filesize_limit then
if type(opts.preview.filesize_hook) == "function" then
opts.preview.filesize_hook(filepath, bufnr, opts)
else
putils.set_preview_message(bufnr, opts.winid, "File exceeds preview size limit", opts.preview.msg_bg_fillchar)
end
return
end
local mb_filesize = bytes_to_megabytes(stat.size)
if not filesize_ok(mb_filesize, filepath, bufnr, opts) then
return
end
opts.start_time = vim.loop.hrtime()
@ -199,7 +220,7 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
if not vim.api.nvim_buf_is_valid(bufnr) then
return
end
local processed_data = split(data, "[\r]?\n", nil, opts)
local processed_data = split(data, "[\r]?\n", opts)
if processed_data then
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, processed_data)
@ -208,7 +229,7 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
end
-- last resort, if ft is still empty at this point in time,
-- we need to determine the filetype using the buffer contents
if opts.ft == nil or opts.ft == "" then
if not ft_known(opts.ft) then
opts.ft = vim.filetype.match { filename = filepath, buf = bufnr }
end
-- we need to attempt to call filetype hook at this point "again"
@ -220,7 +241,7 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
end
end
-- if we still dont have a ft we need to display the binary message
if (opts.ft == nil or opts.ft == "") and possible_binary then
if not ft_known(opts.ft) and possible_binary then
putils.set_preview_message(bufnr, opts.winid, "Binary cannot be previewed", opts.preview.msg_bg_fillchar)
return
end
@ -244,22 +265,81 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
end)
end
local PREVIEW_TIMEOUT_MS = 250
local PREVIEW_FILESIZE_MB = 25
local PREVIEW_HIGHLIGHT_MB = 1
--- create preview from buffer contents
--- this allows previewing unwritten changes to buffers
---@param filepath string
---@param bufnr integer preview bufnr
---@param opts table
local handle_buffer_preview = function(filepath, bufnr, opts)
vim.schedule(function()
opts.ft = vim.bo[opts.entry_bufnr].ft
if type(opts.preview.filetype_hook) == "function" then
if not opts.preview.filetype_hook(filepath, bufnr, opts) then
return
end
end
if not vim.api.nvim_buf_is_valid(bufnr) then
return
end
local start_time = vim.loop.hrtime()
local lines = vim.api.nvim_buf_get_lines(opts.entry_bufnr, 0, -1, false)
local contents = table.concat(lines, "\n")
local mb_bufsize = bytes_to_megabytes(#contents)
if not filesize_ok(mb_bufsize, filepath, bufnr, opts) then
return
end
local end_time = vim.loop.hrtime()
if (end_time - start_time) / 1e6 > opts.preview.timeout then
if type(opts.preview.timeout_hook) == "function" then
opts.preview.timeout_hook(filepath, bufnr, opts)
else
putils.set_preview_message(bufnr, opts.winid, "Previewer timed out", opts.preview.msg_bg_fillchar)
end
return
end
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines)
if not ok then
return
end
if opts.callback then
opts.callback(bufnr)
end
if not (opts.preview.highlight_limit and mb_bufsize > opts.preview.highlight_limit) then
putils.highlighter(bufnr, opts.ft, opts)
end
end)
end
previewers.file_maker = function(filepath, bufnr, opts)
opts = vim.F.if_nil(opts, {})
opts.preview = vim.F.if_nil(opts.preview, {})
opts.preview.timeout = vim.F.if_nil(opts.preview.timeout, PREVIEW_TIMEOUT_MS)
opts.preview.filesize_limit = vim.F.if_nil(opts.preview.filesize_limit, PREVIEW_FILESIZE_MB)
opts.preview.highlight_limit = vim.F.if_nil(opts.preview.highlight_limit, PREVIEW_HIGHLIGHT_MB)
opts.preview.msg_bg_fillchar = vim.F.if_nil(opts.preview.msg_bg_fillchar, "")
opts.preview.treesitter = vim.F.if_nil(opts.preview.treesitter, true)
if opts.use_ft_detect == nil then
opts.use_ft_detect = true
end
if opts.bufname ~= filepath then
opts.preview.timeout = vim.F.if_nil(opts.preview.timeout, conf.preview.timeout)
opts.preview.filesize_limit = vim.F.if_nil(opts.preview.filesize_limit, conf.preview.filesize_limit)
opts.preview.highlight_limit = vim.F.if_nil(opts.preview.highlight_limit, conf.preview.highlight_limit)
opts.preview.msg_bg_fillchar = vim.F.if_nil(opts.preview.msg_bg_fillchar, conf.preview.msg_bg_fillchar)
opts.preview.treesitter = vim.F.if_nil(opts.preview.treesitter, conf.preview.treesitter)
opts.use_ft_detect = vim.F.if_nil(opts.use_ft_detect, true)
vim.validate {
filetype_hook = { opts.preview.filetype_hook, "f", true },
mime_hook = { opts.preview.mime_hook, "f", true },
filesize_hook = { opts.preview.filesize_hook, "f", true },
timeout_hook = { opts.preview.timeout_hook, "f", true },
}
opts.preview.filetype_hook = vim.F.if_nil(opts.preview.filetype_hook, function()
return true
end)
if opts.entry_bufnr and ft_known(vim.bo[opts.entry_bufnr]) then
handle_buffer_preview(vim.api.nvim_buf_get_name(opts.entry_bufnr), bufnr, opts)
elseif opts.bufname ~= filepath then
if not vim.in_fast_event() then
filepath = utils.path_expand(filepath)
end
@ -563,20 +643,21 @@ previewers.vimgrep = defaulter(function(opts)
define_preview = function(self, entry)
-- builtin.buffers: bypass path validation for terminal buffers that don't have appropriate path
local has_buftype = entry.bufnr
and vim.api.nvim_buf_is_valid(entry.bufnr)
and vim.api.nvim_buf_get_option(entry.bufnr, "buftype") ~= ""
or false
local p
if not has_buftype then
p = from_entry.path(entry, true, false)
if p == nil or p == "" then
local valid_buf = entry.bufnr and vim.api.nvim_buf_is_valid(entry.bufnr)
local has_buftype = valid_buf and vim.api.nvim_buf_get_option(entry.bufnr, "buftype") ~= ""
local path
if valid_buf then
path = vim.api.nvim_buf_get_name(entry.bufnr)
else
path = from_entry.path(entry, true)
if path == nil or path == "" then
return
end
end
-- Workaround for unnamed buffer when using builtin.buffer
if entry.bufnr and (p == "[No Name]" or has_buftype) then
-- Workaround for unnamed or terminal buffer when using builtin.buffer
if valid_buf and (path == "[No Name]" or has_buftype) then
local lines = vim.api.nvim_buf_get_lines(entry.bufnr, 0, -1, false)
vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines)
-- schedule so that the lines are actually there and can be jumped onto when we call jump_to_line
@ -584,7 +665,7 @@ previewers.vimgrep = defaulter(function(opts)
jump_to_line(self, self.state.bufnr, entry)
end)
else
conf.buffer_previewer_maker(p, self.state.bufnr, {
conf.buffer_previewer_maker(path, self.state.bufnr, {
bufname = self.state.bufname,
winid = self.state.winid,
preview = opts.preview,
@ -592,6 +673,7 @@ previewers.vimgrep = defaulter(function(opts)
jump_to_line(self, bufnr, entry)
end,
file_encoding = opts.file_encoding,
entry_bufnr = (valid_buf and not has_buftype) and entry.bufnr or nil,
})
end
end,