Fix inconsistencies in "Transform" vs "Generate" tooltips for assistant v2 (#22160)

Also makes the inline assistant and inline terminal assistant share a
bunch more code.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
This commit is contained in:
Richard Feldman 2024-12-18 07:10:55 -05:00 committed by GitHub
parent 6192c83f23
commit 4bfc107e3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 230 additions and 220 deletions

View file

@ -6,6 +6,7 @@ mod context_picker;
mod context_store; mod context_store;
mod context_strip; mod context_strip;
mod inline_assistant; mod inline_assistant;
mod inline_prompt_editor;
mod message_editor; mod message_editor;
mod prompts; mod prompts;
mod streaming_diff; mod streaming_diff;

View file

@ -2,6 +2,9 @@ use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker; use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip; use crate::context_strip::ContextStrip;
use crate::inline_prompt_editor::{
render_cancel_button, CodegenStatus, PromptEditorEvent, PromptMode,
};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{ use crate::{
assistant_settings::AssistantSettings, assistant_settings::AssistantSettings,
@ -652,7 +655,7 @@ impl InlineAssistant {
PromptEditorEvent::StopRequested => { PromptEditorEvent::StopRequested => {
self.stop_assist(assist_id, cx); self.stop_assist(assist_id, cx);
} }
PromptEditorEvent::ConfirmRequested => { PromptEditorEvent::ConfirmRequested { execute: _ } => {
self.finish_assist(assist_id, false, cx); self.finish_assist(assist_id, false, cx);
} }
PromptEditorEvent::CancelRequested => { PromptEditorEvent::CancelRequested => {
@ -661,6 +664,9 @@ impl InlineAssistant {
PromptEditorEvent::DismissRequested => { PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, cx); self.dismiss_assist(assist_id, cx);
} }
PromptEditorEvent::Resized { .. } => {
// This only matters for the terminal inline
}
} }
} }
@ -1475,14 +1481,6 @@ impl InlineAssistGroupId {
} }
} }
enum PromptEditorEvent {
StartRequested,
StopRequested,
ConfirmRequested,
CancelRequested,
DismissRequested,
}
struct PromptEditor { struct PromptEditor {
id: InlineAssistId, id: InlineAssistId,
editor: View<Editor>, editor: View<Editor>,
@ -1510,93 +1508,20 @@ impl Render for PromptEditor {
if codegen.alternative_count(cx) > 1 { if codegen.alternative_count(cx) > 1 {
buttons.push(self.render_cycle_controls(cx)); buttons.push(self.render_cycle_controls(cx));
} }
let prompt_mode = if codegen.is_insertion {
PromptMode::Generate {
supports_execute: false,
}
} else {
PromptMode::Transform
};
let status = codegen.status(cx); buttons.extend(render_cancel_button(
buttons.extend(match status { codegen.status(cx).into(),
CodegenStatus::Idle => { self.edited_since_done,
vec![ prompt_mode,
IconButton::new("cancel", IconName::Close) cx,
.icon_color(Color::Muted) ));
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
)
.into_any_element(),
IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
)
.into_any_element(),
]
}
CodegenStatus::Pending => {
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
)
.into_any_element(),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Interrupt Transformation",
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element(),
]
}
CodegenStatus::Error(_) | CodegenStatus::Done => {
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
)
.into_any_element(),
if self.edited_since_done || matches!(status, CodegenStatus::Error(_)) {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Restart Transformation",
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element()
} else {
IconButton::new("confirm", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx))
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested);
}))
.into_any_element()
},
]
}
});
v_flex() v_flex()
.border_y_1() .border_y_1()
@ -1747,7 +1672,7 @@ impl PromptEditor {
// always show the cursor (even when it isn't focused) because // always show the cursor (even when it isn't focused) because
// typing in one will make what you typed appear in all of them. // typing in one will make what you typed appear in all of them.
editor.set_show_cursor_when_unfocused(true, cx); editor.set_show_cursor_when_unfocused(true, cx);
editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx)), cx); editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
@ -1815,7 +1740,7 @@ impl PromptEditor {
self.editor = cx.new_view(|cx| { self.editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx); let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx)), cx); editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
editor.set_placeholder_text("Add a prompt…", cx); editor.set_placeholder_text("Add a prompt…", cx);
editor.set_text(prompt, cx); editor.set_text(prompt, cx);
if focus { if focus {
@ -1826,14 +1751,17 @@ impl PromptEditor {
self.subscribe_to_editor(cx); self.subscribe_to_editor(cx);
} }
fn placeholder_text(codegen: &Codegen) -> String { fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
let action = if codegen.is_insertion { let action = if codegen.is_insertion {
"Generate" "Generate"
} else { } else {
"Transform" "Transform"
}; };
let assistant_panel_keybinding = ui::text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} to chat ― "))
.unwrap_or_default();
format!("{action}… ↓↑ for history") format!("{action}({assistant_panel_keybinding}↓↑ for history)")
} }
fn prompt(&self, cx: &AppContext) -> String { fn prompt(&self, cx: &AppContext) -> String {
@ -1950,7 +1878,7 @@ impl PromptEditor {
if self.edited_since_done { if self.edited_since_done {
cx.emit(PromptEditorEvent::StartRequested); cx.emit(PromptEditorEvent::StartRequested);
} else { } else {
cx.emit(PromptEditorEvent::ConfirmRequested); cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
} }
} }
CodegenStatus::Error(_) => { CodegenStatus::Error(_) => {
@ -2566,13 +2494,6 @@ pub struct CodegenAlternative {
message_id: Option<String>, message_id: Option<String>,
} }
enum CodegenStatus {
Idle,
Pending,
Done,
Error(anyhow::Error),
}
#[derive(Default)] #[derive(Default)]
struct Diff { struct Diff {
deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>, deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,

View file

@ -0,0 +1,191 @@
use gpui::{AnyElement, EventEmitter};
use ui::{prelude::*, IconButtonShape, Tooltip};
pub enum CodegenStatus {
Idle,
Pending,
Done,
Error(anyhow::Error),
}
/// This is just CodegenStatus without the anyhow::Error, which causes a lifetime issue for rendering the Cancel button.
#[derive(Copy, Clone)]
pub enum CancelButtonState {
Idle,
Pending,
Done,
Error,
}
impl Into<CancelButtonState> for &CodegenStatus {
fn into(self) -> CancelButtonState {
match self {
CodegenStatus::Idle => CancelButtonState::Idle,
CodegenStatus::Pending => CancelButtonState::Pending,
CodegenStatus::Done => CancelButtonState::Done,
CodegenStatus::Error(_) => CancelButtonState::Error,
}
}
}
#[derive(Copy, Clone)]
pub enum PromptMode {
Generate { supports_execute: bool },
Transform,
}
impl PromptMode {
fn start_label(self) -> &'static str {
match self {
PromptMode::Generate { .. } => "Generate",
PromptMode::Transform => "Transform",
}
}
fn tooltip_interrupt(self) -> &'static str {
match self {
PromptMode::Generate { .. } => "Interrupt Generation",
PromptMode::Transform => "Interrupt Transform",
}
}
fn tooltip_restart(self) -> &'static str {
match self {
PromptMode::Generate { .. } => "Restart Generation",
PromptMode::Transform => "Restart Transform",
}
}
fn tooltip_accept(self) -> &'static str {
match self {
PromptMode::Generate { .. } => "Accept Generation",
PromptMode::Transform => "Accept Transform",
}
}
}
pub fn render_cancel_button<T: EventEmitter<PromptEditorEvent>>(
cancel_button_state: CancelButtonState,
edited_since_done: bool,
mode: PromptMode,
cx: &mut ViewContext<T>,
) -> Vec<AnyElement> {
match cancel_button_state {
CancelButtonState::Idle => {
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
Button::new("start", mode.start_label())
.icon(IconName::Return)
.icon_color(Color::Muted)
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element(),
]
}
CancelButtonState::Pending => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element(),
],
CancelButtonState::Done | CancelButtonState::Error => {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element();
let has_error = matches!(cancel_button_state, CancelButtonState::Error);
if has_error || edited_since_done {
vec![
cancel,
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element(),
]
} else {
let mut buttons = vec![
cancel,
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element(),
];
match mode {
PromptMode::Generate { supports_execute } => {
if supports_execute {
buttons.push(
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested {
execute: true,
});
}))
.into_any_element(),
)
}
}
PromptMode::Transform => {}
}
buttons
}
}
}
}
pub enum PromptEditorEvent {
StartRequested,
StopRequested,
ConfirmRequested { execute: bool },
CancelRequested,
DismissRequested,
Resized { height_in_lines: u8 },
}

View file

@ -1,11 +1,12 @@
use crate::assistant_settings::AssistantSettings;
use crate::context::attach_context_to_message; use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker; use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip; use crate::context_strip::ContextStrip;
use crate::inline_prompt_editor::{CodegenStatus, PromptEditorEvent, PromptMode};
use crate::prompts::PromptBuilder; use crate::prompts::PromptBuilder;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::ToggleContextPicker; use crate::ToggleContextPicker;
use crate::{assistant_settings::AssistantSettings, inline_prompt_editor::render_cancel_button};
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use client::telemetry::Telemetry; use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
@ -448,15 +449,6 @@ impl TerminalInlineAssist {
} }
} }
enum PromptEditorEvent {
StartRequested,
StopRequested,
ConfirmRequested { execute: bool },
CancelRequested,
DismissRequested,
Resized { height_in_lines: u8 },
}
struct PromptEditor { struct PromptEditor {
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
height_in_lines: u8, height_in_lines: u8,
@ -477,104 +469,16 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor { impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status;
let mut buttons = Vec::new(); let mut buttons = Vec::new();
buttons.extend(match status { buttons.extend(render_cancel_button(
CodegenStatus::Idle => vec![ (&self.codegen.read(cx).status).into(),
IconButton::new("cancel", IconName::Close) self.edited_since_done,
.icon_color(Color::Muted) PromptMode::Generate {
.shape(IconButtonShape::Square) supports_execute: true,
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx)) },
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested))) cx,
.into_any_element(), ));
IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element(),
],
CodegenStatus::Pending => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Interrupt Generation",
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element(),
],
CodegenStatus::Error(_) | CodegenStatus::Done => {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element();
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
vec![
cancel,
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Restart Generation",
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element(),
]
} else {
vec![
cancel,
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element(),
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}))
.into_any_element(),
]
}
}
});
v_flex() v_flex()
.border_y_1() .border_y_1()
@ -1097,10 +1001,3 @@ impl Codegen {
} }
} }
} }
enum CodegenStatus {
Idle,
Pending,
Done,
Error(anyhow::Error),
}