diff options
Diffstat (limited to 'src/client/shell/actions.rs')
| -rw-r--r-- | src/client/shell/actions.rs | 162 |
1 files changed, 117 insertions, 45 deletions
diff --git a/src/client/shell/actions.rs b/src/client/shell/actions.rs index efc9aae9..34c780da 100644 --- a/src/client/shell/actions.rs +++ b/src/client/shell/actions.rs @@ -227,11 +227,13 @@ impl ClientShellState { if action == crate::protocol::ClientShellCommandAction::Popup { self.popup_pending = true; self.popup_pending_deadline = None; - self.push_endpoint_method_with_kind( + if !self.push_endpoint_method_with_kind( crate::api::schema::Method::CommandInvoke(params), PendingEndpointKind::PopupCommand, outcome, - ); + ) { + self.popup_pending = false; + } } else { self.push_endpoint_method( crate::api::schema::Method::CommandInvoke(params), @@ -323,7 +325,7 @@ impl ClientShellState { self.word_selection_generation = self.word_selection_generation.saturating_add(1); let generation = self.word_selection_generation; self.pending_word_selection = Some(generation); - self.push_endpoint_method_with_kind( + if !self.push_endpoint_method_with_kind( crate::api::schema::Method::PaneSelectionRead( crate::api::schema::PaneSelectionReadParams { pane_id: hit.pane_id.clone(), @@ -345,7 +347,9 @@ impl ClientShellState { generation, }, outcome, - ); + ) { + self.pending_word_selection = None; + } } pub(super) fn push_endpoint_method( @@ -356,15 +360,65 @@ impl ClientShellState { self.push_endpoint_method_with_kind(method, PendingEndpointKind::Generic, outcome); } + fn push_endpoint_notice( + &mut self, + kind: ClientEndpointNoticeKind, + code: impl Into<String>, + title: impl Into<String>, + body: impl Into<String>, + ) -> bool { + let key = ClientEndpointNoticeKey { + boot_id: self + .snapshot + .as_deref() + .map(|snapshot| snapshot.boot_id.clone()) + .unwrap_or_else(|| "disconnected".to_owned()), + kind, + code: code.into(), + }; + let body = body.into(); + if kind == ClientEndpointNoticeKind::Rejected { + if self + .visible_endpoint_notice + .as_ref() + .is_some_and(|notice| notice.key == key && notice.body == body) + { + return false; + } + } else if !self.endpoint_notice_seen.insert(key.clone()) { + return false; + } + self.visible_endpoint_notice = Some(ClientVisibleEndpointNotice { + key, + title: title.into(), + body, + deadline: std::time::Instant::now() + std::time::Duration::from_secs(8), + }); + true + } + pub(super) fn push_endpoint_method_with_kind( &mut self, method: crate::api::schema::Method, kind: PendingEndpointKind, outcome: &mut ClientShellInput, - ) { - let Some(snapshot) = self.snapshot.as_deref() else { - return; - }; + ) -> bool { + if self.snapshot.is_none() { + return false; + } + let method_name = crate::api::api_method_name(&method).to_owned(); + if !self.supports_endpoint_method(&method) { + outcome.repaint |= self.push_endpoint_notice( + ClientEndpointNoticeKind::Unsupported, + method_name.clone(), + "Action unavailable", + format!( + "This server does not support {method_name} yet. Update and restart it to enable this action." + ), + ); + return false; + } + let snapshot = self.snapshot.as_deref().expect("checked snapshot"); let confirmation_workspace_id = match &method { crate::api::schema::Method::TabClose(target) => snapshot .tabs @@ -385,6 +439,7 @@ impl ClientShellState { request_id.clone(), PendingEndpointRequest { boot_id: snapshot.boot_id.clone(), + method_name, confirmation_workspace_id, kind, }, @@ -396,14 +451,16 @@ impl ClientShellState { method, }), }); + true } pub(crate) fn receive_endpoint_error(&mut self, message: String) -> bool { - if self.endpoint_error.as_deref() == Some(message.as_str()) { - return false; - } - self.endpoint_error = Some(message); - true + self.push_endpoint_notice( + ClientEndpointNoticeKind::Rejected, + "paste_rejected", + "Paste rejected", + message, + ) } pub(crate) fn handle_endpoint_result( @@ -423,12 +480,49 @@ impl ClientShellState { { return (false, Vec::new()); } + if result.is_ok() { + let timeout_key = ClientEndpointNoticeKey { + boot_id: boot_id.to_owned(), + kind: ClientEndpointNoticeKind::Timeout, + code: pending.method_name.clone(), + }; + self.endpoint_notice_seen.remove(&timeout_key); + } + if let Err(error) = &result { + let code = error.code.as_deref().unwrap_or("invalid_response"); + if !matches!( + code, + "confirmation_required" | "stale_content" | "stale_target" + ) { + let (kind, notice_code, title, body) = match code { + "endpoint_timeout" => ( + ClientEndpointNoticeKind::Timeout, + pending.method_name.clone(), + "Server timed out", + format!("This server did not respond to {}.", pending.method_name), + ), + "server_unavailable" => ( + ClientEndpointNoticeKind::Unavailable, + "server".to_owned(), + "Server unavailable", + error.message.clone(), + ), + _ => ( + ClientEndpointNoticeKind::Rejected, + format!("{}:{code}", pending.method_name), + "Action rejected", + error.message.clone(), + ), + }; + self.push_endpoint_notice(kind, notice_code, title, body); + } + } match pending.kind { PendingEndpointKind::Generic => {} PendingEndpointKind::ProductAnnouncementDismiss { version, id } => { return match result { Ok(_) => (false, Vec::new()), - Err(error) => { + Err(_) => { let key = (version.clone(), id.clone()); if self.dismissed_product_announcement.as_ref() == Some(&key) { self.dismissed_product_announcement = None; @@ -447,7 +541,6 @@ impl ClientShellState { )); } } - self.endpoint_error = Some(error.message); (true, Vec::new()) } }; @@ -455,7 +548,7 @@ impl ClientShellState { PendingEndpointKind::ReleaseNotesDismiss => { return match result { Ok(_) => (false, Vec::new()), - Err(error) => { + Err(_) => { if self.overlay.is_none() { if let Some(notes) = self .snapshot @@ -467,7 +560,6 @@ impl ClientShellState { )); } } - self.endpoint_error = Some(error.message); (true, Vec::new()) } }; @@ -479,10 +571,9 @@ impl ClientShellState { Some(std::time::Instant::now() + std::time::Duration::from_secs(1)); (false, Vec::new()) } - Err(error) => { + Err(_) => { self.popup_pending = false; self.popup_pending_deadline = None; - self.endpoint_error = Some(error.message); (true, Vec::new()) } }; @@ -517,10 +608,7 @@ impl ClientShellState { Some("endpoint returned an unexpected selection result".to_owned()); (true, fallback()) } - Err(error) => { - self.endpoint_error = Some(error.message); - (true, fallback()) - } + Err(_) => (true, fallback()), }; } PendingEndpointKind::WordSelection { @@ -551,10 +639,7 @@ impl ClientShellState { ); return (true, Vec::new()); } - Err(error) => { - self.endpoint_error = Some(error.message); - return (true, Vec::new()); - } + Err(_) => return (true, Vec::new()), }; let Some((start_col, end_col)) = crate::app::actions::word_bounds_at_column(&row_text, col) @@ -640,10 +725,7 @@ impl ClientShellState { self.url_click_consumes_until_up = completed_before_release; (false, Vec::new()) } - Err(error) => { - self.endpoint_error = Some(error.message); - (true, replay_action(replay)) - } + Err(_) => (true, replay_action(replay)), }; } PendingEndpointKind::CopyMotion { @@ -673,10 +755,7 @@ impl ClientShellState { Some("endpoint returned an unexpected copy-motion result".to_owned()); (true, false) } - Err(error) => { - self.endpoint_error = Some(error.message); - (true, false) - } + Err(_) => (true, false), }; self.complete_copy_operation(session_generation, continue_queue, &mut outcome); return (repaint || outcome.repaint, outcome.actions); @@ -731,9 +810,8 @@ impl ClientShellState { Some("endpoint returned an unexpected copy-search result".to_owned()); (true, false) } - Err(error) => { + Err(_) => { self.cancel_deferred_copy_after_search(generation); - self.endpoint_error = Some(error.message); (true, false) } }; @@ -748,10 +826,7 @@ impl ClientShellState { Some("endpoint returned an unexpected config reload result".to_owned()); true } - Err(error) => { - self.endpoint_error = Some(error.message); - true - } + Err(_) => true, }; return (repaint, Vec::new()); } @@ -778,10 +853,7 @@ impl ClientShellState { } true } - Err(error) => { - self.endpoint_error = Some(error.message); - true - } + Err(_) => true, }; (repaint, Vec::new()) } |
