Skip to content

Commit e91c366

Browse files
committed
feat: Current file and distance penalties
1 parent 84a8ebc commit e91c366

7 files changed

Lines changed: 161 additions & 124 deletions

File tree

lua/fff/file_picker/init.lua

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,7 @@ end
8484
function M.get_file_score(index)
8585
if not M.state.last_search_result or not M.state.last_search_result.scores then return nil end
8686

87-
-- Convert to 0-based index for Lua table access
88-
local score = M.state.last_search_result.scores[index]
89-
if not score then return nil end
90-
91-
return {
92-
total = score.total or 0,
93-
base_score = score.base_score or 0,
94-
filename_bonus = score.filename_bonus or 0,
95-
special_filename_bonus = score.special_filename_bonus or 0,
96-
frecency_boost = score.frecency_boost or 0,
97-
distance_penalty = score.distance_penalty or 0,
98-
match_type = score.match_type or 'unknown',
99-
}
87+
return M.state.last_search_result.scores[index]
10088
end
10189

10290
--- Record file access for frecency tracking

lua/fff/file_picker/preview.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,12 @@ function M.create_file_info_content(file, info, file_index)
455455
)
456456
table.insert(
457457
lines,
458-
string.format('Score Modifiers: frec_boost=%d, dist_penalty=%d', score.frecency_boost, score.distance_penalty)
458+
string.format(
459+
'Score Modifiers: frec_boost=%d, dist_penalty=%d, current_penalty=%d',
460+
score.frecency_boost,
461+
score.distance_penalty,
462+
score.current_file_penalty or 0
463+
)
459464
)
460465
else
461466
table.insert(lines, 'Score Breakdown: N/A (no score data available)')

lua/fff/picker_ui.lua

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -825,14 +825,12 @@ function M.render_list()
825825
end
826826
end
827827

828-
local current_indicator = item.is_current_file and ' (current)' or ''
829-
local available_width = math.max(max_path_width - #icon - 1 - #frecency - #current_indicator, 40)
828+
local available_width = math.max(max_path_width - #icon - 1 - #frecency, 40)
830829

831830
local filename, dir_path = format_file_display(item, available_width)
832831
path_data[i] = { filename, dir_path }
833832

834-
local line = string.format('%s %s %s%s%s', icon, filename, dir_path, frecency, current_indicator)
835-
if item.is_current_file then line = string.format('\027[90m%s\027[0m', line) end
833+
local line = string.format('%s %s %s%s', icon, filename, dir_path, frecency)
836834

837835
local line_len = vim.fn.strdisplaywidth(line)
838836
local padding = math.max(0, win_width - line_len + 5)
@@ -881,12 +879,17 @@ function M.render_list()
881879
local icon, icon_hl_group = unpack(icon_data[i])
882880
local filename, dir_path = unpack(path_data[i])
883881

882+
local score = file_picker.get_file_score(i)
883+
if score and score.current_file_penalty ~= 0 then vim.print(score.current_file_penalty) end
884+
local is_current_file = score and score.current_file_penalty and score.current_file_penalty < 0
885+
884886
-- Icon highlighting
885887
if icon_hl_group and vim.fn.strdisplaywidth(icon) > 0 then
888+
local icon_highlight = is_current_file and 'Comment' or icon_hl_group
886889
vim.api.nvim_buf_add_highlight(
887890
M.state.list_buf,
888891
M.state.ns_id,
889-
icon_hl_group,
892+
icon_highlight,
890893
line_idx - 1,
891894
0,
892895
vim.fn.strdisplaywidth(icon)
@@ -921,6 +924,18 @@ function M.render_list()
921924
)
922925
end
923926

927+
if is_current_file then
928+
if not is_cursor_line then
929+
vim.api.nvim_buf_add_highlight(M.state.list_buf, M.state.ns_id, 'Comment', line_idx - 1, 0, -1)
930+
end
931+
932+
local virt_text_hl = is_cursor_line and M.state.config.hl.active_file or 'Comment'
933+
vim.api.nvim_buf_set_extmark(M.state.list_buf, M.state.ns_id, line_idx - 1, 0, {
934+
virt_text = { { ' (current)', virt_text_hl } },
935+
virt_text_pos = 'right_align',
936+
})
937+
end
938+
924939
local border_char = ' '
925940
local border_hl = nil
926941

@@ -1228,9 +1243,34 @@ end
12281243
function M.open(opts)
12291244
if M.state.active then return end
12301245

1246+
-- Get base path first
1247+
local base_path = opts and opts.cwd or vim.fn.getcwd()
1248+
1249+
-- Capture current file before creating UI (which changes current buffer)
1250+
local current_buf = vim.api.nvim_get_current_buf()
1251+
if current_buf and vim.api.nvim_buf_is_valid(current_buf) then
1252+
local current_file = vim.api.nvim_buf_get_name(current_buf)
1253+
if current_file ~= '' and vim.fn.filereadable(current_file) == 1 then
1254+
local absolute_path = vim.fn.fnamemodify(current_file, ':p')
1255+
-- Convert to relative path from base_path
1256+
local relative_path =
1257+
vim.fn.fnamemodify(vim.fn.resolve(absolute_path), ':s?' .. vim.fn.escape(base_path, '\\') .. '/??')
1258+
M.state.current_file_cache = relative_path
1259+
vim.notify(
1260+
'DEBUG: Current file captured (relative): ' .. tostring(M.state.current_file_cache),
1261+
vim.log.levels.INFO
1262+
)
1263+
else
1264+
M.state.current_file_cache = nil
1265+
end
1266+
else
1267+
vim.notify('DEBUG: No valid current buffer found', vim.log.levels.INFO)
1268+
M.state.current_file_cache = nil
1269+
end
1270+
12311271
if not file_picker.is_initialized() then
12321272
local config = {
1233-
base_path = opts and opts.cwd or vim.fn.getcwd(),
1273+
base_path = base_path,
12341274
max_results = 100,
12351275
frecency = {
12361276
enabled = true,

lua/fff/rust/frecency.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ impl FrecencyTracker {
102102
}
103103

104104
pub fn get_access_score(&self, file_path: &Path) -> i64 {
105-
tracing::debug!(?file_path, "Calculating access score");
106105
let accesses = self
107106
.get_accesses(file_path)
108107
.ok()

lua/fff/rust/path_utils.rs

Lines changed: 80 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -35,105 +35,97 @@ pub fn calculate_distance_penalty(current_file: Option<&str>, candidate_path: &s
3535
.count();
3636

3737
let current_depth_from_common = current_parts.len() - common_len;
38-
let candidate_depth_from_common = candidate_parts.len() - common_len;
39-
let total_distance = current_depth_from_common + candidate_depth_from_common;
4038

41-
if total_distance == 0 {
42-
return 0; // Same path
39+
if current_depth_from_common == 0 {
40+
return 0; // Current file is at the common ancestor level
4341
}
4442

45-
let penalty = -(total_distance as i32 * 2);
43+
let penalty = -(current_depth_from_common as i32);
4644

4745
penalty.max(-20)
4846
}
4947

5048
#[cfg(test)]
5149
mod tests {
5250
use super::*;
53-
use std::path::Path;
51+
5452
#[test]
53+
#[cfg(not(target_family = "windows"))]
5554
fn test_calculate_distance_penalty() {
56-
{
57-
let other_path = Path::new("path").join("to").join("file.txt");
58-
assert_eq!(
59-
calculate_distance_penalty(None, other_path.to_str().unwrap()),
60-
0
61-
);
62-
}
63-
{
64-
let base_path = Path::new("path").join("to").join("current");
65-
let current_path = base_path.join("file.txt");
66-
let other_path = base_path.join("other.txt");
67-
assert_eq!(
68-
calculate_distance_penalty(
69-
Some(current_path.to_str().unwrap()),
70-
other_path.to_str().unwrap()
71-
),
72-
0
73-
);
74-
}
75-
{
76-
let base_path = Path::new("path").join("to");
77-
let current_path = base_path.join("current").join("file.txt");
78-
let other_path = base_path.join("file.txt");
79-
assert_eq!(
80-
calculate_distance_penalty(
81-
Some(current_path.to_str().unwrap()),
82-
other_path.to_str().unwrap()
83-
),
84-
-2
85-
);
86-
}
87-
{
88-
let base_path = Path::new("path").join("to");
89-
let current_path = base_path.join("current").join("file.txt");
90-
let other_path = base_path.join("other").join("file.txt");
91-
assert_eq!(
92-
calculate_distance_penalty(
93-
Some(current_path.to_str().unwrap()),
94-
other_path.to_str().unwrap()
95-
),
96-
-4
97-
);
98-
}
99-
{
100-
let base_path = Path::new("path").join("to");
101-
let current_path = base_path.join("current").join("file.txt");
102-
let other_path = base_path.join("another").join("dir").join("file.txt");
103-
assert_eq!(
104-
calculate_distance_penalty(
105-
Some(current_path.to_str().unwrap()),
106-
other_path.to_str().unwrap()
107-
),
108-
-6
109-
);
110-
}
111-
{
112-
let current_path = Path::new("a")
113-
.join("b")
114-
.join("c")
115-
.join("d")
116-
.join("file.txt");
117-
let other_path = Path::new("x")
118-
.join("y")
119-
.join("z")
120-
.join("w")
121-
.join("file.txt");
122-
assert_eq!(
123-
calculate_distance_penalty(
124-
Some(current_path.to_str().unwrap()),
125-
other_path.to_str().unwrap()
126-
),
127-
-16
128-
);
129-
}
130-
{
131-
let current_path = Path::new("file1.txt").to_str().unwrap();
132-
let other_path = Path::new("file2.txt").to_str().unwrap();
133-
assert_eq!(
134-
calculate_distance_penalty(Some(current_path), other_path),
135-
0
136-
);
137-
}
55+
assert_eq!(
56+
calculate_distance_penalty(None, "examples/user/test/mod.rs"),
57+
0
58+
);
59+
// Same directory
60+
assert_eq!(
61+
calculate_distance_penalty(
62+
Some("examples/user/test/main.rs"),
63+
"examples/user/test/mod.rs"
64+
),
65+
0
66+
);
67+
//
68+
// One level apart
69+
assert_eq!(
70+
calculate_distance_penalty(
71+
Some("examples/user/test/subdir/file.rs"),
72+
"examples/user/test/mod.rs"
73+
),
74+
-1
75+
);
76+
//
77+
// Different subdirectories (same parent)
78+
assert_eq!(
79+
calculate_distance_penalty(
80+
Some("examples/user/test/dir1/file.rs"),
81+
"examples/user/test/dir2/mod.rs"
82+
),
83+
-1
84+
);
85+
86+
assert_eq!(
87+
calculate_distance_penalty(
88+
Some("examples/audio-announce/src/lib/audio-announce.rs"),
89+
"examples/audio-announce/src/main.rs"
90+
),
91+
-1
92+
);
93+
94+
assert_eq!(
95+
calculate_distance_penalty(
96+
Some("examples/audio-announce/src/audio-announce.rs"),
97+
"examples/pixel/src/main.rs"
98+
),
99+
-2
100+
);
101+
102+
// Root level files
103+
assert_eq!(calculate_distance_penalty(Some("main.rs"), "lib.rs"), 0);
104+
}
105+
106+
#[test]
107+
#[cfg(target_family = "windows")]
108+
fn distance_penalty_works_on_windows() {
109+
assert_eq!(
110+
calculate_distance_penalty(None, "examples\\user\\test\\mod.rs"),
111+
0
112+
);
113+
// Same directory
114+
assert_eq!(
115+
calculate_distance_penalty(
116+
Some("examples\\user\\test\\main.rs"),
117+
"examples\\user\\test\\mod.rs"
118+
),
119+
0
120+
);
121+
//
122+
// One level apart
123+
assert_eq!(
124+
calculate_distance_penalty(
125+
Some("examples\\user\\test\\subdir\\file.rs"),
126+
"examples\\user\\test\\mod.rs"
127+
),
128+
-1
129+
);
138130
}
139131
}

0 commit comments

Comments
 (0)