Compare commits

...

7 Commits

Author SHA1 Message Date
Dmitriy Kovalenko 7074def415 Add more style improvements 2025-08-02 22:43:36 +02:00
dandxy89 fc95af5861 ci: Adding Rust CI workflow 2025-08-02 09:57:55 +01:00
dandxy89 ac93e1a323 refactor: Addressing some more clippy warnings 2025-08-02 09:53:29 +01:00
dandxy89 4d1bd5c29d refactor: Simplify expression 2025-08-02 09:35:12 +01:00
dandxy89 392e9455cd refactor: Remove * imports and simplify match statement 2025-08-02 09:33:36 +01:00
dandxy89 458fb2bd2a refactor: Updating file_picker.rs 2025-08-02 09:32:55 +01:00
dandxy89 0de3af8eea build: Remove tokio as a dependency 2025-08-02 09:23:12 +01:00
11 changed files with 220 additions and 258 deletions
+59
View File
@@ -0,0 +1,59 @@
name: Rust CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- name: Install Lua
if: matrix.os == 'macos-latest'
run: brew install lua
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: true
cache-on-failure: true
cache-key: "v1-rust"
components: rustfmt, clippy
target: wasm32-unknown-unknown
- name: Run tests
run: cargo test --verbose
fmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt
- name: Check formatting
run: cargo fmt -- --check
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- name: Run clippy
run: cargo clippy -- -D warnings
Generated
-92
View File
@@ -2,21 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -59,21 +44,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
name = "bincode"
version = "1.3.3"
@@ -271,7 +241,6 @@ dependencies = [
"pathdiff",
"rayon",
"thiserror 2.0.12",
"tokio",
"tracing",
"tracing-appender",
"tracing-subscriber",
@@ -316,12 +285,6 @@ dependencies = [
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git2"
version = "0.20.2"
@@ -555,17 +518,6 @@ dependencies = [
"libc",
]
[[package]]
name = "io-uring"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"libc",
]
[[package]]
name = "itoa"
version = "1.0.15"
@@ -712,15 +664,6 @@ version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.4"
@@ -866,15 +809,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -1131,12 +1065,6 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-demangle"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -1223,12 +1151,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
[[package]]
name = "smallvec"
version = "1.15.1"
@@ -1368,20 +1290,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tokio"
version = "1.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35"
dependencies = [
"backtrace",
"io-uring",
"libc",
"mio",
"pin-project-lite",
"slab",
]
[[package]]
name = "tracing"
version = "0.1.41"
+9 -9
View File
@@ -11,20 +11,20 @@ crate-type = ["cdylib", "rlib"]
name = "test_watcher"
path = "src/bin/test_watcher.rs"
[dependencies]
neo_frizbee = { version = "0.6.0" }
heed = "0.22.0"
rayon = "1.8.0"
mlua = { version = "0.11.1", features = ["module", "luajit"] }
thiserror = "2.0.10"
blake3 = "1.8.2"
ignore = "0.4.22"
pathdiff = "0.2.1"
chrono = { version = "0.4", features = ["serde"] }
git2 = "0.20.2"
heed = "0.22.0"
ignore = "0.4.22"
mlua = { version = "0.11.1", features = ["module", "luajit"] }
neo_frizbee = { version = "0.6.0" }
notify = "8.1.0"
notify-debouncer-full = "0.5"
tokio = { version = "1.0", features = ["time", "sync"] }
chrono = { version = "0.4", features = ["serde"] }
pathdiff = "0.2.1"
rayon = "1.8.0"
thiserror = "2.0.10"
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+86 -87
View File
@@ -121,8 +121,8 @@ impl FileItem {
.modified()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_secs())
.unwrap_or(0);
.map_or(0, |d| d.as_secs());
(size, modified)
}
Err(_) => (0, 0),
@@ -180,7 +180,7 @@ impl std::fmt::Debug for FilePicker {
f.debug_struct("FilePicker")
.field("base_path", &self.base_path)
.field("git_workdir", &self.git_workdir)
.finish()
.finish_non_exhaustive()
}
}
@@ -195,7 +195,7 @@ impl FilePicker {
let git_workdir = Repository::discover(&path)
.ok()
.and_then(|repo| repo.workdir().map(|p| p.to_path_buf()));
.and_then(|repo| repo.workdir().map(Path::to_path_buf));
if let Some(ref git_dir) = git_workdir {
debug!("Git repository found at: {}", git_dir.display());
@@ -230,8 +230,8 @@ impl FilePicker {
query: &str,
max_results: usize,
max_threads: usize,
current_file: Option<String>,
) -> Result<SearchResult, Error> {
current_file: Option<&String>,
) -> SearchResult {
let max_threads = max_threads.max(1); // Ensure at least 1 to avoid neo_frizbee division by zero
debug!(
@@ -249,7 +249,7 @@ impl FilePicker {
query,
max_typos,
max_threads,
current_file: current_file.as_deref(),
current_file,
};
let scored_indices = match_and_score_files(&sync_data.files, &context);
@@ -280,12 +280,12 @@ impl FilePicker {
);
debug!("Total search time: {:?}", time.elapsed());
Ok(SearchResult {
SearchResult {
items,
scores,
total_matched,
total_files,
})
}
}
pub fn get_cached_files(&self) -> Vec<FileItem> {
@@ -302,9 +302,9 @@ impl FilePicker {
}
}
pub fn refresh_git_status(&self) -> Result<Vec<FileItem>, Error> {
pub fn refresh_git_status(&self) -> Vec<FileItem> {
let sync_data: &Arc<RwLock<FileSync>> = &self.sync_data;
let git_workdir: &Option<PathBuf> = &self.git_workdir;
let git_workdir = self.git_workdir.as_deref();
let new_git_status_cache = GitStatusCache::read_git_status(git_workdir);
if let Ok(mut sync_data_write) = sync_data.write() {
@@ -317,9 +317,9 @@ impl FilePicker {
file.update_frecency_scores();
}
};
}
Ok(self.get_cached_files())
self.get_cached_files()
}
pub fn trigger_rescan(&self) -> Result<(), Error> {
@@ -338,7 +338,7 @@ impl FilePicker {
thread::spawn(move || {
debug!("Background scan thread started");
if let Ok((files, git_cache)) = scan_filesystem(&base_path, &git_workdir) {
if let Ok((files, git_cache)) = scan_filesystem(&base_path, git_workdir.as_ref()) {
info!("Filesystem scan completed: found {} files", files.len());
if let Ok(mut data) = sync_data.write() {
data.update_files(files, git_cache);
@@ -364,6 +364,7 @@ impl FilePicker {
}
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct ScanProgress {
pub total_files: usize,
@@ -382,7 +383,7 @@ fn spawn_background_watcher(
scan_signal.store(true, Ordering::Relaxed);
info!("starting background watcher thread");
match scan_filesystem(&base_path, &git_workdir) {
match scan_filesystem(&base_path, git_workdir.as_ref()) {
Ok((files, git_cache)) => {
info!(
"Initial parallel filesystem scan completed: found {} files",
@@ -445,9 +446,7 @@ fn handle_debounced_events(
.paths
.iter()
.filter_map(|path| {
let Some(relative_path) = pathdiff::diff_paths(path, base_path) else {
return None;
};
let relative_path = pathdiff::diff_paths(path, base_path)?;
let Ok(sync_read) = sync_data.read() else {
return None;
@@ -461,7 +460,7 @@ fn handle_debounced_events(
match event.event.kind {
EventKind::Create(_) => {
if should_add_new_file(path, git_workdir) {
if should_add_new_file(path, git_workdir.as_ref()) {
Some(path.clone())
} else {
None
@@ -479,7 +478,7 @@ fn handle_debounced_events(
debug!(?event, "File watcher event");
match event.event.kind {
EventKind::Create(_) => {
handle_create_events(&relevant_paths, sync_data, base_path, git_workdir);
handle_create_events(&relevant_paths, sync_data, base_path, git_workdir.as_ref());
affected_paths.extend(relevant_paths);
}
EventKind::Modify(_) => {
@@ -499,7 +498,7 @@ fn handle_debounced_events(
}
}
fn should_add_new_file(path: &Path, git_workdir: &Option<PathBuf>) -> bool {
fn should_add_new_file(path: &Path, git_workdir: Option<&PathBuf>) -> bool {
if is_git_file(path) {
return false;
}
@@ -523,24 +522,21 @@ fn handle_create_events(
paths: &[PathBuf],
sync_data: &Arc<RwLock<FileSync>>,
base_path: &Path,
git_workdir: &Option<PathBuf>,
git_workdir: Option<&PathBuf>,
) {
let repo = git_workdir.as_ref().and_then(|p| Repository::open(p).ok());
if let Ok(mut sync_write) = sync_data.write() {
for path in paths {
if repo
.as_ref()
.is_some_and(|repo| repo.is_path_ignored(&path).unwrap_or(false))
.is_some_and(|repo| repo.is_path_ignored(path).unwrap_or(false))
{
debug!("Ignoring file {} due to gitignore rules", path.display());
continue;
}
// we will update the path for every
let mut file_item = FileItem::new(path.to_path_buf(), base_path, None);
let mut file_item = FileItem::new(path.clone(), base_path, None);
file_item.update_frecency_scores();
sync_write.insert_file_sorted(file_item);
}
}
@@ -563,80 +559,85 @@ fn remove_paths_from_index(
fn scan_filesystem(
base_path: &Path,
git_workdir: &Option<PathBuf>,
git_workdir: Option<&PathBuf>,
) -> Result<(Vec<FileItem>, Option<GitStatusCache>), Error> {
let scan_start = std::time::Instant::now();
let git_workdir = git_workdir.map(|p| p.as_path());
info!("SCAN: Starting parallel filesystem scan and git status");
let git_handle = GitStatusCache::read_git_status_parallel(git_workdir.clone());
// run separate thread for git status because it effectively does another separate file
// traversal which could be pretty slow on large repos (in general 300-500ms)
thread::scope(|s| {
let git_handle = s.spawn(|| GitStatusCache::read_git_status(git_workdir));
let walker = WalkBuilder::new(base_path)
.hidden(false)
.git_ignore(true)
.git_exclude(true)
.git_global(true)
.ignore(true)
.follow_links(false)
.sort_by_file_name(std::cmp::Ord::cmp)
.build_parallel();
let walker = WalkBuilder::new(base_path)
.hidden(false)
.git_ignore(true)
.git_exclude(true)
.git_global(true)
.ignore(true)
.follow_links(false)
.sort_by_file_name(std::cmp::Ord::cmp)
.build_parallel();
let walker_start = std::time::Instant::now();
info!("SCAN: Starting file walker");
let walker_start = std::time::Instant::now();
info!("SCAN: Starting file walker");
let files = Arc::new(std::sync::Mutex::new(Vec::new()));
walker.run(|| {
let files = Arc::clone(&files);
let base_path = base_path.to_path_buf();
let files = Arc::new(std::sync::Mutex::new(Vec::new()));
walker.run(|| {
let files = Arc::clone(&files);
let base_path = base_path.to_path_buf();
Box::new(move |result| {
if let Ok(entry) = result {
if let Some(file_type) = entry.file_type() {
if file_type.is_file() {
let path = entry.path();
Box::new(move |result| {
if let Ok(entry) = result {
if let Some(file_type) = entry.file_type() {
if file_type.is_file() {
let path = entry.path();
if is_git_file(path) {
return WalkState::Continue;
}
if is_git_file(path) {
return WalkState::Continue;
}
let file_item = FileItem::new(
path.to_path_buf(),
&base_path,
None, // Git status will be added after join
);
let file_item = FileItem::new(
path.to_path_buf(),
&base_path,
None, // Git status will be added after join
);
if let Ok(mut files_vec) = files.lock() {
files_vec.push(file_item);
if let Ok(mut files_vec) = files.lock() {
files_vec.push(file_item);
}
}
}
}
}
WalkState::Continue
})
});
let mut files = Arc::try_unwrap(files).unwrap().into_inner().unwrap();
let walker_time = walker_start.elapsed();
info!("SCAN: File walking completed in {:?}", walker_time);
let git_cache = git_handle
.join()
.map_err(|_| Error::InvalidPath("Git status thread panicked".to_string()))?;
if let Some(git_cache) = &git_cache {
files.par_iter_mut().for_each(|file| {
file.git_status = git_cache.lookup_status(&file.path);
file.update_frecency_scores();
WalkState::Continue
})
});
}
let total_time = scan_start.elapsed();
info!(
"SCAN: Total scan time {:?} for {} files",
total_time,
files.len()
);
let mut files = Arc::try_unwrap(files).unwrap().into_inner().unwrap();
let walker_time = walker_start.elapsed();
info!("SCAN: File walking completed in {:?}", walker_time);
Ok((files, git_cache))
let git_cache = git_handle
.join()
.map_err(|_| Error::InvalidPath("Git status thread panicked".to_string()))?;
if let Some(git_cache) = &git_cache {
files.par_iter_mut().for_each(|file| {
file.git_status = git_cache.lookup_status(&file.path);
file.update_frecency_scores();
});
}
let total_time = scan_start.elapsed();
info!(
"SCAN: Total scan time {:?} for {} files",
total_time,
files.len()
);
Ok((files, git_cache))
})
}
fn update_git_status_for_paths(
@@ -688,9 +689,7 @@ fn update_git_status_for_paths(
#[inline]
fn is_git_file(path: &Path) -> bool {
path.to_str()
.map(|path| path.contains("/.git/"))
.unwrap_or(false)
path.to_str().is_some_and(|path| path.contains("/.git/"))
}
impl Drop for FilePicker {
+4 -1
View File
@@ -1,6 +1,9 @@
use crate::error::Error;
use crate::file_key::FileKey;
use heed::{types::*, EnvFlags};
use heed::{
types::{Bytes, SerdeBincode},
EnvFlags,
};
use heed::{Database, Env, EnvOpenOptions};
use std::collections::VecDeque;
use std::fs;
+1 -15
View File
@@ -1,6 +1,5 @@
use git2::{Repository, Status, StatusOptions};
use std::path::{Path, PathBuf};
use std::thread::{self, JoinHandle};
use tracing::{error, info};
#[derive(Debug, Clone)]
@@ -10,14 +9,6 @@ pub struct GitStatusCache {
}
impl GitStatusCache {
#[allow(dead_code)]
fn new() -> Self {
Self {
paths: Vec::new(),
statuses: Vec::new(),
}
}
fn from_git_entries(mut entries: Vec<(PathBuf, Status)>) -> Self {
entries.sort_by(|a, b| a.0.cmp(&b.0));
@@ -35,7 +26,7 @@ impl GitStatusCache {
}
}
pub fn read_git_status(git_workdir: &Option<PathBuf>) -> Option<Self> {
pub fn read_git_status(git_workdir: Option<&Path>) -> Option<Self> {
let git_start = std::time::Instant::now();
info!("GIT: Starting git status read");
@@ -72,14 +63,9 @@ impl GitStatusCache {
Some(Self::from_git_entries(entries))
}
pub fn read_git_status_parallel(git_workdir: Option<PathBuf>) -> JoinHandle<Option<Self>> {
thread::spawn(move || Self::read_git_status(&git_workdir))
}
}
#[inline]
#[allow(dead_code)]
pub fn is_modified_status(status: Status) -> bool {
status.intersects(
Status::WT_MODIFIED
+6 -5
View File
@@ -51,9 +51,10 @@ pub fn scan_files(_: &Lua, _: ()) -> LuaResult<()> {
let picker = file_picker
.as_ref()
.ok_or_else(|| Error::InvalidPath("File picker not initialized".to_string()))?;
let result = picker.trigger_rescan()?;
picker.trigger_rescan()?;
::tracing::info!("scan_files trigger_rescan completed");
Ok(result)
Ok(())
}
pub fn get_cached_files(_: &Lua, _: ()) -> LuaResult<Vec<FileItem>> {
@@ -75,8 +76,7 @@ pub fn fuzzy_search_files(
.as_ref()
.ok_or_else(|| Error::InvalidPath("File picker not initialized".to_string()))?;
let results = picker.fuzzy_search(&query, max_results, max_threads, current_file)?;
let results = picker.fuzzy_search(&query, max_results, max_threads, current_file.as_ref());
Ok(results)
}
@@ -116,7 +116,8 @@ pub fn refresh_git_status(_: &Lua, _: ()) -> LuaResult<Vec<FileItem>> {
let picker = file_picker
.as_ref()
.ok_or_else(|| Error::InvalidPath("File picker not initialized".to_string()))?;
Ok(picker.refresh_git_status()?)
Ok(picker.refresh_git_status())
}
pub fn stop_background_monitor(_: &Lua, _: ()) -> LuaResult<bool> {
+8 -11
View File
@@ -1,4 +1,4 @@
pub fn calculate_distance_penalty(current_file: &Option<String>, candidate_path: &str) -> i32 {
pub fn calculate_distance_penalty(current_file: Option<&str>, candidate_path: &str) -> i32 {
let Some(ref current_path) = current_file else {
return 0; // No penalty if no current file
};
@@ -46,27 +46,24 @@ mod tests {
use super::*;
#[test]
fn test_calculate_distance_penalty() {
assert_eq!(calculate_distance_penalty(&None, "/path/to/file.txt"), 0);
assert_eq!(calculate_distance_penalty(None, "/path/to/file.txt"), 0);
assert_eq!(
calculate_distance_penalty(
&Some("/path/to/current/file.txt".to_string()),
Some("/path/to/current/file.txt"),
"/path/to/current/other.txt"
),
0
);
assert_eq!(
calculate_distance_penalty(
&Some("/path/to/current/file.txt".to_string()),
"/path/to/file.txt"
),
calculate_distance_penalty(Some("/path/to/current/file.txt"), "/path/to/file.txt"),
-2
);
assert_eq!(
calculate_distance_penalty(
&Some("/path/to/current/file.txt".to_string()),
Some("/path/to/current/file.txt"),
"/path/to/other/file.txt"
),
-4
@@ -74,19 +71,19 @@ mod tests {
assert_eq!(
calculate_distance_penalty(
&Some("/path/to/current/file.txt".to_string()),
Some("/path/to/current/file.txt"),
"/path/to/another/dir/file.txt"
),
-6
);
assert_eq!(
calculate_distance_penalty(&Some("/a/b/c/d/file.txt".to_string()), "/x/y/z/w/file.txt"),
calculate_distance_penalty(Some("/a/b/c/d/file.txt"), "/x/y/z/w/file.txt"),
-16
);
assert_eq!(
calculate_distance_penalty(&Some("/file1.txt".to_string()), "/file2.txt"),
calculate_distance_penalty(Some("/file1.txt"), "/file2.txt"),
0
);
}
+30 -22
View File
@@ -18,7 +18,6 @@ pub fn match_and_score_files(files: &[FileItem], context: &ScoringContext) -> Ve
prefilter: true,
max_typos: Some(context.max_typos),
sort: false,
..Default::default()
};
let haystack: Vec<&str> = files.iter().map(|f| f.relative_path.as_str()).collect();
@@ -66,7 +65,7 @@ pub fn match_and_score_files(files: &[FileItem], context: &ScoringContext) -> Ve
let base_score = neo_frizbee_match.score as i32;
let frecency_boost = base_score.saturating_mul(file.total_frecency_score as i32) / 100;
let distance_penalty = calculate_distance_penalty(
&context.current_file.map(|s| s.to_string()),
context.current_file.map(|s| s.as_str()),
&file.relative_path,
);
@@ -89,10 +88,11 @@ pub fn match_and_score_files(files: &[FileItem], context: &ScoringContext) -> Ve
base_score / 5 * 2 // 40% bonus for exact filename match
}
Some(_) => base_score / 5, // 20% bonus for fuzzy filename match
// if the file is special directory give it an extra bonus
None if is_special_entry_point_file(&file.file_name) => {
// 18% bonus special filename just as much as exact path
// but a little bit less to give preference to the actual file if present
has_special_filename_bonus = true;
base_score / 5
base_score * 18 / 100
}
None => 0,
};
@@ -132,18 +132,26 @@ pub fn match_and_score_files(files: &[FileItem], context: &ScoringContext) -> Ve
/// Check if a filename is a special entry point file that deserves bonus scoring
/// These are typically files that serve as module exports or entry points
fn is_special_entry_point_file(filename: &str) -> bool {
match filename {
"mod.rs" | "lib.rs" | "main.rs" => true,
"index.js" | "index.jsx" | "index.ts" | "index.tsx" => true,
"index.mjs" | "index.cjs" => true,
"index.vue" => true,
"__init__.py" | "__main__.py" => true,
"main.go" => true,
"main.c" => true,
"index.php" => true,
"main.rb" | "index.rb" => true,
_ => false,
}
matches!(
filename,
"mod.rs"
| "lib.rs"
| "main.rs"
| "index.js"
| "index.jsx"
| "index.ts"
| "index.tsx"
| "index.mjs"
| "index.cjs"
| "index.vue"
| "__init__.py"
| "__main__.py"
| "main.go"
| "main.c"
| "index.php"
| "main.rb"
| "index.rb"
)
}
fn score_all_by_frecency(files: &[FileItem], context: &ScoringContext) -> Vec<(usize, Score)> {
@@ -155,11 +163,14 @@ fn score_all_by_frecency(files: &[FileItem], context: &ScoringContext) -> Vec<(u
+ (file.modification_frecency_score as i32).saturating_mul(4);
let distance_penalty = calculate_distance_penalty(
&context.current_file.map(|s| s.to_string()),
context.current_file.map(|x| x.as_str()),
&file.relative_path,
);
let total = total_frecency_score.saturating_add(distance_penalty);
let total = total_frecency_score
.saturating_add(distance_penalty)
.saturating_add(calculate_file_bonus(file, context));
let score = Score {
total,
base_score: 0,
@@ -176,14 +187,11 @@ fn score_all_by_frecency(files: &[FileItem], context: &ScoringContext) -> Vec<(u
}
#[inline]
#[allow(dead_code)]
fn calculate_file_bonus(file: &FileItem, context: &ScoringContext) -> i32 {
let mut bonus = 0i32;
if let Some(current) = context.current_file {
let is_current = file.relative_path == current || file.relative_path == current;
if is_current {
if file.relative_path == *current {
bonus -= match file.git_status {
Some(status) if is_modified_status(status) => 150,
_ => 300,
+1 -1
View File
@@ -33,7 +33,7 @@ pub struct Score {
#[derive(Debug, Clone)]
pub struct ScoringContext<'a> {
pub query: &'a str,
pub current_file: Option<&'a str>,
pub current_file: Option<&'a String>,
pub max_typos: u16,
pub max_threads: usize,
}
+16 -15
View File
@@ -1,3 +1,6 @@
#![allow(clippy::all)]
#![allow(dead_code)]
#[path = "../../lua/fff/rust/error.rs"]
mod error;
#[path = "../../lua/fff/rust/file_key.rs"]
@@ -22,7 +25,6 @@ use std::io::{self, Write};
use std::sync::{LazyLock, RwLock};
use std::thread;
use std::time::Duration;
use types::FileItem;
use crate::git::format_git_status;
@@ -47,7 +49,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let initial_files = picker.get_cached_files();
println!("Initial file count: {}", initial_files.len());
if initial_files.len() > 0 {
if !initial_files.is_empty() {
println!("Sample files:");
for (i, file) in initial_files.iter().take(5).enumerate() {
println!(
@@ -135,24 +137,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
println!(" 🔄 Testing git status refresh...");
if let Ok(refreshed_files) = picker.refresh_git_status() {
let mut new_git_stats = std::collections::HashMap::new();
for file in &refreshed_files {
let status = format_git_status(file.git_status);
*new_git_stats.entry(status).or_insert(0) += 1;
}
if new_git_stats != git_stats_copy {
print!(" ✨ Git status changed after refresh: ");
for (status, count) in &new_git_stats {
print!("{}:{} ", status, count);
}
println!();
let refreshed_files = picker.refresh_git_status();
let mut new_git_stats = std::collections::HashMap::new();
for file in &refreshed_files {
let status = format_git_status(file.git_status);
*new_git_stats.entry(status).or_insert(0) += 1;
}
if new_git_stats != git_stats_copy {
print!(" ✨ Git status changed after refresh: ");
for (status, count) in &new_git_stats {
print!("{}:{} ", status, count);
}
println!();
}
}
if iteration % 40 == 0 {
let search_results = picker.fuzzy_search("rs", 5, 2, None).unwrap_or_default();
let search_results = picker.fuzzy_search("rs", 5, 2, None);
let timestamp = chrono::Local::now().format("%H:%M:%S");
println!(
"🔍 [{}] Search test 'rs': {} matches",