From 2f378be1a826ddb5354b66239c42031c94509515 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 May 2021 12:00:17 -0700 Subject: [PATCH 01/56] Introduce LanguageRegistry object * Include it, along with settings in `OpenParams` grouped under a new struct called `AppState` Co-Authored-By: Antonio Scandurra Co-Authored-By: Nathan Sobo --- Cargo.lock | 24 ++++++- zed/Cargo.toml | 2 + zed/languages/rust/highlights.scm | 6 ++ zed/src/editor/buffer/mod.rs | 24 +++++-- zed/src/file_finder.rs | 50 ++++++++++---- zed/src/language.rs | 105 ++++++++++++++++++++++++++++++ zed/src/lib.rs | 7 ++ zed/src/main.rs | 18 +++-- zed/src/test.rs | 13 +++- zed/src/workspace.rs | 68 +++++++++++++------ 10 files changed, 271 insertions(+), 46 deletions(-) create mode 100644 zed/languages/rust/highlights.scm create mode 100644 zed/src/language.rs diff --git a/Cargo.lock b/Cargo.lock index 21a08332c5e08e81c35c0d9d4db343a38983d0d6..4562f2708c0d46750cc3259e172f326c214a88e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,7 +1202,7 @@ dependencies = [ "smallvec", "smol", "tiny-skia", - "tree-sitter", + "tree-sitter 0.17.1", "usvg", ] @@ -2712,6 +2712,26 @@ dependencies = [ "regex", ] +[[package]] +name = "tree-sitter" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f41201fed3db3b520405a9c01c61773a250d4c3f43e9861c14b2bb232c981ab" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-rust" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784f7ef9cdbd4c895dc2d4bb785e95b4a5364a602eec803681db83d1927ddf15" +dependencies = [ + "cc", + "tree-sitter 0.19.3", +] + [[package]] name = "ttf-parser" version = "0.9.0" @@ -2987,5 +3007,7 @@ dependencies = [ "smallvec", "smol", "tempdir", + "tree-sitter 0.19.3", + "tree-sitter-rust", "unindent", ] diff --git a/zed/Cargo.toml b/zed/Cargo.toml index a1749f2474930dacb4ba2db0d52b8e7dc9ea4d13..91477e2831ad0b57aca08d372ba2817c658fcf12 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -38,6 +38,8 @@ similar = "1.3" simplelog = "0.9" smallvec = {version = "1.6", features = ["union"]} smol = "1.2.5" +tree-sitter = "0.19.3" +tree-sitter-rust = "0.19.0" [dev-dependencies] cargo-bundle = "0.5.0" diff --git a/zed/languages/rust/highlights.scm b/zed/languages/rust/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..32b3dcac607798a8e1fb47ba6a3bb7584a557671 --- /dev/null +++ b/zed/languages/rust/highlights.scm @@ -0,0 +1,6 @@ +[ + "else" + "fn" + "if" + "while" +] @keyword \ No newline at end of file diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 1b458388bc1cf26495fd97a22ce82bed935528aa..9fdf4e0c3620ee20993d0db1fb5f2ac562992043 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -12,6 +12,7 @@ use similar::{ChangeTag, TextDiff}; use crate::{ editor::Bias, + language::Language, operation_queue::{self, OperationQueue}, sum_tree::{self, FilterCursor, SeekBias, SumTree}, time::{self, ReplicaId}, @@ -68,6 +69,7 @@ pub struct Buffer { undo_map: UndoMap, history: History, file: Option, + language: Option>, selections: HashMap>, pub selections_last_update: SelectionsVersion, deferred_ops: OperationQueue, @@ -357,22 +359,24 @@ impl Buffer { base_text: T, ctx: &mut ModelContext, ) -> Self { - Self::build(replica_id, History::new(base_text.into()), None, ctx) + Self::build(replica_id, History::new(base_text.into()), None, None, ctx) } pub fn from_history( replica_id: ReplicaId, history: History, file: Option, + language: Option>, ctx: &mut ModelContext, ) -> Self { - Self::build(replica_id, history, file, ctx) + Self::build(replica_id, history, file, language, ctx) } fn build( replica_id: ReplicaId, history: History, file: Option, + language: Option>, ctx: &mut ModelContext, ) -> Self { let saved_mtime; @@ -472,6 +476,7 @@ impl Buffer { undo_map: Default::default(), history, file, + language, saved_mtime, selections: HashMap::default(), selections_last_update: 0, @@ -1884,6 +1889,7 @@ impl Clone for Buffer { selections_last_update: self.selections_last_update.clone(), deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), + language: self.language.clone(), deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, local_clock: self.local_clock.clone(), @@ -2812,7 +2818,7 @@ mod tests { let file1 = app.update(|ctx| tree.file("file1", ctx)).await; let buffer1 = app.add_model(|ctx| { - Buffer::from_history(0, History::new("abc".into()), Some(file1), ctx) + Buffer::from_history(0, History::new("abc".into()), Some(file1), None, ctx) }); let events = Rc::new(RefCell::new(Vec::new())); @@ -2877,7 +2883,7 @@ mod tests { move |_, event, _| events.borrow_mut().push(event.clone()) }); - Buffer::from_history(0, History::new("abc".into()), Some(file2), ctx) + Buffer::from_history(0, History::new("abc".into()), Some(file2), None, ctx) }); fs::remove_file(dir.path().join("file2")).unwrap(); @@ -2896,7 +2902,7 @@ mod tests { move |_, event, _| events.borrow_mut().push(event.clone()) }); - Buffer::from_history(0, History::new("abc".into()), Some(file3), ctx) + Buffer::from_history(0, History::new("abc".into()), Some(file3), None, ctx) }); tree.flush_fs_events(&app).await; @@ -2923,7 +2929,13 @@ mod tests { let abs_path = dir.path().join("the-file"); let file = app.update(|ctx| tree.file("the-file", ctx)).await; let buffer = app.add_model(|ctx| { - Buffer::from_history(0, History::new(initial_contents.into()), Some(file), ctx) + Buffer::from_history( + 0, + History::new(initial_contents.into()), + Some(file), + None, + ctx, + ) }); // Add a cursor at the start of each row. diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 7e57057e7215dee4a6ff67fc244aa2e45b99c34e..5c3cffff7b792f5dec627e918234d8a5339ac677 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -458,7 +458,11 @@ impl FileFinder { #[cfg(test)] mod tests { use super::*; - use crate::{editor, settings, test::temp_tree, workspace::Workspace}; + use crate::{ + editor, + test::{build_app_state, temp_tree}, + workspace::Workspace, + }; use serde_json::json; use std::fs; use tempdir::TempDir; @@ -474,9 +478,10 @@ mod tests { editor::init(ctx); }); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (window_id, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings, ctx); + let mut workspace = + Workspace::new(0, app_state.settings, app_state.language_registry, ctx); workspace.add_worktree(tmp_dir.path(), ctx); workspace }); @@ -541,15 +546,21 @@ mod tests { "hi": "", "hiccup": "", })); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (_, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings.clone(), ctx); + let mut workspace = Workspace::new( + 0, + app_state.settings.clone(), + app_state.language_registry.clone(), + ctx, + ); workspace.add_worktree(tmp_dir.path(), ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; - let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); + let (_, finder) = + app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx)); let query = "hi".to_string(); finder @@ -598,15 +609,21 @@ mod tests { fs::create_dir(&dir_path).unwrap(); fs::write(&file_path, "").unwrap(); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (_, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings.clone(), ctx); + let mut workspace = Workspace::new( + 0, + app_state.settings.clone(), + app_state.language_registry.clone(), + ctx, + ); workspace.add_worktree(&file_path, ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; - let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); + let (_, finder) = + app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx)); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. @@ -641,9 +658,17 @@ mod tests { "dir1": { "a.txt": "" }, "dir2": { "a.txt": "" } })); - let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx)); + let app_state = app.read(build_app_state); + + let (_, workspace) = app.add_window(|ctx| { + Workspace::new( + 0, + app_state.settings.clone(), + app_state.language_registry.clone(), + ctx, + ) + }); workspace .update(&mut app, |workspace, ctx| { @@ -656,7 +681,8 @@ mod tests { app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; - let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); + let (_, finder) = + app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx)); // Run a search that matches two files with the same relative path. finder diff --git a/zed/src/language.rs b/zed/src/language.rs new file mode 100644 index 0000000000000000000000000000000000000000..666e55c892c2844aa4d9100181b189df117bee77 --- /dev/null +++ b/zed/src/language.rs @@ -0,0 +1,105 @@ +use rust_embed::RustEmbed; +use std::{path::Path, sync::Arc}; +use tree_sitter::{Language as Grammar, Query}; + +pub use tree_sitter::{Parser, Tree}; + +#[derive(RustEmbed)] +#[folder = "languages"] +pub struct LanguageDir; + +pub struct Language { + name: String, + grammar: Grammar, + highlight_query: Query, + path_suffixes: Vec, +} + +pub struct LanguageRegistry { + languages: Vec>, +} + +impl LanguageRegistry { + pub fn new() -> Self { + let grammar = tree_sitter_rust::language(); + let rust_language = Language { + name: "Rust".to_string(), + grammar, + highlight_query: Query::new( + grammar, + std::str::from_utf8(LanguageDir::get("rust/highlights.scm").unwrap().as_ref()) + .unwrap(), + ) + .unwrap(), + path_suffixes: vec!["rs".to_string()], + }; + + Self { + languages: vec![Arc::new(rust_language)], + } + } + + pub fn select_language(&self, path: impl AsRef) -> Option<&Arc> { + let path = path.as_ref(); + let filename = path.file_name().and_then(|name| name.to_str()); + let extension = path.extension().and_then(|name| name.to_str()); + let path_suffixes = [extension, filename]; + self.languages.iter().find(|language| { + language + .path_suffixes + .iter() + .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_select_language() { + let grammar = tree_sitter_rust::language(); + let registry = LanguageRegistry { + languages: vec![ + Arc::new(Language { + name: "Rust".to_string(), + grammar, + highlight_query: Query::new(grammar, "").unwrap(), + path_suffixes: vec!["rs".to_string()], + }), + Arc::new(Language { + name: "Make".to_string(), + grammar, + highlight_query: Query::new(grammar, "").unwrap(), + path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], + }), + ], + }; + + // matching file extension + assert_eq!( + registry.select_language("zed/lib.rs").map(get_name), + Some("Rust") + ); + assert_eq!( + registry.select_language("zed/lib.mk").map(get_name), + Some("Make") + ); + + // matching filename + assert_eq!( + registry.select_language("zed/Makefile").map(get_name), + Some("Make") + ); + + // matching suffix that is not the full file extension or filename + assert_eq!(registry.select_language("zed/cars").map(get_name), None); + assert_eq!(registry.select_language("zed/a.cars").map(get_name), None); + assert_eq!(registry.select_language("zed/sumk").map(get_name), None); + + fn get_name(language: &Arc) -> &str { + language.name.as_str() + } + } +} diff --git a/zed/src/lib.rs b/zed/src/lib.rs index 7dd383f56e612e8f0db08d6affc8d649d6acd702..936185bf884277f091792737125693d1d4fbe0a1 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -1,6 +1,7 @@ pub mod assets; pub mod editor; pub mod file_finder; +pub mod language; pub mod menus; mod operation_queue; pub mod settings; @@ -11,3 +12,9 @@ mod time; mod util; pub mod workspace; mod worktree; + +#[derive(Clone)] +pub struct AppState { + pub settings: postage::watch::Receiver, + pub language_registry: std::sync::Arc, +} diff --git a/zed/src/main.rs b/zed/src/main.rs index 773acf147e03d55052ca85c8c819989dc40cd27f..7d264e9c329405eb4881db2ad57a72871357340f 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -4,19 +4,27 @@ use fs::OpenOptions; use log::LevelFilter; use simplelog::SimpleLogger; -use std::{fs, path::PathBuf}; +use std::{fs, path::PathBuf, sync::Arc}; use zed::{ - assets, editor, file_finder, menus, settings, + assets, editor, file_finder, language, menus, settings, workspace::{self, OpenParams}, + AppState, }; fn main() { init_logger(); let app = gpui::App::new(assets::Assets).unwrap(); - let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); + + let (_, settings) = settings::channel(&app.font_cache()).unwrap(); + let language_registry = Arc::new(language::LanguageRegistry::new()); + let app_state = AppState { + language_registry, + settings, + }; + app.run(move |ctx| { - ctx.set_menus(menus::menus(settings_rx.clone())); + ctx.set_menus(menus::menus(app_state.settings.clone())); workspace::init(ctx); editor::init(ctx); file_finder::init(ctx); @@ -31,7 +39,7 @@ fn main() { "workspace:open_paths", OpenParams { paths, - settings: settings_rx, + app_state: app_state.clone(), }, ); } diff --git a/zed/src/test.rs b/zed/src/test.rs index 1d155d4a5ab7a4687a8e3e8d15972edcdd5d3880..5efb0f3e3573f3165596b299b02c6dd844e10a4d 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -1,9 +1,11 @@ -use crate::time::ReplicaId; +use crate::{language::LanguageRegistry, settings, time::ReplicaId, AppState}; use ctor::ctor; +use gpui::AppContext; use rand::Rng; use std::{ collections::BTreeMap, path::{Path, PathBuf}, + sync::Arc, }; use tempdir::TempDir; @@ -141,3 +143,12 @@ fn write_tree(path: &Path, tree: serde_json::Value) { panic!("You must pass a JSON object to this helper") } } + +pub fn build_app_state(ctx: &AppContext) -> AppState { + let settings = settings::channel(&ctx.font_cache()).unwrap().1; + let language_registry = Arc::new(LanguageRegistry::new()); + AppState { + settings, + language_registry, + } +} diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 328ebf30e8413f637f7f486f52dca51e0ca5191a..bb77ca11817ad2d961586806eff11b14b85a8939 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -2,9 +2,11 @@ pub mod pane; pub mod pane_group; use crate::{ editor::{Buffer, BufferView}, + language::LanguageRegistry, settings::Settings, time::ReplicaId, worktree::{FileHandle, Worktree, WorktreeHandle}, + AppState, }; use futures_core::Future; use gpui::{ @@ -40,11 +42,11 @@ pub fn init(app: &mut MutableAppContext) { pub struct OpenParams { pub paths: Vec, - pub settings: watch::Receiver, + pub app_state: AppState, } -fn open(settings: &watch::Receiver, ctx: &mut MutableAppContext) { - let settings = settings.clone(); +fn open(app_state: &AppState, ctx: &mut MutableAppContext) { + let app_state = app_state.clone(); ctx.prompt_for_paths( PathPromptOptions { files: true, @@ -53,7 +55,7 @@ fn open(settings: &watch::Receiver, ctx: &mut MutableAppContext) { }, move |paths, ctx| { if let Some(paths) = paths { - ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings }); + ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, app_state }); } }, ); @@ -84,7 +86,12 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { // Add a new workspace if necessary app.add_window(|ctx| { - let mut view = Workspace::new(0, params.settings.clone(), ctx); + let mut view = Workspace::new( + 0, + params.app_state.settings.clone(), + params.app_state.language_registry.clone(), + ctx, + ); let open_paths = view.open_paths(¶ms.paths, ctx); ctx.foreground().spawn(open_paths).detach(); view @@ -284,6 +291,7 @@ pub struct State { pub struct Workspace { pub settings: watch::Receiver, + language_registry: Arc, modal: Option, center: PaneGroup, panes: Vec>, @@ -301,6 +309,7 @@ impl Workspace { pub fn new( replica_id: ReplicaId, settings: watch::Receiver, + language_registry: Arc, ctx: &mut ViewContext, ) -> Self { let pane = ctx.add_view(|_| Pane::new(settings.clone())); @@ -316,6 +325,7 @@ impl Workspace { panes: vec![pane.clone()], active_pane: pane.clone(), settings, + language_registry, replica_id, worktrees: Default::default(), items: Default::default(), @@ -503,6 +513,7 @@ impl Workspace { let (mut tx, rx) = postage::watch::channel(); entry.insert(rx); let replica_id = self.replica_id; + let language_registry = self.language_registry.clone(); ctx.as_mut() .spawn(|mut ctx| async move { @@ -512,7 +523,14 @@ impl Workspace { *tx.borrow_mut() = Some(match history { Ok(history) => Ok(Box::new(ctx.add_model(|ctx| { - Buffer::from_history(replica_id, history, Some(file), ctx) + let language = language_registry.select_language(path); + Buffer::from_history( + replica_id, + history, + Some(file), + language.cloned(), + ctx, + ) }))), Err(error) => Err(Arc::new(error)), }) @@ -757,14 +775,17 @@ impl WorkspaceHandle for ViewHandle { #[cfg(test)] mod tests { use super::*; - use crate::{editor::BufferView, settings, test::temp_tree}; + use crate::{ + editor::BufferView, + test::{build_app_state, temp_tree}, + }; use serde_json::json; use std::{collections::HashSet, fs}; use tempdir::TempDir; #[gpui::test] fn test_open_paths_action(app: &mut gpui::MutableAppContext) { - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = build_app_state(app.as_ref()); init(app); @@ -790,7 +811,7 @@ mod tests { dir.path().join("a").to_path_buf(), dir.path().join("b").to_path_buf(), ], - settings: settings.clone(), + app_state: app_state.clone(), }, ); assert_eq!(app.window_ids().count(), 1); @@ -799,7 +820,7 @@ mod tests { "workspace:open_paths", OpenParams { paths: vec![dir.path().join("a").to_path_buf()], - settings: settings.clone(), + app_state: app_state.clone(), }, ); assert_eq!(app.window_ids().count(), 1); @@ -815,7 +836,7 @@ mod tests { dir.path().join("b").to_path_buf(), dir.path().join("c").to_path_buf(), ], - settings: settings.clone(), + app_state: app_state.clone(), }, ); assert_eq!(app.window_ids().count(), 2); @@ -831,10 +852,11 @@ mod tests { }, })); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (_, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings, ctx); + let mut workspace = + Workspace::new(0, app_state.settings, app_state.language_registry, ctx); workspace.add_worktree(dir.path(), ctx); workspace }); @@ -935,9 +957,10 @@ mod tests { "b.txt": "", })); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (_, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings, ctx); + let mut workspace = + Workspace::new(0, app_state.settings, app_state.language_registry, ctx); workspace.add_worktree(dir1.path(), ctx); workspace }); @@ -1003,9 +1026,10 @@ mod tests { "a.txt": "", })); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (window_id, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings, ctx); + let mut workspace = + Workspace::new(0, app_state.settings, app_state.language_registry, ctx); workspace.add_worktree(dir.path(), ctx); workspace }); @@ -1046,9 +1070,10 @@ mod tests { #[gpui::test] async fn test_open_and_save_new_file(mut app: gpui::TestAppContext) { let dir = TempDir::new("test-new-file").unwrap(); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (_, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings, ctx); + let mut workspace = + Workspace::new(0, app_state.settings, app_state.language_registry, ctx); workspace.add_worktree(dir.path(), ctx); workspace }); @@ -1150,9 +1175,10 @@ mod tests { }, })); - let settings = settings::channel(&app.font_cache()).unwrap().1; + let app_state = app.read(build_app_state); let (window_id, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings, ctx); + let mut workspace = + Workspace::new(0, app_state.settings, app_state.language_registry, ctx); workspace.add_worktree(dir.path(), ctx); workspace }); From 4e74a8726c695d918d22d696b494eee4f590b136 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 May 2021 16:22:00 -0700 Subject: [PATCH 02/56] Bump context-predicate grammar to the latest Tree-sitter --- Cargo.lock | 20 +-- gpui/Cargo.toml | 2 +- gpui/grammars/context-predicate/Cargo.toml | 26 +++ gpui/grammars/context-predicate/binding.gyp | 2 +- .../{src => bindings/node}/binding.cc | 0 .../context-predicate/bindings/node/index.js | 19 +++ .../context-predicate/bindings/rust/build.rs | 40 +++++ .../context-predicate/bindings/rust/lib.rs | 52 ++++++ gpui/grammars/context-predicate/index.js | 13 -- .../context-predicate/package-lock.json | 14 +- gpui/grammars/context-predicate/package.json | 4 +- gpui/grammars/context-predicate/src/parser.c | 56 +++--- .../src/tree_sitter/parser.h | 159 ++++++++---------- zed/Cargo.toml | 2 +- 14 files changed, 255 insertions(+), 154 deletions(-) create mode 100644 gpui/grammars/context-predicate/Cargo.toml rename gpui/grammars/context-predicate/{src => bindings/node}/binding.cc (100%) create mode 100644 gpui/grammars/context-predicate/bindings/node/index.js create mode 100644 gpui/grammars/context-predicate/bindings/rust/build.rs create mode 100644 gpui/grammars/context-predicate/bindings/rust/lib.rs delete mode 100644 gpui/grammars/context-predicate/index.js diff --git a/Cargo.lock b/Cargo.lock index 4562f2708c0d46750cc3259e172f326c214a88e1..1aabe1333d626121e4a5a2c95e2979a53c96befc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,7 +1202,7 @@ dependencies = [ "smallvec", "smol", "tiny-skia", - "tree-sitter 0.17.1", + "tree-sitter", "usvg", ] @@ -2704,19 +2704,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.17.1" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18dcb776d3affaba6db04d11d645946d34a69b3172e588af96ce9fecd20faac" -dependencies = [ - "cc", - "regex", -] - -[[package]] -name = "tree-sitter" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f41201fed3db3b520405a9c01c61773a250d4c3f43e9861c14b2bb232c981ab" +checksum = "ad726ec26496bf4c083fff0f43d4eb3a2ad1bba305323af5ff91383c0b6ecac0" dependencies = [ "cc", "regex", @@ -2729,7 +2719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784f7ef9cdbd4c895dc2d4bb785e95b4a5364a602eec803681db83d1927ddf15" dependencies = [ "cc", - "tree-sitter 0.19.3", + "tree-sitter", ] [[package]] @@ -3007,7 +2997,7 @@ dependencies = [ "smallvec", "smol", "tempdir", - "tree-sitter 0.19.3", + "tree-sitter", "tree-sitter-rust", "unindent", ] diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 1eba8612bfa9c2474ca5a0e389bf0824cba6479f..7f436094c9e8ae892b0cf3cf99d4ccb9c6993b16 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -26,7 +26,7 @@ serde_json = "1.0.64" smallvec = {version = "1.6", features = ["union"]} smol = "1.2" tiny-skia = "0.5" -tree-sitter = "0.17" +tree-sitter = "0.19" usvg = "0.14" [build-dependencies] diff --git a/gpui/grammars/context-predicate/Cargo.toml b/gpui/grammars/context-predicate/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..84d18b218013a1f57102c8c7b1ef3e15203fdf0c --- /dev/null +++ b/gpui/grammars/context-predicate/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "tree-sitter-context-predicate" +description = "context-predicate grammar for the tree-sitter parsing library" +version = "0.0.1" +keywords = ["incremental", "parsing", "context-predicate"] +categories = ["parsing", "text-editors"] +repository = "https://github.com/tree-sitter/tree-sitter-javascript" +edition = "2018" +license = "MIT" + +build = "bindings/rust/build.rs" +include = [ + "bindings/rust/*", + "grammar.js", + "queries/*", + "src/*", +] + +[lib] +path = "bindings/rust/lib.rs" + +[dependencies] +tree-sitter = "0.19.3" + +[build-dependencies] +cc = "1.0" diff --git a/gpui/grammars/context-predicate/binding.gyp b/gpui/grammars/context-predicate/binding.gyp index 456116d62b63230de03a8ff80d3dcda00d68cc6e..16f3d1af27b9c9c711e3301e7f048eb33abe3aa8 100644 --- a/gpui/grammars/context-predicate/binding.gyp +++ b/gpui/grammars/context-predicate/binding.gyp @@ -8,7 +8,7 @@ ], "sources": [ "src/parser.c", - "src/binding.cc" + "bindings/node/binding.cc" ], "cflags_c": [ "-std=c99", diff --git a/gpui/grammars/context-predicate/src/binding.cc b/gpui/grammars/context-predicate/bindings/node/binding.cc similarity index 100% rename from gpui/grammars/context-predicate/src/binding.cc rename to gpui/grammars/context-predicate/bindings/node/binding.cc diff --git a/gpui/grammars/context-predicate/bindings/node/index.js b/gpui/grammars/context-predicate/bindings/node/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3bad018a56ca5528443aac5afa9f67e0637f2ab1 --- /dev/null +++ b/gpui/grammars/context-predicate/bindings/node/index.js @@ -0,0 +1,19 @@ +try { + module.exports = require("../../build/Release/tree_sitter_context_predicate_binding"); +} catch (error1) { + if (error1.code !== 'MODULE_NOT_FOUND') { + throw error1; + } + try { + module.exports = require("../../build/Debug/tree_sitter_context_predicate_binding"); + } catch (error2) { + if (error2.code !== 'MODULE_NOT_FOUND') { + throw error2; + } + throw error1 + } +} + +try { + module.exports.nodeTypeInfo = require("../../src/node-types.json"); +} catch (_) {} diff --git a/gpui/grammars/context-predicate/bindings/rust/build.rs b/gpui/grammars/context-predicate/bindings/rust/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..c6061f0995320f044faeac56bcac458a09747f1d --- /dev/null +++ b/gpui/grammars/context-predicate/bindings/rust/build.rs @@ -0,0 +1,40 @@ +fn main() { + let src_dir = std::path::Path::new("src"); + + let mut c_config = cc::Build::new(); + c_config.include(&src_dir); + c_config + .flag_if_supported("-Wno-unused-parameter") + .flag_if_supported("-Wno-unused-but-set-variable") + .flag_if_supported("-Wno-trigraphs"); + let parser_path = src_dir.join("parser.c"); + c_config.file(&parser_path); + + // If your language uses an external scanner written in C, + // then include this block of code: + + /* + let scanner_path = src_dir.join("scanner.c"); + c_config.file(&scanner_path); + println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); + */ + + c_config.compile("parser"); + println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); + + // If your language uses an external scanner written in C++, + // then include this block of code: + + /* + let mut cpp_config = cc::Build::new(); + cpp_config.cpp(true); + cpp_config.include(&src_dir); + cpp_config + .flag_if_supported("-Wno-unused-parameter") + .flag_if_supported("-Wno-unused-but-set-variable"); + let scanner_path = src_dir.join("scanner.cc"); + cpp_config.file(&scanner_path); + cpp_config.compile("scanner"); + println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); + */ +} diff --git a/gpui/grammars/context-predicate/bindings/rust/lib.rs b/gpui/grammars/context-predicate/bindings/rust/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..41962c960d333c12c2bbccedbf07fc13215a10b5 --- /dev/null +++ b/gpui/grammars/context-predicate/bindings/rust/lib.rs @@ -0,0 +1,52 @@ +//! This crate provides context_predicate language support for the [tree-sitter][] parsing library. +//! +//! Typically, you will use the [language][language func] function to add this language to a +//! tree-sitter [Parser][], and then use the parser to parse some code: +//! +//! ``` +//! let code = ""; +//! let mut parser = tree_sitter::Parser::new(); +//! parser.set_language(tree_sitter_context_predicate::language()).expect("Error loading context_predicate grammar"); +//! let tree = parser.parse(code, None).unwrap(); +//! ``` +//! +//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html +//! [language func]: fn.language.html +//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html +//! [tree-sitter]: https://tree-sitter.github.io/ + +use tree_sitter::Language; + +extern "C" { + fn tree_sitter_context_predicate() -> Language; +} + +/// Get the tree-sitter [Language][] for this grammar. +/// +/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html +pub fn language() -> Language { + unsafe { tree_sitter_context_predicate() } +} + +/// The content of the [`node-types.json`][] file for this grammar. +/// +/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types +pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json"); + +// Uncomment these to include any queries that this grammar contains + +// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm"); +// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm"); +// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm"); +// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm"); + +#[cfg(test)] +mod tests { + #[test] + fn test_can_load_grammar() { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(super::language()) + .expect("Error loading context_predicate language"); + } +} diff --git a/gpui/grammars/context-predicate/index.js b/gpui/grammars/context-predicate/index.js deleted file mode 100644 index 1be86370cd475119ac63c379f5bb7015d66c98a6..0000000000000000000000000000000000000000 --- a/gpui/grammars/context-predicate/index.js +++ /dev/null @@ -1,13 +0,0 @@ -try { - module.exports = require("./build/Release/tree_sitter_context_predicate_binding"); -} catch (error) { - try { - module.exports = require("./build/Debug/tree_sitter_context_predicate_binding"); - } catch (_) { - throw error - } -} - -try { - module.exports.nodeTypeInfo = require("./src/node-types.json"); -} catch (_) { } diff --git a/gpui/grammars/context-predicate/package-lock.json b/gpui/grammars/context-predicate/package-lock.json index d068dc0228b0c0606ab40f1a384262281b4e6445..1da584a856dad08115cb6790c828956fc9e76169 100644 --- a/gpui/grammars/context-predicate/package-lock.json +++ b/gpui/grammars/context-predicate/package-lock.json @@ -9,7 +9,7 @@ "nan": "^2.14.0" }, "devDependencies": { - "tree-sitter-cli": "^0.18.3" + "tree-sitter-cli": "^0.19.5" } }, "node_modules/nan": { @@ -18,9 +18,9 @@ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" }, "node_modules/tree-sitter-cli": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.18.3.tgz", - "integrity": "sha512-ntN8Siljy7dlazb4cSYtZCfibaNppIy6RIr/XGt44GW1hAy/yuTLtKVi4kqYlImB4/7H0AjktcSlQRmI8zLNng==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", + "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", "dev": true, "hasInstallScript": true, "bin": { @@ -35,9 +35,9 @@ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" }, "tree-sitter-cli": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.18.3.tgz", - "integrity": "sha512-ntN8Siljy7dlazb4cSYtZCfibaNppIy6RIr/XGt44GW1hAy/yuTLtKVi4kqYlImB4/7H0AjktcSlQRmI8zLNng==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", + "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", "dev": true } } diff --git a/gpui/grammars/context-predicate/package.json b/gpui/grammars/context-predicate/package.json index a14e9d0145b7073eabeee5d83bacba2ce9a2563b..298e34a6b56d76ef55b4944f85226cc66c441cf5 100644 --- a/gpui/grammars/context-predicate/package.json +++ b/gpui/grammars/context-predicate/package.json @@ -1,8 +1,8 @@ { "name": "tree-sitter-context-predicate", - "main": "index.js", + "main": "bindings/node", "devDependencies": { - "tree-sitter-cli": "^0.18.3" + "tree-sitter-cli": "^0.19.5" }, "dependencies": { "nan": "^2.14.0" diff --git a/gpui/grammars/context-predicate/src/parser.c b/gpui/grammars/context-predicate/src/parser.c index 4bab2ea4dd37070317b1d9b638f5b196558a67be..e2af5e03caa032537a74a10e68de4536efdd8e59 100644 --- a/gpui/grammars/context-predicate/src/parser.c +++ b/gpui/grammars/context-predicate/src/parser.c @@ -5,7 +5,7 @@ #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif -#define LANGUAGE_VERSION 12 +#define LANGUAGE_VERSION 13 #define STATE_COUNT 18 #define LARGE_STATE_COUNT 6 #define SYMBOL_COUNT 17 @@ -14,6 +14,7 @@ #define EXTERNAL_TOKEN_COUNT 0 #define FIELD_COUNT 3 #define MAX_ALIAS_SEQUENCE_LENGTH 3 +#define PRODUCTION_ID_COUNT 3 enum { sym_identifier = 1, @@ -34,7 +35,7 @@ enum { sym_parenthesized = 16, }; -static const char *ts_symbol_names[] = { +static const char * const ts_symbol_names[] = { [ts_builtin_sym_end] = "end", [sym_identifier] = "identifier", [anon_sym_BANG] = "!", @@ -54,7 +55,7 @@ static const char *ts_symbol_names[] = { [sym_parenthesized] = "parenthesized", }; -static TSSymbol ts_symbol_map[] = { +static const TSSymbol ts_symbol_map[] = { [ts_builtin_sym_end] = ts_builtin_sym_end, [sym_identifier] = sym_identifier, [anon_sym_BANG] = anon_sym_BANG, @@ -151,14 +152,14 @@ enum { field_right = 3, }; -static const char *ts_field_names[] = { +static const char * const ts_field_names[] = { [0] = NULL, [field_expression] = "expression", [field_left] = "left", [field_right] = "right", }; -static const TSFieldMapSlice ts_field_map_slices[3] = { +static const TSFieldMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = { [1] = {.index = 0, .length = 1}, [2] = {.index = 1, .length = 2}, }; @@ -171,11 +172,11 @@ static const TSFieldMapEntry ts_field_map_entries[] = { {field_right, 2}, }; -static TSSymbol ts_alias_sequences[3][MAX_ALIAS_SEQUENCE_LENGTH] = { +static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = { [0] = {0}, }; -static uint16_t ts_non_terminal_alias_map[] = { +static const uint16_t ts_non_terminal_alias_map[] = { 0, }; @@ -279,7 +280,7 @@ static bool ts_lex(TSLexer *lexer, TSStateId state) { } } -static TSLexMode ts_lex_modes[STATE_COUNT] = { +static const TSLexMode ts_lex_modes[STATE_COUNT] = { [0] = {.lex_state = 0}, [1] = {.lex_state = 1}, [2] = {.lex_state = 1}, @@ -300,7 +301,7 @@ static TSLexMode ts_lex_modes[STATE_COUNT] = { [17] = {.lex_state = 0}, }; -static uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { +static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [0] = { [ts_builtin_sym_end] = ACTIONS(1), [sym_identifier] = ACTIONS(1), @@ -375,7 +376,7 @@ static uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { }, }; -static uint16_t ts_small_parse_table[] = { +static const uint16_t ts_small_parse_table[] = { [0] = 3, ACTIONS(11), 1, anon_sym_EQ_EQ, @@ -448,7 +449,7 @@ static uint16_t ts_small_parse_table[] = { sym_identifier, }; -static uint32_t ts_small_parse_table_map[] = { +static const uint32_t ts_small_parse_table_map[] = { [SMALL_STATE(6)] = 0, [SMALL_STATE(7)] = 13, [SMALL_STATE(8)] = 20, @@ -463,7 +464,7 @@ static uint32_t ts_small_parse_table_map[] = { [SMALL_STATE(17)] = 85, }; -static TSParseActionEntry ts_parse_actions[] = { +static const TSParseActionEntry ts_parse_actions[] = { [0] = {.entry = {.count = 0, .reusable = false}}, [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6), @@ -495,30 +496,31 @@ extern "C" { #endif extern const TSLanguage *tree_sitter_context_predicate(void) { - static TSLanguage language = { + static const TSLanguage language = { .version = LANGUAGE_VERSION, .symbol_count = SYMBOL_COUNT, .alias_count = ALIAS_COUNT, .token_count = TOKEN_COUNT, .external_token_count = EXTERNAL_TOKEN_COUNT, - .symbol_names = ts_symbol_names, - .symbol_metadata = ts_symbol_metadata, - .parse_table = (const uint16_t *)ts_parse_table, - .parse_actions = ts_parse_actions, - .lex_modes = ts_lex_modes, - .alias_sequences = (const TSSymbol *)ts_alias_sequences, - .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, - .lex_fn = ts_lex, + .state_count = STATE_COUNT, + .large_state_count = LARGE_STATE_COUNT, + .production_id_count = PRODUCTION_ID_COUNT, .field_count = FIELD_COUNT, - .field_map_slices = (const TSFieldMapSlice *)ts_field_map_slices, - .field_map_entries = (const TSFieldMapEntry *)ts_field_map_entries, + .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, + .parse_table = &ts_parse_table[0][0], + .small_parse_table = ts_small_parse_table, + .small_parse_table_map = ts_small_parse_table_map, + .parse_actions = ts_parse_actions, + .symbol_names = ts_symbol_names, .field_names = ts_field_names, - .large_state_count = LARGE_STATE_COUNT, - .small_parse_table = (const uint16_t *)ts_small_parse_table, - .small_parse_table_map = (const uint32_t *)ts_small_parse_table_map, + .field_map_slices = ts_field_map_slices, + .field_map_entries = ts_field_map_entries, + .symbol_metadata = ts_symbol_metadata, .public_symbol_map = ts_symbol_map, .alias_map = ts_non_terminal_alias_map, - .state_count = STATE_COUNT, + .alias_sequences = &ts_alias_sequences[0][0], + .lex_modes = ts_lex_modes, + .lex_fn = ts_lex, }; return &language; } diff --git a/gpui/grammars/context-predicate/src/tree_sitter/parser.h b/gpui/grammars/context-predicate/src/tree_sitter/parser.h index c5a788ff647dc103175b53a296f6f54232343204..cbbc7b4ee3c5d0d594d304c8f1c6b44377b3793e 100644 --- a/gpui/grammars/context-predicate/src/tree_sitter/parser.h +++ b/gpui/grammars/context-predicate/src/tree_sitter/parser.h @@ -13,6 +13,8 @@ extern "C" { #define ts_builtin_sym_end 0 #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 +typedef uint16_t TSStateId; + #ifndef TREE_SITTER_API_H_ typedef uint16_t TSSymbol; typedef uint16_t TSFieldId; @@ -30,12 +32,10 @@ typedef struct { uint16_t length; } TSFieldMapSlice; -typedef uint16_t TSStateId; - typedef struct { - bool visible : 1; - bool named : 1; - bool supertype: 1; + bool visible; + bool named; + bool supertype; } TSSymbolMetadata; typedef struct TSLexer TSLexer; @@ -57,21 +57,21 @@ typedef enum { TSParseActionTypeRecover, } TSParseActionType; -typedef struct { - union { - struct { - TSStateId state; - bool extra : 1; - bool repetition : 1; - } shift; - struct { - TSSymbol symbol; - int16_t dynamic_precedence; - uint8_t child_count; - uint8_t production_id; - } reduce; - } params; - TSParseActionType type : 4; +typedef union { + struct { + uint8_t type; + TSStateId state; + bool extra; + bool repetition; + } shift; + struct { + uint8_t type; + uint8_t child_count; + TSSymbol symbol; + int16_t dynamic_precedence; + uint16_t production_id; + } reduce; + uint8_t type; } TSParseAction; typedef struct { @@ -83,7 +83,7 @@ typedef union { TSParseAction action; struct { uint8_t count; - bool reusable : 1; + bool reusable; } entry; } TSParseActionEntry; @@ -93,13 +93,24 @@ struct TSLanguage { uint32_t alias_count; uint32_t token_count; uint32_t external_token_count; - const char **symbol_names; - const TSSymbolMetadata *symbol_metadata; + uint32_t state_count; + uint32_t large_state_count; + uint32_t production_id_count; + uint32_t field_count; + uint16_t max_alias_sequence_length; const uint16_t *parse_table; + const uint16_t *small_parse_table; + const uint32_t *small_parse_table_map; const TSParseActionEntry *parse_actions; - const TSLexMode *lex_modes; + const char * const *symbol_names; + const char * const *field_names; + const TSFieldMapSlice *field_map_slices; + const TSFieldMapEntry *field_map_entries; + const TSSymbolMetadata *symbol_metadata; + const TSSymbol *public_symbol_map; + const uint16_t *alias_map; const TSSymbol *alias_sequences; - uint16_t max_alias_sequence_length; + const TSLexMode *lex_modes; bool (*lex_fn)(TSLexer *, TSStateId); bool (*keyword_lex_fn)(TSLexer *, TSStateId); TSSymbol keyword_capture_token; @@ -112,16 +123,6 @@ struct TSLanguage { unsigned (*serialize)(void *, char *); void (*deserialize)(void *, const char *, unsigned); } external_scanner; - uint32_t field_count; - const TSFieldMapSlice *field_map_slices; - const TSFieldMapEntry *field_map_entries; - const char **field_names; - uint32_t large_state_count; - const uint16_t *small_parse_table; - const uint32_t *small_parse_table_map; - const TSSymbol *public_symbol_map; - const uint16_t *alias_map; - uint32_t state_count; }; /* @@ -170,66 +171,50 @@ struct TSLanguage { #define ACTIONS(id) id -#define SHIFT(state_value) \ - { \ - { \ - .params = { \ - .shift = { \ - .state = state_value \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } +#define SHIFT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = state_value \ + } \ + }} #define SHIFT_REPEAT(state_value) \ - { \ - { \ - .params = { \ - .shift = { \ - .state = state_value, \ - .repetition = true \ - } \ - }, \ - .type = TSParseActionTypeShift \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = state_value, \ + .repetition = true \ } \ - } - -#define RECOVER() \ - { \ - { .type = TSParseActionTypeRecover } \ - } + }} #define SHIFT_EXTRA() \ - { \ - { \ - .params = { \ - .shift = { \ - .extra = true \ - } \ - }, \ - .type = TSParseActionTypeShift \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .extra = true \ } \ - } + }} #define REDUCE(symbol_val, child_count_val, ...) \ - { \ - { \ - .params = { \ - .reduce = { \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - }, \ - }, \ - .type = TSParseActionTypeReduce \ - } \ - } - -#define ACCEPT_INPUT() \ - { \ - { .type = TSParseActionTypeAccept } \ - } + {{ \ + .reduce = { \ + .type = TSParseActionTypeReduce, \ + .symbol = symbol_val, \ + .child_count = child_count_val, \ + __VA_ARGS__ \ + }, \ + }} + +#define RECOVER() \ + {{ \ + .type = TSParseActionTypeRecover \ + }} + +#define ACCEPT_INPUT() \ + {{ \ + .type = TSParseActionTypeAccept \ + }} #ifdef __cplusplus } diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 91477e2831ad0b57aca08d372ba2817c658fcf12..6779e302ce25fbb9e573c919aa37bddec6ade679 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -38,7 +38,7 @@ similar = "1.3" simplelog = "0.9" smallvec = {version = "1.6", features = ["union"]} smol = "1.2.5" -tree-sitter = "0.19.3" +tree-sitter = "0.19.5" tree-sitter-rust = "0.19.0" [dev-dependencies] From 46e6b2cff47e6aef786ccfe43c850069ea2d797e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 May 2021 16:24:29 -0700 Subject: [PATCH 03/56] Start maintaining a syntax tree on buffers --- zed/src/editor/buffer/mod.rs | 152 +++++++++++++++++++++++-- zed/src/editor/buffer/point.rs | 18 +++ zed/src/editor/buffer/rope.rs | 6 +- zed/src/editor/buffer_view.rs | 1 + zed/src/editor/display_map/fold_map.rs | 2 +- zed/src/language.rs | 6 +- 6 files changed, 170 insertions(+), 15 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 9fdf4e0c3620ee20993d0db1fb5f2ac562992043..a6461c07b776bdf69ca71a10d94dd4ae1587a25c 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -9,19 +9,21 @@ pub use rope::{ChunksIter, Rope, TextSummary}; use seahash::SeaHasher; pub use selection::*; use similar::{ChangeTag, TextDiff}; +use tree_sitter::{InputEdit, Parser}; use crate::{ editor::Bias, - language::Language, + language::{Language, Tree}, operation_queue::{self, OperationQueue}, sum_tree::{self, FilterCursor, SeekBias, SumTree}, - time::{self, ReplicaId}, + time::{self, Global, ReplicaId}, worktree::FileHandle, }; use anyhow::{anyhow, Result}; use gpui::{AppContext, Entity, ModelContext, Task}; use lazy_static::lazy_static; use std::{ + cell::RefCell, cmp, hash::BuildHasher, iter::{self, Iterator}, @@ -57,6 +59,10 @@ type HashMap = std::collections::HashMap; #[cfg(not(test))] type HashSet = std::collections::HashSet; +thread_local! { + pub static PARSER: RefCell = RefCell::new(Parser::new()); +} + pub struct Buffer { fragments: SumTree, visible_text: Rope, @@ -70,6 +76,8 @@ pub struct Buffer { history: History, file: Option, language: Option>, + tree: Option, + is_parsing: bool, selections: HashMap>, pub selections_last_update: SelectionsVersion, deferred_ops: OperationQueue, @@ -465,7 +473,7 @@ impl Buffer { ); } - Self { + let mut result = Self { visible_text, deleted_text: Rope::new(), fragments, @@ -476,6 +484,8 @@ impl Buffer { undo_map: Default::default(), history, file, + tree: None, + is_parsing: false, language, saved_mtime, selections: HashMap::default(), @@ -485,7 +495,9 @@ impl Buffer { replica_id, local_clock: time::Local::new(replica_id), lamport_clock: time::Lamport::new(replica_id), - } + }; + result.reparse(ctx); + result } pub fn snapshot(&self) -> Rope { @@ -534,6 +546,109 @@ impl Buffer { ctx.emit(Event::Saved); } + fn reparse(&mut self, ctx: &mut ModelContext) { + // Avoid spawning a new parsing task if the buffer is already being reparsed + // due to an earlier edit. + if self.is_parsing { + return; + } + + if let Some(language) = self.language.clone() { + self.is_parsing = true; + let mut old_text = self.visible_text.clone(); + let mut old_tree = self.tree.clone(); + let mut old_version = self.version(); + let mut had_changes = true; + ctx.spawn(|handle, mut ctx| async move { + while had_changes { + // Parse the current text in a background thread. + let (mut tree, text) = ctx + .background_executor() + .spawn({ + let language = language.clone(); + async move { + let tree = Self::parse_text(&old_text, old_tree, &language); + (tree, old_text) + } + }) + .await; + + // When the parsing completes, check if any new changes have occurred since + // this parse began. If so, edit the new tree to reflect these new changes. + let (has_changes, new_text, new_tree, new_version) = + handle.update(&mut ctx, move |this, ctx| { + let mut delta = 0_isize; + let mut has_further_changes = false; + for Edit { + old_range, + new_range, + } in this.edits_since(old_version) + { + let old_len = old_range.end - old_range.start; + let new_len = new_range.end - new_range.start; + let old_start = (old_range.start as isize + delta) as usize; + tree.edit(&InputEdit { + start_byte: old_start, + old_end_byte: old_start + old_len, + new_end_byte: old_start + new_len, + start_position: text.to_point(old_start).into(), + old_end_position: text.to_point(old_start + old_len).into(), + new_end_position: this + .point_for_offset(old_start + new_len) + .unwrap() + .into(), + }); + delta += new_len as isize - old_len as isize; + has_further_changes = true; + } + + this.tree = Some(tree); + ctx.emit(Event::Reparsed); + + // If there were new changes, then continue the loop, spawning a new + // parsing task. Otherwise, record the fact that parsing is complete. + if has_further_changes { + ( + true, + this.visible_text.clone(), + this.tree.clone(), + this.version(), + ) + } else { + this.is_parsing = false; + (false, Rope::new(), None, Global::new()) + } + }); + + had_changes = has_changes; + old_text = new_text; + old_tree = new_tree; + old_version = new_version; + } + }) + .detach(); + } + } + + fn parse_text(text: &Rope, old_tree: Option, language: &Language) -> Tree { + PARSER.with(|parser| { + let mut parser = parser.borrow_mut(); + parser + .set_language(language.grammar) + .expect("incompatible grammar"); + let mut chunks = text.chunks_in_range(0..text.len()); + parser + .parse_with( + &mut move |offset, _| { + chunks.seek(offset); + chunks.next().map(str::as_bytes).unwrap_or(&[]) + }, + old_tree.as_ref(), + ) + .unwrap() + }) + } + fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { // TODO: it would be nice to not allocate here. let old_text = self.text(); @@ -725,6 +840,7 @@ impl Buffer { if self.edits_since(since).next().is_some() { self.did_edit(was_dirty, ctx); + self.reparse(ctx); } } } @@ -746,17 +862,32 @@ impl Buffer { self.start_transaction_at(None, Instant::now())?; let new_text = new_text.into(); + let old_ranges = old_ranges + .into_iter() + .map(|range| range.start.to_offset(self)..range.end.to_offset(self)) + .collect::>>(); + + if let Some(tree) = self.tree.as_mut() { + let new_extent = TextSummary::from(new_text.as_str()).lines; + for old_range in old_ranges.iter().rev() { + let start_position = self.visible_text.to_point(old_range.start); + tree.edit(&InputEdit { + start_byte: old_range.start, + old_end_byte: old_range.end, + new_end_byte: old_range.start + new_text.len(), + start_position: start_position.into(), + old_end_position: self.visible_text.to_point(old_range.end).into(), + new_end_position: (start_position + new_extent).into(), + }); + } + } + let new_text = if new_text.len() > 0 { Some(new_text) } else { None }; - let old_ranges = old_ranges - .into_iter() - .map(|range| range.start.to_offset(self)..range.end.to_offset(self)) - .collect::>>(); - let has_new_text = new_text.is_some(); let ops = self.splice_fragments( old_ranges @@ -1890,6 +2021,8 @@ impl Clone for Buffer { deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), language: self.language.clone(), + tree: self.tree.clone(), + is_parsing: false, deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, local_clock: self.local_clock.clone(), @@ -1957,6 +2090,7 @@ pub enum Event { Saved, FileHandleChanged, Reloaded, + Reparsed, } impl Entity for Buffer { diff --git a/zed/src/editor/buffer/point.rs b/zed/src/editor/buffer/point.rs index d4ecc69e0c67ec8942312f355de9f22e32e0d1e8..f1fb81ef105ad4bd8c68a8249713ad1e389b3450 100644 --- a/zed/src/editor/buffer/point.rs +++ b/zed/src/editor/buffer/point.rs @@ -98,3 +98,21 @@ impl Ord for Point { } } } + +impl Into for Point { + fn into(self) -> tree_sitter::Point { + tree_sitter::Point { + row: self.row as usize, + column: self.column as usize, + } + } +} + +impl From for Point { + fn from(point: tree_sitter::Point) -> Self { + Self { + row: point.row as u32, + column: point.column as u32, + } + } +} diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs index 101620f6c269cde2600c2edddb1f2817c1356297..174210c1a9efe8c5c7533bc8234d7753af98b621 100644 --- a/zed/src/editor/buffer/rope.rs +++ b/zed/src/editor/buffer/rope.rs @@ -284,11 +284,13 @@ impl<'a> ChunksIter<'a> { self.range.start.max(*self.chunks.start()) } - pub fn advance_to(&mut self, offset: usize) { + pub fn seek(&mut self, offset: usize) { if offset >= self.chunks.end() { self.chunks.seek_forward(&offset, SeekBias::Right, &()); - self.range.start = offset; + } else { + self.chunks.seek(&offset, SeekBias::Right, &()); } + self.range.start = offset; } pub fn peek(&self) -> Option<&'a str> { diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 5f05defdf63cb112552189833055382b599c172c..4c2469095f1cff79151f941b1cbb472c0d70dc8e 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2246,6 +2246,7 @@ impl BufferView { buffer::Event::Saved => ctx.emit(Event::Saved), buffer::Event::FileHandleChanged => ctx.emit(Event::FileHandleChanged), buffer::Event::Reloaded => ctx.emit(Event::FileHandleChanged), + buffer::Event::Reparsed => {} } } } diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 4cfcd8933704892bf8e831df4d03bceea63b2d70..243fabf0a637ac49ce782bd95434d2f634716e39 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -685,7 +685,7 @@ impl<'a> Iterator for Chunks<'a> { // advance the transform and buffer cursors to the end of the fold. if let Some(display_text) = transform.display_text { self.buffer_offset += transform.summary.buffer.bytes; - self.buffer_chunks.advance_to(self.buffer_offset); + self.buffer_chunks.seek(self.buffer_offset); while self.buffer_offset >= self.transform_cursor.end().buffer.bytes && self.transform_cursor.item().is_some() diff --git a/zed/src/language.rs b/zed/src/language.rs index 666e55c892c2844aa4d9100181b189df117bee77..04bc42a522556751f3e3d86449e61337da1f5e3a 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -9,9 +9,9 @@ pub use tree_sitter::{Parser, Tree}; pub struct LanguageDir; pub struct Language { - name: String, - grammar: Grammar, - highlight_query: Query, + pub name: String, + pub grammar: Grammar, + pub highlight_query: Query, path_suffixes: Vec, } From fe8dc9fba07992e08d053f133d2ded817ad0a74d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 20 May 2021 18:05:19 -0700 Subject: [PATCH 04/56] Add a unit test for reparsing a buffer --- zed/src/editor/buffer/mod.rs | 103 ++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index a6461c07b776bdf69ca71a10d94dd4ae1587a25c..ed3917f404a1c126311cd6d7d9f79f4cda4d2541 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2482,11 +2482,11 @@ impl ToPoint for usize { mod tests { use super::*; use crate::{ - test::temp_tree, + test::{build_app_state, temp_tree}, util::RandomCharIter, worktree::{Worktree, WorktreeHandle}, }; - use gpui::App; + use gpui::{App, ModelHandle}; use rand::prelude::*; use serde_json::json; use std::{ @@ -3337,6 +3337,105 @@ mod tests { } } + #[gpui::test] + async fn test_reparse(mut ctx: gpui::TestAppContext) { + let app_state = ctx.read(build_app_state); + let rust_lang = app_state + .language_registry + .select_language("test.rs") + .cloned(); + assert!(rust_lang.is_some()); + + let buffer = ctx.add_model(|ctx| { + let text = "fn a() {}"; + + let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx); + assert!(buffer.is_parsing); + assert!(buffer.tree.is_none()); + buffer + }); + + // Wait for the initial text to parse + buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + assert_eq!( + get_tree_sexp(&buffer, &ctx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters) ", + "body: (block)))" + ) + ); + + // Perform some edits (add parameter and variable reference) + // Parsing doesn't begin until the transaction is complete + buffer.update(&mut ctx, |buf, ctx| { + buf.start_transaction(None).unwrap(); + + let offset = buf.text().find(")").unwrap(); + buf.edit(vec![offset..offset], "b: C", Some(ctx)).unwrap(); + assert!(!buf.is_parsing); + + let offset = buf.text().find("}").unwrap(); + buf.edit(vec![offset..offset], " d; ", Some(ctx)).unwrap(); + assert!(!buf.is_parsing); + + buf.end_transaction(None, Some(ctx)).unwrap(); + assert_eq!(buf.text(), "fn a(b: C) { d; }"); + assert!(buf.is_parsing); + }); + buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + assert_eq!( + get_tree_sexp(&buffer, &ctx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (identifier))))" + ) + ); + + // Perform a series of edits without waiting for the current parse to complete: + // * turn identifier into a field expression + // * turn field expression into a method call + // * add a turbofish to the method call + buffer.update(&mut ctx, |buf, ctx| { + let offset = buf.text().find(";").unwrap(); + buf.edit(vec![offset..offset], ".e", Some(ctx)).unwrap(); + assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); + assert!(buf.is_parsing); + }); + buffer.update(&mut ctx, |buf, ctx| { + let offset = buf.text().find(";").unwrap(); + buf.edit(vec![offset..offset], "(f)", Some(ctx)).unwrap(); + assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); + assert!(buf.is_parsing); + }); + buffer.update(&mut ctx, |buf, ctx| { + let offset = buf.text().find("(f)").unwrap(); + buf.edit(vec![offset..offset], "::", Some(ctx)).unwrap(); + assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); + assert!(buf.is_parsing); + }); + buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + assert_eq!( + get_tree_sexp(&buffer, &ctx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (call_expression ", + "function: (generic_function ", + "function: (field_expression value: (identifier) field: (field_identifier)) ", + "type_arguments: (type_arguments (type_identifier))) ", + "arguments: (arguments (identifier))))))", + ) + ); + + fn get_tree_sexp(buffer: &ModelHandle, ctx: &gpui::TestAppContext) -> String { + buffer.read_with(ctx, |buffer, _| { + buffer.tree.as_ref().unwrap().root_node().to_sexp() + }) + } + } + impl Buffer { fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl Rng) -> Range { let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); From 9836f1f63829d11929ccd2dafc2bc06bafb71aad Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 May 2021 10:15:45 +0200 Subject: [PATCH 05/56] Reparse buffer on undo, redo and after applying a remote operation --- zed/src/editor/buffer/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index ed3917f404a1c126311cd6d7d9f79f4cda4d2541..233ec2832d02480a91b580b403f437a9ea5db4bc 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -1025,6 +1025,7 @@ impl Buffer { ctx.notify(); if self.edits_since(old_version).next().is_some() { self.did_edit(was_dirty, ctx); + self.reparse(ctx); } } @@ -1250,6 +1251,7 @@ impl Buffer { ctx.notify(); if self.edits_since(old_version).next().is_some() { self.did_edit(was_dirty, ctx); + self.reparse(ctx); } } @@ -1276,6 +1278,7 @@ impl Buffer { ctx.notify(); if self.edits_since(old_version).next().is_some() { self.did_edit(was_dirty, ctx); + self.reparse(ctx); } } From 64b1113455f3b64e8d042b0b8aa649913b351a9d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 May 2021 11:46:59 +0200 Subject: [PATCH 06/56] Restructure parsing to use `edits_since` --- zed/src/editor/buffer/mod.rs | 166 +++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 233ec2832d02480a91b580b403f437a9ea5db4bc..178f1844a45534d632fd07eca3c2b6d9858886bc 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -16,7 +16,7 @@ use crate::{ language::{Language, Tree}, operation_queue::{self, OperationQueue}, sum_tree::{self, FilterCursor, SeekBias, SumTree}, - time::{self, Global, ReplicaId}, + time::{self, ReplicaId}, worktree::FileHandle, }; use anyhow::{anyhow, Result}; @@ -76,7 +76,7 @@ pub struct Buffer { history: History, file: Option, language: Option>, - tree: Option, + tree: Option<(Tree, time::Global, Rope)>, is_parsing: bool, selections: HashMap>, pub selections_last_update: SelectionsVersion, @@ -546,6 +546,12 @@ impl Buffer { ctx.emit(Event::Saved); } + fn should_reparse(&self) -> bool { + self.tree + .as_ref() + .map_or(true, |(_, tree_version, _)| *tree_version != self.version) + } + fn reparse(&mut self, ctx: &mut ModelContext) { // Avoid spawning a new parsing task if the buffer is already being reparsed // due to an earlier edit. @@ -555,76 +561,60 @@ impl Buffer { if let Some(language) = self.language.clone() { self.is_parsing = true; - let mut old_text = self.visible_text.clone(); - let mut old_tree = self.tree.clone(); - let mut old_version = self.version(); - let mut had_changes = true; ctx.spawn(|handle, mut ctx| async move { - while had_changes { - // Parse the current text in a background thread. - let (mut tree, text) = ctx - .background_executor() - .spawn({ - let language = language.clone(); - async move { - let tree = Self::parse_text(&old_text, old_tree, &language); - (tree, old_text) - } - }) - .await; - - // When the parsing completes, check if any new changes have occurred since - // this parse began. If so, edit the new tree to reflect these new changes. - let (has_changes, new_text, new_tree, new_version) = - handle.update(&mut ctx, move |this, ctx| { + while handle.read_with(&ctx, |this, _| this.should_reparse()) { + // The parse tree is out of date, so we clone it and synchronously splice in all + // the edits that have happened since the last parse. + let (new_version, new_text) = handle + .read_with(&ctx, |this, _| (this.version(), this.visible_text.clone())); + let mut new_tree = None; + handle.update(&mut ctx, |this, _| { + if let Some((mut tree, tree_version, old_text)) = this.tree.clone() { let mut delta = 0_isize; - let mut has_further_changes = false; for Edit { old_range, new_range, - } in this.edits_since(old_version) + } in this.edits_since(tree_version) { - let old_len = old_range.end - old_range.start; - let new_len = new_range.end - new_range.start; - let old_start = (old_range.start as isize + delta) as usize; + let start_offset = (old_range.start as isize + delta) as usize; + let start_point = new_text.to_point(start_offset); + let old_bytes = old_range.end - old_range.start; + let new_bytes = new_range.end - new_range.start; + let old_lines = old_text.to_point(old_range.end) + - old_text.to_point(old_range.start); tree.edit(&InputEdit { - start_byte: old_start, - old_end_byte: old_start + old_len, - new_end_byte: old_start + new_len, - start_position: text.to_point(old_start).into(), - old_end_position: text.to_point(old_start + old_len).into(), - new_end_position: this - .point_for_offset(old_start + new_len) - .unwrap() + start_byte: start_offset, + old_end_byte: start_offset + old_bytes, + new_end_byte: start_offset + new_bytes, + start_position: start_point.into(), + old_end_position: (start_point + old_lines).into(), + new_end_position: new_text + .to_point(start_offset + new_bytes) .into(), }); - delta += new_len as isize - old_len as isize; - has_further_changes = true; + delta += new_bytes as isize - old_bytes as isize; } - this.tree = Some(tree); - ctx.emit(Event::Reparsed); - - // If there were new changes, then continue the loop, spawning a new - // parsing task. Otherwise, record the fact that parsing is complete. - if has_further_changes { - ( - true, - this.visible_text.clone(), - this.tree.clone(), - this.version(), - ) - } else { - this.is_parsing = false; - (false, Rope::new(), None, Global::new()) - } - }); + new_tree = Some(tree); + } + }); - had_changes = has_changes; - old_text = new_text; - old_tree = new_tree; - old_version = new_version; + // Parse the current text in a background thread. + let new_tree = ctx + .background_executor() + .spawn({ + let language = language.clone(); + let new_text = new_text.clone(); + async move { Self::parse_text(&new_text, new_tree, &language) } + }) + .await; + + handle.update(&mut ctx, |this, ctx| { + this.tree = Some((new_tree, new_version, new_text)); + ctx.emit(Event::Reparsed); + }); } + handle.update(&mut ctx, |this, _| this.is_parsing = false); }) .detach(); } @@ -637,15 +627,16 @@ impl Buffer { .set_language(language.grammar) .expect("incompatible grammar"); let mut chunks = text.chunks_in_range(0..text.len()); - parser + let tree = parser .parse_with( &mut move |offset, _| { chunks.seek(offset); - chunks.next().map(str::as_bytes).unwrap_or(&[]) + chunks.next().unwrap_or("").as_bytes() }, old_tree.as_ref(), ) - .unwrap() + .unwrap(); + tree }) } @@ -867,21 +858,6 @@ impl Buffer { .map(|range| range.start.to_offset(self)..range.end.to_offset(self)) .collect::>>(); - if let Some(tree) = self.tree.as_mut() { - let new_extent = TextSummary::from(new_text.as_str()).lines; - for old_range in old_ranges.iter().rev() { - let start_position = self.visible_text.to_point(old_range.start); - tree.edit(&InputEdit { - start_byte: old_range.start, - old_end_byte: old_range.end, - new_end_byte: old_range.start + new_text.len(), - start_position: start_position.into(), - old_end_position: self.visible_text.to_point(old_range.end).into(), - new_end_position: (start_position + new_extent).into(), - }); - } - } - let new_text = if new_text.len() > 0 { Some(new_text) } else { @@ -3432,9 +3408,43 @@ mod tests { ) ); + buffer.update(&mut ctx, |buf, ctx| { + buf.undo(Some(ctx)); + assert_eq!(buf.text(), "fn a() {}"); + assert!(buf.is_parsing); + }); + buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + assert_eq!( + get_tree_sexp(&buffer, &ctx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters) ", + "body: (block)))" + ) + ); + + buffer.update(&mut ctx, |buf, ctx| { + buf.redo(Some(ctx)); + assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); + assert!(buf.is_parsing); + }); + buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + assert_eq!( + get_tree_sexp(&buffer, &ctx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (call_expression ", + "function: (generic_function ", + "function: (field_expression value: (identifier) field: (field_identifier)) ", + "type_arguments: (type_arguments (type_identifier))) ", + "arguments: (arguments (identifier))))))", + ) + ); + fn get_tree_sexp(buffer: &ModelHandle, ctx: &gpui::TestAppContext) -> String { buffer.read_with(ctx, |buffer, _| { - buffer.tree.as_ref().unwrap().root_node().to_sexp() + buffer.tree.as_ref().unwrap().0.root_node().to_sexp() }) } } From 3a576f0b654eb1ad19eed01de1fc7cfdc51bebcf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 May 2021 12:25:24 +0200 Subject: [PATCH 07/56] Don't clone `visible_text` for splicing edits into the parse tree --- zed/src/editor/buffer/mod.rs | 28 +++++++++++++++++--------- zed/src/editor/display_map/fold_map.rs | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 178f1844a45534d632fd07eca3c2b6d9858886bc..fd1c263b3061d8585c4bca38ea88f85e2300c556 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -76,7 +76,7 @@ pub struct Buffer { history: History, file: Option, language: Option>, - tree: Option<(Tree, time::Global, Rope)>, + tree: Option<(Tree, time::Global)>, is_parsing: bool, selections: HashMap>, pub selections_last_update: SelectionsVersion, @@ -248,16 +248,18 @@ impl UndoMap { } struct Edits<'a, F: Fn(&FragmentSummary) -> bool> { - cursor: FilterCursor<'a, F, Fragment, usize>, + deleted_text: &'a Rope, + cursor: FilterCursor<'a, F, Fragment, FragmentTextSummary>, undos: &'a UndoMap, since: time::Global, delta: isize, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Edit { pub old_range: Range, pub new_range: Range, + pub old_lines: Point, } impl Edit { @@ -549,7 +551,7 @@ impl Buffer { fn should_reparse(&self) -> bool { self.tree .as_ref() - .map_or(true, |(_, tree_version, _)| *tree_version != self.version) + .map_or(true, |(_, tree_version)| *tree_version != self.version) } fn reparse(&mut self, ctx: &mut ModelContext) { @@ -569,19 +571,18 @@ impl Buffer { .read_with(&ctx, |this, _| (this.version(), this.visible_text.clone())); let mut new_tree = None; handle.update(&mut ctx, |this, _| { - if let Some((mut tree, tree_version, old_text)) = this.tree.clone() { + if let Some((mut tree, tree_version)) = this.tree.clone() { let mut delta = 0_isize; for Edit { old_range, new_range, + old_lines, } in this.edits_since(tree_version) { let start_offset = (old_range.start as isize + delta) as usize; let start_point = new_text.to_point(start_offset); let old_bytes = old_range.end - old_range.start; let new_bytes = new_range.end - new_range.start; - let old_lines = old_text.to_point(old_range.end) - - old_text.to_point(old_range.start); tree.edit(&InputEdit { start_byte: start_offset, old_end_byte: start_offset + old_bytes, @@ -604,13 +605,12 @@ impl Buffer { .background_executor() .spawn({ let language = language.clone(); - let new_text = new_text.clone(); async move { Self::parse_text(&new_text, new_tree, &language) } }) .await; handle.update(&mut ctx, |this, ctx| { - this.tree = Some((new_tree, new_version, new_text)); + this.tree = Some((new_tree, new_version)); ctx.emit(Event::Reparsed); }); } @@ -767,6 +767,7 @@ impl Buffer { .filter(move |summary| summary.max_version.changed_since(&since_2)); Edits { + deleted_text: &self.deleted_text, cursor, undos: &self.undo_map, since, @@ -2083,7 +2084,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { let mut change: Option = None; while let Some(fragment) = self.cursor.item() { - let new_offset = *self.cursor.start(); + let new_offset = self.cursor.start().visible; let old_offset = (new_offset as isize - self.delta) as usize; if !fragment.was_visible(&self.since, &self.undos) && fragment.visible { @@ -2098,13 +2099,18 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { change = Some(Edit { old_range: old_offset..old_offset, new_range: new_offset..new_offset + fragment.len(), + old_lines: Point::zero(), }); self.delta += fragment.len() as isize; } } else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible { + let deleted_start = self.cursor.start().deleted; + let old_lines = self.deleted_text.to_point(deleted_start + fragment.len()) + - self.deleted_text.to_point(deleted_start); if let Some(ref mut change) = change { if change.new_range.end == new_offset { change.old_range.end += fragment.len(); + change.old_lines += &old_lines; self.delta -= fragment.len() as isize; } else { break; @@ -2113,6 +2119,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { change = Some(Edit { old_range: old_offset..old_offset + fragment.len(), new_range: new_offset..new_offset, + old_lines, }); self.delta -= fragment.len() as isize; } @@ -2594,6 +2601,7 @@ mod tests { for Edit { old_range, new_range, + .. } in buffer.edits_since(old_buffer.version.clone()) { let old_len = old_range.end - old_range.start; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 243fabf0a637ac49ce782bd95434d2f634716e39..5615105359cb396dba2940718e661dd34c61d5f8 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -100,6 +100,7 @@ impl FoldMap { edits.push(Edit { old_range: range.clone(), new_range: range.clone(), + ..Default::default() }); } } @@ -144,6 +145,7 @@ impl FoldMap { edits.push(Edit { old_range: offset_range.clone(), new_range: offset_range, + ..Default::default() }); fold_ixs_to_delete.push(*folds_cursor.start()); folds_cursor.next(); From c1b4cca48b0e631449bffc48c86ded3de06be2a1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 May 2021 12:37:45 +0200 Subject: [PATCH 08/56] Delete commented-out diff code --- zed/src/editor/buffer/mod.rs | 65 ------------------------------------ 1 file changed, 65 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index fd1c263b3061d8585c4bca38ea88f85e2300c556..a5994aeffdc2b2af87a10567861a4cc17be2f585 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2132,71 +2132,6 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { } } -// pub fn diff(a: &[u16], b: &[u16]) -> Vec { -// struct EditCollector<'a> { -// a: &'a [u16], -// b: &'a [u16], -// position: Point, -// changes: Vec, -// } -// -// impl<'a> diffs::Diff for EditCollector<'a> { -// type Error = (); -// -// fn equal(&mut self, old: usize, _: usize, len: usize) -> Result<(), ()> { -// self.position += &Text::extent(&self.a[old..old + len]); -// Ok(()) -// } -// -// fn delete(&mut self, old: usize, len: usize) -> Result<(), ()> { -// self.changes.push(Edit { -// range: self.position..self.position + &Text::extent(&self.a[old..old + len]), -// chars: Vec::new(), -// new_char_count: Point::zero(), -// }); -// Ok(()) -// } -// -// fn insert(&mut self, _: usize, new: usize, new_len: usize) -> Result<(), ()> { -// let new_char_count = Text::extent(&self.b[new..new + new_len]); -// self.changes.push(Edit { -// range: self.position..self.position, -// chars: Vec::from(&self.b[new..new + new_len]), -// new_char_count, -// }); -// self.position += &new_char_count; -// Ok(()) -// } -// -// fn replace( -// &mut self, -// old: usize, -// old_len: usize, -// new: usize, -// new_len: usize, -// ) -> Result<(), ()> { -// let old_extent = text::extent(&self.a[old..old + old_len]); -// let new_char_count = text::extent(&self.b[new..new + new_len]); -// self.changes.push(Edit { -// range: self.position..self.position + &old_extent, -// chars: Vec::from(&self.b[new..new + new_len]), -// new_char_count, -// }); -// self.position += &new_char_count; -// Ok(()) -// } -// } -// -// let mut collector = diffs::Replace::new(EditCollector { -// a, -// b, -// position: Point::zero(), -// changes: Vec::new(), -// }); -// diffs::myers::diff(&mut collector, a, 0, a.len(), b, 0, b.len()).unwrap(); -// collector.into_inner().changes -// } - #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] struct FragmentId(Arc<[u16]>); From b0859d42655faa3dd22bd9b49a64cb0c4972a6f6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 May 2021 18:09:48 +0200 Subject: [PATCH 09/56] Extract a `Buffer::syntax_tree` to sync the syntax tree with the buffer Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer/mod.rs | 83 +++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index a5994aeffdc2b2af87a10567861a4cc17be2f585..6c0086a03ae08df303b2d9b535f3c0965d80a951 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -4,6 +4,7 @@ pub mod rope; mod selection; pub use anchor::*; +use parking_lot::Mutex; pub use point::*; pub use rope::{ChunksIter, Rope, TextSummary}; use seahash::SeaHasher; @@ -76,7 +77,7 @@ pub struct Buffer { history: History, file: Option, language: Option>, - tree: Option<(Tree, time::Global)>, + tree: Mutex>, is_parsing: bool, selections: HashMap>, pub selections_last_update: SelectionsVersion, @@ -486,7 +487,7 @@ impl Buffer { undo_map: Default::default(), history, file, - tree: None, + tree: Mutex::new(None), is_parsing: false, language, saved_mtime, @@ -548,8 +549,39 @@ impl Buffer { ctx.emit(Event::Saved); } + pub fn syntax_tree(&self) -> Option { + if let Some((tree, tree_version)) = self.tree.lock().as_mut() { + let mut delta = 0_isize; + for Edit { + old_range, + new_range, + old_lines, + } in self.edits_since(tree_version.clone()) + { + let start_offset = (old_range.start as isize + delta) as usize; + let start_point = self.visible_text.to_point(start_offset); + let old_bytes = old_range.end - old_range.start; + let new_bytes = new_range.end - new_range.start; + tree.edit(&InputEdit { + start_byte: start_offset, + old_end_byte: start_offset + old_bytes, + new_end_byte: start_offset + new_bytes, + start_position: start_point.into(), + old_end_position: (start_point + old_lines).into(), + new_end_position: self.visible_text.to_point(start_offset + new_bytes).into(), + }); + delta += new_bytes as isize - old_bytes as isize; + } + *tree_version = self.version(); + Some(tree.clone()) + } else { + None + } + } + fn should_reparse(&self) -> bool { self.tree + .lock() .as_ref() .map_or(true, |(_, tree_version)| *tree_version != self.version) } @@ -565,40 +597,11 @@ impl Buffer { self.is_parsing = true; ctx.spawn(|handle, mut ctx| async move { while handle.read_with(&ctx, |this, _| this.should_reparse()) { - // The parse tree is out of date, so we clone it and synchronously splice in all - // the edits that have happened since the last parse. - let (new_version, new_text) = handle - .read_with(&ctx, |this, _| (this.version(), this.visible_text.clone())); - let mut new_tree = None; - handle.update(&mut ctx, |this, _| { - if let Some((mut tree, tree_version)) = this.tree.clone() { - let mut delta = 0_isize; - for Edit { - old_range, - new_range, - old_lines, - } in this.edits_since(tree_version) - { - let start_offset = (old_range.start as isize + delta) as usize; - let start_point = new_text.to_point(start_offset); - let old_bytes = old_range.end - old_range.start; - let new_bytes = new_range.end - new_range.start; - tree.edit(&InputEdit { - start_byte: start_offset, - old_end_byte: start_offset + old_bytes, - new_end_byte: start_offset + new_bytes, - start_position: start_point.into(), - old_end_position: (start_point + old_lines).into(), - new_end_position: new_text - .to_point(start_offset + new_bytes) - .into(), - }); - delta += new_bytes as isize - old_bytes as isize; - } - - new_tree = Some(tree); - } - }); + // The parse tree is out of date, so grab the syntax tree to synchronously + // splice all the edits that have happened since the last parse. + let new_tree = handle.update(&mut ctx, |this, _| this.syntax_tree()); + let (new_text, new_version) = handle + .read_with(&ctx, |this, _| (this.visible_text.clone(), this.version())); // Parse the current text in a background thread. let new_tree = ctx @@ -610,7 +613,7 @@ impl Buffer { .await; handle.update(&mut ctx, |this, ctx| { - this.tree = Some((new_tree, new_version)); + *this.tree.lock() = Some((new_tree, new_version)); ctx.emit(Event::Reparsed); }); } @@ -2001,7 +2004,7 @@ impl Clone for Buffer { deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), language: self.language.clone(), - tree: self.tree.clone(), + tree: Mutex::new(self.tree.lock().clone()), is_parsing: false, deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, @@ -3273,7 +3276,7 @@ mod tests { let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx); assert!(buffer.is_parsing); - assert!(buffer.tree.is_none()); + assert!(buffer.syntax_tree().is_none()); buffer }); @@ -3387,7 +3390,7 @@ mod tests { fn get_tree_sexp(buffer: &ModelHandle, ctx: &gpui::TestAppContext) -> String { buffer.read_with(ctx, |buffer, _| { - buffer.tree.as_ref().unwrap().0.root_node().to_sexp() + buffer.syntax_tree().unwrap().root_node().to_sexp() }) } } From b51ae1f66852eb8f45f75f455c15c1dd493da8fd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sun, 23 May 2021 21:42:45 -0700 Subject: [PATCH 10/56] WIP --- Cargo.lock | 3 +- Cargo.toml | 1 + zed/src/editor/buffer/mod.rs | 77 ++++++++++++++++++++++++++++++------ 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1aabe1333d626121e4a5a2c95e2979a53c96befc..503333e6a1850a1d064b4927b94318f32846f2ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2705,8 +2705,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad726ec26496bf4c083fff0f43d4eb3a2ad1bba305323af5ff91383c0b6ecac0" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=8c3d1466ecae2a22a9625d1456ffaae84b13fd3e#8c3d1466ecae2a22a9625d1456ffaae84b13fd3e" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 8109db121bbf0926b9348bec198a2cd009fa9cd2..28ab1b44a0ccea19f62b095f63335a9d4455d508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} +tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "8c3d1466ecae2a22a9625d1456ffaae84b13fd3e"} # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 6c0086a03ae08df303b2d9b535f3c0965d80a951..194f0a4d6f9b4a738c290156df9405410c855ccc 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -4,13 +4,12 @@ pub mod rope; mod selection; pub use anchor::*; -use parking_lot::Mutex; pub use point::*; pub use rope::{ChunksIter, Rope, TextSummary}; use seahash::SeaHasher; pub use selection::*; use similar::{ChangeTag, TextDiff}; -use tree_sitter::{InputEdit, Parser}; +use tree_sitter::{InputEdit, Parser, QueryCursor}; use crate::{ editor::Bias, @@ -77,9 +76,10 @@ pub struct Buffer { history: History, file: Option, language: Option>, - tree: Mutex>, + tree: Option<(Tree, time::Global)>, is_parsing: bool, selections: HashMap>, + cursor: QueryCursor, pub selections_last_update: SelectionsVersion, deferred_ops: OperationQueue, deferred_replicas: HashSet, @@ -487,8 +487,9 @@ impl Buffer { undo_map: Default::default(), history, file, - tree: Mutex::new(None), + tree: None, is_parsing: false, + cursor: QueryCursor::new(), language, saved_mtime, selections: HashMap::default(), @@ -549,8 +550,8 @@ impl Buffer { ctx.emit(Event::Saved); } - pub fn syntax_tree(&self) -> Option { - if let Some((tree, tree_version)) = self.tree.lock().as_mut() { + pub fn syntax_tree(&mut self) -> Option { + if let Some((mut tree, tree_version)) = self.tree.take() { let mut delta = 0_isize; for Edit { old_range, @@ -572,8 +573,9 @@ impl Buffer { }); delta += new_bytes as isize - old_bytes as isize; } - *tree_version = self.version(); - Some(tree.clone()) + let result = tree.clone(); + self.tree = Some((tree, self.version())); + Some(result) } else { None } @@ -581,7 +583,6 @@ impl Buffer { fn should_reparse(&self) -> bool { self.tree - .lock() .as_ref() .map_or(true, |(_, tree_version)| *tree_version != self.version) } @@ -613,7 +614,7 @@ impl Buffer { .await; handle.update(&mut ctx, |this, ctx| { - *this.tree.lock() = Some((new_tree, new_version)); + this.tree = Some((new_tree, new_version)); ctx.emit(Event::Reparsed); }); } @@ -750,6 +751,36 @@ impl Buffer { self.visible_text.chunks_in_range(start..end) } + pub fn highlighted_text_for_range<'a, T: ToOffset>( + &'a mut self, + range: Range, + ) -> impl Iterator { + if let (Some(language), Some((tree, _))) = (&self.language, self.tree.as_ref()) { + let visible_text = &self.visible_text; + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.cursor.set_byte_range(start, end); + let chunks = self.visible_text.chunks_in_range(start..end); + let captures = self.cursor.captures( + &language.highlight_query, + tree.root_node(), + move |node: tree_sitter::Node| { + visible_text + .chunks_in_range(node.byte_range()) + .map(str::as_bytes) + }, + ); + + HighlightedChunksIter { + captures, + chunks, + stack: Default::default(), + } + } else { + todo!() + } + } + pub fn chars(&self) -> impl Iterator + '_ { self.chars_at(0) } @@ -2003,8 +2034,9 @@ impl Clone for Buffer { selections_last_update: self.selections_last_update.clone(), deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), + cursor: tree_sitter::QueryCursor::new(), language: self.language.clone(), - tree: Mutex::new(self.tree.lock().clone()), + tree: self.tree.clone(), is_parsing: false, deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, @@ -2135,6 +2167,25 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { } } +pub struct HighlightedChunksIter<'a, T: tree_sitter::TextProvider<'a>> { + chunks: ChunksIter<'a>, + captures: tree_sitter::QueryCaptures<'a, 'a, T>, + stack: Vec<(usize, usize)>, +} + +impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunksIter<'a, T> { + type Item = (&'a str, usize); + + fn next(&mut self) -> Option { + if let Some((mat, capture_ix)) = self.captures.next() { + let capture = mat.captures[capture_ix as usize]; + let range = capture.node.range(); + } + + todo!() + } +} + #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] struct FragmentId(Arc<[u16]>); @@ -3276,7 +3327,7 @@ mod tests { let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx); assert!(buffer.is_parsing); - assert!(buffer.syntax_tree().is_none()); + assert!(buffer.tree.is_none()); buffer }); @@ -3390,7 +3441,7 @@ mod tests { fn get_tree_sexp(buffer: &ModelHandle, ctx: &gpui::TestAppContext) -> String { buffer.read_with(ctx, |buffer, _| { - buffer.syntax_tree().unwrap().root_node().to_sexp() + buffer.tree.as_ref().unwrap().0.root_node().to_sexp() }) } } From 23b25307d9a98c4b11b784413145eabfb83d171f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 11:23:42 +0200 Subject: [PATCH 11/56] Start on a basic implementation of `HighlightedChunksIter` --- zed/src/editor/buffer/mod.rs | 53 ++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 194f0a4d6f9b4a738c290156df9405410c855ccc..5f54c3d615cff899ef25cd9f3033b7e304c7bddd 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -754,7 +754,7 @@ impl Buffer { pub fn highlighted_text_for_range<'a, T: ToOffset>( &'a mut self, range: Range, - ) -> impl Iterator { + ) -> impl Iterator)> { if let (Some(language), Some((tree, _))) = (&self.language, self.tree.as_ref()) { let visible_text = &self.visible_text; let start = range.start.to_offset(self); @@ -772,9 +772,10 @@ impl Buffer { ); HighlightedChunksIter { - captures, + captures: captures.peekable(), chunks, stack: Default::default(), + offset: start, } } else { todo!() @@ -2169,20 +2170,56 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { pub struct HighlightedChunksIter<'a, T: tree_sitter::TextProvider<'a>> { chunks: ChunksIter<'a>, - captures: tree_sitter::QueryCaptures<'a, 'a, T>, + captures: iter::Peekable>, stack: Vec<(usize, usize)>, + offset: usize, } impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunksIter<'a, T> { - type Item = (&'a str, usize); + type Item = (&'a str, Option); fn next(&mut self) -> Option { - if let Some((mat, capture_ix)) = self.captures.next() { - let capture = mat.captures[capture_ix as usize]; - let range = capture.node.range(); + while let Some((parent_capture_end, _)) = self.stack.last() { + if *parent_capture_end <= self.offset { + self.stack.pop(); + } else { + break; + } + } + + let mut next_capture_start = usize::MAX; + while let Some((mat, capture_ix)) = self.captures.peek() { + let capture = mat.captures[*capture_ix as usize]; + if self.offset < capture.node.start_byte() { + next_capture_start = capture.node.start_byte(); + break; + } else { + self.stack + .push((capture.node.end_byte(), capture.index as usize)); + self.captures.next().unwrap(); + } } - todo!() + if let Some(chunk) = self.chunks.peek() { + let chunk_start = self.offset; + let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); + let mut capture_ix = None; + if let Some((parent_capture_end, parent_capture_ix)) = self.stack.last() { + chunk_end = chunk_end.min(*parent_capture_end); + capture_ix = Some(*parent_capture_ix); + } + + let slice = + &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()]; + self.offset = chunk_end; + if self.offset == self.chunks.offset() + chunk.len() { + self.chunks.next().unwrap(); + } + + Some((slice, capture_ix)) + } else { + None + } } } From 6e791edb1898f844b329c44df517b292480bad34 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 12:43:29 +0200 Subject: [PATCH 12/56] Don't acquire the buffer mutably in `highlighted_text_for_range` --- zed/src/editor/buffer/mod.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 5f54c3d615cff899ef25cd9f3033b7e304c7bddd..93e803173acc9db0c3a26cb444b470855e55502a 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -4,6 +4,7 @@ pub mod rope; mod selection; pub use anchor::*; +use parking_lot::Mutex; pub use point::*; pub use rope::{ChunksIter, Rope, TextSummary}; use seahash::SeaHasher; @@ -77,9 +78,9 @@ pub struct Buffer { file: Option, language: Option>, tree: Option<(Tree, time::Global)>, + query_cursor: Mutex>, is_parsing: bool, selections: HashMap>, - cursor: QueryCursor, pub selections_last_update: SelectionsVersion, deferred_ops: OperationQueue, deferred_replicas: HashSet, @@ -489,8 +490,8 @@ impl Buffer { file, tree: None, is_parsing: false, - cursor: QueryCursor::new(), language, + query_cursor: Mutex::new(Some(QueryCursor::new())), saved_mtime, selections: HashMap::default(), selections_last_update: 0, @@ -752,16 +753,22 @@ impl Buffer { } pub fn highlighted_text_for_range<'a, T: ToOffset>( - &'a mut self, + &'a self, range: Range, ) -> impl Iterator)> { if let (Some(language), Some((tree, _))) = (&self.language, self.tree.as_ref()) { let visible_text = &self.visible_text; + let mut cursor = self + .query_cursor + .lock() + .take() + .unwrap_or_else(|| QueryCursor::new()); let start = range.start.to_offset(self); let end = range.end.to_offset(self); - self.cursor.set_byte_range(start, end); + cursor.set_byte_range(start, end); let chunks = self.visible_text.chunks_in_range(start..end); - let captures = self.cursor.captures( + let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; + let captures = cursor_ref.captures( &language.highlight_query, tree.root_node(), move |node: tree_sitter::Node| { @@ -776,6 +783,8 @@ impl Buffer { chunks, stack: Default::default(), offset: start, + query_cursor: Some(cursor), + buffer: self, } } else { todo!() @@ -2035,10 +2044,10 @@ impl Clone for Buffer { selections_last_update: self.selections_last_update.clone(), deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), - cursor: tree_sitter::QueryCursor::new(), language: self.language.clone(), tree: self.tree.clone(), is_parsing: false, + query_cursor: Mutex::new(Some(QueryCursor::new())), deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, local_clock: self.local_clock.clone(), @@ -2173,6 +2182,8 @@ pub struct HighlightedChunksIter<'a, T: tree_sitter::TextProvider<'a>> { captures: iter::Peekable>, stack: Vec<(usize, usize)>, offset: usize, + query_cursor: Option, + buffer: &'a Buffer, } impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunksIter<'a, T> { @@ -2223,6 +2234,16 @@ impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunksIter<'a } } +impl<'a, T: tree_sitter::TextProvider<'a>> Drop for HighlightedChunksIter<'a, T> { + fn drop(&mut self) { + let query_cursor = self.query_cursor.take().unwrap(); + let mut buffer_cursor = self.buffer.query_cursor.lock(); + if buffer_cursor.is_none() { + *buffer_cursor = Some(query_cursor); + } + } +} + #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] struct FragmentId(Arc<[u16]>); From 4ada5e0666c4adab04b9ede19821bb93cf8ffbb0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 15:05:11 +0200 Subject: [PATCH 13/56] Rename `rope::ChunksIter` to `rope::Chunks` This is consistent with how we name the other chunk iterators. --- zed/src/editor/buffer/mod.rs | 6 +++--- zed/src/editor/buffer/rope.rs | 12 ++++++------ zed/src/editor/display_map/fold_map.rs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 93e803173acc9db0c3a26cb444b470855e55502a..ea3ba66db469618b98b447a1979d4b0009bb3778 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -6,7 +6,7 @@ mod selection; pub use anchor::*; use parking_lot::Mutex; pub use point::*; -pub use rope::{ChunksIter, Rope, TextSummary}; +pub use rope::{Chunks, Rope, TextSummary}; use seahash::SeaHasher; pub use selection::*; use similar::{ChangeTag, TextDiff}; @@ -746,7 +746,7 @@ impl Buffer { self.text_for_range(0..self.len()).collect() } - pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range) -> ChunksIter<'a> { + pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range) -> Chunks<'a> { let start = range.start.to_offset(self); let end = range.end.to_offset(self); self.visible_text.chunks_in_range(start..end) @@ -2178,7 +2178,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { } pub struct HighlightedChunksIter<'a, T: tree_sitter::TextProvider<'a>> { - chunks: ChunksIter<'a>, + chunks: Chunks<'a>, captures: iter::Peekable>, stack: Vec<(usize, usize)>, offset: usize, diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs index 174210c1a9efe8c5c7533bc8234d7753af98b621..98b317c0ed29c8db0c143977faa513238083d28e 100644 --- a/zed/src/editor/buffer/rope.rs +++ b/zed/src/editor/buffer/rope.rs @@ -118,12 +118,12 @@ impl Rope { self.chunks_in_range(start..self.len()).flat_map(str::chars) } - pub fn chunks<'a>(&'a self) -> ChunksIter<'a> { + pub fn chunks<'a>(&'a self) -> Chunks<'a> { self.chunks_in_range(0..self.len()) } - pub fn chunks_in_range<'a>(&'a self, range: Range) -> ChunksIter<'a> { - ChunksIter::new(self, range) + pub fn chunks_in_range<'a>(&'a self, range: Range) -> Chunks<'a> { + Chunks::new(self, range) } pub fn to_point(&self, offset: usize) -> Point { @@ -268,12 +268,12 @@ impl<'a> Cursor<'a> { } } -pub struct ChunksIter<'a> { +pub struct Chunks<'a> { chunks: sum_tree::Cursor<'a, Chunk, usize, usize>, range: Range, } -impl<'a> ChunksIter<'a> { +impl<'a> Chunks<'a> { pub fn new(rope: &'a Rope, range: Range) -> Self { let mut chunks = rope.chunks.cursor(); chunks.seek(&range.start, SeekBias::Right, &()); @@ -306,7 +306,7 @@ impl<'a> ChunksIter<'a> { } } -impl<'a> Iterator for ChunksIter<'a> { +impl<'a> Iterator for Chunks<'a> { type Item = &'a str; fn next(&mut self) -> Option { diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 5615105359cb396dba2940718e661dd34c61d5f8..905164612a41d7d1ea3d398cb6bfe0548e10544f 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -669,7 +669,7 @@ impl<'a> Iterator for BufferRows<'a> { pub struct Chunks<'a> { transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, - buffer_chunks: buffer::ChunksIter<'a>, + buffer_chunks: buffer::Chunks<'a>, buffer_offset: usize, } From 491932b69105469cb4cb7d24c03476a2540ecd3a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 15:07:02 +0200 Subject: [PATCH 14/56] Rename `HighlightedChunksIter` to `HighlightedChunks` --- zed/src/editor/buffer/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index ea3ba66db469618b98b447a1979d4b0009bb3778..4c05c248786fd8dd88123cf8c4bbf747fa582fdf 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -778,7 +778,7 @@ impl Buffer { }, ); - HighlightedChunksIter { + HighlightedChunks { captures: captures.peekable(), chunks, stack: Default::default(), @@ -2177,7 +2177,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { } } -pub struct HighlightedChunksIter<'a, T: tree_sitter::TextProvider<'a>> { +pub struct HighlightedChunks<'a, T: tree_sitter::TextProvider<'a>> { chunks: Chunks<'a>, captures: iter::Peekable>, stack: Vec<(usize, usize)>, @@ -2186,7 +2186,7 @@ pub struct HighlightedChunksIter<'a, T: tree_sitter::TextProvider<'a>> { buffer: &'a Buffer, } -impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunksIter<'a, T> { +impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunks<'a, T> { type Item = (&'a str, Option); fn next(&mut self) -> Option { @@ -2234,7 +2234,7 @@ impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunksIter<'a } } -impl<'a, T: tree_sitter::TextProvider<'a>> Drop for HighlightedChunksIter<'a, T> { +impl<'a, T: tree_sitter::TextProvider<'a>> Drop for HighlightedChunks<'a, T> { fn drop(&mut self) { let query_cursor = self.query_cursor.take().unwrap(); let mut buffer_cursor = self.buffer.query_cursor.lock(); From 4ddf10967e455f1b55d3d64ed44d5773701ab8d4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 15:36:40 +0200 Subject: [PATCH 15/56] Introduce `fold_map::HighlightedChunks` --- zed/src/editor/buffer/mod.rs | 79 +++++++++++++++++++------- zed/src/editor/display_map/fold_map.rs | 78 +++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 21 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 4c05c248786fd8dd88123cf8c4bbf747fa582fdf..35e88e42652ace3e9b1ebd1ed29d2cdcee8aa7c2 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -752,12 +752,8 @@ impl Buffer { self.visible_text.chunks_in_range(start..end) } - pub fn highlighted_text_for_range<'a, T: ToOffset>( - &'a self, - range: Range, - ) -> impl Iterator)> { + pub fn highlighted_text_for_range(&self, range: Range) -> HighlightedChunks { if let (Some(language), Some((tree, _))) = (&self.language, self.tree.as_ref()) { - let visible_text = &self.visible_text; let mut cursor = self .query_cursor .lock() @@ -771,18 +767,14 @@ impl Buffer { let captures = cursor_ref.captures( &language.highlight_query, tree.root_node(), - move |node: tree_sitter::Node| { - visible_text - .chunks_in_range(node.byte_range()) - .map(str::as_bytes) - }, + TextProvider(&self.visible_text), ); HighlightedChunks { captures: captures.peekable(), chunks, stack: Default::default(), - offset: start, + range: start..end, query_cursor: Some(cursor), buffer: self, } @@ -2177,21 +2169,66 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { } } -pub struct HighlightedChunks<'a, T: tree_sitter::TextProvider<'a>> { +struct ByteChunks<'a>(rope::Chunks<'a>); + +impl<'a> Iterator for ByteChunks<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + self.0.next().map(str::as_bytes) + } +} + +struct TextProvider<'a>(&'a Rope); + +impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> { + type I = ByteChunks<'a>; + + fn text(&mut self, node: tree_sitter::Node) -> Self::I { + ByteChunks(self.0.chunks_in_range(node.byte_range())) + } +} + +pub struct HighlightedChunks<'a> { chunks: Chunks<'a>, - captures: iter::Peekable>, + captures: iter::Peekable>>, stack: Vec<(usize, usize)>, - offset: usize, + range: Range, query_cursor: Option, buffer: &'a Buffer, } -impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunks<'a, T> { +impl<'a> HighlightedChunks<'a> { + pub fn seek(&mut self, offset: usize) { + let language = self.buffer.language.as_ref().unwrap(); + let tree = &self.buffer.tree.as_ref().unwrap().0; + let mut cursor = self.query_cursor.as_mut().unwrap(); + let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; + + self.stack.clear(); + self.range.start = offset; + self.chunks.seek(offset); + cursor.set_byte_range(self.range.start, self.range.end); + self.captures = cursor_ref + .captures( + &language.highlight_query, + tree.root_node(), + TextProvider(&self.buffer.visible_text), + ) + .peekable(); + } + + pub fn offset(&self) -> usize { + self.range.start + } +} + +impl<'a> Iterator for HighlightedChunks<'a> { type Item = (&'a str, Option); fn next(&mut self) -> Option { while let Some((parent_capture_end, _)) = self.stack.last() { - if *parent_capture_end <= self.offset { + if *parent_capture_end <= self.range.start { self.stack.pop(); } else { break; @@ -2201,7 +2238,7 @@ impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunks<'a, T> let mut next_capture_start = usize::MAX; while let Some((mat, capture_ix)) = self.captures.peek() { let capture = mat.captures[*capture_ix as usize]; - if self.offset < capture.node.start_byte() { + if self.range.start < capture.node.start_byte() { next_capture_start = capture.node.start_byte(); break; } else { @@ -2212,7 +2249,7 @@ impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunks<'a, T> } if let Some(chunk) = self.chunks.peek() { - let chunk_start = self.offset; + let chunk_start = self.range.start; let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); let mut capture_ix = None; if let Some((parent_capture_end, parent_capture_ix)) = self.stack.last() { @@ -2222,8 +2259,8 @@ impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunks<'a, T> let slice = &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()]; - self.offset = chunk_end; - if self.offset == self.chunks.offset() + chunk.len() { + self.range.start = chunk_end; + if self.range.start == self.chunks.offset() + chunk.len() { self.chunks.next().unwrap(); } @@ -2234,7 +2271,7 @@ impl<'a, T: tree_sitter::TextProvider<'a>> Iterator for HighlightedChunks<'a, T> } } -impl<'a, T: tree_sitter::TextProvider<'a>> Drop for HighlightedChunks<'a, T> { +impl<'a> Drop for HighlightedChunks<'a> { fn drop(&mut self) { let query_cursor = self.query_cursor.take().unwrap(); let mut buffer_cursor = self.buffer.query_cursor.lock(); diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 905164612a41d7d1ea3d398cb6bfe0548e10544f..3d0c9ab06abcb81953cb92da9fab822d3ae7520c 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -426,6 +426,24 @@ impl FoldMapSnapshot { } } + pub fn highlighted_chunks_at<'a>( + &'a self, + offset: DisplayOffset, + ctx: &'a AppContext, + ) -> HighlightedChunks<'a> { + let mut transform_cursor = self.transforms.cursor::(); + transform_cursor.seek(&offset, SeekBias::Right, &()); + let overshoot = offset.0 - transform_cursor.start().display.bytes; + let buffer_offset = transform_cursor.start().buffer.bytes + overshoot; + let buffer = self.buffer.read(ctx); + HighlightedChunks { + transform_cursor, + buffer_offset, + buffer_chunks: buffer.highlighted_text_for_range(buffer_offset..buffer.len()), + buffer_chunk: None, + } + } + pub fn chars_at<'a>( &'a self, point: DisplayPoint, @@ -720,6 +738,66 @@ impl<'a> Iterator for Chunks<'a> { } } +pub struct HighlightedChunks<'a> { + transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, + buffer_chunks: buffer::HighlightedChunks<'a>, + buffer_chunk: Option<(&'a str, Option)>, + buffer_offset: usize, +} + +impl<'a> Iterator for HighlightedChunks<'a> { + type Item = (&'a str, Option); + + fn next(&mut self) -> Option { + let transform = if let Some(item) = self.transform_cursor.item() { + item + } else { + return None; + }; + + // If we're in a fold, then return the fold's display text and + // advance the transform and buffer cursors to the end of the fold. + if let Some(display_text) = transform.display_text { + self.buffer_chunk.take(); + self.buffer_offset += transform.summary.buffer.bytes; + self.buffer_chunks.seek(self.buffer_offset); + + while self.buffer_offset >= self.transform_cursor.end().buffer.bytes + && self.transform_cursor.item().is_some() + { + self.transform_cursor.next(); + } + + return Some((display_text, None)); + } + + // Retrieve a chunk from the current buffer cursor's location. + if self.buffer_chunk.is_none() { + self.buffer_chunk = self.buffer_chunks.next(); + } + + // Otherwise, take a chunk from the buffer's text. + if let Some((mut chunk, capture_ix)) = self.buffer_chunk { + let offset_in_chunk = self.buffer_offset - self.buffer_chunks.offset(); + chunk = &chunk[offset_in_chunk..]; + + // Truncate the chunk so that it ends at the next fold. + let region_end = self.transform_cursor.end().buffer.bytes - self.buffer_offset; + if chunk.len() >= region_end { + chunk = &chunk[0..region_end]; + self.transform_cursor.next(); + } else { + self.buffer_chunk.take(); + } + + self.buffer_offset += chunk.len(); + return Some((chunk, capture_ix)); + } + + None + } +} + impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayPoint { fn add_summary(&mut self, summary: &'a TransformSummary) { self.0 += &summary.display.lines; From 4b8805bad2917e2b492c4ea4682412e19b01e94d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 16:46:11 +0200 Subject: [PATCH 16/56] Add `DisplayMap::highlighted_chunks_at(row)` Note that this API doesn't take a DisplayPoint: it could but it makes things a bit harder on the implementation side and we don't really need it anyway, as text is laid out on a line-by-line basis anyway. --- zed/src/editor/display_map/mod.rs | 68 ++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index b2874a5805822830fbcd3ebf9ae64a688ac46c90..390ab11685f584d7400ed899127a9a2f713864db 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -4,7 +4,7 @@ use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint}; pub use fold_map::BufferRows; use fold_map::{FoldMap, FoldMapSnapshot}; use gpui::{AppContext, ModelHandle}; -use std::ops::Range; +use std::{mem, ops::Range}; pub struct DisplayMap { buffer: ModelHandle, @@ -151,6 +151,23 @@ impl DisplayMapSnapshot { } } + pub fn highlighted_chunks_at<'a>( + &'a self, + row: u32, + app: &'a AppContext, + ) -> HighlightedChunks<'a> { + let point = DisplayPoint::new(row, 0); + let offset = self.folds_snapshot.to_display_offset(point, app); + HighlightedChunks { + fold_chunks: self.folds_snapshot.highlighted_chunks_at(offset, app), + column: 0, + tab_size: self.tab_size, + chunk: "", + capture_ix: None, + skip_leading_tab: false, + } + } + pub fn chars_at<'a>( &'a self, point: DisplayPoint, @@ -336,6 +353,55 @@ impl<'a> Iterator for Chunks<'a> { } } +pub struct HighlightedChunks<'a> { + fold_chunks: fold_map::HighlightedChunks<'a>, + chunk: &'a str, + capture_ix: Option, + column: usize, + tab_size: usize, + skip_leading_tab: bool, +} + +impl<'a> Iterator for HighlightedChunks<'a> { + type Item = (&'a str, Option); + + fn next(&mut self) -> Option { + if self.chunk.is_empty() { + if let Some((chunk, capture_ix)) = self.fold_chunks.next() { + self.chunk = chunk; + self.capture_ix = capture_ix; + if self.skip_leading_tab { + self.chunk = &self.chunk[1..]; + self.skip_leading_tab = false; + } + } else { + return None; + } + } + + for (ix, c) in self.chunk.char_indices() { + match c { + '\t' => { + if ix > 0 { + let (prefix, suffix) = self.chunk.split_at(ix); + self.chunk = suffix; + return Some((prefix, self.capture_ix)); + } else { + self.chunk = &self.chunk[1..]; + let len = self.tab_size - self.column % self.tab_size; + self.column += len; + return Some((&SPACES[0..len], self.capture_ix)); + } + } + '\n' => self.column = 0, + _ => self.column += 1, + } + } + + Some((mem::take(&mut self.chunk), self.capture_ix.take())) + } +} + pub fn expand_tabs(chars: impl Iterator, column: usize, tab_size: usize) -> usize { let mut expanded_chars = 0; let mut expanded_bytes = 0; From d1788c69abfc719a8f30f2cf79cad9f33ae32c72 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 16:47:13 +0200 Subject: [PATCH 17/56] Support `highlighted_text_for_range` for buffers without a language --- zed/src/editor/buffer/mod.rs | 117 +++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 35e88e42652ace3e9b1ebd1ed29d2cdcee8aa7c2..336bdef953d097694f0d18da84e79922eb7464a4 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -753,16 +753,18 @@ impl Buffer { } pub fn highlighted_text_for_range(&self, range: Range) -> HighlightedChunks { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + let chunks = self.visible_text.chunks_in_range(start..end); + if let (Some(language), Some((tree, _))) = (&self.language, self.tree.as_ref()) { let mut cursor = self .query_cursor .lock() .take() .unwrap_or_else(|| QueryCursor::new()); - let start = range.start.to_offset(self); - let end = range.end.to_offset(self); + cursor.set_byte_range(start, end); - let chunks = self.visible_text.chunks_in_range(start..end); let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; let captures = cursor_ref.captures( &language.highlight_query, @@ -771,15 +773,22 @@ impl Buffer { ); HighlightedChunks { - captures: captures.peekable(), - chunks, - stack: Default::default(), range: start..end, - query_cursor: Some(cursor), + chunks, + highlights: Some(Highlights { + captures: captures.peekable(), + stack: Default::default(), + cursor, + }), buffer: self, } } else { - todo!() + HighlightedChunks { + range: start..end, + chunks, + highlights: None, + buffer: self, + } } } @@ -2189,33 +2198,40 @@ impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> { } } -pub struct HighlightedChunks<'a> { - chunks: Chunks<'a>, +struct Highlights<'a> { captures: iter::Peekable>>, stack: Vec<(usize, usize)>, + cursor: QueryCursor, +} + +pub struct HighlightedChunks<'a> { range: Range, - query_cursor: Option, + chunks: Chunks<'a>, + highlights: Option>, buffer: &'a Buffer, } impl<'a> HighlightedChunks<'a> { pub fn seek(&mut self, offset: usize) { - let language = self.buffer.language.as_ref().unwrap(); - let tree = &self.buffer.tree.as_ref().unwrap().0; - let mut cursor = self.query_cursor.as_mut().unwrap(); - let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; - - self.stack.clear(); self.range.start = offset; - self.chunks.seek(offset); - cursor.set_byte_range(self.range.start, self.range.end); - self.captures = cursor_ref - .captures( - &language.highlight_query, - tree.root_node(), - TextProvider(&self.buffer.visible_text), - ) - .peekable(); + self.chunks.seek(self.range.start); + if let Some(highlights) = self.highlights.as_mut() { + let language = self.buffer.language.as_ref().unwrap(); + let tree = &self.buffer.tree.as_ref().unwrap().0; + let cursor_ref = + unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut highlights.cursor) }; + highlights + .cursor + .set_byte_range(self.range.start, self.range.end); + highlights.stack.clear(); + highlights.captures = cursor_ref + .captures( + &language.highlight_query, + tree.root_node(), + TextProvider(&self.buffer.visible_text), + ) + .peekable(); + } } pub fn offset(&self) -> usize { @@ -2227,24 +2243,28 @@ impl<'a> Iterator for HighlightedChunks<'a> { type Item = (&'a str, Option); fn next(&mut self) -> Option { - while let Some((parent_capture_end, _)) = self.stack.last() { - if *parent_capture_end <= self.range.start { - self.stack.pop(); - } else { - break; + let mut next_capture_start = usize::MAX; + + if let Some(highlights) = self.highlights.as_mut() { + while let Some((parent_capture_end, _)) = highlights.stack.last() { + if *parent_capture_end <= self.range.start { + highlights.stack.pop(); + } else { + break; + } } - } - let mut next_capture_start = usize::MAX; - while let Some((mat, capture_ix)) = self.captures.peek() { - let capture = mat.captures[*capture_ix as usize]; - if self.range.start < capture.node.start_byte() { - next_capture_start = capture.node.start_byte(); - break; - } else { - self.stack - .push((capture.node.end_byte(), capture.index as usize)); - self.captures.next().unwrap(); + while let Some((mat, capture_ix)) = highlights.captures.peek() { + let capture = mat.captures[*capture_ix as usize]; + if self.range.start < capture.node.start_byte() { + next_capture_start = capture.node.start_byte(); + break; + } else { + highlights + .stack + .push((capture.node.end_byte(), capture.index as usize)); + highlights.captures.next().unwrap(); + } } } @@ -2252,7 +2272,9 @@ impl<'a> Iterator for HighlightedChunks<'a> { let chunk_start = self.range.start; let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); let mut capture_ix = None; - if let Some((parent_capture_end, parent_capture_ix)) = self.stack.last() { + if let Some((parent_capture_end, parent_capture_ix)) = + self.highlights.as_ref().and_then(|h| h.stack.last()) + { chunk_end = chunk_end.min(*parent_capture_end); capture_ix = Some(*parent_capture_ix); } @@ -2273,10 +2295,11 @@ impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Drop for HighlightedChunks<'a> { fn drop(&mut self) { - let query_cursor = self.query_cursor.take().unwrap(); - let mut buffer_cursor = self.buffer.query_cursor.lock(); - if buffer_cursor.is_none() { - *buffer_cursor = Some(query_cursor); + if let Some(highlights) = self.highlights.take() { + let mut buffer_cursor = self.buffer.query_cursor.lock(); + if buffer_cursor.is_none() { + *buffer_cursor = Some(highlights.cursor); + } } } } From c50f978efff7b52bd858dc130c9e5cb7a7881360 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 16:51:32 +0200 Subject: [PATCH 18/56] Change `BufferView::layout_lines` to use `highlighted_chunks_at` --- zed/src/editor/buffer_view.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 4c2469095f1cff79151f941b1cbb472c0d70dc8e..d7a4be9aa37d9ffbb0e6c358e2d62fece20c408e 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2141,26 +2141,27 @@ impl BufferView { let mut layouts = Vec::with_capacity(rows.len()); let mut line = String::new(); + let mut styles = Vec::new(); let mut row = rows.start; let snapshot = self.display_map.snapshot(ctx); - let chunks = snapshot.chunks_at(DisplayPoint::new(rows.start, 0), ctx); - for (chunk_row, chunk_line) in chunks - .chain(Some("\n")) - .flat_map(|chunk| chunk.split("\n").enumerate()) - { - if chunk_row > 0 { - layouts.push(layout_cache.layout_str( - &line, - font_size, - &[(line.len(), font_id, ColorU::black())], - )); - line.clear(); - row += 1; - if row == rows.end { - break; + let chunks = snapshot.highlighted_chunks_at(rows.start, ctx); + 'outer: for (chunk, capture_ix) in chunks.chain(Some(("\n", None))) { + for (ix, chunk_line) in chunk.split('\n').enumerate() { + if ix > 0 { + layouts.push(layout_cache.layout_str(&line, font_size, &styles)); + line.clear(); + styles.clear(); + row += 1; + if row == rows.end { + break 'outer; + } + } + + if !chunk_line.is_empty() { + line.push_str(chunk_line); + styles.push((chunk_line.len(), font_id, ColorU::black())); } } - line.push_str(chunk_line); } Ok(layouts) From d063ebdf9b43ed193541861d5713c39a62a2b8a1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 16:52:01 +0200 Subject: [PATCH 19/56] Correctly store current chunk offset in `fold_map::HighlightedChunks` --- zed/src/editor/display_map/fold_map.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 3d0c9ab06abcb81953cb92da9fab822d3ae7520c..960bc80bf730941de51fda7e0effde5ea047c629 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -741,7 +741,7 @@ impl<'a> Iterator for Chunks<'a> { pub struct HighlightedChunks<'a> { transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, buffer_chunks: buffer::HighlightedChunks<'a>, - buffer_chunk: Option<(&'a str, Option)>, + buffer_chunk: Option<(usize, &'a str, Option)>, buffer_offset: usize, } @@ -771,14 +771,18 @@ impl<'a> Iterator for HighlightedChunks<'a> { return Some((display_text, None)); } - // Retrieve a chunk from the current buffer cursor's location. + // Retrieve a chunk from the current location in the buffer. if self.buffer_chunk.is_none() { - self.buffer_chunk = self.buffer_chunks.next(); + let chunk_offset = self.buffer_chunks.offset(); + self.buffer_chunk = self + .buffer_chunks + .next() + .map(|(chunk, capture_ix)| (chunk_offset, chunk, capture_ix)); } // Otherwise, take a chunk from the buffer's text. - if let Some((mut chunk, capture_ix)) = self.buffer_chunk { - let offset_in_chunk = self.buffer_offset - self.buffer_chunks.offset(); + if let Some((chunk_offset, mut chunk, capture_ix)) = self.buffer_chunk { + let offset_in_chunk = self.buffer_offset - chunk_offset; chunk = &chunk[offset_in_chunk..]; // Truncate the chunk so that it ends at the next fold. From bc8d2b2f1defc0744858ea895c823723b291beca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 May 2021 16:52:39 +0200 Subject: [PATCH 20/56] :lipstick: --- zed/src/editor/buffer_view.rs | 9 +++++---- zed/src/editor/display_map/mod.rs | 6 ------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index d7a4be9aa37d9ffbb0e6c358e2d62fece20c408e..49f72b365fa7409a0101674737e3d3830b69ac01 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2145,8 +2145,9 @@ impl BufferView { let mut row = rows.start; let snapshot = self.display_map.snapshot(ctx); let chunks = snapshot.highlighted_chunks_at(rows.start, ctx); + 'outer: for (chunk, capture_ix) in chunks.chain(Some(("\n", None))) { - for (ix, chunk_line) in chunk.split('\n').enumerate() { + for (ix, line_chunk) in chunk.split('\n').enumerate() { if ix > 0 { layouts.push(layout_cache.layout_str(&line, font_size, &styles)); line.clear(); @@ -2157,9 +2158,9 @@ impl BufferView { } } - if !chunk_line.is_empty() { - line.push_str(chunk_line); - styles.push((chunk_line.len(), font_id, ColorU::black())); + if !line_chunk.is_empty() { + line.push_str(line_chunk); + styles.push((line_chunk.len(), font_id, ColorU::black())); } } } diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 390ab11685f584d7400ed899127a9a2f713864db..a2582b2cd5818878e3cc5fc8bceaedab75e468be 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -164,7 +164,6 @@ impl DisplayMapSnapshot { tab_size: self.tab_size, chunk: "", capture_ix: None, - skip_leading_tab: false, } } @@ -359,7 +358,6 @@ pub struct HighlightedChunks<'a> { capture_ix: Option, column: usize, tab_size: usize, - skip_leading_tab: bool, } impl<'a> Iterator for HighlightedChunks<'a> { @@ -370,10 +368,6 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some((chunk, capture_ix)) = self.fold_chunks.next() { self.chunk = chunk; self.capture_ix = capture_ix; - if self.skip_leading_tab { - self.chunk = &self.chunk[1..]; - self.skip_leading_tab = false; - } } else { return None; } From 8340958b33c8b8f2e54326fa4bc079844b8c7802 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 24 May 2021 15:38:43 -0700 Subject: [PATCH 21/56] Convert query capture indices to style ids * Introduce a Theme struct as a new part of the app's settings * Store on each Language a ThemeMap, which converts the capture ids from that language's highlight query into StyleIds, which identify styles in the current Theme. * Update `highlighted_chunks` methods to provide StyleIds instead of capture ids. --- Cargo.lock | 12 +- zed/Cargo.toml | 1 + zed/assets/themes/light.toml | 13 ++ zed/languages/rust/highlights.scm | 45 ++++- zed/src/editor/buffer/mod.rs | 19 +- zed/src/editor/buffer_view.rs | 12 +- zed/src/editor/display_map/fold_map.rs | 7 +- zed/src/editor/display_map/mod.rs | 20 +- zed/src/language.rs | 22 +++ zed/src/main.rs | 1 + zed/src/settings.rs | 263 ++++++++++++++++++++++++- 11 files changed, 389 insertions(+), 26 deletions(-) create mode 100644 zed/assets/themes/light.toml diff --git a/Cargo.lock b/Cargo.lock index 503333e6a1850a1d064b4927b94318f32846f2ea..ac3322b6e03a4c35d1afd54c566f854b8113b1e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,7 +347,7 @@ dependencies = [ "tar", "target_build_utils", "term", - "toml", + "toml 0.4.10", "uuid", "walkdir", ] @@ -2702,6 +2702,15 @@ dependencies = [ "serde 1.0.125", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde 1.0.125", +] + [[package]] name = "tree-sitter" version = "0.19.5" @@ -2996,6 +3005,7 @@ dependencies = [ "smallvec", "smol", "tempdir", + "toml 0.5.8", "tree-sitter", "tree-sitter-rust", "unindent", diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 6779e302ce25fbb9e573c919aa37bddec6ade679..89e780a5bf385a73ac2958bf154ea0c12503c9ab 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -38,6 +38,7 @@ similar = "1.3" simplelog = "0.9" smallvec = {version = "1.6", features = ["union"]} smol = "1.2.5" +toml = "0.5" tree-sitter = "0.19.5" tree-sitter-rust = "0.19.0" diff --git a/zed/assets/themes/light.toml b/zed/assets/themes/light.toml new file mode 100644 index 0000000000000000000000000000000000000000..056d2f4a63b40ce9e9b3ab4e4d974a93dd0a6ea9 --- /dev/null +++ b/zed/assets/themes/light.toml @@ -0,0 +1,13 @@ +[ui] +background = 0xffffff +line_numbers = 0x237791 +text = 0x0d0d0d + +[syntax] +keyword = 0xaf00db +function = 0x795e26 +string = 0xa31515 +type = 0x267599 +number = 0x0d885b +comment = 0x048204 +property = 0x001080 \ No newline at end of file diff --git a/zed/languages/rust/highlights.scm b/zed/languages/rust/highlights.scm index 32b3dcac607798a8e1fb47ba6a3bb7584a557671..a8a2bd673c3677a452f487d503b92ce1750dbc3f 100644 --- a/zed/languages/rust/highlights.scm +++ b/zed/languages/rust/highlights.scm @@ -1,6 +1,49 @@ +(type_identifier) @type + +(call_expression + function: [ + (identifier) @function + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function.method) + ]) + +(field_identifier) @property + +(function_item + name: (identifier) @function.definition) + [ + "async" + "break" + "const" + "continue" + "dyn" "else" + "enum" + "for" "fn" "if" + "impl" + "let" + "loop" + "match" + "mod" + "move" + "pub" + "return" + "struct" + "trait" + "type" + "use" + "where" "while" -] @keyword \ No newline at end of file +] @keyword + +(string_literal) @string + +[ + (line_comment) + (block_comment) +] @comment diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 336bdef953d097694f0d18da84e79922eb7464a4..6c237212898e248d529b73d69c5ac516eda56d52 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -16,6 +16,7 @@ use crate::{ editor::Bias, language::{Language, Tree}, operation_queue::{self, OperationQueue}, + settings::{StyleId, ThemeMap}, sum_tree::{self, FilterCursor, SeekBias, SumTree}, time::{self, ReplicaId}, worktree::FileHandle, @@ -617,6 +618,7 @@ impl Buffer { handle.update(&mut ctx, |this, ctx| { this.tree = Some((new_tree, new_version)); ctx.emit(Event::Reparsed); + ctx.notify(); }); } handle.update(&mut ctx, |this, _| this.is_parsing = false); @@ -778,6 +780,7 @@ impl Buffer { highlights: Some(Highlights { captures: captures.peekable(), stack: Default::default(), + theme_mapping: language.theme_mapping(), cursor, }), buffer: self, @@ -2200,8 +2203,9 @@ impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> { struct Highlights<'a> { captures: iter::Peekable>>, - stack: Vec<(usize, usize)>, + stack: Vec<(usize, StyleId)>, cursor: QueryCursor, + theme_mapping: ThemeMap, } pub struct HighlightedChunks<'a> { @@ -2240,7 +2244,7 @@ impl<'a> HighlightedChunks<'a> { } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, Option); + type Item = (&'a str, StyleId); fn next(&mut self) -> Option { let mut next_capture_start = usize::MAX; @@ -2260,9 +2264,8 @@ impl<'a> Iterator for HighlightedChunks<'a> { next_capture_start = capture.node.start_byte(); break; } else { - highlights - .stack - .push((capture.node.end_byte(), capture.index as usize)); + let style_id = highlights.theme_mapping.get(capture.index); + highlights.stack.push((capture.node.end_byte(), style_id)); highlights.captures.next().unwrap(); } } @@ -2271,12 +2274,12 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some(chunk) = self.chunks.peek() { let chunk_start = self.range.start; let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); - let mut capture_ix = None; - if let Some((parent_capture_end, parent_capture_ix)) = + let mut capture_ix = StyleId::default(); + if let Some((parent_capture_end, parent_style_id)) = self.highlights.as_ref().and_then(|h| h.stack.last()) { chunk_end = chunk_end.min(*parent_capture_end); - capture_ix = Some(*parent_capture_ix); + capture_ix = *parent_style_id; } let slice = diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 49f72b365fa7409a0101674737e3d3830b69ac01..55ccfbc2c98879f495e160a6d538e3cf1592888d 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2,7 +2,12 @@ use super::{ buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, Selection, SelectionGoal, SelectionSetId, ToOffset, ToPoint, }; -use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle}; +use crate::{ + settings::{Settings, StyleId}, + util::post_inc, + workspace, + worktree::FileHandle, +}; use anyhow::Result; use gpui::{ color::ColorU, fonts::Properties as FontProperties, geometry::vector::Vector2F, @@ -2145,8 +2150,9 @@ impl BufferView { let mut row = rows.start; let snapshot = self.display_map.snapshot(ctx); let chunks = snapshot.highlighted_chunks_at(rows.start, ctx); + let theme = settings.theme.clone(); - 'outer: for (chunk, capture_ix) in chunks.chain(Some(("\n", None))) { + 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", StyleId::default()))) { for (ix, line_chunk) in chunk.split('\n').enumerate() { if ix > 0 { layouts.push(layout_cache.layout_str(&line, font_size, &styles)); @@ -2160,7 +2166,7 @@ impl BufferView { if !line_chunk.is_empty() { line.push_str(line_chunk); - styles.push((line_chunk.len(), font_id, ColorU::black())); + styles.push((line_chunk.len(), font_id, theme.syntax_style(style_ix).0)); } } } diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 960bc80bf730941de51fda7e0effde5ea047c629..7a975003fdb75a553a91e00593996942c0708112 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -4,6 +4,7 @@ use super::{ }; use crate::{ editor::buffer, + settings::StyleId, sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree}, time, }; @@ -741,12 +742,12 @@ impl<'a> Iterator for Chunks<'a> { pub struct HighlightedChunks<'a> { transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, buffer_chunks: buffer::HighlightedChunks<'a>, - buffer_chunk: Option<(usize, &'a str, Option)>, + buffer_chunk: Option<(usize, &'a str, StyleId)>, buffer_offset: usize, } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, Option); + type Item = (&'a str, StyleId); fn next(&mut self) -> Option { let transform = if let Some(item) = self.transform_cursor.item() { @@ -768,7 +769,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.transform_cursor.next(); } - return Some((display_text, None)); + return Some((display_text, StyleId::default())); } // Retrieve a chunk from the current location in the buffer. diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index a2582b2cd5818878e3cc5fc8bceaedab75e468be..1dfe340a846921d06eb4f0ffe254eadc6fe26c34 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -1,5 +1,7 @@ mod fold_map; +use crate::settings::StyleId; + use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint}; pub use fold_map::BufferRows; use fold_map::{FoldMap, FoldMapSnapshot}; @@ -163,7 +165,7 @@ impl DisplayMapSnapshot { column: 0, tab_size: self.tab_size, chunk: "", - capture_ix: None, + style_id: Default::default(), } } @@ -355,19 +357,19 @@ impl<'a> Iterator for Chunks<'a> { pub struct HighlightedChunks<'a> { fold_chunks: fold_map::HighlightedChunks<'a>, chunk: &'a str, - capture_ix: Option, + style_id: StyleId, column: usize, tab_size: usize, } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, Option); + type Item = (&'a str, StyleId); fn next(&mut self) -> Option { if self.chunk.is_empty() { - if let Some((chunk, capture_ix)) = self.fold_chunks.next() { + if let Some((chunk, style_id)) = self.fold_chunks.next() { self.chunk = chunk; - self.capture_ix = capture_ix; + self.style_id = style_id; } else { return None; } @@ -379,12 +381,12 @@ impl<'a> Iterator for HighlightedChunks<'a> { if ix > 0 { let (prefix, suffix) = self.chunk.split_at(ix); self.chunk = suffix; - return Some((prefix, self.capture_ix)); + return Some((prefix, self.style_id)); } else { self.chunk = &self.chunk[1..]; let len = self.tab_size - self.column % self.tab_size; self.column += len; - return Some((&SPACES[0..len], self.capture_ix)); + return Some((&SPACES[0..len], self.style_id)); } } '\n' => self.column = 0, @@ -392,7 +394,9 @@ impl<'a> Iterator for HighlightedChunks<'a> { } } - Some((mem::take(&mut self.chunk), self.capture_ix.take())) + let style_id = self.style_id; + self.style_id = StyleId::default(); + Some((mem::take(&mut self.chunk), style_id)) } } diff --git a/zed/src/language.rs b/zed/src/language.rs index 04bc42a522556751f3e3d86449e61337da1f5e3a..d153760cfe74f8940d6588ce711a0bddcaddb8cc 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -1,3 +1,5 @@ +use crate::settings::{Theme, ThemeMap}; +use parking_lot::Mutex; use rust_embed::RustEmbed; use std::{path::Path, sync::Arc}; use tree_sitter::{Language as Grammar, Query}; @@ -13,12 +15,23 @@ pub struct Language { pub grammar: Grammar, pub highlight_query: Query, path_suffixes: Vec, + theme_mapping: Mutex, } pub struct LanguageRegistry { languages: Vec>, } +impl Language { + pub fn theme_mapping(&self) -> ThemeMap { + self.theme_mapping.lock().clone() + } + + fn set_theme(&self, theme: &Theme) { + *self.theme_mapping.lock() = ThemeMap::new(self.highlight_query.capture_names(), theme); + } +} + impl LanguageRegistry { pub fn new() -> Self { let grammar = tree_sitter_rust::language(); @@ -32,6 +45,7 @@ impl LanguageRegistry { ) .unwrap(), path_suffixes: vec!["rs".to_string()], + theme_mapping: Mutex::new(ThemeMap::default()), }; Self { @@ -39,6 +53,12 @@ impl LanguageRegistry { } } + pub fn set_theme(&self, theme: &Theme) { + for language in &self.languages { + language.set_theme(theme); + } + } + pub fn select_language(&self, path: impl AsRef) -> Option<&Arc> { let path = path.as_ref(); let filename = path.file_name().and_then(|name| name.to_str()); @@ -67,12 +87,14 @@ mod tests { grammar, highlight_query: Query::new(grammar, "").unwrap(), path_suffixes: vec!["rs".to_string()], + theme_mapping: Default::default(), }), Arc::new(Language { name: "Make".to_string(), grammar, highlight_query: Query::new(grammar, "").unwrap(), path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], + theme_mapping: Default::default(), }), ], }; diff --git a/zed/src/main.rs b/zed/src/main.rs index 7d264e9c329405eb4881db2ad57a72871357340f..8df011e7d2bba5f6d9222d64f29ad88a947aa3d4 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -18,6 +18,7 @@ fn main() { let (_, settings) = settings::channel(&app.font_cache()).unwrap(); let language_registry = Arc::new(language::LanguageRegistry::new()); + language_registry.set_theme(&settings.borrow().theme); let app_state = AppState { language_registry, settings, diff --git a/zed/src/settings.rs b/zed/src/settings.rs index 44b05c99faec208b334f5de8a73e589d94712102..849dc85518c3de691238bb467d8d696a6c0e807c 100644 --- a/zed/src/settings.rs +++ b/zed/src/settings.rs @@ -1,6 +1,15 @@ -use anyhow::Result; -use gpui::font_cache::{FamilyId, FontCache}; +use super::assets::Assets; +use anyhow::{anyhow, Context, Result}; +use gpui::{ + color::ColorU, + font_cache::{FamilyId, FontCache}, + fonts::Weight, +}; use postage::watch; +use serde::Deserialize; +use std::{collections::HashMap, sync::Arc}; + +const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX); #[derive(Clone)] pub struct Settings { @@ -9,8 +18,23 @@ pub struct Settings { pub tab_size: usize, pub ui_font_family: FamilyId, pub ui_font_size: f32, + pub theme: Arc, +} + +#[derive(Clone, Default)] +pub struct Theme { + pub background_color: ColorU, + pub line_number_color: ColorU, + pub default_text_color: ColorU, + syntax_styles: Vec<(String, ColorU, Weight)>, } +#[derive(Clone, Debug)] +pub struct ThemeMap(Arc<[StyleId]>); + +#[derive(Clone, Copy, Debug)] +pub struct StyleId(u32); + impl Settings { pub fn new(font_cache: &FontCache) -> Result { Ok(Self { @@ -19,12 +43,247 @@ impl Settings { tab_size: 4, ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?, ui_font_size: 12.0, + theme: Arc::new( + Theme::parse(Assets::get("themes/light.toml").unwrap()) + .expect("Failed to parse built-in theme"), + ), }) } } +impl Theme { + pub fn parse(source: impl AsRef<[u8]>) -> Result { + #[derive(Deserialize)] + struct ThemeToml { + #[serde(default)] + syntax: HashMap, + #[serde(default)] + ui: HashMap, + } + + #[derive(Deserialize)] + #[serde(untagged)] + enum StyleToml { + Color(u32), + Full { + color: Option, + weight: Option, + }, + } + + let theme_toml: ThemeToml = + toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?; + + let mut syntax_styles = Vec::<(String, ColorU, Weight)>::new(); + for (key, style) in theme_toml.syntax { + let (color, weight) = match style { + StyleToml::Color(color) => (color, None), + StyleToml::Full { color, weight } => (color.unwrap_or(0), weight), + }; + match syntax_styles.binary_search_by_key(&&key, |e| &e.0) { + Ok(i) | Err(i) => syntax_styles.insert( + i, + (key, deserialize_color(color), deserialize_weight(weight)?), + ), + } + } + + let background_color = theme_toml + .ui + .get("background") + .copied() + .map_or(ColorU::from_u32(0xffffffff), deserialize_color); + let line_number_color = theme_toml + .ui + .get("line_numbers") + .copied() + .map_or(ColorU::black(), deserialize_color); + let default_text_color = theme_toml + .ui + .get("text") + .copied() + .map_or(ColorU::black(), deserialize_color); + + Ok(Theme { + background_color, + line_number_color, + default_text_color, + syntax_styles, + }) + } + + pub fn syntax_style(&self, id: StyleId) -> (ColorU, Weight) { + self.syntax_styles + .get(id.0 as usize) + .map_or((self.default_text_color, Weight::NORMAL), |entry| { + (entry.1, entry.2) + }) + } + + #[cfg(test)] + pub fn syntax_style_name(&self, id: StyleId) -> Option<&str> { + self.syntax_styles.get(id.0 as usize).map(|e| e.0.as_str()) + } +} + +impl ThemeMap { + pub fn new(capture_names: &[String], theme: &Theme) -> Self { + // For each capture name in the highlight query, find the longest + // key in the theme's syntax styles that matches all of the + // dot-separated components of the capture name. + ThemeMap( + capture_names + .iter() + .map(|capture_name| { + theme + .syntax_styles + .iter() + .enumerate() + .filter_map(|(i, (key, _, _))| { + let mut len = 0; + let capture_parts = capture_name.split('.'); + for key_part in key.split('.') { + if capture_parts.clone().any(|part| part == key_part) { + len += 1; + } else { + return None; + } + } + Some((i, len)) + }) + .max_by_key(|(_, len)| *len) + .map_or(DEFAULT_STYLE_ID, |(i, _)| StyleId(i as u32)) + }) + .collect(), + ) + } + + pub fn get(&self, capture_id: u32) -> StyleId { + self.0 + .get(capture_id as usize) + .copied() + .unwrap_or(DEFAULT_STYLE_ID) + } +} + +impl Default for ThemeMap { + fn default() -> Self { + Self(Arc::new([])) + } +} + +impl Default for StyleId { + fn default() -> Self { + DEFAULT_STYLE_ID + } +} + pub fn channel( font_cache: &FontCache, ) -> Result<(watch::Sender, watch::Receiver)> { Ok(watch::channel_with(Settings::new(font_cache)?)) } + +fn deserialize_color(color: u32) -> ColorU { + ColorU::from_u32((color << 8) + 0xFF) +} + +fn deserialize_weight(weight: Option) -> Result { + match &weight { + None => return Ok(Weight::NORMAL), + Some(toml::Value::Integer(i)) => return Ok(Weight(*i as f32)), + Some(toml::Value::String(s)) => match s.as_str() { + "normal" => return Ok(Weight::NORMAL), + "bold" => return Ok(Weight::BOLD), + "light" => return Ok(Weight::LIGHT), + "semibold" => return Ok(Weight::SEMIBOLD), + _ => {} + }, + _ => {} + } + Err(anyhow!("Invalid weight {}", weight.unwrap())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_theme() { + let theme = Theme::parse( + r#" + [ui] + background = 0x00ed00 + line_numbers = 0xdddddd + + [syntax] + "beta.two" = 0xAABBCC + "alpha.one" = {color = 0x112233, weight = "bold"} + "gamma.three" = {weight = "light"} + "#, + ) + .unwrap(); + + assert_eq!(theme.background_color, ColorU::from_u32(0x00ED00FF)); + assert_eq!(theme.line_number_color, ColorU::from_u32(0xddddddff)); + assert_eq!( + theme.syntax_styles, + &[ + ( + "alpha.one".to_string(), + ColorU::from_u32(0x112233FF), + Weight::BOLD + ), + ( + "beta.two".to_string(), + ColorU::from_u32(0xAABBCCFF), + Weight::NORMAL + ), + ( + "gamma.three".to_string(), + ColorU::from_u32(0x000000FF), + Weight::LIGHT, + ), + ] + ); + } + + #[test] + fn test_parse_empty_theme() { + Theme::parse("").unwrap(); + } + + #[test] + fn test_theme_map() { + let theme = Theme { + default_text_color: Default::default(), + background_color: ColorU::default(), + line_number_color: ColorU::default(), + syntax_styles: [ + ("function", ColorU::from_u32(0x100000ff)), + ("function.method", ColorU::from_u32(0x200000ff)), + ("function.async", ColorU::from_u32(0x300000ff)), + ("variable.builtin.self.rust", ColorU::from_u32(0x400000ff)), + ("variable.builtin", ColorU::from_u32(0x500000ff)), + ("variable", ColorU::from_u32(0x600000ff)), + ] + .iter() + .map(|e| (e.0.to_string(), e.1, Weight::NORMAL)) + .collect(), + }; + + let capture_names = &[ + "function.special".to_string(), + "function.async.rust".to_string(), + "variable.builtin.self".to_string(), + ]; + + let map = ThemeMap::new(capture_names, &theme); + assert_eq!(theme.syntax_style_name(map.get(0)), Some("function")); + assert_eq!(theme.syntax_style_name(map.get(1)), Some("function.async")); + assert_eq!( + theme.syntax_style_name(map.get(2)), + Some("variable.builtin") + ); + } +} From e54a31ead2f36b03304be1a6a2a1a3d9696a730a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 24 May 2021 21:21:31 -0700 Subject: [PATCH 22/56] Bump tree-sitter for QueryCaptures::advance methods --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac3322b6e03a4c35d1afd54c566f854b8113b1e9..774d804eea77e26bbbda599aa075ae470337053c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.19.5" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=8c3d1466ecae2a22a9625d1456ffaae84b13fd3e#8c3d1466ecae2a22a9625d1456ffaae84b13fd3e" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=a61f25bc58e3affe81aaacaaf5d9b6150a5e90ef#a61f25bc58e3affe81aaacaaf5d9b6150a5e90ef" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 28ab1b44a0ccea19f62b095f63335a9d4455d508..10177f5fbbcff9a2229427b4ee0c6e147e273f9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} -tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "8c3d1466ecae2a22a9625d1456ffaae84b13fd3e"} +tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "a61f25bc58e3affe81aaacaaf5d9b6150a5e90ef"} # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} From aeb68867dabdc8e13086eaad6c949c797224f3a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 May 2021 11:51:29 +0200 Subject: [PATCH 23/56] Move `highlighted_text_in_range` from `Buffer` to `buffer::Snapshot` --- zed/src/editor/buffer/mod.rs | 199 ++++++++++++++----------- zed/src/editor/buffer_view.rs | 4 +- zed/src/editor/display_map/fold_map.rs | 67 ++++----- zed/src/editor/display_map/mod.rs | 91 +++++------ zed/src/editor/movement.rs | 16 +- zed/src/worktree.rs | 5 +- 6 files changed, 182 insertions(+), 200 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 6c237212898e248d529b73d69c5ac516eda56d52..dd669a317ec7799a673dd12ce4315a01f31a86dc 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -62,7 +62,11 @@ type HashMap = std::collections::HashMap; type HashSet = std::collections::HashSet; thread_local! { - pub static PARSER: RefCell = RefCell::new(Parser::new()); + static PARSER: RefCell = RefCell::new(Parser::new()); +} + +lazy_static! { + static ref QUERY_CURSORS: Mutex> = Default::default(); } pub struct Buffer { @@ -78,8 +82,7 @@ pub struct Buffer { history: History, file: Option, language: Option>, - tree: Option<(Tree, time::Global)>, - query_cursor: Mutex>, + tree: Mutex>, is_parsing: bool, selections: HashMap>, pub selections_last_update: SelectionsVersion, @@ -489,10 +492,9 @@ impl Buffer { undo_map: Default::default(), history, file, - tree: None, + tree: Mutex::new(None), is_parsing: false, language, - query_cursor: Mutex::new(Some(QueryCursor::new())), saved_mtime, selections: HashMap::default(), selections_last_update: 0, @@ -506,8 +508,14 @@ impl Buffer { result } - pub fn snapshot(&self) -> Rope { - self.visible_text.clone() + pub fn snapshot(&self) -> Snapshot { + let mut cursors = QUERY_CURSORS.lock(); + Snapshot { + text: self.visible_text.clone(), + tree: self.syntax_tree(), + language: self.language.clone(), + query_cursor: Some(cursors.pop().unwrap_or_else(|| QueryCursor::new())), + } } pub fn file(&self) -> Option<&FileHandle> { @@ -519,13 +527,13 @@ impl Buffer { new_file: Option, ctx: &mut ModelContext, ) -> Task> { - let snapshot = self.snapshot(); + let text = self.visible_text.clone(); let version = self.version.clone(); let file = self.file.clone(); ctx.spawn(|handle, mut ctx| async move { if let Some(file) = new_file.as_ref().or(file.as_ref()) { - let result = ctx.read(|ctx| file.save(snapshot, ctx.as_ref())).await; + let result = ctx.read(|ctx| file.save(text, ctx.as_ref())).await; if result.is_ok() { handle.update(&mut ctx, |me, ctx| me.did_save(version, new_file, ctx)); } @@ -552,8 +560,8 @@ impl Buffer { ctx.emit(Event::Saved); } - pub fn syntax_tree(&mut self) -> Option { - if let Some((mut tree, tree_version)) = self.tree.take() { + pub fn syntax_tree(&self) -> Option { + if let Some((tree, tree_version)) = self.tree.lock().as_mut() { let mut delta = 0_isize; for Edit { old_range, @@ -575,9 +583,8 @@ impl Buffer { }); delta += new_bytes as isize - old_bytes as isize; } - let result = tree.clone(); - self.tree = Some((tree, self.version())); - Some(result) + *tree_version = self.version(); + Some(tree.clone()) } else { None } @@ -585,6 +592,7 @@ impl Buffer { fn should_reparse(&self) -> bool { self.tree + .lock() .as_ref() .map_or(true, |(_, tree_version)| *tree_version != self.version) } @@ -616,7 +624,7 @@ impl Buffer { .await; handle.update(&mut ctx, |this, ctx| { - this.tree = Some((new_tree, new_version)); + *this.tree.lock() = Some((new_tree, new_version)); ctx.emit(Event::Reparsed); ctx.notify(); }); @@ -754,47 +762,6 @@ impl Buffer { self.visible_text.chunks_in_range(start..end) } - pub fn highlighted_text_for_range(&self, range: Range) -> HighlightedChunks { - let start = range.start.to_offset(self); - let end = range.end.to_offset(self); - let chunks = self.visible_text.chunks_in_range(start..end); - - if let (Some(language), Some((tree, _))) = (&self.language, self.tree.as_ref()) { - let mut cursor = self - .query_cursor - .lock() - .take() - .unwrap_or_else(|| QueryCursor::new()); - - cursor.set_byte_range(start, end); - let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; - let captures = cursor_ref.captures( - &language.highlight_query, - tree.root_node(), - TextProvider(&self.visible_text), - ); - - HighlightedChunks { - range: start..end, - chunks, - highlights: Some(Highlights { - captures: captures.peekable(), - stack: Default::default(), - theme_mapping: language.theme_mapping(), - cursor, - }), - buffer: self, - } - } else { - HighlightedChunks { - range: start..end, - chunks, - highlights: None, - buffer: self, - } - } - } - pub fn chars(&self) -> impl Iterator + '_ { self.chars_at(0) } @@ -2049,9 +2016,8 @@ impl Clone for Buffer { deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), language: self.language.clone(), - tree: self.tree.clone(), + tree: Mutex::new(self.tree.lock().clone()), is_parsing: false, - query_cursor: Mutex::new(Some(QueryCursor::new())), deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, local_clock: self.local_clock.clone(), @@ -2060,6 +2026,79 @@ impl Clone for Buffer { } } +pub struct Snapshot { + text: Rope, + tree: Option, + language: Option>, + query_cursor: Option, +} + +impl Snapshot { + pub fn len(&self) -> usize { + self.text.len() + } + + pub fn text(&self) -> Rope { + self.text.clone() + } + + pub fn text_for_range(&self, range: Range) -> Chunks { + self.text.chunks_in_range(range) + } + + pub fn highlighted_text_for_range(&mut self, range: Range) -> HighlightedChunks { + let chunks = self.text.chunks_in_range(range.clone()); + if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { + let query_cursor = self.query_cursor.as_mut().unwrap(); + query_cursor.set_byte_range(range.start, range.end); + let captures = query_cursor.captures( + &language.highlight_query, + tree.root_node(), + TextProvider(&self.text), + ); + + HighlightedChunks { + range, + chunks, + highlights: Some(Highlights { + captures, + next_capture: None, + stack: Default::default(), + theme_mapping: language.theme_mapping(), + }), + } + } else { + HighlightedChunks { + range, + chunks, + highlights: None, + } + } + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + self.text.clip_offset(offset, bias) + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + self.text.clip_point(point, bias) + } + + pub fn to_offset(&self, point: Point) -> usize { + self.text.to_offset(point) + } + + pub fn to_point(&self, offset: usize) -> Point { + self.text.to_point(offset) + } +} + +impl Drop for Snapshot { + fn drop(&mut self) { + QUERY_CURSORS.lock().push(self.query_cursor.take().unwrap()); + } +} + struct RopeBuilder<'a> { old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>, @@ -2202,9 +2241,9 @@ impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> { } struct Highlights<'a> { - captures: iter::Peekable>>, + captures: tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>, + next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>, stack: Vec<(usize, StyleId)>, - cursor: QueryCursor, theme_mapping: ThemeMap, } @@ -2212,7 +2251,6 @@ pub struct HighlightedChunks<'a> { range: Range, chunks: Chunks<'a>, highlights: Option>, - buffer: &'a Buffer, } impl<'a> HighlightedChunks<'a> { @@ -2220,21 +2258,9 @@ impl<'a> HighlightedChunks<'a> { self.range.start = offset; self.chunks.seek(self.range.start); if let Some(highlights) = self.highlights.as_mut() { - let language = self.buffer.language.as_ref().unwrap(); - let tree = &self.buffer.tree.as_ref().unwrap().0; - let cursor_ref = - unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut highlights.cursor) }; - highlights - .cursor - .set_byte_range(self.range.start, self.range.end); highlights.stack.clear(); - highlights.captures = cursor_ref - .captures( - &language.highlight_query, - tree.root_node(), - TextProvider(&self.buffer.visible_text), - ) - .peekable(); + highlights.next_capture.take(); + highlights.captures.advance_to_byte(self.range.start); } } @@ -2258,7 +2284,11 @@ impl<'a> Iterator for HighlightedChunks<'a> { } } - while let Some((mat, capture_ix)) = highlights.captures.peek() { + if highlights.next_capture.is_none() { + highlights.next_capture = highlights.captures.next(); + } + + while let Some((mat, capture_ix)) = highlights.next_capture.as_ref() { let capture = mat.captures[*capture_ix as usize]; if self.range.start < capture.node.start_byte() { next_capture_start = capture.node.start_byte(); @@ -2266,7 +2296,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } else { let style_id = highlights.theme_mapping.get(capture.index); highlights.stack.push((capture.node.end_byte(), style_id)); - highlights.captures.next().unwrap(); + highlights.next_capture = highlights.captures.next(); } } } @@ -2296,17 +2326,6 @@ impl<'a> Iterator for HighlightedChunks<'a> { } } -impl<'a> Drop for HighlightedChunks<'a> { - fn drop(&mut self) { - if let Some(highlights) = self.highlights.take() { - let mut buffer_cursor = self.buffer.query_cursor.lock(); - if buffer_cursor.is_none() { - *buffer_cursor = Some(highlights.cursor); - } - } - } -} - #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] struct FragmentId(Arc<[u16]>); @@ -3448,7 +3467,7 @@ mod tests { let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx); assert!(buffer.is_parsing); - assert!(buffer.tree.is_none()); + assert!(buffer.syntax_tree().is_none()); buffer }); @@ -3562,7 +3581,7 @@ mod tests { fn get_tree_sexp(buffer: &ModelHandle, ctx: &gpui::TestAppContext) -> String { buffer.read_with(ctx, |buffer, _| { - buffer.tree.as_ref().unwrap().0.root_node().to_sexp() + buffer.syntax_tree().unwrap().root_node().to_sexp() }) } } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 55ccfbc2c98879f495e160a6d538e3cf1592888d..b8f52e5073b8e95d1676dfb7390cf5105ca275c6 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2148,8 +2148,8 @@ impl BufferView { let mut line = String::new(); let mut styles = Vec::new(); let mut row = rows.start; - let snapshot = self.display_map.snapshot(ctx); - let chunks = snapshot.highlighted_chunks_at(rows.start, ctx); + let mut snapshot = self.display_map.snapshot(ctx); + let chunks = snapshot.highlighted_chunks_at(rows.start); let theme = settings.theme.clone(); 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", StyleId::default()))) { diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 7a975003fdb75a553a91e00593996942c0708112..8a8471d6ee2f37a0b84d67eb04d5d02eb23f6b1a 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -46,7 +46,7 @@ impl FoldMap { pub fn snapshot(&self, ctx: &AppContext) -> FoldMapSnapshot { FoldMapSnapshot { transforms: self.sync(ctx).clone(), - buffer: self.buffer.clone(), + buffer: self.buffer.read(ctx).snapshot(), } } @@ -210,11 +210,11 @@ impl FoldMap { } pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> usize { - self.snapshot(ctx).to_buffer_offset(point, ctx) + self.snapshot(ctx).to_buffer_offset(point) } pub fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset { - self.snapshot(ctx).to_display_offset(point, ctx) + self.snapshot(ctx).to_display_offset(point) } pub fn to_buffer_point(&self, display_point: DisplayPoint, ctx: &AppContext) -> Point { @@ -394,7 +394,7 @@ impl FoldMap { pub struct FoldMapSnapshot { transforms: SumTree, - buffer: ModelHandle, + buffer: buffer::Snapshot, } impl FoldMapSnapshot { @@ -413,13 +413,12 @@ impl FoldMapSnapshot { } } - pub fn chunks_at<'a>(&'a self, offset: DisplayOffset, ctx: &'a AppContext) -> Chunks<'a> { + pub fn chunks_at(&self, offset: DisplayOffset) -> Chunks { let mut transform_cursor = self.transforms.cursor::(); transform_cursor.seek(&offset, SeekBias::Right, &()); let overshoot = offset.0 - transform_cursor.start().display.bytes; let buffer_offset = transform_cursor.start().buffer.bytes + overshoot; - let buffer = self.buffer.read(ctx); - let rope_cursor = buffer.text_for_range(buffer_offset..buffer.len()); + let rope_cursor = self.buffer.text_for_range(buffer_offset..self.buffer.len()); Chunks { transform_cursor, buffer_offset, @@ -427,34 +426,27 @@ impl FoldMapSnapshot { } } - pub fn highlighted_chunks_at<'a>( - &'a self, - offset: DisplayOffset, - ctx: &'a AppContext, - ) -> HighlightedChunks<'a> { + pub fn highlighted_chunks_at(&mut self, offset: DisplayOffset) -> HighlightedChunks { let mut transform_cursor = self.transforms.cursor::(); transform_cursor.seek(&offset, SeekBias::Right, &()); let overshoot = offset.0 - transform_cursor.start().display.bytes; let buffer_offset = transform_cursor.start().buffer.bytes + overshoot; - let buffer = self.buffer.read(ctx); HighlightedChunks { transform_cursor, buffer_offset, - buffer_chunks: buffer.highlighted_text_for_range(buffer_offset..buffer.len()), + buffer_chunks: self + .buffer + .highlighted_text_for_range(buffer_offset..self.buffer.len()), buffer_chunk: None, } } - pub fn chars_at<'a>( - &'a self, - point: DisplayPoint, - ctx: &'a AppContext, - ) -> impl Iterator + 'a { - let offset = self.to_display_offset(point, ctx); - self.chunks_at(offset, ctx).flat_map(str::chars) + pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { + let offset = self.to_display_offset(point); + self.chunks_at(offset).flat_map(str::chars) } - pub fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset { + pub fn to_display_offset(&self, point: DisplayPoint) -> DisplayOffset { let mut cursor = self.transforms.cursor::(); cursor.seek(&point, SeekBias::Right, &()); let overshoot = point.0 - cursor.start().display.lines; @@ -462,27 +454,24 @@ impl FoldMapSnapshot { if !overshoot.is_zero() { let transform = cursor.item().expect("display point out of range"); assert!(transform.display_text.is_none()); - let end_buffer_offset = - (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx)); + let end_buffer_offset = self + .buffer + .to_offset(cursor.start().buffer.lines + overshoot); offset += end_buffer_offset - cursor.start().buffer.bytes; } DisplayOffset(offset) } - pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> usize { + pub fn to_buffer_offset(&self, point: DisplayPoint) -> usize { let mut cursor = self.transforms.cursor::(); cursor.seek(&point, SeekBias::Right, &()); let overshoot = point.0 - cursor.start().display.lines; - (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx)) + self.buffer + .to_offset(cursor.start().buffer.lines + overshoot) } #[cfg(test)] - pub fn clip_offset( - &self, - offset: DisplayOffset, - bias: Bias, - ctx: &AppContext, - ) -> DisplayOffset { + pub fn clip_offset(&self, offset: DisplayOffset, bias: Bias) -> DisplayOffset { let mut cursor = self.transforms.cursor::(); cursor.seek(&offset, SeekBias::Right, &()); if let Some(transform) = cursor.item() { @@ -496,7 +485,7 @@ impl FoldMapSnapshot { } else { let overshoot = offset.0 - transform_start; let buffer_offset = cursor.start().buffer.bytes + overshoot; - let clipped_buffer_offset = self.buffer.read(ctx).clip_offset(buffer_offset, bias); + let clipped_buffer_offset = self.buffer.clip_offset(buffer_offset, bias); DisplayOffset( (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize)) as usize, @@ -507,7 +496,7 @@ impl FoldMapSnapshot { } } - pub fn clip_point(&self, point: DisplayPoint, bias: Bias, ctx: &AppContext) -> DisplayPoint { + pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { let mut cursor = self.transforms.cursor::(); cursor.seek(&point, SeekBias::Right, &()); if let Some(transform) = cursor.item() { @@ -521,8 +510,7 @@ impl FoldMapSnapshot { } else { let overshoot = point.0 - transform_start; let buffer_position = cursor.start().buffer.lines + overshoot; - let clipped_buffer_position = - self.buffer.read(ctx).clip_point(buffer_position, bias); + let clipped_buffer_position = self.buffer.clip_point(buffer_position, bias); DisplayPoint::new( point.row(), ((point.column() as i32) + clipped_buffer_position.column as i32 @@ -1131,11 +1119,10 @@ mod tests { let offset = map.snapshot(app.as_ref()).clip_offset( DisplayOffset(rng.gen_range(0..=map.len(app.as_ref()))), Bias::Right, - app.as_ref(), ); assert_eq!( map.snapshot(app.as_ref()) - .chunks_at(offset, app.as_ref()) + .chunks_at(offset) .collect::(), &expected_text[offset.0..], ); @@ -1218,9 +1205,7 @@ mod tests { impl FoldMap { fn text(&self, app: &AppContext) -> String { - self.snapshot(app) - .chunks_at(DisplayOffset(0), app) - .collect() + self.snapshot(app).chunks_at(DisplayOffset(0)).collect() } fn merged_fold_ranges(&self, app: &AppContext) -> Vec> { diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 1dfe340a846921d06eb4f0ffe254eadc6fe26c34..2cf9de0b8c8deaf58030d4bc659e939636cc8989 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -62,16 +62,14 @@ impl DisplayMap { } pub fn text(&self, ctx: &AppContext) -> String { - self.snapshot(ctx) - .chunks_at(DisplayPoint::zero(), ctx) - .collect() + self.snapshot(ctx).chunks_at(DisplayPoint::zero()).collect() } pub fn line(&self, display_row: u32, ctx: &AppContext) -> String { let mut result = String::new(); for chunk in self .snapshot(ctx) - .chunks_at(DisplayPoint::new(display_row, 0), ctx) + .chunks_at(DisplayPoint::new(display_row, 0)) { if let Some(ix) = chunk.find('\n') { result.push_str(&chunk[0..ix]); @@ -88,7 +86,7 @@ impl DisplayMap { let mut is_blank = true; for c in self .snapshot(ctx) - .chars_at(DisplayPoint::new(display_row, 0), ctx) + .chars_at(DisplayPoint::new(display_row, 0)) { if c == ' ' { indent += 1; @@ -138,12 +136,11 @@ impl DisplayMapSnapshot { self.folds_snapshot.buffer_rows(start_row) } - pub fn chunks_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chunks<'a> { - let (point, expanded_char_column, to_next_stop) = - self.collapse_tabs(point, Bias::Left, app); + pub fn chunks_at(&self, point: DisplayPoint) -> Chunks { + let (point, expanded_char_column, to_next_stop) = self.collapse_tabs(point, Bias::Left); let fold_chunks = self .folds_snapshot - .chunks_at(self.folds_snapshot.to_display_offset(point, app), app); + .chunks_at(self.folds_snapshot.to_display_offset(point)); Chunks { fold_chunks, column: expanded_char_column, @@ -153,15 +150,11 @@ impl DisplayMapSnapshot { } } - pub fn highlighted_chunks_at<'a>( - &'a self, - row: u32, - app: &'a AppContext, - ) -> HighlightedChunks<'a> { + pub fn highlighted_chunks_at(&mut self, row: u32) -> HighlightedChunks { let point = DisplayPoint::new(row, 0); - let offset = self.folds_snapshot.to_display_offset(point, app); + let offset = self.folds_snapshot.to_display_offset(point); HighlightedChunks { - fold_chunks: self.folds_snapshot.highlighted_chunks_at(offset, app), + fold_chunks: self.folds_snapshot.highlighted_chunks_at(offset), column: 0, tab_size: self.tab_size, chunk: "", @@ -169,18 +162,14 @@ impl DisplayMapSnapshot { } } - pub fn chars_at<'a>( - &'a self, - point: DisplayPoint, - app: &'a AppContext, - ) -> impl Iterator + 'a { - self.chunks_at(point, app).flat_map(str::chars) + pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { + self.chunks_at(point).flat_map(str::chars) } - pub fn column_to_chars(&self, display_row: u32, target: u32, ctx: &AppContext) -> u32 { + pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 { let mut count = 0; let mut column = 0; - for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) { + for c in self.chars_at(DisplayPoint::new(display_row, 0)) { if column >= target { break; } @@ -190,10 +179,10 @@ impl DisplayMapSnapshot { count } - pub fn column_from_chars(&self, display_row: u32, char_count: u32, ctx: &AppContext) -> u32 { + pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 { let mut count = 0; let mut column = 0; - for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) { + for c in self.chars_at(DisplayPoint::new(display_row, 0)) { if c == '\n' || count >= char_count { break; } @@ -203,32 +192,26 @@ impl DisplayMapSnapshot { column } - pub fn clip_point(&self, point: DisplayPoint, bias: Bias, ctx: &AppContext) -> DisplayPoint { + pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { self.expand_tabs( self.folds_snapshot - .clip_point(self.collapse_tabs(point, bias, ctx).0, bias, ctx), - ctx, + .clip_point(self.collapse_tabs(point, bias).0, bias), ) } - fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint { + fn expand_tabs(&self, mut point: DisplayPoint) -> DisplayPoint { let chars = self .folds_snapshot - .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx); + .chars_at(DisplayPoint(Point::new(point.row(), 0))); let expanded = expand_tabs(chars, point.column() as usize, self.tab_size); *point.column_mut() = expanded as u32; point } - fn collapse_tabs( - &self, - mut point: DisplayPoint, - bias: Bias, - ctx: &AppContext, - ) -> (DisplayPoint, usize, usize) { + fn collapse_tabs(&self, mut point: DisplayPoint, bias: Bias) -> (DisplayPoint, usize, usize) { let chars = self .folds_snapshot - .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx); + .chars_at(DisplayPoint(Point::new(point.row(), 0))); let expanded = point.column() as usize; let (collapsed, expanded_char_column, to_next_stop) = collapse_tabs(chars, expanded, bias, self.tab_size); @@ -276,11 +259,11 @@ impl DisplayPoint { } fn expand_tabs(self, map: &DisplayMap, ctx: &AppContext) -> Self { - map.snapshot(ctx).expand_tabs(self, ctx) + map.snapshot(ctx).expand_tabs(self) } fn collapse_tabs(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Self { - map.snapshot(ctx).collapse_tabs(self, bias, ctx).0 + map.snapshot(ctx).collapse_tabs(self, bias).0 } } @@ -288,7 +271,7 @@ impl Point { pub fn to_display_point(self, map: &DisplayMap, ctx: &AppContext) -> DisplayPoint { let mut display_point = map.fold_map.to_display_point(self, ctx); let snapshot = map.fold_map.snapshot(ctx); - let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0), ctx); + let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0)); *display_point.column_mut() = expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32; display_point @@ -487,19 +470,19 @@ mod tests { assert_eq!( &map.snapshot(app.as_ref()) - .chunks_at(DisplayPoint::new(1, 0), app.as_ref()) + .chunks_at(DisplayPoint::new(1, 0)) .collect::()[0..10], " b bb" ); assert_eq!( &map.snapshot(app.as_ref()) - .chunks_at(DisplayPoint::new(1, 2), app.as_ref()) + .chunks_at(DisplayPoint::new(1, 2)) .collect::()[0..10], " b bbbb" ); assert_eq!( &map.snapshot(app.as_ref()) - .chunks_at(DisplayPoint::new(1, 6), app.as_ref()) + .chunks_at(DisplayPoint::new(1, 6)) .collect::()[0..13], " bbbbb\nc c" ); @@ -534,7 +517,7 @@ mod tests { ), ] { assert_eq!( - map.clip_point(DisplayPoint::new(1, input_column as u32), bias, ctx), + map.clip_point(DisplayPoint::new(1, input_column as u32), bias), DisplayPoint::new(1, output_column as u32), "clip_point(({}, {}))", 1, @@ -584,7 +567,7 @@ mod tests { ); assert_eq!( map.snapshot(ctx) - .chunks_at(DisplayPoint::new(0, "✅ ".len() as u32), ctx) + .chunks_at(DisplayPoint::new(0, "✅ ".len() as u32)) .collect::(), " α\nβ \n🏀β γ" ); @@ -598,26 +581,20 @@ mod tests { ); assert_eq!( map.snapshot(ctx) - .chunks_at(DisplayPoint::new(0, "✅ ".len() as u32), ctx) + .chunks_at(DisplayPoint::new(0, "✅ ".len() as u32)) .collect::(), " α\nβ \n🏀β γ" ); // Clipping display points inside of multi-byte characters assert_eq!( - map.snapshot(ctx).clip_point( - DisplayPoint::new(0, "✅".len() as u32 - 1), - Bias::Left, - ctx - ), + map.snapshot(ctx) + .clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Left), DisplayPoint::new(0, 0) ); assert_eq!( - map.snapshot(ctx).clip_point( - DisplayPoint::new(0, "✅".len() as u32 - 1), - Bias::Right, - ctx - ), + map.snapshot(ctx) + .clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Right), DisplayPoint::new(0, "✅".len() as u32) ); } diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index b40573e5e8ea0d4f91112e77967154beedf0e4ee..f7e89776ecf1d3fd24dc90e03b689ea5f63de22f 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -9,7 +9,7 @@ pub fn left(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Resu *point.row_mut() -= 1; *point.column_mut() = map.line_len(point.row(), app); } - Ok(map.snapshot(app).clip_point(point, Bias::Left, app)) + Ok(map.snapshot(app).clip_point(point, Bias::Left)) } pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Result { @@ -20,7 +20,7 @@ pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Res *point.row_mut() += 1; *point.column_mut() = 0; } - Ok(map.snapshot(app).clip_point(point, Bias::Right, app)) + Ok(map.snapshot(app).clip_point(point, Bias::Right)) } pub fn up( @@ -33,12 +33,12 @@ pub fn up( let goal_column = if let SelectionGoal::Column(column) = goal { column } else { - map.column_to_chars(point.row(), point.column(), app) + map.column_to_chars(point.row(), point.column()) }; if point.row() > 0 { *point.row_mut() -= 1; - *point.column_mut() = map.column_from_chars(point.row(), goal_column, app); + *point.column_mut() = map.column_from_chars(point.row(), goal_column); } else { point = DisplayPoint::new(0, 0); } @@ -57,12 +57,12 @@ pub fn down( let goal_column = if let SelectionGoal::Column(column) = goal { column } else { - map.column_to_chars(point.row(), point.column(), app) + map.column_to_chars(point.row(), point.column()) }; if point.row() < max_point.row() { *point.row_mut() += 1; - *point.column_mut() = map.column_from_chars(point.row(), goal_column, app); + *point.column_mut() = map.column_from_chars(point.row(), goal_column); } else { point = max_point; } @@ -107,7 +107,7 @@ pub fn prev_word_boundary( let mut boundary = DisplayPoint::new(point.row(), 0); let mut column = 0; let mut prev_c = None; - for c in map.snapshot(app).chars_at(boundary, app) { + for c in map.snapshot(app).chars_at(boundary) { if column >= point.column() { break; } @@ -129,7 +129,7 @@ pub fn next_word_boundary( app: &AppContext, ) -> Result { let mut prev_c = None; - for c in map.snapshot(app).chars_at(point, app) { + for c in map.snapshot(app).chars_at(point) { if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) { break; } diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 33b23c8d698102f699529183f187466b9fe92c47..a50dacc4683de41d7a8c02f627746ab229122f5c 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -1483,7 +1483,8 @@ mod tests { let path = tree.update(&mut app, |tree, ctx| { let path = tree.files(0).next().unwrap().path().clone(); assert_eq!(path.file_name().unwrap(), "file1"); - smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap(); + smol::block_on(tree.save(&path, buffer.read(ctx).snapshot().text(), ctx.as_ref())) + .unwrap(); path }); @@ -1512,7 +1513,7 @@ mod tests { let file = app.update(|ctx| tree.file("", ctx)).await; app.update(|ctx| { assert_eq!(file.path().file_name(), None); - smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap(); + smol::block_on(file.save(buffer.read(ctx).snapshot().text(), ctx.as_ref())).unwrap(); }); let history = app.read(|ctx| file.load_history(ctx)).await.unwrap(); From bafac35d6a38a265f7aabb2b61dc499a35420a47 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 May 2021 12:07:57 +0200 Subject: [PATCH 24/56] Reparse if syntax tree is up-to-date but in an interpolated state --- zed/src/editor/buffer/mod.rs | 41 ++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index dd669a317ec7799a673dd12ce4315a01f31a86dc..00b87dbc34a0dad52705a3fe30ac21c443209614 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -82,7 +82,7 @@ pub struct Buffer { history: History, file: Option, language: Option>, - tree: Mutex>, + syntax_tree: Mutex>, is_parsing: bool, selections: HashMap>, pub selections_last_update: SelectionsVersion, @@ -93,6 +93,13 @@ pub struct Buffer { lamport_clock: time::Lamport, } +#[derive(Clone)] +struct SyntaxTree { + tree: Tree, + parsed: bool, + version: time::Global, +} + #[derive(Clone)] struct Transaction { start: time::Global, @@ -492,7 +499,7 @@ impl Buffer { undo_map: Default::default(), history, file, - tree: Mutex::new(None), + syntax_tree: Mutex::new(None), is_parsing: false, language, saved_mtime, @@ -561,19 +568,20 @@ impl Buffer { } pub fn syntax_tree(&self) -> Option { - if let Some((tree, tree_version)) = self.tree.lock().as_mut() { + if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() { + let mut edited = false; let mut delta = 0_isize; for Edit { old_range, new_range, old_lines, - } in self.edits_since(tree_version.clone()) + } in self.edits_since(syntax_tree.version.clone()) { let start_offset = (old_range.start as isize + delta) as usize; let start_point = self.visible_text.to_point(start_offset); let old_bytes = old_range.end - old_range.start; let new_bytes = new_range.end - new_range.start; - tree.edit(&InputEdit { + syntax_tree.tree.edit(&InputEdit { start_byte: start_offset, old_end_byte: start_offset + old_bytes, new_end_byte: start_offset + new_bytes, @@ -582,19 +590,22 @@ impl Buffer { new_end_position: self.visible_text.to_point(start_offset + new_bytes).into(), }); delta += new_bytes as isize - old_bytes as isize; + edited = true; } - *tree_version = self.version(); - Some(tree.clone()) + syntax_tree.parsed &= !edited; + syntax_tree.version = self.version(); + Some(syntax_tree.tree.clone()) } else { None } } fn should_reparse(&self) -> bool { - self.tree - .lock() - .as_ref() - .map_or(true, |(_, tree_version)| *tree_version != self.version) + if let Some(syntax_tree) = self.syntax_tree.lock().as_ref() { + !syntax_tree.parsed || syntax_tree.version != self.version + } else { + self.language.is_some() + } } fn reparse(&mut self, ctx: &mut ModelContext) { @@ -624,7 +635,11 @@ impl Buffer { .await; handle.update(&mut ctx, |this, ctx| { - *this.tree.lock() = Some((new_tree, new_version)); + *this.syntax_tree.lock() = Some(SyntaxTree { + tree: new_tree, + parsed: true, + version: new_version, + }); ctx.emit(Event::Reparsed); ctx.notify(); }); @@ -2016,7 +2031,7 @@ impl Clone for Buffer { deferred_ops: self.deferred_ops.clone(), file: self.file.clone(), language: self.language.clone(), - tree: Mutex::new(self.tree.lock().clone()), + syntax_tree: Mutex::new(self.syntax_tree.lock().clone()), is_parsing: false, deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, From 2e7593dd79ebbe19a6316ce69edc9356c0128c6b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 May 2021 14:16:33 +0200 Subject: [PATCH 25/56] :lipstick: --- zed/src/editor/display_map/fold_map.rs | 3 +-- zed/src/editor/display_map/mod.rs | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 8a8471d6ee2f37a0b84d67eb04d5d02eb23f6b1a..3496433d677335e1a653e209cb0e0eef58f77abb 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -418,11 +418,10 @@ impl FoldMapSnapshot { transform_cursor.seek(&offset, SeekBias::Right, &()); let overshoot = offset.0 - transform_cursor.start().display.bytes; let buffer_offset = transform_cursor.start().buffer.bytes + overshoot; - let rope_cursor = self.buffer.text_for_range(buffer_offset..self.buffer.len()); Chunks { transform_cursor, buffer_offset, - buffer_chunks: rope_cursor, + buffer_chunks: self.buffer.text_for_range(buffer_offset..self.buffer.len()), } } diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 2cf9de0b8c8deaf58030d4bc659e939636cc8989..56cc06468f8ae0a6313c6e697bffbc1e18e7e443 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -377,9 +377,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } } - let style_id = self.style_id; - self.style_id = StyleId::default(); - Some((mem::take(&mut self.chunk), style_id)) + Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id))) } } From 43ddb6f64080884eade12b48e5af239083199170 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 May 2021 17:20:46 +0200 Subject: [PATCH 26/56] WIP: Push failing test for `DisplayMap::highlighted_chunks_at` --- zed/src/editor/buffer/mod.rs | 42 ++++++++----- zed/src/editor/display_map/mod.rs | 101 +++++++++++++++++++++++++++++- zed/src/language.rs | 6 +- 3 files changed, 131 insertions(+), 18 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 00b87dbc34a0dad52705a3fe30ac21c443209614..5e14f5104f9cd74def0fa180f3d57a4226a2b7ed 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -600,6 +600,10 @@ impl Buffer { } } + pub fn is_parsing(&self) -> bool { + self.is_parsing + } + fn should_reparse(&self) -> bool { if let Some(syntax_tree) = self.syntax_tree.lock().as_ref() { !syntax_tree.parsed || syntax_tree.version != self.version @@ -3481,13 +3485,15 @@ mod tests { let text = "fn a() {}"; let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx); - assert!(buffer.is_parsing); + assert!(buffer.is_parsing()); assert!(buffer.syntax_tree().is_none()); buffer }); // Wait for the initial text to parse - buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + buffer + .condition(&ctx, |buffer, _| !buffer.is_parsing()) + .await; assert_eq!( get_tree_sexp(&buffer, &ctx), concat!( @@ -3504,17 +3510,19 @@ mod tests { let offset = buf.text().find(")").unwrap(); buf.edit(vec![offset..offset], "b: C", Some(ctx)).unwrap(); - assert!(!buf.is_parsing); + assert!(!buf.is_parsing()); let offset = buf.text().find("}").unwrap(); buf.edit(vec![offset..offset], " d; ", Some(ctx)).unwrap(); - assert!(!buf.is_parsing); + assert!(!buf.is_parsing()); buf.end_transaction(None, Some(ctx)).unwrap(); assert_eq!(buf.text(), "fn a(b: C) { d; }"); - assert!(buf.is_parsing); + assert!(buf.is_parsing()); }); - buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + buffer + .condition(&ctx, |buffer, _| !buffer.is_parsing()) + .await; assert_eq!( get_tree_sexp(&buffer, &ctx), concat!( @@ -3532,21 +3540,23 @@ mod tests { let offset = buf.text().find(";").unwrap(); buf.edit(vec![offset..offset], ".e", Some(ctx)).unwrap(); assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); - assert!(buf.is_parsing); + assert!(buf.is_parsing()); }); buffer.update(&mut ctx, |buf, ctx| { let offset = buf.text().find(";").unwrap(); buf.edit(vec![offset..offset], "(f)", Some(ctx)).unwrap(); assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); - assert!(buf.is_parsing); + assert!(buf.is_parsing()); }); buffer.update(&mut ctx, |buf, ctx| { let offset = buf.text().find("(f)").unwrap(); buf.edit(vec![offset..offset], "::", Some(ctx)).unwrap(); assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); - assert!(buf.is_parsing); + assert!(buf.is_parsing()); }); - buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + buffer + .condition(&ctx, |buffer, _| !buffer.is_parsing()) + .await; assert_eq!( get_tree_sexp(&buffer, &ctx), concat!( @@ -3563,9 +3573,11 @@ mod tests { buffer.update(&mut ctx, |buf, ctx| { buf.undo(Some(ctx)); assert_eq!(buf.text(), "fn a() {}"); - assert!(buf.is_parsing); + assert!(buf.is_parsing()); }); - buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + buffer + .condition(&ctx, |buffer, _| !buffer.is_parsing()) + .await; assert_eq!( get_tree_sexp(&buffer, &ctx), concat!( @@ -3578,9 +3590,11 @@ mod tests { buffer.update(&mut ctx, |buf, ctx| { buf.redo(Some(ctx)); assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); - assert!(buf.is_parsing); + assert!(buf.is_parsing()); }); - buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await; + buffer + .condition(&ctx, |buffer, _| !buffer.is_parsing()) + .await; assert_eq!( get_tree_sexp(&buffer, &ctx), concat!( diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 56cc06468f8ae0a6313c6e697bffbc1e18e7e443..54765ed0b97f1315640817105bfec6472401fa84 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -445,7 +445,9 @@ pub fn collapse_tabs( #[cfg(test)] mod tests { use super::*; - use crate::test::*; + use crate::{language::Language, settings::Theme, test::*}; + use buffer::History; + use std::sync::Arc; #[gpui::test] fn test_chunks_at(app: &mut gpui::MutableAppContext) { @@ -486,6 +488,103 @@ mod tests { ); } + #[gpui::test] + async fn test_highlighted_chunks_at(mut app: gpui::TestAppContext) { + use unindent::Unindent as _; + + let grammar = tree_sitter_rust::language(); + let text = r#" + fn outer() {} + + mod module { + fn inner() {} + }"# + .unindent(); + let query = tree_sitter::Query::new( + grammar, + r#" + (mod_item name: (identifier) body: _ @mod.body) + (function_item name: (identifier) @fn.name)"#, + ) + .unwrap(); + let theme = Theme::parse( + r#" + [syntax] + "mod.body" = 0xff0000 + "fn.name" = 0x00ff00"#, + ) + .unwrap(); + let lang = Arc::new(Language { + name: "Test".to_string(), + grammar: grammar.clone(), + highlight_query: query, + path_suffixes: vec![".test".to_string()], + theme_mapping: Default::default(), + }); + lang.set_theme(&theme); + + let buffer = app.add_model(|ctx| { + Buffer::from_history(0, History::new(text.into()), None, Some(lang), ctx) + }); + buffer.condition(&app, |buf, _| !buf.is_parsing()).await; + + let mut map = app.read(|ctx| DisplayMap::new(buffer, 2, ctx)); + assert_eq!( + app.read(|ctx| highlighted_chunks(0, &map, &theme, ctx)), + vec![ + ("fn ".to_string(), None), + ("outer".to_string(), Some("fn.name")), + ("() {}\n\nmod module ".to_string(), None), + ("{\n fn ".to_string(), Some("mod.body")), + ("inner".to_string(), Some("fn.name")), + ("() {}\n}".to_string(), Some("mod.body")), + ] + ); + assert_eq!( + app.read(|ctx| highlighted_chunks(3, &map, &theme, ctx)), + vec![ + (" fn ".to_string(), Some("mod.body")), + ("inner".to_string(), Some("fn.name")), + ("() {}\n}".to_string(), Some("mod.body")), + ] + ); + + app.read(|ctx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], ctx)); + assert_eq!( + app.read(|ctx| highlighted_chunks(0, &map, &theme, ctx)), + vec![ + ("fn ".to_string(), None), + ("out".to_string(), Some("fn.name")), + ("…".to_string(), None), + (" fn ".to_string(), Some("mod.body")), + ("inner".to_string(), Some("fn.name")), + ("() {}\n}".to_string(), Some("mod.body")), + ] + ); + + fn highlighted_chunks<'a>( + row: u32, + map: &DisplayMap, + theme: &'a Theme, + ctx: &AppContext, + ) -> Vec<(String, Option<&'a str>)> { + let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); + for (chunk, style_id) in map.snapshot(ctx).highlighted_chunks_at(row) { + let style_name = theme.syntax_style_name(style_id); + if let Some((last_chunk, last_style_name)) = chunks.last_mut() { + if style_name == *last_style_name { + last_chunk.push_str(chunk); + } else { + chunks.push((chunk.to_string(), style_name)); + } + } else { + chunks.push((chunk.to_string(), style_name)); + } + } + chunks + } + } + #[gpui::test] fn test_clip_point(app: &mut gpui::MutableAppContext) { use Bias::{Left, Right}; diff --git a/zed/src/language.rs b/zed/src/language.rs index d153760cfe74f8940d6588ce711a0bddcaddb8cc..4e0bf6ff8d8343636af2e8d29a43236f5e600e8b 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -14,8 +14,8 @@ pub struct Language { pub name: String, pub grammar: Grammar, pub highlight_query: Query, - path_suffixes: Vec, - theme_mapping: Mutex, + pub path_suffixes: Vec, + pub theme_mapping: Mutex, } pub struct LanguageRegistry { @@ -27,7 +27,7 @@ impl Language { self.theme_mapping.lock().clone() } - fn set_theme(&self, theme: &Theme) { + pub fn set_theme(&self, theme: &Theme) { *self.theme_mapping.lock() = ThemeMap::new(self.highlight_query.capture_names(), theme); } } From 5d2ac3f4e42838669749ce1fab1dc5a1c21febb2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 May 2021 16:02:43 -0700 Subject: [PATCH 27/56] Use new Tree-sitter captures API Co-Authored-By: Nathan Sobo --- Cargo.lock | 2 +- Cargo.toml | 2 +- zed/assets/themes/light.toml | 2 +- zed/languages/rust/highlights.scm | 4 ++-- zed/src/editor/buffer/mod.rs | 32 +++++++++++++++++++------- zed/src/editor/buffer_view.rs | 2 +- zed/src/editor/display_map/fold_map.rs | 22 +++++++++++++----- zed/src/editor/display_map/mod.rs | 27 +++++++++++++--------- 8 files changed, 62 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 774d804eea77e26bbbda599aa075ae470337053c..c3da6eb1b3e054ad673865192597aee5bb03b3b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.19.5" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=a61f25bc58e3affe81aaacaaf5d9b6150a5e90ef#a61f25bc58e3affe81aaacaaf5d9b6150a5e90ef" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=3112fa546852785151700d3b2fc598599ef75063#3112fa546852785151700d3b2fc598599ef75063" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 10177f5fbbcff9a2229427b4ee0c6e147e273f9c..a696ffe315b81572eb20e66659d349863c868a6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} -tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "a61f25bc58e3affe81aaacaaf5d9b6150a5e90ef"} +tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "3112fa546852785151700d3b2fc598599ef75063"} # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} diff --git a/zed/assets/themes/light.toml b/zed/assets/themes/light.toml index 056d2f4a63b40ce9e9b3ab4e4d974a93dd0a6ea9..1d706fd7241b9f83d3734c64eb6b7ffd670de69c 100644 --- a/zed/assets/themes/light.toml +++ b/zed/assets/themes/light.toml @@ -10,4 +10,4 @@ string = 0xa31515 type = 0x267599 number = 0x0d885b comment = 0x048204 -property = 0x001080 \ No newline at end of file +property = 0x001080 diff --git a/zed/languages/rust/highlights.scm b/zed/languages/rust/highlights.scm index a8a2bd673c3677a452f487d503b92ce1750dbc3f..858bb96e7b1fd878b1c6dffb9dbe0ba3f3a59028 100644 --- a/zed/languages/rust/highlights.scm +++ b/zed/languages/rust/highlights.scm @@ -1,5 +1,7 @@ (type_identifier) @type +(field_identifier) @property + (call_expression function: [ (identifier) @function @@ -9,8 +11,6 @@ field: (field_identifier) @function.method) ]) -(field_identifier) @property - (function_item name: (identifier) @function.definition) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 5e14f5104f9cd74def0fa180f3d57a4226a2b7ed..fbab3503a8f1a73a14cb937638a98bf9051d6df1 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2069,12 +2069,12 @@ impl Snapshot { let chunks = self.text.chunks_in_range(range.clone()); if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { let query_cursor = self.query_cursor.as_mut().unwrap(); - query_cursor.set_byte_range(range.start, range.end); - let captures = query_cursor.captures( + let mut captures = query_cursor.captures( &language.highlight_query, tree.root_node(), TextProvider(&self.text), ); + captures.set_byte_range(range.start, range.end); HighlightedChunks { range, @@ -2277,9 +2277,25 @@ impl<'a> HighlightedChunks<'a> { self.range.start = offset; self.chunks.seek(self.range.start); if let Some(highlights) = self.highlights.as_mut() { - highlights.stack.clear(); - highlights.next_capture.take(); - highlights.captures.advance_to_byte(self.range.start); + highlights + .stack + .retain(|(end_offset, _)| *end_offset > offset); + if let Some((mat, capture_ix)) = &highlights.next_capture { + let capture = mat.captures[*capture_ix as usize]; + if offset >= capture.node.start_byte() { + let next_capture_end = capture.node.end_byte(); + if offset < next_capture_end { + highlights.stack.push(( + next_capture_end, + highlights.theme_mapping.get(capture.index), + )); + } + highlights.next_capture.take(); + } + } + highlights + .captures + .set_byte_range(self.range.start, self.range.end); } } @@ -2323,12 +2339,12 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some(chunk) = self.chunks.peek() { let chunk_start = self.range.start; let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); - let mut capture_ix = StyleId::default(); + let mut style_id = StyleId::default(); if let Some((parent_capture_end, parent_style_id)) = self.highlights.as_ref().and_then(|h| h.stack.last()) { chunk_end = chunk_end.min(*parent_capture_end); - capture_ix = *parent_style_id; + style_id = *parent_style_id; } let slice = @@ -2338,7 +2354,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.chunks.next().unwrap(); } - Some((slice, capture_ix)) + Some((slice, style_id)) } else { None } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index b8f52e5073b8e95d1676dfb7390cf5105ca275c6..e104db2f89d1657d746b7a7d5242fea945276553 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2149,7 +2149,7 @@ impl BufferView { let mut styles = Vec::new(); let mut row = rows.start; let mut snapshot = self.display_map.snapshot(ctx); - let chunks = snapshot.highlighted_chunks_at(rows.start); + let chunks = snapshot.highlighted_chunks_for_rows(rows.clone()); let theme = settings.theme.clone(); 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", StyleId::default()))) { diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 3496433d677335e1a653e209cb0e0eef58f77abb..77887d32889916715507bf58d83e0dece9917634 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -413,6 +413,10 @@ impl FoldMapSnapshot { } } + pub fn max_point(&self) -> DisplayPoint { + DisplayPoint(self.transforms.summary().display.lines) + } + pub fn chunks_at(&self, offset: DisplayOffset) -> Chunks { let mut transform_cursor = self.transforms.cursor::(); transform_cursor.seek(&offset, SeekBias::Right, &()); @@ -425,17 +429,23 @@ impl FoldMapSnapshot { } } - pub fn highlighted_chunks_at(&mut self, offset: DisplayOffset) -> HighlightedChunks { + pub fn highlighted_chunks(&mut self, range: Range) -> HighlightedChunks { let mut transform_cursor = self.transforms.cursor::(); - transform_cursor.seek(&offset, SeekBias::Right, &()); - let overshoot = offset.0 - transform_cursor.start().display.bytes; - let buffer_offset = transform_cursor.start().buffer.bytes + overshoot; + + transform_cursor.seek(&range.end, SeekBias::Right, &()); + let overshoot = range.end.0 - transform_cursor.start().display.bytes; + let buffer_end = transform_cursor.start().buffer.bytes + overshoot; + + transform_cursor.seek(&range.start, SeekBias::Right, &()); + let overshoot = range.start.0 - transform_cursor.start().display.bytes; + let buffer_start = transform_cursor.start().buffer.bytes + overshoot; + HighlightedChunks { transform_cursor, - buffer_offset, + buffer_offset: buffer_start, buffer_chunks: self .buffer - .highlighted_text_for_range(buffer_offset..self.buffer.len()), + .highlighted_text_for_range(buffer_start..buffer_end), buffer_chunk: None, } } diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 54765ed0b97f1315640817105bfec6472401fa84..86d7743556c3f0efab20074f742f224e8e866ccc 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -104,9 +104,8 @@ impl DisplayMap { .column() } - // TODO - make this delegate to the DisplayMapSnapshot pub fn max_point(&self, ctx: &AppContext) -> DisplayPoint { - self.fold_map.max_point(ctx).expand_tabs(self, ctx) + self.snapshot(ctx).max_point().expand_tabs(self, ctx) } pub fn longest_row(&self, ctx: &AppContext) -> u32 { @@ -136,6 +135,10 @@ impl DisplayMapSnapshot { self.folds_snapshot.buffer_rows(start_row) } + pub fn max_point(&self) -> DisplayPoint { + self.expand_tabs(self.folds_snapshot.max_point()) + } + pub fn chunks_at(&self, point: DisplayPoint) -> Chunks { let (point, expanded_char_column, to_next_stop) = self.collapse_tabs(point, Bias::Left); let fold_chunks = self @@ -150,11 +153,13 @@ impl DisplayMapSnapshot { } } - pub fn highlighted_chunks_at(&mut self, row: u32) -> HighlightedChunks { - let point = DisplayPoint::new(row, 0); - let offset = self.folds_snapshot.to_display_offset(point); + pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { + let start = DisplayPoint::new(rows.start, 0); + let start = self.folds_snapshot.to_display_offset(start); + let end = DisplayPoint::new(rows.end, 0).min(self.max_point()); + let end = self.folds_snapshot.to_display_offset(end); HighlightedChunks { - fold_chunks: self.folds_snapshot.highlighted_chunks_at(offset), + fold_chunks: self.folds_snapshot.highlighted_chunks(start..end), column: 0, tab_size: self.tab_size, chunk: "", @@ -530,7 +535,7 @@ mod tests { let mut map = app.read(|ctx| DisplayMap::new(buffer, 2, ctx)); assert_eq!( - app.read(|ctx| highlighted_chunks(0, &map, &theme, ctx)), + app.read(|ctx| highlighted_chunks(0..5, &map, &theme, ctx)), vec![ ("fn ".to_string(), None), ("outer".to_string(), Some("fn.name")), @@ -541,7 +546,7 @@ mod tests { ] ); assert_eq!( - app.read(|ctx| highlighted_chunks(3, &map, &theme, ctx)), + app.read(|ctx| highlighted_chunks(3..5, &map, &theme, ctx)), vec![ (" fn ".to_string(), Some("mod.body")), ("inner".to_string(), Some("fn.name")), @@ -551,7 +556,7 @@ mod tests { app.read(|ctx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], ctx)); assert_eq!( - app.read(|ctx| highlighted_chunks(0, &map, &theme, ctx)), + app.read(|ctx| highlighted_chunks(0..2, &map, &theme, ctx)), vec![ ("fn ".to_string(), None), ("out".to_string(), Some("fn.name")), @@ -563,13 +568,13 @@ mod tests { ); fn highlighted_chunks<'a>( - row: u32, + rows: Range, map: &DisplayMap, theme: &'a Theme, ctx: &AppContext, ) -> Vec<(String, Option<&'a str>)> { let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); - for (chunk, style_id) in map.snapshot(ctx).highlighted_chunks_at(row) { + for (chunk, style_id) in map.snapshot(ctx).highlighted_chunks_for_rows(rows) { let style_name = theme.syntax_style_name(style_id); if let Some((last_chunk, last_style_name)) = chunks.last_mut() { if style_name == *last_style_name { From c80812c8096d5a6d100ae535e81864fa54e8f900 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 May 2021 17:42:46 -0700 Subject: [PATCH 28/56] Add a few more rust highlighting patterns Co-Authored-By: Nathan Sobo --- zed/languages/rust/highlights.scm | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/zed/languages/rust/highlights.scm b/zed/languages/rust/highlights.scm index 858bb96e7b1fd878b1c6dffb9dbe0ba3f3a59028..2b425faf5c0f9b44967e4afcc66b5f2306d70b15 100644 --- a/zed/languages/rust/highlights.scm +++ b/zed/languages/rust/highlights.scm @@ -1,4 +1,5 @@ (type_identifier) @type +(primitive_type) @type.builtin (field_identifier) @property @@ -11,37 +12,50 @@ field: (field_identifier) @function.method) ]) -(function_item - name: (identifier) @function.definition) +(function_item name: (identifier) @function.definition) +(function_signature_item name: (identifier) @function.definition) [ "async" "break" "const" "continue" + "default" "dyn" "else" "enum" + "extern" "for" "fn" "if" + "in" "impl" "let" "loop" + "macro_rules!" "match" "mod" "move" "pub" "return" + "static" "struct" "trait" "type" "use" "where" "while" + "union" + "unsafe" + (mutable_specifier) + (super) ] @keyword -(string_literal) @string +[ + (string_literal) + (raw_string_literal) + (char_literal) +] @string [ (line_comment) From ab64f2dd1dd44bc0bf79bdfc3e5cec236f4370ef Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 09:10:06 +0200 Subject: [PATCH 29/56] Update tree-sitter --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3da6eb1b3e054ad673865192597aee5bb03b3b4..51681d91dd38e4ff35fa8c45cff9cd7af3a946c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.19.5" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=3112fa546852785151700d3b2fc598599ef75063#3112fa546852785151700d3b2fc598599ef75063" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=036aceed574c2c23eee8f0ff90be5a2409e524c1#036aceed574c2c23eee8f0ff90be5a2409e524c1" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index a696ffe315b81572eb20e66659d349863c868a6c..1b176d520beaa3b5260dd39efafe942c8452abda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"] [patch.crates-io] async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} -tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "3112fa546852785151700d3b2fc598599ef75063"} +tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "036aceed574c2c23eee8f0ff90be5a2409e524c1"} # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} From e0cb5ccfba470be6e74e656364fa61f671f8c7da Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 09:23:48 +0200 Subject: [PATCH 30/56] Honor `bold` in syntax themes --- zed/src/editor/buffer_view.rs | 18 +++++++++++++++--- zed/src/settings.rs | 27 ++++++++++++++------------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index e104db2f89d1657d746b7a7d5242fea945276553..15bc94feecc9a0b6aff62e3f046ed8f0a6e64544 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2140,9 +2140,12 @@ impl BufferView { } let settings = self.settings.borrow(); - let font_id = - font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?; let font_size = settings.buffer_font_size; + let font_family = settings.buffer_font_family; + let mut prev_font_properties = FontProperties::new(); + let mut prev_font_id = font_cache + .select_font(font_family, &prev_font_properties) + .unwrap(); let mut layouts = Vec::with_capacity(rows.len()); let mut line = String::new(); @@ -2165,8 +2168,17 @@ impl BufferView { } if !line_chunk.is_empty() { + let (color, font_properties) = theme.syntax_style(style_ix); + // Avoid a lookup if the font properties match the previous ones. + let font_id = if font_properties == prev_font_properties { + prev_font_id + } else { + font_cache.select_font(font_family, &font_properties)? + }; line.push_str(line_chunk); - styles.push((line_chunk.len(), font_id, theme.syntax_style(style_ix).0)); + styles.push((line_chunk.len(), font_id, color)); + prev_font_id = font_id; + prev_font_properties = font_properties; } } } diff --git a/zed/src/settings.rs b/zed/src/settings.rs index 849dc85518c3de691238bb467d8d696a6c0e807c..fd436ff2a09200dc8457bf3f31719ad0a5480bda 100644 --- a/zed/src/settings.rs +++ b/zed/src/settings.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use gpui::{ color::ColorU, font_cache::{FamilyId, FontCache}, - fonts::Weight, + fonts::{Properties as FontProperties, Weight}, }; use postage::watch; use serde::Deserialize; @@ -26,7 +26,7 @@ pub struct Theme { pub background_color: ColorU, pub line_number_color: ColorU, pub default_text_color: ColorU, - syntax_styles: Vec<(String, ColorU, Weight)>, + syntax_styles: Vec<(String, ColorU, FontProperties)>, } #[derive(Clone, Debug)] @@ -74,17 +74,18 @@ impl Theme { let theme_toml: ThemeToml = toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?; - let mut syntax_styles = Vec::<(String, ColorU, Weight)>::new(); + let mut syntax_styles = Vec::<(String, ColorU, FontProperties)>::new(); for (key, style) in theme_toml.syntax { let (color, weight) = match style { StyleToml::Color(color) => (color, None), StyleToml::Full { color, weight } => (color.unwrap_or(0), weight), }; match syntax_styles.binary_search_by_key(&&key, |e| &e.0) { - Ok(i) | Err(i) => syntax_styles.insert( - i, - (key, deserialize_color(color), deserialize_weight(weight)?), - ), + Ok(i) | Err(i) => { + let mut properties = FontProperties::new(); + properties.weight = deserialize_weight(weight)?; + syntax_styles.insert(i, (key, deserialize_color(color), properties)); + } } } @@ -112,10 +113,10 @@ impl Theme { }) } - pub fn syntax_style(&self, id: StyleId) -> (ColorU, Weight) { + pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) { self.syntax_styles .get(id.0 as usize) - .map_or((self.default_text_color, Weight::NORMAL), |entry| { + .map_or((self.default_text_color, FontProperties::new()), |entry| { (entry.1, entry.2) }) } @@ -232,17 +233,17 @@ mod tests { ( "alpha.one".to_string(), ColorU::from_u32(0x112233FF), - Weight::BOLD + *FontProperties::new().weight(Weight::BOLD) ), ( "beta.two".to_string(), ColorU::from_u32(0xAABBCCFF), - Weight::NORMAL + *FontProperties::new().weight(Weight::NORMAL) ), ( "gamma.three".to_string(), ColorU::from_u32(0x000000FF), - Weight::LIGHT, + *FontProperties::new().weight(Weight::LIGHT), ), ] ); @@ -268,7 +269,7 @@ mod tests { ("variable", ColorU::from_u32(0x600000ff)), ] .iter() - .map(|e| (e.0.to_string(), e.1, Weight::NORMAL)) + .map(|e| (e.0.to_string(), e.1, FontProperties::new())) .collect(), }; From 9642e9b4254e65bf60e9f701692672ce928ed431 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 09:41:30 +0200 Subject: [PATCH 31/56] Honor `italic` in syntax themes --- zed/src/settings.rs | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/zed/src/settings.rs b/zed/src/settings.rs index fd436ff2a09200dc8457bf3f31719ad0a5480bda..7e60e6da233d89c6ceea7735ff1bb0f6bf05a258 100644 --- a/zed/src/settings.rs +++ b/zed/src/settings.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use gpui::{ color::ColorU, font_cache::{FamilyId, FontCache}, - fonts::{Properties as FontProperties, Weight}, + fonts::{Properties as FontProperties, Style as FontStyle, Weight as FontWeight}, }; use postage::watch; use serde::Deserialize; @@ -68,6 +68,8 @@ impl Theme { Full { color: Option, weight: Option, + #[serde(default)] + italic: bool, }, } @@ -76,14 +78,21 @@ impl Theme { let mut syntax_styles = Vec::<(String, ColorU, FontProperties)>::new(); for (key, style) in theme_toml.syntax { - let (color, weight) = match style { - StyleToml::Color(color) => (color, None), - StyleToml::Full { color, weight } => (color.unwrap_or(0), weight), + let (color, weight, italic) = match style { + StyleToml::Color(color) => (color, None, false), + StyleToml::Full { + color, + weight, + italic, + } => (color.unwrap_or(0), weight, italic), }; match syntax_styles.binary_search_by_key(&&key, |e| &e.0) { Ok(i) | Err(i) => { let mut properties = FontProperties::new(); properties.weight = deserialize_weight(weight)?; + if italic { + properties.style = FontStyle::Italic; + } syntax_styles.insert(i, (key, deserialize_color(color), properties)); } } @@ -189,15 +198,15 @@ fn deserialize_color(color: u32) -> ColorU { ColorU::from_u32((color << 8) + 0xFF) } -fn deserialize_weight(weight: Option) -> Result { +fn deserialize_weight(weight: Option) -> Result { match &weight { - None => return Ok(Weight::NORMAL), - Some(toml::Value::Integer(i)) => return Ok(Weight(*i as f32)), + None => return Ok(FontWeight::NORMAL), + Some(toml::Value::Integer(i)) => return Ok(FontWeight(*i as f32)), Some(toml::Value::String(s)) => match s.as_str() { - "normal" => return Ok(Weight::NORMAL), - "bold" => return Ok(Weight::BOLD), - "light" => return Ok(Weight::LIGHT), - "semibold" => return Ok(Weight::SEMIBOLD), + "normal" => return Ok(FontWeight::NORMAL), + "bold" => return Ok(FontWeight::BOLD), + "light" => return Ok(FontWeight::LIGHT), + "semibold" => return Ok(FontWeight::SEMIBOLD), _ => {} }, _ => {} @@ -220,7 +229,7 @@ mod tests { [syntax] "beta.two" = 0xAABBCC "alpha.one" = {color = 0x112233, weight = "bold"} - "gamma.three" = {weight = "light"} + "gamma.three" = {weight = "light", italic = true} "#, ) .unwrap(); @@ -233,17 +242,19 @@ mod tests { ( "alpha.one".to_string(), ColorU::from_u32(0x112233FF), - *FontProperties::new().weight(Weight::BOLD) + *FontProperties::new().weight(FontWeight::BOLD) ), ( "beta.two".to_string(), ColorU::from_u32(0xAABBCCFF), - *FontProperties::new().weight(Weight::NORMAL) + *FontProperties::new().weight(FontWeight::NORMAL) ), ( "gamma.three".to_string(), ColorU::from_u32(0x000000FF), - *FontProperties::new().weight(Weight::LIGHT), + *FontProperties::new() + .weight(FontWeight::LIGHT) + .style(FontStyle::Italic), ), ] ); From 5485c1a749b923e76e7fb6d20900cd88e8cf5927 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 09:41:57 +0200 Subject: [PATCH 32/56] Add test for font selection --- gpui/src/font_cache.rs | 26 +++++++++++++++ gpui/src/platform/mac/fonts.rs | 60 ++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/gpui/src/font_cache.rs b/gpui/src/font_cache.rs index 4e109ef24099a4c3cf74c6169d80b5fcf44bcfc3..75ee206b35e7e3c35400ed584216da3bc2e512ff 100644 --- a/gpui/src/font_cache.rs +++ b/gpui/src/font_cache.rs @@ -161,3 +161,29 @@ impl FontCache { metric * font_size / self.metric(font_id, |m| m.units_per_em as f32) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + fonts::{Style, Weight}, + platform::{test, Platform as _}, + }; + + #[test] + fn test_select_font() { + let platform = test::platform(); + let fonts = FontCache::new(platform.fonts()); + let arial = fonts.load_family(&["Arial"]).unwrap(); + let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap(); + let arial_italic = fonts + .select_font(arial, &Properties::new().style(Style::Italic)) + .unwrap(); + let arial_bold = fonts + .select_font(arial, &Properties::new().weight(Weight::BOLD)) + .unwrap(); + assert_ne!(arial_regular, arial_italic); + assert_ne!(arial_regular, arial_bold); + assert_ne!(arial_italic, arial_bold); + } +} diff --git a/gpui/src/platform/mac/fonts.rs b/gpui/src/platform/mac/fonts.rs index c33234090fa4be51768cfd6078ccff3d85304ab2..ac455b93b997a3086c466d905cc229930e6de800 100644 --- a/gpui/src/platform/mac/fonts.rs +++ b/gpui/src/platform/mac/fonts.rs @@ -322,6 +322,9 @@ mod tests { let menlo_regular = fonts.select_font(&menlo, &Properties::new())?; let menlo_italic = fonts.select_font(&menlo, &Properties::new().style(Style::Italic))?; let menlo_bold = fonts.select_font(&menlo, &Properties::new().weight(Weight::BOLD))?; + assert_ne!(menlo_regular, menlo_italic); + assert_ne!(menlo_regular, menlo_bold); + assert_ne!(menlo_italic, menlo_bold); let line = fonts.layout_str( "hello world", @@ -371,32 +374,33 @@ mod tests { Ok(()) } - // #[test] - // fn test_rasterize_glyph() { - // use std::{fs::File, io::BufWriter, path::Path}; - - // let fonts = FontSystem::new(); - // let font_ids = fonts.load_family("Fira Code").unwrap(); - // let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); - // let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap(); - - // const VARIANTS: usize = 1; - // for i in 0..VARIANTS { - // let variant = i as f32 / VARIANTS as f32; - // let (bounds, bytes) = fonts - // .rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.) - // .unwrap(); - - // let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); - // let path = Path::new(&name); - // let file = File::create(path).unwrap(); - // let ref mut w = BufWriter::new(file); - - // let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32); - // encoder.set_color(png::ColorType::Grayscale); - // encoder.set_depth(png::BitDepth::Eight); - // let mut writer = encoder.write_header().unwrap(); - // writer.write_image_data(&bytes).unwrap(); - // } - // } + #[test] + #[ignore] + fn test_rasterize_glyph() { + use std::{fs::File, io::BufWriter, path::Path}; + + let fonts = FontSystem::new(); + let font_ids = fonts.load_family("Fira Code").unwrap(); + let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); + let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap(); + + const VARIANTS: usize = 1; + for i in 0..VARIANTS { + let variant = i as f32 / VARIANTS as f32; + let (bounds, bytes) = fonts + .rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.) + .unwrap(); + + let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); + let path = Path::new(&name); + let file = File::create(path).unwrap(); + let ref mut w = BufWriter::new(file); + + let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32); + encoder.set_color(png::ColorType::Grayscale); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().unwrap(); + writer.write_image_data(&bytes).unwrap(); + } + } } From 04c7989720d3e96630a29fc6da64ccc905fe55d4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 10:26:20 +0200 Subject: [PATCH 33/56] Extract a `LanguageConfig` and load it from `language-dir/config.toml` --- zed/languages/rust/config.toml | 2 ++ zed/src/editor/display_map/mod.rs | 13 ++++++++--- zed/src/language.rs | 37 ++++++++++++++++++++----------- 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 zed/languages/rust/config.toml diff --git a/zed/languages/rust/config.toml b/zed/languages/rust/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..17b54c05ab263278ec819121650a26150ffeed0b --- /dev/null +++ b/zed/languages/rust/config.toml @@ -0,0 +1,2 @@ +name = "Rust" +path_suffixes = ["rs"] diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 86d7743556c3f0efab20074f742f224e8e866ccc..de8a6b3c0a7ee5cef8304a846c1456c3732e7ef4 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -450,7 +450,11 @@ pub fn collapse_tabs( #[cfg(test)] mod tests { use super::*; - use crate::{language::Language, settings::Theme, test::*}; + use crate::{ + language::{Language, LanguageConfig}, + settings::Theme, + test::*, + }; use buffer::History; use std::sync::Arc; @@ -520,10 +524,13 @@ mod tests { ) .unwrap(); let lang = Arc::new(Language { - name: "Test".to_string(), + config: LanguageConfig { + name: "Test".to_string(), + path_suffixes: vec![".test".to_string()], + ..Default::default() + }, grammar: grammar.clone(), highlight_query: query, - path_suffixes: vec![".test".to_string()], theme_mapping: Default::default(), }); lang.set_theme(&theme); diff --git a/zed/src/language.rs b/zed/src/language.rs index 4e0bf6ff8d8343636af2e8d29a43236f5e600e8b..45c889c99c65996a8a5e7f500e03c9a3ead07e4c 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -1,20 +1,25 @@ use crate::settings::{Theme, ThemeMap}; use parking_lot::Mutex; use rust_embed::RustEmbed; -use std::{path::Path, sync::Arc}; +use serde::Deserialize; +use std::{path::Path, str, sync::Arc}; use tree_sitter::{Language as Grammar, Query}; - pub use tree_sitter::{Parser, Tree}; #[derive(RustEmbed)] #[folder = "languages"] pub struct LanguageDir; -pub struct Language { +#[derive(Default, Deserialize)] +pub struct LanguageConfig { pub name: String, + pub path_suffixes: Vec, +} + +pub struct Language { + pub config: LanguageConfig, pub grammar: Grammar, pub highlight_query: Query, - pub path_suffixes: Vec, pub theme_mapping: Mutex, } @@ -35,16 +40,15 @@ impl Language { impl LanguageRegistry { pub fn new() -> Self { let grammar = tree_sitter_rust::language(); + let rust_config = toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap()).unwrap(); let rust_language = Language { - name: "Rust".to_string(), + config: rust_config, grammar, highlight_query: Query::new( grammar, - std::str::from_utf8(LanguageDir::get("rust/highlights.scm").unwrap().as_ref()) - .unwrap(), + str::from_utf8(LanguageDir::get("rust/highlights.scm").unwrap().as_ref()).unwrap(), ) .unwrap(), - path_suffixes: vec!["rs".to_string()], theme_mapping: Mutex::new(ThemeMap::default()), }; @@ -66,6 +70,7 @@ impl LanguageRegistry { let path_suffixes = [extension, filename]; self.languages.iter().find(|language| { language + .config .path_suffixes .iter() .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) @@ -83,17 +88,23 @@ mod tests { let registry = LanguageRegistry { languages: vec![ Arc::new(Language { - name: "Rust".to_string(), + config: LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, grammar, highlight_query: Query::new(grammar, "").unwrap(), - path_suffixes: vec!["rs".to_string()], theme_mapping: Default::default(), }), Arc::new(Language { - name: "Make".to_string(), + config: LanguageConfig { + name: "Make".to_string(), + path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], + ..Default::default() + }, grammar, highlight_query: Query::new(grammar, "").unwrap(), - path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], theme_mapping: Default::default(), }), ], @@ -121,7 +132,7 @@ mod tests { assert_eq!(registry.select_language("zed/sumk").map(get_name), None); fn get_name(language: &Arc) -> &str { - language.name.as_str() + language.config.name.as_str() } } } From d38d944fef4d6629ec0cc184e20fec3db08d0701 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 15:45:34 +0200 Subject: [PATCH 34/56] Add `indent` and `indent_nodes` configs in the language `config.toml` --- zed/languages/rust/config.toml | 2 ++ zed/src/language.rs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/zed/languages/rust/config.toml b/zed/languages/rust/config.toml index 17b54c05ab263278ec819121650a26150ffeed0b..8605f243ab51683c6cbe0272275b1f753661638b 100644 --- a/zed/languages/rust/config.toml +++ b/zed/languages/rust/config.toml @@ -1,2 +1,4 @@ name = "Rust" path_suffixes = ["rs"] +indent = 4 +indent_nodes = ["mod_item", "block"] diff --git a/zed/src/language.rs b/zed/src/language.rs index 45c889c99c65996a8a5e7f500e03c9a3ead07e4c..a52bb51ae95317b8bfe32ce7cd340452fa357924 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -2,7 +2,7 @@ use crate::settings::{Theme, ThemeMap}; use parking_lot::Mutex; use rust_embed::RustEmbed; use serde::Deserialize; -use std::{path::Path, str, sync::Arc}; +use std::{collections::HashSet, path::Path, str, sync::Arc}; use tree_sitter::{Language as Grammar, Query}; pub use tree_sitter::{Parser, Tree}; @@ -13,6 +13,8 @@ pub struct LanguageDir; #[derive(Default, Deserialize)] pub struct LanguageConfig { pub name: String, + pub indent: usize, + pub indent_nodes: HashSet, pub path_suffixes: Vec, } From acf84f939dc6194038fdb0aa72f3c7aa70ab98d2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 15:46:34 +0200 Subject: [PATCH 35/56] Add `Buffer::autoindent_for_row` --- zed/src/editor/buffer/mod.rs | 132 +++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 6 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index fbab3503a8f1a73a14cb937638a98bf9051d6df1..d15e19e1c686c2903ae6fa4bdda4bcd59157b1ad 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -674,6 +674,72 @@ impl Buffer { }) } + fn indent_for_row(&self, row: u32) -> (usize, bool) { + let mut is_whitespace = true; + let mut indent = 0; + for c in self.chars_at(Point::new(row, 0)) { + match c { + ' ' => indent += 1, + '\n' => break, + _ => { + is_whitespace = false; + break; + } + } + } + (indent, is_whitespace) + } + + fn autoindent_for_row(&self, row: u32) -> usize { + let mut indent_parent = None; + let mut indent = 2; + if let Some((language, syntax_tree)) = self.language.as_ref().zip(self.syntax_tree()) { + let row_start = Point::new(row, 0).into(); + let mut cursor = syntax_tree.walk(); + loop { + let node = cursor.node(); + if row_start >= node.end_position() { + if !cursor.goto_next_sibling() { + break; + } + } else if node.start_position() > row_start { + break; + } else { + if node.start_position().row as u32 != row + && language.config.indent_nodes.contains(node.kind()) + { + let parent_ends_at_row = node.end_position().row as u32 == row; + indent_parent = + Some((node.start_position().row as u32, parent_ends_at_row)); + } + + if !cursor.goto_first_child() { + break; + } + } + } + + indent = language.config.indent; + } + + if let Some((parent_row, parent_ends_at_row)) = indent_parent { + let (parent_indent, _) = self.indent_for_row(parent_row); + if parent_ends_at_row { + parent_indent + } else { + parent_indent + indent + } + } else { + for prev_row in (0..row).rev() { + let (prev_indent, is_whitespace) = self.indent_for_row(prev_row); + if prev_indent != 0 || !is_whitespace { + return prev_indent; + } + } + 0 + } + } + fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { // TODO: it would be nice to not allocate here. let old_text = self.text(); @@ -765,12 +831,6 @@ impl Buffer { self.visible_text.max_point() } - pub fn line(&self, row: u32) -> String { - self.chars_at(Point::new(row, 0)) - .take_while(|c| *c != '\n') - .collect() - } - pub fn text(&self) -> String { self.text_for_range(0..self.len()).collect() } @@ -2632,6 +2692,7 @@ impl ToPoint for usize { mod tests { use super::*; use crate::{ + language::LanguageConfig, test::{build_app_state, temp_tree}, util::RandomCharIter, worktree::{Worktree, WorktreeHandle}, @@ -2643,9 +2704,11 @@ mod tests { cell::RefCell, cmp::Ordering, fs, + iter::FromIterator as _, rc::Rc, sync::atomic::{self, AtomicUsize}, }; + use unindent::Unindent as _; #[gpui::test] fn test_edit(ctx: &mut gpui::MutableAppContext) { @@ -3631,6 +3694,63 @@ mod tests { } } + #[gpui::test] + async fn test_indent(mut ctx: gpui::TestAppContext) { + let grammar = tree_sitter_rust::language(); + let lang = Arc::new(Language { + config: LanguageConfig { + indent: 3, + indent_nodes: std::collections::HashSet::from_iter(vec!["block".to_string()]), + ..Default::default() + }, + grammar: grammar.clone(), + highlight_query: tree_sitter::Query::new(grammar, "").unwrap(), + theme_mapping: Default::default(), + }); + + let buffer = ctx.add_model(|ctx| { + let text = " + fn a() {} + + fn b() { + } + + fn c() { + let badly_indented_line; + + } + + struct D { + x: 1, + + + } + " + .unindent(); + Buffer::from_history(0, History::new(text.into()), None, Some(lang), ctx) + }); + + buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; + buffer.read_with(&ctx, |buf, _| { + assert_eq!(buf.autoindent_for_row(6), 3); + assert_eq!(buf.autoindent_for_row(7), 3); + + // Don't autoindent rows that start or end on the same row as the indent node. + assert_eq!(buf.autoindent_for_row(0), 0); + assert_eq!(buf.autoindent_for_row(2), 0); + assert_eq!(buf.autoindent_for_row(3), 0); + assert_eq!(buf.autoindent_for_row(4), 0); + assert_eq!(buf.autoindent_for_row(8), 0); + + // We didn't find any matching indent node in the language for the struct definition. + // Don't autoindent the first line inside of the struct. Instead, align the second and + // third line to the first non-whitespace line preceding them. + assert_eq!(buf.autoindent_for_row(11), 0); + assert_eq!(buf.autoindent_for_row(12), 4); + assert_eq!(buf.autoindent_for_row(13), 4); + }); + } + impl Buffer { fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl Rng) -> Range { let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); From 9b69cea75a1475d098c814cdc584ffd7fdcb3b29 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 15:58:37 +0200 Subject: [PATCH 36/56] Replace `mod_item` with `declaration_list` for Rust's autoindent --- zed/languages/rust/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zed/languages/rust/config.toml b/zed/languages/rust/config.toml index 8605f243ab51683c6cbe0272275b1f753661638b..66e21d4dfa978510073acdbbdf08408ce619d73b 100644 --- a/zed/languages/rust/config.toml +++ b/zed/languages/rust/config.toml @@ -1,4 +1,4 @@ name = "Rust" path_suffixes = ["rs"] indent = 4 -indent_nodes = ["mod_item", "block"] +indent_nodes = ["declaration_list", "block"] From 7c084846afd8c341a7ce661ae17f06c4f5a35b83 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 May 2021 17:37:50 +0200 Subject: [PATCH 37/56] Batch `indent_for_rows` There's still a `todo!` in the test to write more assertions and verify this more complex, batched logic. Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer/mod.rs | 119 +++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index d15e19e1c686c2903ae6fa4bdda4bcd59157b1ad..89229dc5f70fc1b466b9780487817c02840be23e 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -690,54 +690,78 @@ impl Buffer { (indent, is_whitespace) } - fn autoindent_for_row(&self, row: u32) -> usize { - let mut indent_parent = None; + fn autoindent_for_rows(&self, rows: Range) -> Vec { + let mut indents = Vec::new(); let mut indent = 2; if let Some((language, syntax_tree)) = self.language.as_ref().zip(self.syntax_tree()) { - let row_start = Point::new(row, 0).into(); + let mut stack = Vec::new(); let mut cursor = syntax_tree.walk(); - loop { + let mut row = rows.start; + while row < rows.end { let node = cursor.node(); - if row_start >= node.end_position() { + let row_start = Point::new(row, 0).into(); + + if node.end_position() <= row_start { if !cursor.goto_next_sibling() { - break; + if stack.last() == Some(&node) { + stack.pop(); + } + + if !cursor.goto_parent() { + break; + } } - } else if node.start_position() > row_start { - break; - } else { - if node.start_position().row as u32 != row - && language.config.indent_nodes.contains(node.kind()) - { - let parent_ends_at_row = node.end_position().row as u32 == row; - indent_parent = - Some((node.start_position().row as u32, parent_ends_at_row)); + } else if node.start_position() <= row_start && cursor.goto_first_child() { + if language.config.indent_nodes.contains(node.kind()) { + stack.push(node); } + } else { + let mut indented = false; + for ancestor in stack.iter().rev() { + let ancestor_start_row = ancestor.start_position().row as u32; + if ancestor_start_row < row { + let ancestor_indent = if ancestor_start_row < rows.start { + self.indent_for_row(ancestor_start_row).0 + } else { + indents[(ancestor_start_row - rows.start) as usize] + }; - if !cursor.goto_first_child() { - break; + if ancestor.end_position().row as u32 == row { + indents.push(ancestor_indent); + } else { + indents.push(ancestor_indent + language.config.indent); + } + + indented = true; + break; + } } - } - } - indent = language.config.indent; - } + if !indented { + let mut indent = 0; + for prev_row in (0..row).rev() { + if prev_row < rows.start { + let (prev_indent, is_whitespace) = self.indent_for_row(prev_row); + if prev_indent != 0 || !is_whitespace { + indent = prev_indent; + break; + } + } else { + indent = indents[(prev_row - rows.start) as usize]; + break; + } + } + indents.push(indent); + } - if let Some((parent_row, parent_ends_at_row)) = indent_parent { - let (parent_indent, _) = self.indent_for_row(parent_row); - if parent_ends_at_row { - parent_indent - } else { - parent_indent + indent - } - } else { - for prev_row in (0..row).rev() { - let (prev_indent, is_whitespace) = self.indent_for_row(prev_row); - if prev_indent != 0 || !is_whitespace { - return prev_indent; + row += 1; } } - 0 + } else { + panic!() } + + indents } fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { @@ -3712,15 +3736,15 @@ mod tests { let text = " fn a() {} - fn b() { - } + fn b() { + } fn c() { let badly_indented_line; } - struct D { + struct D { // we deliberately don't auto-indent structs for this example x: 1, @@ -3732,22 +3756,11 @@ mod tests { buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; buffer.read_with(&ctx, |buf, _| { - assert_eq!(buf.autoindent_for_row(6), 3); - assert_eq!(buf.autoindent_for_row(7), 3); - - // Don't autoindent rows that start or end on the same row as the indent node. - assert_eq!(buf.autoindent_for_row(0), 0); - assert_eq!(buf.autoindent_for_row(2), 0); - assert_eq!(buf.autoindent_for_row(3), 0); - assert_eq!(buf.autoindent_for_row(4), 0); - assert_eq!(buf.autoindent_for_row(8), 0); - - // We didn't find any matching indent node in the language for the struct definition. - // Don't autoindent the first line inside of the struct. Instead, align the second and - // third line to the first non-whitespace line preceding them. - assert_eq!(buf.autoindent_for_row(11), 0); - assert_eq!(buf.autoindent_for_row(12), 4); - assert_eq!(buf.autoindent_for_row(13), 4); + assert_eq!( + buf.autoindent_for_rows(0..buf.max_point().row + 1), + vec![0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0] + ); + todo!("write assertions to test how indents work with different subset of rows"); }); } From 3c0673852b000b8acc780ebf8695d9d4ddfb5ccc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 May 2021 12:01:09 -0700 Subject: [PATCH 38/56] :lipstick: Add Buffer::row_count --- zed/src/editor/buffer/mod.rs | 7 +++++-- zed/src/editor/buffer_view.rs | 5 +---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 89229dc5f70fc1b466b9780487817c02840be23e..6ac8b2c2c0bf65456ba4bbc82eb6dd4d4873f5d8 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -692,7 +692,6 @@ impl Buffer { fn autoindent_for_rows(&self, rows: Range) -> Vec { let mut indents = Vec::new(); - let mut indent = 2; if let Some((language, syntax_tree)) = self.language.as_ref().zip(self.syntax_tree()) { let mut stack = Vec::new(); let mut cursor = syntax_tree.walk(); @@ -855,6 +854,10 @@ impl Buffer { self.visible_text.max_point() } + pub fn row_count(&self) -> u32 { + self.max_point().row + 1 + } + pub fn text(&self) -> String { self.text_for_range(0..self.len()).collect() } @@ -3757,7 +3760,7 @@ mod tests { buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; buffer.read_with(&ctx, |buf, _| { assert_eq!( - buf.autoindent_for_rows(0..buf.max_point().row + 1), + buf.autoindent_for_rows(0..buf.row_count()), vec![0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0] ); todo!("write assertions to test how indents work with different subset of rows"); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 15bc94feecc9a0b6aff62e3f046ed8f0a6e64544..e2516d21137a915929be516b08eef9b05257e2ab 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2074,10 +2074,7 @@ impl BufferView { let font_size = settings.buffer_font_size; let font_id = font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?; - let digit_count = ((self.buffer.read(app).max_point().row + 1) as f32) - .log10() - .floor() as usize - + 1; + let digit_count = (self.buffer.read(app).row_count() as f32).log10().floor() as usize + 1; Ok(layout_cache .layout_str( From 7bea3fd2fe1d8e3cf6758c4799babbbb560a57b4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 26 May 2021 17:23:53 -0700 Subject: [PATCH 39/56] Start on a query-based autoindent system Co-Authored-By: Nathan Sobo --- zed/languages/rust/indent.scm | 7 + zed/src/editor/buffer/mod.rs | 266 +++++++++++++++++++----------- zed/src/editor/display_map/mod.rs | 5 +- zed/src/language.rs | 21 ++- 4 files changed, 195 insertions(+), 104 deletions(-) create mode 100644 zed/languages/rust/indent.scm diff --git a/zed/languages/rust/indent.scm b/zed/languages/rust/indent.scm new file mode 100644 index 0000000000000000000000000000000000000000..709897be4d71709dd40685fa38ab5f675e100d5e --- /dev/null +++ b/zed/languages/rust/indent.scm @@ -0,0 +1,7 @@ +(where_clause) @indent + +(field_expression) @indent + +(_ "(" ")" @outdent) @indent +(_ "[" "]" @outdent) @indent +(_ "{" "}" @outdent) @indent diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 6ac8b2c2c0bf65456ba4bbc82eb6dd4d4873f5d8..9396cb59ba5aba71f67cb36d735e2e082bb2d10f 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -516,12 +516,11 @@ impl Buffer { } pub fn snapshot(&self) -> Snapshot { - let mut cursors = QUERY_CURSORS.lock(); Snapshot { text: self.visible_text.clone(), tree: self.syntax_tree(), language: self.language.clone(), - query_cursor: Some(cursors.pop().unwrap_or_else(|| QueryCursor::new())), + query_cursor: Some(acquire_query_cursor()), } } @@ -691,73 +690,99 @@ impl Buffer { } fn autoindent_for_rows(&self, rows: Range) -> Vec { - let mut indents = Vec::new(); - if let Some((language, syntax_tree)) = self.language.as_ref().zip(self.syntax_tree()) { - let mut stack = Vec::new(); - let mut cursor = syntax_tree.walk(); - let mut row = rows.start; - while row < rows.end { - let node = cursor.node(); - let row_start = Point::new(row, 0).into(); - - if node.end_position() <= row_start { - if !cursor.goto_next_sibling() { - if stack.last() == Some(&node) { - stack.pop(); - } + // Find the indentation level of the previous non-whitespace row. + let mut prev_row = rows.start; + let prev_indent = loop { + if prev_row == 0 { + break 0; + } + prev_row -= 1; + let (indent, is_whitespace) = self.indent_for_row(prev_row); + if !is_whitespace { + break indent; + } + }; - if !cursor.goto_parent() { - break; - } - } - } else if node.start_position() <= row_start && cursor.goto_first_child() { - if language.config.indent_nodes.contains(node.kind()) { - stack.push(node); - } - } else { - let mut indented = false; - for ancestor in stack.iter().rev() { - let ancestor_start_row = ancestor.start_position().row as u32; - if ancestor_start_row < row { - let ancestor_indent = if ancestor_start_row < rows.start { - self.indent_for_row(ancestor_start_row).0 - } else { - indents[(ancestor_start_row - rows.start) as usize] - }; + let (language, syntax_tree) = match self.language.as_ref().zip(self.syntax_tree()) { + Some(e) => e, + None => return vec![prev_indent; rows.len()], + }; - if ancestor.end_position().row as u32 == row { - indents.push(ancestor_indent); - } else { - indents.push(ancestor_indent + language.config.indent); - } + // Find the capture indices in the language's indent query that represent increased + // and decreased indentation. + let mut indent_capture_ix = u32::MAX; + let mut outdent_capture_ix = u32::MAX; + for (ix, name) in language.indent_query.capture_names().iter().enumerate() { + match name.as_ref() { + "indent" => indent_capture_ix = ix as u32, + "outdent" => outdent_capture_ix = ix as u32, + _ => continue, + } + } - indented = true; - break; - } - } + let start_row = rows.start as usize; + let mut indents = vec![prev_indent; rows.len()]; - if !indented { - let mut indent = 0; - for prev_row in (0..row).rev() { - if prev_row < rows.start { - let (prev_indent, is_whitespace) = self.indent_for_row(prev_row); - if prev_indent != 0 || !is_whitespace { - indent = prev_indent; - break; - } - } else { - indent = indents[(prev_row - rows.start) as usize]; - break; - } - } - indents.push(indent); + // Find all of the indent and outdent nodes in the given row range. + let mut cursor = acquire_query_cursor(); + cursor.set_point_range( + Point::new(prev_row, 0).into(), + Point::new(rows.end + 1, 0).into(), + ); + for mat in cursor.matches( + &language.indent_query, + syntax_tree.root_node(), + TextProvider(&self.visible_text), + ) { + for capture in mat.captures { + if capture.index == indent_capture_ix { + let node_start_row = capture.node.start_position().row; + let node_end_row = capture.node.end_position().row; + let start_ix = (node_start_row + 1).saturating_sub(start_row); + let end_ix = (node_end_row + 1).saturating_sub(start_row); + for ix in start_ix..end_ix { + indents[ix] += language.config.indent; } - - row += 1; } } - } else { - panic!() + for capture in mat.captures { + if capture.index == outdent_capture_ix { + let node_start_row = capture.node.start_position().row; + let node_end_row = capture.node.end_position().row; + let start_ix = node_start_row.saturating_sub(start_row); + let end_ix = (node_end_row + 1).saturating_sub(start_row); + for ix in start_ix..end_ix { + indents[ix] = indents[ix].saturating_sub(language.config.indent); + } + } + } + } + + // Post-process indents to fix doubly-indented lines. + struct Indent { + initial: usize, + adjusted: usize, + } + let mut indent_stack = vec![Indent { + initial: prev_indent, + adjusted: prev_indent, + }]; + for indent in indents.iter_mut() { + while *indent < indent_stack.last().unwrap().initial { + indent_stack.pop(); + } + + let cur_indent = indent_stack.last().unwrap(); + if *indent > cur_indent.initial { + let adjusted_indent = cur_indent.adjusted + language.config.indent; + indent_stack.push(Indent { + initial: *indent, + adjusted: adjusted_indent, + }); + *indent = adjusted_indent; + } else { + *indent = cur_indent.adjusted; + } } indents @@ -2201,10 +2226,21 @@ impl Snapshot { impl Drop for Snapshot { fn drop(&mut self) { - QUERY_CURSORS.lock().push(self.query_cursor.take().unwrap()); + release_query_cursor(self.query_cursor.take().unwrap()); } } +fn acquire_query_cursor() -> QueryCursor { + QUERY_CURSORS + .lock() + .pop() + .unwrap_or_else(|| QueryCursor::new()) +} + +fn release_query_cursor(cursor: QueryCursor) { + QUERY_CURSORS.lock().push(cursor) +} + struct RopeBuilder<'a> { old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>, @@ -2731,7 +2767,6 @@ mod tests { cell::RefCell, cmp::Ordering, fs, - iter::FromIterator as _, rc::Rc, sync::atomic::{self, AtomicUsize}, }; @@ -3722,49 +3757,90 @@ mod tests { } #[gpui::test] - async fn test_indent(mut ctx: gpui::TestAppContext) { + async fn test_autoindent(mut ctx: gpui::TestAppContext) { let grammar = tree_sitter_rust::language(); let lang = Arc::new(Language { config: LanguageConfig { - indent: 3, - indent_nodes: std::collections::HashSet::from_iter(vec!["block".to_string()]), + indent: 4, ..Default::default() }, grammar: grammar.clone(), highlight_query: tree_sitter::Query::new(grammar, "").unwrap(), + indent_query: tree_sitter::Query::new( + grammar, + r#" + (block "}" @outdent) @indent + (_ ")" @outdent) @indent + (where_clause) @indent + "#, + ) + .unwrap(), theme_mapping: Default::default(), }); - let buffer = ctx.add_model(|ctx| { - let text = " - fn a() {} - - fn b() { - } - - fn c() { - let badly_indented_line; - - } - - struct D { // we deliberately don't auto-indent structs for this example - x: 1, - + let examples = vec![ + " + fn a() { + b( + c, + d + ) + e(|f| { + g(); + h(|| { + i(); + }) + j(); + }); + k(); + } + " + .unindent(), + " + fn a( + d: e + ) -> D + where + B: E, + C: F + { + + } + " + .unindent(), + ]; + for (example_ix, text) in examples.into_iter().enumerate() { + let buffer = ctx.add_model(|ctx| { + Buffer::from_history(0, History::new(text.into()), None, Some(lang.clone()), ctx) + }); + buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; + + buffer.read_with(&ctx, |buffer, _| { + let row_range = 0..buffer.row_count(); + let current_indents = row_range + .clone() + .map(|row| buffer.indent_for_row(row).0) + .collect::>(); + let autoindents = buffer.autoindent_for_rows(row_range); + assert_eq!( + autoindents.len(), + current_indents.len(), + "wrong number of autoindents returned for example {}", + example_ix + ); + for (row, indent) in autoindents.into_iter().enumerate() { + assert_eq!( + indent, + current_indents[row], + "wrong autoindent for example {}, row {}, line {:?}", + example_ix, + row, + buffer.text().split('\n').skip(row).next().unwrap(), + ); } - " - .unindent(); - Buffer::from_history(0, History::new(text.into()), None, Some(lang), ctx) - }); - - buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; - buffer.read_with(&ctx, |buf, _| { - assert_eq!( - buf.autoindent_for_rows(0..buf.row_count()), - vec![0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0] - ); - todo!("write assertions to test how indents work with different subset of rows"); - }); + }); + } } impl Buffer { diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index de8a6b3c0a7ee5cef8304a846c1456c3732e7ef4..3dd899220bcf2bef1b230c990cc4979dab4627ed 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -509,7 +509,7 @@ mod tests { fn inner() {} }"# .unindent(); - let query = tree_sitter::Query::new( + let highlight_query = tree_sitter::Query::new( grammar, r#" (mod_item name: (identifier) body: _ @mod.body) @@ -530,7 +530,8 @@ mod tests { ..Default::default() }, grammar: grammar.clone(), - highlight_query: query, + highlight_query, + indent_query: tree_sitter::Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }); lang.set_theme(&theme); diff --git a/zed/src/language.rs b/zed/src/language.rs index a52bb51ae95317b8bfe32ce7cd340452fa357924..123699657aec077938eb1465672326e89c2af884 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -2,7 +2,7 @@ use crate::settings::{Theme, ThemeMap}; use parking_lot::Mutex; use rust_embed::RustEmbed; use serde::Deserialize; -use std::{collections::HashSet, path::Path, str, sync::Arc}; +use std::{path::Path, str, sync::Arc}; use tree_sitter::{Language as Grammar, Query}; pub use tree_sitter::{Parser, Tree}; @@ -14,7 +14,6 @@ pub struct LanguageDir; pub struct LanguageConfig { pub name: String, pub indent: usize, - pub indent_nodes: HashSet, pub path_suffixes: Vec, } @@ -22,6 +21,7 @@ pub struct Language { pub config: LanguageConfig, pub grammar: Grammar, pub highlight_query: Query, + pub indent_query: Query, pub theme_mapping: Mutex, } @@ -46,11 +46,8 @@ impl LanguageRegistry { let rust_language = Language { config: rust_config, grammar, - highlight_query: Query::new( - grammar, - str::from_utf8(LanguageDir::get("rust/highlights.scm").unwrap().as_ref()).unwrap(), - ) - .unwrap(), + highlight_query: Self::load_query(grammar, "rust/highlights.scm"), + indent_query: Self::load_query(grammar, "rust/indents.scm"), theme_mapping: Mutex::new(ThemeMap::default()), }; @@ -78,6 +75,14 @@ impl LanguageRegistry { .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) }) } + + fn load_query(grammar: tree_sitter::Language, path: &str) -> Query { + Query::new( + grammar, + str::from_utf8(LanguageDir::get(path).unwrap().as_ref()).unwrap(), + ) + .unwrap() + } } #[cfg(test)] @@ -97,6 +102,7 @@ mod tests { }, grammar, highlight_query: Query::new(grammar, "").unwrap(), + indent_query: Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }), Arc::new(Language { @@ -107,6 +113,7 @@ mod tests { }, grammar, highlight_query: Query::new(grammar, "").unwrap(), + indent_query: Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }), ], From 1cd04bd0d1a731d76eb175785feea1f17128224a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 May 2021 11:20:19 +0200 Subject: [PATCH 40/56] Rename `indent.scm` to `indents.scm` --- zed/languages/rust/{indent.scm => indents.scm} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename zed/languages/rust/{indent.scm => indents.scm} (100%) diff --git a/zed/languages/rust/indent.scm b/zed/languages/rust/indents.scm similarity index 100% rename from zed/languages/rust/indent.scm rename to zed/languages/rust/indents.scm From 362b369448a5bf26c84a9261a96c98c643618aa0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 May 2021 18:22:33 +0200 Subject: [PATCH 41/56] WIP: Print autoindents on tab --- zed/src/editor/buffer_view.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index e2516d21137a915929be516b08eef9b05257e2ab..b741f271cc512b5090fc9e5c2d2bbb98eecd3d4a 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -41,7 +41,7 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("delete", "buffer:delete", Some("BufferView")), Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), - Binding::new("tab", "buffer:insert", Some("BufferView")).with_arg("\t".to_string()), + Binding::new("tab", "buffer:print_autoindents", Some("BufferView")), Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), Binding::new( "alt-backspace", @@ -177,6 +177,7 @@ pub fn init(app: &mut MutableAppContext) { ), ]); + app.add_action("buffer:print_autoindents", BufferView::print_autoindents); app.add_action("buffer:scroll", BufferView::scroll); app.add_action("buffer:select", BufferView::select); app.add_action("buffer:cancel", BufferView::cancel); @@ -638,6 +639,11 @@ impl BufferView { Ok(()) } + pub fn print_autoindents(&mut self, _: &(), ctx: &mut ViewContext) { + let buf = self.buffer.read(ctx); + dbg!(buf.autoindent_for_rows(0..buf.row_count())); + } + pub fn insert(&mut self, text: &String, ctx: &mut ViewContext) { let mut old_selections = SmallVec::<[_; 32]>::new(); { From c42303304296ffec635825ca9b45395f64784ca6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 27 May 2021 18:23:32 +0200 Subject: [PATCH 42/56] Fix panic in `autoindent_for_rows` caused by overshooting row range --- zed/src/editor/buffer/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 9396cb59ba5aba71f67cb36d735e2e082bb2d10f..82fe359689068a31c6f733c20e8fd3f8fcb71bba 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -740,7 +740,7 @@ impl Buffer { let node_end_row = capture.node.end_position().row; let start_ix = (node_start_row + 1).saturating_sub(start_row); let end_ix = (node_end_row + 1).saturating_sub(start_row); - for ix in start_ix..end_ix { + for ix in start_ix..cmp::min(end_ix, indents.len()) { indents[ix] += language.config.indent; } } @@ -751,7 +751,7 @@ impl Buffer { let node_end_row = capture.node.end_position().row; let start_ix = node_start_row.saturating_sub(start_row); let end_ix = (node_end_row + 1).saturating_sub(start_row); - for ix in start_ix..end_ix { + for ix in start_ix..cmp::min(end_ix, indents.len()) { indents[ix] = indents[ix].saturating_sub(language.config.indent); } } From fc8758c6193f9f81e5f0279c53ee09c139ce8873 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 27 May 2021 09:37:17 -0700 Subject: [PATCH 43/56] Reset QueryCursors before putting them back in the pool Co-Authored-By: Antonio Scandurra --- zed/src/editor/buffer/mod.rs | 6 ++++-- zed/src/editor/buffer/point.rs | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 82fe359689068a31c6f733c20e8fd3f8fcb71bba..826e9b07eb36601f23313948b58c682c433c927a 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -689,7 +689,7 @@ impl Buffer { (indent, is_whitespace) } - fn autoindent_for_rows(&self, rows: Range) -> Vec { + pub fn autoindent_for_rows(&self, rows: Range) -> Vec { // Find the indentation level of the previous non-whitespace row. let mut prev_row = rows.start; let prev_indent = loop { @@ -2237,7 +2237,9 @@ fn acquire_query_cursor() -> QueryCursor { .unwrap_or_else(|| QueryCursor::new()) } -fn release_query_cursor(cursor: QueryCursor) { +fn release_query_cursor(mut cursor: QueryCursor) { + cursor.set_byte_range(0, usize::MAX); + cursor.set_point_range(Point::zero().into(), Point::MAX.into()); QUERY_CURSORS.lock().push(cursor) } diff --git a/zed/src/editor/buffer/point.rs b/zed/src/editor/buffer/point.rs index f1fb81ef105ad4bd8c68a8249713ad1e389b3450..63deb052f61067b9a444b25dd724eb530a5b6a1c 100644 --- a/zed/src/editor/buffer/point.rs +++ b/zed/src/editor/buffer/point.rs @@ -10,6 +10,11 @@ pub struct Point { } impl Point { + pub const MAX: Self = Self { + row: u32::MAX, + column: u32::MAX, + }; + pub fn new(row: u32, column: u32) -> Self { Point { row, column } } From 79878f20ccfac68bcd264af515ee10cc46e8f0db Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 27 May 2021 15:05:19 -0700 Subject: [PATCH 44/56] WIP - new approach to autoindent - Indent one row at a time. - Consider only the innermost match. --- zed/languages/rust/config.toml | 10 ++- zed/languages/rust/indents.scm | 11 +-- zed/src/editor/buffer/mod.rs | 139 ++++++++++++++------------------- zed/src/editor/buffer_view.rs | 5 +- 4 files changed, 79 insertions(+), 86 deletions(-) diff --git a/zed/languages/rust/config.toml b/zed/languages/rust/config.toml index 66e21d4dfa978510073acdbbdf08408ce619d73b..67dda1b7d3c826b5aed62dbe329c34b2d33a2fb4 100644 --- a/zed/languages/rust/config.toml +++ b/zed/languages/rust/config.toml @@ -1,4 +1,12 @@ name = "Rust" path_suffixes = ["rs"] indent = 4 -indent_nodes = ["declaration_list", "block"] + +idents = [ + { inside = "let_declaration" }, + { inside = "field_declaration" }, + { start = "where_clause", end = ["block", "field_declaration_list"] }, + { after = "{", before = "}" }, + { start = "(", end = ")" }, + { start = "[", end = "]" }, +] diff --git a/zed/languages/rust/indents.scm b/zed/languages/rust/indents.scm index 709897be4d71709dd40685fa38ab5f675e100d5e..f9791c007c23a628164a23f0fd4a8ac4397d2d3c 100644 --- a/zed/languages/rust/indents.scm +++ b/zed/languages/rust/indents.scm @@ -1,7 +1,8 @@ -(where_clause) @indent +(field_expression) @inside +(let_declaration) @inside -(field_expression) @indent +((_ . "where" @after) _ @until) -(_ "(" ")" @outdent) @indent -(_ "[" "]" @outdent) @indent -(_ "{" "}" @outdent) @indent +(_ "{" @after "}" @until) +(_ "[" @after "]" @until) +(_ "(" @after ")" @until) \ No newline at end of file diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 826e9b07eb36601f23313948b58c682c433c927a..ec47c6abe14de770eb207d84005545892aa7c8c1 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -654,6 +654,7 @@ impl Buffer { } fn parse_text(text: &Rope, old_tree: Option, language: &Language) -> Tree { + let t0 = std::time::Instant::now(); PARSER.with(|parser| { let mut parser = parser.borrow_mut(); parser @@ -669,6 +670,7 @@ impl Buffer { old_tree.as_ref(), ) .unwrap(); + eprintln!("parsed in {:?}", t0.elapsed()); tree }) } @@ -689,9 +691,9 @@ impl Buffer { (indent, is_whitespace) } - pub fn autoindent_for_rows(&self, rows: Range) -> Vec { + pub fn autoindent_for_row(&self, row: u32) -> usize { // Find the indentation level of the previous non-whitespace row. - let mut prev_row = rows.start; + let mut prev_row = row; let prev_indent = loop { if prev_row == 0 { break 0; @@ -705,87 +707,73 @@ impl Buffer { let (language, syntax_tree) = match self.language.as_ref().zip(self.syntax_tree()) { Some(e) => e, - None => return vec![prev_indent; rows.len()], + None => return prev_indent, }; - // Find the capture indices in the language's indent query that represent increased - // and decreased indentation. - let mut indent_capture_ix = u32::MAX; - let mut outdent_capture_ix = u32::MAX; + // Find the capture indices in the language's indent query. + let mut indent_inside_capture_ix = u32::MAX; + let mut indent_after_capture_ix = u32::MAX; + let mut indent_until_capture_ix = u32::MAX; for (ix, name) in language.indent_query.capture_names().iter().enumerate() { - match name.as_ref() { - "indent" => indent_capture_ix = ix as u32, - "outdent" => outdent_capture_ix = ix as u32, + *match name.as_ref() { + "inside" => &mut indent_inside_capture_ix, + "after" => &mut indent_after_capture_ix, + "until" => &mut indent_until_capture_ix, _ => continue, - } + } = ix as u32; } - let start_row = rows.start as usize; - let mut indents = vec![prev_indent; rows.len()]; + eprintln!("autoindent for row: {}", row); - // Find all of the indent and outdent nodes in the given row range. + // Find all of the indentation nodes intersecting the previous and current row. + let mut does_start_indent = false; + let mut start_row_of_most_recent_ending_indent = None; let mut cursor = acquire_query_cursor(); cursor.set_point_range( Point::new(prev_row, 0).into(), - Point::new(rows.end + 1, 0).into(), + Point::new(row + 1, 0).into(), ); for mat in cursor.matches( &language.indent_query, syntax_tree.root_node(), TextProvider(&self.visible_text), ) { + let mut row_range = 0..self.row_count(); + eprintln!(" match: {:?}", mat); + for capture in mat.captures { - if capture.index == indent_capture_ix { - let node_start_row = capture.node.start_position().row; - let node_end_row = capture.node.end_position().row; - let start_ix = (node_start_row + 1).saturating_sub(start_row); - let end_ix = (node_end_row + 1).saturating_sub(start_row); - for ix in start_ix..cmp::min(end_ix, indents.len()) { - indents[ix] += language.config.indent; - } - } - } - for capture in mat.captures { - if capture.index == outdent_capture_ix { - let node_start_row = capture.node.start_position().row; - let node_end_row = capture.node.end_position().row; - let start_ix = node_start_row.saturating_sub(start_row); - let end_ix = (node_end_row + 1).saturating_sub(start_row); - for ix in start_ix..cmp::min(end_ix, indents.len()) { - indents[ix] = indents[ix].saturating_sub(language.config.indent); - } + let node_start_row = capture.node.start_position().row as u32; + let node_end_row = capture.node.end_position().row as u32; + if capture.index == indent_inside_capture_ix { + row_range.start = row_range.start.max(node_start_row + 1); + row_range.end = row_range.end.min(node_end_row + 1); + } else if capture.index == indent_after_capture_ix { + row_range.start = row_range.start.max(node_end_row + 1); + } else if capture.index == indent_until_capture_ix { + row_range.end = row_range.end.min(node_start_row); } } - } - // Post-process indents to fix doubly-indented lines. - struct Indent { - initial: usize, - adjusted: usize, - } - let mut indent_stack = vec![Indent { - initial: prev_indent, - adjusted: prev_indent, - }]; - for indent in indents.iter_mut() { - while *indent < indent_stack.last().unwrap().initial { - indent_stack.pop(); - } + eprintln!(" row_range: {:?}", row_range); - let cur_indent = indent_stack.last().unwrap(); - if *indent > cur_indent.initial { - let adjusted_indent = cur_indent.adjusted + language.config.indent; - indent_stack.push(Indent { - initial: *indent, - adjusted: adjusted_indent, - }); - *indent = adjusted_indent; - } else { - *indent = cur_indent.adjusted; + if row_range.contains(&row) { + does_start_indent = true; + } else if !row_range.is_empty() && row_range.end == row { + start_row_of_most_recent_ending_indent = Some( + start_row_of_most_recent_ending_indent + .unwrap_or(0) + .max(row_range.start), + ); } } - indents + if does_start_indent { + prev_indent + language.config.indent + } else if let Some(start_row) = start_row_of_most_recent_ending_indent { + self.indent_for_row(start_row).0 + } else { + prev_indent + } } fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { @@ -3771,9 +3759,11 @@ mod tests { indent_query: tree_sitter::Query::new( grammar, r#" - (block "}" @outdent) @indent - (_ ")" @outdent) @indent - (where_clause) @indent + (field_expression) @inside + (let_declaration) @inside + ((_ . "where" @after) _ @until) + (_ "{" @after "}" @until) + (_ "(" @after ")" @until) "#, ) .unwrap(), @@ -3806,7 +3796,10 @@ mod tests { B: E, C: F { - + a + .b + .c + .d } " .unindent(), @@ -3819,26 +3812,14 @@ mod tests { buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; buffer.read_with(&ctx, |buffer, _| { - let row_range = 0..buffer.row_count(); - let current_indents = row_range - .clone() - .map(|row| buffer.indent_for_row(row).0) - .collect::>(); - let autoindents = buffer.autoindent_for_rows(row_range); - assert_eq!( - autoindents.len(), - current_indents.len(), - "wrong number of autoindents returned for example {}", - example_ix - ); - for (row, indent) in autoindents.into_iter().enumerate() { + for row in 0..buffer.row_count() { assert_eq!( - indent, - current_indents[row], + buffer.autoindent_for_row(row), + buffer.indent_for_row(row).0, "wrong autoindent for example {}, row {}, line {:?}", example_ix, row, - buffer.text().split('\n').skip(row).next().unwrap(), + buffer.text().split('\n').skip(row as usize).next().unwrap(), ); } }); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index b741f271cc512b5090fc9e5c2d2bbb98eecd3d4a..f8425b03697edeb922d2d680a7de18ec71b7bd8e 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -641,7 +641,10 @@ impl BufferView { pub fn print_autoindents(&mut self, _: &(), ctx: &mut ViewContext) { let buf = self.buffer.read(ctx); - dbg!(buf.autoindent_for_rows(0..buf.row_count())); + eprintln!("autoindents:"); + for row in 0..buf.row_count() { + eprintln!(" {}: {}", row, buf.autoindent_for_row(row)); + } } pub fn insert(&mut self, text: &String, ctx: &mut ViewContext) { From 07aeb32ef48ea0d7034aabdba6217aeb5cbf9391 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 10:17:10 +0200 Subject: [PATCH 45/56] Punt on auto-indentation for now --- zed/languages/rust/config.toml | 10 -- zed/src/editor/buffer/mod.rs | 185 ------------------------------ zed/src/editor/buffer_view.rs | 11 +- zed/src/editor/display_map/mod.rs | 1 - zed/src/language.rs | 5 - 5 files changed, 1 insertion(+), 211 deletions(-) diff --git a/zed/languages/rust/config.toml b/zed/languages/rust/config.toml index 67dda1b7d3c826b5aed62dbe329c34b2d33a2fb4..17b54c05ab263278ec819121650a26150ffeed0b 100644 --- a/zed/languages/rust/config.toml +++ b/zed/languages/rust/config.toml @@ -1,12 +1,2 @@ name = "Rust" path_suffixes = ["rs"] -indent = 4 - -idents = [ - { inside = "let_declaration" }, - { inside = "field_declaration" }, - { start = "where_clause", end = ["block", "field_declaration_list"] }, - { after = "{", before = "}" }, - { start = "(", end = ")" }, - { start = "[", end = "]" }, -] diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index ec47c6abe14de770eb207d84005545892aa7c8c1..aa7a9f491372e2a2194bacd57ed59009043c0d7c 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -654,7 +654,6 @@ impl Buffer { } fn parse_text(text: &Rope, old_tree: Option, language: &Language) -> Tree { - let t0 = std::time::Instant::now(); PARSER.with(|parser| { let mut parser = parser.borrow_mut(); parser @@ -670,112 +669,10 @@ impl Buffer { old_tree.as_ref(), ) .unwrap(); - eprintln!("parsed in {:?}", t0.elapsed()); tree }) } - fn indent_for_row(&self, row: u32) -> (usize, bool) { - let mut is_whitespace = true; - let mut indent = 0; - for c in self.chars_at(Point::new(row, 0)) { - match c { - ' ' => indent += 1, - '\n' => break, - _ => { - is_whitespace = false; - break; - } - } - } - (indent, is_whitespace) - } - - pub fn autoindent_for_row(&self, row: u32) -> usize { - // Find the indentation level of the previous non-whitespace row. - let mut prev_row = row; - let prev_indent = loop { - if prev_row == 0 { - break 0; - } - prev_row -= 1; - let (indent, is_whitespace) = self.indent_for_row(prev_row); - if !is_whitespace { - break indent; - } - }; - - let (language, syntax_tree) = match self.language.as_ref().zip(self.syntax_tree()) { - Some(e) => e, - None => return prev_indent, - }; - - // Find the capture indices in the language's indent query. - let mut indent_inside_capture_ix = u32::MAX; - let mut indent_after_capture_ix = u32::MAX; - let mut indent_until_capture_ix = u32::MAX; - for (ix, name) in language.indent_query.capture_names().iter().enumerate() { - *match name.as_ref() { - "inside" => &mut indent_inside_capture_ix, - "after" => &mut indent_after_capture_ix, - "until" => &mut indent_until_capture_ix, - _ => continue, - } = ix as u32; - } - - eprintln!("autoindent for row: {}", row); - - // Find all of the indentation nodes intersecting the previous and current row. - let mut does_start_indent = false; - let mut start_row_of_most_recent_ending_indent = None; - let mut cursor = acquire_query_cursor(); - cursor.set_point_range( - Point::new(prev_row, 0).into(), - Point::new(row + 1, 0).into(), - ); - for mat in cursor.matches( - &language.indent_query, - syntax_tree.root_node(), - TextProvider(&self.visible_text), - ) { - let mut row_range = 0..self.row_count(); - eprintln!(" match: {:?}", mat); - - for capture in mat.captures { - let node_start_row = capture.node.start_position().row as u32; - let node_end_row = capture.node.end_position().row as u32; - if capture.index == indent_inside_capture_ix { - row_range.start = row_range.start.max(node_start_row + 1); - row_range.end = row_range.end.min(node_end_row + 1); - } else if capture.index == indent_after_capture_ix { - row_range.start = row_range.start.max(node_end_row + 1); - } else if capture.index == indent_until_capture_ix { - row_range.end = row_range.end.min(node_start_row); - } - } - - eprintln!(" row_range: {:?}", row_range); - - if row_range.contains(&row) { - does_start_indent = true; - } else if !row_range.is_empty() && row_range.end == row { - start_row_of_most_recent_ending_indent = Some( - start_row_of_most_recent_ending_indent - .unwrap_or(0) - .max(row_range.start), - ); - } - } - - if does_start_indent { - prev_indent + language.config.indent - } else if let Some(start_row) = start_row_of_most_recent_ending_indent { - self.indent_for_row(start_row).0 - } else { - prev_indent - } - } - fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { // TODO: it would be nice to not allocate here. let old_text = self.text(); @@ -2745,7 +2642,6 @@ impl ToPoint for usize { mod tests { use super::*; use crate::{ - language::LanguageConfig, test::{build_app_state, temp_tree}, util::RandomCharIter, worktree::{Worktree, WorktreeHandle}, @@ -2760,7 +2656,6 @@ mod tests { rc::Rc, sync::atomic::{self, AtomicUsize}, }; - use unindent::Unindent as _; #[gpui::test] fn test_edit(ctx: &mut gpui::MutableAppContext) { @@ -3746,86 +3641,6 @@ mod tests { } } - #[gpui::test] - async fn test_autoindent(mut ctx: gpui::TestAppContext) { - let grammar = tree_sitter_rust::language(); - let lang = Arc::new(Language { - config: LanguageConfig { - indent: 4, - ..Default::default() - }, - grammar: grammar.clone(), - highlight_query: tree_sitter::Query::new(grammar, "").unwrap(), - indent_query: tree_sitter::Query::new( - grammar, - r#" - (field_expression) @inside - (let_declaration) @inside - ((_ . "where" @after) _ @until) - (_ "{" @after "}" @until) - (_ "(" @after ")" @until) - "#, - ) - .unwrap(), - theme_mapping: Default::default(), - }); - - let examples = vec![ - " - fn a() { - b( - c, - d - ) - e(|f| { - g(); - h(|| { - i(); - }) - j(); - }); - k(); - } - " - .unindent(), - " - fn a( - d: e - ) -> D - where - B: E, - C: F - { - a - .b - .c - .d - } - " - .unindent(), - ]; - - for (example_ix, text) in examples.into_iter().enumerate() { - let buffer = ctx.add_model(|ctx| { - Buffer::from_history(0, History::new(text.into()), None, Some(lang.clone()), ctx) - }); - buffer.condition(&ctx, |buf, _| !buf.is_parsing()).await; - - buffer.read_with(&ctx, |buffer, _| { - for row in 0..buffer.row_count() { - assert_eq!( - buffer.autoindent_for_row(row), - buffer.indent_for_row(row).0, - "wrong autoindent for example {}, row {}, line {:?}", - example_ix, - row, - buffer.text().split('\n').skip(row as usize).next().unwrap(), - ); - } - }); - } - } - impl Buffer { fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl Rng) -> Range { let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index f8425b03697edeb922d2d680a7de18ec71b7bd8e..e2516d21137a915929be516b08eef9b05257e2ab 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -41,7 +41,7 @@ pub fn init(app: &mut MutableAppContext) { Binding::new("delete", "buffer:delete", Some("BufferView")), Binding::new("ctrl-d", "buffer:delete", Some("BufferView")), Binding::new("enter", "buffer:newline", Some("BufferView")), - Binding::new("tab", "buffer:print_autoindents", Some("BufferView")), + Binding::new("tab", "buffer:insert", Some("BufferView")).with_arg("\t".to_string()), Binding::new("ctrl-shift-K", "buffer:delete_line", Some("BufferView")), Binding::new( "alt-backspace", @@ -177,7 +177,6 @@ pub fn init(app: &mut MutableAppContext) { ), ]); - app.add_action("buffer:print_autoindents", BufferView::print_autoindents); app.add_action("buffer:scroll", BufferView::scroll); app.add_action("buffer:select", BufferView::select); app.add_action("buffer:cancel", BufferView::cancel); @@ -639,14 +638,6 @@ impl BufferView { Ok(()) } - pub fn print_autoindents(&mut self, _: &(), ctx: &mut ViewContext) { - let buf = self.buffer.read(ctx); - eprintln!("autoindents:"); - for row in 0..buf.row_count() { - eprintln!(" {}: {}", row, buf.autoindent_for_row(row)); - } - } - pub fn insert(&mut self, text: &String, ctx: &mut ViewContext) { let mut old_selections = SmallVec::<[_; 32]>::new(); { diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 3dd899220bcf2bef1b230c990cc4979dab4627ed..12abb7fdb75f3fbf0afd1bf34766c286673ea1fa 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -531,7 +531,6 @@ mod tests { }, grammar: grammar.clone(), highlight_query, - indent_query: tree_sitter::Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }); lang.set_theme(&theme); diff --git a/zed/src/language.rs b/zed/src/language.rs index 123699657aec077938eb1465672326e89c2af884..cc5c55e49b2e92cd9d126c468b5381c3c06d7aa2 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -13,7 +13,6 @@ pub struct LanguageDir; #[derive(Default, Deserialize)] pub struct LanguageConfig { pub name: String, - pub indent: usize, pub path_suffixes: Vec, } @@ -21,7 +20,6 @@ pub struct Language { pub config: LanguageConfig, pub grammar: Grammar, pub highlight_query: Query, - pub indent_query: Query, pub theme_mapping: Mutex, } @@ -47,7 +45,6 @@ impl LanguageRegistry { config: rust_config, grammar, highlight_query: Self::load_query(grammar, "rust/highlights.scm"), - indent_query: Self::load_query(grammar, "rust/indents.scm"), theme_mapping: Mutex::new(ThemeMap::default()), }; @@ -102,7 +99,6 @@ mod tests { }, grammar, highlight_query: Query::new(grammar, "").unwrap(), - indent_query: Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }), Arc::new(Language { @@ -113,7 +109,6 @@ mod tests { }, grammar, highlight_query: Query::new(grammar, "").unwrap(), - indent_query: Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }), ], From 6992c19aee80ad79e858cbb504a0ed1a04eab2dd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 11:48:49 +0200 Subject: [PATCH 46/56] Acquire and release `QueryCursor`s via a `QueryCursorHandle` --- zed/src/editor/buffer/mod.rs | 64 ++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index aa7a9f491372e2a2194bacd57ed59009043c0d7c..81272ced2d7e2eb0c8c5099c1241acd406e08cd5 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -30,7 +30,7 @@ use std::{ hash::BuildHasher, iter::{self, Iterator}, mem, - ops::Range, + ops::{Deref, DerefMut, Range}, str, sync::Arc, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, @@ -69,6 +69,42 @@ lazy_static! { static ref QUERY_CURSORS: Mutex> = Default::default(); } +struct QueryCursorHandle(Option); + +impl QueryCursorHandle { + fn new() -> Self { + QueryCursorHandle(Some( + QUERY_CURSORS + .lock() + .pop() + .unwrap_or_else(|| QueryCursor::new()), + )) + } +} + +impl Deref for QueryCursorHandle { + type Target = QueryCursor; + + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } +} + +impl DerefMut for QueryCursorHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } +} + +impl Drop for QueryCursorHandle { + fn drop(&mut self) { + let mut cursor = self.0.take().unwrap(); + cursor.set_byte_range(0, usize::MAX); + cursor.set_point_range(Point::zero().into(), Point::MAX.into()); + QUERY_CURSORS.lock().push(cursor) + } +} + pub struct Buffer { fragments: SumTree, visible_text: Rope, @@ -520,7 +556,7 @@ impl Buffer { text: self.visible_text.clone(), tree: self.syntax_tree(), language: self.language.clone(), - query_cursor: Some(acquire_query_cursor()), + query_cursor: QueryCursorHandle::new(), } } @@ -2046,7 +2082,7 @@ pub struct Snapshot { text: Rope, tree: Option, language: Option>, - query_cursor: Option, + query_cursor: QueryCursorHandle, } impl Snapshot { @@ -2065,8 +2101,7 @@ impl Snapshot { pub fn highlighted_text_for_range(&mut self, range: Range) -> HighlightedChunks { let chunks = self.text.chunks_in_range(range.clone()); if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { - let query_cursor = self.query_cursor.as_mut().unwrap(); - let mut captures = query_cursor.captures( + let mut captures = self.query_cursor.captures( &language.highlight_query, tree.root_node(), TextProvider(&self.text), @@ -2109,25 +2144,6 @@ impl Snapshot { } } -impl Drop for Snapshot { - fn drop(&mut self) { - release_query_cursor(self.query_cursor.take().unwrap()); - } -} - -fn acquire_query_cursor() -> QueryCursor { - QUERY_CURSORS - .lock() - .pop() - .unwrap_or_else(|| QueryCursor::new()) -} - -fn release_query_cursor(mut cursor: QueryCursor) { - cursor.set_byte_range(0, usize::MAX); - cursor.set_point_range(Point::zero().into(), Point::MAX.into()); - QUERY_CURSORS.lock().push(cursor) -} - struct RopeBuilder<'a> { old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>, From 23d1d9141444dce7a2db10c11ba178ec7edc1eca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 13:23:16 +0200 Subject: [PATCH 47/56] Implement `select_larger_syntax_node` for buffer --- zed/src/editor/buffer/mod.rs | 17 +++++++++++++ zed/src/editor/buffer_view.rs | 35 +++++++++++++++++++++++++- zed/src/editor/display_map/fold_map.rs | 12 +++++++++ zed/src/editor/display_map/mod.rs | 4 +++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 81272ced2d7e2eb0c8c5099c1241acd406e08cd5..9a021fb740059f5f1d4b71ae606a10e401e4d2bc 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -709,6 +709,23 @@ impl Buffer { }) } + pub fn range_for_containing_syntax_node( + &self, + range: Range, + ) -> Option> { + if let Some(tree) = self.syntax_tree() { + let root = tree.root_node(); + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut node = root.descendant_for_byte_range(range.start, range.end); + while node.map_or(false, |n| n.byte_range() == range) { + node = node.unwrap().parent(); + } + node.map(|n| n.byte_range()) + } else { + None + } + } + fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { // TODO: it would be nice to not allocate here. let old_text = self.text(); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index e2516d21137a915929be516b08eef9b05257e2ab..1026758ce7e6f6eedee4bbd3966ad01c849d99f9 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -166,6 +166,11 @@ pub fn init(app: &mut MutableAppContext) { "buffer:add_selection_below", Some("BufferView"), ), + Binding::new( + "alt-up", + "buffer:select-larger-syntax-node", + Some("BufferView"), + ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), Binding::new("pagedown", "buffer:page_down", Some("BufferView")), Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")), @@ -270,6 +275,10 @@ pub fn init(app: &mut MutableAppContext) { "buffer:add_selection_below", BufferView::add_selection_below, ); + app.add_action( + "buffer:select-larger-syntax-node", + BufferView::select_larger_syntax_node, + ); app.add_action("buffer:page_up", BufferView::page_up); app.add_action("buffer:page_down", BufferView::page_down); app.add_action("buffer:fold", BufferView::fold); @@ -1659,7 +1668,7 @@ impl BufferView { self.add_selection(false, ctx); } - pub fn add_selection(&mut self, above: bool, ctx: &mut ViewContext) { + fn add_selection(&mut self, above: bool, ctx: &mut ViewContext) { use super::RangeExt; let app = ctx.as_ref(); @@ -1751,6 +1760,30 @@ impl BufferView { } } + pub fn select_larger_syntax_node(&mut self, _: &(), ctx: &mut ViewContext) { + let app = ctx.as_ref(); + let buffer = self.buffer.read(app); + let mut selections = self.selections(app).to_vec(); + for selection in &mut selections { + let mut range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + while let Some(containing_range) = + buffer.range_for_containing_syntax_node(range.clone()) + { + range = containing_range; + if !self.display_map.intersects_fold(range.start, app) + && !self.display_map.intersects_fold(range.end, app) + { + break; + } + } + + selection.start = buffer.anchor_before(range.start); + selection.end = buffer.anchor_before(range.end); + } + + self.update_selections(selections, true, ctx); + } + fn build_columnar_selection( &mut self, row: u32, diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 77887d32889916715507bf58d83e0dece9917634..3f8c8be58045e2f98197921ddde4efcbc675511d 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -192,6 +192,18 @@ impl FoldMap { }) } + pub fn intersects_fold(&self, offset: T, ctx: &AppContext) -> bool + where + T: ToOffset, + { + let buffer = self.buffer.read(ctx); + let offset = offset.to_offset(buffer); + let transforms = self.sync(ctx); + let mut cursor = transforms.cursor::(); + cursor.seek(&offset, SeekBias::Right, &()); + cursor.item().map_or(false, |t| t.display_text.is_some()) + } + pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool { let transforms = self.sync(ctx); let mut cursor = transforms.cursor::(); diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 12abb7fdb75f3fbf0afd1bf34766c286673ea1fa..29241623ebe66b80a564194951ed6b8a4375df0c 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -57,6 +57,10 @@ impl DisplayMap { self.fold_map.unfold(ranges, ctx) } + pub fn intersects_fold(&self, offset: T, ctx: &AppContext) -> bool { + self.fold_map.intersects_fold(offset, ctx) + } + pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool { self.fold_map.is_line_folded(display_row, ctx) } From 8a97e9c57308b710d7610c0546ba798d8de70728 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 13:35:21 +0200 Subject: [PATCH 48/56] Implement `select-smaller-syntax-node` for buffer --- zed/src/editor/buffer_view.rs | 55 ++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 1026758ce7e6f6eedee4bbd3966ad01c849d99f9..22aa5d7476f32771fe41f30ab0319de5aa66f674 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -171,6 +171,11 @@ pub fn init(app: &mut MutableAppContext) { "buffer:select-larger-syntax-node", Some("BufferView"), ), + Binding::new( + "alt-down", + "buffer:select-smaller-syntax-node", + Some("BufferView"), + ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), Binding::new("pagedown", "buffer:page_down", Some("BufferView")), Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")), @@ -279,6 +284,10 @@ pub fn init(app: &mut MutableAppContext) { "buffer:select-larger-syntax-node", BufferView::select_larger_syntax_node, ); + app.add_action( + "buffer:select-smaller-syntax-node", + BufferView::select_smaller_syntax_node, + ); app.add_action("buffer:page_up", BufferView::page_up); app.add_action("buffer:page_down", BufferView::page_down); app.add_action("buffer:fold", BufferView::fold); @@ -309,6 +318,7 @@ pub struct BufferView { pending_selection: Option, next_selection_id: usize, add_selections_state: Option, + select_larger_syntax_node_stack: Vec>, scroll_position: Mutex, autoscroll_requested: Mutex, settings: watch::Receiver, @@ -368,6 +378,7 @@ impl BufferView { pending_selection: None, next_selection_id, add_selections_state: None, + select_larger_syntax_node_stack: Vec::new(), scroll_position: Mutex::new(Vector2F::zero()), autoscroll_requested: Mutex::new(false), settings, @@ -1763,25 +1774,48 @@ impl BufferView { pub fn select_larger_syntax_node(&mut self, _: &(), ctx: &mut ViewContext) { let app = ctx.as_ref(); let buffer = self.buffer.read(app); - let mut selections = self.selections(app).to_vec(); - for selection in &mut selections { - let mut range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + + let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); + let mut selected_larger_node = false; + let old_selections = self.selections(app).to_vec(); + let mut new_selections = Vec::new(); + for selection in &old_selections { + let old_range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + let mut new_range = old_range.clone(); while let Some(containing_range) = - buffer.range_for_containing_syntax_node(range.clone()) + buffer.range_for_containing_syntax_node(new_range.clone()) { - range = containing_range; - if !self.display_map.intersects_fold(range.start, app) - && !self.display_map.intersects_fold(range.end, app) + new_range = containing_range; + if !self.display_map.intersects_fold(new_range.start, app) + && !self.display_map.intersects_fold(new_range.end, app) { break; } } - selection.start = buffer.anchor_before(range.start); - selection.end = buffer.anchor_before(range.end); + selected_larger_node |= new_range != old_range; + new_selections.push(Selection { + id: selection.id, + start: buffer.anchor_before(new_range.start), + end: buffer.anchor_before(new_range.end), + reversed: selection.reversed, + goal: SelectionGoal::None, + }); } - self.update_selections(selections, true, ctx); + if selected_larger_node { + stack.push(old_selections); + self.update_selections(new_selections, true, ctx); + } + self.select_larger_syntax_node_stack = stack; + } + + pub fn select_smaller_syntax_node(&mut self, _: &(), ctx: &mut ViewContext) { + let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); + if let Some(selections) = stack.pop() { + self.update_selections(selections, true, ctx); + } + self.select_larger_syntax_node_stack = stack; } fn build_columnar_selection( @@ -1898,6 +1932,7 @@ impl BufferView { } self.add_selections_state = None; + self.select_larger_syntax_node_stack.clear(); } fn start_transaction(&self, ctx: &mut ViewContext) { From 0ed9e7478514c3a0feb5a2e55cd8a4687fa5c2d3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 13:36:20 +0200 Subject: [PATCH 49/56] :lipstick: --- zed/src/editor/buffer_view.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 22aa5d7476f32771fe41f30ab0319de5aa66f674..68dee5f75c290e183bd6b9cc1d1e3709c05de805 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -168,12 +168,12 @@ pub fn init(app: &mut MutableAppContext) { ), Binding::new( "alt-up", - "buffer:select-larger-syntax-node", + "buffer:select_larger_syntax_node", Some("BufferView"), ), Binding::new( "alt-down", - "buffer:select-smaller-syntax-node", + "buffer:select_smaller_syntax_node", Some("BufferView"), ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), @@ -281,11 +281,11 @@ pub fn init(app: &mut MutableAppContext) { BufferView::add_selection_below, ); app.add_action( - "buffer:select-larger-syntax-node", + "buffer:select_larger_syntax_node", BufferView::select_larger_syntax_node, ); app.add_action( - "buffer:select-smaller-syntax-node", + "buffer:select_smaller_syntax_node", BufferView::select_smaller_syntax_node, ); app.add_action("buffer:page_up", BufferView::page_up); From 36c1d21025d7ed2cc74a971b5ec786ec5b5b1aa7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 14:59:29 +0200 Subject: [PATCH 50/56] Add unit test for `BufferView::select_{larger,smaller}_syntax_node` --- zed/src/editor/buffer/mod.rs | 5 +- zed/src/editor/buffer_view.rs | 151 +++++++++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 8 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 9a021fb740059f5f1d4b71ae606a10e401e4d2bc..d0e082b7cc849c768ab745e60b13a672fdab21f9 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -709,10 +709,7 @@ impl Buffer { }) } - pub fn range_for_containing_syntax_node( - &self, - range: Range, - ) -> Option> { + pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { if let Some(tree) = self.syntax_tree() { let root = tree.root_node(); let range = range.start.to_offset(self)..range.end.to_offset(self); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 68dee5f75c290e183bd6b9cc1d1e3709c05de805..bd51be56f4a61b08614f47cef90c200621d1e045 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1782,9 +1782,7 @@ impl BufferView { for selection in &old_selections { let old_range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); let mut new_range = old_range.clone(); - while let Some(containing_range) = - buffer.range_for_containing_syntax_node(new_range.clone()) - { + while let Some(containing_range) = buffer.range_for_syntax_ancestor(new_range.clone()) { new_range = containing_range; if !self.display_map.intersects_fold(new_range.start, app) && !self.display_map.intersects_fold(new_range.end, app) @@ -2445,7 +2443,12 @@ impl workspace::ItemView for BufferView { #[cfg(test)] mod tests { use super::*; - use crate::{editor::Point, settings, test::sample_text}; + use crate::{ + editor::Point, + settings, + test::{build_app_state, sample_text}, + }; + use buffer::History; use unindent::Unindent; #[gpui::test] @@ -3926,6 +3929,146 @@ mod tests { ); } + #[gpui::test] + async fn test_select_larger_smaller_syntax_node(mut app: gpui::TestAppContext) { + let app_state = app.read(build_app_state); + let lang = app_state.language_registry.select_language("z.rs"); + let text = r#" + use mod1::mod2::{mod3, mod4}; + + fn fn_1(param1: bool, param2: &str) { + let var1 = "text"; + } + "# + .unindent(); + let buffer = app.add_model(|ctx| { + let history = History::new(text.into()); + Buffer::from_history(0, history, None, lang.cloned(), ctx) + }); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, app_state.settings, ctx)); + view.condition(&app, |view, ctx| !view.buffer.read(ctx).is_parsing()) + .await; + + view.update(&mut app, |view, ctx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), + DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), + DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), + ], + ctx, + ) + .unwrap(); + view.select_larger_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27), + DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), + DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21), + ] + ); + + view.update(&mut app, |view, ctx| { + view.select_larger_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), + DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0), + ] + ); + + view.update(&mut app, |view, ctx| { + view.select_larger_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[DisplayPoint::new(0, 0)..DisplayPoint::new(5, 0)] + ); + + // Trying to expand the selected syntax node one more time has no effect. + view.update(&mut app, |view, ctx| { + view.select_larger_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[DisplayPoint::new(0, 0)..DisplayPoint::new(5, 0)] + ); + + view.update(&mut app, |view, ctx| { + view.select_smaller_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), + DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0), + ] + ); + + view.update(&mut app, |view, ctx| { + view.select_smaller_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27), + DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), + DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21), + ] + ); + + view.update(&mut app, |view, ctx| { + view.select_smaller_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), + DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), + DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), + ] + ); + + // Trying to shrink the selected syntax node one more time has no effect. + view.update(&mut app, |view, ctx| { + view.select_smaller_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), + DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), + DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), + ] + ); + + // Ensure that we keep expanding the selection if the larger selection starts or ends within + // a fold. + view.update(&mut app, |view, ctx| { + view.fold_ranges( + vec![ + Point::new(0, 21)..Point::new(0, 24), + Point::new(3, 20)..Point::new(3, 22), + ], + ctx, + ); + view.select_larger_syntax_node(&(), ctx); + }); + assert_eq!( + view.read_with(&app, |view, ctx| view.selection_ranges(ctx)), + &[ + DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), + DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), + DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23), + ] + ); + } + impl BufferView { fn selection_ranges(&self, app: &AppContext) -> Vec> { self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app) From 0b51e99c6e8ef002b0f7bd6dbb7d1af5c75135e8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 15:11:54 +0200 Subject: [PATCH 51/56] Delete stale `indents.scm` file --- zed/languages/rust/indents.scm | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 zed/languages/rust/indents.scm diff --git a/zed/languages/rust/indents.scm b/zed/languages/rust/indents.scm deleted file mode 100644 index f9791c007c23a628164a23f0fd4a8ac4397d2d3c..0000000000000000000000000000000000000000 --- a/zed/languages/rust/indents.scm +++ /dev/null @@ -1,8 +0,0 @@ -(field_expression) @inside -(let_declaration) @inside - -((_ . "where" @after) _ @until) - -(_ "{" @after "}" @until) -(_ "[" @after "]" @until) -(_ "(" @after ")" @until) \ No newline at end of file From bf8eee26f81ddecc8eaa3df670f6ee8e7de96629 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 19:35:04 +0200 Subject: [PATCH 52/56] Add `Buffer::enclosing_bracket_ranges` Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- zed/languages/rust/config.toml | 6 ++ zed/src/editor/buffer/mod.rs | 110 ++++++++++++++++++++++++++++++--- zed/src/language.rs | 7 +++ 3 files changed, 116 insertions(+), 7 deletions(-) diff --git a/zed/languages/rust/config.toml b/zed/languages/rust/config.toml index 17b54c05ab263278ec819121650a26150ffeed0b..4cde06f6f80fb035ce02e0f6d7e88cb657e88336 100644 --- a/zed/languages/rust/config.toml +++ b/zed/languages/rust/config.toml @@ -1,2 +1,8 @@ name = "Rust" path_suffixes = ["rs"] +bracket_pairs = [ + { start = "{", end = "}" }, + { start = "[", end = "]" }, + { start = "(", end = ")" }, + { start = "<", end = ">" }, +] diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index d0e082b7cc849c768ab745e60b13a672fdab21f9..31f8536c5114f1f0f27ca4386e77bb16f59aae3a 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -723,6 +723,47 @@ impl Buffer { } } + pub fn enclosing_bracket_ranges( + &self, + range: Range, + ) -> Option<(Range, Range)> { + let mut bracket_ranges = None; + if let Some((lang, tree)) = self.language.as_ref().zip(self.syntax_tree()) { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut cursor = tree.root_node().walk(); + 'outer: loop { + let node = cursor.node(); + if node.child_count() >= 2 { + if let Some((first_child, last_child)) = + node.child(0).zip(node.child(node.child_count() - 1)) + { + for pair in &lang.config.bracket_pairs { + if pair.start == first_child.kind() && pair.end == last_child.kind() { + bracket_ranges = + Some((first_child.byte_range(), last_child.byte_range())); + } + } + } + } + + if !cursor.goto_first_child() { + break; + } + + while cursor.node().end_byte() < range.end { + if !cursor.goto_next_sibling() { + break 'outer; + } + } + + if cursor.node().start_byte() > range.start { + break; + } + } + } + bracket_ranges + } + fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { // TODO: it would be nice to not allocate here. let old_text = self.text(); @@ -3531,16 +3572,12 @@ mod tests { #[gpui::test] async fn test_reparse(mut ctx: gpui::TestAppContext) { let app_state = ctx.read(build_app_state); - let rust_lang = app_state - .language_registry - .select_language("test.rs") - .cloned(); + let rust_lang = app_state.language_registry.select_language("test.rs"); assert!(rust_lang.is_some()); let buffer = ctx.add_model(|ctx| { - let text = "fn a() {}"; - - let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx); + let text = "fn a() {}".into(); + let buffer = Buffer::from_history(0, History::new(text), None, rust_lang.cloned(), ctx); assert!(buffer.is_parsing()); assert!(buffer.syntax_tree().is_none()); buffer @@ -3671,6 +3708,54 @@ mod tests { } } + #[gpui::test] + async fn test_enclosing_bracket_ranges(mut ctx: gpui::TestAppContext) { + use unindent::Unindent as _; + + let app_state = ctx.read(build_app_state); + let rust_lang = app_state.language_registry.select_language("test.rs"); + assert!(rust_lang.is_some()); + + let buffer = ctx.add_model(|ctx| { + let text = " + mod x { + mod y { + + } + } + " + .unindent() + .into(); + Buffer::from_history(0, History::new(text), None, rust_lang.cloned(), ctx) + }); + buffer + .condition(&ctx, |buffer, _| !buffer.is_parsing()) + .await; + buffer.read_with(&ctx, |buf, _| { + assert_eq!( + buf.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)), + Some(( + Point::new(0, 6)..Point::new(0, 7), + Point::new(4, 0)..Point::new(4, 1) + )) + ); + assert_eq!( + buf.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)), + Some(( + Point::new(1, 10)..Point::new(1, 11), + Point::new(3, 4)..Point::new(3, 5) + )) + ); + assert_eq!( + buf.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)), + Some(( + Point::new(1, 10)..Point::new(1, 11), + Point::new(3, 4)..Point::new(3, 5) + )) + ); + }); + } + impl Buffer { fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl Rng) -> Range { let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); @@ -3817,6 +3902,17 @@ mod tests { .keys() .map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap())) } + + pub fn enclosing_bracket_point_ranges( + &self, + range: Range, + ) -> Option<(Range, Range)> { + self.enclosing_bracket_ranges(range).map(|(start, end)| { + let point_start = start.start.to_point(self)..start.end.to_point(self); + let point_end = end.start.to_point(self)..end.end.to_point(self); + (point_start, point_end) + }) + } } impl Operation { diff --git a/zed/src/language.rs b/zed/src/language.rs index cc5c55e49b2e92cd9d126c468b5381c3c06d7aa2..60576a374d5087682d1ad000cbf99f04c7f44c2f 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -14,6 +14,13 @@ pub struct LanguageDir; pub struct LanguageConfig { pub name: String, pub path_suffixes: Vec, + pub bracket_pairs: Vec, +} + +#[derive(Deserialize)] +pub struct BracketPair { + pub start: String, + pub end: String, } pub struct Language { From 276991fb5f6cfaa7989fe4eab1f463fc3ada1dfa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 28 May 2021 19:41:13 +0200 Subject: [PATCH 53/56] WIP --- zed/src/editor/buffer_view.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index bd51be56f4a61b08614f47cef90c200621d1e045..792d009fcc97f119ff418fdf63017cf316402a5f 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -176,6 +176,11 @@ pub fn init(app: &mut MutableAppContext) { "buffer:select_smaller_syntax_node", Some("BufferView"), ), + Binding::new( + "ctrl-m", + "buffer:jump_to_enclosing_bracket", + Some("BufferView"), + ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), Binding::new("pagedown", "buffer:page_down", Some("BufferView")), Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")), @@ -288,6 +293,10 @@ pub fn init(app: &mut MutableAppContext) { "buffer:select_smaller_syntax_node", BufferView::select_smaller_syntax_node, ); + app.add_action( + "buffer:move_to_enclosing_bracket", + BufferView::move_to_enclosing_bracket, + ); app.add_action("buffer:page_up", BufferView::page_up); app.add_action("buffer:page_down", BufferView::page_down); app.add_action("buffer:fold", BufferView::fold); @@ -1816,6 +1825,19 @@ impl BufferView { self.select_larger_syntax_node_stack = stack; } + pub fn move_to_enclosing_bracket(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt as _; + + let buffer = self.buffer.read(ctx.as_ref()); + let mut selections = self.selections(ctx.as_ref()).to_vec(); + for selection in &mut selections { + let range = selection.range(buffer).sorted(); + if let Some((open_range, close_range)) = buffer.enclosing_bracket_ranges(range) {} + } + + self.update_selections(selections, true, ctx); + } + fn build_columnar_selection( &mut self, row: u32, From e9bd872b06efa66fbd003183675b403bad20b81d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 28 May 2021 12:07:30 -0600 Subject: [PATCH 54/56] Implement buffer::move_to_enclosing_bracket --- zed/src/editor/buffer/selection.rs | 14 ++++++++++-- zed/src/editor/buffer_view.rs | 36 +++++++++++++++++++----------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index e150ce0725966f49cdba352f008506eae9ce8913..2064b84cca4d9b57d18509fc431e65fb93933299 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -1,6 +1,6 @@ use crate::{ editor::{ - buffer::{Anchor, Buffer, Point, ToPoint}, + buffer::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _}, display_map::DisplayMap, Bias, DisplayPoint, }, @@ -61,7 +61,7 @@ impl Selection { } } - pub fn range(&self, buffer: &Buffer) -> Range { + pub fn point_range(&self, buffer: &Buffer) -> Range { let start = self.start.to_point(buffer); let end = self.end.to_point(buffer); if self.reversed { @@ -71,6 +71,16 @@ impl Selection { } } + pub fn offset_range(&self, buffer: &Buffer) -> Range { + let start = self.start.to_offset(buffer); + let end = self.end.to_offset(buffer); + if self.reversed { + end..start + } else { + start..end + } + } + pub fn display_range(&self, map: &DisplayMap, app: &AppContext) -> Range { let start = self.start.to_display_point(map, app); let end = self.end.to_display_point(map, app); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 792d009fcc97f119ff418fdf63017cf316402a5f..30c7683e8bed2e50e6cf1faf7198f07b06fdcbc3 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -178,7 +178,7 @@ pub fn init(app: &mut MutableAppContext) { ), Binding::new( "ctrl-m", - "buffer:jump_to_enclosing_bracket", + "buffer:move_to_enclosing_bracket", Some("BufferView"), ), Binding::new("pageup", "buffer:page_up", Some("BufferView")), @@ -724,7 +724,7 @@ impl BufferView { { let buffer = self.buffer.read(ctx); for selection in &mut selections { - let range = selection.range(buffer); + let range = selection.point_range(buffer); if range.start == range.end { let head = selection .head() @@ -751,7 +751,7 @@ impl BufferView { { let buffer = self.buffer.read(ctx); for selection in &mut selections { - let range = selection.range(buffer); + let range = selection.point_range(buffer); if range.start == range.end { let head = selection .head() @@ -930,7 +930,7 @@ impl BufferView { let mut contiguous_selections = Vec::new(); while let Some(selection) = selections.next() { // Accumulate contiguous regions of rows that we want to move. - contiguous_selections.push(selection.range(buffer)); + contiguous_selections.push(selection.point_range(buffer)); let (mut buffer_rows, mut display_rows) = selection.buffer_rows_for_display_rows(false, &self.display_map, app); while let Some(next_selection) = selections.peek() { @@ -939,7 +939,7 @@ impl BufferView { if next_buffer_rows.start <= buffer_rows.end { buffer_rows.end = next_buffer_rows.end; display_rows.end = next_display_rows.end; - contiguous_selections.push(next_selection.range(buffer)); + contiguous_selections.push(next_selection.point_range(buffer)); selections.next().unwrap(); } else { break; @@ -1014,7 +1014,7 @@ impl BufferView { let mut contiguous_selections = Vec::new(); while let Some(selection) = selections.next() { // Accumulate contiguous regions of rows that we want to move. - contiguous_selections.push(selection.range(buffer)); + contiguous_selections.push(selection.point_range(buffer)); let (mut buffer_rows, mut display_rows) = selection.buffer_rows_for_display_rows(false, &self.display_map, app); while let Some(next_selection) = selections.peek() { @@ -1023,7 +1023,7 @@ impl BufferView { if next_buffer_rows.start <= buffer_rows.end { buffer_rows.end = next_buffer_rows.end; display_rows.end = next_display_rows.end; - contiguous_selections.push(next_selection.range(buffer)); + contiguous_selections.push(next_selection.point_range(buffer)); selections.next().unwrap(); } else { break; @@ -1647,7 +1647,7 @@ impl BufferView { let mut to_unfold = Vec::new(); let mut new_selections = Vec::new(); for selection in self.selections(app) { - let range = selection.range(buffer).sorted(); + let range = selection.point_range(buffer).sorted(); if range.start.row != range.end.row { new_selections.push(Selection { id: post_inc(&mut self.next_selection_id), @@ -1826,13 +1826,23 @@ impl BufferView { } pub fn move_to_enclosing_bracket(&mut self, _: &(), ctx: &mut ViewContext) { - use super::RangeExt as _; - let buffer = self.buffer.read(ctx.as_ref()); let mut selections = self.selections(ctx.as_ref()).to_vec(); for selection in &mut selections { - let range = selection.range(buffer).sorted(); - if let Some((open_range, close_range)) = buffer.enclosing_bracket_ranges(range) {} + let selection_range = selection.offset_range(buffer); + if let Some((open_range, close_range)) = + buffer.enclosing_bracket_ranges(selection_range.clone()) + { + let destination = if close_range.contains(&selection_range.start) + && close_range.contains(&selection_range.end) + { + open_range.end + } else { + close_range.start + }; + selection.start = buffer.anchor_before(destination); + selection.end = selection.start.clone(); + } } self.update_selections(selections, true, ctx); @@ -2080,7 +2090,7 @@ impl BufferView { let ranges = self .selections(ctx.as_ref()) .iter() - .map(|s| s.range(buffer).sorted()) + .map(|s| s.point_range(buffer).sorted()) .collect(); self.fold_ranges(ranges, ctx); } From 8b7a314474b7cb51035470afc4c41d5812957f29 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 28 May 2021 13:09:57 -0700 Subject: [PATCH 55/56] Implement bracket matching using queries Co-Authored-By: Nathan Sobo --- Cargo.lock | 2 +- Cargo.toml | 12 +++---- zed/languages/rust/brackets.scm | 6 ++++ zed/src/editor/buffer/mod.rs | 54 ++++++++++++------------------- zed/src/editor/buffer_view.rs | 5 ++- zed/src/editor/display_map/mod.rs | 1 + zed/src/editor/mod.rs | 10 +++++- zed/src/language.rs | 5 ++- 8 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 zed/languages/rust/brackets.scm diff --git a/Cargo.lock b/Cargo.lock index 51681d91dd38e4ff35fa8c45cff9cd7af3a946c5..5387343c44f4a9eda062591dca2278fb4b6b913f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.19.5" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=036aceed574c2c23eee8f0ff90be5a2409e524c1#036aceed574c2c23eee8f0ff90be5a2409e524c1" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=97dfee63257b5e92197399b381aa993514640adf#97dfee63257b5e92197399b381aa993514640adf" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 1b176d520beaa3b5260dd39efafe942c8452abda..46866e31472c3369b07fa27aa8b0c8a3dad8a0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,14 +2,14 @@ members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"] [patch.crates-io] -async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} -tree-sitter = {git = "https://github.com/tree-sitter/tree-sitter", rev = "036aceed574c2c23eee8f0ff90be5a2409e524c1"} +async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "97dfee63257b5e92197399b381aa993514640adf" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 -cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} -cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} -core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} -core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"} +cocoa = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" } +cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" } +core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" } +core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" } [profile.dev] split-debuginfo = "unpacked" diff --git a/zed/languages/rust/brackets.scm b/zed/languages/rust/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..0be534c48cc4b07c577d5f4d8181e1e2b6f1fcfe --- /dev/null +++ b/zed/languages/rust/brackets.scm @@ -0,0 +1,6 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) +("<" @open ">" @close) +("\"" @open "\"" @close) +(closure_parameters "|" @open "|" @close) \ No newline at end of file diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 31f8536c5114f1f0f27ca4386e77bb16f59aae3a..1f064a049dcbc437afe4934f2dadf0369ab9ea45 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -727,41 +727,27 @@ impl Buffer { &self, range: Range, ) -> Option<(Range, Range)> { - let mut bracket_ranges = None; - if let Some((lang, tree)) = self.language.as_ref().zip(self.syntax_tree()) { - let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut cursor = tree.root_node().walk(); - 'outer: loop { - let node = cursor.node(); - if node.child_count() >= 2 { - if let Some((first_child, last_child)) = - node.child(0).zip(node.child(node.child_count() - 1)) - { - for pair in &lang.config.bracket_pairs { - if pair.start == first_child.kind() && pair.end == last_child.kind() { - bracket_ranges = - Some((first_child.byte_range(), last_child.byte_range())); - } - } - } - } - - if !cursor.goto_first_child() { - break; - } - - while cursor.node().end_byte() < range.end { - if !cursor.goto_next_sibling() { - break 'outer; - } - } + let (lang, tree) = self.language.as_ref().zip(self.syntax_tree())?; + let open_capture_ix = lang.brackets_query.capture_index_for_name("open")?; + let close_capture_ix = lang.brackets_query.capture_index_for_name("close")?; + + // Find bracket pairs that *inclusively* contain the given range. + let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1; + let mut cursor = QueryCursorHandle::new(); + let matches = cursor.set_byte_range(range.start, range.end).matches( + &lang.brackets_query, + tree.root_node(), + TextProvider(&self.visible_text), + ); - if cursor.node().start_byte() > range.start { - break; - } - } - } - bracket_ranges + // Get the ranges of the innermost pair of brackets. + matches + .filter_map(|mat| { + let open = mat.nodes_for_capture_index(open_capture_ix).next()?; + let close = mat.nodes_for_capture_index(close_capture_ix).next()?; + Some((open.byte_range(), close.byte_range())) + }) + .min_by_key(|(open_range, close_range)| close_range.end - open_range.start) } fn diff(&self, new_text: Arc, ctx: &AppContext) -> Task { diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 30c7683e8bed2e50e6cf1faf7198f07b06fdcbc3..2381a22ad87c3441f71bfc3ac1bb495e79b11273 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1826,6 +1826,8 @@ impl BufferView { } pub fn move_to_enclosing_bracket(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt as _; + let buffer = self.buffer.read(ctx.as_ref()); let mut selections = self.selections(ctx.as_ref()).to_vec(); for selection in &mut selections { @@ -1833,12 +1835,13 @@ impl BufferView { if let Some((open_range, close_range)) = buffer.enclosing_bracket_ranges(selection_range.clone()) { + let close_range = close_range.to_inclusive(); let destination = if close_range.contains(&selection_range.start) && close_range.contains(&selection_range.end) { open_range.end } else { - close_range.start + *close_range.start() }; selection.start = buffer.anchor_before(destination); selection.end = selection.start.clone(); diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 29241623ebe66b80a564194951ed6b8a4375df0c..4b5fcd36abe8f6b84fda783e5d6f1e580f5db730 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -535,6 +535,7 @@ mod tests { }, grammar: grammar.clone(), highlight_query, + brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }); lang.set_theme(&theme); diff --git a/zed/src/editor/mod.rs b/zed/src/editor/mod.rs index d3c892367471b6a6a2aa46eabef36020853d5f2f..dc81ffbfbc8eab82a27496e0d4007462a593210c 100644 --- a/zed/src/editor/mod.rs +++ b/zed/src/editor/mod.rs @@ -9,7 +9,10 @@ pub use buffer_element::*; pub use buffer_view::*; pub use display_map::DisplayPoint; use display_map::*; -use std::{cmp, ops::Range}; +use std::{ + cmp, + ops::{Range, RangeInclusive}, +}; #[derive(Copy, Clone)] pub enum Bias { @@ -19,10 +22,15 @@ pub enum Bias { trait RangeExt { fn sorted(&self) -> Range; + fn to_inclusive(&self) -> RangeInclusive; } impl RangeExt for Range { fn sorted(&self) -> Self { cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone() } + + fn to_inclusive(&self) -> RangeInclusive { + self.start.clone()..=self.end.clone() + } } diff --git a/zed/src/language.rs b/zed/src/language.rs index 60576a374d5087682d1ad000cbf99f04c7f44c2f..9f977bf408fc4e402d884a58c3da47e10c8e5fb8 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -14,7 +14,6 @@ pub struct LanguageDir; pub struct LanguageConfig { pub name: String, pub path_suffixes: Vec, - pub bracket_pairs: Vec, } #[derive(Deserialize)] @@ -27,6 +26,7 @@ pub struct Language { pub config: LanguageConfig, pub grammar: Grammar, pub highlight_query: Query, + pub brackets_query: Query, pub theme_mapping: Mutex, } @@ -52,6 +52,7 @@ impl LanguageRegistry { config: rust_config, grammar, highlight_query: Self::load_query(grammar, "rust/highlights.scm"), + brackets_query: Self::load_query(grammar, "rust/brackets.scm"), theme_mapping: Mutex::new(ThemeMap::default()), }; @@ -106,6 +107,7 @@ mod tests { }, grammar, highlight_query: Query::new(grammar, "").unwrap(), + brackets_query: Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }), Arc::new(Language { @@ -116,6 +118,7 @@ mod tests { }, grammar, highlight_query: Query::new(grammar, "").unwrap(), + brackets_query: Query::new(grammar, "").unwrap(), theme_mapping: Default::default(), }), ], From 7339b9bce74eed2d401f0706e436ddf6ec32489a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 28 May 2021 14:25:30 -0700 Subject: [PATCH 56/56] Bump Tree-sitter Pass ranges to `set_byte_range`, `set_point_range` Co-Authored-By: Nathan Sobo --- Cargo.lock | 2 +- Cargo.toml | 2 +- zed/src/editor/buffer/mod.rs | 13 +++++-------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5387343c44f4a9eda062591dca2278fb4b6b913f..c5645c6f25154581d84cbfe088fa4c3eff984242 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.19.5" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=97dfee63257b5e92197399b381aa993514640adf#97dfee63257b5e92197399b381aa993514640adf" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=d72771a19f4143530b1cfd23808e344f1276e176#d72771a19f4143530b1cfd23808e344f1276e176" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 46866e31472c3369b07fa27aa8b0c8a3dad8a0fd..02ae3d61a4ddabf5361e3d34c1b85e581de468b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"] [patch.crates-io] async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "97dfee63257b5e92197399b381aa993514640adf" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "d72771a19f4143530b1cfd23808e344f1276e176" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 cocoa = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" } diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 1f064a049dcbc437afe4934f2dadf0369ab9ea45..65b8075a35d438ec42ab476a6e998307c977dfaa 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -99,8 +99,8 @@ impl DerefMut for QueryCursorHandle { impl Drop for QueryCursorHandle { fn drop(&mut self) { let mut cursor = self.0.take().unwrap(); - cursor.set_byte_range(0, usize::MAX); - cursor.set_point_range(Point::zero().into(), Point::MAX.into()); + cursor.set_byte_range(0..usize::MAX); + cursor.set_point_range(Point::zero().into()..Point::MAX.into()); QUERY_CURSORS.lock().push(cursor) } } @@ -734,7 +734,7 @@ impl Buffer { // Find bracket pairs that *inclusively* contain the given range. let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1; let mut cursor = QueryCursorHandle::new(); - let matches = cursor.set_byte_range(range.start, range.end).matches( + let matches = cursor.set_byte_range(range).matches( &lang.brackets_query, tree.root_node(), TextProvider(&self.visible_text), @@ -2142,12 +2142,11 @@ impl Snapshot { pub fn highlighted_text_for_range(&mut self, range: Range) -> HighlightedChunks { let chunks = self.text.chunks_in_range(range.clone()); if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { - let mut captures = self.query_cursor.captures( + let captures = self.query_cursor.set_byte_range(range.clone()).captures( &language.highlight_query, tree.root_node(), TextProvider(&self.text), ); - captures.set_byte_range(range.start, range.end); HighlightedChunks { range, @@ -2360,9 +2359,7 @@ impl<'a> HighlightedChunks<'a> { highlights.next_capture.take(); } } - highlights - .captures - .set_byte_range(self.range.start, self.range.end); + highlights.captures.set_byte_range(self.range.clone()); } }