Show loading state for predictions (#23172)

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
This commit is contained in:
Antonio Scandurra 2025-01-15 14:05:18 +01:00 committed by GitHub
parent bf75b33464
commit da8e65b3e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 113 additions and 40 deletions

View file

@ -1,8 +1,8 @@
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior, div, pulsating_between, px, uniform_list, Animation, AnimationExt, AnyElement,
Model, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText, BackgroundExecutor, Div, FontWeight, ListSizingBehavior, Model, ScrollStrategy, SharedString,
UniformListScrollHandle, ViewContext, WeakView, Size, StrikethroughStyle, StyledText, UniformListScrollHandle, ViewContext, WeakView,
}; };
use language::Buffer; use language::Buffer;
use language::{CodeLabel, Documentation}; use language::{CodeLabel, Documentation};
@ -10,6 +10,8 @@ use lsp::LanguageServerId;
use multi_buffer::{Anchor, ExcerptId}; use multi_buffer::{Anchor, ExcerptId};
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use project::{CodeAction, Completion, TaskSourceKind}; use project::{CodeAction, Completion, TaskSourceKind};
use settings::Settings;
use std::time::Duration;
use std::{ use std::{
cell::RefCell, cell::RefCell,
cmp::{min, Reverse}, cmp::{min, Reverse},
@ -333,7 +335,6 @@ impl CompletionsMenu {
entries[0] = hint; entries[0] = hint;
} }
_ => { _ => {
self.selected_item += 1;
entries.insert(0, hint); entries.insert(0, hint);
} }
} }
@ -461,10 +462,7 @@ impl CompletionsMenu {
len len
} }
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint { CompletionEntry::InlineCompletionHint(hint) => hint.label().chars().count(),
provider_name,
..
}) => provider_name.len(),
}) })
.map(|(ix, _)| ix); .map(|(ix, _)| ix);
drop(completions); drop(completions);
@ -488,6 +486,12 @@ impl CompletionsMenu {
.enumerate() .enumerate()
.map(|(ix, mat)| { .map(|(ix, mat)| {
let item_ix = start_ix + ix; let item_ix = start_ix + ix;
let buffer_font = theme::ThemeSettings::get_global(cx).buffer_font.clone();
let base_label = h_flex()
.gap_1()
.child(div().font(buffer_font.clone()).child("Zed AI"))
.child(div().px_0p5().child("/").opacity(0.2));
match mat { match mat {
CompletionEntry::Match(mat) => { CompletionEntry::Match(mat) => {
let candidate_id = mat.candidate_id; let candidate_id = mat.candidate_id;
@ -571,20 +575,57 @@ impl CompletionsMenu {
.end_slot::<Label>(documentation_label), .end_slot::<Label>(documentation_label),
) )
} }
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint { CompletionEntry::InlineCompletionHint(
provider_name, hint @ InlineCompletionMenuHint::None,
.. ) => div().min_w(px(250.)).max_w(px(500.)).child(
}) => div().min_w(px(250.)).max_w(px(500.)).child(
ListItem::new("inline-completion") ListItem::new("inline-completion")
.inset(true) .inset(true)
.toggle_state(item_ix == selected_item) .toggle_state(item_ix == selected_item)
.start_slot(Icon::new(IconName::ZedPredict)) .start_slot(Icon::new(IconName::ZedPredict))
.child( .child(
StyledText::new(format!( base_label.child(
"{} Completion", StyledText::new(hint.label())
SharedString::new_static(provider_name)
))
.with_highlights(&style.text, None), .with_highlights(&style.text, None),
),
),
),
CompletionEntry::InlineCompletionHint(
hint @ InlineCompletionMenuHint::Loading,
) => div().min_w(px(250.)).max_w(px(500.)).child(
ListItem::new("inline-completion")
.inset(true)
.toggle_state(item_ix == selected_item)
.start_slot(Icon::new(IconName::ZedPredict))
.child(base_label.child({
let text_style = style.text.clone();
StyledText::new(hint.label())
.with_highlights(&text_style, None)
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(1))
.repeat()
.with_easing(pulsating_between(0.4, 0.8)),
move |text, delta| {
let mut text_style = text_style.clone();
text_style.color =
text_style.color.opacity(delta);
text.with_highlights(&text_style, None)
},
)
})),
),
CompletionEntry::InlineCompletionHint(
hint @ InlineCompletionMenuHint::Loaded { .. },
) => div().min_w(px(250.)).max_w(px(500.)).child(
ListItem::new("inline-completion")
.inset(true)
.toggle_state(item_ix == selected_item)
.start_slot(Icon::new(IconName::ZedPredict))
.child(
base_label.child(
StyledText::new(hint.label())
.with_highlights(&style.text, None),
),
) )
.on_click(cx.listener(move |editor, _event, cx| { .on_click(cx.listener(move |editor, _event, cx| {
cx.stop_propagation(); cx.stop_propagation();
@ -641,19 +682,20 @@ impl CompletionsMenu {
Documentation::Undocumented => return None, Documentation::Undocumented => return None,
} }
} }
CompletionEntry::InlineCompletionHint(hint) => match &hint.text { CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint::Loaded { text }) => {
match text {
InlineCompletionText::Edit { text, highlights } => div() InlineCompletionText::Edit { text, highlights } => div()
.mx_1() .mx_1()
.rounded(px(6.)) .rounded_md()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.child( .child(
gpui::StyledText::new(text.clone()) gpui::StyledText::new(text.clone())
.with_highlights(&style.text, highlights.clone()), .with_highlights(&style.text, highlights.clone()),
), ),
InlineCompletionText::Move(text) => div().child(text.clone()), InlineCompletionText::Move(text) => div().child(text.clone()),
}, }
}
CompletionEntry::InlineCompletionHint(_) => return None,
}; };
Some( Some(

View file

@ -459,9 +459,21 @@ pub fn make_suggestion_styles(cx: &WindowContext) -> InlineCompletionStyles {
type CompletionId = usize; type CompletionId = usize;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct InlineCompletionMenuHint { enum InlineCompletionMenuHint {
provider_name: &'static str, Loading,
text: InlineCompletionText, Loaded { text: InlineCompletionText },
None,
}
impl InlineCompletionMenuHint {
pub fn label(&self) -> &'static str {
match self {
InlineCompletionMenuHint::Loading | InlineCompletionMenuHint::Loaded { .. } => {
"Edit Prediction"
}
InlineCompletionMenuHint::None => "No Prediction",
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -3828,6 +3840,26 @@ impl Editor {
) -> Option<Task<std::result::Result<(), anyhow::Error>>> { ) -> Option<Task<std::result::Result<(), anyhow::Error>>> {
use language::ToOffset as _; use language::ToOffset as _;
{
let context_menu = self.context_menu.borrow();
if let CodeContextMenu::Completions(menu) = context_menu.as_ref()? {
let entries = menu.entries.borrow();
let entry = entries.get(item_ix.unwrap_or(menu.selected_item));
match entry {
Some(CompletionEntry::InlineCompletionHint(
InlineCompletionMenuHint::Loading,
)) => return Some(Task::ready(Ok(()))),
Some(CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint::None)) => {
drop(entries);
drop(context_menu);
self.context_menu_next(&Default::default(), cx);
return Some(Task::ready(Ok(())));
}
_ => {}
}
}
}
let completions_menu = let completions_menu =
if let CodeContextMenu::Completions(menu) = self.hide_context_menu(cx)? { if let CodeContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
menu menu
@ -3838,7 +3870,7 @@ impl Editor {
let entries = completions_menu.entries.borrow(); let entries = completions_menu.entries.borrow();
let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?; let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
let mat = match mat { let mat = match mat {
CompletionEntry::InlineCompletionHint { .. } => { CompletionEntry::InlineCompletionHint(_) => {
self.accept_inline_completion(&AcceptInlineCompletion, cx); self.accept_inline_completion(&AcceptInlineCompletion, cx);
cx.stop_propagation(); cx.stop_propagation();
return Some(Task::ready(Ok(()))); return Some(Task::ready(Ok(())));
@ -4912,8 +4944,8 @@ impl Editor {
&mut self, &mut self,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<InlineCompletionMenuHint> { ) -> Option<InlineCompletionMenuHint> {
let provider = self.inline_completion_provider()?;
if self.has_active_inline_completion() { if self.has_active_inline_completion() {
let provider_name = self.inline_completion_provider()?.display_name();
let editor_snapshot = self.snapshot(cx); let editor_snapshot = self.snapshot(cx);
let text = match &self.active_inline_completion.as_ref()?.completion { let text = match &self.active_inline_completion.as_ref()?.completion {
@ -4930,12 +4962,11 @@ impl Editor {
} }
}; };
Some(InlineCompletionMenuHint { Some(InlineCompletionMenuHint::Loaded { text })
provider_name, } else if provider.is_refreshing(cx) {
text, Some(InlineCompletionMenuHint::Loading)
})
} else { } else {
None Some(InlineCompletionMenuHint::None)
} }
} }