diff --git a/Cargo.lock b/Cargo.lock index 8c0f51e2893f53c3e6ef78dbd5ef711f8d9dc95c..ea2db9546109c19e65ce0960df8d5abb041dcb6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5461,6 +5461,24 @@ dependencies = [ "ztracing", ] +[[package]] +name = "editor_benchmarks" +version = "0.1.0" +dependencies = [ + "anyhow", + "editor", + "gpui", + "gpui_platform", + "language", + "multi_buffer", + "project", + "release_channel", + "semver", + "settings", + "theme", + "workspace", +] + [[package]] name = "either" version = "1.15.0" diff --git a/Cargo.toml b/Cargo.toml index 59f8e265694f060c9f65b30143b79e324de1f08c..c72c370924f107cdd0ec37e30b903b4c7e34eeb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "crates/edit_prediction_types", "crates/edit_prediction_ui", "crates/editor", + "crates/editor_benchmarks", "crates/encoding_selector", "crates/env_var", "crates/etw_tracing", diff --git a/crates/editor_benchmarks/Cargo.toml b/crates/editor_benchmarks/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..8db5d4b26aefd8fbbaf1143ce6a3587b7e0cc0e2 --- /dev/null +++ b/crates/editor_benchmarks/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "editor_benchmarks" +version = "0.1.0" +publish.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +editor.workspace = true +gpui.workspace = true +gpui_platform.workspace = true +language.workspace = true +multi_buffer.workspace = true +project.workspace = true +release_channel.workspace = true +semver.workspace = true +settings.workspace = true +theme.workspace = true +workspace.workspace = true + +[lints] +workspace = true diff --git a/crates/editor_benchmarks/src/main.rs b/crates/editor_benchmarks/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a6888bf30d390c1c975912ff39bdba556937779 --- /dev/null +++ b/crates/editor_benchmarks/src/main.rs @@ -0,0 +1,178 @@ +use std::sync::Arc; + +use editor::Editor; +use gpui::{ + AppContext as _, AsyncApp, AsyncWindowContext, WeakEntity, WindowBounds, WindowOptions, +}; +use language::Buffer; +use multi_buffer::Anchor; +use project::search::SearchQuery; +use workspace::searchable::SearchableItem; + +#[derive(Debug)] +struct Args { + file: String, + query: String, + replace: Option, + regex: bool, + whole_word: bool, + case_sensitive: bool, +} + +fn parse_args() -> Args { + let mut args_iter = std::env::args().skip(1); + let mut parsed = Args { + file: String::new(), + query: String::new(), + replace: None, + regex: false, + whole_word: false, + case_sensitive: false, + }; + + let mut positional = Vec::new(); + while let Some(arg) = args_iter.next() { + match arg.as_str() { + "--regex" => parsed.regex = true, + "--whole-word" => parsed.whole_word = true, + "--case-sensitive" => parsed.case_sensitive = true, + "-r" | "--replace" => { + parsed.replace = args_iter.next(); + } + "--help" | "-h" => { + eprintln!( + "Usage: editor_benchmarks [OPTIONS] \n\n\ + Arguments:\n \ + Path to the file to search in\n \ + The search query string\n\n\ + Options:\n \ + -r, --replace Replacement text (runs replace_all)\n \ + --regex Treat query as regex\n \ + --whole-word Match whole words only\n \ + --case-sensitive Case-sensitive matching\n \ + -h, --help Print help" + ); + std::process::exit(0); + } + other => positional.push(other.to_string()), + } + } + + if positional.len() < 2 { + eprintln!("Usage: editor_benchmarks [OPTIONS] "); + std::process::exit(1); + } + parsed.file = positional.remove(0); + parsed.query = positional.remove(0); + parsed +} + +fn main() { + let args = parse_args(); + + dbg!(&args); + let file_contents = std::fs::read_to_string(&args.file).expect("failed to read input file"); + let file_len = file_contents.len(); + println!("Read {} ({file_len} bytes)", args.file); + + let mut query = if args.regex { + SearchQuery::regex( + &args.query, + args.whole_word, + args.case_sensitive, + false, + false, + Default::default(), + Default::default(), + false, + None, + ) + .expect("invalid regex query") + } else { + SearchQuery::text( + &args.query, + args.whole_word, + args.case_sensitive, + false, + Default::default(), + Default::default(), + false, + None, + ) + .expect("invalid text query") + }; + + if let Some(replacement) = args.replace.as_deref() { + query = query.with_replacement(replacement.to_string()); + } + + let query = Arc::new(query); + let has_replacement = args.replace.is_some(); + + gpui_platform::headless().run(move |cx| { + release_channel::init_test( + semver::Version::new(0, 0, 0), + release_channel::ReleaseChannel::Dev, + cx, + ); + settings::init(cx); + theme::init(theme::LoadThemes::JustBase, cx); + editor::init(cx); + + let buffer = cx.new(|cx| Buffer::local(file_contents, cx)); + + let window_handle = cx + .open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(gpui::Bounds { + origin: Default::default(), + size: gpui::size(gpui::px(800.0), gpui::px(600.0)), + })), + focus: false, + show: false, + ..Default::default() + }, + |window, cx| cx.new(|cx| Editor::for_buffer(buffer, None, window, cx)), + ) + .expect("failed to open window"); + + window_handle.update(cx, move |this, window, cx| { + cx.spawn_in( + window, + async move |weak: WeakEntity, cx: &mut AsyncWindowContext| { + dbg!("A"); + let find_task = weak.update_in(cx, |editor, window, cx| { + editor.find_matches(query.clone(), window, cx) + })?; + + println!("Finding matches..."); + let timer = std::time::Instant::now(); + let matches: Vec> = find_task.await; + let find_elapsed = timer.elapsed(); + println!("Found {} matches in {find_elapsed:?}", matches.len()); + + if has_replacement && !matches.is_empty() { + window_handle.update(cx, |editor: &mut Editor, window, cx| { + let mut match_iter = matches.iter(); + println!("Replacing all matches..."); + let timer = std::time::Instant::now(); + editor.replace_all( + &mut match_iter, + &query, + Default::default(), + window, + cx, + ); + let replace_elapsed = timer.elapsed(); + println!("Replaced {} matches in {replace_elapsed:?}", matches.len()); + })?; + } + + cx.update(|_, cx: &mut gpui::App| cx.quit()); + anyhow::Ok(()) + }, + ) + .detach(); + }); + }); +}