inline_prompt_editor.rs

  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}