Improve local tool execution and shell output panes

This commit is contained in:
2026-08-28 10:20:16 -05:00
parent a69b927cc3
commit 88c1ef9716
23 changed files with 807 additions and 238 deletions
+1 -1
View File
@@ -448,7 +448,7 @@ impl TryFrom<GrepResult> for api::request::input::tool_call_result::Result {
fn try_from(result: GrepResult) -> Result<Self, Self::Error> {
match result {
GrepResult::Success { matched_files } => Ok(
GrepResult::Success { matched_files, .. } => Ok(
api::request::input::tool_call_result::Result::Grep(api::GrepResult {
result: Some(api::grep_result::Result::Success(
api::grep_result::Success {
+26 -6
View File
@@ -1166,15 +1166,22 @@ impl AIAgentActionResultType {
Self::RequestFileEdits(RequestFileEditsResult::Cancelled) => {
"apply_file_diffs: cancelled".to_string()
}
Self::Grep(GrepResult::Success { matched_files }) => {
format!(
Self::Grep(GrepResult::Success {
matched_files,
truncated,
}) => {
let mut summary = format!(
"grep: [{}]",
matched_files
.iter()
.map(|f| f.file_path.as_str())
.collect::<Vec<_>>()
.join(", ")
)
);
if *truncated {
summary.push_str(" (additional matches omitted)");
}
summary
}
Self::Grep(GrepResult::Error(error)) => format!("grep: error={error}"),
Self::Grep(GrepResult::Cancelled) => "grep: cancelled".to_string(),
@@ -1294,7 +1301,10 @@ impl AIAgentActionResultType {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GrepResult {
Success { matched_files: Vec<GrepFileMatch> },
Success {
matched_files: Vec<GrepFileMatch>,
truncated: bool,
},
Error(String),
Cancelled,
}
@@ -1302,12 +1312,22 @@ pub enum GrepResult {
impl Display for GrepResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GrepResult::Success { matched_files } => {
GrepResult::Success {
matched_files,
truncated,
} => {
write!(
f,
"Grep found matches in: [{}]",
matched_files.iter().format(", ")
)
)?;
if *truncated {
write!(
f,
". Additional matches were omitted; narrow the query or path to retrieve them"
)?;
}
Ok(())
}
GrepResult::Error(error) => write!(f, "Grep error: {error}"),
GrepResult::Cancelled => write!(f, "Grep cancelled"),
@@ -38,6 +38,8 @@ pub struct ScrollTarget {
#[derive(Clone, Default)]
pub struct ClippedScrollData {
scroll_start_px: Pixels,
max_scroll_start_px: Pixels,
follow_end: bool,
pub(super) scroll_to_position: Option<ScrollTarget>,
selection_scroll_anchor: Option<ClippedSelectionScrollAnchor>,
}
@@ -77,6 +79,36 @@ impl ClippedScrollStateHandle {
self.clipped_scroll_data.lock().scroll_start_px
}
/// Returns whether the scrollable is currently at its maximum scroll position.
///
/// The maximum is captured during layout, so consumers can implement follow-output
/// behavior without estimating content or viewport sizes themselves.
pub fn is_scrolled_to_end(&self) -> bool {
let data = self.clipped_scroll_data.lock();
data.scroll_start_px.as_f32() + 0.5 >= data.max_scroll_start_px.as_f32()
}
/// Controls whether layout should keep the scroll position docked to the content's end.
pub fn set_follow_end(&self, follow_end: bool) {
self.clipped_scroll_data.lock().follow_end = follow_end;
}
pub(in crate::elements) fn update_scroll_extent(
&self,
visible_px: Pixels,
total_size: Pixels,
) -> Pixels {
let max_scroll_start_px = (total_size - visible_px).max(Pixels::zero());
let mut data = self.clipped_scroll_data.lock();
data.max_scroll_start_px = max_scroll_start_px;
data.scroll_start_px = if data.follow_end {
max_scroll_start_px
} else {
data.scroll_start_px.min(max_scroll_start_px)
};
data.scroll_start_px
}
pub fn scroll_by(&self, delta: Pixels) {
self.scroll_to(self.scroll_start() + delta);
}
@@ -417,12 +449,8 @@ impl Element for ClippedScrollable {
// Make sure that the new layout doesn't put the scroll bar in an invalid
// location.
if let Some(scroll_data) = self.scroll_data(app) {
let max_scroll_top =
(scroll_data.total_size - scroll_data.visible_px).max(Pixels::zero());
let scroll_top = scroll_data.scroll_start;
if scroll_top > max_scroll_top {
self.state.scroll_to(max_scroll_top);
}
self.state
.update_scroll_extent(scroll_data.visible_px, scroll_data.total_size);
}
}
}
@@ -1577,10 +1577,12 @@ impl SelectableElement for NewScrollable {
impl ClippedScrollStateHandle {
fn scroll_data(&self, viewport_size: Vector2F, child_size: Vector2F, axis: Axis) -> ScrollData {
let visible_px = viewport_size.along(axis).into_pixels();
let total_size = child_size.along(axis).into_pixels();
ScrollData {
scroll_start: self.scroll_start(),
visible_px: (viewport_size.along(axis)).into_pixels(),
total_size: child_size.along(axis).into_pixels(),
scroll_start: self.update_scroll_extent(visible_px, total_size),
visible_px,
total_size,
}
}
}