--- File Renderer --- Simple renderer for file items with 2 functions: render_line and apply_highlights local M = {} --- File Item structure from Rust --- @class FileItem --- @field path string Absolute file path --- @field relative_path string Relative file path from base directory --- @field name string File name --- @field extension string File extension --- @field size number File size in bytes --- @field modified number Last modified timestamp --- @field total_frecency_score number Total frecency score --- @field access_frecency_score number Access-based frecency score --- @field modification_frecency_score number Modification-based frecency score --- @field git_status string|nil Git status string (e.g. 'modified', 'untracked') if file is in git repo --- Render a file item line --- @param item FileItem File item from Rust --- @param ctx ListRenderContext Render context with all state --- @param item_idx number|nil 1-based item index in ctx.items --- @return string[] Array of line strings (always exactly 1) function M.render_line(item, ctx, item_idx) -- luacheck: ignore item_idx local icons = require('fff.file_picker.icons') local lines = {} local icon, _ = icons.get_icon(item.name, item.extension, false) -- Build frecency indicator (debug mode only) local frecency = '' if ctx.debug_enabled then local total = item.total_frecency_score or 0 local access = item.access_frecency_score or 0 local mod = item.modification_frecency_score or 0 if total > 0 then local indicator = '' if mod >= 6 then indicator = '🔥' elseif access >= 4 then indicator = '⭐️' elseif total >= 3 then indicator = '✨' elseif total >= 1 then indicator = '•' end frecency = string.format(' %s%d', indicator, total) end end -- Format filename and path -- Don't reserve space for frecency - path takes priority local icon_width = icon and (vim.fn.strdisplaywidth(icon) + 1) or 0 local available_width = math.max(ctx.max_path_width - icon_width, 40) local filename, dir_path = ctx.format_file_display(item, available_width) -- Build line local line = icon and string.format('%s %s %s%s', icon, filename, dir_path, frecency) or string.format('%s %s%s', filename, dir_path, frecency) local padding = math.max(0, ctx.win_width - vim.fn.strdisplaywidth(line) + 5) table.insert(lines, line .. string.rep(' ', padding)) return lines end --- Apply highlights to a rendered line --- @param item FileItem File item from Rust --- @param ctx ListRenderContext Render context with all state --- @param item_idx number Item index (1-based) --- @param buf number Buffer handle --- @param ns_id number Namespace ID --- @param line_idx number 1-based line index in buffer --- @param line_content string The actual line content function M.apply_highlights(item, ctx, item_idx, buf, ns_id, line_idx, line_content) local icons = require('fff.file_picker.icons') local highlights = require('fff.highlights') local file_picker = require('fff.file_picker') local is_cursor = (ctx.cursor == item_idx) local score = file_picker.get_file_score(item_idx) local is_current_file = score and score.current_file_penalty and score.current_file_penalty < 0 -- Get icon and paths local icon, icon_hl_group = icons.get_icon(item.name, item.extension, false) local icon_width = icon and (vim.fn.strdisplaywidth(icon) + 1) or 0 local available_width = math.max(ctx.max_path_width - icon_width, 40) local filename, dir_path = ctx.format_file_display(item, available_width) -- 1. Cursor highlight if is_cursor then vim.api.nvim_buf_set_extmark(buf, ns_id, line_idx - 1, 0, { end_col = 0, end_row = line_idx, hl_group = ctx.config.hl.cursor, hl_eol = true, priority = 100, }) end -- 2. Icon if icon and icon_hl_group and vim.fn.strdisplaywidth(icon) > 0 then local icon_hl = is_current_file and 'Comment' or icon_hl_group vim.api.nvim_buf_set_extmark( buf, ns_id, line_idx - 1, 0, { end_col = vim.fn.strdisplaywidth(icon), hl_group = icon_hl } ) end -- 3. Git text color (filename) if ctx.config.git and ctx.config.git.status_text_color and icon and #filename > 0 then local git_text_hl = item.git_status and highlights.get_git_text_highlight(item.git_status) or nil if git_text_hl and git_text_hl ~= '' and not is_current_file then local filename_start = #icon + 1 vim.api.nvim_buf_set_extmark( buf, ns_id, line_idx - 1, filename_start, { end_col = filename_start + #filename, hl_group = git_text_hl } ) end end -- 4. Frecency indicator if ctx.debug_enabled then local start_pos, end_pos = line_content:find('[⭐️🔥✨•]%d+') if start_pos and end_pos then vim.api.nvim_buf_set_extmark( buf, ns_id, line_idx - 1, start_pos - 1, { end_col = end_pos, hl_group = ctx.config.hl.frecency } ) end end -- 5. Directory path (dimmed) if #filename > 0 and #dir_path > 0 then local prefix_len = #filename + 1 -- filename bytes + space if icon then prefix_len = prefix_len + #icon + 1 -- if icon add icon bytes + space end vim.api.nvim_buf_set_extmark( buf, ns_id, line_idx - 1, prefix_len, { end_col = prefix_len + #dir_path, hl_group = ctx.config.hl.directory_path } ) end -- 6. Current file if is_current_file then local hl if is_cursor then hl = ctx.config.hl.cursor else hl = 'Comment' end vim.api.nvim_buf_set_extmark(buf, ns_id, line_idx - 1, 0, { virt_text = { { ' ' .. ctx.config.file_picker.current_file_label, hl } }, virt_text_pos = 'right_align', }) end -- 7. Git sign if item.git_status and highlights.should_show_git_border(item.git_status) then local border_char = highlights.get_git_border_char(item.git_status) local border_hl = highlights.get_git_sign_highlight(item.git_status, is_cursor, ctx.config.hl.cursor) if border_hl and border_hl ~= '' then vim.api.nvim_buf_set_extmark(buf, ns_id, line_idx - 1, 0, { sign_text = border_char, sign_hl_group = border_hl, priority = 1000, }) end elseif is_cursor then vim.api.nvim_buf_set_extmark(buf, ns_id, line_idx - 1, 0, { sign_text = ' ', sign_hl_group = ctx.config.hl.cursor, priority = 1000, }) end -- 8. Selection if ctx.selected_files and ctx.selected_files[item.relative_path] then local selection_hl = is_cursor and ctx.config.hl.selected_active or ctx.config.hl.selected vim.api.nvim_buf_set_extmark(buf, ns_id, line_idx - 1, 0, { sign_text = '▊', sign_hl_group = selection_hl, priority = 1001, }) end -- 9. Query match if ctx.query and ctx.query ~= '' then local match_start, match_end = string.find(line_content, ctx.query, 1, true) if match_start and match_end then vim.api.nvim_buf_set_extmark( buf, ns_id, line_idx - 1, match_start - 1, { end_col = match_end, hl_group = ctx.config.hl.matched or 'IncSearch' } ) end end end return M