diff --git a/app/src/ai/blocklist/block.rs b/app/src/ai/blocklist/block.rs index c36e96d351a..b26cd1874f4 100644 --- a/app/src/ai/blocklist/block.rs +++ b/app/src/ai/blocklist/block.rs @@ -6619,9 +6619,9 @@ impl TypedActionView for AIBlock { // Clear all other selections within the `AIBlock`'s view sub-hierarchy to ensure // only one component has a selection at a time. self.clear_other_selections(None, ctx.window_id(), ctx); - // If we have a selection, we should use the default cursor, even if it's over a link. - ctx.reset_cursor(); if has_selection { + // An empty selection can come from clicking another input; preserve its cursor. + ctx.reset_cursor(); self.dismiss_ai_tooltips(ctx); } // Notify the terminal view so it can keep the model's record of which rich diff --git a/app/src/terminal/view_tests.rs b/app/src/terminal/view_tests.rs index ecec085a069..5ce87644435 100644 --- a/app/src/terminal/view_tests.rs +++ b/app/src/terminal/view_tests.rs @@ -10255,6 +10255,23 @@ fn copy_selected_text_from_ai_block() { .expect("an AI block should have been inserted") }); + terminal.update(&mut app, |view, ctx| { + view.input.update(ctx, |_, ctx| { + ctx.set_cursor_shape(Cursor::IBeam); + }); + }); + ai_block.update(&mut app, |block, ctx| { + block.set_block_level_selected_text_for_test(None); + block.handle_action(&AIBlockAction::SelectText, ctx); + }); + app.read(|ctx| { + assert_eq!( + ctx.get_cursor_shape(), + Cursor::IBeam, + "an empty AI block selection must not override the input's hover cursor" + ); + }); + // Simulate a block-level text selection within the AI block and notify the // terminal view (mirrors the `SelectableArea` selection callback plus the // `AIBlockAction::SelectText` dispatch that happens on a real drag). @@ -10262,6 +10279,13 @@ fn copy_selected_text_from_ai_block() { block.set_block_level_selected_text_for_test(Some("quick brown".to_owned())); block.handle_action(&AIBlockAction::SelectText, ctx); }); + app.read(|ctx| { + assert_eq!( + ctx.get_cursor_shape(), + Cursor::Arrow, + "a non-empty AI block selection must still reset the cursor" + ); + }); // The model must now record that the AI block has an active text // selection, which is what lets the copy/insert paths (via @@ -10284,10 +10308,22 @@ fn copy_selected_text_from_ai_block() { // Clearing the AI block's selection must clear the tracked model selection // so stale text isn't returned by later copy/insert operations. + terminal.update(&mut app, |view, ctx| { + view.input.update(ctx, |_, ctx| { + ctx.set_cursor_shape(Cursor::IBeam); + }); + }); ai_block.update(&mut app, |block, ctx| { block.set_block_level_selected_text_for_test(None); block.handle_action(&AIBlockAction::SelectText, ctx); }); + app.read(|ctx| { + assert_eq!( + ctx.get_cursor_shape(), + Cursor::IBeam, + "clearing an AI block selection must preserve the input's hover cursor" + ); + }); terminal.read(&app, |view, ctx| { let semantic_selection = SemanticSelection::as_ref(ctx); let model = view.model.lock(); diff --git a/crates/warpui_core/src/core/app.rs b/crates/warpui_core/src/core/app.rs index 56e9b338779..1973a143a60 100644 --- a/crates/warpui_core/src/core/app.rs +++ b/crates/warpui_core/src/core/app.rs @@ -2354,7 +2354,7 @@ impl AppContext { self.platform_delegate.set_cursor_shape(cursor) } - #[cfg(test)] + #[cfg(any(test, feature = "test-util"))] pub fn get_cursor_shape(&self) -> Cursor { self.platform_delegate.get_cursor_shape() }