glob.rs

 1use anyhow::{anyhow, Result};
 2use gpui::{App, AppContext, Entity, SharedString, Task};
 3use project::Project;
 4use schemars::JsonSchema;
 5use serde::Deserialize;
 6use std::{path::PathBuf, sync::Arc};
 7use util::paths::PathMatcher;
 8use worktree::Snapshot as WorktreeSnapshot;
 9
10use crate::{
11    templates::{GlobTemplate, Template, Templates},
12    thread::AgentTool,
13};
14
15// Description is dynamic, see `fn description` below
16#[derive(Deserialize, JsonSchema)]
17struct GlobInput {
18    /// A POSIX glob pattern
19    glob: SharedString,
20}
21
22struct GlobTool {
23    project: Entity<Project>,
24    templates: Arc<Templates>,
25}
26
27impl AgentTool for GlobTool {
28    type Input = GlobInput;
29
30    fn name(&self) -> SharedString {
31        "glob".into()
32    }
33
34    fn description(&self, cx: &mut App) -> SharedString {
35        let project_roots = self
36            .project
37            .read(cx)
38            .worktrees(cx)
39            .map(|worktree| worktree.read(cx).root_name().into())
40            .collect::<Vec<String>>()
41            .join("\n");
42
43        GlobTemplate { project_roots }
44            .render(&self.templates)
45            .expect("template failed to render")
46            .into()
47    }
48
49    fn needs_authorization(&self, _input: Self::Input, _cx: &App) -> bool {
50        false
51    }
52
53    fn run(self: Arc<Self>, input: Self::Input, cx: &mut App) -> Task<Result<String>> {
54        let path_matcher = match PathMatcher::new([&input.glob]) {
55            Ok(matcher) => matcher,
56            Err(error) => return Task::ready(Err(anyhow!(error))),
57        };
58
59        let snapshots: Vec<WorktreeSnapshot> = self
60            .project
61            .read(cx)
62            .worktrees(cx)
63            .map(|worktree| worktree.read(cx).snapshot())
64            .collect();
65
66        cx.background_spawn(async move {
67            let paths = snapshots.iter().flat_map(|snapshot| {
68                let root_name = PathBuf::from(snapshot.root_name());
69                snapshot
70                    .entries(false, 0)
71                    .map(move |entry| root_name.join(&entry.path))
72                    .filter(|path| path_matcher.is_match(&path))
73            });
74            let output = paths
75                .map(|path| format!("{}\n", path.display()))
76                .collect::<String>();
77            Ok(output)
78        })
79    }
80}