feat: skip/timeout preview if file cannot be easily previewed (#1231)
* For full configuration, see `:h telescope.defaults.preview` * Unblocks previewer on binaries, too large files, and files that take too long to read * Allows toggling treesitter highlighting for buffer_previewer * Allows to globally opt out of previewer
This commit is contained in:
@@ -279,6 +279,48 @@ telescope.setup({opts}) *telescope.setup()*
|
|||||||
Default: 1000
|
Default: 1000
|
||||||
|
|
||||||
|
|
||||||
|
*telescope.defaults.preview*
|
||||||
|
preview: ~
|
||||||
|
This field handles the global configuration for previewers.
|
||||||
|
By default it is a table, with default values (more below).
|
||||||
|
To disable previewing, set it to false. If you have disabled previewers
|
||||||
|
globally, but want to opt in to previewing for single pickers, you will have to
|
||||||
|
pass `preview = true` or `preview = {...}` (your config) to the `opts` of
|
||||||
|
your picker.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- check_mime_type: Use `file` if available to try to infer whether the
|
||||||
|
file to preview is a binary if plenary's
|
||||||
|
filetype detection fails.
|
||||||
|
Windows users get `file` from:
|
||||||
|
https://github.com/julian-r/file-windows
|
||||||
|
Set to false to attempt to preview any mime type.
|
||||||
|
Default: true
|
||||||
|
- filesize_limit: The maximum file size in MB attempted to be previewed.
|
||||||
|
Set to false to attempt to preview any file size.
|
||||||
|
Default: 25
|
||||||
|
- timeout: Timeout the previewer if the preview did not
|
||||||
|
complete within `timeout` milliseconds.
|
||||||
|
Set to false to not timeout preview.
|
||||||
|
Default: 250
|
||||||
|
- hook(s): Function(s) that takes `(filepath, bufnr, opts)`
|
||||||
|
to be run if the buffer previewer was not shown due to
|
||||||
|
the respective test.
|
||||||
|
Available hooks are: {mime, filesize, timeout}_hook, e.g.
|
||||||
|
preview = {
|
||||||
|
mime_hook = function(filepath, bufnr, opts) ... end
|
||||||
|
}
|
||||||
|
See `telescope/previewers/*.lua` for relevant examples.
|
||||||
|
Default: nil
|
||||||
|
- treesitter: Determines whether the previewer performs treesitter
|
||||||
|
highlighting, which falls back to regex-based highlighting.
|
||||||
|
`true`: treesitter highlighting for all available filetypes
|
||||||
|
`false`: regex-based highlighting for all filetypes
|
||||||
|
`table`: table of filetypes for which to attach treesitter
|
||||||
|
highlighting
|
||||||
|
Default: true
|
||||||
|
|
||||||
|
|
||||||
*telescope.defaults.vimgrep_arguments*
|
*telescope.defaults.vimgrep_arguments*
|
||||||
vimgrep_arguments: ~
|
vimgrep_arguments: ~
|
||||||
Defines the command that will be used for `live_grep` and `grep_string`
|
Defines the command that will be used for `live_grep` and `grep_string`
|
||||||
|
|||||||
@@ -382,6 +382,56 @@ append(
|
|||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
append(
|
||||||
|
"preview",
|
||||||
|
{
|
||||||
|
check_mime_type = true,
|
||||||
|
filesize_limit = 25,
|
||||||
|
timeout = 250,
|
||||||
|
treesitter = true,
|
||||||
|
},
|
||||||
|
[[
|
||||||
|
This field handles the global configuration for previewers.
|
||||||
|
By default it is a table, with default values (more below).
|
||||||
|
To disable previewing, set it to false. If you have disabled previewers
|
||||||
|
globally, but want to opt in to previewing for single pickers, you will have to
|
||||||
|
pass `preview = true` or `preview = {...}` (your config) to the `opts` of
|
||||||
|
your picker.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- check_mime_type: Use `file` if available to try to infer whether the
|
||||||
|
file to preview is a binary if plenary's
|
||||||
|
filetype detection fails.
|
||||||
|
Windows users get `file` from:
|
||||||
|
https://github.com/julian-r/file-windows
|
||||||
|
Set to false to attempt to preview any mime type.
|
||||||
|
Default: true
|
||||||
|
- filesize_limit: The maximum file size in MB attempted to be previewed.
|
||||||
|
Set to false to attempt to preview any file size.
|
||||||
|
Default: 25
|
||||||
|
- timeout: Timeout the previewer if the preview did not
|
||||||
|
complete within `timeout` milliseconds.
|
||||||
|
Set to false to not timeout preview.
|
||||||
|
Default: 250
|
||||||
|
- hook(s): Function(s) that takes `(filepath, bufnr, opts)`
|
||||||
|
to be run if the buffer previewer was not shown due to
|
||||||
|
the respective test.
|
||||||
|
Available hooks are: {mime, filesize, timeout}_hook, e.g.
|
||||||
|
preview = {
|
||||||
|
mime_hook = function(filepath, bufnr, opts) ... end
|
||||||
|
}
|
||||||
|
See `telescope/previewers/*.lua` for relevant examples.
|
||||||
|
Default: nil
|
||||||
|
- treesitter: Determines whether the previewer performs treesitter
|
||||||
|
highlighting, which falls back to regex-based highlighting.
|
||||||
|
`true`: treesitter highlighting for all available filetypes
|
||||||
|
`false`: regex-based highlighting for all filetypes
|
||||||
|
`table`: table of filetypes for which to attach treesitter
|
||||||
|
highlighting
|
||||||
|
Default: true
|
||||||
|
]]
|
||||||
|
)
|
||||||
|
|
||||||
append(
|
append(
|
||||||
"vimgrep_arguments",
|
"vimgrep_arguments",
|
||||||
{ "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" },
|
{ "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local from_entry = require "telescope.from_entry"
|
local from_entry = require "telescope.from_entry"
|
||||||
local Path = require "plenary.path"
|
local Path = require "plenary.path"
|
||||||
local utils = require "telescope.utils"
|
local utils = require "telescope.utils"
|
||||||
|
local strings = require "plenary.strings"
|
||||||
local putils = require "telescope.previewers.utils"
|
local putils = require "telescope.previewers.utils"
|
||||||
local Previewer = require "telescope.previewers.previewer"
|
local Previewer = require "telescope.previewers.previewer"
|
||||||
local conf = require("telescope.config").values
|
local conf = require("telescope.config").values
|
||||||
@@ -9,12 +10,99 @@ local pfiletype = require "plenary.filetype"
|
|||||||
local pscan = require "plenary.scandir"
|
local pscan = require "plenary.scandir"
|
||||||
|
|
||||||
local buf_delete = utils.buf_delete
|
local buf_delete = utils.buf_delete
|
||||||
local defaulter = utils.make_default_callable
|
|
||||||
|
|
||||||
local previewers = {}
|
local previewers = {}
|
||||||
|
|
||||||
local ns_previewer = vim.api.nvim_create_namespace "telescope.previewers"
|
local ns_previewer = vim.api.nvim_create_namespace "telescope.previewers"
|
||||||
|
|
||||||
|
local has_file = 1 == vim.fn.executable "file"
|
||||||
|
|
||||||
|
-- TODO(fdschmidt93) switch to Job once file_maker callbacks get cleaned up with plenary async
|
||||||
|
-- avoids SIGABRT from utils.get_os_command_output due to vim.time in fs_stat cb
|
||||||
|
local function capture(cmd, raw)
|
||||||
|
local f = assert(io.popen(cmd, "r"))
|
||||||
|
local s = assert(f:read "*a")
|
||||||
|
f:close()
|
||||||
|
if raw then
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
s = string.gsub(s, "^%s+", "")
|
||||||
|
s = string.gsub(s, "%s+$", "")
|
||||||
|
s = string.gsub(s, "[\n\r]+", " ")
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
local function defaulter(f, default_opts)
|
||||||
|
default_opts = default_opts or {}
|
||||||
|
return {
|
||||||
|
new = function(opts)
|
||||||
|
if conf.preview == false and not opts.preview then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
opts.preview = type(opts.preview) ~= "table" and {} or opts.preview
|
||||||
|
if type(conf.preview) == "table" then
|
||||||
|
for k, v in pairs(conf.preview) do
|
||||||
|
opts.preview[k] = vim.F.if_nil(opts.preview[k], v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return f(opts)
|
||||||
|
end,
|
||||||
|
__call = function()
|
||||||
|
local ok, err = pcall(f(default_opts))
|
||||||
|
if not ok then
|
||||||
|
error(debug.traceback(err))
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_timeout_message(bufnr, winid, message)
|
||||||
|
local height = vim.api.nvim_win_get_height(winid)
|
||||||
|
local width = vim.api.nvim_win_get_width(winid)
|
||||||
|
vim.api.nvim_buf_set_lines(
|
||||||
|
bufnr,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false,
|
||||||
|
utils.repeated_table(height, table.concat(utils.repeated_table(width, "╱"), ""))
|
||||||
|
)
|
||||||
|
local anon_ns = vim.api.nvim_create_namespace ""
|
||||||
|
local padding = table.concat(utils.repeated_table(#message + 4, " "), "")
|
||||||
|
local lines = {
|
||||||
|
padding,
|
||||||
|
" " .. message .. " ",
|
||||||
|
padding,
|
||||||
|
}
|
||||||
|
|
||||||
|
local col = math.floor((width - strings.strdisplaywidth(lines[2])) / 2)
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
vim.api.nvim_buf_set_extmark(
|
||||||
|
bufnr,
|
||||||
|
anon_ns,
|
||||||
|
math.floor(height / 2) - 1 + i,
|
||||||
|
0,
|
||||||
|
{ virt_text = { { line, "Normal" } }, virt_text_pos = "overlay", virt_text_win_col = col }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- modified vim.split to incorporate a timer
|
||||||
|
local function split(s, sep, plain, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local t = {}
|
||||||
|
for c in vim.gsplit(s, sep, plain) do
|
||||||
|
table.insert(t, c)
|
||||||
|
if opts.preview.timeout then
|
||||||
|
local diff_time = (vim.loop.hrtime() - opts.start_time) / 1e6
|
||||||
|
if diff_time > opts.preview.timeout then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
local bytes_to_megabytes = math.pow(1024, 2)
|
||||||
|
|
||||||
local color_hash = {
|
local color_hash = {
|
||||||
["p"] = "TelescopePreviewPipe",
|
["p"] = "TelescopePreviewPipe",
|
||||||
["c"] = "TelescopePreviewCharDev",
|
["c"] = "TelescopePreviewCharDev",
|
||||||
@@ -96,11 +184,13 @@ end
|
|||||||
|
|
||||||
previewers.file_maker = function(filepath, bufnr, opts)
|
previewers.file_maker = function(filepath, bufnr, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
opts.preview = opts.preview or {}
|
||||||
|
opts.preview.timeout = vim.F.if_nil(opts.preview.timeout, 250) -- in ms
|
||||||
|
opts.preview.filesize_limit = vim.F.if_nil(opts.preview.filesize_limit, 25) -- in mb
|
||||||
if opts.use_ft_detect == nil then
|
if opts.use_ft_detect == nil then
|
||||||
opts.use_ft_detect = true
|
opts.use_ft_detect = true
|
||||||
end
|
end
|
||||||
local ft = opts.use_ft_detect and pfiletype.detect(filepath)
|
local ft = opts.use_ft_detect and pfiletype.detect(filepath)
|
||||||
|
|
||||||
if opts.bufname ~= filepath then
|
if opts.bufname ~= filepath then
|
||||||
if not vim.in_fast_event() then
|
if not vim.in_fast_event() then
|
||||||
filepath = vim.fn.expand(filepath)
|
filepath = vim.fn.expand(filepath)
|
||||||
@@ -122,11 +212,45 @@ previewers.file_maker = function(filepath, bufnr, opts)
|
|||||||
end),
|
end),
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
|
if opts.preview.check_mime_type == true and has_file and ft == "" then
|
||||||
|
-- avoid SIGABRT in buffer previewer happening with utils.get_os_command_output
|
||||||
|
local output = capture(string.format([[file --mime-type -b "%s"]], filepath))
|
||||||
|
local mime_type = vim.split(output, "/")[1]
|
||||||
|
if mime_type ~= "text" and mime_type ~= "inode" then
|
||||||
|
if type(opts.preview.mime_hook) == "function" then
|
||||||
|
opts.preview.mime_hook(filepath, bufnr, opts)
|
||||||
|
else
|
||||||
|
vim.schedule(function()
|
||||||
|
set_timeout_message(bufnr, opts.winid, "Binary cannot be previewed")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.preview.filesize_limit then
|
||||||
|
local mb_filesize = math.floor(stat.size / bytes_to_megabytes)
|
||||||
|
if mb_filesize > opts.preview.filesize_limit then
|
||||||
|
if type(opts.preview.filesize_hook) == "function" then
|
||||||
|
opts.preview.filesize_hook(filepath, bufnr, opts)
|
||||||
|
else
|
||||||
|
vim.schedule(function()
|
||||||
|
set_timeout_message(bufnr, opts.winid, "File exceeds preview size limit")
|
||||||
|
end)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.start_time = vim.loop.hrtime()
|
||||||
Path:new(filepath):_read_async(vim.schedule_wrap(function(data)
|
Path:new(filepath):_read_async(vim.schedule_wrap(function(data)
|
||||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, vim.split(data, "[\r]?\n"))
|
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)
|
||||||
if not ok then
|
if not ok then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -134,7 +258,15 @@ previewers.file_maker = function(filepath, bufnr, opts)
|
|||||||
if opts.callback then
|
if opts.callback then
|
||||||
opts.callback(bufnr)
|
opts.callback(bufnr)
|
||||||
end
|
end
|
||||||
putils.highlighter(bufnr, ft)
|
putils.highlighter(bufnr, ft, opts)
|
||||||
|
else
|
||||||
|
if type(opts.preview.timeout_hook) == "function" then
|
||||||
|
opts.preview.timeout_hook(filepath, bufnr, opts)
|
||||||
|
else
|
||||||
|
set_timeout_message(bufnr, opts.winid, "Previewer timed out")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
end))
|
end))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -323,6 +455,8 @@ previewers.cat = defaulter(function(opts)
|
|||||||
end
|
end
|
||||||
conf.buffer_previewer_maker(p, self.state.bufnr, {
|
conf.buffer_previewer_maker(p, self.state.bufnr, {
|
||||||
bufname = self.state.bufname,
|
bufname = self.state.bufname,
|
||||||
|
winid = self.state.winid,
|
||||||
|
preview = opts.preview,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -376,6 +510,8 @@ previewers.vimgrep = defaulter(function(opts)
|
|||||||
|
|
||||||
conf.buffer_previewer_maker(p, self.state.bufnr, {
|
conf.buffer_previewer_maker(p, self.state.bufnr, {
|
||||||
bufname = self.state.bufname,
|
bufname = self.state.bufname,
|
||||||
|
winid = self.state.winid,
|
||||||
|
preview = opts.preview,
|
||||||
callback = function(bufnr)
|
callback = function(bufnr)
|
||||||
jump_to_line(self, bufnr, entry.lnum)
|
jump_to_line(self, bufnr, entry.lnum)
|
||||||
end,
|
end,
|
||||||
@@ -432,6 +568,7 @@ previewers.ctags = defaulter(function(_)
|
|||||||
define_preview = function(self, entry, status)
|
define_preview = function(self, entry, status)
|
||||||
conf.buffer_previewer_maker(entry.filename, self.state.bufnr, {
|
conf.buffer_previewer_maker(entry.filename, self.state.bufnr, {
|
||||||
bufname = self.state.bufname,
|
bufname = self.state.bufname,
|
||||||
|
winid = self.state.winid,
|
||||||
callback = function(bufnr)
|
callback = function(bufnr)
|
||||||
vim.api.nvim_buf_call(bufnr, function()
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
determine_jump(entry)(self, bufnr)
|
determine_jump(entry)(self, bufnr)
|
||||||
@@ -462,6 +599,7 @@ previewers.builtin = defaulter(function(_)
|
|||||||
|
|
||||||
conf.buffer_previewer_maker(entry.filename, self.state.bufnr, {
|
conf.buffer_previewer_maker(entry.filename, self.state.bufnr, {
|
||||||
bufname = self.state.bufname,
|
bufname = self.state.bufname,
|
||||||
|
winid = self.state.winid,
|
||||||
callback = function(bufnr)
|
callback = function(bufnr)
|
||||||
search_cb_jump(self, bufnr, text)
|
search_cb_jump(self, bufnr, text)
|
||||||
end,
|
end,
|
||||||
@@ -486,6 +624,7 @@ previewers.help = defaulter(function(_)
|
|||||||
|
|
||||||
conf.buffer_previewer_maker(entry.filename, self.state.bufnr, {
|
conf.buffer_previewer_maker(entry.filename, self.state.bufnr, {
|
||||||
bufname = self.state.bufname,
|
bufname = self.state.bufname,
|
||||||
|
winid = self.state.winid,
|
||||||
callback = function(bufnr)
|
callback = function(bufnr)
|
||||||
putils.regex_highlighter(bufnr, "help")
|
putils.regex_highlighter(bufnr, "help")
|
||||||
search_cb_jump(self, bufnr, query)
|
search_cb_jump(self, bufnr, query)
|
||||||
@@ -729,6 +868,7 @@ previewers.git_file_diff = defaulter(function(opts)
|
|||||||
end
|
end
|
||||||
conf.buffer_previewer_maker(p, self.state.bufnr, {
|
conf.buffer_previewer_maker(p, self.state.bufnr, {
|
||||||
bufname = self.state.bufname,
|
bufname = self.state.bufname,
|
||||||
|
winid = self.state.winid,
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
putils.job_maker({ "git", "--no-pager", "diff", entry.value }, self.state.bufnr, {
|
putils.job_maker({ "git", "--no-pager", "diff", entry.value }, self.state.bufnr, {
|
||||||
|
|||||||
@@ -63,8 +63,18 @@ local function has_filetype(ft)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Attach default highlighter which will choose between regex and ts
|
--- Attach default highlighter which will choose between regex and ts
|
||||||
utils.highlighter = function(bufnr, ft)
|
utils.highlighter = function(bufnr, ft, opts)
|
||||||
if not (utils.ts_highlighter(bufnr, ft)) then
|
opts = opts or {}
|
||||||
|
opts.preview = opts.preview or {}
|
||||||
|
opts.preview.treesitter = vim.F.if_nil(opts.preview.treesitter, true)
|
||||||
|
local ts_highlighting = opts.preview.treesitter == true
|
||||||
|
or type(opts.preview.treesitter) == "table" and vim.tbl_contains(opts.preview.treesitter, ft)
|
||||||
|
|
||||||
|
local ts_success
|
||||||
|
if ts_highlighting then
|
||||||
|
ts_success = utils.ts_highlighter(bufnr, ft)
|
||||||
|
end
|
||||||
|
if not (ts_highlighting or ts_success) then
|
||||||
utils.regex_highlighter(bufnr, ft)
|
utils.regex_highlighter(bufnr, ft)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user