From 06dbce41b3bc28d817b31699976de17f9530334f Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:30:50 -0300 Subject: [PATCH] agent_ui: Improve adding selection as context (#52860) This PR improves adding selection as context particularly for terminals, making them not depend on open buffers. It also now works adding selection from a terminal that's no in the panel but as a tab. Also, I'm removing a behavior introduced in https://github.com/zed-industries/zed/pull/48045 that turned out to be confusing, where the selection keybinding would add as context the content of the current line I'm focused on. I think we shouldn't do this given that a lot of times, particularly when adding a selection from a terminal, I'd also end up adding content from a buffer just because my cursor was previously in there, even without anything selected on it. Saw myself multiple times deleting the unwanted buffer context crease in this case. If the keybinding is about _selection_, we should only trigger it when there's something selected.ing not do anything if there isn't any selection. Release Notes: - Agent: Improved adding selection as context particularly for terminals, making them not depend on open buffers. --- crates/agent_ui/src/agent_panel.rs | 45 +++++++++++++ crates/agent_ui/src/completion_provider.rs | 73 ++++++++++++---------- 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index a85cb86de4b71c8fc70783b643b13087eeb4d22f..0ed0aeb78bf8889136a479ed2dac5caba633db55 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -66,7 +66,10 @@ use project::project_settings::ProjectSettings; use project::{Project, ProjectPath, Worktree}; use prompt_store::{PromptStore, UserPromptId}; use rules_library::{RulesLibrary, open_rules_library}; +use settings::TerminalDockPosition; use settings::{Settings, update_settings_file}; +use terminal::terminal_settings::TerminalSettings; +use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use theme_settings::ThemeSettings; use ui::{ Button, Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, DocumentationSide, @@ -423,6 +426,48 @@ pub fn init(cx: &mut App) { }) .register_action( |workspace: &mut Workspace, _: &AddSelectionToThread, window, cx| { + let active_editor = workspace + .active_item(cx) + .and_then(|item| item.act_as::(cx)); + let has_editor_selection = active_editor.is_some_and(|editor| { + editor.update(cx, |editor, cx| { + editor.has_non_empty_selection(&editor.display_snapshot(cx)) + }) + }); + + let has_terminal_selection = workspace + .active_item(cx) + .and_then(|item| item.act_as::(cx)) + .is_some_and(|terminal_view| { + terminal_view + .read(cx) + .terminal() + .read(cx) + .last_content + .selection_text + .as_ref() + .is_some_and(|text| !text.is_empty()) + }); + + let has_terminal_panel_selection = + workspace.panel::(cx).is_some_and(|panel| { + let position = match TerminalSettings::get_global(cx).dock { + TerminalDockPosition::Left => DockPosition::Left, + TerminalDockPosition::Bottom => DockPosition::Bottom, + TerminalDockPosition::Right => DockPosition::Right, + }; + let dock_is_open = + workspace.dock_at_position(position).read(cx).is_open(); + dock_is_open && !panel.read(cx).terminal_selections(cx).is_empty() + }); + + if !has_editor_selection + && !has_terminal_selection + && !has_terminal_panel_selection + { + return; + } + let Some(panel) = workspace.panel::(cx) else { return; }; diff --git a/crates/agent_ui/src/completion_provider.rs b/crates/agent_ui/src/completion_provider.rs index b6be6502b152847822a79bc8c486195345c0a195..6259269834b0add5b87fd9d397e17671d30adb9f 100644 --- a/crates/agent_ui/src/completion_provider.rs +++ b/crates/agent_ui/src/completion_provider.rs @@ -28,7 +28,7 @@ use prompt_store::{PromptStore, UserPromptId}; use rope::Point; use settings::{Settings, TerminalDockPosition}; use terminal::terminal_settings::TerminalSettings; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use text::{Anchor, ToOffset as _, ToPoint as _}; use ui::IconName; use ui::prelude::*; @@ -562,8 +562,7 @@ impl PromptCompletionProvider { .collect(); // Collect terminal selections from all terminal views if the terminal panel is visible - let terminal_selections: Vec = - terminal_selections_if_panel_open(workspace, cx); + let terminal_selections: Vec = terminal_selections(workspace, cx); const EDITOR_PLACEHOLDER: &str = "selection "; const TERMINAL_PLACEHOLDER: &str = "terminal "; @@ -1198,7 +1197,7 @@ impl PromptCompletionProvider { }) }); - let has_terminal_selection = !terminal_selections_if_panel_open(workspace, cx).is_empty(); + let has_terminal_selection = !terminal_selections(workspace, cx).is_empty(); if has_editor_selection || has_terminal_selection { entries.push(PromptContextEntry::Action( @@ -2169,28 +2168,45 @@ fn build_code_label_for_path( label.build() } -/// Returns terminal selections from all terminal views if the terminal panel is open. -fn terminal_selections_if_panel_open(workspace: &Entity, cx: &App) -> Vec { - let Some(panel) = workspace.read(cx).panel::(cx) else { - return Vec::new(); - }; +fn terminal_selections(workspace: &Entity, cx: &App) -> Vec { + let mut selections = Vec::new(); - // Check if the dock containing this panel is open - let position = match TerminalSettings::get_global(cx).dock { - TerminalDockPosition::Left => DockPosition::Left, - TerminalDockPosition::Bottom => DockPosition::Bottom, - TerminalDockPosition::Right => DockPosition::Right, - }; - let dock_is_open = workspace + // Check if the active item is a terminal (in a panel or not) + if let Some(terminal_view) = workspace .read(cx) - .dock_at_position(position) - .read(cx) - .is_open(); - if !dock_is_open { - return Vec::new(); + .active_item(cx) + .and_then(|item| item.act_as::(cx)) + { + if let Some(text) = terminal_view + .read(cx) + .terminal() + .read(cx) + .last_content + .selection_text + .clone() + .filter(|text| !text.is_empty()) + { + selections.push(text); + } } - panel.read(cx).terminal_selections(cx) + if let Some(panel) = workspace.read(cx).panel::(cx) { + let position = match TerminalSettings::get_global(cx).dock { + TerminalDockPosition::Left => DockPosition::Left, + TerminalDockPosition::Bottom => DockPosition::Bottom, + TerminalDockPosition::Right => DockPosition::Right, + }; + let dock_is_open = workspace + .read(cx) + .dock_at_position(position) + .read(cx) + .is_open(); + if dock_is_open { + selections.extend(panel.read(cx).terminal_selections(cx)); + } + } + + selections } fn selection_ranges( @@ -2213,17 +2229,8 @@ fn selection_ranges( selections .into_iter() - .map(|s| { - let (start, end) = if s.is_empty() { - let row = multi_buffer::MultiBufferRow(s.start.row); - let line_start = text::Point::new(s.start.row, 0); - let line_end = text::Point::new(s.start.row, snapshot.line_len(row)); - (line_start, line_end) - } else { - (s.start, s.end) - }; - snapshot.anchor_after(start)..snapshot.anchor_before(end) - }) + .filter(|s| !s.is_empty()) + .map(|s| snapshot.anchor_after(s.start)..snapshot.anchor_before(s.end)) .flat_map(|range| { let (start_buffer, start) = buffer.text_anchor_for_position(range.start, cx)?; let (end_buffer, end) = buffer.text_anchor_for_position(range.end, cx)?;