Re-filter existing completions on selection update

We still request new completions, but this ensures results are up-to-date in the meantime.

Also: Cancel any pending completions task when we dismiss the completions dialog or start a new completions request.
Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2022-02-01 07:59:37 -07:00
parent b89a39bcb3
commit 497626ef2b
2 changed files with 115 additions and 91 deletions

View file

@ -18,11 +18,12 @@ use gpui::{
action, action,
color::Color, color::Color,
elements::*, elements::*,
executor,
fonts::TextStyle, fonts::TextStyle,
geometry::vector::{vec2f, Vector2F}, geometry::vector::{vec2f, Vector2F},
keymap::Binding, keymap::Binding,
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, 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 items::BufferItemHandle;
use itertools::Itertools as _; use itertools::Itertools as _;
@ -52,7 +53,7 @@ use std::{
pub use sum_tree::Bias; pub use sum_tree::Bias;
use text::rope::TextDimension; use text::rope::TextDimension;
use theme::{DiagnosticStyle, EditorStyle}; use theme::{DiagnosticStyle, EditorStyle};
use util::{post_inc, ResultExt}; use util::{post_inc, ResultExt, TryFutureExt};
use workspace::{ItemNavHistory, PathOpener, Workspace}; use workspace::{ItemNavHistory, PathOpener, Workspace};
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@ -396,6 +397,7 @@ pub struct Editor {
highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>, highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
nav_history: Option<ItemNavHistory>, nav_history: Option<ItemNavHistory>,
completion_state: Option<CompletionState>, completion_state: Option<CompletionState>,
completions_task: Option<Task<Option<()>>>,
} }
pub struct EditorSnapshot { pub struct EditorSnapshot {
@ -432,11 +434,54 @@ struct BracketPairState {
struct CompletionState { struct CompletionState {
initial_position: Anchor, initial_position: Anchor,
completions: Arc<[Completion<Anchor>]>, completions: Arc<[Completion<Anchor>]>,
match_candidates: Vec<StringMatchCandidate>,
matches: Arc<[StringMatch]>, matches: Arc<[StringMatch]>,
selected_item: usize, selected_item: usize,
list: UniformListState, list: UniformListState,
} }
impl CompletionState {
pub async fn filter(&mut self, query: Option<&str>, executor: Arc<executor::Background>) {
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)] #[derive(Debug)]
struct ActiveDiagnosticGroup { struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>, primary_range: Range<Anchor>,
@ -546,6 +591,7 @@ impl Editor {
highlighted_ranges: Default::default(), highlighted_ranges: Default::default(),
nav_history: None, nav_history: None,
completion_state: None, completion_state: None,
completions_task: None,
}; };
let selection = Selection { let selection = Selection {
id: post_inc(&mut this.next_selection_id), id: post_inc(&mut this.next_selection_id),
@ -1101,8 +1147,7 @@ impl Editor {
} }
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
if self.completion_state.take().is_some() { if self.hide_completions(cx).is_some() {
cx.notify();
return; return;
} }
@ -1394,8 +1439,7 @@ impl Editor {
{ {
self.show_completions(&ShowCompletions, cx); self.show_completions(&ShowCompletions, cx);
} else { } else {
self.completion_state.take(); self.hide_completions(cx);
cx.notify();
} }
} }
} }
@ -1526,16 +1570,8 @@ impl Editor {
} }
} }
fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) { fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
let position = if let Some(selection) = self.newest_anchor_selection() { let offset = position.to_offset(buffer);
selection.head()
} else {
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); let (word_range, kind) = buffer.surrounding_word(offset);
if offset > word_range.start && kind == Some(CharKind::Word) { if offset > word_range.start && kind == Some(CharKind::Word) {
Some( Some(
@ -1546,15 +1582,27 @@ impl Editor {
} else { } else {
None None
} }
}
fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
let position = if let Some(selection) = self.newest_anchor_selection() {
selection.head()
} else {
return;
}; };
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
let completions = self let completions = self
.buffer .buffer
.update(cx, |buffer, cx| buffer.completions(position.clone(), cx)); .update(cx, |buffer, cx| buffer.completions(position.clone(), cx));
cx.spawn_weak(|this, mut cx| async move { self.completions_task = Some(cx.spawn_weak(|this, mut cx| {
async move {
let completions = completions.await?; let completions = completions.await?;
let candidates = completions
let mut completion_state = CompletionState {
initial_position: position,
match_candidates: completions
.iter() .iter()
.enumerate() .enumerate()
.map(|(id, completion)| { .map(|(id, completion)| {
@ -1563,67 +1611,42 @@ impl Editor {
completion.label()[completion.filter_range()].into(), completion.label()[completion.filter_range()].into(),
) )
}) })
.collect::<Vec<_>>(); .collect(),
let mut matches = if let Some(query) = query.as_ref() { completions: completions.into(),
fuzzy::match_strings( matches: Vec::new().into(),
&candidates, selected_item: 0,
query, list: Default::default(),
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(),
)
});
for mat in &mut matches { completion_state
let filter_start = completions[mat.candidate_id].filter_range().start; .filter(query.as_deref(), cx.background())
for position in &mut mat.positions { .await;
*position += filter_start;
}
}
if let Some(this) = cx.read(|cx| this.upgrade(cx)) { if let Some(this) = cx.read(|cx| this.upgrade(cx)) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if matches.is_empty() { if completion_state.matches.is_empty() {
this.completion_state.take(); this.hide_completions(cx);
} else if this.focused { } else if this.focused {
this.completion_state = Some(CompletionState { this.completion_state = Some(completion_state);
initial_position: position,
completions: completions.into(),
matches: matches.into(),
selected_item: 0,
list: Default::default(),
});
} }
cx.notify(); cx.notify();
}); });
} }
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}) }
.detach_and_log_err(cx); .log_err()
}));
}
fn hide_completions(&mut self, cx: &mut ViewContext<Self>) -> Option<CompletionState> {
cx.notify();
self.completions_task.take();
self.completion_state.take()
} }
fn confirm_completion(&mut self, _: &ConfirmCompletion, cx: &mut ViewContext<Self>) { fn confirm_completion(&mut self, _: &ConfirmCompletion, cx: &mut ViewContext<Self>) {
if let Some(completion_state) = self.completion_state.take() { if let Some(completion_state) = self.hide_completions(cx) {
if let Some(completion) = completion_state if let Some(completion) = completion_state
.completions .completions
.get(completion_state.selected_item) .get(completion_state.selected_item)
@ -3686,17 +3709,18 @@ impl Editor {
); );
if let Some((completion_state, cursor_position)) = 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 cursor_position = cursor_position.to_offset(&buffer);
let (word_range, kind) = let (word_range, kind) =
buffer.surrounding_word(completion_state.initial_position.clone()); buffer.surrounding_word(completion_state.initial_position.clone());
if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) 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); self.show_completions(&ShowCompletions, cx);
} else { } else {
self.completion_state.take(); self.hide_completions(cx);
cx.notify();
} }
} }
} }
@ -4304,7 +4328,7 @@ impl View for Editor {
self.show_local_cursors = false; self.show_local_cursors = false;
self.buffer self.buffer
.update(cx, |buffer, cx| buffer.remove_active_selections(cx)); .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
self.completion_state.take(); self.hide_completions(cx);
cx.emit(Event::Blurred); cx.emit(Event::Blurred);
cx.notify(); cx.notify();
} }

View file

@ -106,7 +106,7 @@ impl Anchor {
} }
impl ToOffset for Anchor { impl ToOffset for Anchor {
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize {
self.summary(snapshot) self.summary(snapshot)
} }
} }