1- --- Image handling module for file picker
2- --- Simple implementation that delegates to Snacks.nvim
1+ local utils = require (' fff.utils' )
32
43local 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+
710local 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
2452end
2553
26- -- Common image extensions (SVG excluded for text preview)
2754local IMAGE_EXTENSIONS = {
2855 ' .jpg' ,
2956 ' .jpeg' ,
5986function 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
7496end
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
117138function 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 )
171150end
172151
173152return M
0 commit comments