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 gpui::{
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior,
Model, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
UniformListScrollHandle, ViewContext, WeakView,
div, pulsating_between, px, uniform_list, Animation, AnimationExt, AnyElement,
BackgroundExecutor, Div, FontWeight, ListSizingBehavior, Model, ScrollStrategy, SharedString,
Size, StrikethroughStyle, StyledText, UniformListScrollHandle, ViewContext, WeakView,
};
use language::Buffer;
use language::{CodeLabel, Documentation};
@ -10,6 +10,8 @@ use lsp::LanguageServerId;
use multi_buffer::{Anchor, ExcerptId};
use ordered_float::OrderedFloat;
use project::{CodeAction, Completion, TaskSourceKind};
use settings::Settings;
use std::time::Duration;
use std::{
cell::RefCell,
cmp::{min, Reverse},
@ -333,7 +335,6 @@ impl CompletionsMenu {
entries[0] = hint;
}
_ => {
self.selected_item += 1;
entries.insert(0, hint);
}
}
@ -461,10 +462,7 @@ impl CompletionsMenu {
len
}
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint {
provider_name,
..
}) => provider_name.len(),
CompletionEntry::InlineCompletionHint(hint) => hint.label().chars().count(),
})
.map(|(ix, _)| ix);
drop(completions);
@ -488,6 +486,12 @@ impl CompletionsMenu {
.enumerate()
.map(|(ix, mat)| {
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 {
CompletionEntry::Match(mat) => {
let candidate_id = mat.candidate_id;
@ -571,20 +575,57 @@ impl CompletionsMenu {
.end_slot::<Label>(documentation_label),
)
}
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint {
provider_name,
..
}) => div().min_w(px(250.)).max_w(px(500.)).child(
CompletionEntry::InlineCompletionHint(
hint @ InlineCompletionMenuHint::None,
) => 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(
StyledText::new(format!(
"{} Completion",
SharedString::new_static(provider_name)
))
.with_highlights(&style.text, None),
base_label.child(
StyledText::new(hint.label())
.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| {
cx.stop_propagation();
@ -641,19 +682,20 @@ impl CompletionsMenu {
Documentation::Undocumented => return None,
}
}
CompletionEntry::InlineCompletionHint(hint) => match &hint.text {
InlineCompletionText::Edit { text, highlights } => div()
.mx_1()
.rounded(px(6.))
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.child(
gpui::StyledText::new(text.clone())
.with_highlights(&style.text, highlights.clone()),
),
InlineCompletionText::Move(text) => div().child(text.clone()),
},
CompletionEntry::InlineCompletionHint(InlineCompletionMenuHint::Loaded { text }) => {
match text {
InlineCompletionText::Edit { text, highlights } => div()
.mx_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()),
}
}
CompletionEntry::InlineCompletionHint(_) => return None,
};
Some(

View file

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