Detailed changes
@@ -471,22 +471,13 @@
}
},
{
- "context": "Editor && !inline_completion && showing_completions",
+ "context": "Editor && showing_completions",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::ConfirmCompletion",
"tab": "editor::ComposeCompletion"
}
},
- {
- "context": "Editor && inline_completion && showing_completions",
- "use_key_equivalents": true,
- "bindings": {
- "enter": "editor::ConfirmCompletion",
- "tab": "editor::ComposeCompletion",
- "shift-tab": "editor::AcceptInlineCompletion"
- }
- },
{
"context": "Editor && inline_completion && !showing_completions",
"use_key_equivalents": true,
@@ -542,22 +542,13 @@
}
},
{
- "context": "Editor && !inline_completion && showing_completions",
+ "context": "Editor && showing_completions",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::ConfirmCompletion",
"tab": "editor::ComposeCompletion"
}
},
- {
- "context": "Editor && inline_completion && showing_completions",
- "use_key_equivalents": true,
- "bindings": {
- "enter": "editor::ConfirmCompletion",
- "tab": "editor::ComposeCompletion",
- "shift-tab": "editor::AcceptInlineCompletion"
- }
- },
{
"context": "Editor && inline_completion && !showing_completions",
"use_key_equivalents": true,
@@ -55,6 +55,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
"copilot"
}
+ fn display_name() -> &'static str {
+ "Copilot"
+ }
+
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -324,10 +328,15 @@ mod tests {
cx.update_editor(|editor, cx| {
// We want to show both: the inline completion and the completion menu
assert!(editor.context_menu_visible());
+ assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
+ // Since we have both, the copilot suggestion is not shown inline
+ assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
+ assert_eq!(editor.display_text(cx), "one.\ntwo\nthree\n");
- // Confirming a completion inserts it and hides the context menu, without showing
+ // Confirming a non-copilot completion inserts it and hides the context menu, without showing
// the copilot suggestion afterwards.
+ editor.context_menu_next(&Default::default(), cx);
editor
.confirm_completion(&Default::default(), cx)
.unwrap()
@@ -338,13 +347,14 @@ mod tests {
assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
});
- // Reset editor and test that accepting completions works
+ // Reset editor and only return copilot suggestions
cx.set_state(indoc! {"
oneΛ
two
three
"});
cx.simulate_keystroke(".");
+
drop(handle_completion_request(
&mut cx,
indoc! {"
@@ -352,7 +362,7 @@ mod tests {
two
three
"},
- vec!["completion_a", "completion_b"],
+ vec![],
));
handle_copilot_completion_request(
&copilot_lsp,
@@ -365,16 +375,15 @@ mod tests {
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
- assert!(editor.context_menu_visible());
+ assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
+ // Since only the copilot is available, it's shown inline
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
});
// Ensure existing inline completion is interpolated when inserting again.
cx.simulate_keystroke("c");
- // We still request a normal LSP completion, but we interpolate the
- // existing inline completion.
drop(handle_completion_request(
&mut cx,
indoc! {"
@@ -382,13 +391,16 @@ mod tests {
two
three
"},
- vec!["ompletion_a", "ompletion_b"],
+ vec!["completion_a", "completion_b"],
));
executor.run_until_parked();
cx.update_editor(|editor, cx| {
- assert!(!editor.context_menu_visible());
+ // Since we have an LSP completion too, the inline completion is
+ // shown in the menu now
+ assert!(editor.context_menu_visible());
+ assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
- assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
+ assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
});
@@ -404,6 +416,14 @@ mod tests {
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
+ assert!(editor.context_menu_visible());
+ assert!(editor.has_active_inline_completion());
+ assert!(editor.context_menu_contains_inline_completion());
+ assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
+ assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
+
+ // Canceling should first hide the menu and make Copilot suggestion visible.
+ editor.cancel(&Default::default(), cx);
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
@@ -908,8 +928,8 @@ mod tests {
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
assert!(editor.context_menu_visible());
+ assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion(),);
- assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
});
}
@@ -28,6 +28,7 @@ use crate::{
render_parsed_markdown, split_words, styled_runs_for_code_label, CodeActionProvider,
CompletionId, CompletionProvider, DisplayRow, Editor, EditorStyle, ResolvedTasks,
};
+use crate::{AcceptInlineCompletion, InlineCompletionMenuHint, InlineCompletionText};
pub enum CodeContextMenu {
Completions(CompletionsMenu),
@@ -141,7 +142,7 @@ pub struct CompletionsMenu {
pub buffer: Model<Buffer>,
pub completions: Rc<RefCell<Box<[Completion]>>>,
match_candidates: Rc<[StringMatchCandidate]>,
- pub matches: Rc<[StringMatch]>,
+ pub entries: Rc<[CompletionEntry]>,
pub selected_item: usize,
scroll_handle: UniformListScrollHandle,
resolve_completions: bool,
@@ -149,6 +150,12 @@ pub struct CompletionsMenu {
show_completion_documentation: bool,
}
+#[derive(Clone, Debug)]
+pub(crate) enum CompletionEntry {
+ Match(StringMatch),
+ InlineCompletionHint(InlineCompletionMenuHint),
+}
+
impl CompletionsMenu {
pub fn new(
id: CompletionId,
@@ -173,7 +180,7 @@ impl CompletionsMenu {
show_completion_documentation,
completions: RefCell::new(completions).into(),
match_candidates,
- matches: Vec::new().into(),
+ entries: Vec::new().into(),
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: true,
@@ -210,14 +217,16 @@ impl CompletionsMenu {
.enumerate()
.map(|(id, completion)| StringMatchCandidate::new(id, &completion))
.collect();
- let matches = choices
+ let entries = choices
.iter()
.enumerate()
- .map(|(id, completion)| StringMatch {
- candidate_id: id,
- score: 1.,
- positions: vec![],
- string: completion.clone(),
+ .map(|(id, completion)| {
+ CompletionEntry::Match(StringMatch {
+ candidate_id: id,
+ score: 1.,
+ positions: vec![],
+ string: completion.clone(),
+ })
})
.collect();
Self {
@@ -227,7 +236,7 @@ impl CompletionsMenu {
buffer,
completions: RefCell::new(completions).into(),
match_candidates,
- matches,
+ entries,
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: false,
@@ -256,7 +265,7 @@ impl CompletionsMenu {
if self.selected_item > 0 {
self.selected_item -= 1;
} else {
- self.selected_item = self.matches.len() - 1;
+ self.selected_item = self.entries.len() - 1;
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
@@ -269,7 +278,7 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
- if self.selected_item + 1 < self.matches.len() {
+ if self.selected_item + 1 < self.entries.len() {
self.selected_item += 1;
} else {
self.selected_item = 0;
@@ -285,13 +294,33 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
- self.selected_item = self.matches.len() - 1;
+ self.selected_item = self.entries.len() - 1;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
+ pub fn show_inline_completion_hint(&mut self, hint: InlineCompletionMenuHint) {
+ let hint = CompletionEntry::InlineCompletionHint(hint);
+
+ self.entries = match self.entries.first() {
+ Some(CompletionEntry::InlineCompletionHint { .. }) => {
+ let mut entries = Vec::from(&*self.entries);
+ entries[0] = hint;
+ entries
+ }
+ _ => {
+ let mut entries = Vec::with_capacity(self.entries.len() + 1);
+ entries.push(hint);
+ entries.extend_from_slice(&self.entries);
+ entries
+ }
+ }
+ .into();
+ self.selected_item = 0;
+ }
+
pub fn resolve_selected_completion(
&mut self,
provider: Option<&dyn CompletionProvider>,
@@ -304,24 +333,29 @@ impl CompletionsMenu {
return;
};
- let completion_index = self.matches[self.selected_item].candidate_id;
- let resolve_task = provider.resolve_completions(
- self.buffer.clone(),
- vec![completion_index],
- self.completions.clone(),
- cx,
- );
-
- cx.spawn(move |editor, mut cx| async move {
- if let Some(true) = resolve_task.await.log_err() {
- editor.update(&mut cx, |_, cx| cx.notify()).ok();
+ match &self.entries[self.selected_item] {
+ CompletionEntry::Match(entry) => {
+ let completion_index = entry.candidate_id;
+ let resolve_task = provider.resolve_completions(
+ self.buffer.clone(),
+ vec![completion_index],
+ self.completions.clone(),
+ cx,
+ );
+
+ cx.spawn(move |editor, mut cx| async move {
+ if let Some(true) = resolve_task.await.log_err() {
+ editor.update(&mut cx, |_, cx| cx.notify()).ok();
+ }
+ })
+ .detach();
}
- })
- .detach();
+ CompletionEntry::InlineCompletionHint { .. } => {}
+ }
}
- fn visible(&self) -> bool {
- !self.matches.is_empty()
+ pub fn visible(&self) -> bool {
+ !self.entries.is_empty()
}
fn origin(&self, cursor_position: DisplayPoint) -> ContextMenuOrigin {
@@ -340,21 +374,27 @@ impl CompletionsMenu {
let completions = self.completions.borrow_mut();
let show_completion_documentation = self.show_completion_documentation;
let widest_completion_ix = self
- .matches
+ .entries
.iter()
.enumerate()
- .max_by_key(|(_, mat)| {
- let completion = &completions[mat.candidate_id];
- let documentation = &completion.documentation;
-
- let mut len = completion.label.text.chars().count();
- if let Some(Documentation::SingleLine(text)) = documentation {
- if show_completion_documentation {
- len += text.chars().count();
+ .max_by_key(|(_, mat)| match mat {
+ CompletionEntry::Match(mat) => {
+ let completion = &completions[mat.candidate_id];
+ let documentation = &completion.documentation;
+
+ let mut len = completion.label.text.chars().count();
+ if let Some(Documentation::SingleLine(text)) = documentation {
+ if show_completion_documentation {
+ len += text.chars().count();
+ }
}
- }
- len
+ len
+ }
+ CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint {
+ provider_name,
+ ..
+ }) => provider_name.len(),
})
.map(|(ix, _)| ix);
@@ -362,24 +402,36 @@ impl CompletionsMenu {
let style = style.clone();
let multiline_docs = if show_completion_documentation {
- let mat = &self.matches[selected_item];
- match &completions[mat.candidate_id].documentation {
- Some(Documentation::MultiLinePlainText(text)) => {
- Some(div().child(SharedString::from(text.clone())))
- }
- Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
- Some(div().child(render_parsed_markdown(
- "completions_markdown",
- parsed,
- &style,
- workspace,
- cx,
- )))
- }
- Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
- Some(div().child("No documentation"))
- }
- _ => None,
+ match &self.entries[selected_item] {
+ CompletionEntry::Match(mat) => match &completions[mat.candidate_id].documentation {
+ Some(Documentation::MultiLinePlainText(text)) => {
+ Some(div().child(SharedString::from(text.clone())))
+ }
+ Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
+ Some(div().child(render_parsed_markdown(
+ "completions_markdown",
+ parsed,
+ &style,
+ workspace,
+ cx,
+ )))
+ }
+ Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
+ Some(div().child("No documentation"))
+ }
+ _ => None,
+ },
+ CompletionEntry::InlineCompletionHint(hint) => Some(match &hint.text {
+ InlineCompletionText::Edit { text, highlights } => div()
+ .my_1()
+ .rounded_md()
+ .bg(cx.theme().colors().editor_background)
+ .child(
+ gpui::StyledText::new(text.clone())
+ .with_highlights(&style.text, highlights.clone()),
+ ),
+ InlineCompletionText::Move(text) => div().child(text.clone()),
+ }),
}
} else {
None
@@ -409,7 +461,7 @@ impl CompletionsMenu {
drop(completions);
let completions = self.completions.clone();
- let matches = self.matches.clone();
+ let matches = self.entries.clone();
let list = uniform_list(
cx.view().clone(),
"completions",
@@ -423,82 +475,111 @@ impl CompletionsMenu {
.enumerate()
.map(|(ix, mat)| {
let item_ix = start_ix + ix;
- let candidate_id = mat.candidate_id;
- let completion = &completions_guard[candidate_id];
-
- let documentation = if show_completion_documentation {
- &completion.documentation
- } else {
- &None
- };
-
- let filter_start = completion.label.filter_range.start;
- let highlights = gpui::combine_highlights(
- mat.ranges().map(|range| {
- (
- filter_start + range.start..filter_start + range.end,
- FontWeight::BOLD.into(),
- )
- }),
- styled_runs_for_code_label(&completion.label, &style.syntax).map(
- |(range, mut highlight)| {
- // Ignore font weight for syntax highlighting, as we'll use it
- // for fuzzy matches.
- highlight.font_weight = None;
-
- if completion.lsp_completion.deprecated.unwrap_or(false) {
- highlight.strikethrough = Some(StrikethroughStyle {
- thickness: 1.0.into(),
- ..Default::default()
- });
- highlight.color = Some(cx.theme().colors().text_muted);
- }
-
- (range, highlight)
- },
- ),
- );
- let completion_label = StyledText::new(completion.label.text.clone())
- .with_highlights(&style.text, highlights);
- let documentation_label =
- if let Some(Documentation::SingleLine(text)) = documentation {
- if text.trim().is_empty() {
- None
- } else {
- Some(
- Label::new(text.clone())
- .ml_4()
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- }
- } else {
- None
- };
-
- let color_swatch = completion
- .color()
- .map(|color| div().size_4().bg(color).rounded_sm());
+ match mat {
+ CompletionEntry::Match(mat) => {
+ let candidate_id = mat.candidate_id;
+ let completion = &completions_guard[candidate_id];
- div().min_w(px(220.)).max_w(px(540.)).child(
- ListItem::new(mat.candidate_id)
- .inset(true)
- .toggle_state(item_ix == selected_item)
- .on_click(cx.listener(move |editor, _event, cx| {
- cx.stop_propagation();
- if let Some(task) = editor.confirm_completion(
- &ConfirmCompletion {
- item_ix: Some(item_ix),
- },
- cx,
- ) {
- task.detach_and_log_err(cx)
- }
- }))
- .start_slot::<Div>(color_swatch)
- .child(h_flex().overflow_hidden().child(completion_label))
- .end_slot::<Label>(documentation_label),
- )
+ let documentation = if show_completion_documentation {
+ &completion.documentation
+ } else {
+ &None
+ };
+
+ let filter_start = completion.label.filter_range.start;
+ let highlights = gpui::combine_highlights(
+ mat.ranges().map(|range| {
+ (
+ filter_start + range.start..filter_start + range.end,
+ FontWeight::BOLD.into(),
+ )
+ }),
+ styled_runs_for_code_label(&completion.label, &style.syntax)
+ .map(|(range, mut highlight)| {
+ // Ignore font weight for syntax highlighting, as we'll use it
+ // for fuzzy matches.
+ highlight.font_weight = None;
+
+ if completion.lsp_completion.deprecated.unwrap_or(false)
+ {
+ highlight.strikethrough =
+ Some(StrikethroughStyle {
+ thickness: 1.0.into(),
+ ..Default::default()
+ });
+ highlight.color =
+ Some(cx.theme().colors().text_muted);
+ }
+
+ (range, highlight)
+ }),
+ );
+ let completion_label =
+ StyledText::new(completion.label.text.clone())
+ .with_highlights(&style.text, highlights);
+ let documentation_label =
+ if let Some(Documentation::SingleLine(text)) = documentation {
+ if text.trim().is_empty() {
+ None
+ } else {
+ Some(
+ Label::new(text.clone())
+ .ml_4()
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ }
+ } else {
+ None
+ };
+
+ let color_swatch = completion
+ .color()
+ .map(|color| div().size_4().bg(color).rounded_sm());
+
+ div().min_w(px(220.)).max_w(px(540.)).child(
+ ListItem::new(mat.candidate_id)
+ .inset(true)
+ .toggle_state(item_ix == selected_item)
+ .on_click(cx.listener(move |editor, _event, cx| {
+ cx.stop_propagation();
+ if let Some(task) = editor.confirm_completion(
+ &ConfirmCompletion {
+ item_ix: Some(item_ix),
+ },
+ cx,
+ ) {
+ task.detach_and_log_err(cx)
+ }
+ }))
+ .start_slot::<Div>(color_swatch)
+ .child(h_flex().overflow_hidden().child(completion_label))
+ .end_slot::<Label>(documentation_label),
+ )
+ }
+ CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint {
+ provider_name,
+ ..
+ }) => div()
+ .min_w(px(250.))
+ .max_w(px(500.))
+ .pb_1()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ .child(
+ ListItem::new("inline-completion")
+ .inset(true)
+ .toggle_state(item_ix == selected_item)
+ .on_click(cx.listener(move |editor, _event, cx| {
+ cx.stop_propagation();
+ editor.accept_inline_completion(
+ &AcceptInlineCompletion {},
+ cx,
+ );
+ }))
+ .child(Label::new(SharedString::new_static(provider_name))),
+ ),
+ }
})
.collect()
},
@@ -611,7 +692,12 @@ impl CompletionsMenu {
}
drop(completions);
- self.matches = matches.into();
+ let mut new_entries: Vec<_> = matches.into_iter().map(CompletionEntry::Match).collect();
+ if let Some(CompletionEntry::InlineCompletionHint(hint)) = self.entries.first() {
+ new_entries.insert(0, CompletionEntry::InlineCompletionHint(hint.clone()));
+ }
+
+ self.entries = new_entries.into();
self.selected_item = 0;
}
}
@@ -73,7 +73,7 @@ use fuzzy::StringMatchCandidate;
use code_context_menus::{
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
- CompletionsMenu, ContextMenuOrigin,
+ CompletionEntry, CompletionsMenu, ContextMenuOrigin,
};
use git::blame::GitBlame;
use gpui::{
@@ -457,6 +457,21 @@ pub fn make_suggestion_styles(cx: &WindowContext) -> InlineCompletionStyles {
type CompletionId = usize;
+#[derive(Debug, Clone)]
+struct InlineCompletionMenuHint {
+ provider_name: &'static str,
+ text: InlineCompletionText,
+}
+
+#[derive(Clone, Debug)]
+enum InlineCompletionText {
+ Move(SharedString),
+ Edit {
+ text: SharedString,
+ highlights: Vec<(Range<usize>, HighlightStyle)>,
+ },
+}
+
enum InlineCompletion {
Edit(Vec<(Range<Anchor>, String)>),
Move(Anchor),
@@ -2458,6 +2473,9 @@ impl Editor {
}
if self.hide_context_menu(cx).is_some() {
+ if self.has_active_inline_completion() {
+ self.update_visible_inline_completion(cx);
+ }
return true;
}
@@ -3704,21 +3722,17 @@ impl Editor {
completions.into(),
aside_was_displayed,
);
+
menu.filter(query.as_deref(), cx.background_executor().clone())
.await;
- if menu.matches.is_empty() {
- None
- } else {
- Some(menu)
- }
+ menu.visible().then_some(menu)
} else {
None
};
editor.update(&mut cx, |editor, cx| {
- let mut context_menu = editor.context_menu.borrow_mut();
- match context_menu.as_ref() {
+ match editor.context_menu.borrow().as_ref() {
None => {}
Some(CodeContextMenu::Completions(prev_menu)) => {
if prev_menu.id > id {
@@ -3731,14 +3745,20 @@ impl Editor {
if editor.focus_handle.is_focused(cx) && menu.is_some() {
let mut menu = menu.unwrap();
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
- *context_menu = Some(CodeContextMenu::Completions(menu));
- drop(context_menu);
+
+ if let Some(hint) = editor.inline_completion_menu_hint(cx) {
+ editor.hide_active_inline_completion(cx);
+ menu.show_inline_completion_hint(hint);
+ }
+
+ *editor.context_menu.borrow_mut() =
+ Some(CodeContextMenu::Completions(menu));
+
cx.notify();
} else if editor.completion_tasks.len() <= 1 {
// If there are no more completion tasks and the last menu was
// empty, we should hide it. If it was already hidden, we should
// also show the copilot completion when available.
- drop(context_menu);
editor.hide_context_menu(cx);
}
})?;
@@ -3775,7 +3795,6 @@ impl Editor {
) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
use language::ToOffset as _;
- self.discard_inline_completion(true, cx);
let completions_menu =
if let CodeContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
menu
@@ -3784,8 +3803,21 @@ impl Editor {
};
let mat = completions_menu
- .matches
+ .entries
.get(item_ix.unwrap_or(completions_menu.selected_item))?;
+
+ let mat = match mat {
+ CompletionEntry::InlineCompletionHint { .. } => {
+ self.accept_inline_completion(&AcceptInlineCompletion, cx);
+ cx.stop_propagation();
+ return Some(Task::ready(Ok(())));
+ }
+ CompletionEntry::Match(mat) => {
+ self.discard_inline_completion(true, cx);
+ mat
+ }
+ };
+
let buffer_handle = completions_menu.buffer;
let completions = completions_menu.completions.borrow_mut();
let completion = completions.get(mat.candidate_id)?;
@@ -4668,6 +4700,17 @@ impl Editor {
Some(active_inline_completion.completion)
}
+ fn hide_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(active_inline_completion) = self.active_inline_completion.as_ref() {
+ self.splice_inlays(
+ active_inline_completion.inlay_ids.clone(),
+ Default::default(),
+ cx,
+ );
+ self.clear_highlights::<InlineCompletionHighlight>(cx);
+ }
+ }
+
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
let selection = self.selections.newest_anchor();
let cursor = selection.head();
@@ -4739,32 +4782,34 @@ impl Editor {
invalidation_row_range = edit_start_row..cursor_row;
completion = InlineCompletion::Move(first_edit_start);
} else {
- if edits
- .iter()
- .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
- {
- let mut inlays = Vec::new();
- for (range, new_text) in &edits {
- let inlay = Inlay::inline_completion(
- post_inc(&mut self.next_inlay_id),
- range.start,
- new_text.as_str(),
+ if !self.has_active_completions_menu() {
+ if edits
+ .iter()
+ .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
+ {
+ let mut inlays = Vec::new();
+ for (range, new_text) in &edits {
+ let inlay = Inlay::inline_completion(
+ post_inc(&mut self.next_inlay_id),
+ range.start,
+ new_text.as_str(),
+ );
+ inlay_ids.push(inlay.id);
+ inlays.push(inlay);
+ }
+
+ self.splice_inlays(vec![], inlays, cx);
+ } else {
+ let background_color = cx.theme().status().deleted_background;
+ self.highlight_text::<InlineCompletionHighlight>(
+ edits.iter().map(|(range, _)| range.clone()).collect(),
+ HighlightStyle {
+ background_color: Some(background_color),
+ ..Default::default()
+ },
+ cx,
);
- inlay_ids.push(inlay.id);
- inlays.push(inlay);
}
-
- self.splice_inlays(vec![], inlays, cx);
- } else {
- let background_color = cx.theme().status().deleted_background;
- self.highlight_text::<InlineCompletionHighlight>(
- edits.iter().map(|(range, _)| range.clone()).collect(),
- HighlightStyle {
- background_color: Some(background_color),
- ..Default::default()
- },
- cx,
- );
}
invalidation_row_range = edit_start_row..edit_end_row;
@@ -4783,11 +4828,54 @@ impl Editor {
completion,
invalidation_range,
});
+
+ if self.has_active_completions_menu() {
+ if let Some(hint) = self.inline_completion_menu_hint(cx) {
+ match self.context_menu.borrow_mut().as_mut() {
+ Some(CodeContextMenu::Completions(menu)) => {
+ menu.show_inline_completion_hint(hint);
+ }
+ _ => {}
+ }
+ }
+ }
+
cx.notify();
Some(())
}
+ fn inline_completion_menu_hint(
+ &mut self,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<InlineCompletionMenuHint> {
+ if self.has_active_inline_completion() {
+ let provider_name = self.inline_completion_provider()?.display_name();
+ let editor_snapshot = self.snapshot(cx);
+
+ let text = match &self.active_inline_completion.as_ref()?.completion {
+ InlineCompletion::Edit(edits) => {
+ inline_completion_edit_text(&editor_snapshot, edits, cx)
+ }
+ InlineCompletion::Move(target) => {
+ let target_point =
+ target.to_point(&editor_snapshot.display_snapshot.buffer_snapshot);
+ let target_line = target_point.row + 1;
+ InlineCompletionText::Move(
+ format!("Jump to edit in line {}", target_line).into(),
+ )
+ }
+ };
+
+ Some(InlineCompletionMenuHint {
+ provider_name,
+ text,
+ })
+ } else {
+ None
+ }
+ }
+
fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
Some(self.inline_completion_provider.as_ref()?.provider.clone())
}
@@ -5002,6 +5090,19 @@ impl Editor {
.map_or(false, |menu| menu.visible())
}
+ #[cfg(feature = "test-support")]
+ pub fn context_menu_contains_inline_completion(&self) -> bool {
+ self.context_menu
+ .borrow()
+ .as_ref()
+ .map_or(false, |menu| match menu {
+ CodeContextMenu::Completions(menu) => menu.entries.first().map_or(false, |entry| {
+ matches!(entry, CompletionEntry::InlineCompletionHint(_))
+ }),
+ CodeContextMenu::CodeActions(_) => false,
+ })
+ }
+
fn context_menu_origin(&self, cursor_position: DisplayPoint) -> Option<ContextMenuOrigin> {
self.context_menu
.borrow()
@@ -14491,6 +14592,64 @@ pub fn diagnostic_block_renderer(
})
}
+fn inline_completion_edit_text(
+ editor_snapshot: &EditorSnapshot,
+ edits: &Vec<(Range<Anchor>, String)>,
+ cx: &WindowContext,
+) -> InlineCompletionText {
+ let edit_start = edits
+ .first()
+ .unwrap()
+ .0
+ .start
+ .to_display_point(editor_snapshot);
+
+ let mut text = String::new();
+ let mut offset = DisplayPoint::new(edit_start.row(), 0).to_offset(editor_snapshot, Bias::Left);
+ let mut highlights = Vec::new();
+ for (old_range, new_text) in edits {
+ let old_offset_range = old_range.to_offset(&editor_snapshot.buffer_snapshot);
+ text.extend(
+ editor_snapshot
+ .buffer_snapshot
+ .chunks(offset..old_offset_range.start, false)
+ .map(|chunk| chunk.text),
+ );
+ offset = old_offset_range.end;
+
+ let start = text.len();
+ text.push_str(new_text);
+ let end = text.len();
+ highlights.push((
+ start..end,
+ HighlightStyle {
+ background_color: Some(cx.theme().status().created_background),
+ ..Default::default()
+ },
+ ));
+ }
+
+ let edit_end = edits
+ .last()
+ .unwrap()
+ .0
+ .end
+ .to_display_point(editor_snapshot);
+ let end_of_line = DisplayPoint::new(edit_end.row(), editor_snapshot.line_len(edit_end.row()))
+ .to_offset(editor_snapshot, Bias::Right);
+ text.extend(
+ editor_snapshot
+ .buffer_snapshot
+ .chunks(offset..end_of_line, false)
+ .map(|chunk| chunk.text),
+ );
+
+ InlineCompletionText::Edit {
+ text: text.into(),
+ highlights,
+ }
+}
+
pub fn highlight_diagnostic_message(
diagnostic: &Diagnostic,
mut max_message_rows: Option<u8>,
@@ -8470,10 +8470,7 @@ async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
cx.update_editor(|editor, _| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
- assert_eq!(
- menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
- &["first", "last"]
- );
+ assert_eq!(completion_menu_entries(&menu.entries), &["first", "last"]);
} else {
panic!("expected completion menu to be open");
}
@@ -8566,7 +8563,7 @@ async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
assert_eq!(
- menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+ completion_menu_entries(&menu.entries),
&["r", "ret", "Range", "return"]
);
} else {
@@ -10962,11 +10959,7 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
match menu.as_ref().expect("should have the completions menu") {
CodeContextMenu::Completions(completions_menu) => {
assert_eq!(
- completions_menu
- .matches
- .iter()
- .map(|c| c.string.as_str())
- .collect::<Vec<_>>(),
+ completion_menu_entries(&completions_menu.entries),
vec!["Some(2)", "vec![2]"]
);
}
@@ -11066,7 +11059,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
assert_eq!(
- menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+ completion_menu_entries(&menu.entries),
&["bg-red", "bg-blue", "bg-yellow"]
);
} else {
@@ -11080,7 +11073,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
assert_eq!(
- menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+ completion_menu_entries(&menu.entries),
&["bg-blue", "bg-yellow"]
);
} else {
@@ -11096,16 +11089,23 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
cx.update_editor(|editor, _| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
- assert_eq!(
- menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
- &["bg-yellow"]
- );
+ assert_eq!(completion_menu_entries(&menu.entries), &["bg-yellow"]);
} else {
panic!("expected completion menu to be open");
}
});
}
+fn completion_menu_entries(entries: &[CompletionEntry]) -> Vec<&str> {
+ entries
+ .iter()
+ .flat_map(|e| match e {
+ CompletionEntry::Match(mat) => Some(mat.string.as_str()),
+ _ => None,
+ })
+ .collect()
+}
+
#[gpui::test]
async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
@@ -14363,6 +14363,175 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
);
}
+#[gpui::test]
+fn test_inline_completion_text(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ // Test case 1: Simple insertion
+ {
+ let window = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("Hello, world!", cx);
+ Editor::new(EditorMode::Full, buffer, None, true, cx)
+ });
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+
+ window
+ .update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx);
+ let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
+ let edits = vec![(edit_range, " beautiful".to_string())];
+
+ let InlineCompletionText::Edit { text, highlights } =
+ inline_completion_edit_text(&snapshot, &edits, cx)
+ else {
+ panic!("Failed to generate inline completion text");
+ };
+
+ assert_eq!(text, "Hello, beautiful world!");
+ assert_eq!(highlights.len(), 1);
+ assert_eq!(highlights[0].0, 6..16);
+ assert_eq!(
+ highlights[0].1.background_color,
+ Some(cx.theme().status().created_background)
+ );
+ })
+ .unwrap();
+ }
+
+ // Test case 2: Replacement
+ {
+ let window = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("This is a test.", cx);
+ Editor::new(EditorMode::Full, buffer, None, true, cx)
+ });
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+
+ window
+ .update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx);
+ let edits = vec![(
+ snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
+ "That".to_string(),
+ )];
+
+ let InlineCompletionText::Edit { text, highlights } =
+ inline_completion_edit_text(&snapshot, &edits, cx)
+ else {
+ panic!("Failed to generate inline completion text");
+ };
+
+ assert_eq!(text, "That is a test.");
+ assert_eq!(highlights.len(), 1);
+ assert_eq!(highlights[0].0, 0..4);
+ assert_eq!(
+ highlights[0].1.background_color,
+ Some(cx.theme().status().created_background)
+ );
+ })
+ .unwrap();
+ }
+
+ // Test case 3: Multiple edits
+ {
+ let window = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("Hello, world!", cx);
+ Editor::new(EditorMode::Full, buffer, None, true, cx)
+ });
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+
+ window
+ .update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx);
+ let edits = vec![
+ (
+ snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
+ "Greetings".into(),
+ ),
+ (
+ snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
+ " and universe".into(),
+ ),
+ ];
+
+ let InlineCompletionText::Edit { text, highlights } =
+ inline_completion_edit_text(&snapshot, &edits, cx)
+ else {
+ panic!("Failed to generate inline completion text");
+ };
+
+ assert_eq!(text, "Greetings, world and universe!");
+ assert_eq!(highlights.len(), 2);
+ assert_eq!(highlights[0].0, 0..9);
+ assert_eq!(highlights[1].0, 16..29);
+ assert_eq!(
+ highlights[0].1.background_color,
+ Some(cx.theme().status().created_background)
+ );
+ assert_eq!(
+ highlights[1].1.background_color,
+ Some(cx.theme().status().created_background)
+ );
+ })
+ .unwrap();
+ }
+
+ // Test case 4: Multiple lines with edits
+ {
+ let window = cx.add_window(|cx| {
+ let buffer =
+ MultiBuffer::build_simple("First line\nSecond line\nThird line\nFourth line", cx);
+ Editor::new(EditorMode::Full, buffer, None, true, cx)
+ });
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+
+ window
+ .update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx);
+ let edits = vec![
+ (
+ snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
+ "modified".to_string(),
+ ),
+ (
+ snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
+ "New third line".to_string(),
+ ),
+ (
+ snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
+ ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
+ " updated".to_string(),
+ ),
+ ];
+
+ let InlineCompletionText::Edit { text, highlights } =
+ inline_completion_edit_text(&snapshot, &edits, cx)
+ else {
+ panic!("Failed to generate inline completion text");
+ };
+
+ assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
+ assert_eq!(highlights.len(), 3);
+ assert_eq!(highlights[0].0, 7..15); // "modified"
+ assert_eq!(highlights[1].0, 16..30); // "New third line"
+ assert_eq!(highlights[2].0, 37..45); // " updated"
+
+ for highlight in &highlights {
+ assert_eq!(
+ highlight.1.background_color,
+ Some(cx.theme().status().created_background)
+ );
+ }
+ })
+ .unwrap();
+ }
+}
+
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point
@@ -33,11 +33,11 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnyElement, AvailableSpace, Bounds, ClickEvent, ClipboardItem,
ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler,
- Entity, FontId, GlobalElementId, HighlightStyle, Hitbox, Hsla, InteractiveElement, IntoElement,
- Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
- PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString,
- Size, StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement,
- View, ViewContext, WeakView, WindowContext,
+ Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
+ ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
+ ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
+ StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, View,
+ ViewContext, WeakView, WindowContext,
};
use itertools::Itertools;
use language::{
@@ -2967,6 +2967,10 @@ impl EditorElement {
}
}
InlineCompletion::Edit(edits) => {
+ if self.editor.read(cx).has_active_completions_menu() {
+ return None;
+ }
+
let edit_start = edits
.first()
.unwrap()
@@ -2990,7 +2994,11 @@ impl EditorElement {
return None;
}
- let (text, highlights) = inline_completion_popover_text(editor_snapshot, edits, cx);
+ let crate::InlineCompletionText::Edit { text, highlights } =
+ crate::inline_completion_edit_text(editor_snapshot, edits, cx)
+ else {
+ return None;
+ };
let line_count = text.lines().count() + 1;
let longest_row =
@@ -3010,7 +3018,7 @@ impl EditorElement {
};
let styled_text =
- gpui::StyledText::new(text).with_highlights(&style.text, highlights);
+ gpui::StyledText::new(text.clone()).with_highlights(&style.text, highlights);
let mut element = div()
.bg(cx.theme().colors().editor_background)
@@ -4519,61 +4527,6 @@ fn jump_data(
}
}
-fn inline_completion_popover_text(
- editor_snapshot: &EditorSnapshot,
- edits: &Vec<(Range<Anchor>, String)>,
- cx: &WindowContext,
-) -> (String, Vec<(Range<usize>, HighlightStyle)>) {
- let edit_start = edits
- .first()
- .unwrap()
- .0
- .start
- .to_display_point(editor_snapshot);
-
- let mut text = String::new();
- let mut offset = DisplayPoint::new(edit_start.row(), 0).to_offset(editor_snapshot, Bias::Left);
- let mut highlights = Vec::new();
- for (old_range, new_text) in edits {
- let old_offset_range = old_range.to_offset(&editor_snapshot.buffer_snapshot);
- text.extend(
- editor_snapshot
- .buffer_snapshot
- .chunks(offset..old_offset_range.start, false)
- .map(|chunk| chunk.text),
- );
- offset = old_offset_range.end;
-
- let start = text.len();
- text.push_str(new_text);
- let end = text.len();
- highlights.push((
- start..end,
- HighlightStyle {
- background_color: Some(cx.theme().status().created_background),
- ..Default::default()
- },
- ));
- }
-
- let edit_end = edits
- .last()
- .unwrap()
- .0
- .end
- .to_display_point(editor_snapshot);
- let end_of_line = DisplayPoint::new(edit_end.row(), editor_snapshot.line_len(edit_end.row()))
- .to_offset(editor_snapshot, Bias::Right);
- text.extend(
- editor_snapshot
- .buffer_snapshot
- .chunks(offset..end_of_line, false)
- .map(|chunk| chunk.text),
- );
-
- (text, highlights)
-}
-
fn all_edits_insertions_or_deletions(
edits: &Vec<(Range<Anchor>, String)>,
snapshot: &MultiBufferSnapshot,
@@ -7323,161 +7276,6 @@ mod tests {
}
}
- #[gpui::test]
- fn test_inline_completion_popover_text(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
-
- // Test case 1: Simple insertion
- {
- let window = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("Hello, world!", cx);
- Editor::new(EditorMode::Full, buffer, None, true, cx)
- });
- let cx = &mut VisualTestContext::from_window(*window, cx);
-
- window
- .update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
- let edits = vec![(edit_range, " beautiful".to_string())];
-
- let (text, highlights) = inline_completion_popover_text(&snapshot, &edits, cx);
-
- assert_eq!(text, "Hello, beautiful world!");
- assert_eq!(highlights.len(), 1);
- assert_eq!(highlights[0].0, 6..16);
- assert_eq!(
- highlights[0].1.background_color,
- Some(cx.theme().status().created_background)
- );
- })
- .unwrap();
- }
-
- // Test case 2: Replacement
- {
- let window = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("This is a test.", cx);
- Editor::new(EditorMode::Full, buffer, None, true, cx)
- });
- let cx = &mut VisualTestContext::from_window(*window, cx);
-
- window
- .update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let edits = vec![(
- snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 4)),
- "That".to_string(),
- )];
-
- let (text, highlights) = inline_completion_popover_text(&snapshot, &edits, cx);
-
- assert_eq!(text, "That is a test.");
- assert_eq!(highlights.len(), 1);
- assert_eq!(highlights[0].0, 0..4);
- assert_eq!(
- highlights[0].1.background_color,
- Some(cx.theme().status().created_background)
- );
- })
- .unwrap();
- }
-
- // Test case 3: Multiple edits
- {
- let window = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("Hello, world!", cx);
- Editor::new(EditorMode::Full, buffer, None, true, cx)
- });
- let cx = &mut VisualTestContext::from_window(*window, cx);
-
- window
- .update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let edits = vec![
- (
- snapshot.buffer_snapshot.anchor_after(Point::new(0, 0))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 5)),
- "Greetings".into(),
- ),
- (
- snapshot.buffer_snapshot.anchor_after(Point::new(0, 12))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(0, 12)),
- " and universe".into(),
- ),
- ];
-
- let (text, highlights) = inline_completion_popover_text(&snapshot, &edits, cx);
-
- assert_eq!(text, "Greetings, world and universe!");
- assert_eq!(highlights.len(), 2);
- assert_eq!(highlights[0].0, 0..9);
- assert_eq!(highlights[1].0, 16..29);
- assert_eq!(
- highlights[0].1.background_color,
- Some(cx.theme().status().created_background)
- );
- assert_eq!(
- highlights[1].1.background_color,
- Some(cx.theme().status().created_background)
- );
- })
- .unwrap();
- }
-
- // Test case 4: Multiple lines with edits
- {
- let window = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(
- "First line\nSecond line\nThird line\nFourth line",
- cx,
- );
- Editor::new(EditorMode::Full, buffer, None, true, cx)
- });
- let cx = &mut VisualTestContext::from_window(*window, cx);
-
- window
- .update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let edits = vec![
- (
- snapshot.buffer_snapshot.anchor_before(Point::new(1, 7))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(1, 11)),
- "modified".to_string(),
- ),
- (
- snapshot.buffer_snapshot.anchor_before(Point::new(2, 0))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(2, 10)),
- "New third line".to_string(),
- ),
- (
- snapshot.buffer_snapshot.anchor_before(Point::new(3, 6))
- ..snapshot.buffer_snapshot.anchor_before(Point::new(3, 6)),
- " updated".to_string(),
- ),
- ];
-
- let (text, highlights) = inline_completion_popover_text(&snapshot, &edits, cx);
-
- assert_eq!(text, "Second modified\nNew third line\nFourth updated line");
- assert_eq!(highlights.len(), 3);
- assert_eq!(highlights[0].0, 7..15); // "modified"
- assert_eq!(highlights[1].0, 16..30); // "New third line"
- assert_eq!(highlights[2].0, 37..45); // " updated"
-
- for highlight in &highlights {
- assert_eq!(
- highlight.1.background_color,
- Some(cx.theme().status().created_background)
- );
- }
- })
- .unwrap();
- }
- }
-
fn collect_invisibles_from_new_editor(
cx: &mut TestAppContext,
editor_mode: EditorMode,
@@ -317,6 +317,10 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
"fake-completion-provider"
}
+ fn display_name() -> &'static str {
+ "Fake Completion Provider"
+ }
+
fn is_enabled(
&self,
_buffer: &gpui::Model<language::Buffer>,
@@ -19,6 +19,7 @@ pub struct InlineCompletion {
pub trait InlineCompletionProvider: 'static + Sized {
fn name() -> &'static str;
+ fn display_name() -> &'static str;
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -51,6 +52,7 @@ pub trait InlineCompletionProvider: 'static + Sized {
pub trait InlineCompletionProviderHandle {
fn name(&self) -> &'static str;
+ fn display_name(&self) -> &'static str;
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -89,6 +91,10 @@ where
T::name()
}
+ fn display_name(&self) -> &'static str {
+ T::display_name()
+ }
+
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -98,6 +98,10 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
"supermaven"
}
+ fn display_name() -> &'static str {
+ "Supermaven"
+ }
+
fn is_enabled(&self, buffer: &Model<Buffer>, cursor_position: Anchor, cx: &AppContext) -> bool {
if !self.supermaven.read(cx).is_enabled() {
return false;
@@ -930,6 +930,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
"zeta"
}
+ fn display_name() -> &'static str {
+ "Zeta"
+ }
+
fn is_enabled(
&self,
buffer: &Model<Buffer>,