diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml index 421488346d4b98505190664a531c90565e705ec2..5b2cbf58fcfb82452b1555702a2e33f95c5e082e 100644 --- a/crates/agent_ui/Cargo.toml +++ b/crates/agent_ui/Cargo.toml @@ -13,7 +13,7 @@ path = "src/agent_ui.rs" doctest = false [features] -test-support = ["assistant_text_thread/test-support", "acp_thread/test-support", "eval_utils", "gpui/test-support", "language/test-support", "reqwest_client", "workspace/test-support", "agent/test-support"] +test-support = ["assistant_text_thread/test-support", "acp_thread/test-support", "eval_utils", "gpui/test-support", "language/test-support", "reqwest_client", "workspace/test-support", "agent/test-support", "rules_library/test-support"] unit-eval = [] [dependencies] diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 1e17d79a0af7fe9465185068fa24e9944225852e..995a230e146288ba94cf991f7118f930ec9f55a1 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -6903,6 +6903,9 @@ impl Render for AcpThreadView { v_flex() .size_full() .key_context("AcpThread") + .on_action(cx.listener(|this, _: &menu::Cancel, _, cx| { + this.cancel_generation(cx); + })) .on_action(cx.listener(Self::toggle_burn_mode)) .on_action(cx.listener(Self::keep_all)) .on_action(cx.listener(Self::reject_all)) @@ -8224,6 +8227,140 @@ pub(crate) mod tests { }); } + struct GeneratingThreadSetup { + thread_view: Entity, + thread: Entity, + message_editor: Entity, + } + + async fn setup_generating_thread( + cx: &mut TestAppContext, + ) -> (GeneratingThreadSetup, &mut VisualTestContext) { + let connection = StubAgentConnection::new(); + + let (thread_view, cx) = + setup_thread_view(StubAgentServer::new(connection.clone()), cx).await; + add_to_workspace(thread_view.clone(), cx); + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + let (thread, session_id) = thread_view.read_with(cx, |view, cx| { + let thread = view.thread().unwrap(); + (thread.clone(), thread.read(cx).session_id().clone()) + }); + + cx.run_until_parked(); + + cx.update(|_, cx| { + connection.send_update( + session_id.clone(), + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new( + "Response chunk".into(), + )), + cx, + ); + }); + + cx.run_until_parked(); + + thread.read_with(cx, |thread, _cx| { + assert_eq!(thread.status(), ThreadStatus::Generating); + }); + + ( + GeneratingThreadSetup { + thread_view, + thread, + message_editor, + }, + cx, + ) + } + + #[gpui::test] + async fn test_escape_cancels_generation_from_conversation_focus(cx: &mut TestAppContext) { + init_test(cx); + + let (setup, cx) = setup_generating_thread(cx).await; + + let focus_handle = setup + .thread_view + .read_with(cx, |view, _cx| view.focus_handle.clone()); + cx.update(|window, cx| { + window.focus(&focus_handle, cx); + }); + + setup.thread_view.update_in(cx, |_, window, cx| { + window.dispatch_action(menu::Cancel.boxed_clone(), cx); + }); + + cx.run_until_parked(); + + setup.thread.read_with(cx, |thread, _cx| { + assert_eq!(thread.status(), ThreadStatus::Idle); + }); + } + + #[gpui::test] + async fn test_escape_cancels_generation_from_editor_focus(cx: &mut TestAppContext) { + init_test(cx); + + let (setup, cx) = setup_generating_thread(cx).await; + + let editor_focus_handle = setup + .message_editor + .read_with(cx, |editor, cx| editor.focus_handle(cx)); + cx.update(|window, cx| { + window.focus(&editor_focus_handle, cx); + }); + + setup.message_editor.update_in(cx, |_, window, cx| { + window.dispatch_action(editor::actions::Cancel.boxed_clone(), cx); + }); + + cx.run_until_parked(); + + setup.thread.read_with(cx, |thread, _cx| { + assert_eq!(thread.status(), ThreadStatus::Idle); + }); + } + + #[gpui::test] + async fn test_escape_when_idle_is_noop(cx: &mut TestAppContext) { + init_test(cx); + + let (thread_view, cx) = + setup_thread_view(StubAgentServer::new(StubAgentConnection::new()), cx).await; + add_to_workspace(thread_view.clone(), cx); + + let thread = thread_view.read_with(cx, |view, _cx| view.thread().unwrap().clone()); + + thread.read_with(cx, |thread, _cx| { + assert_eq!(thread.status(), ThreadStatus::Idle); + }); + + let focus_handle = thread_view.read_with(cx, |view, _cx| view.focus_handle.clone()); + cx.update(|window, cx| { + window.focus(&focus_handle, cx); + }); + + thread_view.update_in(cx, |_, window, cx| { + window.dispatch_action(menu::Cancel.boxed_clone(), cx); + }); + + cx.run_until_parked(); + + thread.read_with(cx, |thread, _cx| { + assert_eq!(thread.status(), ThreadStatus::Idle); + }); + } + #[gpui::test] async fn test_interrupt(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index f79c84019500af21de23823af68073608e57d3e5..67d9594bbf48b7740a97fe18c32e92fba19ce5e5 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -14,7 +14,7 @@ doctest = false [features] default = [] -test-support = ["remote/test-support"] +test-support = ["remote/test-support", "project/test-support", "workspace/test-support"] [dependencies] anyhow.workspace = true diff --git a/crates/rules_library/Cargo.toml b/crates/rules_library/Cargo.toml index d2fdd765e044181ecb16535076fd31175ddb87c9..d9f1b2ced8026a50052cc84172733a498b088a17 100644 --- a/crates/rules_library/Cargo.toml +++ b/crates/rules_library/Cargo.toml @@ -11,6 +11,9 @@ workspace = true [lib] path = "src/rules_library.rs" +[features] +test-support = ["title_bar/test-support"] + [dependencies] anyhow.workspace = true collections.workspace = true