aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-06 15:36:35 +0200
committerGitHub <noreply@github.com>2026-09-06 15:36:35 +0200
commit5a64e8da4551990367dd26d77cf3b654dfa27a73 (patch)
treed7eaf5f4143cb7b0d34bfe73dd3d1ea315fa6c1a
parente366a05f03b6e37549c7a1744c3551553017c541 (diff)
fix: consume failed selection copies without interrupting agents (#3681)
-rw-r--r--src/client/shell/actions.rs46
-rw-r--r--src/client/shell/input.rs2
-rw-r--r--src/client/shell/state.rs5
-rw-r--r--src/client/shell/tests/copy.rs25
-rw-r--r--src/client/shell/tests/endpoint_requests.rs10
-rw-r--r--src/client/shell/worktrees.rs2
-rw-r--r--src/client/shell_runtime.rs5
7 files changed, 14 insertions, 81 deletions
diff --git a/src/client/shell/actions.rs b/src/client/shell/actions.rs
index f99d8f3a..e62d75ff 100644
--- a/src/client/shell/actions.rs
+++ b/src/client/shell/actions.rs
@@ -248,14 +248,6 @@ impl ClientShellState {
}
pub(super) fn request_selection_copy(&mut self, outcome: &mut ClientShellInput) {
- self.request_selection_copy_with_fallback(outcome, None);
- }
-
- pub(super) fn request_selection_copy_with_fallback(
- &mut self,
- outcome: &mut ClientShellInput,
- fallback_key: Option<crate::input::TerminalKey>,
- ) {
let Some(selection) = self.selection.as_ref() else {
return;
};
@@ -266,27 +258,6 @@ impl ClientShellState {
.and_then(|surface| surface.panes.iter().find(|pane| pane.pane_id == pane_id))
.map(|pane| pane.content_revision);
let (anchor, cursor) = selection.ordered_cells();
- let fallback = fallback_key.and_then(|key| {
- let press = ClientPaneInputEvent::from_terminal_key(key.clone())?;
- let tracks_release = matches!(
- &press,
- ClientPaneInputEvent::Key {
- tracks_release: true,
- ..
- }
- );
- let mut message =
- super::target_event_message(ClientInputTarget::Pane(pane_id.clone()), press);
- if tracks_release {
- let release = ClientPaneInputEvent::from_terminal_key(
- key.with_kind(crossterm::event::KeyEventKind::Release),
- )?;
- if let ClientMessage::ClientShellPaneInput { events, .. } = &mut message {
- events.push(release);
- }
- }
- Some(message)
- });
self.push_endpoint_method_with_kind(
crate::api::schema::Method::PaneSelectionRead(
crate::api::schema::PaneSelectionReadParams {
@@ -302,7 +273,7 @@ impl ClientShellState {
content_revision,
},
),
- PendingEndpointKind::SelectionCopy { fallback },
+ PendingEndpointKind::SelectionCopy,
outcome,
);
}
@@ -654,13 +625,7 @@ impl ClientShellState {
let repaint = self.complete_pane_scroll(pane_id, serial, result, &mut outcome);
return (repaint, outcome.actions);
}
- PendingEndpointKind::SelectionCopy { fallback } => {
- let fallback = || {
- fallback
- .map(ClientShellAction::Request)
- .into_iter()
- .collect::<Vec<_>>()
- };
+ PendingEndpointKind::SelectionCopy => {
return match result {
Ok(crate::api::schema::ResponseResult::PaneSelection { text, .. })
if !text.is_empty() =>
@@ -672,17 +637,14 @@ impl ClientShellState {
)
}
Ok(crate::api::schema::ResponseResult::PaneSelection { .. }) => {
- (false, fallback())
+ (false, Vec::new())
}
Ok(_) => {
self.endpoint_error =
Some("endpoint returned an unexpected selection result".to_owned());
- (true, fallback())
- }
- Err(error) if error.code.as_deref() == Some("endpoint_cancelled") => {
(true, Vec::new())
}
- Err(_) => (true, fallback()),
+ Err(_) => (true, Vec::new()),
};
}
PendingEndpointKind::WordSelection {
diff --git a/src/client/shell/input.rs b/src/client/shell/input.rs
index f8826136..539dec6e 100644
--- a/src/client/shell/input.rs
+++ b/src/client/shell/input.rs
@@ -523,7 +523,7 @@ impl ClientShellState {
.as_ref()
.is_some_and(crate::selection::Selection::is_visible)
{
- self.request_selection_copy_with_fallback(outcome, Some(key.clone()));
+ self.request_selection_copy(outcome);
self.selection = None;
self.stop_selection_autoscroll();
self.selection_highlight_clear_deadline = None;
diff --git a/src/client/shell/state.rs b/src/client/shell/state.rs
index 67bdff91..b30f57d0 100644
--- a/src/client/shell/state.rs
+++ b/src/client/shell/state.rs
@@ -298,7 +298,6 @@ pub(crate) enum ClientShellAction {
request: Box<crate::api::schema::Request>,
},
ClipboardWrite(Vec<u8>),
- Request(ClientMessage),
OpenSafeWebUrl(String),
ActivateEndpoint {
endpoint_id: ClientEndpointId,
@@ -689,9 +688,7 @@ pub(super) enum PendingEndpointKind {
WorktreeRemove {
forced: bool,
},
- SelectionCopy {
- fallback: Option<ClientMessage>,
- },
+ SelectionCopy,
PaneScroll {
pane_id: String,
serial: u64,
diff --git a/src/client/shell/tests/copy.rs b/src/client/shell/tests/copy.rs
index 7d71a981..7cc3241b 100644
--- a/src/client/shell/tests/copy.rs
+++ b/src/client/shell/tests/copy.rs
@@ -224,7 +224,7 @@ fn retained_mouse_selection_copies_only_on_exact_copy_shortcut() {
ClientShellAction::Endpoint { request, .. } => request.id.clone(),
_ => unreachable!(),
};
- let (_, fallback) = state.handle_endpoint_result(
+ let (_, actions) = state.handle_endpoint_result(
"boot-1",
&request_id,
Ok(crate::api::schema::ResponseResult::PaneSelection {
@@ -232,25 +232,10 @@ fn retained_mouse_selection_copies_only_on_exact_copy_shortcut() {
text: String::new(),
}),
);
- assert!(matches!(
- &fallback[..],
- [ClientShellAction::Request(ClientMessage::ClientShellPaneInput {
- pane_id,
- events,
- })] if pane_id == "pane_1"
- && matches!(
- &events[..],
- [ClientPaneInputEvent::Key {
- code: crate::protocol::ClientKeyCode::Char('c'),
- kind: crate::protocol::ClientKeyKind::Press,
- ..
- }, ClientPaneInputEvent::Key {
- code: crate::protocol::ClientKeyCode::Char('c'),
- kind: crate::protocol::ClientKeyKind::Release,
- ..
- }]
- )
- ));
+ assert!(
+ actions.is_empty(),
+ "failed copy must not send terminal input"
+ );
}
#[test]
diff --git a/src/client/shell/tests/endpoint_requests.rs b/src/client/shell/tests/endpoint_requests.rs
index 8149b530..ab5a3cb2 100644
--- a/src/client/shell/tests/endpoint_requests.rs
+++ b/src/client/shell/tests/endpoint_requests.rs
@@ -216,7 +216,7 @@ fn cancelled_integration_install_does_not_queue_a_refresh() {
}
#[test]
-fn cancelled_selection_does_not_replay_its_fallback_key() {
+fn cancelled_selection_does_not_send_terminal_input() {
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
state.set_snapshot(Box::new(snapshot()));
state.set_pane_surface(surface());
@@ -226,13 +226,7 @@ fn cancelled_selection_does_not_replay_its_fallback_key() {
(0, 2),
));
let mut outcome = ClientShellInput::default();
- state.request_selection_copy_with_fallback(
- &mut outcome,
- Some(crate::input::TerminalKey::new(
- KeyCode::Char('c'),
- KeyModifiers::CONTROL,
- )),
- );
+ state.request_selection_copy(&mut outcome);
let (_, actions) = state.handle_endpoint_result(
"boot-1",
request_id(&outcome.actions),
diff --git a/src/client/shell/worktrees.rs b/src/client/shell/worktrees.rs
index ea2e8f24..8218c0cc 100644
--- a/src/client/shell/worktrees.rs
+++ b/src/client/shell/worktrees.rs
@@ -540,7 +540,7 @@ impl ClientShellState {
| PendingEndpointKind::ReloadConfig
| PendingEndpointKind::IntegrationList
| PendingEndpointKind::IntegrationInstall
- | PendingEndpointKind::SelectionCopy { .. }
+ | PendingEndpointKind::SelectionCopy
| PendingEndpointKind::PaneScroll { .. }
| PendingEndpointKind::WordSelection { .. }
| PendingEndpointKind::PaneLinkActivate { .. }
diff --git a/src/client/shell_runtime.rs b/src/client/shell_runtime.rs
index 8a4af0b7..00bb1406 100644
--- a/src/client/shell_runtime.rs
+++ b/src/client/shell_runtime.rs
@@ -28,11 +28,6 @@ pub(super) fn dispatch_client_shell_actions(
shell::ClientShellAction::ClipboardWrite(bytes) => {
crate::selection::write_osc52_bytes(&bytes);
}
- shell::ClientShellAction::Request(request) => {
- if endpoints.active_surface_available() {
- write_to_server(endpoints, &request).map_err(ClientError::ConnectionLost)?;
- }
- }
shell::ClientShellAction::ActivateEndpoint {
endpoint_id,
target,