assistant: Insert creases for tool output (#17464)

Marshall Bowers created

This PR makes it so we insert creases for tool output.

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs | 41 +++++++++++++++++++++++++++
crates/assistant/src/context.rs         | 37 ++++++++++++++++++-----
2 files changed, 69 insertions(+), 9 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -2198,6 +2198,47 @@ impl ContextEditor {
                     }
                 }
             }
+            ContextEvent::ToolFinished {
+                tool_use_id,
+                output_range,
+            } => {
+                self.editor.update(cx, |editor, cx| {
+                    let buffer = editor.buffer().read(cx).snapshot(cx);
+                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
+                    let excerpt_id = *excerpt_id;
+
+                    let placeholder = FoldPlaceholder {
+                        render: render_fold_icon_button(
+                            cx.view().downgrade(),
+                            IconName::PocketKnife,
+                            format!("Tool Result: {tool_use_id}").into(),
+                        ),
+                        constrain_width: false,
+                        merge_adjacent: false,
+                    };
+                    let render_trailer =
+                        move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
+
+                    let start = buffer
+                        .anchor_in_excerpt(excerpt_id, output_range.start)
+                        .unwrap();
+                    let end = buffer
+                        .anchor_in_excerpt(excerpt_id, output_range.end)
+                        .unwrap();
+
+                    let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
+
+                    let crease = Crease::new(
+                        start..end,
+                        placeholder,
+                        fold_toggle("tool-use"),
+                        render_trailer,
+                    );
+
+                    editor.insert_creases([crease], cx);
+                    editor.fold_at(&FoldAt { buffer_row }, cx);
+                });
+            }
             ContextEvent::Operation(_) => {}
             ContextEvent::ShowAssistError(error_message) => {
                 self.error_message = Some(error_message.clone());

crates/assistant/src/context.rs 🔗

@@ -307,6 +307,10 @@ pub enum ContextEvent {
         expand_result: bool,
     },
     UsePendingTools,
+    ToolFinished {
+        tool_use_id: Arc<str>,
+        output_range: Range<language::Anchor>,
+    },
     Operation(ContextOperation),
 }
 
@@ -1923,28 +1927,43 @@ impl Context {
 
     pub fn insert_tool_output(
         &mut self,
-        tool_id: Arc<str>,
+        tool_use_id: Arc<str>,
         output: Task<Result<String>>,
         cx: &mut ModelContext<Self>,
     ) {
         let insert_output_task = cx.spawn(|this, mut cx| {
-            let tool_id = tool_id.clone();
+            let tool_use_id = tool_use_id.clone();
             async move {
                 let output = output.await;
                 this.update(&mut cx, |this, cx| match output {
                     Ok(mut output) => {
-                        if !output.ends_with('\n') {
-                            output.push('\n');
+                        const NEWLINE: char = '\n';
+
+                        if !output.ends_with(NEWLINE) {
+                            output.push(NEWLINE);
                         }
 
-                        this.buffer.update(cx, |buffer, cx| {
-                            let buffer_end = buffer.len().to_offset(buffer);
+                        let anchor_range = this.buffer.update(cx, |buffer, cx| {
+                            let insert_start = buffer.len().to_offset(buffer);
+                            let insert_end = insert_start;
+
+                            let start = insert_start;
+                            let end = start + output.len() - NEWLINE.len_utf8();
+
+                            buffer.edit([(insert_start..insert_end, output)], None, cx);
+
+                            let output_range = buffer.anchor_after(start)..buffer.anchor_after(end);
+
+                            output_range
+                        });
 
-                            buffer.edit([(buffer_end..buffer_end, output)], None, cx);
+                        cx.emit(ContextEvent::ToolFinished {
+                            tool_use_id,
+                            output_range: anchor_range,
                         });
                     }
                     Err(err) => {
-                        if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_id) {
+                        if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_use_id) {
                             tool_use.status = PendingToolUseStatus::Error(err.to_string());
                         }
                     }
@@ -1953,7 +1972,7 @@ impl Context {
             }
         });
 
-        if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_id) {
+        if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
             tool_use.status = PendingToolUseStatus::Running {
                 _task: insert_output_task.shared(),
             };