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 cx,
77 );
78
79 project.clone().update(cx, move |_, cx| {
80 cx.spawn(async move |_, cx| {
81 println!("Loading worktrees");
82 let worktrees = project.update(cx, |this, cx| {
83 args.worktrees
84 .into_iter()
85 .map(|worktree| this.find_or_create_worktree(worktree, true, cx))
86 .collect::<Vec<_>>()
87 })?;
88
89 let worktrees = futures::future::join_all(worktrees)
90 .await
91 .into_iter()
92 .collect::<Result<Vec<_>, anyhow::Error>>()?;
93
94 for (worktree, _) in &worktrees {
95 worktree
96 .update(cx, |this, _| this.as_local().unwrap().scan_complete())?
97 .await;
98 }
99 println!("Worktrees loaded");
100
101 println!("Starting a project search");
102 let timer = std::time::Instant::now();
103 let mut first_match = None;
104 let matches = project
105 .update(cx, |this, cx| this.search(query, cx))
106 .unwrap();
107 let mut matched_files = 0;
108 let mut matched_chunks = 0;
109 while let Ok(match_result) = matches.recv().await {
110 if first_match.is_none() {
111 let time = timer.elapsed();
112 first_match = Some(time);
113 println!("First match found after {time:?}");
114 }
115 if let SearchResult::Buffer { ranges, .. } = match_result {
116 matched_files += 1;
117 matched_chunks += ranges.len();
118 } else {
119 break;
120 }
121 }
122 let elapsed = timer.elapsed();
123 println!(
124 "Finished project search after {elapsed:?}. Matched {matched_files} files and {matched_chunks} excerpts"
125 );
126 drop(project);
127 cx.update(|cx| cx.quit())?;
128
129 anyhow::Ok(())
130 })
131 .detach();
132 });
133 });
134 Ok(())
135}