Race completion filter w/completion request & make not block UI

This commit is contained in:
Julia 2023-10-12 13:23:26 -04:00
parent 4688a94a54
commit 85332eacbd
3 changed files with 76 additions and 47 deletions

View file

@ -656,7 +656,7 @@ pub struct Editor {
background_highlights: BTreeMap<TypeId, BackgroundHighlight>, background_highlights: BTreeMap<TypeId, BackgroundHighlight>,
inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>, inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
nav_history: Option<ItemNavHistory>, nav_history: Option<ItemNavHistory>,
context_menu: Option<ContextMenu>, context_menu: RwLock<Option<ContextMenu>>,
mouse_context_menu: ViewHandle<context_menu::ContextMenu>, mouse_context_menu: ViewHandle<context_menu::ContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>, completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
next_completion_id: CompletionId, next_completion_id: CompletionId,
@ -934,12 +934,13 @@ impl ContextMenu {
} }
} }
#[derive(Clone)]
struct CompletionsMenu { struct CompletionsMenu {
id: CompletionId, id: CompletionId,
initial_position: Anchor, initial_position: Anchor,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
completions: Arc<RwLock<Box<[Completion]>>>, completions: Arc<RwLock<Box<[Completion]>>>,
match_candidates: Vec<StringMatchCandidate>, match_candidates: Arc<[StringMatchCandidate]>,
matches: Arc<[StringMatch]>, matches: Arc<[StringMatch]>,
selected_item: usize, selected_item: usize,
list: UniformListState, list: UniformListState,
@ -1805,7 +1806,7 @@ impl Editor {
background_highlights: Default::default(), background_highlights: Default::default(),
inlay_background_highlights: Default::default(), inlay_background_highlights: Default::default(),
nav_history: None, nav_history: None,
context_menu: None, context_menu: RwLock::new(None),
mouse_context_menu: cx mouse_context_menu: cx
.add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
completion_tasks: Default::default(), completion_tasks: Default::default(),
@ -2100,10 +2101,12 @@ impl Editor {
if local { if local {
let new_cursor_position = self.selections.newest_anchor().head(); let new_cursor_position = self.selections.newest_anchor().head();
let completion_menu = match self.context_menu.as_mut() { let mut context_menu = self.context_menu.write();
let completion_menu = match context_menu.as_ref() {
Some(ContextMenu::Completions(menu)) => Some(menu), Some(ContextMenu::Completions(menu)) => Some(menu),
_ => { _ => {
self.context_menu.take(); *context_menu = None;
None None
} }
}; };
@ -2115,13 +2118,39 @@ impl Editor {
if kind == Some(CharKind::Word) if kind == Some(CharKind::Word)
&& word_range.to_inclusive().contains(&cursor_position) && word_range.to_inclusive().contains(&cursor_position)
{ {
let mut completion_menu = completion_menu.clone();
drop(context_menu);
let query = Self::completion_query(buffer, cursor_position); let query = Self::completion_query(buffer, cursor_position);
cx.background() cx.spawn(move |this, mut cx| async move {
.block(completion_menu.filter(query.as_deref(), cx.background().clone())); completion_menu
.filter(query.as_deref(), cx.background().clone())
.await;
this.update(&mut cx, |this, cx| {
let mut context_menu = this.context_menu.write();
let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else {
return;
};
if menu.id > completion_menu.id {
return;
}
*context_menu = Some(ContextMenu::Completions(completion_menu));
drop(context_menu);
cx.notify();
})
})
.detach();
self.show_completions(&ShowCompletions, cx); self.show_completions(&ShowCompletions, cx);
} else { } else {
drop(context_menu);
self.hide_context_menu(cx); self.hide_context_menu(cx);
} }
} else {
drop(context_menu);
} }
hide_hover(self, cx); hide_hover(self, cx);
@ -3432,23 +3461,31 @@ impl Editor {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.completion_tasks.retain(|(task_id, _)| *task_id > id); this.completion_tasks.retain(|(task_id, _)| *task_id > id);
match this.context_menu.as_ref() { let mut context_menu = this.context_menu.write();
match context_menu.as_ref() {
None => {} None => {}
Some(ContextMenu::Completions(prev_menu)) => { Some(ContextMenu::Completions(prev_menu)) => {
if prev_menu.id > id { if prev_menu.id > id {
return; return;
} }
} }
_ => return, _ => return,
} }
if this.focused && menu.is_some() { if this.focused && menu.is_some() {
let menu = menu.unwrap(); let menu = menu.unwrap();
this.show_context_menu(ContextMenu::Completions(menu), cx); *context_menu = Some(ContextMenu::Completions(menu));
drop(context_menu);
this.completion_tasks.clear();
this.discard_copilot_suggestion(cx);
cx.notify();
} else if this.completion_tasks.is_empty() { } else if this.completion_tasks.is_empty() {
// If there are no more completion tasks and the last menu was // If there are no more completion tasks and the last menu was
// empty, we should hide it. If it was already hidden, we should // empty, we should hide it. If it was already hidden, we should
// also show the copilot suggestion when available. // also show the copilot suggestion when available.
drop(context_menu);
if this.hide_context_menu(cx).is_none() { if this.hide_context_menu(cx).is_none() {
this.update_visible_copilot_suggestion(cx); this.update_visible_copilot_suggestion(cx);
} }
@ -3593,14 +3630,13 @@ impl Editor {
} }
pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) { pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
if matches!( let mut context_menu = self.context_menu.write();
self.context_menu.as_ref(), if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
Some(ContextMenu::CodeActions(_)) *context_menu = None;
) {
self.context_menu.take();
cx.notify(); cx.notify();
return; return;
} }
drop(context_menu);
let deployed_from_indicator = action.deployed_from_indicator; let deployed_from_indicator = action.deployed_from_indicator;
let mut task = self.code_actions_task.take(); let mut task = self.code_actions_task.take();
@ -3613,16 +3649,16 @@ impl Editor {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if this.focused { if this.focused {
if let Some((buffer, actions)) = this.available_code_actions.clone() { if let Some((buffer, actions)) = this.available_code_actions.clone() {
this.show_context_menu( this.completion_tasks.clear();
ContextMenu::CodeActions(CodeActionsMenu { this.discard_copilot_suggestion(cx);
*this.context_menu.write() =
Some(ContextMenu::CodeActions(CodeActionsMenu {
buffer, buffer,
actions, actions,
selected_item: Default::default(), selected_item: Default::default(),
list: Default::default(), list: Default::default(),
deployed_from_indicator, deployed_from_indicator,
}), }));
cx,
);
} }
} }
})?; })?;
@ -4086,7 +4122,7 @@ impl Editor {
let selection = self.selections.newest_anchor(); let selection = self.selections.newest_anchor();
let cursor = selection.head(); let cursor = selection.head();
if self.context_menu.is_some() if self.context_menu.read().is_some()
|| !self.completion_tasks.is_empty() || !self.completion_tasks.is_empty()
|| selection.start != selection.end || selection.start != selection.end
{ {
@ -4220,6 +4256,7 @@ impl Editor {
pub fn context_menu_visible(&self) -> bool { pub fn context_menu_visible(&self) -> bool {
self.context_menu self.context_menu
.read()
.as_ref() .as_ref()
.map_or(false, |menu| menu.visible()) .map_or(false, |menu| menu.visible())
} }
@ -4230,7 +4267,7 @@ impl Editor {
style: EditorStyle, style: EditorStyle,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, AnyElement<Editor>)> { ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
self.context_menu.as_ref().map(|menu| { self.context_menu.read().as_ref().map(|menu| {
menu.render( menu.render(
cursor_position, cursor_position,
style, style,
@ -4240,19 +4277,10 @@ impl Editor {
}) })
} }
fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext<Self>) {
if !matches!(menu, ContextMenu::Completions(_)) {
self.completion_tasks.clear();
}
self.context_menu = Some(menu);
self.discard_copilot_suggestion(cx);
cx.notify();
}
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> { fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
cx.notify(); cx.notify();
self.completion_tasks.clear(); self.completion_tasks.clear();
let context_menu = self.context_menu.take(); let context_menu = self.context_menu.write().take();
if context_menu.is_some() { if context_menu.is_some() {
self.update_visible_copilot_suggestion(cx); self.update_visible_copilot_suggestion(cx);
} }
@ -5604,6 +5632,7 @@ impl Editor {
if self if self
.context_menu .context_menu
.write()
.as_mut() .as_mut()
.map(|menu| menu.select_last(self.project.as_ref(), cx)) .map(|menu| menu.select_last(self.project.as_ref(), cx))
.unwrap_or(false) .unwrap_or(false)
@ -5648,25 +5677,25 @@ impl Editor {
} }
pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) { pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.as_mut() { if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_first(self.project.as_ref(), cx); context_menu.select_first(self.project.as_ref(), cx);
} }
} }
pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) { pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.as_mut() { if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_prev(self.project.as_ref(), cx); context_menu.select_prev(self.project.as_ref(), cx);
} }
} }
pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) { pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.as_mut() { if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_next(self.project.as_ref(), cx); context_menu.select_next(self.project.as_ref(), cx);
} }
} }
pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) { pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.as_mut() { if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_last(self.project.as_ref(), cx); context_menu.select_last(self.project.as_ref(), cx);
} }
} }
@ -9164,7 +9193,7 @@ impl View for Editor {
keymap.add_identifier("renaming"); keymap.add_identifier("renaming");
} }
if self.context_menu_visible() { if self.context_menu_visible() {
match self.context_menu.as_ref() { match self.context_menu.read().as_ref() {
Some(ContextMenu::Completions(_)) => { Some(ContextMenu::Completions(_)) => {
keymap.add_identifier("menu"); keymap.add_identifier("menu");
keymap.add_identifier("showing_completions") keymap.add_identifier("showing_completions")

View file

@ -5430,9 +5430,9 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
additional edit additional edit
"}); "});
cx.simulate_keystroke(" "); cx.simulate_keystroke(" ");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
cx.simulate_keystroke("s"); cx.simulate_keystroke("s");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one.second_completion one.second_completion
@ -5494,12 +5494,12 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
}); });
cx.set_state("editorˇ"); cx.set_state("editorˇ");
cx.simulate_keystroke("."); cx.simulate_keystroke(".");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
cx.simulate_keystroke("c"); cx.simulate_keystroke("c");
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.simulate_keystroke("o"); cx.simulate_keystroke("o");
cx.assert_editor_state("editor.cloˇ"); cx.assert_editor_state("editor.cloˇ");
assert!(cx.editor(|e, _| e.context_menu.is_none())); assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
editor.show_completions(&ShowCompletions, cx); editor.show_completions(&ShowCompletions, cx);
}); });
@ -7788,7 +7788,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
cx.simulate_keystroke("-"); cx.simulate_keystroke("-");
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.update_editor(|editor, _| { cx.update_editor(|editor, _| {
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert_eq!( assert_eq!(
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(), menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
&["bg-red", "bg-blue", "bg-yellow"] &["bg-red", "bg-blue", "bg-yellow"]
@ -7801,7 +7801,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.update_editor(|editor, _| { cx.update_editor(|editor, _| {
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert_eq!( assert_eq!(
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(), menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
&["bg-blue", "bg-yellow"] &["bg-blue", "bg-yellow"]
@ -7817,7 +7817,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.update_editor(|editor, _| { cx.update_editor(|editor, _| {
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert_eq!( assert_eq!(
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(), menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
&["bg-yellow"] &["bg-yellow"]

View file

@ -2428,7 +2428,7 @@ impl Element<Editor> for EditorElement {
} }
let active = matches!( let active = matches!(
editor.context_menu, editor.context_menu.read().as_ref(),
Some(crate::ContextMenu::CodeActions(_)) Some(crate::ContextMenu::CodeActions(_))
); );