assistant2: Wire up the directory context picker (#22582)

Marshall Bowers and Agus created

This PR wires up the functionality of the directory context picker.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>

Change summary

Cargo.lock                                                       |  1 
crates/assistant2/Cargo.toml                                     |  1 
crates/assistant2/src/context_picker.rs                          | 16 
crates/assistant2/src/context_picker/directory_context_picker.rs | 72 +
crates/assistant2/src/context_picker/file_context_picker.rs      |  5 
5 files changed, 75 insertions(+), 20 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -494,7 +494,6 @@ dependencies = [
  "project",
  "proto",
  "rand 0.8.5",
- "release_channel",
  "rope",
  "schemars",
  "serde",

crates/assistant2/Cargo.toml 🔗

@@ -50,7 +50,6 @@ parking_lot.workspace = true
 picker.workspace = true
 project.workspace = true
 proto.workspace = true
-release_channel.workspace = true
 rope.workspace = true
 schemars.workspace = true
 serde.workspace = true

crates/assistant2/src/context_picker.rs 🔗

@@ -10,7 +10,6 @@ use gpui::{
     WeakModel, WeakView,
 };
 use picker::{Picker, PickerDelegate};
-use release_channel::ReleaseChannel;
 use ui::{prelude::*, ListItem, ListItemSpacing};
 use util::ResultExt;
 use workspace::Workspace;
@@ -56,16 +55,11 @@ impl ContextPicker {
             kind: ContextPickerEntryKind::File,
             icon: IconName::File,
         });
-        let release_channel = ReleaseChannel::global(cx);
-        // The directory context picker isn't fully implemented yet, so limit it
-        // to development builds.
-        if release_channel == ReleaseChannel::Dev {
-            entries.push(ContextPickerEntry {
-                name: "Folder".into(),
-                kind: ContextPickerEntryKind::Directory,
-                icon: IconName::Folder,
-            });
-        }
+        entries.push(ContextPickerEntry {
+            name: "Folder".into(),
+            kind: ContextPickerEntryKind::Directory,
+            icon: IconName::Folder,
+        });
         entries.push(ContextPickerEntry {
             name: "Fetch".into(),
             kind: ContextPickerEntryKind::FetchedUrl,

crates/assistant2/src/context_picker/directory_context_picker.rs 🔗

@@ -1,19 +1,18 @@
-// TODO: Remove this when we finish the implementation.
-#![allow(unused)]
-
 use std::path::Path;
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
+use anyhow::anyhow;
 use fuzzy::PathMatch;
 use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
 use picker::{Picker, PickerDelegate};
-use project::{PathMatchCandidateSet, WorktreeId};
+use project::{PathMatchCandidateSet, ProjectPath, Worktree, WorktreeId};
 use ui::{prelude::*, ListItem};
 use util::ResultExt as _;
 use workspace::Workspace;
 
 use crate::context::ContextKind;
+use crate::context_picker::file_context_picker::codeblock_fence_for_path;
 use crate::context_picker::{ConfirmBehavior, ContextPicker};
 use crate::context_store::ContextStore;
 
@@ -193,14 +192,61 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
         let worktree_id = WorktreeId::from_usize(mat.worktree_id);
         let confirm_behavior = self.confirm_behavior;
         cx.spawn(|this, mut cx| async move {
+            let worktree = project.update(&mut cx, |project, cx| {
+                project
+                    .worktree_for_id(worktree_id, cx)
+                    .ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
+            })??;
+
+            let files = worktree.update(&mut cx, |worktree, _cx| {
+                collect_files_in_path(worktree, &path)
+            })?;
+
+            let open_buffer_tasks = project.update(&mut cx, |project, cx| {
+                files
+                    .into_iter()
+                    .map(|file_path| {
+                        project.open_buffer(
+                            ProjectPath {
+                                worktree_id,
+                                path: file_path.clone(),
+                            },
+                            cx,
+                        )
+                    })
+                    .collect::<Vec<_>>()
+            })?;
+
+            let open_all_buffers_tasks = cx.background_executor().spawn(async move {
+                let mut buffers = Vec::with_capacity(open_buffer_tasks.len());
+
+                for open_buffer_task in open_buffer_tasks {
+                    let buffer = open_buffer_task.await?;
+
+                    buffers.push(buffer);
+                }
+
+                anyhow::Ok(buffers)
+            });
+
+            let buffers = open_all_buffers_tasks.await?;
+
             this.update(&mut cx, |this, cx| {
                 let mut text = String::new();
 
-                // TODO: Add the files from the selected directory.
+                for buffer in buffers {
+                    text.push_str(&codeblock_fence_for_path(Some(&path), None));
+                    text.push_str(&buffer.read(cx).text());
+                    if !text.ends_with('\n') {
+                        text.push('\n');
+                    }
+
+                    text.push_str("```\n");
+                }
 
                 this.delegate
                     .context_store
-                    .update(cx, |context_store, cx| {
+                    .update(cx, |context_store, _cx| {
                         context_store.insert_context(
                             ContextKind::Directory,
                             path.to_string_lossy().to_string(),
@@ -247,3 +293,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
         )
     }
 }
+
+fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
+    let mut files = Vec::new();
+
+    for entry in worktree.child_entries(path) {
+        if entry.is_dir() {
+            files.extend(collect_files_in_path(worktree, &entry.path));
+        } else if entry.is_file() {
+            files.push(entry.path.clone());
+        }
+    }
+
+    files
+}

crates/assistant2/src/context_picker/file_context_picker.rs 🔗

@@ -316,7 +316,10 @@ impl PickerDelegate for FileContextPickerDelegate {
     }
 }
 
-fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<RangeInclusive<u32>>) -> String {
+pub(crate) fn codeblock_fence_for_path(
+    path: Option<&Path>,
+    row_range: Option<RangeInclusive<u32>>,
+) -> String {
     let mut text = String::new();
     write!(text, "```").unwrap();