1use std::sync::Arc;
2
3use editor::Editor;
4use gpui::{AppContext as _, AsyncWindowContext, WeakEntity, WindowBounds, WindowOptions};
5use language::Buffer;
6use multi_buffer::Anchor;
7use project::search::SearchQuery;
8use workspace::searchable::SearchableItem;
9
10#[derive(Debug)]
11struct Args {
12 file: String,
13 query: String,
14 replace: Option<String>,
15 regex: bool,
16 whole_word: bool,
17 case_sensitive: bool,
18}
19
20fn parse_args() -> Args {
21 let mut args_iter = std::env::args().skip(1);
22 let mut parsed = Args {
23 file: String::new(),
24 query: String::new(),
25 replace: None,
26 regex: false,
27 whole_word: false,
28 case_sensitive: false,
29 };
30
31 let mut positional = Vec::new();
32 while let Some(arg) = args_iter.next() {
33 match arg.as_str() {
34 "--regex" => parsed.regex = true,
35 "--whole-word" => parsed.whole_word = true,
36 "--case-sensitive" => parsed.case_sensitive = true,
37 "-r" | "--replace" => {
38 parsed.replace = args_iter.next();
39 }
40 "--help" | "-h" => {
41 eprintln!(
42 "Usage: editor_benchmarks [OPTIONS] <FILE> <QUERY>\n\n\
43 Arguments:\n \
44 <FILE> Path to the file to search in\n \
45 <QUERY> The search query string\n\n\
46 Options:\n \
47 -r, --replace <TEXT> Replacement text (runs replace_all)\n \
48 --regex Treat query as regex\n \
49 --whole-word Match whole words only\n \
50 --case-sensitive Case-sensitive matching\n \
51 -h, --help Print help"
52 );
53 std::process::exit(0);
54 }
55 other => positional.push(other.to_string()),
56 }
57 }
58
59 if positional.len() < 2 {
60 eprintln!("Usage: editor_benchmarks [OPTIONS] <FILE> <QUERY>");
61 std::process::exit(1);
62 }
63 parsed.file = positional.remove(0);
64 parsed.query = positional.remove(0);
65 parsed
66}
67
68fn main() {
69 let args = parse_args();
70
71 let file_contents = std::fs::read_to_string(&args.file).expect("failed to read input file");
72 let file_len = file_contents.len();
73 println!("Read {} ({file_len} bytes)", args.file);
74
75 let mut query = if args.regex {
76 SearchQuery::regex(
77 &args.query,
78 args.whole_word,
79 args.case_sensitive,
80 false,
81 false,
82 Default::default(),
83 Default::default(),
84 false,
85 None,
86 )
87 .expect("invalid regex query")
88 } else {
89 SearchQuery::text(
90 &args.query,
91 args.whole_word,
92 args.case_sensitive,
93 false,
94 Default::default(),
95 Default::default(),
96 false,
97 None,
98 )
99 .expect("invalid text query")
100 };
101
102 if let Some(replacement) = args.replace.as_deref() {
103 query = query.with_replacement(replacement.to_string());
104 }
105
106 let query = Arc::new(query);
107 let has_replacement = args.replace.is_some();
108
109 gpui_platform::headless().run(move |cx| {
110 release_channel::init_test(
111 semver::Version::new(0, 0, 0),
112 release_channel::ReleaseChannel::Dev,
113 cx,
114 );
115 settings::init(cx);
116 theme::init(theme::LoadThemes::JustBase, cx);
117 editor::init(cx);
118
119 let buffer = cx.new(|cx| Buffer::local(file_contents, cx));
120
121 let window_handle = cx
122 .open_window(
123 WindowOptions {
124 window_bounds: Some(WindowBounds::Windowed(gpui::Bounds {
125 origin: Default::default(),
126 size: gpui::size(gpui::px(800.0), gpui::px(600.0)),
127 })),
128 focus: false,
129 show: false,
130 ..Default::default()
131 },
132 |window, cx| cx.new(|cx| Editor::for_buffer(buffer, None, window, cx)),
133 )
134 .expect("failed to open window");
135
136 window_handle
137 .update(cx, move |_, window, cx| {
138 cx.spawn_in(
139 window,
140 async move |weak: WeakEntity<Editor>,
141 cx: &mut AsyncWindowContext|
142 -> anyhow::Result<()> {
143 let find_task = weak.update_in(cx, |editor, window, cx| {
144 editor.find_matches(query.clone(), window, cx)
145 })?;
146
147 println!("Finding matches...");
148 let timer = std::time::Instant::now();
149 let matches: Vec<std::ops::Range<Anchor>> = find_task.await;
150 let find_elapsed = timer.elapsed();
151 println!("Found {} matches in {find_elapsed:?}", matches.len());
152
153 if has_replacement && !matches.is_empty() {
154 window_handle.update(cx, |editor: &mut Editor, window, cx| {
155 let mut match_iter = matches.iter();
156 println!("Replacing all matches...");
157 let timer = std::time::Instant::now();
158 editor.replace_all(
159 &mut match_iter,
160 &query,
161 Default::default(),
162 window,
163 cx,
164 );
165 let replace_elapsed = timer.elapsed();
166 println!(
167 "Replaced {} matches in {replace_elapsed:?}",
168 matches.len()
169 );
170 })?;
171 }
172
173 std::process::exit(0);
174 },
175 )
176 .detach();
177 })
178 .unwrap();
179 });
180}