diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9cdb6700675186c27bb509f8b937565d4cc79172..97bdfd1df1bb65431767d6f2c7181996758c518c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -18,11 +18,12 @@ use gpui::{ action, color::Color, elements::*, + executor, fonts::TextStyle, geometry::vector::{vec2f, Vector2F}, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, - MutableAppContext, RenderContext, View, ViewContext, WeakModelHandle, WeakViewHandle, + MutableAppContext, RenderContext, Task, View, ViewContext, WeakModelHandle, WeakViewHandle, }; use items::BufferItemHandle; use itertools::Itertools as _; @@ -52,7 +53,7 @@ use std::{ pub use sum_tree::Bias; use text::rope::TextDimension; use theme::{DiagnosticStyle, EditorStyle}; -use util::{post_inc, ResultExt}; +use util::{post_inc, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -396,6 +397,7 @@ pub struct Editor { highlighted_ranges: BTreeMap>)>, nav_history: Option, completion_state: Option, + completions_task: Option>>, } pub struct EditorSnapshot { @@ -432,11 +434,54 @@ struct BracketPairState { struct CompletionState { initial_position: Anchor, completions: Arc<[Completion]>, + match_candidates: Vec, matches: Arc<[StringMatch]>, selected_item: usize, list: UniformListState, } +impl CompletionState { + pub async fn filter(&mut self, query: Option<&str>, executor: Arc) { + let mut matches = if let Some(query) = query { + fuzzy::match_strings( + &self.match_candidates, + query, + false, + 100, + &Default::default(), + executor, + ) + .await + } else { + self.match_candidates + .iter() + .enumerate() + .map(|(candidate_id, candidate)| StringMatch { + candidate_id, + score: Default::default(), + positions: Default::default(), + string: candidate.string.clone(), + }) + .collect() + }; + matches.sort_unstable_by_key(|mat| { + ( + Reverse(OrderedFloat(mat.score)), + self.completions[mat.candidate_id].sort_key(), + ) + }); + + for mat in &mut matches { + let filter_start = self.completions[mat.candidate_id].filter_range().start; + for position in &mut mat.positions { + *position += filter_start; + } + } + + self.matches = matches.into(); + } +} + #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, @@ -546,6 +591,7 @@ impl Editor { highlighted_ranges: Default::default(), nav_history: None, completion_state: None, + completions_task: None, }; let selection = Selection { id: post_inc(&mut this.next_selection_id), @@ -1101,8 +1147,7 @@ impl Editor { } pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - if self.completion_state.take().is_some() { - cx.notify(); + if self.hide_completions(cx).is_some() { return; } @@ -1394,8 +1439,7 @@ impl Editor { { self.show_completions(&ShowCompletions, cx); } else { - self.completion_state.take(); - cx.notify(); + self.hide_completions(cx); } } } @@ -1526,6 +1570,20 @@ impl Editor { } } + fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option { + let offset = position.to_offset(buffer); + let (word_range, kind) = buffer.surrounding_word(offset); + if offset > word_range.start && kind == Some(CharKind::Word) { + Some( + buffer + .text_for_range(word_range.start..offset) + .collect::(), + ) + } else { + None + } + } + fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { let position = if let Some(selection) = self.newest_anchor_selection() { selection.head() @@ -1533,97 +1591,62 @@ impl Editor { return; }; - let query = { - let buffer = self.buffer.read(cx).read(cx); - let offset = position.to_offset(&buffer); - let (word_range, kind) = buffer.surrounding_word(offset); - if offset > word_range.start && kind == Some(CharKind::Word) { - Some( - buffer - .text_for_range(word_range.start..offset) - .collect::(), - ) - } else { - None - } - }; - + let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone()); let completions = self .buffer .update(cx, |buffer, cx| buffer.completions(position.clone(), cx)); - cx.spawn_weak(|this, mut cx| async move { - let completions = completions.await?; - let candidates = completions - .iter() - .enumerate() - .map(|(id, completion)| { - StringMatchCandidate::new( - id, - completion.label()[completion.filter_range()].into(), - ) - }) - .collect::>(); - let mut matches = if let Some(query) = query.as_ref() { - fuzzy::match_strings( - &candidates, - query, - false, - 100, - &Default::default(), - cx.background(), - ) - .await - } else { - candidates - .into_iter() - .enumerate() - .map(|(candidate_id, candidate)| StringMatch { - candidate_id, - score: Default::default(), - positions: Default::default(), - string: candidate.string, - }) - .collect() - }; - matches.sort_unstable_by_key(|mat| { - ( - Reverse(OrderedFloat(mat.score)), - completions[mat.candidate_id].sort_key(), - ) - }); + self.completions_task = Some(cx.spawn_weak(|this, mut cx| { + async move { + let completions = completions.await?; - for mat in &mut matches { - let filter_start = completions[mat.candidate_id].filter_range().start; - for position in &mut mat.positions { - *position += filter_start; - } - } + let mut completion_state = CompletionState { + initial_position: position, + match_candidates: completions + .iter() + .enumerate() + .map(|(id, completion)| { + StringMatchCandidate::new( + id, + completion.label()[completion.filter_range()].into(), + ) + }) + .collect(), + completions: completions.into(), + matches: Vec::new().into(), + selected_item: 0, + list: Default::default(), + }; - if let Some(this) = cx.read(|cx| this.upgrade(cx)) { - this.update(&mut cx, |this, cx| { - if matches.is_empty() { - this.completion_state.take(); - } else if this.focused { - this.completion_state = Some(CompletionState { - initial_position: position, - completions: completions.into(), - matches: matches.into(), - selected_item: 0, - list: Default::default(), - }); - } + completion_state + .filter(query.as_deref(), cx.background()) + .await; - cx.notify(); - }); + if let Some(this) = cx.read(|cx| this.upgrade(cx)) { + this.update(&mut cx, |this, cx| { + if completion_state.matches.is_empty() { + this.hide_completions(cx); + } else if this.focused { + this.completion_state = Some(completion_state); + } + + cx.notify(); + }); + } + Ok::<_, anyhow::Error>(()) } - Ok::<_, anyhow::Error>(()) - }) - .detach_and_log_err(cx); + .log_err() + })); + } + + fn hide_completions(&mut self, cx: &mut ViewContext) -> Option { + cx.notify(); + self.completions_task.take(); + self.completion_state.take() } fn confirm_completion(&mut self, _: &ConfirmCompletion, cx: &mut ViewContext) { - if let Some(completion_state) = self.completion_state.take() { + if let Some(completion_state) = self.hide_completions(cx) { if let Some(completion) = completion_state .completions .get(completion_state.selected_item) @@ -3686,17 +3709,18 @@ impl Editor { ); if let Some((completion_state, cursor_position)) = - self.completion_state.as_ref().zip(new_cursor_position) + self.completion_state.as_mut().zip(new_cursor_position) { let cursor_position = cursor_position.to_offset(&buffer); let (word_range, kind) = buffer.surrounding_word(completion_state.initial_position.clone()); if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) { + let query = Self::completion_query(&buffer, cursor_position); + smol::block_on(completion_state.filter(query.as_deref(), cx.background().clone())); self.show_completions(&ShowCompletions, cx); } else { - self.completion_state.take(); - cx.notify(); + self.hide_completions(cx); } } } @@ -4304,7 +4328,7 @@ impl View for Editor { self.show_local_cursors = false; self.buffer .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); - self.completion_state.take(); + self.hide_completions(cx); cx.emit(Event::Blurred); cx.notify(); } diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index e2b6d2f2adf36b477b11236019488e258e7b4fcd..03d16db1efc86ff8e80aff87e871feebdc258682 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -106,7 +106,7 @@ impl Anchor { } impl ToOffset for Anchor { - fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize { self.summary(snapshot) } }