From 263e0004cefedc7cbdde24d24b971a5e03e41a60 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:26:23 -0300 Subject: [PATCH] agent_ui: Add adjustments to terminal selection as context (#47950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up to https://github.com/zed-industries/zed/pull/47637 - Removes the requirement for the terminal to be focused to use the `cmd->` keybinding. This way, we match the behavior of buffer selections and you can always add what's selected in the terminal inside the agent panel - Add number of lines selected in the mention button as well - Make the "Selection" menu item inside the "Add Context" menu also observe terminal selections Screenshot 2026-01-29 at 1  36@2x Release Notes: - Agent: Add number of liners selected in the terminal context mention --- crates/acp_thread/src/mention.rs | 42 +++++++++++++++++----- crates/agent/src/thread.rs | 2 +- crates/agent_ui/src/acp/message_editor.rs | 3 +- crates/agent_ui/src/acp/thread_view.rs | 19 ++++++---- crates/agent_ui/src/completion_provider.rs | 3 +- crates/agent_ui/src/mention_set.rs | 2 +- crates/agent_ui/src/text_thread_editor.rs | 41 ++------------------- 7 files changed, 55 insertions(+), 57 deletions(-) diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs index edfbe3443fef822ce34950f1630be6862df00b54..6550e6f4ade781c165fe6671c9150004149f60da 100644 --- a/crates/acp_thread/src/mention.rs +++ b/crates/acp_thread/src/mention.rs @@ -54,7 +54,9 @@ pub enum MentionUri { Fetch { url: Url, }, - TerminalSelection, + TerminalSelection { + line_count: u32, + }, } impl MentionUri { @@ -201,7 +203,11 @@ impl MentionUri { line_range, }) } else if path.starts_with("/agent/terminal-selection") { - Ok(Self::TerminalSelection) + let line_count = single_query_param(&url, "lines")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0); + Ok(Self::TerminalSelection { line_count }) } else { bail!("invalid zed url: {:?}", input); } @@ -224,7 +230,13 @@ impl MentionUri { MentionUri::TextThread { name, .. } => name.clone(), MentionUri::Rule { name, .. } => name.clone(), MentionUri::Diagnostics { .. } => "Diagnostics".to_string(), - MentionUri::TerminalSelection => "Terminal".to_string(), + MentionUri::TerminalSelection { line_count } => { + if *line_count == 1 { + "Terminal (1 line)".to_string() + } else { + format!("Terminal ({} lines)", line_count) + } + } MentionUri::Selection { abs_path: path, line_range, @@ -247,7 +259,7 @@ impl MentionUri { MentionUri::TextThread { .. } => IconName::Thread.path().into(), MentionUri::Rule { .. } => IconName::Reader.path().into(), MentionUri::Diagnostics { .. } => IconName::Warning.path().into(), - MentionUri::TerminalSelection => IconName::Terminal.path().into(), + MentionUri::TerminalSelection { .. } => IconName::Terminal.path().into(), MentionUri::Selection { .. } => IconName::Reader.path().into(), MentionUri::Fetch { .. } => IconName::ToolWeb.path().into(), } @@ -342,7 +354,12 @@ impl MentionUri { url } MentionUri::Fetch { url } => url.clone(), - MentionUri::TerminalSelection => Url::parse("zed:///agent/terminal-selection").unwrap(), + MentionUri::TerminalSelection { line_count } => { + let mut url = Url::parse("zed:///agent/terminal-selection").unwrap(); + url.query_pairs_mut() + .append_pair("lines", &line_count.to_string()); + url + } } } } @@ -650,13 +667,20 @@ mod tests { #[test] fn test_parse_terminal_selection_uri() { - let terminal_uri = "zed:///agent/terminal-selection"; + let terminal_uri = "zed:///agent/terminal-selection?lines=42"; let parsed = MentionUri::parse(terminal_uri, PathStyle::local()).unwrap(); match &parsed { - MentionUri::TerminalSelection => {} - _ => panic!("Expected Terminal variant"), + MentionUri::TerminalSelection { line_count } => { + assert_eq!(*line_count, 42); + } + _ => panic!("Expected TerminalSelection variant"), } assert_eq!(parsed.to_uri().to_string(), terminal_uri); - assert_eq!(parsed.name(), "Terminal"); + assert_eq!(parsed.name(), "Terminal (42 lines)"); + + // Test single line + let single_line_uri = "zed:///agent/terminal-selection?lines=1"; + let parsed_single = MentionUri::parse(single_line_uri, PathStyle::local()).unwrap(); + assert_eq!(parsed_single.name(), "Terminal (1 line)"); } } diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index bb5c09c38e3836dfdaba15436ad280f2a0cd5910..6c7c532a7984350fba2f4e6079f851482365c2e8 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -317,7 +317,7 @@ impl UserMessage { MentionUri::Diagnostics { .. } => { write!(&mut diagnostics_context, "\n{}\n", content).ok(); } - MentionUri::TerminalSelection => { + MentionUri::TerminalSelection { .. } => { write!( &mut selection_context, "\n{}", diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index f6d1eba70f84cc98b05ac9566d0260bd61996da8..bbcc0c1bbf6eba0ebeca0f7a97b732ca2721e8c3 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -1017,7 +1017,8 @@ impl MessageEditor { window: &mut Window, cx: &mut Context, ) { - let mention_uri = MentionUri::TerminalSelection; + let line_count = text.lines().count() as u32; + let mention_uri = MentionUri::TerminalSelection { line_count }; let mention_text = mention_uri.as_link().to_string(); let (excerpt_id, text_anchor, content_len) = self.editor.update(cx, |editor, cx| { diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 5f1b52d701f0337c8450726595543b9193298ea1..b739823be0a168d51c0a36cc9e07d49da9277345 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -6621,7 +6621,7 @@ impl AcpThreadView { .map(|active| active.prompt_capabilities.borrow().image) .unwrap_or_default(); - let has_selection = workspace + let has_editor_selection = workspace .upgrade() .and_then(|ws| { ws.read(cx) @@ -6634,6 +6634,13 @@ impl AcpThreadView { }) }); + let has_terminal_selection = workspace + .upgrade() + .and_then(|ws| ws.read(cx).panel::(cx)) + .is_some_and(|panel| !panel.read(cx).terminal_selections(cx).is_empty()); + + let has_selection = has_editor_selection || has_terminal_selection; + ContextMenu::build(window, cx, move |menu, _window, _cx| { menu.key_context("AddContextMenu") .header("Context") @@ -6721,10 +6728,10 @@ impl AcpThreadView { .disabled(!has_selection) .handler({ move |window, cx| { - message_editor.focus_handle(cx).focus(window, cx); - message_editor.update(cx, |editor, cx| { - editor.insert_selections(window, cx); - }); + window.dispatch_action( + zed_actions::agent::AddSelectionToThread.boxed_clone(), + cx, + ); } }), ) @@ -6870,7 +6877,7 @@ impl AcpThreadView { cx.open_url(url.as_str()); } MentionUri::Diagnostics { .. } => {} - MentionUri::TerminalSelection => {} + MentionUri::TerminalSelection { .. } => {} }) } else { cx.open_url(&url); diff --git a/crates/agent_ui/src/completion_provider.rs b/crates/agent_ui/src/completion_provider.rs index 8b6f6f560c278c03205f790e9819fcf87484d0aa..0195522df6b1801f6200f09bfc0a255963470695 100644 --- a/crates/agent_ui/src/completion_provider.rs +++ b/crates/agent_ui/src/completion_provider.rs @@ -636,7 +636,8 @@ impl PromptCompletionProvider { }; let offset = start.to_offset(&snapshot); - let mention_uri = MentionUri::TerminalSelection; + let line_count = terminal_text.lines().count() as u32; + let mention_uri = MentionUri::TerminalSelection { line_count }; let range = snapshot.anchor_after(offset + terminal_range.start) ..snapshot.anchor_after(offset + terminal_range.end); diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index 2bd8e6d502dafc879b468a1c47735c0a57ab7aa6..07a7841be764b34381f88087e1d7f6c447d9d910 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/crates/agent_ui/src/mention_set.rs @@ -249,7 +249,7 @@ impl MentionSet { debug_panic!("unexpected selection URI"); Task::ready(Err(anyhow!("unexpected selection URI"))) } - MentionUri::TerminalSelection => { + MentionUri::TerminalSelection { .. } => { debug_panic!("unexpected terminal URI"); Task::ready(Err(anyhow!("unexpected terminal URI"))) } diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 4be047c47d491f7121e3f2322d6edcdbcc7baebe..9f3234ef0489c8576f5d1fd1db95232d6a8b6d1c 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -65,10 +65,8 @@ use workspace::{ searchable::{Direction, SearchableItemHandle}, }; -use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use workspace::{ Save, Toast, Workspace, - dock::Panel, item::{self, FollowableItem, Item}, notifications::NotificationId, pane, @@ -1498,39 +1496,8 @@ impl TextThreadEditor { return; }; - // Try terminal selection first (requires focus, so more specific) - if let Some(terminal_text) = maybe!({ - let terminal_panel = workspace.panel::(cx)?; - - if !terminal_panel - .read(cx) - .focus_handle(cx) - .contains_focused(window, cx) - { - return None; - } - - let terminal_view = terminal_panel.read(cx).pane().and_then(|pane| { - pane.read(cx) - .active_item() - .and_then(|t| t.downcast::()) - })?; - - terminal_view - .read(cx) - .terminal() - .read(cx) - .last_content - .selection_text - .clone() - }) { - if !terminal_text.is_empty() { - agent_panel_delegate.quote_terminal_text(workspace, terminal_text, window, cx); - return; - } - } - - // Try editor selection + // Get buffer info for the delegate call (even if empty, AcpThreadView ignores these + // params and calls insert_selections which handles both terminal and buffer) if let Some((selections, buffer)) = maybe!({ let editor = workspace .active_item(cx) @@ -1551,9 +1518,7 @@ impl TextThreadEditor { }); Some((selections, buffer)) }) { - if !selections.is_empty() { - agent_panel_delegate.quote_selection(workspace, selections, buffer, window, cx); - } + agent_panel_delegate.quote_selection(workspace, selections, buffer, window, cx); } }