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 = true)]
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 client::init_settings(cx);
63 language::init(cx);
64 Project::init_settings(cx);
65 let client = Client::production(cx);
66 let http_client = FakeHttpClient::with_200_response();
67 let (_, rx) = watch::channel(None);
68 let node = NodeRuntime::new(http_client, None, rx);
69 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
70 let registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
71 let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
72 let project = Project::local(
73 client,
74 node,
75 user_store,
76 registry,
77 fs,
78 Some(Default::default()),
79 cx,
80 );
81
82 project.clone().update(cx, move |_, cx| {
83 cx.spawn(async move |_, cx| {
84 println!("Loading worktrees");
85 let worktrees = project.update(cx, |this, cx| {
86 args.worktrees
87 .into_iter()
88 .map(|worktree| this.find_or_create_worktree(worktree, true, cx))
89 .collect::<Vec<_>>()
90 })?;
91
92 let worktrees = futures::future::join_all(worktrees)
93 .await
94 .into_iter()
95 .collect::<Result<Vec<_>, anyhow::Error>>()?;
96
97 for (worktree, _) in &worktrees {
98 worktree
99 .update(cx, |this, _| this.as_local().unwrap().scan_complete())?
100 .await;
101 }
102 println!("Worktrees loaded");
103
104 println!("Starting a project search");
105 let timer = std::time::Instant::now();
106 let mut first_match = None;
107 let matches = project
108 .update(cx, |this, cx| this.search(query, cx))
109 .unwrap();
110 let mut matched_files = 0;
111 let mut matched_chunks = 0;
112 while let Ok(match_result) = matches.recv().await {
113 if first_match.is_none() {
114 let time = timer.elapsed();
115 first_match = Some(time);
116 println!("First match found after {time:?}");
117 }
118 if let SearchResult::Buffer { ranges, .. } = match_result {
119 matched_files += 1;
120 matched_chunks += ranges.len();
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}