Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,13 @@ Defaults are sensible. Override only what you care about.
```lua
require('fff').setup({
base_path = vim.fn.getcwd(),
prompt = '> ',
prompt = '🪿 ',
title = 'FFFiles',
max_results = 100,
max_threads = 4,
lazy_sync = true,
prompt_vim_mode = false,
wrap_around = false, -- true to wrap the cursor around when moving past the first/last item
follow_symlinks = false,
-- Allow indexing the user's $HOME directory. Enabled by default.
-- Disable if you strictly sure you don't want this, as it makes whole fff error hard
Expand All @@ -354,8 +355,15 @@ require('fff').setup({
flex = { size = 130, wrap = 'top' },
min_list_height = 10, -- do not display anything except the list below this threshold
show_scrollbar = true,
path_shorten_strategy = 'middle_number', -- 'middle_number' | 'middle' | 'end' | 'start'
path_shorten_strategy = 'middle', -- 'middle' | 'middle_number' | 'end' | 'start'
-- 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
anchor = 'center',
show_path_first = false, -- true renders results as `path/to/file` instead of `file path/to`
},
-- find_files specific rendering
file_picker = {
current_file_label = '(current)', -- virtual text marking the buffer the picker was opened from
fuzzy_query_highlighting = false, -- true to highlight fuzzy query matches, not just the literal query
},
preview = {
enabled = true,
Expand Down Expand Up @@ -389,6 +397,7 @@ require('fff').setup({
grep_jump_to_next_file = { '<C-A-n>', '<A-Down>' },
grep_jump_to_prev_file = { '<C-A-p>', '<A-Up>' },
cycle_previous_query = '<C-Up>',
cycle_forward_query = '<C-Down>',
-- unbound by default, wipes the whole input line
-- clear_query = '<C-u>', -- overrides preview_scroll_up in insert mode
toggle_select = '<Tab>',
Expand All @@ -413,9 +422,6 @@ require('fff').setup({
git = {
status_text_color = false, -- true to color filenames by git status
},
file_picker = {
fuzzy_query_highlighting = false, -- true to highlight fuzzy query matches in file picker results
},
select = {
-- Return winid to open the chosen file in, or nil to open in the original window
select_window = function(current_buf, action) --[[ default impl ]] end,
Expand Down Expand Up @@ -447,6 +453,7 @@ require('fff').setup({
},
},
logging = {
enabled = true,
-- logs will be written in a parent directory of this file path in files like
-- `<stem>+<UTC-timestamp>+<pid>.<ext>`. Run :FFFOpenLog to open current one
log_file = vim.fn.stdpath('log') .. '/fff.log',
Expand Down
17 changes: 12 additions & 5 deletions doc/fff.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,13 @@ Defaults are sensible. Override only what you care about.
>lua
require('fff').setup({
base_path = vim.fn.getcwd(),
prompt = '> ',
prompt = '🪿 ',
title = 'FFFiles',
max_results = 100,
max_threads = 4,
lazy_sync = true,
prompt_vim_mode = false,
wrap_around = false, -- true to wrap the cursor around when moving past the first/last item
follow_symlinks = false,
-- Allow indexing the user's $HOME directory. Enabled by default.
-- Disable if you strictly sure you don't want this, as it makes whole fff error hard
Expand All @@ -223,8 +224,15 @@ Defaults are sensible. Override only what you care about.
flex = { size = 130, wrap = 'top' },
min_list_height = 10, -- do not display anything except the list below this threshold
show_scrollbar = true,
path_shorten_strategy = 'middle_number', -- 'middle_number' | 'middle' | 'end' | 'start'
path_shorten_strategy = 'middle', -- 'middle' | 'middle_number' | 'end' | 'start'
-- 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
anchor = 'center',
show_path_first = false, -- true renders results as `path/to/file` instead of `file path/to`
},
-- find_files specific rendering
file_picker = {
current_file_label = '(current)', -- virtual text marking the buffer the picker was opened from
fuzzy_query_highlighting = false, -- true to highlight fuzzy query matches, not just the literal query
},
preview = {
enabled = true,
Expand Down Expand Up @@ -258,6 +266,7 @@ Defaults are sensible. Override only what you care about.
grep_jump_to_next_file = { '<C-A-n>', '<A-Down>' },
grep_jump_to_prev_file = { '<C-A-p>', '<A-Up>' },
cycle_previous_query = '<C-Up>',
cycle_forward_query = '<C-Down>',
-- unbound by default, wipes the whole input line
-- clear_query = '<C-u>', -- overrides preview_scroll_up in insert mode
toggle_select = '<Tab>',
Expand All @@ -282,9 +291,6 @@ Defaults are sensible. Override only what you care about.
git = {
status_text_color = false, -- true to color filenames by git status
},
file_picker = {
fuzzy_query_highlighting = false, -- true to highlight fuzzy query matches in file picker results
},
select = {
-- Return winid to open the chosen file in, or nil to open in the original window
select_window = function(current_buf, action) --[[ default impl ]] end,
Expand Down Expand Up @@ -316,6 +322,7 @@ Defaults are sensible. Override only what you care about.
},
},
logging = {
enabled = true,
-- logs will be written in a parent directory of this file path in files like
-- `<stem>+<UTC-timestamp>+<pid>.<ext>`. Run :FFFOpenLog to open current one
log_file = vim.fn.stdpath('log') .. '/fff.log',
Expand Down
3 changes: 3 additions & 0 deletions lua/fff/conf.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local M = {}
--- @field min_list_height number
--- @field show_scrollbar boolean
--- @field path_shorten_strategy string
--- @field show_path_first boolean
--- @field border? 'single'|'double'|'rounded'|'solid'|'shadow'|'none'|table<string[],string[]> Border preset; falls back to `vim.o.winborder` when nil

--- @class FffPreviewConfig
Expand Down Expand Up @@ -278,6 +279,8 @@ local function init()
-- 'end': truncates from the end, keeps the start (home/user/projects)
-- 'start': truncates from the start, keeps the end (.../parts/ai_extracted)
path_shorten_strategy = 'middle',
-- Render results as `path/to/file` instead of `file path/to`
show_path_first = false,
},
preview = {
enabled = true,
Expand Down
84 changes: 84 additions & 0 deletions lua/fff/picker_ui/file_name_renderer.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
local M = {}

local path_separator = package.config:sub(1, 1)

--- @class FffFileNameLayout
--- @field text string Icon + name section of the rendered line
--- @field filename string File name as returned by `ctx.format_file_display`
--- @field dir_path string Shortened directory, '' when the file sits at the root
--- @field filename_col number 0-based byte column of the file name inside `text`
--- @field dir_col number 0-based byte column of `dir_path` inside `text`
--- @field dir_end_col number Byte column past `dir_path`, separator included when path first
--- @field path_first boolean

--- True when file names render as `dir/name` instead of `name dir`.
--- @param config FffConfig|nil
--- @return boolean
function M.is_path_first(config) return (config and config.layout and config.layout.show_path_first) == true end

--- Build the icon + name section of a file line together with its byte offsets.
--- @param item FileItem File item from Rust
--- @param ctx ListRenderContext Render context with all state
--- @param icon string|nil Already resolved file icon
--- @return FffFileNameLayout
function M.build(item, ctx, icon)
local path_first = M.is_path_first(ctx.config)
local icon_width = icon and (vim.fn.strdisplaywidth(icon) + 1) or 0
-- name-first needs a floor so the directory column does not jump around,
-- path-first has nothing to align and takes whatever the window gives
local available_width = math.max(ctx.max_path_width - icon_width, path_first and 0 or 40)
local filename, dir_path = ctx.format_file_display(item, available_width)

local prefix = icon and (icon .. ' ') or ''
local prefix_len = #prefix
if path_first then
local separator = dir_path ~= '' and path_separator or ''
local dir_end_col = prefix_len + #dir_path + #separator
return {
text = prefix .. dir_path .. separator .. filename,
filename = filename,
dir_path = dir_path,
filename_col = dir_end_col,
dir_col = prefix_len,
dir_end_col = dir_end_col,
path_first = true,
}
end

local dir_col = prefix_len + #filename + 1
return {
text = prefix .. filename .. ' ' .. dir_path,
filename = filename,
dir_path = dir_path,
filename_col = prefix_len,
dir_col = dir_col,
dir_end_col = dir_col + #dir_path,
path_first = false,
}
end

--- Map fuzzy match ranges over `item.relative_path` onto line byte columns.
--- Segments are `{ source_start, source_end, target_col }` triples.
--- @param item FileItem File item from Rust
--- @param layout FffFileNameLayout Layout returned by `M.build`
--- @return number[][]
function M.fuzzy_segments(item, layout)
local rel_path = item.relative_path or ''
if type(rel_path) ~= 'string' then rel_path = tostring(rel_path) end

local filename_rel_start = math.max(0, #rel_path - #layout.filename)
local segments = { { filename_rel_start, filename_rel_start + #layout.filename, layout.filename_col } }

local parent_dir = vim.fn.fnamemodify(rel_path, ':h')
if parent_dir == '.' then parent_dir = '' end

-- a shortened directory breaks the byte mapping, only map an intact one
if parent_dir ~= '' and layout.dir_path == parent_dir then
local dir_source_end = layout.path_first and filename_rel_start or #parent_dir
segments[#segments + 1] = { 0, dir_source_end, layout.dir_col }
end

return segments
end

return M
44 changes: 9 additions & 35 deletions lua/fff/picker_ui/file_renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
--- Simple renderer for file items with 2 functions: render_line and apply_highlights
local M = {}

local file_name_renderer = require('fff.picker_ui.file_name_renderer')

--- File Item structure from Rust
--- @class FileItem
--- @field path string Absolute file path
Expand Down Expand Up @@ -49,15 +51,7 @@ function M.render_line(item, ctx, item_idx) -- luacheck: ignore item_idx
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 line = file_name_renderer.build(item, ctx, icon).text .. frecency

local padding = math.max(0, ctx.win_width - vim.fn.strdisplaywidth(line) + 5)
table.insert(lines, line .. string.rep(' ', padding))
Expand All @@ -84,9 +78,8 @@ function M.apply_highlights(item, ctx, item_idx, buf, ns_id, line_idx, line_cont

-- 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)
local name_layout = file_name_renderer.build(item, ctx, icon)
local filename, dir_path = name_layout.filename, name_layout.dir_path

-- 1. Cursor highlight
if is_cursor then
Expand Down Expand Up @@ -115,7 +108,7 @@ function M.apply_highlights(item, ctx, item_idx, buf, ns_id, line_idx, line_cont
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
local filename_start = name_layout.filename_col
vim.api.nvim_buf_set_extmark(
buf,
ns_id,
Expand All @@ -142,16 +135,12 @@ function M.apply_highlights(item, ctx, item_idx, buf, ns_id, line_idx, line_cont

-- 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 }
name_layout.dir_col,
{ end_col = name_layout.dir_end_col, hl_group = ctx.config.hl.directory_path }
)
end

Expand Down Expand Up @@ -219,22 +208,7 @@ function M.apply_highlights(item, ctx, item_idx, buf, ns_id, line_idx, line_cont
local ranges = item.match_ranges
if not ranges or #ranges == 0 then return end

local rel_path = item.relative_path or ''
if type(rel_path) ~= 'string' then rel_path = tostring(rel_path) end

local original_dir_path = ''
local parent_dir = vim.fn.fnamemodify(rel_path, ':h')
if parent_dir ~= '.' and parent_dir ~= '' then original_dir_path = parent_dir end

local filename_rel_start = math.max(0, #rel_path - #filename)
local filename_rel_end = filename_rel_start + #filename
local filename_line_start = icon and (#icon + 1) or 0
local dir_line_start = filename_line_start + #filename + 1
local segments = { { filename_rel_start, filename_rel_end, filename_line_start } }

if original_dir_path ~= '' and dir_path == original_dir_path then
segments[#segments + 1] = { 0, #original_dir_path, dir_line_start }
end
local segments = file_name_renderer.fuzzy_segments(item, name_layout)

local function apply_segment(raw_start, raw_end, segment)
local source_start, source_end, target_start = segment[1], segment[2], segment[3]
Expand Down
Loading
Loading