diff --git a/.github/README.md b/.github/README.md
index ce71e03..0db7f76 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -90,58 +90,13 @@ This extension should be configured using the `extensions` field inside Telescop
However, you could also pass a table into the extension call.
```lua
---- this is optional
require("telescope").setup({
- ---Dimensions of the preview ueberzug window.
- geometry = {
- ---X-offset of the ueberzug window.
- x = -2,
- ---Y-offset of the ueberzug window.
- y = -2,
- ---Width of the ueberzug window.
- width = 1,
- ---Height of the ueberzug window.
- height = 1,
- },
- find_command = {
- "rg",
- "--no-config",
- "--files",
- ".",
- },
- backend = "ueberzug",
- dynamic_preview_title = true,
- on_confirm = canned.open_path,
- on_confirm_muliple = canned.bulk_copy,
- cache_path = "/tmp/tele.media.cache",
- preview = {
- title = "Previews",
- filesize = 35,
- enable_colorizer = true,
- treesitter = true,
- check_mime_type = true,
- window_options = {
- wrap = false,
- winhl = "Normal:TelescopePreviewNormal",
- signcolumn = "no",
- foldlevel = 100,
- scrollbind = false,
- },
- mimeforce = {
- "json",
- "lua",
- "xml",
- },
- filetype_detect = true,
- fill = {
- mime_disable = "",
- not_text_mime = "",
- permission_denied = "⦂",
- caching = "⎪",
- stat_nil = "╱",
- file_limit = "ˆ",
- },
- },
+ extensions = {
+ backend = "viu", -- "ueberzug"|"viu"|"chafa"|"jp2a"|catimg
+ on_confirm = canned.single.copy_path,
+ on_confirm_muliple = canned.multiple.bulk_copy,
+ cache_path = "/tmp/tele.media.cache",
+ }
})
```
@@ -182,15 +137,16 @@ Some of these are optional.
This is getting out of hand.
-- [ ] Add documentations, briefs and notes.
+- [x] Add documentations, briefs and notes.
- [ ] Recalibrate preview size when window is moved.
- [x] Add default text preview.
- [ ] Render html files using elinks, pandoc, lynx and w3m.
- [ ] Render markdown files using glow and pandoc.
-- [ ] Add [viu](https://github.com/atanunq/viu) backend.
-- [ ] Add [jp2a](https://github.com/cslarsen/jp2a) backend.
-- [ ] Add [chafa](https://github.com/hpjansson/chafa/) backend.
+- [x] Add [viu](https://github.com/atanunq/viu) backend.
+- [x] Add [jp2a](https://github.com/cslarsen/jp2a) backend.
+- [x] Add [chafa](https://github.com/hpjansson/chafa/) backend.
- [x] Add support for ZIPs.
+- [x] Add support for binaries.
- [x] Add default image preview.
- [x] Add support for ebooks.
- [x] Add support for Ai/EPS.
diff --git a/lua/telescope/_extensions/media/init.lua b/lua/telescope/_extensions/media/init.lua
index 20733c6..2e1f469 100644
--- a/lua/telescope/_extensions/media/init.lua
+++ b/lua/telescope/_extensions/media/init.lua
@@ -26,19 +26,14 @@ if not present then
return
end
-local utils = require("telescope.utils")
local actions = require("telescope.actions")
local finders = require("telescope.finders")
local pickers = require("telescope.pickers")
local config = require("telescope.config")
local action_state = require("telescope.actions.state")
-local actions_layout = require("telescope.actions.layout")
local make_entry = require("telescope.make_entry")
-local Job = require("plenary.job")
-local Path = require("plenary.path")
-
local scope = require("telescope._extensions.media.scope")
local canned = require("telescope._extensions.media.canned")
local media_previewer = require("telescope._extensions.media.preview")
@@ -50,79 +45,18 @@ local fn = vim.fn
-- The default configuration. {{{
---This is the default configuration.
local _TelescopeMediaConfig = {
- ---Dimensions of the preview ueberzug window.
- geometry = {
- ---X-offset of the ueberzug window.
- x = -2,
- ---Y-offset of the ueberzug window.
- y = -2,
- ---Width of the ueberzug window.
- width = 1,
- ---Height of the ueberzug window.
- height = 1,
- },
- find_command = {
- "rg",
- "--no-config",
- "--files",
- ".",
- },
- backend = "ueberzug",
+ backend = "viu",
on_confirm = canned.single.copy_path,
on_confirm_muliple = canned.multiple.bulk_copy,
cache_path = "/tmp/tele.media.cache",
- mappings = {
- { "n", "p", actions_layout.toggle_preview },
- { "n", "v", canned.actions.multiple_vsplit },
- { "n", "s", canned.actions.multiple_split },
- { "n", "f", actions_layout.cycle_layout_next },
- { "n", "b", actions_layout.cycle_layout_prev },
- },
+ preview_title = "",
results_title = "",
- prompt_title = "",
- previewer = nil,
- theme = "ivy",
- sorting_strategy = "ascending",
- layout_strategy = "horizontal",
- layout_config = {
- preview_cutoff = 1,
- width = function(_, max_columns, _) return math.min(max_columns, 120) end,
- height = function(_, _, max_lines) return math.min(max_lines, 20) end,
- },
- border = true,
- borderchars = {
- prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" },
- results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" },
- preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
- },
+ prompt_title = "Media",
preview = {
- timeout = 250, -- TODO
- all_binaries = true, -- TODO
- title = "",
- filesize = 35,
- colorizer = true,
- treesitter = true,
- check_mime_type = true,
- window_options = {
- wrap = false,
- winhl = "Normal:TelescopePreviewNormal",
- signcolumn = "no",
- foldlevel = 100,
- scrollbind = false,
- },
- mimeforce = {
- "json",
- "lua",
- "xml",
- },
- filetype_detect = true,
fill = {
- mime_disable = "",
- not_text_mime = "",
- permission_denied = "╱",
+ mime = "",
+ permission = "╱",
caching = "⎪",
- file_limit = "ˆ",
- timeout = "⦂", -- TODO
},
},
}
@@ -170,10 +104,6 @@ end
local function media(options)
options = F.if_nil(options, {})
options.attach_mappings = function(buffer, map)
- for _, mapping in ipairs(options.mappings) do
- map(mapping[1], mapping[2], mapping[3])
- end
-
actions.select_default:replace(function(prompt_buffer)
local current_picker = action_state.get_current_picker(prompt_buffer)
local selections = current_picker:get_multi_selection()
@@ -192,7 +122,6 @@ local function media(options)
-- we need to do this everytime because a new table might be passed
-- for example: one might want to run this through the cmdline or whatever
options = vim.tbl_deep_extend("keep", options, _TelescopeMediaConfig)
- options.find_command = find_command(options)
-- Validate find_command {{{
---@see telescope.previewers.buffer_previewer
diff --git a/lua/telescope/_extensions/media/preview.lua b/lua/telescope/_extensions/media/preview.lua
index 8415452..d61b47b 100644
--- a/lua/telescope/_extensions/media/preview.lua
+++ b/lua/telescope/_extensions/media/preview.lua
@@ -1,294 +1,124 @@
----@tag media.preview
-
----@config { ["name"] = "MEDIA PREVIEWER", ["field_heading"] = "Options", ["module"] = "telescope._extensions.media.preview" }
-
----@brief [[
---- Implementation of a custom previewer.
----@brief ]]
-
--- Imports and local declarations. {{{
local Path = require("plenary.path")
-local Job = require("plenary.job")
local Ueberzug = require("telescope._extensions.media.ueberzug")
+local Job = require("plenary.job")
local utils = require("telescope.utils")
-local previewers = require("telescope.previewers")
-local scope = require("telescope._extensions.media.scope")
-local filetype = require("plenary.filetype")
+local state = require("telescope.state")
+local view = require("telescope.previewers.buffer_previewer")
+local view_util = require("telescope.previewers.utils")
-local preview_utils = require("telescope.previewers.utils")
+local scope = require("telescope._extensions.media.scope")
+local present, colorizer = pcall(require, "colorizer")
-local api = vim.api
+local NULL = vim.NIL
+local F = vim.F
local fn = vim.fn
+local api = vim.api
local uv = vim.loop
-local F = vim.F
-
-local present, colorizer = pcall(require, "colorizer")
--- }}}
--- Helper functions. {{{
---- Hide the ueberzug window (stops viewing the image).
----@param options table needs to have the backend key.
----@param ueberzug Ueberzug the ueberzug object.
----@private
-local function ueberzug_hide(options, ueberzug)
- if options.backend == "ueberzug" and ueberzug then
- ueberzug:send({
- path = vim.NIL, -- vim.NIL represents null
- x = 100,
- y = 100,
- width = 1,
- height = 1,
- })
- end
-end
--- }}}
-
--- Preview function. {{{
---- A subsidiary function of `preview_fn`. This will only be called if a text file is opened. Essentially,
---- this will load a text file and will apply syntax highlights to it if said text file turns out to be a
---- code file (like python, Java, etc.).
----@param extension string file extension
----@param buffer integer the preview buffer identity
----@param window integer the preview window identity
----@param options table plugin settings
----@param mimetype string detect mime-type by `file --mime-type --brief `
----@param preview_filetype string detected filetype
----@param self table reference to the Previewer
----@param code integer exit code
----@param signal integer exit signal
----@private
-local function text_highlighter(extension, buffer, window, options, mimetype, preview_filetype, result)
- if not options.preview.check_mime_type then
- preview_utils.set_preview_message(buffer, window, "MIMETYPE CHECK IS DISABLED", options.preview.fill.mime_disable)
- elseif
- not vim.tbl_contains(options.preview.mimeforce, extension) -- allow hardcoded filetypes
- and mimetype ~= "text"
- and mimetype ~= "inode"
- then
- preview_utils.set_preview_message(buffer, window, "PREVIEW UNAVAILABLE", options.preview.fill.not_text_mime)
- else
- if not api.nvim_buf_is_valid(buffer) then return end
- api.nvim_buf_set_lines(buffer, 0, -1, false, result)
-
- if extension ~= "text" and extension ~= "txt" then
- -- WARN: Slows down telescope when a file has more than ~1500 lines. Please help.
- preview_utils.highlighter(buffer, preview_filetype, options)
- end
-
- -- set window options
- for option, value in pairs(options.preview.window_options) do
- api.nvim_win_set_option(window, option, value)
- end
- -- enable nvim-colorizer.lua for rendering colors (if installed)
- if options.preview.colorizer then colorizer.attach_to_buffer(buffer) end
- end
-end
-
-local function handle_executable(buffer, filepath)
- local task = Job:new({
- "readelf",
- "-WCa",
- filepath,
+local function backend_proxy(buffer, args)
+ local terminal = vim.api.nvim_open_term(buffer, {})
+ fn.jobstart(args, {
+ on_stdout = function(_, data, _)
+ for _, datum in ipairs(data) do api.nvim_chan_send(terminal, datum .. "\r\n") end
+ end, stdout_buffered = true
})
- task:after_success(function(self, _)
- local result = self:result()
- vim.schedule(function() api.nvim_buf_set_lines(buffer, 0, -1, false, result) end)
- end)
- if not api.nvim_buf_is_valid(buffer) then return end
- task:start()
end
---- Another subsidiary of the `preview_fn` function. This one however, handles media files like images, audios,
---- videos, etc. This will look for a handler in |media.scope| and if it finds one then it'll call that handler.
---- The handler may return a path to the cached image. Which will then be previewed.
----@param ueberzug Ueberzug the ueberzug daemon object
----@param window integer the preview window identity
----@param buffer integer the preview buffer identity
----@param filepath string the path to the image/audio/video/font/... file.
----@param cache_path Path path where the images are being cached
----@param handler function media file handler
----@param preview_window table geometry of the preview window
----@param options table plugin settings
----@private
-local function handle_backends(ueberzug, window, buffer, filepath, cache_path, handler, preview_window, options)
- -- clear the preview buffer
- -- TODO: Is there a better way to do this?
- api.nvim_buf_set_lines(buffer, 0, -1, false, { "" })
- local path = handler(filepath, cache_path, {})
-
- if path == vim.NIL then
- ueberzug_hide(options, ueberzug)
- preview_utils.set_preview_message(buffer, window, "CACHING PREVIEW IMAGE", options.preview.fill.caching)
- return
- end
-
- if options.backend == "ueberzug" and ueberzug then
- ueberzug:send({
- path = path,
- x = preview_window.col + options.geometry.x,
- y = preview_window.line + options.geometry.y,
- width = preview_window.width + options.geometry.width,
- height = preview_window.height + options.geometry.height,
- })
- elseif options.backend == "viu" then
- ---@todo
- elseif options.backend == "chafa" then
- ---@todo
- elseif options.backend == "jp2a" then
- ---@todo
- end
-end
-
--- stylua: ignore start
---- As the name implies, this function is to be called from `Previewer.preview_fn`. It acts a proxy
---- to `preview_fn`. We did this mainly because the code looks cleaner this way.
----@param self table the Previewer
----@param entry table current selected item on the prompt list
----@param status table I don't know what this is called - Previewer metadata perhaps
----@param cache_path Path path where all the cached images are stored
----@param ueberzug Ueberzug the ueberzug daemon object
----@param options table plugin settings
----@private
-local function preview_fn_proxy(self, entry, status, cache_path, ueberzug, options)
- local buffer = status.preview_bufnr
- local window = status.preview_win
-
- self.state.winid = window
- local filepath = fn.fnamemodify(entry.value, ":p")
+local function _hook(filepath, buffer, options)
local extension = fn.fnamemodify(filepath, ":e"):lower()
-
- local preview_filetype = ""
- if options.preview.filetype_detect then preview_filetype = filetype.detect(extension, { fs_access = true }) end
- if preview_filetype == "" then preview_filetype = extension end
-
+ local absolute = fn.fnamemodify(filepath, ":p")
local handler = scope.supports[extension]
- -- do not load a file i.e. greater than this size
- uv.fs_stat(entry.value, vim.schedule_wrap(function(_, stat)
- if not stat then
- preview_utils.set_preview_message(buffer, window, "INSUFFICIENT PERMISSIONS", options.preview.fill.permission_denied)
+
+ if handler then
+ local cached_file = handler(absolute, options.cache_path, options)
+ if cached_file == NULL then
+ view_util.set_preview_message(buffer, options.preview.winid, "CACHING ITEM", options.preview.fill.caching)
return
end
- if options.preview.filesize then
- local megabyte_filesize = math.floor(stat.size / math.pow(1024, 2))
- if megabyte_filesize > options.preview.filesize then
- ueberzug_hide(options, ueberzug)
- preview_utils.set_preview_message(buffer, window, "FILE EXCEEDS PREVIEW SIZE LIMIT", options.preview.fill.file_limit)
- return
- end
+ local window = options.get_preview_window()
+ if options.backend == "ueberzug" then
+ options._ueberzug:send({
+ path = cached_file,
+ x = window.col - 2,
+ y = window.line - 2,
+ width = window.width,
+ height = window.height,
+ })
+ elseif options.backend == "viu" then
+ backend_proxy(buffer, { "viu", "-s", cached_file })
+ elseif options.backend == "chafa" then
+ backend_proxy(buffer, { "chafa", cached_file })
+ elseif options.backend == "jp2a" then
+ backend_proxy(buffer, { "jp2a", "--colors", cached_file })
+ elseif options.backend == "catimg" then
+ local width = api.nvim_win_get_width(options.preview.winid)
+ backend_proxy(buffer, { "catimg", "-w", math.floor(width * 1.5), cached_file })
end
+ return
+ end
- -- if a handler exists for the current file (entry) then call that handler
- -- else check if it is a text/text-like file - if so then view its contents - else view a message dialog
- if handler then
- handle_backends(ueberzug, window, buffer, filepath, cache_path, handler, options.get_preview_window(), options)
- else
- ueberzug_hide(options, ueberzug)
- -- TODO: Use read_async instead.
- local task = Job:new({ "cat", filepath })
- -- TODO: Is there a better way?
- local mime = (utils.get_os_command_output({ "file", "--mime-type", "--brief", filepath }))[1]
- local splited = vim.split(mime, "/", { plain = true })
-
- task:add_on_exit_callback(function(...)
- local args = { ... }
- vim.schedule(function()
- text_highlighter(extension, buffer, window, options, splited[1], preview_filetype, args[1]:result())
- end)
+ local mime = vim.split(utils.get_os_command_output({ "file", "--dereference", "--brief", "--mime-type", absolute })[1], "/", { plain = true })
+ if vim.tbl_contains({ "x-executable", "x-pie-executable", "x-sharedlib" }, mime[2]) then
+ Job:new({
+ "readelf",
+ "--wide",
+ "--demangle=auto",
+ "--all",
+ absolute,
+ on_exit = vim.schedule_wrap(function(self, code, _)
+ if code == 0 then api.nvim_buf_set_lines(buffer, 0, -1, false, self:result()) end
end)
-
- -- TODO: Improve this.
- if vim.tbl_contains({
- "x-executable",
- "x-pie-executable",
- "x-sharedlib",
- }, splited[2]) then
- handle_executable(buffer, filepath)
- else
- task:start()
- end
- end
- end))
-end
--- stylua: ignore end
--- }}}
-
--- Setup, teardown and scroll functions. {{{
---- Delete all residue cache files from archives and setup the previewer.
----@param self table the Previewer
----@param cache_path Path path to the cache images
----@param ueberzug Ueberzug the ueberzug daemon object
----@param options table plugin settings
----@return table
----@private
-local function setup_proxy(self, cache_path, ueberzug, options)
- local state = {}
- scope.cleanup(cache_path)
- return state
-end
-
---- The scrolling function for the previewer.
----@param self table the Previewer itself
----@param direction integer up/down scroll units
----@param cache_path Path path to the cache images
----@param ueberzug Ueberzug the ueberzug daemon
----@param options table plugin settings
----@private
-local function scroll_fn_proxy(self, direction, cache_path, ueberzug, options)
- if not self.state then return end
- local input = direction > 0 and [[]] or [[]]
- local count = math.abs(direction)
- api.nvim_win_call(self.state.winid, function() api.nvim_command([[normal! ]] .. count .. input) end)
-end
-
---- The cleanup function.
----@param self table the Previewer itself
----@param cache_path Path path to the cache images
----@param buffer integer the preview buffer identity
----@param ueberzug Ueberzug the ueberzug daemon
----@param options table plugin settings
----@private
-local function teardown_proxy(self, cache_path, buffer, ueberzug, options)
- if ueberzug then ueberzug:kill() end
- if options.preview.colorizer and present and colorizer.is_buffer_attached(buffer) then
- colorizer.detach_from_buffer(buffer)
+ }):start()
+ else
+ Job:new({
+ "file",
+ absolute,
+ on_exit = vim.schedule_wrap(function(self, code, _)
+ if code == 0 then api.nvim_buf_set_lines(buffer, 0, -1, false, self:result()) end
+ end),
+ }):start()
end
end
--- }}}
--- MediaPreview previewer {{{
---- A new previewer definition which handles viewing of both media files and text/text-like files.
----@param options table plugin settings
----@return table
----@private
-local function media_previewer(options)
- local cache_path = Path:new(options.cache_path)
- scope.load_caches(cache_path)
- local UEBERZUG
+return utils.make_default_callable(function(options)
+ options.cache_path = Path:new(options.cache_path)
+ scope.load_caches(options.cache_path)
- -- WARN: This is the most problematic part. If your previewer is open and an error occurs,
- -- we will not be able to terminate the ueberzug job.
- -- NOTE: We could solve this by going back to non-daemon process and running the script everytime
- -- preview_fn is called... but the preview won't be fast.
if options.backend == "ueberzug" then
- UEBERZUG = Ueberzug:new(os.tmpname())
- UEBERZUG:listen()
+ options._ueberzug = Ueberzug:new(os.tmpname())
+ options._ueberzug:listen()
end
- --@see https://is.gd/HbG2AD this is so much better now.
- return previewers.new({
- setup = function(self) return setup_proxy(self, cache_path, UEBERZUG, options) end,
- preview_fn = function(self, entry, status) preview_fn_proxy(self, entry, status, cache_path, UEBERZUG, options) end,
- teardown = function(self) teardown_proxy(self, cache_path, buffer, UEBERZUG, options) end,
- scroll_fn = function(self, direction) scroll_fn_proxy(self, direction, cache_path, UEBERZUG, options) end,
- title = options.preview.title,
- -- TODO: Why does this not work? LMAO?
- dynamic_title = function(self, entry) return fn.fnamemodify(entry.value, ":t") end,
+ options.preview.mime_hook = _hook
+ options.preview.filetype_hook = _hook
+ options.preview.check_mime_type = true
+ options.preview.msg_bg_fillchar = options.preview.fill.mime
+
+ return view.new_buffer_previewer({
+ define_preview = function(self, entry, _)
+ uv.fs_access(entry.value, "R", vim.schedule_wrap(function(_, permission)
+ if permission then
+ options.preview.winid = self.state.winid
+ view.file_maker(entry.value, self.state.bufnr, options)
+ return
+ end
+ view_util.set_preview_message(self.state.bufnr, self.state.winid, "PERMISSION DENIED", options.preview.fill.permission)
+ end))
+ if options.backend == "ueberzug" then options._ueberzug:hide() end
+ end,
+ setup = function(self)
+ scope.cleanup(options.cache_path)
+ return F.if_nil(self.state, {})
+ end,
+ teardown = function(self)
+ if options.backend == "ueberzug" and options._ueberzug then
+ options._ueberzug:kill()
+ options._ueberzug = nil
+ end
+ end,
})
-end
-
-return utils.make_default_callable(media_previewer, {})
--- }}}
+end, options)
-- vim:filetype=lua:fileencoding=utf-8
diff --git a/lua/telescope/_extensions/media/ueberzug.lua b/lua/telescope/_extensions/media/ueberzug.lua
index 15e08a7..613c9e7 100644
--- a/lua/telescope/_extensions/media/ueberzug.lua
+++ b/lua/telescope/_extensions/media/ueberzug.lua
@@ -117,6 +117,10 @@ function Ueberzug:send(message)
self.fifo:write((vim.json.encode(message):gsub("\\", "")) .. "\n", "a")
end
+function Ueberzug:hide()
+ self:send({ path = vim.NIL, x = 1, y = 1, width = 1, height = 1 })
+end
+
return Ueberzug
-- vim:filetype=lua:fileencoding=utf-8