1use std::sync::Arc;
2
3use clap::Parser;
4use client::{Client, UserStore};
5use gpui::{AppContext as _, Application};
6use http_client::FakeHttpClient;
7use language::LanguageRegistry;
8use node_runtime::NodeRuntime;
9use project::{
10 Project, RealFs,
11 search::{SearchQuery, SearchResult},
12};
13
14#[derive(Parser)]
15struct Args {
16 /// List of worktrees to run the search against.
17 worktrees: Vec<String>,
18 #[clap(short)]
19 query: String,
20 /// Treat query as a regex.
21 #[clap(short, long)]
22 regex: bool,
23 /// Matches have to be standalone words.
24 #[clap(long)]
25 whole_word: bool,
26 /// Make matching case-sensitive.
27 #[clap(long, default_value_t = false)]
28 case_sensitive: bool,
29 /// Include gitignored files in the search.
30 #[clap(long)]
31 include_ignored: bool,
32}
33
34fn main() -> Result<(), anyhow::Error> {
35 let args = Args::parse();
36 let query = if args.regex {
37 SearchQuery::regex(
38 args.query,
39 args.whole_word,
40 args.case_sensitive,
41 args.include_ignored,
42 false,
43 Default::default(),
44 Default::default(),
45 false,
46 None,
47 )
48 } else {
49 SearchQuery::text(
50 args.query,
51 args.whole_word,
52 args.case_sensitive,
53 args.include_ignored,
54 Default::default(),
55 Default::default(),
56 false,
57 None,
58 )
59 }?;
60 Application::headless().run(|cx| {
61 settings::init(cx);
62 let client = Client::production(cx);
63 let http_client = FakeHttpClient::with_200_response();
64 let (_, rx) = watch::channel(None);
65 let node = NodeRuntime::new(http_client, None, rx, None);
66 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
67 let registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
68 let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
69 let project = Project::local(
70 client,
71 node,
72 user_store,
73 registry,
74 fs,
75 Some(Default::default()),
76 false,
77 cx,
78 );
79
80 project.clone().update(cx, move |_, cx| {
81 cx.spawn(async move |_, cx| {
82 println!("Loading worktrees");
83 let worktrees = project.update(cx, |this, cx| {
84 args.worktrees
85 .into_iter()
86 .map(|worktree| this.find_or_create_worktree(worktree, true, cx))
87 .collect::<Vec<_>>()
88 })?;
89
90 let worktrees = futures::future::join_all(worktrees)
91 .await
92 .into_iter()
93 .collect::<Result<Vec<_>, anyhow::Error>>()?;
94
95 for (worktree, _) in &worktrees {
96 worktree
97 .update(cx, |this, _| this.as_local().unwrap().scan_complete())?
98 .await;
99 }
100 println!("Worktrees loaded");
101
102 println!("Starting a project search");
103 let timer = std::time::Instant::now();
104 let mut first_match = None;
105 let matches = project
106 .update(cx, |this, cx| this.search(query, cx))
107 .unwrap();
108 let mut matched_files = 0;
109 let mut matched_chunks = 0;
110 while let Ok(match_result) = matches.recv().await {
111 if first_match.is_none() {
112 let time = timer.elapsed();
113 first_match = Some(time);
114 println!("First match found after {time:?}");
115 }
116 if let SearchResult::Buffer { ranges, .. } = match_result {
117 matched_files += 1;
118 matched_chunks += ranges.len();
119 } else {
120 break;
121 }
122 }
123 let elapsed = timer.elapsed();
124 println!(
125 "Finished project search after {elapsed:?}. Matched {matched_files} files and {matched_chunks} excerpts"
126 );
127 drop(project);
128 cx.update(|cx| cx.quit())?;
129
130 anyhow::Ok(())
131 })
132 .detach();
133 });
134 });
135 Ok(())
136}