Skip to content

Commit 4a1297d

Browse files
committed
feat: Display image detailed info for preview + improve positioning
1 parent b5807d5 commit 4a1297d

5 files changed

Lines changed: 127 additions & 267 deletions

File tree

lua/fff/file_picker/image.lua

Lines changed: 65 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,56 @@
1-
--- Image handling module for file picker
2-
--- Simple implementation that delegates to Snacks.nvim
1+
local utils = require('fff.utils')
32

43
local M = {}
54

6-
-- Track active image placements per buffer
5+
local function get_main_config()
6+
local main = require('fff.main')
7+
return main.config
8+
end
9+
710
local active_placements = {} ---@type table<number, any>
811

9-
-- Helper function to safely set buffer lines
10-
local function safe_set_buffer_lines(bufnr, start, end_line, strict_indexing, lines)
11-
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then return false end
12+
local function identify_image_lines(file_path)
13+
local stat = vim.uv.fs_stat(file_path)
14+
local size_str = stat and utils.format_file_size(stat.size) or 'Unknown'
15+
16+
local info_lines = {}
17+
table.insert(info_lines, ' Size: ' .. size_str)
18+
19+
local config = get_main_config()
20+
local format_str = config and config.preview and config.preview.imagemagick_info_format_str
21+
or '%m: %wx%h, %[colorspace], %q-bit'
22+
local cmd = string.format('identify -format "%s" "%s" 2>/dev/null', format_str, file_path)
23+
local magick_info = vim.fn.system(cmd)
24+
25+
if vim.v.shell_error == 0 and magick_info and magick_info ~= '' then
26+
magick_info = ' ' .. magick_info:gsub('\n', '')
27+
table.insert(info_lines, magick_info)
28+
end
29+
30+
return info_lines
31+
end
32+
33+
-- This is a required function for snacks nvim to fill the buffer with enough space
34+
local function fill_buffer_space_for_image_preview(bufnr, info_lines)
35+
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then return end
36+
37+
local win = vim.fn.bufwinid(bufnr)
38+
local buffer_height = win ~= -1 and vim.api.nvim_win_get_height(win) or 24
39+
local lines_for_image = math.max(buffer_height - #info_lines - 2, 5)
40+
41+
local buffer_lines = vim.list_extend({}, info_lines)
42+
for _ = 1, lines_for_image do
43+
table.insert(buffer_lines, '')
44+
end
1245

13-
-- Make buffer modifiable temporarily
1446
local was_modifiable = vim.api.nvim_buf_get_option(bufnr, 'modifiable')
1547
vim.api.nvim_buf_set_option(bufnr, 'modifiable', true)
1648

17-
-- Set the lines
18-
local ok, err = pcall(vim.api.nvim_buf_set_lines, bufnr, start, end_line, strict_indexing, lines)
49+
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, buffer_lines)
1950

20-
-- Restore modifiable state
2151
vim.api.nvim_buf_set_option(bufnr, 'modifiable', was_modifiable)
22-
23-
return ok
2452
end
2553

26-
-- Common image extensions (SVG excluded for text preview)
2754
local IMAGE_EXTENSIONS = {
2855
'.jpg',
2956
'.jpeg',
@@ -59,18 +86,13 @@ end
5986
function M.clear_buffer_images(bufnr)
6087
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then return end
6188

62-
-- Close any tracked placement for this buffer
6389
if active_placements[bufnr] then
6490
pcall(active_placements[bufnr].close, active_placements[bufnr])
6591
active_placements[bufnr] = nil
6692
end
6793

6894
local ok, snacks = pcall(require, 'snacks')
69-
if ok and snacks.image and snacks.image.placement then
70-
-- Use the proper Snacks.nvim placement cleanup API
71-
-- This removes all image placements for the specified buffer
72-
pcall(snacks.image.placement.clean, bufnr)
73-
end
95+
if ok and snacks.image and snacks.image.placement then pcall(snacks.image.placement.clean, bufnr) end
7496
end
7597

7698
--- Display image using the simplest approach that works
@@ -82,29 +104,28 @@ function M.display_image(file_path, bufnr, max_width, max_height)
82104
max_width = max_width or 80
83105
max_height = max_height or 24
84106

85-
-- Try Snacks.nvim first (most reliable)
86107
local ok, snacks = pcall(require, 'snacks')
87108
if ok and snacks.image and snacks.image.buf then
88-
-- Clear any existing image attachments first
89109
M.clear_buffer_images(bufnr)
90110

91-
-- Clear buffer content to prevent text/image overlap
92-
safe_set_buffer_lines(bufnr, 0, -1, false, {})
93-
94-
-- Configure Snacks image to prevent repetition
95-
local success, placement = pcall(snacks.image.buf.attach, bufnr, {
96-
src = file_path,
97-
fit = 'contain', -- Fit image within bounds without repetition
98-
})
99-
100-
if success and placement then
101-
-- Track the placement so we can clean it up later
102-
active_placements[bufnr] = placement
103-
return -- Successfully attached image, we're done
104-
else
105-
M.display_image_info(file_path, bufnr, 'Snacks.nvim failed: ' .. tostring(placement or 'unknown error'))
106-
return
107-
end
111+
local info_lines = identify_image_lines(file_path)
112+
113+
fill_buffer_space_for_image_preview(bufnr, info_lines)
114+
vim.schedule(function()
115+
local success, placement = pcall(snacks.image.placement.new, bufnr, file_path, {
116+
pos = { #info_lines + 1, 1 },
117+
inline = true,
118+
fit = 'contain',
119+
auto_resize = true,
120+
})
121+
122+
if success and placement then
123+
active_placements[bufnr] = placement
124+
else
125+
M.display_image_info(file_path, bufnr, 'Snacks.nvim failed: ' .. tostring(placement or 'unknown error'))
126+
end
127+
end)
128+
return
108129
end
109130

110131
M.display_image_info(file_path, bufnr, 'Snacks.nvim not available')
@@ -115,59 +136,17 @@ end
115136
--- @param bufnr number Buffer number to display in
116137
--- @param reason string|nil Reason for failure
117138
function M.display_image_info(file_path, bufnr, reason)
118-
local info = {}
119-
120-
local stat = vim.uv.fs_stat(file_path)
121-
if stat then
122-
table.insert(info, string.format('📁 File: %s', vim.fn.fnamemodify(file_path, ':t')))
123-
table.insert(info, string.format('📏 Size: %d bytes', stat.size))
124-
table.insert(info, string.format('🕒 Modified: %s', os.date('%Y-%m-%d %H:%M:%S', stat.mtime.sec)))
125-
end
126-
127-
local ok, snacks = pcall(require, 'snacks.image')
128-
local term_supported = ok and snacks and snacks.supports_terminal()
129-
130-
local width, height = M.get_image_dimensions(file_path)
131-
if width and height then table.insert(info, string.format('🖼️ Dimensions: %dx%d pixels', width, height)) end
132-
133-
local ext = string.lower(vim.fn.fnamemodify(file_path, ':e'))
134-
if ext ~= '' then table.insert(info, string.format('🎨 Format: %s', ext:upper())) end
139+
local info_lines = identify_image_lines(file_path)
135140

136-
table.insert(info, '')
137-
table.insert(info, '┌─ Image Preview Debug ─┐')
138-
table.insert(info, string.format('│ Supported: %s │', term_supported and 'Yes' or 'No'))
139141
if reason then
140-
table.insert(info, string.format('│ Issue: %s', reason:sub(1, 16)))
141-
if #reason > 16 then table.insert(info, string.format('│ %s', reason:sub(17, 32))) end
142-
end
143-
table.insert(info, '└─────────────────────────┘')
144-
145-
safe_set_buffer_lines(bufnr, 0, -1, false, info)
146-
end
147-
148-
--- Get image dimensions using file command
149-
--- @param file_path string Path to the image file
150-
--- @return number|nil, number|nil Width and height in pixels
151-
function M.get_image_dimensions(file_path)
152-
-- Try file command first
153-
local cmd = string.format('file %s', vim.fn.shellescape(file_path))
154-
local result = vim.fn.system(cmd)
155-
156-
if vim.v.shell_error == 0 then
157-
local width, height = result:match('(%d+)%s*x%s*(%d+)')
158-
if width and height then return tonumber(width), tonumber(height) end
159-
end
160-
161-
-- Fallback to identify command (ImageMagick)
162-
cmd = string.format('identify -format "%%w %%h" %s 2>/dev/null', vim.fn.shellescape(file_path))
163-
result = vim.fn.system(cmd)
142+
table.insert(info_lines, '')
143+
table.insert(info_lines, string.rep('', 50))
164144

165-
if vim.v.shell_error == 0 then
166-
local width, height = result:match('(%d+)%s+(%d+)')
167-
if width and height then return tonumber(width), tonumber(height) end
145+
table.insert(info_lines, ' Preview is not available')
146+
table.insert(info_lines, ' Reason: ' .. reason)
168147
end
169148

170-
return nil, nil
149+
fill_buffer_space_for_image_preview(bufnr, info_lines)
171150
end
172151

173152
return M

0 commit comments

Comments
 (0)