crates/assistant2/src/assistant.rs π
@@ -6,6 +6,7 @@ mod context_picker;
mod context_store;
mod context_strip;
mod inline_assistant;
+mod inline_prompt_editor;
mod message_editor;
mod prompts;
mod streaming_diff;
Richard Feldman , Agus , and Agus Zubiaga created
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>
crates/assistant2/src/assistant.rs | 1
crates/assistant2/src/inline_assistant.rs | 135 ++--------
crates/assistant2/src/inline_prompt_editor.rs | 191 ++++++++++++++++
crates/assistant2/src/terminal_inline_assistant.rs | 123 ---------
4 files changed, 230 insertions(+), 220 deletions(-)
@@ -6,6 +6,7 @@ mod context_picker;
mod context_store;
mod context_strip;
mod inline_assistant;
+mod inline_prompt_editor;
mod message_editor;
mod prompts;
mod streaming_diff;
@@ -2,6 +2,9 @@ use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
+use crate::inline_prompt_editor::{
+ render_cancel_button, CodegenStatus, PromptEditorEvent, PromptMode,
+};
use crate::thread_store::ThreadStore;
use crate::{
assistant_settings::AssistantSettings,
@@ -652,7 +655,7 @@ impl InlineAssistant {
PromptEditorEvent::StopRequested => {
self.stop_assist(assist_id, cx);
}
- PromptEditorEvent::ConfirmRequested => {
+ PromptEditorEvent::ConfirmRequested { execute: _ } => {
self.finish_assist(assist_id, false, cx);
}
PromptEditorEvent::CancelRequested => {
@@ -661,6 +664,9 @@ impl InlineAssistant {
PromptEditorEvent::DismissRequested => {
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 {
id: InlineAssistId,
editor: View<Editor>,
@@ -1510,93 +1508,20 @@ impl Render for PromptEditor {
if codegen.alternative_count(cx) > 1 {
buttons.push(self.render_cycle_controls(cx));
}
-
- let status = codegen.status(cx);
- buttons.extend(match status {
- CodegenStatus::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(),
- 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(),
- ]
+ let prompt_mode = if codegen.is_insertion {
+ PromptMode::Generate {
+ supports_execute: false,
}
- 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()
- },
- ]
- }
- });
+ } else {
+ PromptMode::Transform
+ };
+
+ buttons.extend(render_cancel_button(
+ codegen.status(cx).into(),
+ self.edited_since_done,
+ prompt_mode,
+ cx,
+ ));
v_flex()
.border_y_1()
@@ -1747,7 +1672,7 @@ impl PromptEditor {
// always show the cursor (even when it isn't focused) because
// typing in one will make what you typed appear in all of them.
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
});
let context_picker_menu_handle = PopoverMenuHandle::default();
@@ -1815,7 +1740,7 @@ impl PromptEditor {
self.editor = cx.new_view(|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_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_text(prompt, cx);
if focus {
@@ -1826,14 +1751,17 @@ impl PromptEditor {
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 {
"Generate"
} else {
"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 {
@@ -1950,7 +1878,7 @@ impl PromptEditor {
if self.edited_since_done {
cx.emit(PromptEditorEvent::StartRequested);
} else {
- cx.emit(PromptEditorEvent::ConfirmRequested);
+ cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}
}
CodegenStatus::Error(_) => {
@@ -2566,13 +2494,6 @@ pub struct CodegenAlternative {
message_id: Option<String>,
}
-enum CodegenStatus {
- Idle,
- Pending,
- Done,
- Error(anyhow::Error),
-}
-
#[derive(Default)]
struct Diff {
deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
@@ -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 },
+}
@@ -1,11 +1,12 @@
-use crate::assistant_settings::AssistantSettings;
use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
+use crate::inline_prompt_editor::{CodegenStatus, PromptEditorEvent, PromptMode};
use crate::prompts::PromptBuilder;
use crate::thread_store::ThreadStore;
use crate::ToggleContextPicker;
+use crate::{assistant_settings::AssistantSettings, inline_prompt_editor::render_cancel_button};
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
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 {
id: TerminalInlineAssistId,
height_in_lines: u8,
@@ -477,104 +469,16 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- let status = &self.codegen.read(cx).status;
let mut buttons = Vec::new();
- buttons.extend(match status {
- CodegenStatus::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(),
- 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(),
- ]
- }
- }
- });
+ buttons.extend(render_cancel_button(
+ (&self.codegen.read(cx).status).into(),
+ self.edited_since_done,
+ PromptMode::Generate {
+ supports_execute: true,
+ },
+ cx,
+ ));
v_flex()
.border_y_1()
@@ -1097,10 +1001,3 @@ impl Codegen {
}
}
}
-
-enum CodegenStatus {
- Idle,
- Pending,
- Done,
- Error(anyhow::Error),
-}