Skip to content

Commit d7bc727

Browse files
feat: Advanced path shortening strategies (#222)
* feat: Advanced path shortening strategies Implements a lot of differnet path displaying shorting strategies. Now you will see paths like path/.4./next_path and it is configurable ## New strategies - middle: "some/path/./next/path", "some/path/../next/path" showing max 3 dots in the middle - middle_number: same as middle but starting from the 4 paths skipped shows the number of skipped components - end: preserves the start of the paths but truncaates not fitting end * chore: Update docs for - feat: Advanced path shortening strategies
1 parent 9a6d8ca commit d7bc727

File tree

8 files changed

+679
-43
lines changed

8 files changed

+679
-43
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
<img alt="Stars" src="https://img.shields.io/github/stars/dmtrKovalenko/fff.nvim?style=for-the-badge&logo=starship&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41"></a>
1212
<a href="https://github.com/dmtrKovalenko/fff.nvim/issues" style="text-decoration: none">
1313
<img alt="Issues" src="https://img.shields.io/github/issues/dmtrKovalenko/fff.nvim?style=for-the-badge&logo=bilibili&color=F5E0DC&logoColor=D9E0EE&labelColor=302D41"></a>
14-
<a href="https://github.com/dmtrKovalenko/fff.nvim/contributors" style="text-decoration: none">
15-
<img alt="Contributors" src="https://img.shields.io/github/contributors/dmtrKovalenko/fff.nvim?color=%23DDB6F2&label=CONTRIBUTORS&logo=git&style=for-the-badge&logoColor=D9E0EE&labelColor=302D41"/></a>
14+
<a href="https://github.com/dmtrKovalenko/fff.nvim/contributors" style="text-decoration: none"> <img alt="Contributors" src="https://img.shields.io/github/contributors/dmtrKovalenko/fff.nvim?color=%23DDB6F2&label=CONTRIBUTORS&logo=git&style=for-the-badge&logoColor=D9E0EE&labelColor=302D41"/></a>
1615
</p>
1716

1817
**FFF** stands for ~freakin fast fuzzy file finder~ (pick 3) and it is an opinionated fuzzy file picker for neovim. Just for files, but we'll try to solve file picking completely.
@@ -124,6 +123,12 @@ require('fff').setup({
124123
preview_position = 'right', -- or 'left', 'right', 'top', 'bottom'
125124
preview_size = 0.5,
126125
show_scrollbar = true, -- Show scrollbar for pagination
126+
-- How to shorten long directory paths in the file list:
127+
-- 'middle_number' (default): uses dots for 1-3 hidden (a/./b, a/../b, a/.../b)
128+
-- and numbers for 4+ (a/.4./b, a/.5./b)
129+
-- 'middle': always uses dots (a/./b, a/../b, a/.../b)
130+
-- 'end': truncates from the end (home/user/projects)
131+
path_shorten_strategy = 'middle_number',
127132
},
128133
preview = {
129134
enabled = true,

doc/fff.nvim.txt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*fff.nvim.txt* For Neovim >= 0.10.0 Last change: 2026 January 09
1+
*fff.nvim.txt* For Neovim >= 0.10.0 Last change: 2026 February 07
22

33
==============================================================================
44
Table of Contents *fff.nvim-table-of-contents*
@@ -9,8 +9,7 @@ FFF.nvimFinally a smart fuzzy file picker for neovim.
99

1010

1111

12-
13-
**FFF** stands for ~freakin fast fuzzy file finder~ (pick 3) and it is an
12+
**FFF** stands for ~freakin fast fuzzy file finder~ (pick 3) and it is an
1413
opinionated fuzzy file picker for neovim. Just for files, but we’ll try to
1514
solve file picking completely.
1615

@@ -132,6 +131,12 @@ all available options:
132131
preview_position = 'right', -- or 'left', 'right', 'top', 'bottom'
133132
preview_size = 0.5,
134133
show_scrollbar = true, -- Show scrollbar for pagination
134+
-- How to shorten long directory paths in the file list:
135+
-- 'middle_number' (default): uses dots for 1-3 hidden (a/./b, a/../b, a/.../b)
136+
-- and numbers for 4+ (a/.4./b, a/.5./b)
137+
-- 'middle': always uses dots (a/./b, a/../b, a/.../b)
138+
-- 'end': truncates from the end (home/user/projects)
139+
path_shorten_strategy = 'middle_number',
135140
},
136141
preview = {
137142
enabled = true,
@@ -347,6 +352,23 @@ with your own custom highlight groups to match your colorscheme.
347352
<
348353

349354

355+
FILE FILTERING
356+
357+
FFF.nvim respects `.gitignore` patterns automatically. To filter files from the
358+
picker without modifying `.gitignore`, create a `.ignore` file in your project
359+
root:
360+
361+
>gitignore
362+
# Exclude all markdown files
363+
*.md
364+
365+
# Exclude specific subdirectory
366+
docs/archive/**/*.md
367+
<
368+
369+
Run `:FFFScan` to force a rescan if needed.
370+
371+
350372
TROUBLESHOOTING ~
351373

352374

lua/fff/conf.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ local function init()
114114
preview_position = 'right', -- or 'left', 'right', 'top', 'bottom'
115115
preview_size = 0.5,
116116
show_scrollbar = true, -- Show scrollbar for pagination
117+
path_shorten_strategy = 'middle_number', -- or 'middle', 'end'
117118
},
118119
preview = {
119120
enabled = true,

lua/fff/file_renderer.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ function M.render_line(item, ctx, item_idx)
8383
end
8484

8585
-- Format filename and path
86+
-- Don't reserve space for frecency - path takes priority
8687
local icon_width = icon and (vim.fn.strdisplaywidth(icon) + 1) or 0
87-
local available_width = math.max(ctx.max_path_width - icon_width - #frecency, 40)
88+
local available_width = math.max(ctx.max_path_width - icon_width, 40)
8889
local filename, dir_path = ctx.format_file_display(item, available_width)
8990

9091
-- Build line

lua/fff/picker_ui.lua

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local utils = require('fff.utils')
77
local location_utils = require('fff.location_utils')
88
local combo_renderer = require('fff.combo_renderer')
99
local scrollbar = require('fff.scrollbar')
10+
local rust = require('fff.rust')
1011

1112
local BORDER_PRESETS = {
1213
single = { '', '', '', '', '', '', '', '' },
@@ -1055,38 +1056,9 @@ function M.render_debounced()
10551056
end
10561057

10571058
local function shrink_path(path, max_width)
1058-
if #path <= max_width then return path end
1059-
1060-
local segments = {}
1061-
for segment in path:gmatch('[^/]+') do
1062-
table.insert(segments, segment)
1063-
end
1064-
1065-
if #segments <= 2 then
1066-
return path -- Can't shrink further
1067-
end
1068-
1069-
local first = segments[1]
1070-
local last = segments[#segments]
1071-
local ellipsis = '../'
1072-
1073-
for middle_count = #segments - 2, 1, -1 do
1074-
local middle_parts = {}
1075-
local start_idx = 2
1076-
local end_idx = math.min(start_idx + middle_count - 1, #segments - 1)
1077-
1078-
for i = start_idx, end_idx do
1079-
table.insert(middle_parts, segments[i])
1080-
end
1081-
1082-
local middle = table.concat(middle_parts, '/')
1083-
if middle_count < #segments - 2 then middle = middle .. ellipsis end
1084-
1085-
local result = first .. '/' .. middle .. '/' .. last
1086-
if #result <= max_width then return result end
1087-
end
1088-
1089-
return first .. '/' .. ellipsis .. last
1059+
local config = conf.get()
1060+
local strategy = config.layout and config.layout.path_shorten_strategy or 'middle_number'
1061+
return rust.shorten_path(path, max_width, strategy)
10901062
end
10911063

10921064
local function format_file_display(item, max_width)
@@ -1098,10 +1070,11 @@ local function format_file_display(item, max_width)
10981070
if parent_dir ~= '.' and parent_dir ~= '' then dir_path = parent_dir end
10991071
end
11001072

1101-
local base_width = #filename + 1 -- filename + " "
1102-
local path_max_width = max_width - base_width
1073+
local filename_width = vim.fn.strdisplaywidth(filename)
1074+
local base_width = filename_width + 1 -- filename + " "
1075+
local path_max_width = math.max(max_width - base_width, 0)
11031076

1104-
if dir_path == '' then return filename, '' end
1077+
if dir_path == '' or path_max_width == 0 then return filename, '' end
11051078
local display_path = shrink_path(dir_path, path_max_width)
11061079

11071080
return filename, display_path
@@ -1131,6 +1104,11 @@ local function build_render_context()
11311104
local win_width = vim.api.nvim_win_get_width(M.state.list_win)
11321105
local prompt_position = get_prompt_position()
11331106

1107+
-- Get actual text offset (signcolumn + foldcolumn + line numbers)
1108+
local win_info = vim.fn.getwininfo(M.state.list_win)[1]
1109+
local text_offset = win_info and win_info.textoff or 2
1110+
local text_width = win_width - text_offset
1111+
11341112
-- Cursor validation
11351113
if M.state.cursor < 1 then
11361114
M.state.cursor = 1
@@ -1170,7 +1148,7 @@ local function build_render_context()
11701148
cursor = M.state.cursor,
11711149
win_height = win_height,
11721150
win_width = win_width,
1173-
max_path_width = config.ui and config.ui.max_path_width or 80,
1151+
max_path_width = text_width, -- Actual text area width (excluding signcolumn)
11741152
debug_enabled = config and config.debug and config.debug.show_scores,
11751153
prompt_position = prompt_position,
11761154
has_combo = has_combo,

lua/fff/rust/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum Error {
1313
AcquireFrecencyLock,
1414
#[error("Failed to acquire lock for items by provider")]
1515
AcquireItemLock,
16+
#[error("Failed to acquire lock for path cache")]
17+
AcquirePathCacheLock,
1618
#[error("Failed to create directory: {0}")]
1719
CreateDir(#[from] std::io::Error),
1820
#[error("Failed to open frecency database env: {0}")]

lua/fff/rust/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mod frecency;
1818
pub mod git;
1919
mod location;
2020
mod log;
21-
mod path_utils;
21+
pub mod path_utils;
2222
pub mod query_tracker;
2323
pub mod score;
2424
mod sort_buffer;
@@ -488,6 +488,19 @@ pub fn health_check(lua: &Lua, test_path: Option<String>) -> LuaResult<LuaValue>
488488
Ok(LuaValue::Table(table))
489489
}
490490

491+
pub fn shorten_path(
492+
_: &Lua,
493+
(path, max_size, strategy): (String, usize, Option<mlua::Value>),
494+
) -> LuaResult<String> {
495+
let strategy = strategy
496+
.map(path_utils::PathShortenStrategy::try_from)
497+
.transpose()?
498+
.unwrap_or_default();
499+
500+
let path = PathBuf::from(&path);
501+
path_utils::shorten_path(strategy, max_size, &path).map_err(Into::into)
502+
}
503+
491504
fn create_exports(lua: &Lua) -> LuaResult<LuaTable> {
492505
let exports = lua.create_table()?;
493506
exports.set("init_db", lua.create_function(init_db)?)?;
@@ -535,6 +548,7 @@ fn create_exports(lua: &Lua) -> LuaResult<LuaTable> {
535548
lua.create_function(get_historical_query)?,
536549
)?;
537550
exports.set("health_check", lua.create_function(health_check)?)?;
551+
exports.set("shorten_path", lua.create_function(shorten_path)?)?;
538552

539553
Ok(exports)
540554
}

0 commit comments

Comments
 (0)