diff --git a/fsevent/src/lib.rs b/fsevent/src/lib.rs index c53be01686ccf9ee0240492320a3473b33f79658..69ea21cdb028b7c2d78a7a3959454cef1634f7a9 100644 --- a/fsevent/src/lib.rs +++ b/fsevent/src/lib.rs @@ -74,7 +74,7 @@ impl EventStream { Self::trampoline, &stream_context, cf_paths, - fs::kFSEventStreamEventIdSinceNow, + FSEventsGetCurrentEventId(), latency.as_secs_f64(), fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer @@ -82,6 +82,15 @@ impl EventStream { ); cf::CFRelease(cf_paths); + fs::FSEventStreamScheduleWithRunLoop( + stream, + cf::CFRunLoopGetCurrent(), + cf::kCFRunLoopDefaultMode, + ); + fs::FSEventStreamStart(stream); + fs::FSEventStreamFlushSync(stream); + fs::FSEventStreamStop(stream); + let state = Arc::new(Mutex::new(Lifecycle::New)); ( @@ -111,12 +120,8 @@ impl EventStream { } } fs::FSEventStreamScheduleWithRunLoop(self.stream, run_loop, cf::kCFRunLoopDefaultMode); - fs::FSEventStreamStart(self.stream); cf::CFRunLoopRun(); - - fs::FSEventStreamFlushSync(self.stream); - fs::FSEventStreamStop(self.stream); fs::FSEventStreamRelease(self.stream); } } @@ -133,11 +138,12 @@ impl EventStream { let event_paths = event_paths as *const *const ::std::os::raw::c_char; let e_ptr = event_flags as *mut u32; let i_ptr = event_ids as *mut u64; - let callback = (info as *mut Option) - .as_mut() - .unwrap() - .as_mut() - .unwrap(); + let callback_ptr = (info as *mut Option).as_mut().unwrap(); + let callback = if let Some(callback) = callback_ptr.as_mut() { + callback + } else { + return; + }; let paths = slice::from_raw_parts(event_paths, num); let flags = slice::from_raw_parts_mut(e_ptr, num); @@ -148,19 +154,25 @@ impl EventStream { let path_c_str = CStr::from_ptr(paths[p]); let path = PathBuf::from(OsStr::from_bytes(path_c_str.to_bytes())); if let Some(flag) = StreamFlags::from_bits(flags[p]) { - events.push(Event { - event_id: ids[p], - flags: flag, - path, - }); + if flag.contains(StreamFlags::HISTORY_DONE) { + events.clear(); + } else { + events.push(Event { + event_id: ids[p], + flags: flag, + path, + }); + } } else { debug_assert!(false, "unknown flag set for fs event: {}", flags[p]); } } - if !callback(events) { - fs::FSEventStreamStop(stream_ref); - cf::CFRunLoopStop(cf::CFRunLoopGetCurrent()); + if !events.is_empty() { + if !callback(events) { + fs::FSEventStreamStop(stream_ref); + cf::CFRunLoopStop(cf::CFRunLoopGetCurrent()); + } } } } @@ -285,6 +297,11 @@ impl std::fmt::Display for StreamFlags { } } +#[link(name = "CoreServices", kind = "framework")] +extern "C" { + pub fn FSEventsGetCurrentEventId() -> u64; +} + #[test] fn test_event_stream() { use std::{fs, sync::mpsc, time::Duration}; diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index c13198f7a65a25238d80da6520f0e870decf7a4b..77cf3bf7799520a13588b6f1aef1e7e4e5d8aaee 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -322,7 +322,7 @@ impl BufferView { }; if !add { - self.update_selections(Vec::new(), ctx); + self.update_selections(Vec::new(), false, ctx); } self.pending_selection = Some(selection); @@ -357,7 +357,7 @@ impl BufferView { let ix = self.selection_insertion_index(&selection.start, ctx.as_ref()); let mut selections = self.selections(ctx.as_ref()).to_vec(); selections.insert(ix, selection); - self.update_selections(selections, ctx); + self.update_selections(selections, false, ctx); } else { log::error!("end_selection dispatched with no pending selection"); } @@ -382,7 +382,7 @@ impl BufferView { goal_column: None, }); } - self.update_selections(selections, ctx); + self.update_selections(selections, false, ctx); Ok(()) } @@ -401,7 +401,7 @@ impl BufferView { goal_column: None, }); } - self.update_selections(selections, ctx); + self.update_selections(selections, false, ctx); Ok(()) } @@ -445,7 +445,7 @@ impl BufferView { .collect(); }); - self.update_selections(new_selections, ctx); + self.update_selections(new_selections, true, ctx); self.end_transaction(ctx); } @@ -482,8 +482,7 @@ impl BufferView { } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); self.insert(&String::new(), ctx); self.end_transaction(ctx); } @@ -517,8 +516,7 @@ impl BufferView { }); } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); self.insert(&String::new(), ctx); self.end_transaction(ctx); @@ -607,7 +605,7 @@ impl BufferView { }); }); } - self.update_selections(new_selections, ctx); + self.update_selections(new_selections, true, ctx); self.end_transaction(ctx); } else { self.insert(clipboard_text, ctx); @@ -647,8 +645,7 @@ impl BufferView { selection.goal_column = None; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext) { @@ -672,8 +669,7 @@ impl BufferView { selection.goal_column = None; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext) { @@ -698,8 +694,7 @@ impl BufferView { selection.goal_column = None; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext) { @@ -720,8 +715,7 @@ impl BufferView { selection.goal_column = None; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } pub fn move_up(&mut self, _: &(), ctx: &mut ViewContext) { @@ -748,8 +742,7 @@ impl BufferView { selection.reversed = false; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } } @@ -770,8 +763,7 @@ impl BufferView { selection.goal_column = goal_column; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } } @@ -799,8 +791,7 @@ impl BufferView { selection.reversed = false; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } } @@ -821,31 +812,10 @@ impl BufferView { selection.goal_column = goal_column; } } - self.update_selections(selections, ctx); - self.changed_selections(ctx); + self.update_selections(selections, true, ctx); } } - pub fn changed_selections(&mut self, ctx: &mut ViewContext) { - self.pause_cursor_blinking(ctx); - *self.autoscroll_requested.lock() = true; - ctx.notify(); - } - - pub fn first_selection(&self, app: &AppContext) -> Range { - self.selections(app) - .first() - .unwrap() - .display_range(self.display_map.read(app), app) - } - - pub fn last_selection(&self, app: &AppContext) -> Range { - self.selections(app) - .last() - .unwrap() - .display_range(self.display_map.read(app), app) - } - pub fn selections_in_range<'a>( &'a self, range: Range, @@ -894,7 +864,12 @@ impl BufferView { .unwrap() } - fn update_selections(&self, mut selections: Vec, ctx: &mut ViewContext) { + fn update_selections( + &mut self, + mut selections: Vec, + autoscroll: bool, + ctx: &mut ViewContext, + ) { // Merge overlapping selections. let buffer = self.buffer.read(ctx); let mut i = 1; @@ -922,6 +897,12 @@ impl BufferView { .update_selection_set(self.selection_set_id, selections, Some(ctx)) .unwrap() }); + self.pause_cursor_blinking(ctx); + + if autoscroll { + *self.autoscroll_requested.lock() = true; + ctx.notify(); + } } fn start_transaction(&self, ctx: &mut ViewContext) { diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index dcefdc7f8d780f931739333a2b194b4c120af59a..3add2d58af4ad03fccf2cf04ba1f96db961c5051 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -30,6 +30,8 @@ pub struct FileFinder { query_buffer: ViewHandle, search_count: usize, latest_search_id: usize, + latest_search_did_cancel: bool, + latest_search_query: String, matches: Vec, include_root_name: bool, selected: Option>, @@ -293,6 +295,8 @@ impl FileFinder { query_buffer, search_count: 0, latest_search_id: 0, + latest_search_did_cancel: false, + latest_search_query: String::new(), matches: Vec::new(), include_root_name: false, selected: None, @@ -395,10 +399,11 @@ impl FileFinder { false, false, 100, - cancel_flag, + cancel_flag.clone(), pool, ); - (search_id, include_root_name, matches) + let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); + (search_id, include_root_name, did_cancel, query, matches) }); ctx.spawn(task, Self::update_matches).detach(); @@ -406,12 +411,24 @@ impl FileFinder { fn update_matches( &mut self, - (search_id, include_root_name, matches): (usize, bool, Vec), + (search_id, include_root_name, did_cancel, query, matches): ( + usize, + bool, + bool, + String, + Vec, + ), ctx: &mut ViewContext, ) { if search_id >= self.latest_search_id { self.latest_search_id = search_id; - self.matches = matches; + if self.latest_search_did_cancel && query == self.latest_search_query { + util::extend_sorted(&mut self.matches, matches.into_iter(), 100, |a, b| b.cmp(a)); + } else { + self.matches = matches; + } + self.latest_search_query = query; + self.latest_search_did_cancel = did_cancel; self.include_root_name = include_root_name; self.list_state.scroll_to(self.selected_index()); ctx.notify(); @@ -432,9 +449,11 @@ mod tests { use super::*; use crate::{ editor, settings, + test::temp_tree, workspace::{Workspace, WorkspaceView}, }; use gpui::App; + use serde_json::json; use smol::fs; use tempdir::TempDir; @@ -508,4 +527,62 @@ mod tests { }); }); } + + #[test] + fn test_matching_cancellation() { + App::test_async((), |mut app| async move { + let tmp_dir = temp_tree(json!({ + "hello": "", + "goodbye": "", + "halogen-light": "", + "happiness": "", + "height": "", + "hi": "", + "hiccup": "", + })); + let settings = settings::channel(&app.font_cache()).unwrap().1; + let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx)); + app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) + .await; + let (_, finder) = + app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); + + let query = "hi".to_string(); + finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx)); + finder.condition(&app, |f, _| f.matches.len() == 5).await; + + finder.update(&mut app, |finder, ctx| { + let matches = finder.matches.clone(); + + // Simulate a search being cancelled after the time limit, + // returning only a subset of the matches that would have been found. + finder.spawn_search(query.clone(), ctx); + finder.update_matches( + ( + finder.latest_search_id, + true, + true, // did-cancel + query.clone(), + vec![matches[1].clone(), matches[3].clone()], + ), + ctx, + ); + + // Simulate another cancellation. + finder.spawn_search(query.clone(), ctx); + finder.update_matches( + ( + finder.latest_search_id, + true, + true, // did-cancel + query.clone(), + vec![matches[0].clone(), matches[2].clone(), matches[3].clone()], + ), + ctx, + ); + + assert_eq!(finder.matches, matches[0..4]) + }); + }); + } } diff --git a/zed/src/util.rs b/zed/src/util.rs index 70f2b1a6d6485445862042fe749ca6d69714b23d..09ce2f87e155c30c996f75f5b1a8a62127ff9c4f 100644 --- a/zed/src/util.rs +++ b/zed/src/util.rs @@ -7,6 +7,29 @@ pub fn post_inc(value: &mut usize) -> usize { prev } +/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and +/// enforcing a maximum length. Sort the items according to the given callback. Before calling this, +/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator. +pub fn extend_sorted(vec: &mut Vec, new_items: I, limit: usize, mut cmp: F) +where + I: IntoIterator, + F: FnMut(&T, &T) -> Ordering, +{ + let mut start_index = 0; + for new_item in new_items { + if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) { + let index = start_index + i; + if vec.len() < limit { + vec.insert(index, new_item); + } else if index < vec.len() { + vec.pop(); + vec.insert(index, new_item); + } + start_index = index; + } + } +} + pub fn find_insertion_index<'a, F, T, E>(slice: &'a [T], mut f: F) -> Result where F: FnMut(&'a T) -> Result, @@ -69,4 +92,18 @@ mod tests { Ok(1) ); } + + #[test] + fn test_extend_sorted() { + let mut vec = vec![]; + + extend_sorted(&mut vec, vec![21, 17, 13, 8, 1, 0], 5, |a, b| b.cmp(a)); + assert_eq!(vec, &[21, 17, 13, 8, 1]); + + extend_sorted(&mut vec, vec![101, 19, 17, 8, 2], 8, |a, b| b.cmp(a)); + assert_eq!(vec, &[101, 21, 19, 17, 13, 8, 2, 1]); + + extend_sorted(&mut vec, vec![1000, 19, 17, 9, 5], 8, |a, b| b.cmp(a)); + assert_eq!(vec, &[1000, 101, 21, 19, 17, 13, 9, 8]); + } } diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index f17b439d013c07868cab0c75f6ba1d30afb1a755..5da80e560cb1a7af607ff3f250322ecf6dd4f93c 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -142,7 +142,7 @@ impl Pane { pub fn activate_prev_item(&mut self, ctx: &mut ViewContext) { if self.active_item > 0 { self.active_item -= 1; - } else { + } else if self.items.len() > 0 { self.active_item = self.items.len() - 1; } self.focus_active_item(ctx); diff --git a/zed/src/worktree/fuzzy.rs b/zed/src/worktree/fuzzy.rs index 10a1cace3196de0a5f6392b0839efcd86ab5deb5..813147d929b0f4ebad3f0bc839861148f1bad4b1 100644 --- a/zed/src/worktree/fuzzy.rs +++ b/zed/src/worktree/fuzzy.rs @@ -1,8 +1,8 @@ use super::{char_bag::CharBag, EntryKind, Snapshot}; +use crate::util; use gpui::scoped_pool; use std::{ - cmp::{max, min, Ordering, Reverse}, - collections::BinaryHeap, + cmp::{max, min, Ordering}, path::Path, sync::atomic::{self, AtomicBool}, sync::Arc, @@ -36,13 +36,17 @@ impl Eq for PathMatch {} impl PartialOrd for PathMatch { fn partial_cmp(&self, other: &Self) -> Option { - self.score.partial_cmp(&other.score) + Some(self.cmp(other)) } } impl Ord for PathMatch { fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap_or(Ordering::Equal) + self.score + .partial_cmp(&other.score) + .unwrap_or(Ordering::Equal) + .then_with(|| self.tree_id.cmp(&other.tree_id)) + .then_with(|| Arc::as_ptr(&self.path).cmp(&Arc::as_ptr(&other.path))) } } @@ -74,7 +78,9 @@ where }; let segment_size = (path_count + cpus - 1) / cpus; - let mut segment_results = (0..cpus).map(|_| BinaryHeap::new()).collect::>(); + let mut segment_results = (0..cpus) + .map(|_| Vec::with_capacity(max_results)) + .collect::>(); pool.scoped(|scope| { for (segment_idx, results) in segment_results.iter_mut().enumerate() { @@ -145,13 +151,14 @@ where } }); - let mut results = segment_results - .into_iter() - .flatten() - .map(|r| r.0) - .collect::>(); - results.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap()); - results.truncate(max_results); + let mut results = Vec::new(); + for segment_result in segment_results { + if results.is_empty() { + results = segment_result; + } else { + util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a)); + } + } results } @@ -163,7 +170,7 @@ fn match_single_tree_paths<'a>( lowercase_query: &[char], query_chars: CharBag, smart_case: bool, - results: &mut BinaryHeap>, + results: &mut Vec, max_results: usize, min_score: &mut f64, match_positions: &mut Vec, @@ -234,14 +241,22 @@ fn match_single_tree_paths<'a>( ); if score > 0.0 { - results.push(Reverse(PathMatch { + let mat = PathMatch { tree_id: snapshot.id, path: candidate.path.clone(), score, positions: match_positions.clone(), - })); - if results.len() == max_results { - *min_score = results.peek().unwrap().0.score; + }; + if let Err(i) = results.binary_search_by(|m| mat.cmp(&m)) { + if results.len() < max_results { + results.insert(i, mat); + } else if i < results.len() { + results.pop(); + results.insert(i, mat); + } + if results.len() == max_results { + *min_score = results.last().unwrap().score; + } } } } @@ -560,7 +575,7 @@ mod tests { last_positions.resize(query.len(), 0); let cancel_flag = AtomicBool::new(false); - let mut results = BinaryHeap::new(); + let mut results = Vec::new(); match_single_tree_paths( &Snapshot { id: 0, @@ -588,15 +603,14 @@ mod tests { results .into_iter() - .rev() .map(|result| { ( paths .iter() .copied() - .find(|p| result.0.path.as_ref() == Path::new(p)) + .find(|p| result.path.as_ref() == Path::new(p)) .unwrap(), - result.0.positions, + result.positions, ) }) .collect()