1use gpui::{AnyElement, EventEmitter};
2use ui::{prelude::*, IconButtonShape, Tooltip};
3
4pub enum CodegenStatus {
5 Idle,
6 Pending,
7 Done,
8 Error(anyhow::Error),
9}
10
11/// This is just CodegenStatus without the anyhow::Error, which causes a lifetime issue for rendering the Cancel button.
12#[derive(Copy, Clone)]
13pub enum CancelButtonState {
14 Idle,
15 Pending,
16 Done,
17 Error,
18}
19
20impl Into<CancelButtonState> for &CodegenStatus {
21 fn into(self) -> CancelButtonState {
22 match self {
23 CodegenStatus::Idle => CancelButtonState::Idle,
24 CodegenStatus::Pending => CancelButtonState::Pending,
25 CodegenStatus::Done => CancelButtonState::Done,
26 CodegenStatus::Error(_) => CancelButtonState::Error,
27 }
28 }
29}
30
31#[derive(Copy, Clone)]
32pub enum PromptMode {
33 Generate { supports_execute: bool },
34 Transform,
35}
36
37impl PromptMode {
38 fn start_label(self) -> &'static str {
39 match self {
40 PromptMode::Generate { .. } => "Generate",
41 PromptMode::Transform => "Transform",
42 }
43 }
44 fn tooltip_interrupt(self) -> &'static str {
45 match self {
46 PromptMode::Generate { .. } => "Interrupt Generation",
47 PromptMode::Transform => "Interrupt Transform",
48 }
49 }
50
51 fn tooltip_restart(self) -> &'static str {
52 match self {
53 PromptMode::Generate { .. } => "Restart Generation",
54 PromptMode::Transform => "Restart Transform",
55 }
56 }
57
58 fn tooltip_accept(self) -> &'static str {
59 match self {
60 PromptMode::Generate { .. } => "Accept Generation",
61 PromptMode::Transform => "Accept Transform",
62 }
63 }
64}
65
66pub fn render_cancel_button<T: EventEmitter<PromptEditorEvent>>(
67 cancel_button_state: CancelButtonState,
68 edited_since_done: bool,
69 mode: PromptMode,
70 cx: &mut ViewContext<T>,
71) -> Vec<AnyElement> {
72 match cancel_button_state {
73 CancelButtonState::Idle => {
74 vec![
75 IconButton::new("cancel", IconName::Close)
76 .icon_color(Color::Muted)
77 .shape(IconButtonShape::Square)
78 .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
79 .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
80 .into_any_element(),
81 Button::new("start", mode.start_label())
82 .icon(IconName::Return)
83 .icon_color(Color::Muted)
84 .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
85 .into_any_element(),
86 ]
87 }
88 CancelButtonState::Pending => vec![
89 IconButton::new("cancel", IconName::Close)
90 .icon_color(Color::Muted)
91 .shape(IconButtonShape::Square)
92 .tooltip(|cx| Tooltip::text("Cancel Assist", cx))
93 .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
94 .into_any_element(),
95 IconButton::new("stop", IconName::Stop)
96 .icon_color(Color::Error)
97 .shape(IconButtonShape::Square)
98 .tooltip(move |cx| {
99 Tooltip::with_meta(
100 mode.tooltip_interrupt(),
101 Some(&menu::Cancel),
102 "Changes won't be discarded",
103 cx,
104 )
105 })
106 .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
107 .into_any_element(),
108 ],
109 CancelButtonState::Done | CancelButtonState::Error => {
110 let cancel = IconButton::new("cancel", IconName::Close)
111 .icon_color(Color::Muted)
112 .shape(IconButtonShape::Square)
113 .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
114 .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
115 .into_any_element();
116
117 let has_error = matches!(cancel_button_state, CancelButtonState::Error);
118 if has_error || edited_since_done {
119 vec![
120 cancel,
121 IconButton::new("restart", IconName::RotateCw)
122 .icon_color(Color::Info)
123 .shape(IconButtonShape::Square)
124 .tooltip(move |cx| {
125 Tooltip::with_meta(
126 mode.tooltip_restart(),
127 Some(&menu::Confirm),
128 "Changes will be discarded",
129 cx,
130 )
131 })
132 .on_click(cx.listener(|_, _, cx| {
133 cx.emit(PromptEditorEvent::StartRequested);
134 }))
135 .into_any_element(),
136 ]
137 } else {
138 let mut buttons = vec![
139 cancel,
140 IconButton::new("accept", IconName::Check)
141 .icon_color(Color::Info)
142 .shape(IconButtonShape::Square)
143 .tooltip(move |cx| {
144 Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
145 })
146 .on_click(cx.listener(|_, _, cx| {
147 cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
148 }))
149 .into_any_element(),
150 ];
151
152 match mode {
153 PromptMode::Generate { supports_execute } => {
154 if supports_execute {
155 buttons.push(
156 IconButton::new("confirm", IconName::Play)
157 .icon_color(Color::Info)
158 .shape(IconButtonShape::Square)
159 .tooltip(|cx| {
160 Tooltip::for_action(
161 "Execute Generated Command",
162 &menu::SecondaryConfirm,
163 cx,
164 )
165 })
166 .on_click(cx.listener(|_, _, cx| {
167 cx.emit(PromptEditorEvent::ConfirmRequested {
168 execute: true,
169 });
170 }))
171 .into_any_element(),
172 )
173 }
174 }
175 PromptMode::Transform => {}
176 }
177
178 buttons
179 }
180 }
181 }
182}
183
184pub enum PromptEditorEvent {
185 StartRequested,
186 StopRequested,
187 ConfirmRequested { execute: bool },
188 CancelRequested,
189 DismissRequested,
190 Resized { height_in_lines: u8 },
191}