mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 05:51:14 +00:00
terminal: Handle menu::SecondaryConfirm properly (#48764)
Closes #48642. - [ ] Tests or screenshots needed? - Tests written by AI. Not sure if the test suite is setup correctly. I’ll come back later and see if the tests are utter BS or not. - [ ] Code Reviewed - [x] Manual QA Release Notes: - Handle `menu::SecondaryConfirm` properly in the terminal Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
This commit is contained in:
parent
2ca94a6032
commit
6b27522843
1 changed files with 218 additions and 1 deletions
|
|
@ -166,6 +166,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||
.child(
|
||||
h_flex()
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::secondary_confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
|
|
@ -530,6 +531,20 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.handle_confirm(false, cx);
|
||||
}
|
||||
|
||||
fn secondary_confirm(
|
||||
&mut self,
|
||||
_: &menu::SecondaryConfirm,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let execute = matches!(self.mode, PromptEditorMode::Terminal { .. });
|
||||
self.handle_confirm(execute, cx);
|
||||
}
|
||||
|
||||
fn handle_confirm(&mut self, execute: bool, cx: &mut Context<Self>) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle => {
|
||||
self.fire_started_telemetry(cx);
|
||||
|
|
@ -541,7 +556,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||
self.fire_started_telemetry(cx);
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
} else {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute });
|
||||
}
|
||||
}
|
||||
CodegenStatus::Error(_) => {
|
||||
|
|
@ -1637,3 +1652,205 @@ fn insert_message_creases(
|
|||
editor.fold_creases(creases, false, window, cx);
|
||||
ids
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use agent::ThreadStore;
|
||||
use collections::VecDeque;
|
||||
use fs::FakeFs;
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use language::Buffer;
|
||||
use project::Project;
|
||||
use settings::SettingsStore;
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use terminal::TerminalBuilder;
|
||||
use terminal::terminal_settings::CursorShape;
|
||||
use util::path;
|
||||
use util::paths::PathStyle;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
theme_settings::init(theme::LoadThemes::JustBase, cx);
|
||||
editor::init(cx);
|
||||
release_channel::init(semver::Version::new(0, 0, 0), cx);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
prompt_store::init(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn build_terminal_prompt_editor(
|
||||
workspace: &Entity<Workspace>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Entity<PromptEditor<TerminalCodegen>> {
|
||||
let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
|
||||
let terminal = cx.update(|_window, cx| {
|
||||
cx.new(|cx| {
|
||||
TerminalBuilder::new_display_only(
|
||||
CursorShape::default(),
|
||||
settings::AlternateScroll::On,
|
||||
None,
|
||||
0,
|
||||
cx.background_executor(),
|
||||
PathStyle::local(),
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
})
|
||||
});
|
||||
|
||||
let session_id = Uuid::new_v4();
|
||||
let codegen =
|
||||
cx.update(|_window, cx| cx.new(|_| TerminalCodegen::new(terminal, session_id)));
|
||||
|
||||
let prompt_buffer = cx.update(|_window, cx| {
|
||||
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local("", cx)), cx))
|
||||
});
|
||||
|
||||
let project = workspace.update(cx, |workspace, _cx| workspace.project().downgrade());
|
||||
|
||||
cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
PromptEditor::new_terminal(
|
||||
TerminalInlineAssistId::default(),
|
||||
VecDeque::new(),
|
||||
prompt_buffer,
|
||||
codegen,
|
||||
session_id,
|
||||
fs,
|
||||
thread_store,
|
||||
None,
|
||||
project,
|
||||
workspace.downgrade(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_secondary_confirm_emits_execute_true_in_terminal_mode(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/project", serde_json::json!({"file": ""}))
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let prompt_editor = build_terminal_prompt_editor(&workspace, cx);
|
||||
|
||||
// Set the codegen status to Done so that confirm logic emits ConfirmRequested.
|
||||
prompt_editor.update(cx, |editor, cx| {
|
||||
editor.codegen().update(cx, |codegen, _| {
|
||||
codegen.status = CodegenStatus::Done;
|
||||
});
|
||||
editor.edited_since_done = false;
|
||||
});
|
||||
|
||||
let events: Rc<RefCell<Vec<PromptEditorEvent>>> = Rc::new(RefCell::new(Vec::new()));
|
||||
let events_clone = events.clone();
|
||||
cx.update(|_window, cx| {
|
||||
cx.subscribe(&prompt_editor, move |_, event: &PromptEditorEvent, _cx| {
|
||||
events_clone.borrow_mut().push(match event {
|
||||
PromptEditorEvent::ConfirmRequested { execute } => {
|
||||
PromptEditorEvent::ConfirmRequested { execute: *execute }
|
||||
}
|
||||
PromptEditorEvent::StartRequested => PromptEditorEvent::StartRequested,
|
||||
PromptEditorEvent::StopRequested => PromptEditorEvent::StopRequested,
|
||||
PromptEditorEvent::CancelRequested => PromptEditorEvent::CancelRequested,
|
||||
PromptEditorEvent::Resized { height_in_lines } => PromptEditorEvent::Resized {
|
||||
height_in_lines: *height_in_lines,
|
||||
},
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
// Dispatch menu::SecondaryConfirm (cmd-enter).
|
||||
prompt_editor.update(cx, |editor, cx| {
|
||||
editor.handle_confirm(true, cx);
|
||||
});
|
||||
|
||||
let events = events.borrow();
|
||||
assert_eq!(events.len(), 1, "Expected exactly one event");
|
||||
assert!(
|
||||
matches!(
|
||||
events[0],
|
||||
PromptEditorEvent::ConfirmRequested { execute: true }
|
||||
),
|
||||
"Expected ConfirmRequested with execute: true, got {:?}",
|
||||
match &events[0] {
|
||||
PromptEditorEvent::ConfirmRequested { execute } =>
|
||||
format!("ConfirmRequested {{ execute: {} }}", execute),
|
||||
_ => "other event".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_confirm_emits_execute_false_in_terminal_mode(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/project", serde_json::json!({"file": ""}))
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let prompt_editor = build_terminal_prompt_editor(&workspace, cx);
|
||||
|
||||
prompt_editor.update(cx, |editor, cx| {
|
||||
editor.codegen().update(cx, |codegen, _| {
|
||||
codegen.status = CodegenStatus::Done;
|
||||
});
|
||||
editor.edited_since_done = false;
|
||||
});
|
||||
|
||||
let events: Rc<RefCell<Vec<PromptEditorEvent>>> = Rc::new(RefCell::new(Vec::new()));
|
||||
let events_clone = events.clone();
|
||||
cx.update(|_window, cx| {
|
||||
cx.subscribe(&prompt_editor, move |_, event: &PromptEditorEvent, _cx| {
|
||||
events_clone.borrow_mut().push(match event {
|
||||
PromptEditorEvent::ConfirmRequested { execute } => {
|
||||
PromptEditorEvent::ConfirmRequested { execute: *execute }
|
||||
}
|
||||
PromptEditorEvent::StartRequested => PromptEditorEvent::StartRequested,
|
||||
PromptEditorEvent::StopRequested => PromptEditorEvent::StopRequested,
|
||||
PromptEditorEvent::CancelRequested => PromptEditorEvent::CancelRequested,
|
||||
PromptEditorEvent::Resized { height_in_lines } => PromptEditorEvent::Resized {
|
||||
height_in_lines: *height_in_lines,
|
||||
},
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
// Dispatch menu::Confirm (enter) — should emit execute: false even in terminal mode.
|
||||
prompt_editor.update(cx, |editor, cx| {
|
||||
editor.handle_confirm(false, cx);
|
||||
});
|
||||
|
||||
let events = events.borrow();
|
||||
assert_eq!(events.len(), 1, "Expected exactly one event");
|
||||
assert!(
|
||||
matches!(
|
||||
events[0],
|
||||
PromptEditorEvent::ConfirmRequested { execute: false }
|
||||
),
|
||||
"Expected ConfirmRequested with execute: false"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue