logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
356
crates/fuzzy_nucleo/src/paths.rs
Normal file
356
crates/fuzzy_nucleo/src/paths.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use gpui::BackgroundExecutor;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{self, AtomicBool},
|
||||
},
|
||||
};
|
||||
use util::{paths::PathStyle, rel_path::RelPath};
|
||||
|
||||
use nucleo::Utf32Str;
|
||||
use nucleo::pattern::Pattern;
|
||||
|
||||
use fuzzy::CharBag;
|
||||
|
||||
use crate::matcher::{self, LENGTH_PENALTY};
|
||||
use crate::{Cancelled, Case, Query, case_penalty, count_case_mismatches, positions_from_sorted};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathMatchCandidate<'a> {
|
||||
pub is_dir: bool,
|
||||
pub path: &'a RelPath,
|
||||
pub char_bag: CharBag,
|
||||
}
|
||||
|
||||
impl<'a> PathMatchCandidate<'a> {
|
||||
/// Build a candidate whose prefilter bag covers both the worktree prefix and the path.
|
||||
/// Pass `None` when matching against paths that have no worktree prefix.
|
||||
pub fn new(path: &'a RelPath, is_dir: bool, path_prefix: Option<&RelPath>) -> Self {
|
||||
let mut char_bag = CharBag::default();
|
||||
if let Some(prefix) = path_prefix
|
||||
&& !prefix.is_empty()
|
||||
{
|
||||
char_bag.extend(prefix.as_unix_str().chars().map(|c| c.to_ascii_lowercase()));
|
||||
}
|
||||
char_bag.extend(path.as_unix_str().chars().map(|c| c.to_ascii_lowercase()));
|
||||
Self {
|
||||
is_dir,
|
||||
path,
|
||||
char_bag,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathMatch {
|
||||
pub score: f64,
|
||||
pub positions: Vec<usize>,
|
||||
pub worktree_id: usize,
|
||||
pub path: Arc<RelPath>,
|
||||
pub path_prefix: Arc<RelPath>,
|
||||
pub is_dir: bool,
|
||||
/// Number of steps removed from a shared parent with the relative path
|
||||
/// Used to order closer paths first in the search list
|
||||
pub distance_to_relative_ancestor: usize,
|
||||
}
|
||||
|
||||
pub trait PathMatchCandidateSet<'a>: Send + Sync {
|
||||
type Candidates: Iterator<Item = PathMatchCandidate<'a>>;
|
||||
fn id(&self) -> usize;
|
||||
fn len(&self) -> usize;
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
fn root_is_file(&self) -> bool;
|
||||
fn prefix(&self) -> Arc<RelPath>;
|
||||
fn candidates(&'a self, start: usize) -> Self::Candidates;
|
||||
fn path_style(&self) -> PathStyle;
|
||||
}
|
||||
|
||||
impl PartialEq for PathMatch {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cmp(other).is_eq()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for PathMatch {}
|
||||
|
||||
impl PartialOrd for PathMatch {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PathMatch {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.score
|
||||
.total_cmp(&other.score)
|
||||
.then_with(|| self.worktree_id.cmp(&other.worktree_id))
|
||||
.then_with(|| {
|
||||
other
|
||||
.distance_to_relative_ancestor
|
||||
.cmp(&self.distance_to_relative_ancestor)
|
||||
})
|
||||
.then_with(|| self.path.cmp(&other.path))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn distance_between_paths(path: &RelPath, relative_to: &RelPath) -> usize {
|
||||
let mut path_components = path.components();
|
||||
let mut relative_components = relative_to.components();
|
||||
|
||||
while path_components
|
||||
.next()
|
||||
.zip(relative_components.next())
|
||||
.map(|(path_component, relative_component)| path_component == relative_component)
|
||||
.unwrap_or_default()
|
||||
{}
|
||||
path_components.count() + relative_components.count() + 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_filename_match_bonus(
|
||||
candidate_buf: &str,
|
||||
pattern: &Pattern,
|
||||
matcher: &mut nucleo::Matcher,
|
||||
) -> f64 {
|
||||
let Some(filename) = std::path::Path::new(candidate_buf)
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.filter(|f| !f.is_empty())
|
||||
else {
|
||||
return 0.0;
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
let haystack = Utf32Str::new(filename, &mut buf);
|
||||
let score: u32 = pattern
|
||||
.atoms
|
||||
.iter()
|
||||
.filter_map(|atom| atom.score(haystack, matcher))
|
||||
.map(|s| s as u32)
|
||||
.sum();
|
||||
|
||||
score as f64 / filename.len().max(1) as f64
|
||||
}
|
||||
|
||||
fn path_match_helper<'a>(
|
||||
matcher: &mut nucleo::Matcher,
|
||||
query: &Query,
|
||||
candidates: impl Iterator<Item = PathMatchCandidate<'a>>,
|
||||
results: &mut Vec<PathMatch>,
|
||||
worktree_id: usize,
|
||||
path_prefix: &Arc<RelPath>,
|
||||
root_is_file: bool,
|
||||
relative_to: &Option<Arc<RelPath>>,
|
||||
path_style: PathStyle,
|
||||
cancel_flag: &AtomicBool,
|
||||
) -> Result<(), Cancelled> {
|
||||
let mut candidate_buf = if !path_prefix.is_empty() && !root_is_file {
|
||||
let mut s = path_prefix.display(path_style).to_string();
|
||||
s.push_str(path_style.primary_separator());
|
||||
s
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let path_prefix_len = candidate_buf.len();
|
||||
let mut buf = Vec::new();
|
||||
let mut matched_chars: Vec<u32> = Vec::new();
|
||||
let mut candidate_chars: Vec<char> = Vec::new();
|
||||
for candidate in candidates {
|
||||
buf.clear();
|
||||
matched_chars.clear();
|
||||
if cancel_flag.load(atomic::Ordering::Relaxed) {
|
||||
return Err(Cancelled);
|
||||
}
|
||||
|
||||
if !candidate.char_bag.is_superset(query.char_bag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
candidate_buf.truncate(path_prefix_len);
|
||||
if root_is_file {
|
||||
candidate_buf.push_str(path_prefix.as_unix_str());
|
||||
} else {
|
||||
candidate_buf.push_str(candidate.path.as_unix_str());
|
||||
}
|
||||
|
||||
let haystack = Utf32Str::new(&candidate_buf, &mut buf);
|
||||
|
||||
let Some(score) = query.pattern.indices(haystack, matcher, &mut matched_chars) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let case_mismatches = count_case_mismatches(
|
||||
query.query_chars.as_deref(),
|
||||
&matched_chars,
|
||||
&candidate_buf,
|
||||
&mut candidate_chars,
|
||||
);
|
||||
|
||||
matched_chars.sort_unstable();
|
||||
matched_chars.dedup();
|
||||
|
||||
let length_penalty = candidate_buf.len() as f64 * LENGTH_PENALTY;
|
||||
let filename_bonus = get_filename_match_bonus(&candidate_buf, &query.pattern, matcher);
|
||||
let positive = (score as f64 + filename_bonus) * case_penalty(case_mismatches);
|
||||
let adjusted_score = positive - length_penalty;
|
||||
let positions = positions_from_sorted(&candidate_buf, &matched_chars);
|
||||
|
||||
results.push(PathMatch {
|
||||
score: adjusted_score,
|
||||
positions,
|
||||
worktree_id,
|
||||
path: if root_is_file {
|
||||
Arc::clone(path_prefix)
|
||||
} else {
|
||||
candidate.path.into()
|
||||
},
|
||||
path_prefix: if root_is_file {
|
||||
RelPath::empty().into()
|
||||
} else {
|
||||
Arc::clone(path_prefix)
|
||||
},
|
||||
is_dir: candidate.is_dir,
|
||||
distance_to_relative_ancestor: relative_to.as_ref().map_or(usize::MAX, |relative_to| {
|
||||
distance_between_paths(candidate.path, relative_to.as_ref())
|
||||
}),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn match_fixed_path_set(
|
||||
candidates: Vec<PathMatchCandidate>,
|
||||
worktree_id: usize,
|
||||
worktree_root_name: Option<Arc<RelPath>>,
|
||||
query: &str,
|
||||
case: Case,
|
||||
max_results: usize,
|
||||
path_style: PathStyle,
|
||||
) -> Vec<PathMatch> {
|
||||
let Some(query) = Query::build(query, case) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut config = nucleo::Config::DEFAULT;
|
||||
config.set_match_paths();
|
||||
let mut matcher = matcher::get_matcher(config);
|
||||
|
||||
let root_is_file = worktree_root_name.is_some() && candidates.iter().all(|c| c.path.is_empty());
|
||||
|
||||
let path_prefix = worktree_root_name.unwrap_or_else(|| RelPath::empty().into());
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
path_match_helper(
|
||||
&mut matcher,
|
||||
&query,
|
||||
candidates.into_iter(),
|
||||
&mut results,
|
||||
worktree_id,
|
||||
&path_prefix,
|
||||
root_is_file,
|
||||
&None,
|
||||
path_style,
|
||||
&AtomicBool::new(false),
|
||||
)
|
||||
.ok();
|
||||
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
|
||||
matcher::return_matcher(matcher);
|
||||
results
|
||||
}
|
||||
|
||||
pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
candidate_sets: &'a [Set],
|
||||
query: &str,
|
||||
relative_to: &Option<Arc<RelPath>>,
|
||||
case: Case,
|
||||
max_results: usize,
|
||||
cancel_flag: &AtomicBool,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Vec<PathMatch> {
|
||||
let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
|
||||
if path_count == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let path_style = candidate_sets[0].path_style();
|
||||
|
||||
let query = if path_style.is_windows() {
|
||||
query.replace('\\', "/")
|
||||
} else {
|
||||
query.to_owned()
|
||||
};
|
||||
|
||||
let Some(query) = Query::build(&query, case) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let num_cpus = executor.num_cpus().min(path_count);
|
||||
let segment_size = path_count.div_ceil(num_cpus);
|
||||
let mut segment_results = (0..num_cpus)
|
||||
.map(|_| Vec::with_capacity(max_results))
|
||||
.collect::<Vec<_>>();
|
||||
let mut config = nucleo::Config::DEFAULT;
|
||||
config.set_match_paths();
|
||||
let mut matchers = matcher::get_matchers(num_cpus, config);
|
||||
executor
|
||||
.scoped(|scope| {
|
||||
for (segment_idx, (results, matcher)) in segment_results
|
||||
.iter_mut()
|
||||
.zip(matchers.iter_mut())
|
||||
.enumerate()
|
||||
{
|
||||
let query = &query;
|
||||
let relative_to = relative_to.clone();
|
||||
scope.spawn(async move {
|
||||
let segment_start = segment_idx * segment_size;
|
||||
let segment_end = segment_start + segment_size;
|
||||
|
||||
let mut tree_start = 0;
|
||||
for candidate_set in candidate_sets {
|
||||
let tree_end = tree_start + candidate_set.len();
|
||||
|
||||
if tree_start < segment_end && segment_start < tree_end {
|
||||
let start = tree_start.max(segment_start) - tree_start;
|
||||
let end = tree_end.min(segment_end) - tree_start;
|
||||
let candidates = candidate_set.candidates(start).take(end - start);
|
||||
|
||||
if path_match_helper(
|
||||
matcher,
|
||||
query,
|
||||
candidates,
|
||||
results,
|
||||
candidate_set.id(),
|
||||
&candidate_set.prefix(),
|
||||
candidate_set.root_is_file(),
|
||||
&relative_to,
|
||||
path_style,
|
||||
cancel_flag,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if tree_end >= segment_end {
|
||||
break;
|
||||
}
|
||||
tree_start = tree_end;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
matcher::return_matchers(matchers);
|
||||
if cancel_flag.load(atomic::Ordering::Acquire) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut results = segment_results.concat();
|
||||
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
|
||||
results
|
||||
}
|
||||
Reference in New Issue
Block a user