Reduce path storage in the file-search index (#55351)

## What changed

Reuse the matcher column for ASCII paths instead of storing a separate full path for every indexed entry. Store the selected search root and entry type alongside each entry so snapshots can build results without resolving the root again.

Retain the original relative path for Unicode entries because matcher columns keep only one code point per grapheme.

GitOrigin-RevId: 1e931482c9a656970e2eeaaade02f19063464f12
This commit is contained in:
Charlie Marsh
2026-10-19 19:10:58 +00:00
committed by copyberry
parent 941b32bf57
commit aeec726de8
3 changed files with 49 additions and 22 deletions
+29 -13
View File
@@ -72,9 +72,19 @@ pub enum MatchType {
Directory,
}
/// Carries the entry type observed by the walker so matched paths are not restatted.
struct IndexedEntry {
full_path: Arc<str>,
/// Carries the selected root and entry type without duplicating ASCII matcher paths.
enum IndexedEntry {
Ascii {
root_idx: usize,
match_type: MatchType,
},
Unicode(Box<UnicodeEntry>),
}
// Unicode matcher columns retain only one codepoint per grapheme.
struct UnicodeEntry {
root_idx: usize,
relative_path: Box<str>,
match_type: MatchType,
}
@@ -470,23 +480,29 @@ fn walker_worker(
Err(_) => return ignore::WalkState::Continue,
};
let path = entry.path();
let Some(full_path) = path.to_str() else {
if path.to_str().is_none() {
return ignore::WalkState::Continue;
};
if let Some((_, relative_path)) = get_file_path(path, &search_directories) {
}
if let Some((root_idx, relative_path)) = get_file_path(path, &search_directories) {
let match_type = match entry.file_type() {
Some(file_type) if file_type.is_dir() => MatchType::Directory,
_ => MatchType::File,
};
injector.push(
IndexedEntry {
full_path: Arc::from(full_path),
let column = Utf32String::from(relative_path);
let indexed_entry = match &column {
Utf32String::Ascii(_) => IndexedEntry::Ascii {
root_idx,
match_type,
},
|_, cols| {
cols[0] = Utf32String::from(relative_path);
},
);
Utf32String::Unicode(_) => IndexedEntry::Unicode(Box::new(UnicodeEntry {
root_idx,
relative_path: Box::from(relative_path),
match_type,
})),
};
injector.push(indexed_entry, |_, cols| {
cols[0] = column;
});
}
n += 1;
if n >= CHECK_INTERVAL {
+2 -3
View File
@@ -18,10 +18,9 @@ fn query_update_does_not_relabel_pending_matches() {
);
let injector = nucleo.injector();
for (relative, match_type) in [("", MatchType::Directory), ("beta.txt", MatchType::File)] {
let full_path = root.path().join(relative);
injector.push(
IndexedEntry {
full_path: Arc::from(full_path.to_str().unwrap()),
IndexedEntry::Ascii {
root_idx: 0,
match_type,
},
|_, columns| columns[0] = Utf32String::from(relative),
+18 -6
View File
@@ -4,10 +4,9 @@ use crate::FileMatch;
use crate::FileSearchSnapshot;
use crate::IndexedEntry;
use crate::SessionInner;
use crate::get_file_path;
use nucleo::Matcher;
use nucleo::Nucleo;
use std::path::Path;
use nucleo::Utf32String;
use std::path::PathBuf;
pub(super) fn for_query(
@@ -31,9 +30,22 @@ pub(super) fn for_query(
.take(limit)
.filter_map(|match_| {
let item = snapshot.get_item(match_.idx)?;
let full_path = item.data.full_path.as_ref();
let (root_idx, relative_path) =
get_file_path(Path::new(full_path), &inner.search_directories)?;
let (root_idx, relative_path, match_type) = match item.data {
IndexedEntry::Ascii {
root_idx,
match_type,
} => {
let Utf32String::Ascii(path) = &item.matcher_columns[0] else {
return None;
};
(*root_idx, path.as_ref(), *match_type)
}
IndexedEntry::Unicode(entry) => (
entry.root_idx,
entry.relative_path.as_ref(),
entry.match_type,
),
};
let indices = if let Some(indices_matcher) = indices_matcher.as_mut() {
let mut indices = Vec::<u32>::new();
let haystack = item.matcher_columns[0].slice(..);
@@ -47,7 +59,7 @@ pub(super) fn for_query(
Some(FileMatch {
score: match_.score,
path: PathBuf::from(relative_path),
match_type: item.data.match_type,
match_type,
root: inner.search_directories[root_idx].clone(),
indices,
})