mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 04:54:24 +00:00
agent_ui: Add adjustments to terminal selection as context (#47950)
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 <img width="420" height="254" alt="Screenshot 2026-01-29 at 1 36@2x" src="https://github.com/user-attachments/assets/25d2eb00-096f-44d9-8882-273932ca43e4" /> Release Notes: - Agent: Add number of liners selected in the terminal context mention
This commit is contained in:
parent
ad24b3f0a0
commit
263e0004ce
7 changed files with 55 additions and 57 deletions
|
|
@ -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::<u32>()
|
||||
.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)");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}",
|
||||
|
|
|
|||
|
|
@ -1017,7 +1017,8 @@ impl MessageEditor {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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| {
|
||||
|
|
|
|||
|
|
@ -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::<TerminalPanel>(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);
|
||||
|
|
|
|||
|
|
@ -636,7 +636,8 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
|
|||
};
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::<TerminalPanel>(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::<TerminalView>())
|
||||
})?;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue