Revert "Refactor completions" (#42014)
Reverts zed-industries/zed#41939
This commit is contained in:
@@ -477,7 +477,7 @@ impl TextThreadEditor {
|
||||
editor.insert(&format!("/{name}"), window, cx);
|
||||
if command.accepts_arguments() {
|
||||
editor.insert(" ", window, cx);
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions::default(), window, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -213,6 +213,15 @@ pub struct ExpandExcerptsDown {
|
||||
pub(super) lines: u32,
|
||||
}
|
||||
|
||||
/// Shows code completion suggestions at the cursor position.
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ShowCompletions {
|
||||
#[serde(default)]
|
||||
pub(super) trigger: Option<String>,
|
||||
}
|
||||
|
||||
/// Handles text input in the editor.
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
@@ -723,8 +732,6 @@ actions!(
|
||||
SelectToStartOfParagraph,
|
||||
/// Extends selection up.
|
||||
SelectUp,
|
||||
/// Shows code completion suggestions at the cursor position.
|
||||
ShowCompletions,
|
||||
/// Shows the system character palette.
|
||||
ShowCharacterPalette,
|
||||
/// Shows edit prediction at cursor.
|
||||
|
||||
@@ -252,17 +252,8 @@ enum MarkdownCacheKey {
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum CompletionsMenuSource {
|
||||
/// Show all completions (words, snippets, LSP)
|
||||
Normal,
|
||||
/// Show only snippets (not words or LSP)
|
||||
///
|
||||
/// Used after typing a non-word character
|
||||
SnippetsOnly,
|
||||
/// Tab stops within a snippet that have a predefined finite set of choices
|
||||
SnippetChoices,
|
||||
/// Show only words (not snippets or LSP)
|
||||
///
|
||||
/// Used when word completions are explicitly triggered
|
||||
Words { ignore_threshold: bool },
|
||||
}
|
||||
|
||||
|
||||
+119
-119
@@ -3138,7 +3138,7 @@ impl Editor {
|
||||
};
|
||||
|
||||
if continue_showing {
|
||||
self.open_or_update_completions_menu(None, None, false, window, cx);
|
||||
self.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
} else {
|
||||
self.hide_context_menu(window, cx);
|
||||
}
|
||||
@@ -4968,18 +4968,57 @@ impl Editor {
|
||||
ignore_threshold: false,
|
||||
}),
|
||||
None,
|
||||
trigger_in_words,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => self.open_or_update_completions_menu(
|
||||
None,
|
||||
Some(text.to_owned()).filter(|x| !x.is_empty()),
|
||||
true,
|
||||
window,
|
||||
Some(CompletionsMenuSource::Normal)
|
||||
| Some(CompletionsMenuSource::SnippetChoices)
|
||||
| None
|
||||
if self.is_completion_trigger(
|
||||
text,
|
||||
trigger_in_words,
|
||||
completions_source.is_some(),
|
||||
cx,
|
||||
) =>
|
||||
{
|
||||
self.show_completions(
|
||||
&ShowCompletions {
|
||||
trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
self.hide_context_menu(window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
text: &str,
|
||||
trigger_in_words: bool,
|
||||
menu_is_open: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
let position = self.selections.newest_anchor().head();
|
||||
let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Some(completion_provider) = &self.completion_provider {
|
||||
completion_provider.is_completion_trigger(
|
||||
&buffer,
|
||||
position.text_anchor,
|
||||
text,
|
||||
trigger_in_words,
|
||||
menu_is_open,
|
||||
cx,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5257,7 +5296,6 @@ impl Editor {
|
||||
ignore_threshold: true,
|
||||
}),
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -5265,18 +5303,17 @@ impl Editor {
|
||||
|
||||
pub fn show_completions(
|
||||
&mut self,
|
||||
_: &ShowCompletions,
|
||||
options: &ShowCompletions,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.open_or_update_completions_menu(None, None, false, window, cx);
|
||||
self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
|
||||
}
|
||||
|
||||
fn open_or_update_completions_menu(
|
||||
&mut self,
|
||||
requested_source: Option<CompletionsMenuSource>,
|
||||
trigger: Option<String>,
|
||||
trigger_in_words: bool,
|
||||
trigger: Option<&str>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -5284,15 +5321,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let completions_source = self
|
||||
.context_menu
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|menu| match menu {
|
||||
CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
|
||||
CodeContextMenu::CodeActions(_) => None,
|
||||
});
|
||||
|
||||
let multibuffer_snapshot = self.buffer.read(cx).read(cx);
|
||||
|
||||
// Typically `start` == `end`, but with snippet tabstop choices the default choice is
|
||||
@@ -5340,8 +5368,7 @@ impl Editor {
|
||||
ignore_word_threshold = ignore_threshold;
|
||||
None
|
||||
}
|
||||
Some(CompletionsMenuSource::SnippetChoices)
|
||||
| Some(CompletionsMenuSource::SnippetsOnly) => {
|
||||
Some(CompletionsMenuSource::SnippetChoices) => {
|
||||
log::error!("bug: SnippetChoices requested_source is not handled");
|
||||
None
|
||||
}
|
||||
@@ -5355,19 +5382,13 @@ impl Editor {
|
||||
.as_ref()
|
||||
.is_none_or(|provider| provider.filter_completions());
|
||||
|
||||
let was_snippets_only = matches!(
|
||||
completions_source,
|
||||
Some(CompletionsMenuSource::SnippetsOnly)
|
||||
);
|
||||
|
||||
if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
|
||||
if filter_completions {
|
||||
menu.filter(query.clone(), provider.clone(), window, cx);
|
||||
}
|
||||
// When `is_incomplete` is false, no need to re-query completions when the current query
|
||||
// is a suffix of the initial query.
|
||||
let was_complete = !menu.is_incomplete;
|
||||
if was_complete && !was_snippets_only {
|
||||
if !menu.is_incomplete {
|
||||
// If the new query is a suffix of the old query (typing more characters) and
|
||||
// the previous result was complete, the existing completions can be filtered.
|
||||
//
|
||||
@@ -5391,6 +5412,23 @@ impl Editor {
|
||||
}
|
||||
};
|
||||
|
||||
let trigger_kind = match trigger {
|
||||
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
|
||||
CompletionTriggerKind::TRIGGER_CHARACTER
|
||||
}
|
||||
_ => CompletionTriggerKind::INVOKED,
|
||||
};
|
||||
let completion_context = CompletionContext {
|
||||
trigger_character: trigger.and_then(|trigger| {
|
||||
if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
|
||||
Some(String::from(trigger))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
trigger_kind,
|
||||
};
|
||||
|
||||
let Anchor {
|
||||
excerpt_id: buffer_excerpt_id,
|
||||
text_anchor: buffer_position,
|
||||
@@ -5448,72 +5486,49 @@ impl Editor {
|
||||
&& match &query {
|
||||
Some(query) => query.chars().count() < completion_settings.words_min_length,
|
||||
None => completion_settings.words_min_length != 0,
|
||||
})
|
||||
|| (provider.is_some() && completion_settings.words == WordsCompletionMode::Disabled);
|
||||
});
|
||||
|
||||
let mut words = if omit_word_completions {
|
||||
Task::ready(BTreeMap::default())
|
||||
} else {
|
||||
cx.background_spawn(async move {
|
||||
buffer_snapshot.words_in_range(WordsQuery {
|
||||
fuzzy_contents: None,
|
||||
range: word_search_range,
|
||||
skip_digits,
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
let load_provider_completions = provider.as_ref().is_some_and(|provider| {
|
||||
trigger.as_ref().is_none_or(|trigger| {
|
||||
provider.is_completion_trigger(
|
||||
let (mut words, provider_responses) = match &provider {
|
||||
Some(provider) => {
|
||||
let provider_responses = provider.completions(
|
||||
buffer_excerpt_id,
|
||||
&buffer,
|
||||
position.text_anchor,
|
||||
trigger,
|
||||
trigger_in_words,
|
||||
completions_source.is_some(),
|
||||
buffer_position,
|
||||
completion_context,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
);
|
||||
|
||||
let provider_responses = if let Some(provider) = &provider
|
||||
&& load_provider_completions
|
||||
{
|
||||
let trigger_character =
|
||||
trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
|
||||
let completion_context = CompletionContext {
|
||||
trigger_kind: match &trigger_character {
|
||||
Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
|
||||
None => CompletionTriggerKind::INVOKED,
|
||||
},
|
||||
trigger_character,
|
||||
};
|
||||
let words = match (omit_word_completions, completion_settings.words) {
|
||||
(true, _) | (_, WordsCompletionMode::Disabled) => {
|
||||
Task::ready(BTreeMap::default())
|
||||
}
|
||||
(false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
|
||||
.background_spawn(async move {
|
||||
buffer_snapshot.words_in_range(WordsQuery {
|
||||
fuzzy_contents: None,
|
||||
range: word_search_range,
|
||||
skip_digits,
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
provider.completions(
|
||||
buffer_excerpt_id,
|
||||
&buffer,
|
||||
buffer_position,
|
||||
completion_context,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
};
|
||||
|
||||
let snippets = if let Some(provider) = &provider
|
||||
&& provider.show_snippets()
|
||||
&& let Some(project) = self.project()
|
||||
{
|
||||
project.update(cx, |project, cx| {
|
||||
snippet_completions(project, &buffer, buffer_position, cx)
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(CompletionResponse {
|
||||
completions: Vec::new(),
|
||||
display_options: Default::default(),
|
||||
is_incomplete: false,
|
||||
}))
|
||||
(words, provider_responses)
|
||||
}
|
||||
None => {
|
||||
let words = if omit_word_completions {
|
||||
Task::ready(BTreeMap::default())
|
||||
} else {
|
||||
cx.background_spawn(async move {
|
||||
buffer_snapshot.words_in_range(WordsQuery {
|
||||
fuzzy_contents: None,
|
||||
range: word_search_range,
|
||||
skip_digits,
|
||||
})
|
||||
})
|
||||
};
|
||||
(words, Task::ready(Ok(Vec::new())))
|
||||
}
|
||||
};
|
||||
|
||||
let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
|
||||
@@ -5571,13 +5586,6 @@ impl Editor {
|
||||
confirm: None,
|
||||
}));
|
||||
|
||||
completions.extend(
|
||||
snippets
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|response| response.completions),
|
||||
);
|
||||
|
||||
let menu = if completions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -5589,11 +5597,7 @@ impl Editor {
|
||||
.map(|workspace| workspace.read(cx).app_state().languages.clone());
|
||||
let menu = CompletionsMenu::new(
|
||||
id,
|
||||
requested_source.unwrap_or(if load_provider_completions {
|
||||
CompletionsMenuSource::Normal
|
||||
} else {
|
||||
CompletionsMenuSource::SnippetsOnly
|
||||
}),
|
||||
requested_source.unwrap_or(CompletionsMenuSource::Normal),
|
||||
sort_completions,
|
||||
show_completion_documentation,
|
||||
position,
|
||||
@@ -5923,7 +5927,7 @@ impl Editor {
|
||||
.as_ref()
|
||||
.is_some_and(|confirm| confirm(intent, window, cx));
|
||||
if show_new_completions_on_confirm {
|
||||
self.open_or_update_completions_menu(None, None, false, window, cx);
|
||||
self.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
}
|
||||
|
||||
let provider = self.completion_provider.as_ref()?;
|
||||
@@ -12683,10 +12687,6 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
// 🤔 | .. | show_in_menu |
|
||||
// | .. | true true
|
||||
// | had_edit_prediction | false true
|
||||
|
||||
let trigger_in_words =
|
||||
this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
|
||||
|
||||
@@ -22888,10 +22888,6 @@ pub trait CompletionProvider {
|
||||
fn filter_completions(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn show_snippets(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CodeActionProvider {
|
||||
@@ -23152,8 +23148,16 @@ impl CompletionProvider for Entity<Project> {
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Task<Result<Vec<CompletionResponse>>> {
|
||||
self.update(cx, |project, cx| {
|
||||
let task = project.completions(buffer, buffer_position, options, cx);
|
||||
cx.background_spawn(task)
|
||||
let snippets = snippet_completions(project, buffer, buffer_position, cx);
|
||||
let project_completions = project.completions(buffer, buffer_position, options, cx);
|
||||
cx.background_spawn(async move {
|
||||
let mut responses = project_completions.await?;
|
||||
let snippets = snippets.await?;
|
||||
if !snippets.completions.is_empty() {
|
||||
responses.push(snippets);
|
||||
}
|
||||
Ok(responses)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23225,10 +23229,6 @@ impl CompletionProvider for Entity<Project> {
|
||||
|
||||
buffer.completion_triggers().contains(text)
|
||||
}
|
||||
|
||||
fn show_snippets(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticsProvider for Entity<Project> {
|
||||
|
||||
@@ -13760,7 +13760,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
|
||||
cx.set_state(&run.initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
@@ -13820,7 +13820,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
||||
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
@@ -13856,7 +13856,7 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
||||
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
handle_completion_request_with_insert_and_replace(
|
||||
&mut cx,
|
||||
@@ -13943,7 +13943,7 @@ async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut T
|
||||
"};
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
handle_completion_request_with_insert_and_replace(
|
||||
&mut cx,
|
||||
@@ -13997,7 +13997,7 @@ async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut T
|
||||
"};
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
handle_completion_request_with_insert_and_replace(
|
||||
&mut cx,
|
||||
@@ -14046,7 +14046,7 @@ async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut T
|
||||
"};
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
handle_completion_request_with_insert_and_replace(
|
||||
&mut cx,
|
||||
@@ -14197,7 +14197,7 @@ async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppConte
|
||||
});
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
|
||||
fake_server
|
||||
@@ -14436,7 +14436,7 @@ async fn test_completion(cx: &mut TestAppContext) {
|
||||
cx.assert_editor_state("editor.cloˇ");
|
||||
assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
handle_completion_request(
|
||||
"editor.<clo|>",
|
||||
@@ -14835,7 +14835,7 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
|
||||
4.5f32
|
||||
"});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions::default(), window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
@@ -14861,7 +14861,7 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
|
||||
33.35f32
|
||||
"});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions::default(), window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
@@ -15285,7 +15285,13 @@ async fn test_as_is_completions(cx: &mut TestAppContext) {
|
||||
cx.set_state("fn a() {}\n nˇ");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.trigger_completion_on_input("n", true, window, cx)
|
||||
editor.show_completions(
|
||||
&ShowCompletions {
|
||||
trigger: Some("\n".into()),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
@@ -15383,7 +15389,7 @@ int fn_branch(bool do_branch1, bool do_branch2);
|
||||
})))
|
||||
});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -15432,7 +15438,7 @@ int fn_branch(bool do_branch1, bool do_branch2);
|
||||
})))
|
||||
});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -17922,7 +17928,7 @@ async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
});
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
completion_requests.next().await;
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
@@ -24318,7 +24324,7 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
|
||||
])))
|
||||
});
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions, window, cx);
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
completion_handle.next().await.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user