main.rs

  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}