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,
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<TypeId, (Color, Vec<Range<Anchor>>)>,
nav_history: Option<ItemNavHistory>,
completion_state: Option<CompletionState>,
completions_task: Option<Task<Option<()>>>,
}
pub struct EditorSnapshot {
@ -432,11 +434,54 @@ struct BracketPairState {
struct CompletionState {
initial_position: Anchor,
completions: Arc<[Completion<Anchor>]>,
match_candidates: Vec<StringMatchCandidate>,
matches: Arc<[StringMatch]>,
selected_item: usize,
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)]
struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>,
@ -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<Self>) {
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,16 +1570,8 @@ impl Editor {
}
}
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 = {
let buffer = self.buffer.read(cx).read(cx);
let offset = position.to_offset(&buffer);
fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
let offset = position.to_offset(buffer);
let (word_range, kind) = buffer.surrounding_word(offset);
if offset > word_range.start && kind == Some(CharKind::Word) {
Some(
@ -1546,15 +1582,27 @@ impl Editor {
} else {
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
.buffer
.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 candidates = completions
let mut completion_state = CompletionState {
initial_position: position,
match_candidates: completions
.iter()
.enumerate()
.map(|(id, completion)| {
@ -1563,67 +1611,42 @@ impl Editor {
completion.label()[completion.filter_range()].into(),
)
})
.collect::<Vec<_>>();
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()
.collect(),
completions: completions.into(),
matches: Vec::new().into(),
selected_item: 0,
list: Default::default(),
};
matches.sort_unstable_by_key(|mat| {
(
Reverse(OrderedFloat(mat.score)),
completions[mat.candidate_id].sort_key(),
)
});
for mat in &mut matches {
let filter_start = completions[mat.candidate_id].filter_range().start;
for position in &mut mat.positions {
*position += filter_start;
}
}
completion_state
.filter(query.as_deref(), cx.background())
.await;
if let Some(this) = cx.read(|cx| this.upgrade(cx)) {
this.update(&mut cx, |this, cx| {
if matches.is_empty() {
this.completion_state.take();
if completion_state.matches.is_empty() {
this.hide_completions(cx);
} else if this.focused {
this.completion_state = Some(CompletionState {
initial_position: position,
completions: completions.into(),
matches: matches.into(),
selected_item: 0,
list: Default::default(),
});
this.completion_state = Some(completion_state);
}
cx.notify();
});
}
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>) {
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();
}

View file

@ -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)
}
}