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);
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
106 let (matches, _search_task) = project
107 .update(cx, |this, cx| this.search(query, cx))
108 .unwrap();
109
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 } else {
122 break;
123 }
124 }
125 let elapsed = timer.elapsed();
126 println!(
127 "Finished project search after {elapsed:?}. Matched {matched_files} files and {matched_chunks} excerpts"
128 );
129 drop(project);
130 cx.update(|cx| cx.quit())?;
131
132 anyhow::Ok(())
133 })
134 .detach();
135 });
136 });
137 Ok(())
138}