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}